| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the 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 <algorithm> |
| #include <cassert> |
| #include <functional> |
| #include <limits> |
| #include <numeric> |
| #include <deque> |
| |
| // Platform |
| #ifdef WNT |
| #include <tools/svwin.h> |
| #include <svsys.h> |
| #endif |
| |
| #ifdef UNX |
| #include <graphite_adaptors.hxx> |
| #endif |
| |
| #include <salgdi.hxx> |
| |
| #include <unicode/uchar.h> |
| #include <unicode/ubidi.h> |
| #include <unicode/uscript.h> |
| |
| // Graphite Libraries (must be after vcl headers on windows) |
| #include <preextstl.h> |
| #include <graphite/GrClient.h> |
| #include <graphite/Font.h> |
| #include <graphite/ITextSource.h> |
| #include <graphite/Segment.h> |
| #include <graphite/SegmentPainter.h> |
| #include <postextstl.h> |
| |
| #include <graphite_layout.hxx> |
| #include <graphite_features.hxx> |
| #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 <graphite_cache.hxx> |
| #endif |
| |
| |
| namespace |
| { |
| typedef ext_std::pair<gr::GlyphIterator, gr::GlyphIterator> glyph_range_t; |
| typedef ext_std::pair<gr::GlyphSetIterator, gr::GlyphSetIterator> glyph_set_range_t; |
| |
| inline long round(const float n) { |
| return long(n + (n < 0 ? -0.5 : 0.5)); |
| } |
| |
| |
| template<typename T> |
| inline bool in_range(const T i, const T b, const T e) { |
| return !(b > i) && i < e; |
| } |
| |
| |
| template<typename T> |
| inline bool is_subrange(const T sb, const T se, const T b, const T e) { |
| return !(b > sb || se > e); |
| } |
| |
| |
| template<typename T> |
| inline bool is_subrange(const std::pair<T, T> &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<const UChar *>(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<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & rCharDxs) |
| { |
| // Create a glyph item for each of the glyph and append it to the base class glyph list. |
| typedef ext_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 fSegmentAdvance = rSegment.advanceWidth(); |
| float fMinX = fSegmentAdvance; |
| 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 <float,float> aBounds = |
| appendCluster(rSegment, rArgs, bRtl, |
| fSegmentAdvance, 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 <float,float> aBounds = |
| appendCluster(rSegment, rArgs, bRtl, fSegmentAdvance, |
| 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<float,float> GraphiteLayout::Glyphs::appendCluster(gr::Segment& rSeg, |
| ImplLayoutArgs & rArgs, bool bRtl,float fSegmentAdvance, |
| int nFirstCharInCluster, int nNextChar, int nFirstGlyphInCluster, |
| int nNextGlyph, float fScaling, std::vector<int> & rChar2Base, |
| std::vector<int> & rGlyph2Char, std::vector<int> & 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 <float, float> 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; |
| |
| // can we break before this cluster? |
| // Glyphs may have either a positive or negative breakWeight refering to |
| // the position after or before the glyph respectively |
| int nPrevBreakWeight = 0; |
| if (nFirstGlyphInCluster > 0) |
| { |
| nPrevBreakWeight = (iGlyphs.first + (nFirstGlyphInCluster - 1))->breakweight(); |
| } |
| int nBreakWeight = aFirstGlyph.breakweight(); |
| if (nBreakWeight < 0) |
| { |
| // negative means it applies to the position before the glyph's character |
| nBreakWeight *= -1; |
| if (nPrevBreakWeight > 0 && nPrevBreakWeight < nBreakWeight) |
| { |
| // prevBreakWeight wins |
| nBreakWeight = nPrevBreakWeight; |
| } |
| } |
| else |
| { |
| nBreakWeight = 0; |
| // positive means break after |
| if (nPrevBreakWeight > 0) |
| nBreakWeight = nPrevBreakWeight; |
| } |
| if (nBreakWeight > gr::klbNoBreak/*0*/ && |
| // nBreakWeight <= gr::klbHyphenBreak) // uses Graphite hyphenation |
| nBreakWeight <= gr::klbLetterBreak) // Needed for issue 111272 |
| { |
| if (nBreakWeight < gr::klbHyphenBreak) |
| rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= WORD_BREAK_BEFORE; |
| else |
| rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= HYPHEN_BREAK_BEFORE; |
| } |
| // always allow a break before a space even if graphite doesn't |
| if (rArgs.mpStr[nFirstCharInCluster] == 0x20) |
| rChar2Base[nFirstCharInCluster-rArgs.mnMinCharPos] |= WORD_BREAK_BEFORE; |
| |
| 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 = fSegmentAdvance; |
| nNextOrigin = round(fSegmentAdvance * fScaling + rDXOffset); |
| aBounds.second = std::max(fSegmentAdvance, 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 bw%d\n", nFirstGlyphInCluster, nNextGlyph, nFirstCharInCluster, nNextChar, rArgs.mpStr[nFirstCharInCluster], nXPos, aFirstGlyph.yOffset(), nBreakWeight); |
| #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<int> & rChar2Base, std::vector<int> & rGlyph2Char, std::vector<int> & 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(true); |
| // 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 = NULL; |
| // Graphite can in rare cases crash with a zero length |
| if (rArgs.mnMinCharPos < rArgs.mnEndCharPos) |
| { |
| pSegment = CreateSegment(rArgs, &pSegRecord); |
| if (!pSegment) |
| return false; |
| } |
| else |
| { |
| clear(); |
| return true; |
| } |
| // layout the glyphs as required by OpenOffice |
| bool success = LayoutGlyphs(rArgs, pSegment, pSegRecord); |
| |
| if (pSegRecord) pSegRecord->unlock(); |
| else delete pSegment; |
| #else |
| gr::Segment * pSegment = NULL; |
| bool success = true; |
| if (rArgs.mnMinCharPos < rArgs.mnEndCharPos) |
| { |
| pSegment = CreateSegment(rArgs); |
| if (!pSegment) |
| return false; |
| success = LayoutGlyphs(rArgs, pSegment); |
| if (pSegment) delete pSegment; |
| } |
| else |
| { |
| clear(); |
| } |
| #endif |
| return success; |
| } |
| |
| #ifdef GRCACHE |
| class GrFontHasher : public gr::Font |
| { |
| public: |
| GrFontHasher(const gr::Font & aFont) : gr::Font(aFont), mrRealFont(const_cast<gr::Font&>(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? |
| ext_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<sal_Int32>(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; |
| }; |
| protected: |
| virtual void UniqueCacheInfo( ext_std::wstring& stuFace, bool& fBold, bool& fItalic ) |
| { |
| #ifdef WIN32 |
| dynamic_cast<GraphiteWinFont&>(mrRealFont).UniqueCacheInfo(stuFace, fBold, fItalic); |
| #else |
| #ifdef UNX |
| dynamic_cast<GraphiteFontAdaptor&>(mrRealFont).UniqueCacheInfo(stuFace, fBold, fItalic); |
| #else |
| #error Unknown base type for gr::Font::UniqueCacheInfo |
| #endif |
| #endif |
| } |
| 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); |
| |
| // 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. Extra context, can also cause problems with ligatures stradling |
| // a hyphenation point, so disable if CTL is disabled. |
| const int nSegCharLimit = min(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH); |
| int limit = rArgs.mnEndCharPos; |
| if ((nSegCharLimit > limit) && !(SAL_LAYOUT_COMPLEX_DISABLED & rArgs.mnFlags)) |
| { |
| limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos, |
| nSegCharLimit - rArgs.mnEndCharPos, 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, limit); |
| 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); |
| if (limit > rArgs.mnMinCharPos && limit == rArgs.mnEndCharPos |
| && pSegment->stopCharacter() != limit) |
| { |
| // check that the last character is not part of a ligature |
| glyph_set_range_t aGlyphSet = pSegment->charToGlyphs(limit - 1); |
| if (aGlyphSet.first == aGlyphSet.second) |
| { |
| // no glyphs associated with this glyph - occurs mid ligature |
| pSegment = NULL; |
| *pSegRecord = NULL; |
| } |
| else |
| { |
| while (aGlyphSet.first != aGlyphSet.second) |
| { |
| int lastChar = static_cast<int>((*aGlyphSet.first).lastChar()); |
| if (lastChar >= limit) |
| { |
| pSegment = NULL; |
| *pSegRecord = NULL; |
| break; |
| } |
| aGlyphSet.first++; |
| } |
| } |
| } |
| if (pSegment) |
| return pSegment; |
| } |
| } |
| #endif |
| |
| // 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 |
| { |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(), "Gr::LayoutText failed: "); |
| for (int i = mnMinCharPos; i < limit; i++) |
| { |
| fprintf(grLog(), "%04x ", rArgs.mpStr[i]); |
| } |
| fprintf(grLog(), "\n"); |
| #endif |
| 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<long>(), 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 |
| { |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(),"Gr::GetTextBreak c[%d-%d) maxWidth %ld char extra %ld factor %d\n", |
| mnMinCharPos, mnEndCharPos, maxmnWidth, char_extra, factor); |
| #endif |
| |
| // return quickly if this segment is narrower than the target width |
| if (maxmnWidth > mnWidth * factor + char_extra * (mnEndCharPos - mnMinCharPos - 1)) |
| return STRING_LEN; |
| |
| long nWidth = mvCharDxs[0] * factor; |
| int nLastBreak = -1; |
| for (size_t i = 1; i < mvCharDxs.size(); i++) |
| { |
| nWidth += char_extra; |
| if (nWidth > maxmnWidth) break; |
| if (mvChar2BaseGlyph[i] != -1) |
| { |
| if (mvChar2BaseGlyph[i] & (WORD_BREAK_BEFORE | HYPHEN_BREAK_BEFORE)) |
| nLastBreak = static_cast<int>(i); |
| } |
| nWidth += (mvCharDxs[i] - mvCharDxs[i-1]) * factor; |
| } |
| int nBreak = mnMinCharPos; |
| if (nLastBreak > -1) |
| nBreak += nLastBreak; |
| |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(), "Gr::GetTextBreak break after %d\n", nBreak - mnMinCharPos); |
| #endif |
| |
| 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) || |
| ((signed)(mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK) < (signed)mvGlyphs.size())); |
| if (mvChar2BaseGlyph[i] != -1 && |
| mvGlyphs[mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK].maGlyphId == 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,%d ", (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<int> 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 ); |
| } |
| } |
| } |
| else if (rArgs.mnLayoutWidth > 0) |
| { |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(), "AdjustLayout width %ld=>%ld\n", mnWidth, rArgs.mnLayoutWidth); |
| #endif |
| expandOrCondense(rArgs); |
| } |
| } |
| |
| void GraphiteLayout::expandOrCondense(ImplLayoutArgs &rArgs) |
| { |
| int nDeltaWidth = rArgs.mnLayoutWidth - mnWidth; |
| if (nDeltaWidth > 0) // expand, just expand between clusters |
| { |
| int nClusterCount = 0; |
| for (size_t j = 0; j < mvGlyphs.size(); j++) |
| { |
| if (mvGlyphs[j].IsClusterStart()) |
| { |
| ++nClusterCount; |
| } |
| } |
| if (nClusterCount > 1) |
| { |
| float fExtraPerCluster = static_cast<float>(nDeltaWidth) / static_cast<float>(nClusterCount - 1); |
| int nCluster = 0; |
| int nOffset = 0; |
| for (size_t i = 0; i < mvGlyphs.size(); i++) |
| { |
| if (mvGlyphs[i].IsClusterStart()) |
| { |
| nOffset = FRound( fExtraPerCluster * nCluster ); |
| size_t nCharIndex = mvGlyph2Char[i]; |
| mvCharDxs[nCharIndex] += nOffset; |
| // adjust char dxs for rest of characters in cluster |
| while (++nCharIndex < mvGlyph2Char.size()) |
| { |
| int nChar2Base = (mvChar2BaseGlyph[nCharIndex] == -1)? -1 : (int)(mvChar2BaseGlyph[nCharIndex] & GLYPH_INDEX_MASK); |
| if (nChar2Base == -1 || nChar2Base == static_cast<int>(i)) |
| mvCharDxs[nCharIndex] += nOffset; |
| } |
| ++nCluster; |
| } |
| mvGlyphs[i].maLinearPos.X() += nOffset; |
| } |
| } |
| } |
| else // condense - apply a factor to all glyph positions |
| { |
| if (mvGlyphs.size() == 0) return; |
| Glyphs::iterator iLastGlyph = mvGlyphs.begin() + (mvGlyphs.size() - 1); |
| // position last glyph using original width |
| float fXFactor = static_cast<float>(rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth) / static_cast<float>(iLastGlyph->maLinearPos.X()); |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(), "Condense by factor %f\n", fXFactor); |
| #endif |
| iLastGlyph->maLinearPos.X() = rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth; |
| Glyphs::iterator iGlyph = mvGlyphs.begin(); |
| while (iGlyph != iLastGlyph) |
| { |
| iGlyph->maLinearPos.X() = FRound( fXFactor * iGlyph->maLinearPos.X() ); |
| ++iGlyph; |
| } |
| for (size_t i = 0; i < mvCharDxs.size(); i++) |
| { |
| mvCharDxs[i] = FRound( fXFactor * mvCharDxs[i] ); |
| } |
| } |
| mnWidth = rArgs.mnLayoutWidth; |
| } |
| |
| void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector<int> & 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,%d ", (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)? (signed)mvGlyphs.size() : -1; |
| int nPrevClusterLastChar = -1; |
| for (size_t i = 0; i < nChars; i++) |
| { |
| int nChar2Base = (mvChar2BaseGlyph[i] == -1)? -1 : (int)(mvChar2BaseGlyph[i] & GLYPH_INDEX_MASK); |
| if ((nChar2Base > -1) && (nChar2Base != nPrevClusterGlyph)) |
| { |
| assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size())); |
| GlyphItem & gi = mvGlyphs[nChar2Base]; |
| if (!gi.IsClusterStart()) |
| continue; |
| |
| // find last glyph of this cluster |
| size_t j = i + 1; |
| int nLastChar = i; |
| int nLastGlyph = nChar2Base; |
| for (; j < nChars; j++) |
| { |
| int nChar2BaseJ = (mvChar2BaseGlyph[j] == -1)? -1 : (int)(mvChar2BaseGlyph[j] & GLYPH_INDEX_MASK); |
| assert((nChar2BaseJ >= -1) && (nChar2BaseJ < (signed)mvGlyphs.size())); |
| if (nChar2BaseJ != -1 && mvGlyphs[nChar2BaseJ].IsClusterStart()) |
| { |
| nLastGlyph = nChar2BaseJ + ((bRtl)? +1 : -1); |
| nLastChar = j - 1; |
| break; |
| } |
| } |
| if (nLastGlyph < 0) |
| { |
| nLastGlyph = nChar2Base; |
| } |
| // 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 = nChar2Base; |
| 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%lu last glyph %d/%lu\n", i, nLastGlyph, mvGlyphs.size()); |
| #endif |
| assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size())); |
| mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; |
| if (gi.maGlyphId != GF_DROPPED) |
| mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; |
| else |
| nDGlyphOrigin += nDWidth; |
| // update glyph positions |
| if (bRtl) |
| { |
| for (int n = nChar2Base; n <= nLastGlyph; n++) |
| { |
| assert((n > - 1) && (n < (signed)mvGlyphs.size())); |
| mvGlyphs[n].maLinearPos.X() += -nDGlyphOrigin + nXOffset; |
| } |
| } |
| else |
| { |
| for (int n = nChar2Base; n <= nLastGlyph; n++) |
| { |
| assert((n > - 1) && (n < (signed)mvGlyphs.size())); |
| mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + nXOffset; |
| } |
| } |
| rDeltaWidth[nChar2Base] = nDWidth; |
| #ifdef GRLAYOUT_DEBUG |
| fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, nChar2Base, nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[nChar2Base].maLinearPos.X()); |
| #endif |
| nPrevClusterGlyph = nChar2Base; |
| 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 %d(%ld)\n", args.mpDXArray[nChars - 1], mnWidth); |
| #endif |
| mnWidth = args.mpDXArray[nChars - 1]; |
| } |
| |
| void GraphiteLayout::kashidaJustify(std::vector<int>& 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).maGlyphId ) ) |
| { |
| ++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 %u\n", nKashidaCount, (*i).maGlyphId); |
| #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<GraphiteLayout*>(this)->maLayout.rightToLeft(); |
| int prevBase = -1; |
| long prevClusterWidth = 0; |
| for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast<int>(mvCharDxs.size()); ++nCharSlot, i+=2) |
| { |
| if (mvChar2BaseGlyph[nCharSlot] != -1) |
| { |
| int nChar2Base = mvChar2BaseGlyph[nCharSlot] & GLYPH_INDEX_MASK; |
| assert((mvChar2BaseGlyph[nCharSlot] > -1) && (nChar2Base < (signed)mvGlyphs.size())); |
| GlyphItem gi = mvGlyphs[nChar2Base]; |
| if (gi.maGlyphId == GF_DROPPED) |
| { |
| continue; |
| } |
| int nCluster = nChar2Base; |
| 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<int>(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 = nChar2Base; |
| 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<int>(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<int>(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,%d-%d\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)->maGlyphId == 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->maGlyphId; |
| |
| // 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<int>(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, |
| GLYPH_INDEX_MASK&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->maGlyphId == 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<int>(mvGlyphs.size()) && |
| (mvGlyphs.begin() + glyph_slot)->maGlyphId == 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].maGlyphId == 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.maGlyphId = 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->maGlyphId == 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; |
| } |