/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // Description: An implementation of the SalLayout interface that uses the // Graphite engine. // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" // We need this to enable namespace support in libgrengine headers. #define GR_NAMESPACE // Enable lots of debug info #ifdef DEBUG //#define GRLAYOUT_DEBUG 1 //#undef NDEBUG #endif // Header files // // Standard Library #include #include #include #include #include #include // Platform #ifdef WNT #include #include #endif #include #include #include #include // Graphite Libraries (must be after vcl headers on windows) #include "pregraphitestl.h" #include #include #include #include #include #include "postgraphitestl.h" #include #include #include "graphite_textsrc.hxx" // Module private type definitions and forward declarations. // // Module private names. // #ifdef GRLAYOUT_DEBUG FILE * grLogFile = NULL; FILE * grLog() { #ifdef WNT std::string logFileName(getenv("TEMP")); logFileName.append("\\graphitelayout.log"); if (grLogFile == NULL) grLogFile = fopen(logFileName.c_str(),"w"); else fflush(grLogFile); return grLogFile; #else return stdout; #endif } #endif #ifdef GRCACHE #include #endif namespace { typedef sil_std::pair glyph_range_t; typedef sil_std::pair glyph_set_range_t; inline long round(const float n) { return long(n + (n < 0 ? -0.5 : 0.5)); } template inline bool in_range(const T i, const T b, const T e) { return !(b > i) && i < e; } template inline bool is_subrange(const T sb, const T se, const T b, const T e) { return !(b > sb || se > e); } template inline bool is_subrange(const std::pair &s, const T b, const T e) { return is_subrange(s.first, s.second, b, e); } int findSameDirLimit(const xub_Unicode* buffer, int charCount, bool rtl) { UErrorCode status = U_ZERO_ERROR; UBiDi *ubidi = ubidi_openSized(charCount, 0, &status); int limit = 0; ubidi_setPara(ubidi, reinterpret_cast(buffer), charCount, (rtl)?UBIDI_DEFAULT_RTL:UBIDI_DEFAULT_LTR, NULL, &status); UBiDiLevel level = 0; ubidi_getLogicalRun(ubidi, 0, &limit, &level); ubidi_close(ubidi); if ((rtl && !(level & 1)) || (!rtl && (level & 1))) { limit = 0; } return limit; } } // namespace // Impementation of the GraphiteLayout::Glyphs container class. // This is an extended vector class with methods added to enable // o Correctly filling with glyphs. // o Querying clustering relationships. // o manipulations that affect neighouring glyphs. const int GraphiteLayout::EXTRA_CONTEXT_LENGTH = 10; #ifdef GRCACHE GraphiteCacheHandler GraphiteCacheHandler::instance; #endif // The Graphite glyph stream is really a sequence of glyph attachment trees // each rooted at a non-attached base glyph. fill_from walks the glyph stream // find each non-attached base glyph and calls append to record them as a // sequence of clusters. void GraphiteLayout::Glyphs::fill_from(gr::Segment & rSegment, ImplLayoutArgs &rArgs, bool bRtl, long &rWidth, float fScaling, std::vector & rChar2Base, std::vector & rGlyph2Char, std::vector & rCharDxs) { // Create a glyph item for each of the glyph and append it to the base class glyph list. typedef sil_std::pair< gr::GlyphSetIterator, gr::GlyphSetIterator > GrGlyphSet; int nChar = rArgs.mnEndCharPos - rArgs.mnMinCharPos; glyph_range_t iGlyphs = rSegment.glyphs(); int nGlyphs = iGlyphs.second - iGlyphs.first; gr::GlyphIterator prevBase = iGlyphs.second; float fMinX = rSegment.advanceWidth(); float fMaxX = 0.0f; rGlyph2Char.assign(nGlyphs, -1); long nDxOffset = 0; int nGlyphIndex = (bRtl)? (nGlyphs - 1) : 0; // OOo always expects the glyphs in ltr order int nDelta = (bRtl)? -1 : 1; int nLastGlyph = (bRtl)? nGlyphs - 1: 0; int nNextChar = (bRtl)? (rSegment.stopCharacter() - 1) : rSegment.startCharacter();//rArgs.mnMinCharPos; // current glyph number (Graphite glyphs) //int currGlyph = 0; int nFirstCharInCluster = nNextChar; int nFirstGlyphInCluster = nLastGlyph; // ltr first char in cluster is lowest, same is true for rtl // ltr first glyph in cluster is lowest, rtl first glyph is highest // loop over the glyphs determining which characters are linked to them gr::GlyphIterator gi; for (gi = iGlyphs.first + nGlyphIndex; nGlyphIndex >= 0 && nGlyphIndex < nGlyphs; nGlyphIndex+= nDelta, gi = iGlyphs.first + nGlyphIndex) { gr::GlyphInfo info = (*gi); #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Glyph %d %f,%f\n", (int)info.logicalIndex(), info.origin(), info.yOffset()); #endif // the last character associated with this glyph is after // our current cluster buffer position if ((bRtl && ((signed)info.firstChar() <= nNextChar)) || (!bRtl && ((signed)info.lastChar() >= nNextChar))) { if ((bRtl && nGlyphIndex < nLastGlyph) || (!bRtl && nGlyphIndex > nLastGlyph)) { // this glyph is after the previous one left->right // if insertion is allowed before it then we are in a // new cluster int nAttachedBase = (*(info.attachedClusterBase())).logicalIndex(); if (!info.isAttached() || !in_range(nAttachedBase, nFirstGlyphInCluster, nGlyphIndex)) { if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) && nFirstGlyphInCluster != nGlyphIndex) { std::pair aBounds = appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster, nNextChar, nFirstGlyphInCluster, nGlyphIndex, fScaling, rChar2Base, rGlyph2Char, rCharDxs, nDxOffset); fMinX = std::min(aBounds.first, fMinX); fMaxX = std::max(aBounds.second, fMaxX); } nFirstCharInCluster = (bRtl)? info.lastChar() : info.firstChar(); nFirstGlyphInCluster = nGlyphIndex; } nLastGlyph = (bRtl)? std::min(nGlyphIndex, nAttachedBase) : std::max(nGlyphIndex, nAttachedBase); } // loop over chacters associated with this glyph and characters // between nextChar and the last character associated with this glyph // giving them the current cluster id. This allows for character /glyph // order reversal. // For each character we do a reverse glyph id look up // and store the glyph id with the highest logical index in nLastGlyph while ((bRtl && ((signed)info.firstChar() <= nNextChar)) || (!bRtl && (signed)info.lastChar() >= nNextChar)) { GrGlyphSet charGlyphs = rSegment.charToGlyphs(nNextChar); nNextChar += nDelta; gr::GlyphSetIterator gj = charGlyphs.first; while (gj != charGlyphs.second) { nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*gj).logicalIndex()) : max(nLastGlyph, (signed)(*gj).logicalIndex()); ++gj; } } // Loop over attached glyphs and make sure they are all in the cluster since you // can have glyphs attached with another base glyph in between glyph_set_range_t iAttached = info.attachedClusterGlyphs(); for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi) { nLastGlyph = (bRtl)? min(nLastGlyph, (signed)(*agi).logicalIndex()) : max(nLastGlyph, (signed)(*agi).logicalIndex()); } // if this is a rtl attached glyph, then we need to include its // base in the cluster, which will have a lower graphite index if (bRtl) { if ((signed)info.attachedClusterBase()->logicalIndex() < nLastGlyph) { nLastGlyph = info.attachedClusterBase()->logicalIndex(); } } } // it is possible for the lastChar to be after nextChar and // firstChar to be before the nFirstCharInCluster in rare // circumstances e.g. Myanmar word for cemetery if ((bRtl && ((signed)info.lastChar() > nFirstCharInCluster)) || (!bRtl && ((signed)info.firstChar() < nFirstCharInCluster))) { nFirstCharInCluster = info.firstChar(); } } // process last cluster if (in_range(nFirstCharInCluster, rArgs.mnMinCharPos, rArgs.mnEndCharPos) && nFirstGlyphInCluster != nGlyphIndex) { std::pair aBounds = appendCluster(rSegment, rArgs, bRtl, nFirstCharInCluster, nNextChar, nFirstGlyphInCluster, nGlyphIndex, fScaling, rChar2Base, rGlyph2Char, rCharDxs, nDxOffset); fMinX = std::min(aBounds.first, fMinX); fMaxX = std::max(aBounds.second, fMaxX); } long nXOffset = round(fMinX * fScaling); rWidth = round(fMaxX * fScaling) - nXOffset + nDxOffset; if (rWidth < 0) { // This can happen when there was no base inside the range rWidth = 0; } // fill up non-base char dx with cluster widths from previous base glyph if (bRtl) { if (rCharDxs[nChar-1] == -1) rCharDxs[nChar-1] = 0; else rCharDxs[nChar-1] -= nXOffset; for (int i = nChar - 2; i >= 0; i--) { if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i+1]; else rCharDxs[i] -= nXOffset; } } else { if (rCharDxs[0] == -1) rCharDxs[0] = 0; else rCharDxs[0] -= nXOffset; for (int i = 1; i < nChar; i++) { if (rCharDxs[i] == -1) rCharDxs[i] = rCharDxs[i-1]; else rCharDxs[i] -= nXOffset; } } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Glyphs xOff%ld dropDx%ld w%ld\n", nXOffset, nDxOffset, rWidth); #endif // remove offset due to context if there is one if (nXOffset != 0) { for (size_t i = 0; i < size(); i++) (*this)[i].maLinearPos.X() -= nXOffset; } } std::pair GraphiteLayout::Glyphs::appendCluster(gr::Segment & rSeg, ImplLayoutArgs & rArgs, bool bRtl, int nFirstCharInCluster, int nNextChar, int nFirstGlyphInCluster, int nNextGlyph, float fScaling, std::vector & rChar2Base, std::vector & rGlyph2Char, std::vector & rCharDxs, long & rDXOffset) { glyph_range_t iGlyphs = rSeg.glyphs(); int nGlyphs = iGlyphs.second - iGlyphs.first; int nDelta = (bRtl)? -1 : 1; gr::GlyphInfo aFirstGlyph = *(iGlyphs.first + nFirstGlyphInCluster); std::pair aBounds; aBounds.first = aFirstGlyph.origin(); aBounds.second = aFirstGlyph.origin(); // before we add the glyphs to this vector, we record the // glyph's index in the vector (which is not the same as // the Segment's glyph index!) assert(size() < rGlyph2Char.size()); rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] = size(); rGlyph2Char[size()] = nFirstCharInCluster; bool bBaseGlyph = true; for (int j = nFirstGlyphInCluster; j != nNextGlyph; j += nDelta) { long nNextOrigin; float fNextOrigin; gr::GlyphInfo aGlyph = *(iGlyphs.first + j); if (j + nDelta >= nGlyphs || j + nDelta < 0) // at rhs ltr,rtl { fNextOrigin = rSeg.advanceWidth(); nNextOrigin = round(rSeg.advanceWidth() * fScaling + rDXOffset); aBounds.second = std::max(rSeg.advanceWidth(), aBounds.second); } else { gr::GlyphInfo aNextGlyph = *(iGlyphs.first + j + nDelta); fNextOrigin = std::max(aNextGlyph.attachedClusterBase()->origin(), aNextGlyph.origin()); aBounds.second = std::max(fNextOrigin, aBounds.second); nNextOrigin = round(fNextOrigin * fScaling + rDXOffset); } aBounds.first = std::min(aGlyph.origin(), aBounds.first); if ((signed)aGlyph.firstChar() < rArgs.mnEndCharPos && (signed)aGlyph.firstChar() >= rArgs.mnMinCharPos) { rCharDxs[aGlyph.firstChar()-rArgs.mnMinCharPos] = nNextOrigin; } if ((signed)aGlyph.attachedClusterBase()->logicalIndex() == j) { append(rSeg, rArgs, aGlyph, fNextOrigin, fScaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, bBaseGlyph); bBaseGlyph = false; } } // from the point of view of the dx array, the xpos is // the origin of the first glyph of the next cluster ltr // rtl it is the origin of the 1st glyph of the cluster long nXPos = (bRtl)? round(aFirstGlyph.attachedClusterBase()->origin() * fScaling) + rDXOffset : round(aBounds.second * fScaling) + rDXOffset; // force the last char in range to have the width of the cluster if (bRtl) { for (int n = nNextChar + 1; n <= nFirstCharInCluster; n++) { if ((n < rArgs.mnEndCharPos) && (n >= rArgs.mnMinCharPos)) rCharDxs[n-rArgs.mnMinCharPos] = nXPos; } } else { for (int n = nNextChar - 1; n >= nFirstCharInCluster; n--) { if (n < rArgs.mnEndCharPos && n >= rArgs.mnMinCharPos) rCharDxs[n-rArgs.mnMinCharPos] = nXPos; } } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Cluster g[%d-%d) c[%d-%d)%x x%ld y%f\n", nFirstGlyphInCluster, nNextGlyph, nFirstCharInCluster, nNextChar, rArgs.mpStr[nFirstCharInCluster], nXPos, aFirstGlyph.yOffset()); #endif return aBounds; } // append walks an attachment tree, flattening it, and converting it into a // sequence of GlyphItem objects which we can later manipulate. void GraphiteLayout::Glyphs::append(gr::Segment &segment, ImplLayoutArgs &args, gr::GlyphInfo & gi, float nextGlyphOrigin, float scaling, std::vector & rChar2Base, std::vector & rGlyph2Char, std::vector & rCharDxs, long & rDXOffset, bool bIsBase) { float nextOrigin = nextGlyphOrigin; int firstChar = std::min(gi.firstChar(), gi.lastChar()); assert(size() < rGlyph2Char.size()); if (!bIsBase) rGlyph2Char[size()] = firstChar; // is the next glyph attached or in the next cluster? glyph_set_range_t iAttached = gi.attachedClusterGlyphs(); if (iAttached.first != iAttached.second) { nextOrigin = iAttached.first->origin(); } long glyphId = gi.glyphID(); long deltaOffset = 0; int glyphWidth = round(nextOrigin * scaling) - round(gi.origin() * scaling); #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"c%d g%d gWidth%d x%f ", firstChar, (int)gi.logicalIndex(), glyphWidth, nextOrigin); #endif if (glyphId == 0) { args.NeedFallback( firstChar, gr::RightToLeftDir(gr::DirCode(gi.directionality()))); if( (SAL_LAYOUT_FOR_FALLBACK & args.mnFlags )) { glyphId = GF_DROPPED; deltaOffset -= glyphWidth; glyphWidth = 0; } } else if(args.mnFlags & SAL_LAYOUT_FOR_FALLBACK) { #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"fallback c%d %x in run %d\n", firstChar, args.mpStr[firstChar], args.maRuns.PosIsInAnyRun(firstChar)); #endif // glyphs that aren't requested for fallback will be taken from base // layout, so mark them as dropped (should this wait until Simplify(false) is called?) if (!args.maRuns.PosIsInAnyRun(firstChar) && in_range(firstChar, args.mnMinCharPos, args.mnEndCharPos)) { glyphId = GF_DROPPED; deltaOffset -= glyphWidth; glyphWidth = 0; } } // append this glyph. long nGlyphFlags = bIsBase ? 0 : GlyphItem::IS_IN_CLUSTER; // directionality seems to be unreliable //nGlyphFlags |= gr::RightToLeftDir(gr::DirCode(gi.attachedClusterBase()->directionality())) ? GlyphItem::IS_RTL_GLYPH : 0; nGlyphFlags |= (gi.directionLevel() & 0x1)? GlyphItem::IS_RTL_GLYPH : 0; GlyphItem aGlyphItem(size(),//gi.logicalIndex(), glyphId, Point(round(gi.origin() * scaling + rDXOffset), round((-gi.yOffset() * scaling) - segment.AscentOffset()* scaling)), nGlyphFlags, glyphWidth); aGlyphItem.mnOrigWidth = round(gi.advanceWidth() * scaling); push_back(aGlyphItem); // update the offset if this glyph was dropped rDXOffset += deltaOffset; // Recursively apply append all the attached glyphs. for (gr::GlyphSetIterator agi = iAttached.first; agi != iAttached.second; ++agi) { if (agi + 1 == iAttached.second) append(segment, args, *agi, nextGlyphOrigin, scaling, rChar2Base, rGlyph2Char,rCharDxs, rDXOffset, false); else append(segment, args, *agi, (agi + 1)->origin(), scaling, rChar2Base, rGlyph2Char, rCharDxs, rDXOffset, false); } } // // An implementation of the SalLayout interface to enable Graphite enabled fonts to be used. // GraphiteLayout::GraphiteLayout(const gr::Font & font, const grutils::GrFeatureParser * pFeatures) throw() : mpTextSrc(0), mrFont(font), mnWidth(0), mfScaling(1.0), mpFeatures(pFeatures) { // Line settings can have subtle affects on space handling // since we don't really know whether it is the end of a line or just a run // in the middle, it is hard to know what to set them to. // If true, it can cause end of line spaces to be hidden e.g. Doulos SIL maLayout.setStartOfLine(false); maLayout.setEndOfLine(false); // maLayout.setDumbFallback(false); // trailing ws doesn't seem to always take affect if end of line is true maLayout.setTrailingWs(gr::ktwshAll); #ifdef GRLAYOUT_DEBUG gr::ScriptDirCode aDirCode = font.getSupportedScriptDirections(); fprintf(grLog(),"GraphiteLayout scripts %x %lx\n", aDirCode, long(this)); #endif } GraphiteLayout::~GraphiteLayout() throw() { clear(); // the features are owned by the platform layers mpFeatures = NULL; } void GraphiteLayout::clear() { // Destroy the segment and text source from any previous invocation of // LayoutText mvGlyphs.clear(); mvCharDxs.clear(); mvChar2BaseGlyph.clear(); mvGlyph2Char.clear(); #ifndef GRCACHE delete mpTextSrc; #endif // Reset the state to the empty state. mpTextSrc=0; mnWidth = 0; // Don't reset the scaling, because it is set before LayoutText } // This method shouldn't be called on windows, since it needs the dc reset bool GraphiteLayout::LayoutText(ImplLayoutArgs & rArgs) { #ifdef GRCACHE GrSegRecord * pSegRecord = NULL; gr::Segment * pSegment = CreateSegment(rArgs, &pSegRecord); if (!pSegment) return false; // layout the glyphs as required by OpenOffice bool success = LayoutGlyphs(rArgs, pSegment, pSegRecord); if (pSegRecord) pSegRecord->unlock(); else delete pSegment; #else gr::Segment * pSegment = CreateSegment(rArgs); bool success = LayoutGlyphs(rArgs, pSegment); delete pSegment; #endif return success; } #ifdef GRCACHE class GrFontHasher : public gr::Font { public: GrFontHasher(const gr::Font & aFont) : gr::Font(aFont), mrRealFont(const_cast(aFont)) {}; ~GrFontHasher(){}; virtual bool bold() { return mrRealFont.bold(); }; virtual bool italic() { return mrRealFont.italic(); }; virtual float ascent() { return mrRealFont.ascent(); }; virtual float descent() { return mrRealFont.descent(); }; virtual float height() { return mrRealFont.height(); }; virtual gr::Font* copyThis() { return mrRealFont.copyThis(); }; virtual unsigned int getDPIx() { return mrRealFont.getDPIx(); }; virtual unsigned int getDPIy() { return mrRealFont.getDPIy(); }; virtual const void* getTable(gr::fontTableId32 nId, size_t* nSize) { return mrRealFont.getTable(nId,nSize); } virtual void getFontMetrics(float*pA, float*pB, float*pC) { mrRealFont.getFontMetrics(pA,pB,pC); }; sal_Int32 hashCode(const grutils::GrFeatureParser * mpFeatures) { // is this sufficient? sil_std::wstring aFace; bool bBold; bool bItalic; UniqueCacheInfo(aFace, bBold, bItalic); sal_Unicode uName[32]; // max length used in gr::Font // Note: graphite stores font names as UTF-16 even if wchar_t is 32bit // this conversion should be OK. for (size_t i = 0; i < aFace.size() && i < 32; i++) { uName[i] = aFace[i]; } size_t iSize = aFace.size(); if (0 == iSize) return 0; sal_Int32 hash = rtl_ustr_hashCode_WithLength(uName, iSize); hash ^= static_cast(height()); hash |= (bBold)? 0x1000000 : 0; hash |= (bItalic)? 0x2000000 : 0; if (mpFeatures) hash ^= mpFeatures->hashCode(); #ifdef GRLAYOUT_DEBUG fprintf(grLog(), "font hash %x size %f\n", (int)hash, height()); #endif return hash; }; private: gr::Font & mrRealFont; }; #endif #ifdef GRCACHE gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs, GrSegRecord ** pSegRecord) #else gr::Segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs) #endif { assert(rArgs.mnLength >= 0); gr::Segment * pSegment = NULL; // Set the SalLayouts values to be the inital ones. SalLayout::AdjustLayout(rArgs); // TODO check if this is needed if (mnUnitsPerPixel > 1) mfScaling = 1.0f / mnUnitsPerPixel; // Clear out any previous buffers clear(); bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; try { // Don't set RTL if font doesn't support it otherwise it forces rtl on // everything if (bRtl && (mrFont.getSupportedScriptDirections() & gr::kfsdcHorizRtl)) maLayout.setRightToLeft(bRtl); #ifdef GRCACHE GrFontHasher hasher(mrFont); sal_Int32 aFontHash = hasher.hashCode(mpFeatures); GraphiteSegmentCache * pCache = (GraphiteCacheHandler::instance).getCache(aFontHash); if (pCache) { *pSegRecord = pCache->getSegment(rArgs, bRtl); if (*pSegRecord) { pSegment = (*pSegRecord)->getSegment(); mpTextSrc = (*pSegRecord)->getTextSrc(); maLayout.setRightToLeft((*pSegRecord)->isRtl()); if (rArgs.mpStr != mpTextSrc->getLayoutArgs().mpStr || rArgs.mnMinCharPos != mpTextSrc->getLayoutArgs().mnMinCharPos || rArgs.mnEndCharPos != mpTextSrc->getLayoutArgs().mnEndCharPos || (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) ) { (*pSegRecord)->clearVectors(); } mpTextSrc->switchLayoutArgs(rArgs); return pSegment; } } #endif // Context is often needed beyond the specified end, however, we don't // want it if there has been a direction change, since it is hard // to tell between reordering within one direction and multi-directional // text. const int segCharLimit = min(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH); int limit = rArgs.mnEndCharPos; if (segCharLimit > limit) { limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos, segCharLimit - rArgs.mnEndCharPos, bRtl); } // Create a new TextSource object for the engine. mpTextSrc = new TextSourceAdaptor(rArgs, limit); if (mpFeatures) mpTextSrc->setFeatures(mpFeatures); pSegment = new gr::RangeSegment((gr::Font *)&mrFont, mpTextSrc, &maLayout, mnMinCharPos, limit); if (pSegment != NULL) { #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Gr::LayoutText %d-%d, context %d,len%d rtl%d/%d scaling %f\n", rArgs.mnMinCharPos, rArgs.mnEndCharPos, limit, rArgs.mnLength, maLayout.rightToLeft(), pSegment->rightToLeft(), mfScaling); #endif #ifdef GRCACHE // on a new segment rightToLeft should be correct *pSegRecord = pCache->cacheSegment(mpTextSrc, pSegment, pSegment->rightToLeft()); #endif } else { clear(); return NULL; } } catch (...) { clear(); // destroy the text source and any partially built segments. return NULL; } return pSegment; } #ifdef GRCACHE bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment, GrSegRecord * pSegRecord) #else bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr::Segment * pSegment) #endif { #ifdef GRCACHE #ifdef GRCACHE_REUSE_VECTORS // if we have an exact match, then we can reuse the glyph vectors from before if (pSegRecord && (pSegRecord->glyphs().size() > 0) && (pSegRecord->fontScale() == mfScaling) && !(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) ) { mnWidth = pSegRecord->width(); mvGlyphs = pSegRecord->glyphs(); mvCharDxs = pSegRecord->charDxs(); mvChar2BaseGlyph = pSegRecord->char2BaseGlyph(); mvGlyph2Char = pSegRecord->glyph2Char(); return true; } #endif #endif // Calculate the initial character dxs. mvCharDxs.assign(mnEndCharPos - mnMinCharPos, -1); mvChar2BaseGlyph.assign(mnEndCharPos - mnMinCharPos, -1); mnWidth = 0; if (mvCharDxs.size() > 0) { // Discover all the clusters. try { // Note: we use the layout rightToLeft() because in cached segments // rightToLeft() may no longer be valid if the engine has been run // ltr since the segment was created. #ifdef GRCACHE bool bRtl = pSegRecord? pSegRecord->isRtl() : pSegment->rightToLeft(); #else bool bRtl = pSegment->rightToLeft(); #endif mvGlyphs.fill_from(*pSegment, rArgs, bRtl, mnWidth, mfScaling, mvChar2BaseGlyph, mvGlyph2Char, mvCharDxs); if (bRtl) { // not needed for adjacent differences, but for mouse clicks to char std::transform(mvCharDxs.begin(), mvCharDxs.end(), mvCharDxs.begin(), std::bind1st(std::minus(), mnWidth)); // fixup last dx to ensure it always equals the width mvCharDxs[mvCharDxs.size() - 1] = mnWidth; } #ifdef GRCACHE #ifdef GRCACHE_REUSE_VECTORS if (pSegRecord && rArgs.maReruns.IsEmpty() && !(SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags)) { pSegRecord->setGlyphVectors(mnWidth, mvGlyphs, mvCharDxs, mvChar2BaseGlyph, mvGlyph2Char, mfScaling); } #endif #endif } catch (std::exception e) { #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"LayoutGlyphs failed %s\n", e.what()); #endif return false; } catch (...) { #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"LayoutGlyphs failed with exception"); #endif return false; } } else { mnWidth = 0; } return true; } int GraphiteLayout::GetTextBreak(long maxmnWidth, long char_extra, int factor) const { // Adjust maxmnWidth so FindNextBreakPoint returns a sensible answer. maxmnWidth -= (mnEndCharPos-mnMinCharPos-1)*char_extra; // extra character spacing. maxmnWidth /= factor; // scaling factor. // Ask the segment for the nearest whole letter break for the width. //float width; float targetWidth = maxmnWidth/mfScaling; // return quickly if this segment is narrower than the target width // (sometimes graphite doesn't seem to realise this!) if (targetWidth > mnWidth) return STRING_LEN; //int nBreak = mpSegment->findNextBreakPoint(mnMinCharPos, // gr::klbWordBreak, gr::klbLetterBreak, targetWidth, &width); // LineFillSegment seems to give better results that findNextBreakPoint // though it may be slower gr::LayoutEnvironment aLE; gr::LineFillSegment lineSeg(const_cast(&mrFont), mpTextSrc, &aLE, mnMinCharPos, mpTextSrc->getContextLength(), targetWidth); int nBreak = lineSeg.stopCharacter(); if (nBreak > mnEndCharPos) nBreak = STRING_LEN; else if (nBreak < mnMinCharPos) nBreak = mnMinCharPos; return nBreak; } long GraphiteLayout::FillDXArray( sal_Int32* pDXArray ) const { if (mnEndCharPos == mnMinCharPos) // Then we must be zero width! return 0; if (pDXArray) { for (size_t i = 0; i < mvCharDxs.size(); i++) { assert((mvChar2BaseGlyph[i] >= -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size())); if (mvChar2BaseGlyph[i] != -1 && mvGlyphs[mvChar2BaseGlyph[i]].mnGlyphIndex == GF_DROPPED) { // when used in MultiSalLayout::GetTextBreak dropped glyphs // must have zero width pDXArray[i] = 0; } else { pDXArray[i] = mvCharDxs[i]; if (i > 0) pDXArray[i] -= mvCharDxs[i-1]; } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"%d,%d,%ld ", (int)i, (int)mvCharDxs[i], pDXArray[i]); #endif } //std::adjacent_difference(mvCharDxs.begin(), mvCharDxs.end(), pDXArray); //for (size_t i = 0; i < mvCharDxs.size(); i++) // fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]); //fprintf(grLog(),"FillDX %ld,%d\n", mnWidth, std::accumulate(pDXArray, pDXArray + mvCharDxs.size(), 0)); } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"FillDXArray %d-%d,%d=%ld\n", mnMinCharPos, mnEndCharPos, (int)mpTextSrc->getLength(), mnWidth); #endif return mnWidth; } void GraphiteLayout::AdjustLayout(ImplLayoutArgs& rArgs) { SalLayout::AdjustLayout(rArgs); if(rArgs.mpDXArray) { std::vector vDeltaWidths(mvGlyphs.size(), 0); ApplyDXArray(rArgs, vDeltaWidths); if( (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL) && !(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) ) { // check if this is a kashida script bool bKashidaScript = false; for (int i = rArgs.mnMinCharPos; i < rArgs.mnEndCharPos; i++) { UErrorCode aStatus = U_ZERO_ERROR; UScriptCode scriptCode = uscript_getScript(rArgs.mpStr[i], &aStatus); if (scriptCode == USCRIPT_ARABIC || scriptCode == USCRIPT_SYRIAC) { bKashidaScript = true; break; } } int nKashidaWidth = 0; int nKashidaIndex = getKashidaGlyph(nKashidaWidth); if( nKashidaIndex != 0 && bKashidaScript) { kashidaJustify( vDeltaWidths, nKashidaIndex, nKashidaWidth ); } } } } void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector & rDeltaWidth) { const size_t nChars = args.mnEndCharPos - args.mnMinCharPos; if (nChars == 0) return; #ifdef GRLAYOUT_DEBUG for (size_t iDx = 0; iDx < mvCharDxs.size(); iDx++) fprintf(grLog(),"%d,%d,%ld ", (int)iDx, (int)mvCharDxs[iDx], args.mpDXArray[iDx]); fprintf(grLog(),"ApplyDx\n"); #endif bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; int nXOffset = 0; if (bRtl) { nXOffset = args.mpDXArray[nChars - 1] - mvCharDxs[nChars - 1]; } int nPrevClusterGlyph = (bRtl)? mvGlyphs.size() : -1; int nPrevClusterLastChar = -1; for (size_t i = 0; i < nChars; i++) { if (mvChar2BaseGlyph[i] > -1 && mvChar2BaseGlyph[i] != nPrevClusterGlyph) { assert((mvChar2BaseGlyph[i] > -1) && (mvChar2BaseGlyph[i] < (signed)mvGlyphs.size())); GlyphItem & gi = mvGlyphs[mvChar2BaseGlyph[i]]; if (!gi.IsClusterStart()) continue; // find last glyph of this cluster size_t j = i + 1; int nLastChar = i; int nLastGlyph = mvChar2BaseGlyph[i]; for (; j < nChars; j++) { assert((mvChar2BaseGlyph[j] >= -1) && (mvChar2BaseGlyph[j] < (signed)mvGlyphs.size())); if (mvChar2BaseGlyph[j] != -1 && mvGlyphs[mvChar2BaseGlyph[j]].IsClusterStart()) { nLastGlyph = mvChar2BaseGlyph[j] + ((bRtl)? 1 : -1); nLastChar = j - 1; break; } } if (nLastGlyph < 0) { nLastGlyph = mvChar2BaseGlyph[i]; } // Its harder to find the last glyph rtl, since the first of // cluster is still on the left so we need to search towards // the previous cluster to the right if (bRtl) { nLastGlyph = mvChar2BaseGlyph[i]; while (nLastGlyph + 1 < (signed)mvGlyphs.size() && !mvGlyphs[nLastGlyph+1].IsClusterStart()) { ++nLastGlyph; } } if (j == nChars) { nLastChar = nChars - 1; if (!bRtl) nLastGlyph = mvGlyphs.size() - 1; } assert((nLastChar > -1) && (nLastChar < (signed)nChars)); long nNewClusterWidth = args.mpDXArray[nLastChar]; long nOrigClusterWidth = mvCharDxs[nLastChar]; long nDGlyphOrigin = 0; if (nPrevClusterLastChar > - 1) { assert(nPrevClusterLastChar < (signed)nChars); nNewClusterWidth -= args.mpDXArray[nPrevClusterLastChar]; nOrigClusterWidth -= mvCharDxs[nPrevClusterLastChar]; nDGlyphOrigin = args.mpDXArray[nPrevClusterLastChar] - mvCharDxs[nPrevClusterLastChar]; } long nDWidth = nNewClusterWidth - nOrigClusterWidth; #ifdef GRLAYOUT_DEBUG fprintf(grLog(), "c%d last glyph %d/%d\n", i, nLastGlyph, mvGlyphs.size()); #endif assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size())); mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; if (gi.mnGlyphIndex != GF_DROPPED) mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; else nDGlyphOrigin += nDWidth; // update glyph positions if (bRtl) { for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++) { assert((n > - 1) && (n < (signed)mvGlyphs.size())); mvGlyphs[n].maLinearPos.X() += -nDGlyphOrigin + nXOffset; } } else { for (int n = mvChar2BaseGlyph[i]; n <= nLastGlyph; n++) { assert((n > - 1) && (n < (signed)mvGlyphs.size())); mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + nXOffset; } } rDeltaWidth[mvChar2BaseGlyph[i]] = nDWidth; #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, mvChar2BaseGlyph[i], nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[mvChar2BaseGlyph[i]].maLinearPos.X()); #endif nPrevClusterGlyph = mvChar2BaseGlyph[i]; nPrevClusterLastChar = nLastChar; i = nLastChar; } } // Update the dx vector with the new values. std::copy(args.mpDXArray, args.mpDXArray + nChars, mvCharDxs.begin() + (args.mnMinCharPos - mnMinCharPos)); #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"ApplyDx %ld(%ld)\n", args.mpDXArray[nChars - 1], mnWidth); #endif mnWidth = args.mpDXArray[nChars - 1]; } void GraphiteLayout::kashidaJustify(std::vector& rDeltaWidths, sal_GlyphId nKashidaIndex, int nKashidaWidth) { // skip if the kashida glyph in the font looks suspicious if( nKashidaWidth <= 0 ) return; // calculate max number of needed kashidas Glyphs::iterator i = mvGlyphs.begin(); int nKashidaCount = 0; int nOrigGlyphIndex = -1; int nGlyphIndex = -1; while (i != mvGlyphs.end()) { nOrigGlyphIndex++; nGlyphIndex++; // only inject kashidas in RTL contexts if( !(*i).IsRTLGlyph() ) { ++i; continue; } // no kashida-injection for blank justified expansion either if( IsSpacingGlyph( (*i).mnGlyphIndex ) ) { ++i; continue; } // calculate gap, ignore if too small int nGapWidth = rDeltaWidths[nOrigGlyphIndex];; // worst case is one kashida even for mini-gaps if( 3 * nGapWidth < nKashidaWidth ) { ++i; continue; } nKashidaCount = 1 + (nGapWidth / nKashidaWidth); #ifdef GRLAYOUT_DEBUG printf("inserting %d kashidas at %ld\n", nKashidaCount, (*i).mnGlyphIndex); #endif GlyphItem glyphItem = *i; Point aPos(0, 0); aPos.X() = (*i).maLinearPos.X(); GlyphItem newGi(glyphItem.mnCharPos, nKashidaIndex, aPos, GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth); mvGlyphs.reserve(mvGlyphs.size() + nKashidaCount); i = mvGlyphs.begin() + nGlyphIndex; mvGlyphs.insert(i, nKashidaCount, newGi); i = mvGlyphs.begin() + nGlyphIndex; nGlyphIndex += nKashidaCount; // now fix up the kashida positions for (int j = 0; j < nKashidaCount; j++) { (*(i)).maLinearPos.X() -= nGapWidth; nGapWidth -= nKashidaWidth; i++; } // fixup rightmost kashida for gap remainder if( nGapWidth < 0 ) { if( nKashidaCount <= 1 ) nGapWidth /= 2; // for small gap move kashida to middle (*(i-1)).mnNewWidth += nGapWidth; // adjust kashida width to gap width (*(i-1)).maLinearPos.X() += nGapWidth; } (*i).mnNewWidth = (*i).mnOrigWidth; ++i; } } void GraphiteLayout::GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const { // For each character except the last discover the caret positions // immediatly before and after that character. // This is used for underlines in the GUI amongst other things. // It may be used from MultiSalLayout, in which case it must take into account // glyphs that have been moved. std::fill(pCaretXArray, pCaretXArray + nArraySize, -1); // the layout method doesn't modify the layout even though it isn't // const in the interface bool bRtl = const_cast(this)->maLayout.rightToLeft(); int prevBase = -1; long prevClusterWidth = 0; for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast(mvCharDxs.size()); ++nCharSlot, i+=2) { if (mvChar2BaseGlyph[nCharSlot] != -1) { assert((mvChar2BaseGlyph[nCharSlot] > -1) && (mvChar2BaseGlyph[nCharSlot] < (signed)mvGlyphs.size())); GlyphItem gi = mvGlyphs[mvChar2BaseGlyph[nCharSlot]]; if (gi.mnGlyphIndex == GF_DROPPED) { continue; } int nCluster = mvChar2BaseGlyph[nCharSlot]; long origClusterWidth = gi.mnNewWidth; long nMin = gi.maLinearPos.X(); long nMax = gi.maLinearPos.X() + gi.mnNewWidth; // attached glyphs are always stored after their base rtl or ltr while (++nCluster < static_cast(mvGlyphs.size()) && !mvGlyphs[nCluster].IsClusterStart()) { origClusterWidth += mvGlyphs[nCluster].mnNewWidth; if (mvGlyph2Char[nCluster] == nCharSlot) { nMin = std::min(nMin, mvGlyphs[nCluster].maLinearPos.X()); nMax = std::min(nMax, mvGlyphs[nCluster].maLinearPos.X() + mvGlyphs[nCluster].mnNewWidth); } } if (bRtl) { pCaretXArray[i+1] = nMin; pCaretXArray[i] = nMax; } else { pCaretXArray[i] = nMin; pCaretXArray[i+1] = nMax; } prevBase = mvChar2BaseGlyph[nCharSlot]; prevClusterWidth = origClusterWidth; } else if (prevBase > -1) { // this could probably be improved assert((prevBase > -1) && (prevBase < (signed)mvGlyphs.size())); GlyphItem gi = mvGlyphs[prevBase]; int nGlyph = prevBase + 1; // try to find a better match, otherwise default to complete cluster for (; nGlyph < static_cast(mvGlyphs.size()) && !mvGlyphs[nGlyph].IsClusterStart(); nGlyph++) { if (mvGlyph2Char[nGlyph] == nCharSlot) { gi = mvGlyphs[nGlyph]; break; } } long nGWidth = gi.mnNewWidth; // if no match position at end of cluster if (nGlyph == static_cast(mvGlyphs.size()) || mvGlyphs[nGlyph].IsClusterStart()) { nGWidth = prevClusterWidth; if (bRtl) { pCaretXArray[i+1] = gi.maLinearPos.X(); pCaretXArray[i] = gi.maLinearPos.X(); } else { pCaretXArray[i] = gi.maLinearPos.X() + prevClusterWidth; pCaretXArray[i+1] = gi.maLinearPos.X() + prevClusterWidth; } } else { if (bRtl) { pCaretXArray[i+1] = gi.maLinearPos.X(); pCaretXArray[i] = gi.maLinearPos.X() + gi.mnNewWidth; } else { pCaretXArray[i] = gi.maLinearPos.X(); pCaretXArray[i+1] = gi.maLinearPos.X() + gi.mnNewWidth; } } } else { pCaretXArray[i] = pCaretXArray[i+1] = 0; } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"%d,%ld-%ld\t", nCharSlot, pCaretXArray[i], pCaretXArray[i+1]); #endif } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"\n"); #endif } // GetNextGlyphs returns a contiguous sequence of glyphs that can be // rendered together. It should never return a dropped glyph. // The glyph_slot returned should be the index of the next visible // glyph after the last glyph returned by this call. // The char_index array should be filled with the characters corresponding // to each glyph returned. // glyph_adv array should be a virtual width such that if successive // glyphs returned by this method are added one after the other they // have the correct spacing. // The logic in this method must match that expected in MultiSalLayout which // is used when glyph fallback is in operation. int GraphiteLayout::GetNextGlyphs( int length, sal_GlyphId * glyph_out, ::Point & aPosOut, int &glyph_slot, sal_Int32 * glyph_adv, int *char_index) const { // Sanity check on the slot index. if (glyph_slot >= signed(mvGlyphs.size())) { glyph_slot = mvGlyphs.size(); return 0; } assert(glyph_slot >= 0); // Find the first glyph in the substring. for (; glyph_slot < signed(mvGlyphs.size()) && ((mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED); ++glyph_slot) {}; // Update the length const int nGlyphSlotEnd = std::min(size_t(glyph_slot + length), mvGlyphs.size()); // We're all out of glyphs here. if (glyph_slot == nGlyphSlotEnd) { return 0; } // Find as many glyphs as we can which can be drawn in one go. Glyphs::const_iterator glyph_itr = mvGlyphs.begin() + glyph_slot; const int glyph_slot_begin = glyph_slot; const int initial_y_pos = glyph_itr->maLinearPos.Y(); // Set the position to the position of the start glyph. ::Point aStartPos = glyph_itr->maLinearPos; //aPosOut = glyph_itr->maLinearPos; aPosOut = GetDrawPosition(aStartPos); for (;;) // Forever { // last index of the range from glyph_to_chars does not include this glyph if (char_index) { assert((glyph_slot >= -1) && (glyph_slot < (signed)mvGlyph2Char.size())); if (mvGlyph2Char[glyph_slot] == -1) *char_index++ = mvCharDxs.size(); else *char_index++ = mvGlyph2Char[glyph_slot]; } // Copy out this glyphs data. ++glyph_slot; *glyph_out++ = glyph_itr->mnGlyphIndex; // Find the actual advance - this must be correct if called from // MultiSalLayout::AdjustLayout which requests one glyph at a time. const long nGlyphAdvance = (glyph_slot == static_cast(mvGlyphs.size()))? glyph_itr->mnNewWidth : ((glyph_itr+1)->maLinearPos.X() - glyph_itr->maLinearPos.X()); #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"GetNextGlyphs g%d c%d x%ld,%ld adv%ld, pos %ld,%ld\n", glyph_slot - 1, mvGlyph2Char[glyph_slot-1], glyph_itr->maLinearPos.X(), glyph_itr->maLinearPos.Y(), nGlyphAdvance, aPosOut.X(), aPosOut.Y()); #endif if (glyph_adv) // If we are returning advance store it. *glyph_adv++ = nGlyphAdvance; else // Stop when next advance is unexpected. if (glyph_itr->mnOrigWidth != nGlyphAdvance) break; // Have fetched all the glyphs we need to if (glyph_slot == nGlyphSlotEnd) break; ++glyph_itr; // Stop when next y position is unexpected. if (initial_y_pos != glyph_itr->maLinearPos.Y()) break; // Stop if glyph dropped if (glyph_itr->mnGlyphIndex == GF_DROPPED) break; } int numGlyphs = glyph_slot - glyph_slot_begin; // move the next glyph_slot to a glyph that hasn't been dropped while (glyph_slot < static_cast(mvGlyphs.size()) && (mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED) ++glyph_slot; return numGlyphs; } void GraphiteLayout::MoveGlyph( int nGlyphIndex, long nNewPos ) { // TODO it might be better to actualy implement simplify properly, but this // needs to be done carefully so the glyph/char maps are maintained // If a glyph has been dropped then it wasn't returned by GetNextGlyphs, so // the index here may be wrong while ((mvGlyphs[nGlyphIndex].mnGlyphIndex == GF_DROPPED) && (nGlyphIndex < (signed)mvGlyphs.size())) { nGlyphIndex++; } const long dx = nNewPos - mvGlyphs[nGlyphIndex].maLinearPos.X(); if (dx == 0) return; // GenericSalLayout only changes maLinearPos, mvCharDxs doesn't change #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Move %d (%ld,%ld) c%d by %ld\n", nGlyphIndex, mvGlyphs[nGlyphIndex].maLinearPos.X(), nNewPos, mvGlyph2Char[nGlyphIndex], dx); #endif for (size_t gi = nGlyphIndex; gi < mvGlyphs.size(); gi++) { mvGlyphs[gi].maLinearPos.X() += dx; } // width does need to be updated for correct fallback mnWidth += dx; } void GraphiteLayout::DropGlyph( int nGlyphIndex ) { if(nGlyphIndex >= signed(mvGlyphs.size())) return; GlyphItem & glyph = mvGlyphs[nGlyphIndex]; glyph.mnGlyphIndex = GF_DROPPED; #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Dropped %d\n", nGlyphIndex); #endif } void GraphiteLayout::Simplify( bool isBaseLayout ) { const sal_GlyphId dropMarker = isBaseLayout ? GF_DROPPED : 0; Glyphs::iterator gi = mvGlyphs.begin(); // TODO check whether we need to adjust positions here // MultiSalLayout seems to move the glyphs itself, so it may not be needed. long deltaX = 0; while (gi != mvGlyphs.end()) { if (gi->mnGlyphIndex == dropMarker) { deltaX += gi->mnNewWidth; gi->mnNewWidth = 0; } else { deltaX = 0; } //mvCharDxs[mvGlyph2Char[gi->mnCharPos]] -= deltaX; ++gi; } #ifdef GRLAYOUT_DEBUG fprintf(grLog(),"Simplify base%d dx=%ld newW=%ld\n", isBaseLayout, deltaX, mnWidth - deltaX); #endif // discard width from trailing dropped glyphs, but not those in the middle mnWidth -= deltaX; }