xref: /core/vcl/source/gdi/sallayout.cxx (revision 836880d49b5eace490d94a80ab6e7177a4413095)
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 #include <sal/config.h>
21 
22 #include <iostream>
23 #include <iomanip>
24 
25 #include <sal/log.hxx>
26 
27 #include <cstdio>
28 
29 #include <math.h>
30 
31 #include <ImplLayoutArgs.hxx>
32 #include <salgdi.hxx>
33 #include <sallayout.hxx>
34 #include <basegfx/polygon/b2dpolypolygon.hxx>
35 #include <basegfx/matrix/b2dhommatrixtools.hxx>
36 
37 #include <i18nlangtag/lang.h>
38 
39 #include <vcl/svapp.hxx>
40 
41 #include <algorithm>
42 #include <memory>
43 
44 #include <impglyphitem.hxx>
45 
46 // Glyph Flags
47 #define GF_FONTMASK  0xF0000000
48 #define GF_FONTSHIFT 28
49 
50 
GetLocalizedChar(sal_UCS4 nChar,LanguageType eLang)51 sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
52 {
53     // currently only conversion from ASCII digits is interesting
54     if( (nChar < '0') || ('9' < nChar) )
55         return nChar;
56 
57     int nOffset;
58     // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
59     // CAVEAT! To some like Mongolian MS assigned the same primary language
60     // although the script type is different!
61     LanguageType pri = primary(eLang);
62     if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
63         nOffset = 0x0660 - '0';  // arabic-indic digits
64     else if ( pri.anyOf(
65         primary(LANGUAGE_FARSI),
66         primary(LANGUAGE_URDU_PAKISTAN),
67         primary(LANGUAGE_PUNJABI), //???
68         primary(LANGUAGE_SINDHI)))
69         nOffset = 0x06F0 - '0';  // eastern arabic-indic digits
70     else if ( pri == primary(LANGUAGE_BENGALI) )
71         nOffset = 0x09E6 - '0';  // bengali
72     else if ( pri == primary(LANGUAGE_HINDI) )
73         nOffset = 0x0966 - '0';  // devanagari
74     else if ( pri.anyOf(
75         primary(LANGUAGE_AMHARIC_ETHIOPIA),
76         primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
77         // TODO case:
78         nOffset = 0x1369 - '0';  // ethiopic
79     else if ( pri == primary(LANGUAGE_GUJARATI) )
80         nOffset = 0x0AE6 - '0';  // gujarati
81 #ifdef LANGUAGE_GURMUKHI // TODO case:
82     else if ( pri == primary(LANGUAGE_GURMUKHI) )
83         nOffset = 0x0A66 - '0';  // gurmukhi
84 #endif
85     else if ( pri == primary(LANGUAGE_KANNADA) )
86         nOffset = 0x0CE6 - '0';  // kannada
87     else if ( pri == primary(LANGUAGE_KHMER))
88         nOffset = 0x17E0 - '0';  // khmer
89     else if ( pri == primary(LANGUAGE_LAO) )
90         nOffset = 0x0ED0 - '0';  // lao
91     else if ( pri == primary(LANGUAGE_MALAYALAM) )
92         nOffset = 0x0D66 - '0';  // malayalam
93     else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
94     {
95         if (eLang.anyOf(
96              LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
97              LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
98              LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
99                 nOffset = 0x1810 - '0';   // mongolian
100         else
101                 nOffset = 0;              // mongolian cyrillic
102     }
103     else if ( pri == primary(LANGUAGE_BURMESE) )
104         nOffset = 0x1040 - '0';  // myanmar
105     else if ( pri == primary(LANGUAGE_ODIA) )
106         nOffset = 0x0B66 - '0';  // odia
107     else if ( pri == primary(LANGUAGE_TAMIL) )
108         nOffset = 0x0BE7 - '0';  // tamil
109     else if ( pri == primary(LANGUAGE_TELUGU) )
110         nOffset = 0x0C66 - '0';  // telugu
111     else if ( pri == primary(LANGUAGE_THAI) )
112         nOffset = 0x0E50 - '0';  // thai
113     else if ( pri == primary(LANGUAGE_TIBETAN) )
114         nOffset = 0x0F20 - '0';  // tibetan
115     else
116     {
117         nOffset = 0;
118     }
119 
120     nChar += nOffset;
121     return nChar;
122 }
123 
SalLayout()124 SalLayout::SalLayout()
125 :   mnMinCharPos( -1 ),
126     mnEndCharPos( -1 ),
127     maLanguageTag( LANGUAGE_DONTKNOW ),
128     mnOrientation( 0 ),
129     maDrawOffset( 0, 0 ),
130     mbSubpixelPositioning(false)
131 {}
132 
~SalLayout()133 SalLayout::~SalLayout()
134 {}
135 
AdjustLayout(vcl::text::ImplLayoutArgs & rArgs)136 void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
137 {
138     mnMinCharPos  = rArgs.mnMinCharPos;
139     mnEndCharPos  = rArgs.mnEndCharPos;
140     mnOrientation = rArgs.mnOrientation;
141     maLanguageTag = rArgs.maLanguageTag;
142 }
143 
GetDrawPosition(const basegfx::B2DPoint & rRelative) const144 basegfx::B2DPoint SalLayout::GetDrawPosition(const basegfx::B2DPoint& rRelative) const
145 {
146     basegfx::B2DPoint aPos{maDrawBase};
147     basegfx::B2DPoint aOfs = rRelative + maDrawOffset;
148 
149     if( mnOrientation == 0_deg10 )
150         aPos += aOfs;
151     else
152     {
153         // cache trigonometric results
154         static Degree10 nOldOrientation(0);
155         static double fCos = 1.0, fSin = 0.0;
156         if( nOldOrientation != mnOrientation )
157         {
158             nOldOrientation = mnOrientation;
159             double fRad = toRadians(mnOrientation);
160             fCos = cos( fRad );
161             fSin = sin( fRad );
162         }
163 
164         double fX = aOfs.getX();
165         double fY = aOfs.getY();
166         if (mbSubpixelPositioning)
167         {
168             double nX = +fCos * fX + fSin * fY;
169             double nY = +fCos * fY - fSin * fX;
170             aPos += basegfx::B2DPoint(nX, nY);
171         }
172         else
173         {
174             tools::Long nX = static_cast<tools::Long>( +fCos * fX + fSin * fY );
175             tools::Long nY = static_cast<tools::Long>( +fCos * fY - fSin * fX );
176             aPos += basegfx::B2DPoint(nX, nY);
177         }
178     }
179 
180     return aPos;
181 }
182 
GetOutline(basegfx::B2DPolyPolygonVector & rVector) const183 bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
184 {
185     bool bAllOk = true;
186     bool bOneOk = false;
187 
188     basegfx::B2DPolyPolygon aGlyphOutline;
189 
190     basegfx::B2DPoint aPos;
191     const GlyphItem* pGlyph;
192     int nStart = 0;
193     const LogicalFontInstance* pGlyphFont;
194     while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
195     {
196         // get outline of individual glyph, ignoring "empty" glyphs
197         bool bSuccess = pGlyph->GetGlyphOutline(pGlyphFont, aGlyphOutline);
198         bAllOk &= bSuccess;
199         bOneOk |= bSuccess;
200         // only add non-empty outlines
201         if( bSuccess && (aGlyphOutline.count() > 0) )
202         {
203             if( aPos.getX() || aPos.getY() )
204             {
205                 aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.getX(), aPos.getY()));
206             }
207 
208             // insert outline at correct position
209             rVector.push_back( aGlyphOutline );
210         }
211     }
212 
213     return (bAllOk && bOneOk);
214 }
215 
216 // No need to expand to the next pixel, when the character only covers its tiny fraction
trimInsignificant(double n)217 static double trimInsignificant(double n)
218 {
219     return std::abs(n) >= 0x1p53 ? n : std::round(n * 1e5) / 1e5;
220 }
221 
GetBoundRect(basegfx::B2DRectangle & rRect) const222 bool SalLayout::GetBoundRect(basegfx::B2DRectangle& rRect) const
223 {
224     bool bRet = false;
225     rRect.reset();
226     basegfx::B2DRectangle aRectangle;
227 
228     basegfx::B2DPoint aPos;
229     const GlyphItem* pGlyph;
230     int nStart = 0;
231     const LogicalFontInstance* pGlyphFont;
232     while (GetNextGlyph(&pGlyph, aPos, nStart, &pGlyphFont))
233     {
234         // get bounding rectangle of individual glyph
235         if (pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
236         {
237             if (!aRectangle.isEmpty())
238             {
239                 // translate rectangle to correct position
240                 aRectangle.translate(aPos);
241                 // merge rectangle
242                 rRect.expand(aRectangle);
243             }
244             bRet = true;
245         }
246     }
247 
248     return bRet;
249 }
250 
BoundRect2Rectangle(const basegfx::B2DRectangle & rRect)251 tools::Rectangle SalLayout::BoundRect2Rectangle(const basegfx::B2DRectangle& rRect)
252 {
253     if (rRect.isEmpty())
254         return {};
255 
256     double l = rtl::math::approxFloor(trimInsignificant(rRect.getMinX())),
257            t = rtl::math::approxFloor(trimInsignificant(rRect.getMinY())),
258            r = rtl::math::approxCeil(trimInsignificant(rRect.getMaxX())),
259            b = rtl::math::approxCeil(trimInsignificant(rRect.getMaxY()));
260     assert(std::isfinite(l) && std::isfinite(t) && std::isfinite(r) && std::isfinite(b));
261     return tools::Rectangle(l, t, r, b);
262 }
263 
GetGlyphs() const264 SalLayoutGlyphs SalLayout::GetGlyphs() const
265 {
266     return SalLayoutGlyphs(); // invalid
267 }
268 
FillDXArray(std::vector<double> * pCharWidths,const OUString & rStr) const269 double GenericSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
270 {
271     if (pCharWidths)
272         GetCharWidths(*pCharWidths, rStr);
273 
274     return GetTextWidth();
275 }
276 
FillPartialDXArray(std::vector<double> * pCharWidths,const OUString & rStr,sal_Int32 skipStart,sal_Int32 amt) const277 double GenericSalLayout::FillPartialDXArray(std::vector<double>* pCharWidths, const OUString& rStr,
278                                             sal_Int32 skipStart, sal_Int32 amt) const
279 {
280     if (pCharWidths)
281     {
282         GetCharWidths(*pCharWidths, rStr);
283 
284         // Strip excess characters from the array
285         if (skipStart < static_cast<sal_Int32>(pCharWidths->size()))
286         {
287             std::copy(pCharWidths->begin() + skipStart, pCharWidths->end(), pCharWidths->begin());
288         }
289 
290         pCharWidths->resize(amt, 0.0);
291     }
292 
293     return GetPartialTextWidth(skipStart, amt);
294 }
295 
296 // the text width is the maximum logical extent of all glyphs
GetTextWidth() const297 double GenericSalLayout::GetTextWidth() const
298 {
299     if (!m_GlyphItems.IsValid())
300         return 0;
301 
302     double nWidth = 0;
303     for (auto const& aGlyphItem : m_GlyphItems)
304         nWidth += aGlyphItem.newWidth();
305 
306     return nWidth;
307 }
308 
GetPartialTextWidth(sal_Int32 skipStart,sal_Int32 amt) const309 double GenericSalLayout::GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
310 {
311     if (!m_GlyphItems.IsValid())
312     {
313         return 0;
314     }
315 
316     auto skipEnd = skipStart + amt;
317     double nWidth = 0.0;
318     for (auto const& aGlyphItem : m_GlyphItems)
319     {
320         auto pos = aGlyphItem.charPos();
321         if (pos >= skipStart && pos < skipEnd)
322         {
323             nWidth += aGlyphItem.newWidth();
324         }
325     }
326 
327     return nWidth;
328 }
329 
Justify(double nNewWidth)330 void GenericSalLayout::Justify(double nNewWidth)
331 {
332     double nOldWidth = GetTextWidth();
333     if( !nOldWidth || nNewWidth==nOldWidth )
334         return;
335 
336     if (!m_GlyphItems.IsValid())
337     {
338         return;
339     }
340     // find rightmost glyph, it won't get stretched
341     std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.begin();
342     pGlyphIterRight += m_GlyphItems.size() - 1;
343     std::vector<GlyphItem>::iterator pGlyphIter;
344     // count stretchable glyphs
345     int nStretchable = 0;
346     double nMaxGlyphWidth = 0.0;
347     for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
348     {
349         if( !pGlyphIter->IsInCluster() )
350             ++nStretchable;
351         if (nMaxGlyphWidth < pGlyphIter->origWidth())
352             nMaxGlyphWidth = pGlyphIter->origWidth();
353     }
354 
355     // move rightmost glyph to requested position
356     auto nRightGlyphOffset = nOldWidth - pGlyphIterRight->linearPos().getX();
357     nOldWidth -= nRightGlyphOffset;
358 
359     if( nOldWidth <= 0.0 )
360         return;
361     if( nNewWidth < nMaxGlyphWidth)
362         nNewWidth = nMaxGlyphWidth;
363     nNewWidth -= nRightGlyphOffset;
364     pGlyphIterRight->setLinearPosX( nNewWidth );
365 
366     // justify glyph widths and positions
367     double nDiffWidth = nNewWidth - nOldWidth;
368     if( nDiffWidth >= 0.0 ) // expanded case
369     {
370         // expand width by distributing space between glyphs evenly
371         double nDeltaSum = 0.0;
372         for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
373         {
374             // move glyph to justified position
375             pGlyphIter->adjustLinearPosX(nDeltaSum);
376 
377             // do not stretch non-stretchable glyphs
378             if( pGlyphIter->IsInCluster() || (nStretchable <= 0) )
379                 continue;
380 
381             // distribute extra space equally to stretchable glyphs
382             double nDeltaWidth = nDiffWidth / nStretchable--;
383             nDiffWidth     -= nDeltaWidth;
384             pGlyphIter->addNewWidth(nDeltaWidth);
385             nDeltaSum      += nDeltaWidth;
386         }
387     }
388     else // condensed case
389     {
390         // squeeze width by moving glyphs proportionally
391         double fSqueeze = nNewWidth / nOldWidth;
392         if(m_GlyphItems.size() > 1)
393         {
394             for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;)
395             {
396                 double nX = pGlyphIter->linearPos().getX();
397                 nX = nX * fSqueeze;
398                 pGlyphIter->setLinearPosX( nX );
399             }
400         }
401         // adjust glyph widths to new positions
402         for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
403             pGlyphIter->setNewWidth( pGlyphIter[1].linearPos().getX() - pGlyphIter[0].linearPos().getX());
404     }
405 }
406 
407 // returns asian kerning values in quarter of character width units
408 // to enable automatic halfwidth substitution for fullwidth punctuation
409 // return value is negative for l, positive for r, zero for neutral
410 // TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
lcl_CalcAsianKerning(sal_UCS4 c,bool bLeft)411 static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
412 {
413     // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
414     static const signed char nTable[0x30] =
415     {
416          0, -2, -2,  0,   0,  0,  0,  0,  +2, -2, +2, -2,  +2, -2, +2, -2,
417         +2, -2,  0,  0,  +2, -2, +2, -2,   0,  0,  0,  0,   0, +2, -2, -2,
418          0,  0,  0,  0,   0,  0,  0,  0,   0,  0, -2, -2,  +2, +2, -2, -2
419     };
420 
421     int nResult = 0;
422     if( (c >= 0x3000) && (c < 0x3030) )
423         nResult = nTable[ c - 0x3000 ];
424     else switch( c )
425     {
426         case 0x30FB:
427             nResult = bLeft ? -1 : +1;      // 25% left/right/top/bottom
428             break;
429         case 0x2019: case 0x201D:
430         case 0xFF01: case 0xFF09: case 0xFF0C:
431         case 0xFF1A: case 0xFF1B:
432             nResult = -2;
433             break;
434         case 0x2018: case 0x201C:
435         case 0xFF08:
436             nResult = +2;
437             break;
438         default:
439             break;
440     }
441 
442     return nResult;
443 }
444 
lcl_CanApplyAsianKerning(sal_Unicode cp)445 static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
446 {
447     return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
448 }
449 
ApplyAsianKerning(std::u16string_view rStr)450 void GenericSalLayout::ApplyAsianKerning(std::u16string_view rStr)
451 {
452     const int nLength = rStr.size();
453     double nOffset = 0;
454 
455     for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(),
456                                           pGlyphIterEnd = m_GlyphItems.end();
457          pGlyphIter != pGlyphIterEnd; ++pGlyphIter)
458     {
459         const int n = pGlyphIter->charPos();
460         if (n < nLength - 1)
461         {
462             // ignore code ranges that are not affected by asian punctuation compression
463             const sal_Unicode cCurrent = rStr[n];
464             if (!lcl_CanApplyAsianKerning(cCurrent))
465                 continue;
466             const sal_Unicode cNext = rStr[n + 1];
467             if (!lcl_CanApplyAsianKerning(cNext))
468                 continue;
469 
470             // calculate compression values
471             const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true);
472             if (nKernCurrent == 0)
473                 continue;
474             const int nKernNext = -lcl_CalcAsianKerning(cNext, false);
475             if (nKernNext == 0)
476                 continue;
477 
478             // apply punctuation compression to logical glyph widths
479             double nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
480             if (nDelta < 0)
481             {
482                 nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
483                 if( pGlyphIter+1 == pGlyphIterEnd )
484                     pGlyphIter->addNewWidth( nDelta );
485                 nOffset += nDelta;
486             }
487         }
488 
489         // adjust the glyph positions to the new glyph widths
490         if( pGlyphIter+1 != pGlyphIterEnd )
491             pGlyphIter->adjustLinearPosX(nOffset);
492     }
493 }
494 
GetCaretPositions(std::vector<double> & rCaretPositions,const OUString & rStr) const495 void GenericSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
496                                          const OUString& rStr) const
497 {
498     const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
499 
500     rCaretPositions.clear();
501     rCaretPositions.resize(nCaretPositions, -1);
502 
503     if (m_GlyphItems.empty())
504         return;
505 
506     std::vector<double> aCharWidths;
507     GetCharWidths(aCharWidths, rStr);
508 
509     // calculate caret positions using glyph array
510     for (auto const& aGlyphItem : m_GlyphItems)
511     {
512         auto nCurrX = aGlyphItem.linearPos().getX() - aGlyphItem.xOffset();
513         auto nCharStart = aGlyphItem.charPos();
514         auto nCharEnd = nCharStart + aGlyphItem.charCount() - 1;
515         if (!aGlyphItem.IsRTLGlyph())
516         {
517             // unchanged positions for LTR case
518             for (int i = nCharStart; i <= nCharEnd; i++)
519             {
520                 int n = i - mnMinCharPos;
521                 int nCurrIdx = 2 * n;
522 
523                 auto nLeft = nCurrX;
524                 nCurrX += aCharWidths[n];
525                 auto nRight = nCurrX;
526 
527                 rCaretPositions[nCurrIdx] = nLeft;
528                 rCaretPositions[nCurrIdx + 1] = nRight;
529             }
530         }
531         else
532         {
533             // reverse positions for RTL case
534             for (int i = nCharEnd; i >= nCharStart; i--)
535             {
536                 int n = i - mnMinCharPos;
537                 int nCurrIdx = 2 * n;
538 
539                 auto nRight = nCurrX;
540                 nCurrX += aCharWidths[n];
541                 auto nLeft = nCurrX;
542 
543                 rCaretPositions[nCurrIdx] = nLeft;
544                 rCaretPositions[nCurrIdx + 1] = nRight;
545             }
546         }
547     }
548 }
549 
GetTextBreak(double nMaxWidth,double nCharExtra,int nFactor) const550 sal_Int32 GenericSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const
551 {
552     std::vector<double> aCharWidths;
553     GetCharWidths(aCharWidths, {});
554 
555     double nWidth = 0;
556     for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
557     {
558         double nDelta =  aCharWidths[ i - mnMinCharPos ] * nFactor;
559 
560         if (nDelta != 0)
561         {
562             nWidth += nDelta;
563             if( nWidth > nMaxWidth )
564                 return i;
565 
566             nWidth += nCharExtra;
567         }
568     }
569 
570     return -1;
571 }
572 
GetNextGlyph(const GlyphItem ** pGlyph,basegfx::B2DPoint & rPos,int & nStart,const LogicalFontInstance ** ppGlyphFont) const573 bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
574                                     basegfx::B2DPoint& rPos, int& nStart,
575                                     const LogicalFontInstance** ppGlyphFont) const
576 {
577     std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.begin();
578     std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.end();
579     pGlyphIter += nStart;
580 
581     // find next glyph in substring
582     for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter )
583     {
584         int n = pGlyphIter->charPos();
585         if( (mnMinCharPos <= n) && (n < mnEndCharPos) )
586             break;
587     }
588 
589     // return zero if no more glyph found
590     if( nStart >= static_cast<int>(m_GlyphItems.size()) )
591         return false;
592 
593     if( pGlyphIter == pGlyphIterEnd )
594         return false;
595 
596     // update return data with glyph info
597     *pGlyph = &(*pGlyphIter);
598     ++nStart;
599     if (ppGlyphFont)
600         *ppGlyphFont = m_GlyphItems.GetFont().get();
601 
602     // calculate absolute position in pixel units
603     basegfx::B2DPoint aRelativePos = pGlyphIter->linearPos();
604 
605     rPos = GetDrawPosition( aRelativePos );
606 
607     return true;
608 }
609 
MoveGlyph(int nStart,double nNewXPos)610 void GenericSalLayout::MoveGlyph(int nStart, double nNewXPos)
611 {
612     if( nStart >= static_cast<int>(m_GlyphItems.size()) )
613         return;
614 
615     std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
616     pGlyphIter += nStart;
617 
618     // the nNewXPos argument determines the new cell position
619     // as RTL-glyphs are right justified in their cell
620     // the cell position needs to be adjusted to the glyph position
621     if( pGlyphIter->IsRTLGlyph() )
622         nNewXPos += pGlyphIter->newWidth() - pGlyphIter->origWidth();
623     // calculate the x-offset to the old position
624     double nXDelta = nNewXPos - pGlyphIter->linearPos().getX() + pGlyphIter->xOffset();
625     // adjust all following glyph positions if needed
626     if( nXDelta != 0 )
627     {
628         for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
629         {
630             pGlyphIter->adjustLinearPosX(nXDelta);
631         }
632     }
633 }
634 
DropGlyph(int nStart)635 void GenericSalLayout::DropGlyph( int nStart )
636 {
637     if( nStart >= static_cast<int>(m_GlyphItems.size()))
638         return;
639 
640     std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
641     pGlyphIter += nStart;
642     pGlyphIter->dropGlyph();
643 }
644 
Simplify(bool bIsBase)645 void GenericSalLayout::Simplify( bool bIsBase )
646 {
647     // remove dropped glyphs inplace
648     size_t j = 0;
649     for(size_t i = 0; i < m_GlyphItems.size(); i++ )
650     {
651         if (bIsBase && m_GlyphItems[i].IsDropped())
652             continue;
653         if (!bIsBase && m_GlyphItems[i].glyphId() == 0)
654             continue;
655 
656         if( i != j )
657         {
658             m_GlyphItems[j] = m_GlyphItems[i];
659         }
660         j += 1;
661     }
662     m_GlyphItems.erase(m_GlyphItems.begin() + j, m_GlyphItems.end());
663 }
664 
MultiSalLayout(std::unique_ptr<SalLayout> pBaseLayout)665 MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
666 :   mnLevel( 1 )
667 ,   mbIncomplete( false )
668 {
669     assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
670 
671     mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
672 }
673 
ReleaseBaseLayout()674 std::unique_ptr<SalLayout> MultiSalLayout::ReleaseBaseLayout()
675 {
676     return std::move(mpLayouts[0]);
677 }
678 
SetIncomplete(bool bIncomplete)679 void MultiSalLayout::SetIncomplete(bool bIncomplete)
680 {
681     mbIncomplete = bIncomplete;
682     maFallbackRuns[mnLevel-1] = ImplLayoutRuns();
683 }
684 
~MultiSalLayout()685 MultiSalLayout::~MultiSalLayout()
686 {
687 }
688 
AddFallback(std::unique_ptr<SalLayout> pFallback,ImplLayoutRuns const & rFallbackRuns)689 void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
690     ImplLayoutRuns const & rFallbackRuns)
691 {
692     assert(dynamic_cast<GenericSalLayout*>(pFallback.get()));
693     if( mnLevel >= MAX_FALLBACK )
694         return;
695 
696     mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release()));
697     maFallbackRuns[ mnLevel-1 ] = rFallbackRuns;
698     ++mnLevel;
699 }
700 
LayoutText(vcl::text::ImplLayoutArgs & rArgs,const SalLayoutGlyphsImpl *)701 bool MultiSalLayout::LayoutText( vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
702 {
703     if( mnLevel <= 1 )
704         return false;
705     if (!mbIncomplete)
706         maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
707     return true;
708 }
709 
AdjustLayout(vcl::text::ImplLayoutArgs & rArgs)710 void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
711 {
712     SalLayout::AdjustLayout( rArgs );
713     vcl::text::ImplLayoutArgs aMultiArgs = rArgs;
714     std::vector<double> aJustificationArray;
715 
716     if (!rArgs.mstJustification.empty() && rArgs.mnLayoutWidth)
717     {
718         // for stretched text in a MultiSalLayout the target width needs to be
719         // distributed by individually adjusting its virtual character widths
720         double nTargetWidth = aMultiArgs.mnLayoutWidth;
721         aMultiArgs.mnLayoutWidth = 0;
722 
723         // we need to get the original unmodified layouts ready
724         for( int n = 0; n < mnLevel; ++n )
725             mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs );
726         // then we can measure the unmodified metrics
727         int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
728         FillDXArray( &aJustificationArray, {} );
729         // #i17359# multilayout is not simplified yet, so calculating the
730         // unjustified width needs handholding; also count the number of
731         // stretchable virtual char widths
732         double nOrigWidth = 0;
733         int nStretchable = 0;
734         for( int i = 0; i < nCharCount; ++i )
735         {
736             // convert array from widths to sum of widths
737             nOrigWidth += aJustificationArray[i];
738             if( aJustificationArray[i] > 0 )
739                 ++nStretchable;
740         }
741 
742         // now we are able to distribute the extra width over the virtual char widths
743         if( nOrigWidth && (nTargetWidth != nOrigWidth) )
744         {
745             double nDiffWidth = nTargetWidth - nOrigWidth;
746             double nWidthSum = 0;
747             for( int i = 0; i < nCharCount; ++i )
748             {
749                 double nJustWidth = aJustificationArray[i];
750                 if( (nJustWidth > 0) && (nStretchable > 0) )
751                 {
752                     double nDeltaWidth = nDiffWidth / nStretchable;
753                     nJustWidth += nDeltaWidth;
754                     nDiffWidth -= nDeltaWidth;
755                     --nStretchable;
756                 }
757                 nWidthSum += nJustWidth;
758                 aJustificationArray[i] = nWidthSum;
759             }
760             if( nWidthSum != nTargetWidth )
761                 aJustificationArray[ nCharCount-1 ] = nTargetWidth;
762 
763             // change the DXArray temporarily (just for the justification)
764             JustificationData stJustData{ rArgs.mnMinCharPos, nCharCount };
765             for (sal_Int32 i = 0; i < nCharCount; ++i)
766             {
767                 stJustData.SetTotalAdvance(rArgs.mnMinCharPos + i, aJustificationArray[i]);
768             }
769 
770             aMultiArgs.SetJustificationData(std::move(stJustData));
771         }
772     }
773 
774     ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mstJustification);
775 }
776 
ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs & rArgs,vcl::text::ImplLayoutArgs & rMultiArgs,const JustificationData & rstJustification)777 void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
778                                            vcl::text::ImplLayoutArgs& rMultiArgs,
779                                            const JustificationData& rstJustification)
780 {
781     // Compute rtl flags, since in some scripts glyphs/char order can be
782     // reversed for a few character sequences e.g. Myanmar
783     std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false);
784     rArgs.ResetPos();
785     bool bRtl;
786     int nRunStart, nRunEnd;
787     while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl))
788     {
789         if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos),
790                             vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true);
791     }
792     rArgs.ResetPos();
793 
794     // prepare "merge sort"
795     int nStartOld[ MAX_FALLBACK ];
796     int nStartNew[ MAX_FALLBACK ];
797     const GlyphItem* pGlyphs[MAX_FALLBACK];
798     bool bValid[MAX_FALLBACK] = { false };
799 
800     basegfx::B2DPoint aPos;
801     int nLevel = 0, n;
802     for( n = 0; n < mnLevel; ++n )
803     {
804         // now adjust the individual components
805         if( n > 0 )
806         {
807             rMultiArgs.maRuns = maFallbackRuns[ n-1 ];
808             rMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
809         }
810         mpLayouts[n]->AdjustLayout( rMultiArgs );
811 
812         // remove unused parts of component
813         if( n > 0 )
814         {
815             if (mbIncomplete && (n == mnLevel-1))
816                 mpLayouts[n]->Simplify( true );
817             else
818                 mpLayouts[n]->Simplify( false );
819         }
820 
821         // prepare merging components
822         nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0;
823         bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]);
824 
825         if( (n > 0) && !bValid[ nLevel ] )
826         {
827             // an empty fallback layout can be released
828             mpLayouts[n].reset();
829         }
830         else
831         {
832             // reshuffle used fallbacks if needed
833             if( nLevel != n )
834             {
835                 mpLayouts[ nLevel ]         = std::move(mpLayouts[ n ]);
836                 maFallbackRuns[ nLevel ]    = maFallbackRuns[ n ];
837             }
838             ++nLevel;
839         }
840     }
841     mnLevel = nLevel;
842 
843     // prepare merge the fallback levels
844     double nXPos = 0;
845     for( n = 0; n < nLevel; ++n )
846         maFallbackRuns[n].ResetPos();
847 
848     int nFirstValid = -1;
849     for( n = 0; n < nLevel; ++n )
850     {
851         if(bValid[n])
852         {
853             nFirstValid = n;
854             break;
855         }
856     }
857     assert(nFirstValid >= 0);
858 
859     // get the next codepoint index that needs fallback
860     int nActiveCharPos = pGlyphs[nFirstValid]->charPos();
861     int nActiveCharIndex = nActiveCharPos - mnMinCharPos;
862     // get the end index of the active run
863     int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ?
864         rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1;
865     int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
866     // merge the fallback levels
867     while( bValid[nFirstValid] && (nLevel > 0))
868     {
869         // find best fallback level
870         for( n = 0; n < nLevel; ++n )
871             if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) )
872                 // fallback level n wins when it requested no further fallback
873                 break;
874         int nFBLevel = n;
875 
876         if( n < nLevel )
877         {
878             // use base(n==0) or fallback(n>=1) level
879             mpLayouts[n]->MoveGlyph( nStartOld[n], nXPos );
880         }
881         else
882         {
883             n = 0;  // keep NotDef in base level
884         }
885 
886         if( n > 0 )
887         {
888             // drop the NotDef glyphs in the base layout run if a fallback run exists
889             //
890             // tdf#163761: The whole algorithm in this outer loop works by advancing through
891             // all of the glyphs and runs in lock-step. The current glyph in the base layout
892             // must not outpace the fallback runs. The following loop does this by breaking
893             // at the end of the current fallback run (which comes from the previous level).
894             while ((maFallbackRuns[n - 1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
895                    && (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos())))
896             {
897                 mpLayouts[0]->DropGlyph( nStartOld[0] );
898                 nStartOld[0] = nStartNew[0];
899                 bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
900 
901                 if( !bValid[nFirstValid] )
902                    break;
903             }
904         }
905 
906         // skip to end of layout run and calculate its advance width
907         double nRunAdvance = 0;
908         bool bKeepNotDef = (nFBLevel >= nLevel);
909         for(;;)
910         {
911             // check for reordered glyphs
912             // tdf#154104: Moved this up in the loop body to handle the case of single-glyph
913             // runs that start on a reordered glyph.
914             if (!rstJustification.empty())
915             {
916                 if (vRtl[nActiveCharPos - mnMinCharPos])
917                 {
918                     if (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
919                         >= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
920                     {
921                         nRunVisibleEndChar = pGlyphs[n]->charPos();
922                     }
923                 }
924                 else if (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
925                          <= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
926                 {
927                     nRunVisibleEndChar = pGlyphs[n]->charPos();
928                 }
929             }
930 
931             nRunAdvance += pGlyphs[n]->newWidth();
932 
933             // proceed to next glyph
934             nStartOld[n] = nStartNew[n];
935             int nOrigCharPos = pGlyphs[n]->charPos();
936             bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]);
937             // break after last glyph of active layout
938             if( !bValid[n] )
939             {
940                 // performance optimization (when a fallback layout is no longer needed)
941                 if( n >= nLevel-1 )
942                     --nLevel;
943                 break;
944             }
945 
946             //If the next character is one which belongs to the next level, then we
947             //are finished here for now, and we'll pick up after the next level has
948             //been processed
949             if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos))
950             {
951                 if (nOrigCharPos < pGlyphs[n]->charPos())
952                 {
953                     if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos()))
954                         break;
955                 }
956                 else if (nOrigCharPos > pGlyphs[n]->charPos())
957                 {
958                     if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos))
959                         break;
960                 }
961             }
962 
963             // break at end of layout run
964             if( n > 0 )
965             {
966                 // skip until end of fallback run
967                 if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos()))
968                     break;
969             }
970             else
971             {
972                 // break when a fallback is needed and available
973                 bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos());
974                 if( bNeedFallback )
975                     if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
976                         break;
977                 // break when change from resolved to unresolved base layout run
978                 if( bKeepNotDef && !bNeedFallback )
979                     { maFallbackRuns[0].NextRun(); break; }
980                 bKeepNotDef = bNeedFallback;
981             }
982         }
983 
984         // if a justification array is available
985         // => use it directly to calculate the corresponding run width
986         if (!rstJustification.empty())
987         {
988             // the run advance is the width from the first char
989             // in the run to the first char in the next run
990             nRunAdvance = 0;
991             nActiveCharIndex = nActiveCharPos - mnMinCharPos;
992             if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
993             {
994                 nRunAdvance -= rstJustification.GetTotalAdvance(nRunVisibleEndChar - 1);
995                 nRunAdvance += rstJustification.GetTotalAdvance(nLastRunEndChar - 1);
996             }
997             else
998             {
999                 nRunAdvance += rstJustification.GetTotalAdvance(nRunVisibleEndChar);
1000                 nRunAdvance -= rstJustification.GetTotalAdvance(nLastRunEndChar);
1001             }
1002             nLastRunEndChar = nRunVisibleEndChar;
1003             nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
1004         }
1005 
1006         // calculate new x position
1007         nXPos += nRunAdvance;
1008 
1009         // prepare for next fallback run
1010         nActiveCharPos = pGlyphs[nFirstValid]->charPos();
1011         // it essential that the runs don't get ahead of themselves and in the
1012         // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
1013         // have already been reached on the base level
1014         for( int i = nFBLevel; --i >= 0;)
1015         {
1016             if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
1017             {
1018                 // tdf#165510: Need to use the direction of the current character,
1019                 // not the direction of the fallback run.
1020                 nActiveCharIndex = nActiveCharPos - mnMinCharPos;
1021                 if (nActiveCharIndex >= 0)
1022                 {
1023                     bRtl = vRtl[nActiveCharIndex];
1024                 }
1025 
1026                 if (bRtl)
1027                 {
1028                     if (nRunStart > nActiveCharPos)
1029                         maFallbackRuns[i].NextRun();
1030                 }
1031                 else
1032                 {
1033                     if (nRunEnd <= nActiveCharPos)
1034                         maFallbackRuns[i].NextRun();
1035                 }
1036             }
1037         }
1038     }
1039 
1040     mpLayouts[0]->Simplify( true );
1041 }
1042 
DrawText(SalGraphics & rGraphics) const1043 void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
1044 {
1045     for( int i = mnLevel; --i >= 0; )
1046     {
1047         SalLayout& rLayout = *mpLayouts[ i ];
1048         rLayout.DrawBase() += maDrawBase;
1049         rLayout.DrawOffset() += maDrawOffset;
1050         rLayout.DrawText( rGraphics );
1051         rLayout.DrawOffset() -= maDrawOffset;
1052         rLayout.DrawBase() -= maDrawBase;
1053     }
1054     // NOTE: now the baselevel font is active again
1055 }
1056 
GetTextBreak(double nMaxWidth,double nCharExtra,int nFactor) const1057 sal_Int32 MultiSalLayout::GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const
1058 {
1059     if( mnLevel <= 0 )
1060         return -1;
1061     if( mnLevel == 1 )
1062         return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
1063 
1064     int nCharCount = mnEndCharPos - mnMinCharPos;
1065     std::vector<double> aCharWidths;
1066     std::vector<double> aFallbackCharWidths;
1067     mpLayouts[0]->FillDXArray( &aCharWidths, {} );
1068 
1069     for( int n = 1; n < mnLevel; ++n )
1070     {
1071         SalLayout& rLayout = *mpLayouts[ n ];
1072         rLayout.FillDXArray( &aFallbackCharWidths, {} );
1073         for( int i = 0; i < nCharCount; ++i )
1074             if( aCharWidths[ i ] == 0 )
1075                 aCharWidths[i] = aFallbackCharWidths[i];
1076     }
1077 
1078     double nWidth = 0;
1079     for( int i = 0; i < nCharCount; ++i )
1080     {
1081         nWidth += aCharWidths[ i ] * nFactor;
1082         if( nWidth > nMaxWidth )
1083             return (i + mnMinCharPos);
1084         nWidth += nCharExtra;
1085     }
1086 
1087     return -1;
1088 }
1089 
GetTextWidth() const1090 double MultiSalLayout::GetTextWidth() const
1091 {
1092     // Measure text width. There might be holes in each SalLayout due to
1093     // missing chars, so we use GetNextGlyph() to get the glyphs across all
1094     // layouts.
1095     int nStart = 0;
1096     basegfx::B2DPoint aPos;
1097     const GlyphItem* pGlyphItem;
1098 
1099     double nWidth = 0;
1100     while (GetNextGlyph(&pGlyphItem, aPos, nStart))
1101         nWidth += pGlyphItem->newWidth();
1102 
1103     return nWidth;
1104 }
1105 
GetPartialTextWidth(sal_Int32 skipStart,sal_Int32 amt) const1106 double MultiSalLayout::GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
1107 {
1108     // Measure text width. There might be holes in each SalLayout due to
1109     // missing chars, so we use GetNextGlyph() to get the glyphs across all
1110     // layouts.
1111     int nStart = 0;
1112     basegfx::B2DPoint aPos;
1113     const GlyphItem* pGlyphItem;
1114 
1115     auto skipEnd = skipStart + amt;
1116     double nWidth = 0;
1117     while (GetNextGlyph(&pGlyphItem, aPos, nStart))
1118     {
1119         auto cpos = pGlyphItem->charPos();
1120         if (cpos >= skipStart && cpos < skipEnd)
1121         {
1122             nWidth += pGlyphItem->newWidth();
1123         }
1124     }
1125 
1126     return nWidth;
1127 }
1128 
FillDXArray(std::vector<double> * pCharWidths,const OUString & rStr) const1129 double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
1130 {
1131     if (pCharWidths)
1132     {
1133         // prepare merging of fallback levels
1134         std::vector<double> aTempWidths;
1135         const int nCharCount = mnEndCharPos - mnMinCharPos;
1136         pCharWidths->clear();
1137         pCharWidths->resize(nCharCount, 0);
1138 
1139         for (int n = mnLevel; --n >= 0;)
1140         {
1141             // query every fallback level
1142             mpLayouts[n]->FillDXArray(&aTempWidths, rStr);
1143 
1144             // calculate virtual char widths using most probable fallback layout
1145             for (int i = 0; i < nCharCount; ++i)
1146             {
1147                 // #i17359# restriction:
1148                 // one char cannot be resolved from different fallbacks
1149                 if ((*pCharWidths)[i] != 0)
1150                     continue;
1151                 double nCharWidth = aTempWidths[i];
1152                 if (!nCharWidth)
1153                     continue;
1154                 (*pCharWidths)[i] = nCharWidth;
1155             }
1156         }
1157     }
1158 
1159     return GetTextWidth();
1160 }
1161 
FillPartialDXArray(std::vector<double> * pCharWidths,const OUString & rStr,sal_Int32 skipStart,sal_Int32 amt) const1162 double MultiSalLayout::FillPartialDXArray(std::vector<double>* pCharWidths, const OUString& rStr,
1163                                           sal_Int32 skipStart, sal_Int32 amt) const
1164 {
1165     if (pCharWidths)
1166     {
1167         FillDXArray(pCharWidths, rStr);
1168 
1169         // Strip excess characters from the array
1170         if (skipStart < static_cast<sal_Int32>(pCharWidths->size()))
1171         {
1172             std::copy(pCharWidths->begin() + skipStart, pCharWidths->end(), pCharWidths->begin());
1173         }
1174 
1175         pCharWidths->resize(amt);
1176     }
1177 
1178     return GetPartialTextWidth(skipStart, amt);
1179 }
1180 
GetCaretPositions(std::vector<double> & rCaretPositions,const OUString & rStr) const1181 void MultiSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
1182                                        const OUString& rStr) const
1183 {
1184     // prepare merging of fallback levels
1185     std::vector<double> aTempPos;
1186     const int nCaretPositions = (mnEndCharPos - mnMinCharPos) * 2;
1187     rCaretPositions.clear();
1188     rCaretPositions.resize(nCaretPositions, -1);
1189 
1190     for (int n = mnLevel; --n >= 0;)
1191     {
1192         // query every fallback level
1193         mpLayouts[n]->GetCaretPositions(aTempPos, rStr);
1194 
1195         // calculate virtual char widths using most probable fallback layout
1196         for (int i = 0; i < nCaretPositions; ++i)
1197         {
1198             // one char cannot be resolved from different fallbacks
1199             if (rCaretPositions[i] != -1)
1200                 continue;
1201             if (aTempPos[i] >= 0)
1202                 rCaretPositions[i] = aTempPos[i];
1203         }
1204     }
1205 }
1206 
GetNextGlyph(const GlyphItem ** pGlyph,basegfx::B2DPoint & rPos,int & nStart,const LogicalFontInstance ** ppGlyphFont) const1207 bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
1208                                   basegfx::B2DPoint& rPos, int& nStart,
1209                                   const LogicalFontInstance** ppGlyphFont) const
1210 {
1211     // NOTE: nStart is tagged with current font index
1212     int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
1213     nStart &= ~GF_FONTMASK;
1214     for(; nLevel < mnLevel; ++nLevel, nStart=0 )
1215     {
1216         GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
1217         if (rLayout.GetNextGlyph(pGlyph, rPos, nStart, ppGlyphFont))
1218         {
1219             int nFontTag = nLevel << GF_FONTSHIFT;
1220             nStart |= nFontTag;
1221             rPos += maDrawBase + maDrawOffset;
1222             return true;
1223         }
1224     }
1225 
1226     return false;
1227 }
1228 
GetOutline(basegfx::B2DPolyPolygonVector & rPPV) const1229 bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
1230 {
1231     bool bRet = false;
1232 
1233     for( int i = mnLevel; --i >= 0; )
1234     {
1235         SalLayout& rLayout = *mpLayouts[ i ];
1236         rLayout.DrawBase() = maDrawBase;
1237         rLayout.DrawOffset() += maDrawOffset;
1238         bRet |= rLayout.GetOutline(rPPV);
1239         rLayout.DrawOffset() -= maDrawOffset;
1240     }
1241 
1242     return bRet;
1243 }
1244 
HasFontKashidaPositions() const1245 bool MultiSalLayout::HasFontKashidaPositions() const
1246 {
1247     // tdf#163215: VCL cannot suggest valid kashida positions for certain fonts (e.g. AAT).
1248     // In order to strictly validate kashida positions, all fallback fonts must allow it.
1249     for (int n = 0; n < mnLevel; ++n)
1250     {
1251         if (!mpLayouts[n]->HasFontKashidaPositions())
1252         {
1253             return false;
1254         }
1255     }
1256 
1257     return true;
1258 }
1259 
IsKashidaPosValid(int nCharPos,int nNextCharPos) const1260 bool MultiSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
1261 {
1262     // Check the base layout
1263     bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos, nNextCharPos);
1264 
1265     // If base layout returned false, it might be because the character was not
1266     // supported there, so we check fallback layouts.
1267     if (!bValid)
1268     {
1269         for (int i = 1; i < mnLevel; ++i)
1270         {
1271             // - 1 because there is no fallback run for the base layout, IIUC.
1272             if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos) &&
1273                 maFallbackRuns[i - 1].PosIsInAnyRun(nNextCharPos))
1274             {
1275                 bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos, nNextCharPos);
1276                 break;
1277             }
1278         }
1279     }
1280 
1281     return bValid;
1282 }
1283 
GetGlyphs() const1284 SalLayoutGlyphs MultiSalLayout::GetGlyphs() const
1285 {
1286     SalLayoutGlyphs glyphs;
1287     for( int n = 0; n < mnLevel; ++n )
1288         glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone());
1289     return glyphs;
1290 }
1291 
drawSalLayout(void * pSurface,const basegfx::BColor & rTextColor,bool bAntiAliased) const1292 void MultiSalLayout::drawSalLayout(void* pSurface, const basegfx::BColor& rTextColor, bool bAntiAliased) const
1293 {
1294     for( int i = mnLevel; --i >= 0; )
1295     {
1296         Application::GetDefaultDevice()->GetGraphics()->DrawSalLayout(*mpLayouts[ i ], pSurface, rTextColor, bAntiAliased);
1297     }
1298 }
1299 
1300 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1301