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