LCOV - code coverage report
Current view: top level - vcl/source/glyphs - graphite_layout.cxx (source / functions) Hit Total Coverage
Test: commit e02a6cb2c3e2b23b203b422e4e0680877f232636 Lines: 1 619 0.2 %
Date: 2014-04-14 Functions: 2 33 6.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
       2             : /*
       3             :  * This file is part of the LibreOffice project.
       4             :  *
       5             :  * This Source Code Form is subject to the terms of the Mozilla Public
       6             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       7             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       8             :  *
       9             :  * This file incorporates work covered by the following license notice:
      10             :  *
      11             :  *   Licensed to the Apache Software Foundation (ASF) under one or more
      12             :  *   contributor license agreements. See the NOTICE file distributed
      13             :  *   with this work for additional information regarding copyright
      14             :  *   ownership. The ASF licenses this file to you under the Apache
      15             :  *   License, Version 2.0 (the "License"); you may not use this file
      16             :  *   except in compliance with the License. You may obtain a copy of
      17             :  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
      18             :  */
      19             : 
      20             : // Description: An implementation of the SalLayout interface that uses the
      21             : //              Graphite engine.
      22             : 
      23             : // Enable lots of debug info
      24             : #if OSL_DEBUG_LEVEL > 1
      25             : #include <cstdio>
      26             : #define GRLAYOUT_DEBUG 1
      27             : #undef NDEBUG
      28             : #endif
      29             : 
      30             : // #define GRLAYOUT_DEBUG 1
      31             : 
      32             : // Header files
      33             : 
      34             : // Standard Library
      35             : #include <algorithm>
      36             : #include <cassert>
      37             : #include <functional>
      38             : #include <limits>
      39             : #include <numeric>
      40             : #include <deque>
      41             : 
      42             : // Platform
      43             : #include <svsys.h>
      44             : 
      45             : #include <salgdi.hxx>
      46             : 
      47             : #include <unicode/uchar.h>
      48             : #include <unicode/ubidi.h>
      49             : #include <unicode/uscript.h>
      50             : 
      51             : #include <vcl/unohelp.hxx>
      52             : #include <com/sun/star/i18n/XCharacterClassification.hpp>
      53             : #include <com/sun/star/i18n/UnicodeType.hpp>
      54             : 
      55             : // Graphite Libraries (must be after vcl headers on windows)
      56             : #include <graphite_static.hxx>
      57             : #include <graphite2/Segment.h>
      58             : 
      59             : #include <graphite_layout.hxx>
      60             : #include <graphite_features.hxx>
      61             : 
      62             : // Module private type definitions and forward declarations.
      63             : 
      64             : // Module private names.
      65             : 
      66             : #ifdef GRLAYOUT_DEBUG
      67             : static FILE * grLog()
      68             : {
      69             : #ifdef WNT
      70             :     static FILE * grLogFile = NULL;
      71             :     if (grLogFile == NULL)
      72             :     {
      73             :         std::string logFileName(getenv("TEMP"));
      74             :         logFileName.append("/graphitelayout.log");
      75             :         grLogFile = fopen(logFileName.c_str(),"w");
      76             :     }
      77             :     else
      78             :         fflush(grLogFile);
      79             :     return grLogFile;
      80             : #else
      81             :     fflush(stdout);
      82             :     return stdout;
      83             : #endif
      84             : }
      85             : #endif
      86             : 
      87             : namespace
      88             : {
      89           0 :     inline long round(const float n) {
      90           0 :         return long(n + (n < 0 ? -0.5 : 0.5));
      91             :     }
      92             : 
      93             :     template<typename T>
      94           0 :     inline bool in_range(const T i, const T b, const T e) {
      95           0 :         return !(b > i) && i < e;
      96             :     }
      97             : 
      98             :     template<typename T>
      99             :     inline bool is_subrange(const T sb, const T se, const T b, const T e) {
     100             :         return !(b > sb || se > e);
     101             :     }
     102             : 
     103             :     template<typename T>
     104             :     inline bool is_subrange(const std::pair<T, T> &s, const T b, const T e) {
     105             :         return is_subrange(s.first, s.second, b, e);
     106             :     }
     107             : 
     108           0 :     int findSameDirLimit(const sal_Unicode* buffer, int charCount, bool rtl)
     109             :     {
     110           0 :         UErrorCode status = U_ZERO_ERROR;
     111           0 :         UBiDi *ubidi = ubidi_openSized(charCount, 0, &status);
     112           0 :         int limit = 0;
     113             :         ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(buffer), charCount,
     114           0 :             (rtl)?UBIDI_DEFAULT_RTL:UBIDI_DEFAULT_LTR, NULL, &status);
     115           0 :         UBiDiLevel level = 0;
     116           0 :         ubidi_getLogicalRun(ubidi, 0, &limit, &level);
     117           0 :         ubidi_close(ubidi);
     118           0 :         if ((rtl && !(level & 1)) || (!rtl && (level & 1)))
     119             :         {
     120           0 :             limit = 0;
     121             :         }
     122           0 :         return limit;
     123             :     }
     124             : 
     125             :     template <typename T>
     126           0 :     T maximum(T a, T b)
     127             :     {
     128           0 :         return (a > b)? a : b;
     129             :     }
     130             :     template <typename T>
     131           0 :     T minimum(T a, T b)
     132             :     {
     133           0 :         return (a < b)? a : b;
     134             :     }
     135             : 
     136             : } // namespace
     137             : 
     138             : // Impementation of the GraphiteLayout::Glyphs container class.
     139             : //    This is an extended vector class with methods added to enable
     140             : //        o Correctly filling with glyphs.
     141             : //        o Querying clustering relationships.
     142             : //        o manipulations that affect neighouring glyphs.
     143             : 
     144             : const int GraphiteLayout::EXTRA_CONTEXT_LENGTH = 10;
     145             : 
     146             : // find first slot of cluster and first slot of subsequent cluster
     147           0 : static void findFirstClusterSlot(const gr_slot* base, gr_slot const** first, gr_slot const** after, int * firstChar, int * lastChar, bool bRtl)
     148             : {
     149           0 :     if (gr_slot_attached_to(base) == NULL)
     150             :     {
     151           0 :         *first = base;
     152             :         *after = (bRtl)? gr_slot_prev_in_segment(base) :
     153           0 :             gr_slot_next_in_segment(base);
     154           0 :         *firstChar = gr_slot_before(base);
     155           0 :         *lastChar = gr_slot_after(base);
     156             :     }
     157           0 :     const gr_slot * attachment = gr_slot_first_attachment(base);
     158           0 :     while (attachment)
     159             :     {
     160           0 :         if (gr_slot_origin_X(*first) > gr_slot_origin_X(attachment))
     161           0 :             *first = attachment;
     162             :         const gr_slot* attachmentNext = (bRtl)?
     163           0 :             gr_slot_prev_in_segment(attachment) : gr_slot_next_in_segment(attachment);
     164           0 :         if (attachmentNext)
     165             :         {
     166           0 :             if (*after && (gr_slot_origin_X(*after) < gr_slot_origin_X(attachmentNext)))
     167           0 :                 *after = attachmentNext;
     168             :         }
     169             :         else
     170             :         {
     171           0 :             *after = NULL;
     172             :         }
     173           0 :         if (gr_slot_before(attachment) < *firstChar)
     174           0 :             *firstChar = gr_slot_before(attachment);
     175           0 :         if (gr_slot_after(attachment) > *lastChar)
     176           0 :             *lastChar = gr_slot_after(attachment);
     177           0 :         if (gr_slot_first_attachment(attachment))
     178           0 :             findFirstClusterSlot(attachment, first, after, firstChar, lastChar, bRtl);
     179           0 :         attachment = gr_slot_next_sibling_attachment(attachment);
     180             :     }
     181           0 : }
     182             : 
     183             : // The Graphite glyph stream is really a sequence of glyph attachment trees
     184             : //  each rooted at a non-attached base glyph.  fill_from walks the glyph stream,
     185             : //  finds each non-attached base glyph and calls append to record them as a
     186             : //  sequence of clusters.
     187             : void
     188           0 : GraphiteLayout::fillFrom(gr_segment * pSegment, ImplLayoutArgs &rArgs, float fScaling)
     189             : {
     190           0 :     bool bRtl = (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL);
     191           0 :     int nCharRequested = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
     192           0 :     int nChar = gr_seg_n_cinfo(pSegment);
     193           0 :     float fMinX = gr_seg_advance_X(pSegment);
     194           0 :     float fMaxX = 0.0f;
     195           0 :     long nDxOffset = 0; // from dropped glyphs
     196           0 :     int nFirstCharInCluster = 0;
     197           0 :     int nLastCharInCluster = 0;
     198           0 :     unsigned int nGlyphs = gr_seg_n_slots(pSegment);
     199           0 :     mvGlyph2Char.assign(nGlyphs, -1);
     200           0 :     mvGlyphs.reserve(nGlyphs);
     201             : 
     202           0 :     if (bRtl)
     203             :     {
     204           0 :         const gr_slot* baseSlot = gr_seg_last_slot(pSegment);
     205             :         // find first base
     206           0 :         while (baseSlot && (gr_slot_attached_to(baseSlot) != NULL))
     207           0 :             baseSlot = gr_slot_prev_in_segment(baseSlot);
     208           0 :         int iChar = nChar - 1;
     209           0 :         int iNextChar = nChar - 1;
     210           0 :         bool reordered = false;
     211           0 :         int nBaseGlyphIndex = 0;
     212             :         // now loop over bases
     213           0 :         while (baseSlot)
     214             :         {
     215           0 :             bool bCluster = !reordered;
     216           0 :             const gr_slot * clusterFirst = NULL;
     217           0 :             const gr_slot * clusterAfter = NULL;
     218           0 :             int firstChar = -1;
     219           0 :             int lastChar = -1;
     220           0 :             findFirstClusterSlot(baseSlot, &clusterFirst, &clusterAfter, &firstChar, &lastChar, bRtl);
     221           0 :             iNextChar = minimum<int>(firstChar, iNextChar);
     222           0 :             if (bCluster)
     223             :             {
     224           0 :                 nBaseGlyphIndex = mvGlyphs.size();
     225           0 :                 mvGlyph2Char[nBaseGlyphIndex] = iChar + mnSegCharOffset;
     226           0 :                 nFirstCharInCluster = firstChar;
     227           0 :                 nLastCharInCluster = lastChar;
     228             :             }
     229             :             else
     230             :             {
     231           0 :                 mvGlyph2Char[mvGlyphs.size()] = firstChar + mnSegCharOffset;
     232           0 :                 nFirstCharInCluster = minimum<int>(firstChar, nFirstCharInCluster);
     233           0 :                 nLastCharInCluster = maximum<int>(firstChar, nLastCharInCluster);
     234             :             }
     235           0 :             float leftBoundary = gr_slot_origin_X(clusterFirst);
     236             :             float rightBoundary = (clusterAfter)?
     237           0 :                 gr_slot_origin_X(clusterAfter) : gr_seg_advance_X(pSegment);
     238           0 :             if (
     239           0 :                 lastChar < iChar && clusterAfter &&
     240           0 :                  (gr_cinfo_after(gr_seg_cinfo(pSegment, iChar)) >
     241           0 :                  static_cast<int>(gr_slot_index(clusterAfter)))
     242             :                )
     243             :             {
     244           0 :                 reordered = true;
     245             :             }
     246             :             else
     247             :             {
     248           0 :                 reordered = false;
     249           0 :                 iChar = iNextChar - 1;
     250             :             }
     251           0 :             if (mnSegCharOffset + nFirstCharInCluster >= mnMinCharPos &&
     252           0 :                 mnSegCharOffset + nFirstCharInCluster < mnEndCharPos)
     253             :             {
     254           0 :                 fMinX = minimum<float>(fMinX, leftBoundary);
     255           0 :                 fMaxX = maximum<float>(fMaxX, rightBoundary);
     256           0 :                 if (!reordered)
     257             :                 {
     258           0 :                     for (int i = nFirstCharInCluster; i <= nLastCharInCluster; i++)
     259             :                     {
     260           0 :                         if (mnSegCharOffset + i >= mnEndCharPos)
     261           0 :                             break;
     262             :                         // from the point of view of the dx array, the xpos is
     263             :                         // the origin of the first glyph of the cluster rtl
     264           0 :                         mvCharDxs[mnSegCharOffset + i - mnMinCharPos] =
     265           0 :                             static_cast<int>(leftBoundary * fScaling) + nDxOffset;
     266           0 :                         mvCharBreaks[mnSegCharOffset + i - mnMinCharPos] = gr_cinfo_break_weight(gr_seg_cinfo(pSegment, i));
     267             :                     }
     268           0 :                     mvChar2BaseGlyph[mnSegCharOffset + nFirstCharInCluster - mnMinCharPos] = nBaseGlyphIndex;
     269             :                 }
     270             :                 append(pSegment, rArgs, baseSlot, gr_slot_origin_X(baseSlot), rightBoundary, fScaling,
     271           0 :                        nDxOffset, bCluster, mnSegCharOffset + firstChar);
     272             :             }
     273           0 :             if (mnSegCharOffset + nLastCharInCluster < mnMinCharPos)
     274           0 :                 break;
     275           0 :             baseSlot = gr_slot_next_sibling_attachment(baseSlot);
     276             :         }
     277             :     }
     278             :     else
     279             :     {
     280           0 :         const gr_slot* baseSlot = gr_seg_first_slot(pSegment);
     281             :         // find first base
     282           0 :         while (baseSlot && (gr_slot_attached_to(baseSlot) != NULL))
     283           0 :             baseSlot = gr_slot_next_in_segment(baseSlot);
     284           0 :         int iChar = 0; // relative to segment
     285           0 :         int iNextChar = 0;
     286           0 :         bool reordered = false;
     287           0 :         int nBaseGlyphIndex = 0;
     288             :         // now loop over bases
     289           0 :         while (baseSlot)
     290             :         {
     291           0 :             bool bCluster = !reordered;
     292           0 :             const gr_slot * clusterFirst = NULL;
     293           0 :             const gr_slot * clusterAfter = NULL;
     294           0 :             int firstChar = -1;
     295           0 :             int lastChar = -1;
     296           0 :             findFirstClusterSlot(baseSlot, &clusterFirst, &clusterAfter, &firstChar, &lastChar, bRtl);
     297           0 :             iNextChar = maximum<int>(lastChar, iNextChar);
     298           0 :             if (bCluster)
     299             :             {
     300           0 :                 nBaseGlyphIndex = mvGlyphs.size();
     301           0 :                 mvGlyph2Char[nBaseGlyphIndex] = iChar + mnSegCharOffset;
     302           0 :                 nFirstCharInCluster = firstChar;
     303           0 :                 nLastCharInCluster = lastChar;
     304             :             }
     305             :             else
     306             :             {
     307           0 :                 mvGlyph2Char[mvGlyphs.size()] = firstChar + mnSegCharOffset;
     308           0 :                 nFirstCharInCluster = minimum<int>(firstChar, nFirstCharInCluster);
     309           0 :                 nLastCharInCluster = maximum<int>(lastChar, nLastCharInCluster);
     310             :             }
     311           0 :             if (
     312           0 :                 firstChar > iChar &&
     313           0 :                  (gr_cinfo_before(gr_seg_cinfo(pSegment, iChar)) >
     314           0 :                  static_cast<int>(gr_slot_index(clusterFirst)))
     315             :                )
     316             :             {
     317           0 :                 reordered = true;
     318             :             }
     319             :             else
     320             :             {
     321           0 :                 reordered = false;
     322           0 :                 iChar = iNextChar + 1;
     323             :             }
     324           0 :             float leftBoundary = gr_slot_origin_X(clusterFirst);
     325             :             float rightBoundary = (clusterAfter)?
     326           0 :                 gr_slot_origin_X(clusterAfter) : gr_seg_advance_X(pSegment);
     327           0 :             int bFirstChar = gr_cinfo_base(gr_seg_cinfo(pSegment, nFirstCharInCluster));
     328           0 :             if (mnSegCharOffset + bFirstChar >= mnMinCharPos &&
     329           0 :                 mnSegCharOffset + bFirstChar < mnEndCharPos)
     330             :             {
     331           0 :                 fMinX = minimum<float>(fMinX, leftBoundary);
     332           0 :                 fMaxX = maximum<float>(fMaxX, rightBoundary);
     333           0 :                 if (!reordered)
     334             :                 {
     335           0 :                     for (int i = nFirstCharInCluster; i <= nLastCharInCluster; i++)
     336             :                     {
     337           0 :                         int ibase = gr_cinfo_base(gr_seg_cinfo(pSegment, i));
     338           0 :                         if (mnSegCharOffset + ibase >= mnEndCharPos)
     339           0 :                             break;
     340             :                         // from the point of view of the dx array, the xpos is
     341             :                         // the origin of the first glyph of the next cluster ltr
     342           0 :                         mvCharDxs[mnSegCharOffset + ibase - mnMinCharPos] =
     343           0 :                             static_cast<int>(rightBoundary * fScaling) + nDxOffset;
     344           0 :                         mvCharBreaks[mnSegCharOffset + ibase - mnMinCharPos] = gr_cinfo_break_weight(gr_seg_cinfo(pSegment, i));
     345             :                     }
     346             :                     // only set mvChar2BaseGlyph for first character of cluster
     347           0 :                     mvChar2BaseGlyph[mnSegCharOffset + bFirstChar - mnMinCharPos] = nBaseGlyphIndex;
     348             :                 }
     349             :                 append(pSegment, rArgs, baseSlot, gr_slot_origin_X(baseSlot), rightBoundary, fScaling,
     350           0 :                        nDxOffset, true, mnSegCharOffset + firstChar);
     351             :             }
     352           0 :             if (mnSegCharOffset + bFirstChar >= mnEndCharPos)
     353           0 :                 break;
     354           0 :             baseSlot = gr_slot_next_sibling_attachment(baseSlot);
     355             :         }
     356             :     }
     357           0 :     long nXOffset = round(fMinX * fScaling);
     358           0 :     mnWidth = round(fMaxX * fScaling) - nXOffset + nDxOffset;
     359           0 :     if (mnWidth < 0)
     360             :     {
     361             :         // This can happen when there was no base inside the range
     362           0 :         mnWidth = 0;
     363             :     }
     364             :     // fill up non-base char dx with cluster widths from previous base glyph
     365           0 :     if (bRtl)
     366             :     {
     367           0 :         if (mvCharDxs[nCharRequested-1] == -1)
     368           0 :             mvCharDxs[nCharRequested-1] = 0;
     369             :         else
     370           0 :             mvCharDxs[nCharRequested-1] -= nXOffset;
     371           0 :         for (int i = nCharRequested - 2; i >= 0; i--)
     372             :         {
     373           0 :             if (mvCharDxs[i] == -1) mvCharDxs[i] = mvCharDxs[i+1];
     374           0 :             else mvCharDxs[i] -= nXOffset;
     375             :         }
     376             :     }
     377             :     else
     378             :     {
     379           0 :         if (mvCharDxs[0] == -1)
     380           0 :             mvCharDxs[0] = 0;
     381             :         else
     382           0 :             mvCharDxs[0] -= nXOffset;
     383           0 :         for (int i = 1; i < nCharRequested; i++)
     384             :         {
     385           0 :             if (mvCharDxs[i] == -1) mvCharDxs[i] = mvCharDxs[i-1];
     386           0 :             else mvCharDxs[i] -= nXOffset;
     387             : #ifdef GRLAYOUT_DEBUG
     388             :             fprintf(grLog(),"%d,%d ", (int)i, (int)mvCharDxs[i]);
     389             : #endif
     390             :         }
     391             :     }
     392             :     // remove offset due to context if there is one
     393           0 :     if (nXOffset != 0)
     394             :     {
     395           0 :         for (size_t i = 0; i < mvGlyphs.size(); i++)
     396           0 :             mvGlyphs[i].maLinearPos.X() -= nXOffset;
     397             :     }
     398             : #ifdef GRLAYOUT_DEBUG
     399             :     fprintf(grLog(), "fillFrom %" SAL_PRI_SIZET "u glyphs offset %ld width %ld\n", mvGlyphs.size(), nXOffset, mnWidth);
     400             : #endif
     401           0 : }
     402             : 
     403             : // append walks an attachment tree, flattening it, and converting it into a
     404             : // sequence of GlyphItem objects which we can later manipulate.
     405             : float
     406           0 : GraphiteLayout::append(gr_segment *pSeg, ImplLayoutArgs &rArgs,
     407             :     const gr_slot * gi, float gOrigin, float nextGlyphOrigin, float scaling, long & rDXOffset,
     408             :     bool bIsBase, int baseChar)
     409             : {
     410           0 :     bool bRtl = (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL);
     411             :     float nextOrigin;
     412             :     assert(gi);
     413             :     assert(gr_slot_before(gi) <= gr_slot_after(gi));
     414           0 :     int firstChar = gr_slot_before(gi) + mnSegCharOffset;
     415             :     assert(mvGlyphs.size() < mvGlyph2Char.size());
     416           0 :     if (!bIsBase) mvGlyph2Char[mvGlyphs.size()] = baseChar;//firstChar;
     417             :     // is the next glyph attached or in the next cluster?
     418             :     //glyph_set_range_t iAttached = gi.attachedClusterGlyphs();
     419           0 :     const gr_slot * pFirstAttached = gr_slot_first_attachment(gi);
     420           0 :     const gr_slot * pNextSibling = gr_slot_next_sibling_attachment(gi);
     421           0 :     if (pFirstAttached)
     422           0 :         nextOrigin = gr_slot_origin_X(pFirstAttached);
     423           0 :     else if (!bIsBase && pNextSibling)
     424           0 :         nextOrigin = gr_slot_origin_X(pNextSibling);
     425             :     else
     426           0 :         nextOrigin = nextGlyphOrigin;
     427           0 :     long glyphId = gr_slot_gid(gi);
     428           0 :     long deltaOffset = 0;
     429           0 :     int scaledGlyphPos = round(gr_slot_origin_X(gi) * scaling);
     430           0 :     int glyphWidth = round((nextOrigin - gOrigin) * scaling);
     431             : //    if (glyphWidth < 0)
     432             : //    {
     433             : //        nextOrigin = gOrigin;
     434             : //        glyphWidth = 0;
     435             : //    }
     436             : #ifdef GRLAYOUT_DEBUG
     437             :     fprintf(grLog(),"c%d g%ld,X%d W%d nX%f ", firstChar, glyphId,
     438             :         (int)(gr_slot_origin_X(gi) * scaling), glyphWidth, nextOrigin * scaling);
     439             : #endif
     440           0 :     if (glyphId == 0)
     441             :     {
     442           0 :         rArgs.NeedFallback(firstChar, bRtl);
     443           0 :         if( (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags ))
     444             :         {
     445           0 :             glyphId = GF_DROPPED;
     446           0 :             deltaOffset -= glyphWidth;
     447           0 :             glyphWidth = 0;
     448             :         }
     449             :     }
     450           0 :     else if(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK)
     451             :     {
     452             : #ifdef GRLAYOUT_DEBUG
     453             :         fprintf(grLog(),"fallback c%d %x in run %d\n", firstChar, rArgs.mpStr[firstChar],
     454             :             rArgs.maRuns.PosIsInAnyRun(firstChar));
     455             : #endif
     456             :         // glyphs that aren't requested for fallback will be taken from base
     457             :         // layout, so mark them as dropped (should this wait until Simplify(false) is called?)
     458           0 :         if (!rArgs.maRuns.PosIsInAnyRun(firstChar) &&
     459           0 :             in_range(firstChar, rArgs.mnMinCharPos, rArgs.mnEndCharPos))
     460             :         {
     461           0 :             glyphId = GF_DROPPED;
     462           0 :             deltaOffset -= glyphWidth;
     463           0 :             glyphWidth = 0;
     464             :         }
     465             :     }
     466             :     // append this glyph. Set the cluster flag if this glyph is attached to another
     467           0 :     long nGlyphFlags = bIsBase ? 0 : GlyphItem::IS_IN_CLUSTER;
     468           0 :     nGlyphFlags |= (bRtl)? GlyphItem::IS_RTL_GLYPH : 0;
     469           0 :     GlyphItem aGlyphItem(mvGlyphs.size(),
     470             :         glyphId,
     471             :         Point(scaledGlyphPos + rDXOffset,
     472           0 :             round((-gr_slot_origin_Y(gi) * scaling))),
     473             :         nGlyphFlags,
     474           0 :         glyphWidth);
     475           0 :     if (glyphId != static_cast<long>(GF_DROPPED))
     476           0 :         aGlyphItem.mnOrigWidth = round(gr_slot_advance_X(gi, mpFace, mpFont) * scaling);
     477           0 :     mvGlyphs.push_back(aGlyphItem);
     478             : 
     479             :     // update the offset if this glyph was dropped
     480           0 :     rDXOffset += deltaOffset;
     481             : 
     482             :     // Recursively append all the attached glyphs.
     483           0 :     float cOrigin = nextOrigin;
     484           0 :     for (const gr_slot * agi = gr_slot_first_attachment(gi); agi != NULL; agi = gr_slot_next_sibling_attachment(agi))
     485           0 :         cOrigin = append(pSeg, rArgs, agi, cOrigin, nextGlyphOrigin, scaling, rDXOffset, false, baseChar);
     486             : 
     487           0 :     return cOrigin;
     488             : }
     489             : 
     490             : // An implementation of the SalLayout interface to enable Graphite enabled fonts to be used.
     491             : 
     492           0 : GraphiteLayout::GraphiteLayout(const gr_face * face, gr_font * font,
     493             :                                const grutils::GrFeatureParser * pFeatures) throw()
     494             :     : mpFace(face)
     495             :     , mpFont(font)
     496             :     , mnSegCharOffset(0)
     497             :     , mnWidth(0)
     498             :     , mfScaling(1.0)
     499           0 :     , mpFeatures(pFeatures)
     500             : {
     501             : 
     502           0 : }
     503             : 
     504           0 : GraphiteLayout::~GraphiteLayout() throw()
     505             : {
     506           0 :     clear();
     507             :     // the features and font are owned by the platform layers
     508           0 :     mpFeatures = NULL;
     509           0 :     mpFont = NULL;
     510           0 : }
     511             : 
     512           0 : void GraphiteLayout::clear()
     513             : {
     514             :     // Destroy the segment and text source from any previous invocation of
     515             :     // LayoutText
     516           0 :     mvGlyphs.clear();
     517           0 :     mvCharDxs.clear();
     518           0 :     mvChar2BaseGlyph.clear();
     519           0 :     mvGlyph2Char.clear();
     520             : 
     521             :     // Reset the state to the empty state.
     522           0 :     mnWidth = 0;
     523             :     // Don't reset the scaling, because it is set before LayoutText
     524           0 : }
     525             : 
     526             : // This method shouldn't be called on windows, since it needs the dc reset
     527           0 : bool GraphiteLayout::LayoutText(ImplLayoutArgs & rArgs)
     528             : {
     529           0 :     bool success = true;
     530           0 :     if (rArgs.mnMinCharPos < rArgs.mnEndCharPos)
     531             :     {
     532           0 :         gr_segment * pSegment = CreateSegment(rArgs);
     533           0 :         if (!pSegment)
     534           0 :             return false;
     535           0 :         success = LayoutGlyphs(rArgs, pSegment);
     536           0 :         if (pSegment)
     537             :         {
     538           0 :             gr_seg_destroy(pSegment);
     539           0 :             pSegment = NULL;
     540             :         }
     541             :     }
     542             :     else
     543             :     {
     544           0 :         clear();
     545             :     }
     546           0 :     return success;
     547             : }
     548             : 
     549           0 : gr_segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs)
     550             : {
     551             :     assert(rArgs.mnLength >= 0);
     552             : 
     553           0 :     gr_segment * pSegment = NULL;
     554             : 
     555             :     // Set the SalLayouts values to be the initial ones.
     556           0 :     SalLayout::AdjustLayout(rArgs);
     557             :     // TODO check if this is needed
     558           0 :     if (mnUnitsPerPixel > 1)
     559           0 :         mfScaling = 1.0f / mnUnitsPerPixel;
     560             : 
     561             :     // Clear out any previous buffers
     562           0 :     clear();
     563           0 :     bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL;
     564             :     try
     565             :     {
     566             :         // Don't set RTL if font doesn't support it otherwise it forces rtl on
     567             :         // everything
     568             :         //if (bRtl && (mrFont.getSupportedScriptDirections() & gr::kfsdcHorizRtl))
     569             :         //    maLayout.setRightToLeft(bRtl);
     570             : 
     571             :         // Context is often needed beyond the specified end, however, we don't
     572             :         // want it if there has been a direction change, since it is hard
     573             :         // to tell between reordering within one direction and multi-directional
     574             :         // text. Extra context, can also cause problems with ligatures stradling
     575             :         // a hyphenation point, so disable if CTL is disabled.
     576           0 :         mnSegCharOffset = rArgs.mnMinCharPos;
     577           0 :         int limit = rArgs.mnEndCharPos;
     578           0 :         if (!(SAL_LAYOUT_COMPLEX_DISABLED & rArgs.mnFlags))
     579             :         {
     580           0 :             const int nSegCharMin = maximum<int>(0, mnMinCharPos - EXTRA_CONTEXT_LENGTH);
     581           0 :             const int nSegCharLimit = minimum(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH);
     582           0 :             if (nSegCharMin < mnSegCharOffset)
     583             :             {
     584             :                 int sameDirEnd = findSameDirLimit(rArgs.mpStr + nSegCharMin,
     585           0 :                     rArgs.mnEndCharPos - nSegCharMin, bRtl);
     586           0 :                 if (sameDirEnd == rArgs.mnEndCharPos)
     587           0 :                     mnSegCharOffset = nSegCharMin;
     588             :             }
     589           0 :             if (nSegCharLimit > limit)
     590             :             {
     591             :                 limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos,
     592           0 :                     nSegCharLimit - rArgs.mnEndCharPos, bRtl);
     593             :             }
     594             :         }
     595             : 
     596           0 :         size_t numchars = gr_count_unicode_characters(gr_utf16, rArgs.mpStr + mnSegCharOffset,
     597           0 :                  rArgs.mpStr + (rArgs.mnLength > limit + 64 ? limit + 64 : rArgs.mnLength), NULL);
     598           0 :         static com::sun::star::uno::Reference< com::sun::star::i18n::XCharacterClassification > xCharClass;
     599           0 :         if ( !xCharClass.is() )
     600           0 :             xCharClass = vcl::unohelper::CreateCharacterClassification();
     601           0 :         size_t numchars2 = rArgs.mnEndCharPos - mnSegCharOffset; // fdo#52540, fdo#68313, fdo#70666 avoid bad ligature replacement
     602           0 :         if (numchars > numchars2 && xCharClass->getType(rArgs.mpStr, numchars2 + 1) == ::com::sun::star::i18n::UnicodeType::LOWERCASE_LETTER)
     603           0 :             numchars = numchars2;
     604           0 :         if (mpFeatures)
     605           0 :             pSegment = gr_make_seg(mpFont, mpFace, 0, mpFeatures->values(), gr_utf16,
     606           0 :                                         rArgs.mpStr + mnSegCharOffset, numchars, bRtl);
     607             :         else
     608             :             pSegment = gr_make_seg(mpFont, mpFace, 0, NULL, gr_utf16,
     609           0 :                                         rArgs.mpStr + mnSegCharOffset, numchars, bRtl);
     610             : 
     611             :         //pSegment = new gr::RangeSegment((gr::Font *)&mrFont, mpTextSrc, &maLayout, mnMinCharPos, limit);
     612           0 :         if (pSegment != NULL)
     613             :         {
     614             : #ifdef GRLAYOUT_DEBUG
     615             :             fprintf(grLog(),"Gr::LayoutText %d-%d, context %d, len %d, numchars %d, rtl %d scaling %f:", rArgs.mnMinCharPos,
     616             :                rArgs.mnEndCharPos, limit, rArgs.mnLength, numchars, bRtl, mfScaling);
     617             :             for (int i = mnSegCharOffset; i < limit; ++i)
     618             :                 fprintf(grLog(), " %04X", rArgs.mpStr[i]);
     619             :             fprintf(grLog(), "\n");
     620             : #endif
     621             :         }
     622             :         else
     623             :         {
     624             : #ifdef GRLAYOUT_DEBUG
     625             :             fprintf(grLog(), "Gr::LayoutText failed: ");
     626             :             for (int i = mnMinCharPos; i < limit; i++)
     627             :             {
     628             :                 fprintf(grLog(), "%04x ", rArgs.mpStr[i]);
     629             :             }
     630             :             fprintf(grLog(), "\n");
     631             : #endif
     632           0 :             clear();
     633           0 :             return NULL;
     634             :         }
     635             :     }
     636           0 :     catch (...)
     637             :     {
     638           0 :         clear();  // destroy the text source and any partially built segments.
     639           0 :         return NULL;
     640             :     }
     641           0 :     return pSegment;
     642             : }
     643             : 
     644           0 : bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr_segment * pSegment)
     645             : {
     646             :     // Calculate the initial character dxs.
     647           0 :     mvCharDxs.assign(mnEndCharPos - mnMinCharPos, -1);
     648           0 :     mvChar2BaseGlyph.assign(mnEndCharPos - mnMinCharPos, -1);
     649           0 :     mvCharBreaks.assign(mnEndCharPos - mnMinCharPos, 0);
     650           0 :     mnWidth = 0;
     651           0 :     if (mvCharDxs.size() > 0)
     652             :     {
     653             :         // Discover all the clusters.
     654             :         try
     655             :         {
     656           0 :             bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL;
     657           0 :             fillFrom(pSegment, rArgs, mfScaling);
     658             : 
     659           0 :             if (bRtl)
     660             :             {
     661             :                 // not needed for adjacent differences, but for mouse clicks to char
     662             :                 std::transform(mvCharDxs.begin(), mvCharDxs.end(), mvCharDxs.begin(),
     663           0 :                     std::bind1st(std::minus<long>(), mnWidth));
     664             :                 // fixup last dx to ensure it always equals the width
     665           0 :                 mvCharDxs[mvCharDxs.size() - 1] = mnWidth;
     666             :             }
     667             :         }
     668           0 :         catch (const std::exception &e)
     669             :         {
     670             : #ifdef GRLAYOUT_DEBUG
     671             :             fprintf(grLog(),"LayoutGlyphs failed %s\n", e.what());
     672             : #else
     673             :             (void)e;
     674             : #endif
     675           0 :             return false;
     676             :         }
     677           0 :         catch (...)
     678             :         {
     679             : #ifdef GRLAYOUT_DEBUG
     680             :             fprintf(grLog(),"LayoutGlyphs failed with exception");
     681             : #endif
     682           0 :             return false;
     683             :         }
     684             :     }
     685             :     else
     686             :     {
     687           0 :         mnWidth = 0;
     688             :     }
     689           0 :     return true;
     690             : }
     691             : 
     692           0 : sal_Int32 GraphiteLayout::GetTextBreak(long maxmnWidth, long char_extra, int factor) const
     693             : {
     694             : #ifdef GRLAYOUT_DEBUG
     695             :     fprintf(grLog(),"Gr::GetTextBreak c[%d-%d) maxWidth %ld char extra %ld factor %d\n",
     696             :         mnMinCharPos, mnEndCharPos, maxmnWidth, char_extra, factor);
     697             : #endif
     698             : 
     699             :     // return quickly if this segment is narrower than the target width
     700           0 :     if (maxmnWidth > mnWidth * factor + char_extra * (mnEndCharPos - mnMinCharPos - 1))
     701           0 :         return -1;
     702             : 
     703           0 :     long nWidth = mvCharDxs[0] * factor;
     704           0 :     long wLastBreak = 0;
     705           0 :     int nLastBreak = -1;
     706           0 :     int nEmergency = -1;
     707           0 :     for (size_t i = 1; i < mvCharDxs.size(); i++)
     708             :     {
     709           0 :         nWidth += char_extra;
     710           0 :         if (nWidth > maxmnWidth) break;
     711           0 :         if (mvChar2BaseGlyph[i] != -1)
     712             :         {
     713           0 :             if (
     714           0 :                 (mvCharBreaks[i] > -35 || (mvCharBreaks[i-1] > 0 && mvCharBreaks[i-1] < 35)) &&
     715           0 :                 (mvCharBreaks[i-1] < 35 || (mvCharBreaks[i] < 0 && mvCharBreaks[i] > -35))
     716             :                )
     717             :             {
     718           0 :                 nLastBreak = static_cast<int>(i);
     719           0 :                 wLastBreak = nWidth;
     720             :             }
     721           0 :             nEmergency = static_cast<int>(i);
     722             :         }
     723           0 :         nWidth += (mvCharDxs[i] - mvCharDxs[i-1]) * factor;
     724             :     }
     725           0 :     int nBreak = mnMinCharPos;
     726           0 :     if (wLastBreak > 9 * maxmnWidth / 10)
     727           0 :         nBreak += nLastBreak;
     728             :     else
     729           0 :         if (nEmergency > -1)
     730           0 :             nBreak += nEmergency;
     731             : 
     732             : #ifdef GRLAYOUT_DEBUG
     733             :     fprintf(grLog(), "Gr::GetTextBreak break after %d, weights(%d, %d)\n", nBreak - mnMinCharPos, mvCharBreaks[nBreak - mnMinCharPos], mvCharBreaks[nBreak - mnMinCharPos - 1]);
     734             : #endif
     735             : 
     736           0 :     if (nBreak > mnEndCharPos)
     737           0 :         nBreak = -1;
     738           0 :     else if (nBreak < mnMinCharPos)
     739           0 :         nBreak = mnMinCharPos;
     740           0 :     return nBreak;
     741             : }
     742             : 
     743           0 : long GraphiteLayout::FillDXArray( sal_Int32* pDXArray ) const
     744             : {
     745           0 :     if (mnEndCharPos == mnMinCharPos)
     746             :         // Then we must be zero width!
     747           0 :         return 0;
     748             : 
     749           0 :     if (pDXArray)
     750             :     {
     751           0 :         for (size_t i = 0; i < mvCharDxs.size(); i++)
     752             :         {
     753             :             assert( (mvChar2BaseGlyph[i] == -1) ||
     754             :                 ((signed)(mvChar2BaseGlyph[i]) < (signed)mvGlyphs.size()));
     755           0 :             if (mvChar2BaseGlyph[i] != -1 &&
     756           0 :                 mvGlyphs[mvChar2BaseGlyph[i]].maGlyphId == GF_DROPPED)
     757             :             {
     758             :                 // when used in MultiSalLayout::GetTextBreak dropped glyphs
     759             :                 // must have zero width
     760           0 :                 pDXArray[i] = 0;
     761             :             }
     762             :             else
     763             :             {
     764           0 :                 pDXArray[i] = mvCharDxs[i];
     765           0 :                 if (i > 0) pDXArray[i] -= mvCharDxs[i-1];
     766             :             }
     767             : #ifdef GRLAYOUT_DEBUG
     768             :             fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]);
     769             : #endif
     770             :         }
     771             :         //std::adjacent_difference(mvCharDxs.begin(), mvCharDxs.end(), pDXArray);
     772             :         //for (size_t i = 0; i < mvCharDxs.size(); i++)
     773             :         //    fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]);
     774             :         //fprintf(grLog(),"FillDX %ld,%d\n", mnWidth, std::accumulate(pDXArray, pDXArray + mvCharDxs.size(), 0));
     775             :     }
     776             : #ifdef GRLAYOUT_DEBUG
     777             :     fprintf(grLog(),"FillDXArray %d-%d=%ld\n", mnMinCharPos, mnEndCharPos, mnWidth);
     778             : #endif
     779           0 :     return mnWidth;
     780             : }
     781             : 
     782           0 : void  GraphiteLayout::AdjustLayout(ImplLayoutArgs& rArgs)
     783             : {
     784           0 :     SalLayout::AdjustLayout(rArgs);
     785           0 :     if(rArgs.mpDXArray)
     786             :     {
     787           0 :         std::vector<int> vDeltaWidths(mvGlyphs.size(), 0);
     788           0 :         ApplyDXArray(rArgs, vDeltaWidths);
     789             : 
     790           0 :         if( (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL) &&
     791           0 :            !(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) )
     792             :         {
     793             :             // check if this is a kashida script
     794           0 :             bool bKashidaScript = false;
     795           0 :             for (int i = rArgs.mnMinCharPos; i < rArgs.mnEndCharPos; i++)
     796             :             {
     797           0 :                 UErrorCode aStatus = U_ZERO_ERROR;
     798           0 :                 UScriptCode scriptCode = uscript_getScript(rArgs.mpStr[i], &aStatus);
     799           0 :                 if (scriptCode == USCRIPT_ARABIC || scriptCode == USCRIPT_SYRIAC)
     800             :                 {
     801           0 :                     bKashidaScript = true;
     802           0 :                     break;
     803             :                 }
     804             :             }
     805           0 :             int nKashidaWidth = 0;
     806           0 :             int nKashidaIndex = getKashidaGlyph(nKashidaWidth);
     807           0 :             if( nKashidaIndex != 0 && bKashidaScript)
     808             :             {
     809           0 :                 kashidaJustify( vDeltaWidths, nKashidaIndex, nKashidaWidth );
     810             :             }
     811           0 :         }
     812             :     }
     813           0 :     else if (rArgs.mnLayoutWidth > 0)
     814             :     {
     815             : #ifdef GRLAYOUT_DEBUG
     816             :         fprintf(grLog(), "AdjustLayout width %ld=>%ld\n", mnWidth, rArgs.mnLayoutWidth);
     817             : #endif
     818           0 :         expandOrCondense(rArgs);
     819             :     }
     820           0 : }
     821             : 
     822           0 : void GraphiteLayout::expandOrCondense(ImplLayoutArgs &rArgs)
     823             : {
     824           0 :     int nDeltaWidth = rArgs.mnLayoutWidth - mnWidth;
     825           0 :     if (nDeltaWidth > 0) // expand, just expand between clusters
     826             :     {
     827             :         // NOTE: for expansion we can use base glyphs (which have IsClusterStart set)
     828             :         // even though they may have been reordered in which case they will have
     829             :         // been placed in a bigger cluster for other purposes.
     830           0 :         int nClusterCount = 0;
     831           0 :         for (size_t j = 0; j < mvGlyphs.size(); j++)
     832             :         {
     833           0 :             if (mvGlyphs[j].IsClusterStart())
     834             :             {
     835           0 :                 ++nClusterCount;
     836             :             }
     837             :         }
     838           0 :         if (nClusterCount > 1)
     839             :         {
     840           0 :             float fExtraPerCluster = static_cast<float>(nDeltaWidth) / static_cast<float>(nClusterCount - 1);
     841           0 :             int nCluster = 0;
     842           0 :             int nOffset = 0;
     843           0 :             for (size_t i = 0; i < mvGlyphs.size(); i++)
     844             :             {
     845           0 :                 if (mvGlyphs[i].IsClusterStart())
     846             :                 {
     847           0 :                     nOffset = static_cast<int>(fExtraPerCluster * nCluster);
     848           0 :                     int nCharIndex = mvGlyph2Char[i];
     849             :                     assert(nCharIndex > -1);
     850           0 :                     if (nCharIndex < mnMinCharPos ||
     851           0 :                         static_cast<size_t>(nCharIndex-mnMinCharPos)
     852           0 :                             >= mvCharDxs.size())
     853             :                     {
     854           0 :                         continue;
     855             :                     }
     856           0 :                     mvCharDxs[nCharIndex-mnMinCharPos] += nOffset;
     857             :                     // adjust char dxs for rest of characters in cluster
     858           0 :                     while (++nCharIndex - mnMinCharPos < static_cast<int>(mvChar2BaseGlyph.size()))
     859             :                     {
     860           0 :                         int nChar2Base = mvChar2BaseGlyph[nCharIndex-mnMinCharPos];
     861           0 :                         if (nChar2Base == -1 || nChar2Base == static_cast<int>(i))
     862           0 :                             mvCharDxs[nCharIndex-mnMinCharPos] += nOffset;
     863             :                         else
     864             :                             break;
     865             :                     }
     866           0 :                     ++nCluster;
     867             :                 }
     868           0 :                 mvGlyphs[i].maLinearPos.X() += nOffset;
     869             :             }
     870             :         }
     871             :     }
     872           0 :     else if (nDeltaWidth < 0)// condense - apply a factor to all glyph positions
     873             :     {
     874           0 :         if (mvGlyphs.empty()) return;
     875           0 :         Glyphs::iterator iLastGlyph = mvGlyphs.begin() + (mvGlyphs.size() - 1);
     876             :         // position last glyph using original width
     877           0 :         float fXFactor = static_cast<float>(rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth) / static_cast<float>(iLastGlyph->maLinearPos.X());
     878             : #ifdef GRLAYOUT_DEBUG
     879             :         fprintf(grLog(), "Condense by factor %f last x%ld\n", fXFactor, iLastGlyph->maLinearPos.X());
     880             : #endif
     881           0 :         if (fXFactor < 0)
     882           0 :             return; // probably a bad mnOrigWidth value
     883           0 :         iLastGlyph->maLinearPos.X() = rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth;
     884           0 :         Glyphs::iterator iGlyph = mvGlyphs.begin();
     885           0 :         while (iGlyph != iLastGlyph)
     886             :         {
     887           0 :             iGlyph->maLinearPos.X() = static_cast<int>(static_cast<float>(iGlyph->maLinearPos.X()) * fXFactor);
     888           0 :             ++iGlyph;
     889             :         }
     890           0 :         for (size_t i = 0; i < mvCharDxs.size(); i++)
     891             :         {
     892           0 :             mvCharDxs[i] = static_cast<int>(fXFactor * static_cast<float>(mvCharDxs[i]));
     893             :         }
     894             :     }
     895           0 :     mnWidth = rArgs.mnLayoutWidth;
     896             : }
     897             : 
     898           0 : void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector<int> & rDeltaWidth)
     899             : {
     900           0 :     const size_t nChars = args.mnEndCharPos - args.mnMinCharPos;
     901           0 :     if (nChars == 0) return;
     902             : 
     903             : #ifdef GRLAYOUT_DEBUG
     904             :     for (size_t iDx = 0; iDx < mvCharDxs.size(); iDx++)
     905             :          fprintf(grLog(),"%d,%d,%d ", (int)iDx, (int)mvCharDxs[iDx], args.mpDXArray[iDx]);
     906             :     fprintf(grLog(),"ApplyDx\n");
     907             : #endif
     908           0 :     bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL;
     909           0 :     int nXOffset = 0;
     910           0 :     if (bRtl)
     911             :     {
     912           0 :         nXOffset = args.mpDXArray[nChars - 1] - mvCharDxs[nChars - 1];
     913             :     }
     914           0 :     int nPrevClusterGlyph = (bRtl)? (signed)mvGlyphs.size() : -1;
     915           0 :     int nPrevClusterLastChar = -1;
     916           0 :     for (size_t i = 0; i < nChars; i++)
     917             :     {
     918           0 :         int nChar2Base = mvChar2BaseGlyph[i];
     919           0 :         if ((nChar2Base > -1) && (nChar2Base != nPrevClusterGlyph))
     920             :         {
     921             :             assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size()));
     922           0 :             GlyphItem & gi = mvGlyphs[nChar2Base];
     923           0 :             if (!gi.IsClusterStart())
     924           0 :                 continue;
     925             : 
     926             :             // find last glyph of this cluster
     927           0 :             size_t j = i + 1;
     928           0 :             int nLastChar = i;
     929           0 :             int nLastGlyph = nChar2Base;
     930           0 :             int nChar2BaseJ = -1;
     931           0 :             for (; j < nChars; j++)
     932             :             {
     933           0 :                 nChar2BaseJ = mvChar2BaseGlyph[j];
     934             :                 assert((nChar2BaseJ >= -1) && (nChar2BaseJ < (signed)mvGlyphs.size()));
     935           0 :                 if (nChar2BaseJ != -1 )
     936             :                 {
     937           0 :                     nLastGlyph = nChar2BaseJ + ((bRtl)? +1 : -1);
     938           0 :                     nLastChar = j - 1;
     939           0 :                     break;
     940             :                 }
     941             :             }
     942           0 :             if (nLastGlyph < 0)
     943             :             {
     944           0 :                 nLastGlyph = nChar2Base;
     945             :             }
     946             :             // Its harder to find the last glyph rtl, since the first of
     947             :             // cluster is still on the left so we need to search towards
     948             :             // the previous cluster to the right
     949           0 :             if (bRtl)
     950             :             {
     951           0 :                 nLastGlyph = nChar2Base;
     952           0 :                 while (nLastGlyph + 1 < (signed)mvGlyphs.size() &&
     953           0 :                        !mvGlyphs[nLastGlyph+1].IsClusterStart())
     954             :                 {
     955           0 :                     ++nLastGlyph;
     956             :                 }
     957             :             }
     958           0 :             if (j == nChars)
     959             :             {
     960           0 :                 nLastChar = nChars - 1;
     961           0 :                 if (!bRtl) nLastGlyph = mvGlyphs.size() - 1;
     962             :             }
     963           0 :             int nBaseCount = 0;
     964             :             // count bases within cluster - may be more than 1 with reordering
     965           0 :             for (int k = nChar2Base; k <= nLastGlyph; k++)
     966             :             {
     967           0 :                 if (mvGlyphs[k].IsClusterStart()) ++nBaseCount;
     968             :             }
     969             :             assert((nLastChar > -1) && (nLastChar < (signed)nChars));
     970           0 :             long nNewClusterWidth = args.mpDXArray[nLastChar];
     971           0 :             long nOrigClusterWidth = mvCharDxs[nLastChar];
     972           0 :             long nDGlyphOrigin = 0;
     973           0 :             if (nPrevClusterLastChar > - 1)
     974             :             {
     975             :                 assert(nPrevClusterLastChar < (signed)nChars);
     976           0 :                 nNewClusterWidth -= args.mpDXArray[nPrevClusterLastChar];
     977           0 :                 nOrigClusterWidth -= mvCharDxs[nPrevClusterLastChar];
     978           0 :                 nDGlyphOrigin = args.mpDXArray[nPrevClusterLastChar] - mvCharDxs[nPrevClusterLastChar];
     979             :             }
     980           0 :             long nDWidth = nNewClusterWidth - nOrigClusterWidth;
     981             : #ifdef GRLAYOUT_DEBUG
     982             :             fprintf(grLog(), "c%lu last glyph %d/%lu\n", i, nLastGlyph, mvGlyphs.size());
     983             : #endif
     984             :             assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size()));
     985           0 :             mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
     986           0 :             if (gi.maGlyphId != GF_DROPPED)
     987           0 :                 mvGlyphs[nLastGlyph].mnNewWidth += nDWidth;
     988             :             else
     989           0 :                 nDGlyphOrigin += nDWidth;
     990           0 :             long nDOriginPerBase = (nBaseCount > 0)? nDWidth / nBaseCount : 0;
     991           0 :             nBaseCount = -1;
     992             :             // update glyph positions
     993           0 :             if (bRtl)
     994             :             {
     995           0 :                 for (int n = nChar2Base; n <= nLastGlyph; n++)
     996             :                 {
     997           0 :                     if (mvGlyphs[n].IsClusterStart()) ++nBaseCount;
     998             :                     assert((n > - 1) && (n < (signed)mvGlyphs.size()));
     999           0 :                     mvGlyphs[n].maLinearPos.X() += -(nDGlyphOrigin + nDOriginPerBase * nBaseCount) + nXOffset;
    1000             :                 }
    1001             :             }
    1002             :             else
    1003             :             {
    1004           0 :                 for (int n = nChar2Base; n <= nLastGlyph; n++)
    1005             :                 {
    1006           0 :                     if (mvGlyphs[n].IsClusterStart()) ++nBaseCount;
    1007             :                     assert((n > - 1) && (n < (signed)mvGlyphs.size()));
    1008           0 :                     mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + (nDOriginPerBase * nBaseCount) + nXOffset;
    1009             :                 }
    1010             :             }
    1011           0 :             rDeltaWidth[nChar2Base] = nDWidth;
    1012             : #ifdef GRLAYOUT_DEBUG
    1013             :             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());
    1014             : #endif
    1015           0 :             nPrevClusterGlyph = nChar2Base;
    1016           0 :             nPrevClusterLastChar = nLastChar;
    1017           0 :             i = nLastChar;
    1018             :         }
    1019             :     }
    1020             :     // Update the dx vector with the new values.
    1021             :     std::copy(args.mpDXArray, args.mpDXArray + nChars,
    1022           0 :       mvCharDxs.begin() + (args.mnMinCharPos - mnMinCharPos));
    1023             : #ifdef GRLAYOUT_DEBUG
    1024             :     fprintf(grLog(),"ApplyDx %d(%ld)\n", args.mpDXArray[nChars - 1], mnWidth);
    1025             : #endif
    1026           0 :     mnWidth = args.mpDXArray[nChars - 1];
    1027             : }
    1028             : 
    1029           0 : void GraphiteLayout::kashidaJustify(std::vector<int>& rDeltaWidths, sal_GlyphId nKashidaIndex, int nKashidaWidth)
    1030             : {
    1031             :     // skip if the kashida glyph in the font looks suspicious
    1032           0 :     if( nKashidaWidth <= 0 )
    1033           0 :         return;
    1034             : 
    1035             :     // calculate max number of needed kashidas
    1036           0 :     Glyphs::iterator i = mvGlyphs.begin();
    1037           0 :     int nKashidaCount = 0;
    1038           0 :     int nOrigGlyphIndex = -1;
    1039           0 :     int nGlyphIndex = -1;
    1040           0 :     while (i != mvGlyphs.end())
    1041             :     {
    1042           0 :         nOrigGlyphIndex++;
    1043           0 :         nGlyphIndex++;
    1044             :         // only inject kashidas in RTL contexts
    1045           0 :         if( !(*i).IsRTLGlyph() )
    1046             :         {
    1047           0 :             ++i;
    1048           0 :             continue;
    1049             :         }
    1050             :         // no kashida-injection for blank justified expansion either
    1051           0 :         if( IsSpacingGlyph( (*i).maGlyphId ) )
    1052             :         {
    1053           0 :             ++i;
    1054           0 :             continue;
    1055             :         }
    1056             :         // calculate gap, ignore if too small
    1057           0 :         int nGapWidth = rDeltaWidths[nOrigGlyphIndex];
    1058             :         // worst case is one kashida even for mini-gaps
    1059           0 :         if( 3 * nGapWidth < nKashidaWidth )
    1060             :         {
    1061           0 :             ++i;
    1062           0 :             continue;
    1063             :         }
    1064           0 :         nKashidaCount = 1 + (nGapWidth / nKashidaWidth);
    1065             : #ifdef GRLAYOUT_DEBUG
    1066             :         printf("inserting %d kashidas at %u\n", nKashidaCount, (*i).maGlyphId);
    1067             : #endif
    1068           0 :         GlyphItem glyphItem = *i;
    1069           0 :         Point aPos(0, 0);
    1070           0 :         aPos.X() = (*i).maLinearPos.X();
    1071             :         GlyphItem newGi(glyphItem.mnCharPos, nKashidaIndex, aPos,
    1072           0 :                 GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth);
    1073           0 :         mvGlyphs.reserve(mvGlyphs.size() + nKashidaCount);
    1074           0 :         i = mvGlyphs.begin() + nGlyphIndex;
    1075           0 :         mvGlyphs.insert(i, nKashidaCount, newGi);
    1076           0 :         i = mvGlyphs.begin() + nGlyphIndex;
    1077           0 :         nGlyphIndex += nKashidaCount;
    1078             :         // now fix up the kashida positions
    1079           0 :         for (int j = 0; j < nKashidaCount; j++)
    1080             :         {
    1081           0 :             (*(i)).maLinearPos.X() -= nGapWidth;
    1082           0 :             nGapWidth -= nKashidaWidth;
    1083           0 :             ++i;
    1084             :         }
    1085             : 
    1086             :         // fixup rightmost kashida for gap remainder
    1087           0 :         if( nGapWidth < 0 )
    1088             :         {
    1089           0 :             if( nKashidaCount <= 1 )
    1090           0 :                 nGapWidth /= 2;               // for small gap move kashida to middle
    1091           0 :             (*(i-1)).mnNewWidth += nGapWidth;  // adjust kashida width to gap width
    1092           0 :             (*(i-1)).maLinearPos.X() += nGapWidth;
    1093             :         }
    1094             : 
    1095           0 :         (*i).mnNewWidth = (*i).mnOrigWidth;
    1096           0 :         ++i;
    1097             :     }
    1098             : 
    1099             : }
    1100             : 
    1101           0 : void GraphiteLayout::GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const
    1102             : {
    1103             :     // For each character except the last discover the caret positions
    1104             :     // immediately before and after that character.
    1105             :     // This is used for underlines in the GUI amongst other things.
    1106             :     // It may be used from MultiSalLayout, in which case it must take into account
    1107             :     // glyphs that have been moved.
    1108           0 :     std::fill(pCaretXArray, pCaretXArray + nArraySize, -1);
    1109             :     // the layout method doesn't modify the layout even though it isn't
    1110             :     // const in the interface
    1111           0 :     bool bRtl = (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL);//const_cast<GraphiteLayout*>(this)->maLayout.rightToLeft();
    1112           0 :     int prevBase = -1;
    1113           0 :     long prevClusterWidth = 0;
    1114           0 :     for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast<int>(mvCharDxs.size()); ++nCharSlot, i+=2)
    1115             :     {
    1116           0 :         if (mvChar2BaseGlyph[nCharSlot] != -1)
    1117             :         {
    1118           0 :             int nChar2Base = mvChar2BaseGlyph[nCharSlot];
    1119             :             assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size()));
    1120           0 :             GlyphItem gi = mvGlyphs[nChar2Base];
    1121           0 :             if (gi.maGlyphId == GF_DROPPED)
    1122             :             {
    1123           0 :                 continue;
    1124             :             }
    1125           0 :             int nCluster = nChar2Base;
    1126           0 :             long origClusterWidth = gi.mnNewWidth;
    1127           0 :             long nMin = gi.maLinearPos.X();
    1128           0 :             long nMax = gi.maLinearPos.X() + gi.mnNewWidth;
    1129             :             // attached glyphs are always stored after their base rtl or ltr
    1130           0 :             while (++nCluster < static_cast<int>(mvGlyphs.size()) &&
    1131           0 :                 !mvGlyphs[nCluster].IsClusterStart())
    1132             :             {
    1133           0 :                 origClusterWidth += mvGlyphs[nCluster].mnNewWidth;
    1134           0 :                 if (mvGlyph2Char[nCluster] == nCharSlot)
    1135             :                 {
    1136           0 :                     nMin = minimum(nMin, mvGlyphs[nCluster].maLinearPos.X());
    1137           0 :                     nMax = maximum(nMax, mvGlyphs[nCluster].maLinearPos.X() + mvGlyphs[nCluster].mnNewWidth);
    1138             :                 }
    1139             :             }
    1140           0 :             if (bRtl)
    1141             :             {
    1142           0 :                 pCaretXArray[i+1] = nMin;
    1143           0 :                 pCaretXArray[i] = nMax;
    1144             :             }
    1145             :             else
    1146             :             {
    1147           0 :                 pCaretXArray[i] = nMin;
    1148           0 :                 pCaretXArray[i+1] = nMax;
    1149             :             }
    1150           0 :             prevBase = nChar2Base;
    1151           0 :             prevClusterWidth = origClusterWidth;
    1152             :         }
    1153           0 :         else if (prevBase > -1)
    1154             :         {
    1155             :             // this could probably be improved
    1156             :             assert((prevBase > -1) && (prevBase < (signed)mvGlyphs.size()));
    1157           0 :             GlyphItem gi = mvGlyphs[prevBase];
    1158           0 :             int nGlyph = prevBase + 1;
    1159             :             // try to find a better match, otherwise default to complete cluster
    1160           0 :             for (; nGlyph < static_cast<int>(mvGlyphs.size()) &&
    1161           0 :                  !mvGlyphs[nGlyph].IsClusterStart(); nGlyph++)
    1162             :             {
    1163           0 :                 if (mvGlyph2Char[nGlyph] == nCharSlot)
    1164             :                 {
    1165           0 :                     gi = mvGlyphs[nGlyph];
    1166           0 :                     break;
    1167             :                 }
    1168             :             }
    1169             :             // if no match position at end of cluster
    1170           0 :             if (nGlyph == static_cast<int>(mvGlyphs.size()) ||
    1171           0 :                 mvGlyphs[nGlyph].IsClusterStart())
    1172             :             {
    1173           0 :                 if (bRtl)
    1174             :                 {
    1175           0 :                     pCaretXArray[i+1] = gi.maLinearPos.X();
    1176           0 :                     pCaretXArray[i] = gi.maLinearPos.X();
    1177             :                 }
    1178             :                 else
    1179             :                 {
    1180           0 :                     pCaretXArray[i] = gi.maLinearPos.X() + prevClusterWidth;
    1181           0 :                     pCaretXArray[i+1] = gi.maLinearPos.X() + prevClusterWidth;
    1182             :                 }
    1183             :             }
    1184             :             else
    1185             :             {
    1186           0 :                 if (bRtl)
    1187             :                 {
    1188           0 :                     pCaretXArray[i+1] = gi.maLinearPos.X();
    1189           0 :                     pCaretXArray[i] = gi.maLinearPos.X() + gi.mnNewWidth;
    1190             :                 }
    1191             :                 else
    1192             :                 {
    1193           0 :                     pCaretXArray[i] = gi.maLinearPos.X();
    1194           0 :                     pCaretXArray[i+1] = gi.maLinearPos.X() + gi.mnNewWidth;
    1195             :                 }
    1196             :             }
    1197             :         }
    1198             :         else
    1199             :         {
    1200           0 :             pCaretXArray[i] = pCaretXArray[i+1] = 0;
    1201             :         }
    1202             : #ifdef GRLAYOUT_DEBUG
    1203             :         fprintf(grLog(),"%d,%d-%d\t", nCharSlot, pCaretXArray[i], pCaretXArray[i+1]);
    1204             : #endif
    1205             :     }
    1206             : #ifdef GRLAYOUT_DEBUG
    1207             :     fprintf(grLog(),"\n");
    1208             : #endif
    1209           0 : }
    1210             : 
    1211             : // GetNextGlyphs returns a contiguous sequence of glyphs that can be
    1212             : // rendered together. It should never return a dropped glyph.
    1213             : // The glyph_slot returned should be the index of the next visible
    1214             : // glyph after the last glyph returned by this call.
    1215             : // The char_index array should be filled with the characters corresponding
    1216             : // to each glyph returned.
    1217             : // glyph_adv array should be a virtual width such that if successive
    1218             : // glyphs returned by this method are added one after the other they
    1219             : // have the correct spacing.
    1220             : // The logic in this method must match that expected in MultiSalLayout which
    1221             : // is used when glyph fallback is in operation.
    1222           0 : int GraphiteLayout::GetNextGlyphs( int length, sal_GlyphId * glyph_out,
    1223             :         ::Point & aPosOut, int &glyph_slot, sal_Int32 * glyph_adv, int *char_index,
    1224             :         const PhysicalFontFace** /*pFallbackFonts*/ ) const
    1225             : {
    1226             :   // Sanity check on the slot index.
    1227           0 :   if (glyph_slot >= signed(mvGlyphs.size()))
    1228             :   {
    1229           0 :     glyph_slot = mvGlyphs.size();
    1230           0 :     return 0;
    1231             :   }
    1232             :   assert(glyph_slot >= 0);
    1233             :   // Find the first glyph in the substring.
    1234           0 :   for (; glyph_slot < signed(mvGlyphs.size()) &&
    1235           0 :           ((mvGlyphs.begin() + glyph_slot)->maGlyphId == GF_DROPPED);
    1236             :           ++glyph_slot) {};
    1237             : 
    1238             :   // Update the length
    1239           0 :   const int nGlyphSlotEnd = minimum(size_t(glyph_slot + length), mvGlyphs.size());
    1240             : 
    1241             :   // We're all out of glyphs here.
    1242           0 :   if (glyph_slot == nGlyphSlotEnd)
    1243             :   {
    1244           0 :     return 0;
    1245             :   }
    1246             : 
    1247             :   // Find as many glyphs as we can which can be drawn in one go.
    1248           0 :   Glyphs::const_iterator glyph_itr = mvGlyphs.begin() + glyph_slot;
    1249           0 :   const int         glyph_slot_begin = glyph_slot;
    1250           0 :   const int            initial_y_pos = glyph_itr->maLinearPos.Y();
    1251             : 
    1252             :   // Set the position to the position of the start glyph.
    1253           0 :   ::Point aStartPos = glyph_itr->maLinearPos;
    1254             :   //aPosOut = glyph_itr->maLinearPos;
    1255           0 :   aPosOut = GetDrawPosition(aStartPos);
    1256             : 
    1257             :   for (;;)  // Forever
    1258             :   {
    1259             :      // last index of the range from glyph_to_chars does not include this glyph
    1260           0 :      if (char_index)
    1261             :      {
    1262           0 :          if (glyph_slot >= (signed)mvGlyph2Char.size())
    1263             :          {
    1264           0 :             *char_index++ = mnMinCharPos + mvCharDxs.size();
    1265             :          }
    1266             :          else
    1267             :          {
    1268             :             assert(glyph_slot > -1);
    1269           0 :             if (mvGlyph2Char[glyph_slot] == -1)
    1270           0 :                 *char_index++ = mnMinCharPos + mvCharDxs.size();
    1271             :             else
    1272           0 :                 *char_index++ = mvGlyph2Char[glyph_slot];
    1273             :          }
    1274             :      }
    1275             :      // Copy out this glyphs data.
    1276           0 :      ++glyph_slot;
    1277           0 :      *glyph_out++ = glyph_itr->maGlyphId;
    1278             : 
    1279             :      // Find the actual advance - this must be correct if called from
    1280             :      // MultiSalLayout::AdjustLayout which requests one glyph at a time.
    1281           0 :      const long nGlyphAdvance = (glyph_slot == static_cast<int>(mvGlyphs.size()))?
    1282           0 :           glyph_itr->mnNewWidth :
    1283           0 :           ((glyph_itr+1)->maLinearPos.X() - glyph_itr->maLinearPos.X());
    1284             : 
    1285             : #ifdef GRLAYOUT_DEBUG
    1286             :     fprintf(grLog(),"GetNextGlyphs g%d gid%d c%d x%ld,%ld adv%ld, pos %ld,%ld\n",
    1287             :             glyph_slot - 1, glyph_itr->maGlyphId,
    1288             :             mvGlyph2Char[glyph_slot-1], glyph_itr->maLinearPos.X(), glyph_itr->maLinearPos.Y(), nGlyphAdvance,
    1289             :             aPosOut.X(), aPosOut.Y());
    1290             : #endif
    1291             : 
    1292           0 :      if (glyph_adv)  // If we are returning advance store it.
    1293           0 :        *glyph_adv++ = nGlyphAdvance;
    1294             :      else // Stop when next advance is unexpected.
    1295           0 :        if (glyph_itr->mnOrigWidth != nGlyphAdvance)  break;
    1296             : 
    1297             :      // Have fetched all the glyphs we need to
    1298           0 :      if (glyph_slot == nGlyphSlotEnd)
    1299           0 :          break;
    1300             : 
    1301           0 :      ++glyph_itr;
    1302             :      // Stop when next y position is unexpected.
    1303           0 :      if (initial_y_pos != glyph_itr->maLinearPos.Y())
    1304           0 :        break;
    1305             : 
    1306             :      // Stop if glyph dropped
    1307           0 :      if (glyph_itr->maGlyphId == GF_DROPPED)
    1308           0 :        break;
    1309           0 :   }
    1310           0 :   int numGlyphs = glyph_slot - glyph_slot_begin;
    1311             :   // move the next glyph_slot to a glyph that hasn't been dropped
    1312           0 :   while (glyph_slot < static_cast<int>(mvGlyphs.size()) &&
    1313           0 :          (mvGlyphs.begin() + glyph_slot)->maGlyphId == GF_DROPPED)
    1314           0 :          ++glyph_slot;
    1315           0 :   return numGlyphs;
    1316             : }
    1317             : 
    1318           0 : void GraphiteLayout::MoveGlyph( int nGlyphIndex, long nNewPos )
    1319             : {
    1320             :     // TODO it might be better to actualy implement simplify properly, but this
    1321             :     // needs to be done carefully so the glyph/char maps are maintained
    1322             :     // If a glyph has been dropped then it wasn't returned by GetNextGlyphs, so
    1323             :     // the index here may be wrong
    1324           0 :     while ((mvGlyphs[nGlyphIndex].maGlyphId == GF_DROPPED) &&
    1325           0 :            (nGlyphIndex < (signed)mvGlyphs.size()))
    1326             :     {
    1327           0 :         nGlyphIndex++;
    1328             :     }
    1329           0 :     const long dx = nNewPos - mvGlyphs[nGlyphIndex].maLinearPos.X();
    1330             : 
    1331           0 :     if (dx == 0)  return;
    1332             :     // GenericSalLayout only changes maLinearPos, mvCharDxs doesn't change
    1333             : #ifdef GRLAYOUT_DEBUG
    1334             :     fprintf(grLog(),"Move %d (%ld,%ld) c%d by %ld\n", nGlyphIndex, mvGlyphs[nGlyphIndex].maLinearPos.X(), nNewPos, mvGlyph2Char[nGlyphIndex], dx);
    1335             : #endif
    1336           0 :     for (size_t gi = nGlyphIndex; gi < mvGlyphs.size(); gi++)
    1337             :     {
    1338           0 :         mvGlyphs[gi].maLinearPos.X() += dx;
    1339             :     }
    1340             :     // width does need to be updated for correct fallback
    1341           0 :     mnWidth += dx;
    1342             : }
    1343             : 
    1344           0 : void GraphiteLayout::DropGlyph( int nGlyphIndex )
    1345             : {
    1346           0 :     if(nGlyphIndex >= signed(mvGlyphs.size()))
    1347           0 :         return;
    1348             : 
    1349           0 :     GlyphItem & glyph = mvGlyphs[nGlyphIndex];
    1350           0 :     glyph.maGlyphId = GF_DROPPED;
    1351             : #ifdef GRLAYOUT_DEBUG
    1352             :     fprintf(grLog(),"Dropped %d\n", nGlyphIndex);
    1353             : #endif
    1354             : }
    1355             : 
    1356           0 : void GraphiteLayout::Simplify( bool isBaseLayout )
    1357             : {
    1358           0 :   const sal_GlyphId dropMarker = isBaseLayout ? GF_DROPPED : 0;
    1359             : 
    1360           0 :   Glyphs::iterator gi = mvGlyphs.begin();
    1361             :   // TODO check whether we need to adjust positions here
    1362             :   // MultiSalLayout seems to move the glyphs itself, so it may not be needed.
    1363           0 :   long deltaX = 0;
    1364           0 :   while (gi != mvGlyphs.end())
    1365             :   {
    1366           0 :       if (gi->maGlyphId == dropMarker)
    1367             :       {
    1368           0 :         deltaX += gi->mnNewWidth;
    1369           0 :         gi->mnNewWidth = 0;
    1370             :       }
    1371             :       else
    1372             :       {
    1373           0 :         deltaX = 0;
    1374             :       }
    1375           0 :       ++gi;
    1376             :   }
    1377             : #ifdef GRLAYOUT_DEBUG
    1378             :   fprintf(grLog(),"Simplify base%d dx=%ld newW=%ld\n", isBaseLayout, deltaX, mnWidth - deltaX);
    1379             : #endif
    1380             :   // discard width from trailing dropped glyphs, but not those in the middle
    1381           0 :   mnWidth -= deltaX;
    1382           3 : }
    1383             : 
    1384             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.10