bump product version to 7.2.5.1
[LibreOffice.git] / vcl / source / gdi / sallayout.cxx
blob7b8f2c2a2a059fa60d1012bbb37e0caab5d4f586
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <iostream>
21 #include <iomanip>
23 #include <sal/config.h>
24 #include <sal/log.hxx>
26 #include <cstdio>
28 #include <math.h>
30 #include <salgdi.hxx>
31 #include <sallayout.hxx>
32 #include <basegfx/polygon/b2dpolypolygon.hxx>
33 #include <basegfx/matrix/b2dhommatrixtools.hxx>
35 #include <i18nlangtag/lang.h>
37 #include <vcl/svapp.hxx>
39 #include <unicode/ubidi.h>
40 #include <unicode/uchar.h>
42 #include <algorithm>
43 #include <memory>
45 #include <impglyphitem.hxx>
47 // Glyph Flags
48 #define GF_FONTMASK 0xF0000000
49 #define GF_FONTSHIFT 28
52 std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs)
54 #ifndef SAL_LOG_INFO
55 (void) rArgs;
56 #else
57 s << "ImplLayoutArgs{";
59 s << "Flags=";
60 if (rArgs.mnFlags == SalLayoutFlags::NONE)
61 s << 0;
62 else {
63 bool need_or = false;
64 s << "{";
65 #define TEST(x) if (rArgs.mnFlags & SalLayoutFlags::x) { if (need_or) s << "|"; s << #x; need_or = true; }
66 TEST(BiDiRtl);
67 TEST(BiDiStrong);
68 TEST(RightAlign);
69 TEST(DisableKerning);
70 TEST(KerningAsian);
71 TEST(Vertical);
72 TEST(KashidaJustification);
73 TEST(ForFallback);
74 #undef TEST
75 s << "}";
78 const int nLength = rArgs.mrStr.getLength();
80 s << ",Length=" << nLength;
81 s << ",MinCharPos=" << rArgs.mnMinCharPos;
82 s << ",EndCharPos=" << rArgs.mnEndCharPos;
84 s << ",Str=\"";
85 int lim = nLength;
86 if (lim > 10)
87 lim = 7;
88 for (int i = 0; i < lim; i++) {
89 if (rArgs.mrStr[i] == '\n')
90 s << "\\n";
91 else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
92 s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
93 else if (rArgs.mrStr[i] < 0x7F)
94 s << static_cast<char>(rArgs.mrStr[i]);
95 else
96 s << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
98 if (nLength > lim)
99 s << "...";
100 s << "\"";
102 s << ",DXArray=";
103 if (rArgs.mpDXArray) {
104 s << "[";
105 int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
106 lim = count;
107 if (lim > 10)
108 lim = 7;
109 for (int i = 0; i < lim; i++) {
110 s << rArgs.mpDXArray[i];
111 if (i < lim-1)
112 s << ",";
114 if (count > lim) {
115 if (count > lim + 1)
116 s << "...";
117 s << rArgs.mpDXArray[count-1];
119 s << "]";
120 } else
121 s << "NULL";
123 s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
125 s << "}";
127 #endif
128 return s;
131 sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
133 nChar = u_charMirror( nChar );
134 return nChar;
137 sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
139 // currently only conversion from ASCII digits is interesting
140 if( (nChar < '0') || ('9' < nChar) )
141 return nChar;
143 int nOffset;
144 // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
145 // CAVEAT! To some like Mongolian MS assigned the same primary language
146 // although the script type is different!
147 LanguageType pri = primary(eLang);
148 if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) )
149 nOffset = 0x0660 - '0'; // arabic-indic digits
150 else if ( pri.anyOf(
151 primary(LANGUAGE_FARSI),
152 primary(LANGUAGE_URDU_PAKISTAN),
153 primary(LANGUAGE_PUNJABI), //???
154 primary(LANGUAGE_SINDHI)))
155 nOffset = 0x06F0 - '0'; // eastern arabic-indic digits
156 else if ( pri == primary(LANGUAGE_BENGALI) )
157 nOffset = 0x09E6 - '0'; // bengali
158 else if ( pri == primary(LANGUAGE_HINDI) )
159 nOffset = 0x0966 - '0'; // devanagari
160 else if ( pri.anyOf(
161 primary(LANGUAGE_AMHARIC_ETHIOPIA),
162 primary(LANGUAGE_TIGRIGNA_ETHIOPIA)))
163 // TODO case:
164 nOffset = 0x1369 - '0'; // ethiopic
165 else if ( pri == primary(LANGUAGE_GUJARATI) )
166 nOffset = 0x0AE6 - '0'; // gujarati
167 #ifdef LANGUAGE_GURMUKHI // TODO case:
168 else if ( pri == primary(LANGUAGE_GURMUKHI) )
169 nOffset = 0x0A66 - '0'; // gurmukhi
170 #endif
171 else if ( pri == primary(LANGUAGE_KANNADA) )
172 nOffset = 0x0CE6 - '0'; // kannada
173 else if ( pri == primary(LANGUAGE_KHMER))
174 nOffset = 0x17E0 - '0'; // khmer
175 else if ( pri == primary(LANGUAGE_LAO) )
176 nOffset = 0x0ED0 - '0'; // lao
177 else if ( pri == primary(LANGUAGE_MALAYALAM) )
178 nOffset = 0x0D66 - '0'; // malayalam
179 else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
181 if (eLang.anyOf(
182 LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA,
183 LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA,
184 LANGUAGE_MONGOLIAN_MONGOLIAN_LSO))
185 nOffset = 0x1810 - '0'; // mongolian
186 else
187 nOffset = 0; // mongolian cyrillic
189 else if ( pri == primary(LANGUAGE_BURMESE) )
190 nOffset = 0x1040 - '0'; // myanmar
191 else if ( pri == primary(LANGUAGE_ODIA) )
192 nOffset = 0x0B66 - '0'; // odia
193 else if ( pri == primary(LANGUAGE_TAMIL) )
194 nOffset = 0x0BE7 - '0'; // tamil
195 else if ( pri == primary(LANGUAGE_TELUGU) )
196 nOffset = 0x0C66 - '0'; // telugu
197 else if ( pri == primary(LANGUAGE_THAI) )
198 nOffset = 0x0E50 - '0'; // thai
199 else if ( pri == primary(LANGUAGE_TIBETAN) )
200 nOffset = 0x0F20 - '0'; // tibetan
201 else
203 nOffset = 0;
206 nChar += nOffset;
207 return nChar;
210 static bool IsControlChar( sal_UCS4 cChar )
212 // C0 control characters
213 if( (0x0001 <= cChar) && (cChar <= 0x001F) )
214 return true;
215 // formatting characters
216 if( (0x200E <= cChar) && (cChar <= 0x200F) )
217 return true;
218 if( (0x2028 <= cChar) && (cChar <= 0x202E) )
219 return true;
220 // deprecated formatting characters
221 if( (0x206A <= cChar) && (cChar <= 0x206F) )
222 return true;
223 if( 0x2060 == cChar )
224 return true;
225 // byte order markers and invalid unicode
226 if( (cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF) )
227 return true;
228 return false;
231 void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL )
233 // check if charpos could extend current run
234 int nIndex = maRuns.size();
235 if( nIndex >= 2 )
237 int nRunPos0 = maRuns[ nIndex-2 ];
238 int nRunPos1 = maRuns[ nIndex-1 ];
239 if( ((nCharPos + int(bRTL)) == nRunPos1) && ((nRunPos0 > nRunPos1) == bRTL) )
241 // extend current run by new charpos
242 maRuns[ nIndex-1 ] = nCharPos + int(!bRTL);
243 return;
245 // ignore new charpos when it is in current run
246 if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) )
247 return;
248 if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) )
249 return;
252 // else append a new run consisting of the new charpos
253 maRuns.push_back( nCharPos + (bRTL ? 1 : 0) );
254 maRuns.push_back( nCharPos + (bRTL ? 0 : 1) );
257 void ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
259 if( nCharPos0 == nCharPos1 )
260 return;
262 // swap if needed
263 if( bRTL == (nCharPos0 < nCharPos1) )
265 int nTemp = nCharPos0;
266 nCharPos0 = nCharPos1;
267 nCharPos1 = nTemp;
270 if (maRuns.size() >= 2 && nCharPos0 == maRuns[maRuns.size() - 2] && nCharPos1 == maRuns[maRuns.size() - 1])
272 //this run is the same as the last
273 return;
276 // append new run
277 maRuns.push_back( nCharPos0 );
278 maRuns.push_back( nCharPos1 );
281 bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const
283 if( mnRunIndex >= static_cast<int>(maRuns.size()) )
284 return false;
286 int nMinCharPos = maRuns[ mnRunIndex+0 ];
287 int nEndCharPos = maRuns[ mnRunIndex+1 ];
288 if( nMinCharPos > nEndCharPos ) // reversed in RTL case
290 int nTemp = nMinCharPos;
291 nMinCharPos = nEndCharPos;
292 nEndCharPos = nTemp;
295 if( nCharPos < nMinCharPos )
296 return false;
297 if( nCharPos >= nEndCharPos )
298 return false;
299 return true;
302 bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const
304 bool bRet = false;
305 int nRunIndex = mnRunIndex;
307 ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this);
309 pThis->ResetPos();
311 for (size_t i = 0; i < maRuns.size(); i+=2)
313 bRet = PosIsInRun( nCharPos );
314 if( bRet )
315 break;
316 pThis->NextRun();
319 pThis->mnRunIndex = nRunIndex;
320 return bRet;
323 bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft )
325 // negative nCharPos => reset to first run
326 if( *nCharPos < 0 )
327 mnRunIndex = 0;
329 // return false when all runs completed
330 if( mnRunIndex >= static_cast<int>(maRuns.size()) )
331 return false;
333 int nRunPos0 = maRuns[ mnRunIndex+0 ];
334 int nRunPos1 = maRuns[ mnRunIndex+1 ];
335 *bRightToLeft = (nRunPos0 > nRunPos1);
337 if( *nCharPos < 0 )
339 // get first valid nCharPos in run
340 *nCharPos = nRunPos0;
342 else
344 // advance to next nCharPos for LTR case
345 if( !*bRightToLeft )
346 ++(*nCharPos);
348 // advance to next run if current run is completed
349 if( *nCharPos == nRunPos1 )
351 if( (mnRunIndex += 2) >= static_cast<int>(maRuns.size()) )
352 return false;
353 nRunPos0 = maRuns[ mnRunIndex+0 ];
354 nRunPos1 = maRuns[ mnRunIndex+1 ];
355 *bRightToLeft = (nRunPos0 > nRunPos1);
356 *nCharPos = nRunPos0;
360 // advance to next nCharPos for RTL case
361 if( *bRightToLeft )
362 --(*nCharPos);
364 return true;
367 bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const
369 if( mnRunIndex >= static_cast<int>(maRuns.size()) )
370 return false;
372 int nRunPos0 = maRuns[ mnRunIndex+0 ];
373 int nRunPos1 = maRuns[ mnRunIndex+1 ];
374 *bRightToLeft = (nRunPos1 < nRunPos0) ;
375 if( !*bRightToLeft )
377 *nMinRunPos = nRunPos0;
378 *nEndRunPos = nRunPos1;
380 else
382 *nMinRunPos = nRunPos1;
383 *nEndRunPos = nRunPos0;
385 return true;
388 ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr,
389 int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
390 vcl::TextLayoutCache const*const pLayoutCache)
392 maLanguageTag( rLanguageTag ),
393 mnFlags( nFlags ),
394 mrStr( rStr ),
395 mnMinCharPos( nMinCharPos ),
396 mnEndCharPos( nEndCharPos ),
397 m_pTextLayoutCache(pLayoutCache),
398 mpDXArray( nullptr ),
399 mnLayoutWidth( 0 ),
400 mnOrientation( 0 )
402 if( mnFlags & SalLayoutFlags::BiDiStrong )
404 // handle strong BiDi mode
406 // do not bother to BiDi analyze strong LTR/RTL
407 // TODO: can we assume these strings do not have unicode control chars?
408 // if not remove the control characters from the runs
409 bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
410 AddRun( mnMinCharPos, mnEndCharPos, bRTL );
412 else
414 // handle weak BiDi mode
415 UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl)? 1 : 0;
417 // prepare substring for BiDi analysis
418 // TODO: reuse allocated pParaBidi
419 UErrorCode rcI18n = U_ZERO_ERROR;
420 const int nLength = mrStr.getLength();
421 UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
422 if( !pParaBidi )
423 return;
424 ubidi_setPara(pParaBidi, reinterpret_cast<const UChar *>(mrStr.getStr()), nLength, nLevel, nullptr, &rcI18n);
426 UBiDi* pLineBidi = pParaBidi;
427 int nSubLength = mnEndCharPos - mnMinCharPos;
428 if (nSubLength != nLength)
430 pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n );
431 ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n );
434 // run BiDi algorithm
435 const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n );
436 //maRuns.resize( 2 * nRunCount );
437 for( int i = 0; i < nRunCount; ++i )
439 int32_t nMinPos, nRunLength;
440 const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, i, &nMinPos, &nRunLength );
441 const int nPos0 = nMinPos + mnMinCharPos;
442 const int nPos1 = nPos0 + nRunLength;
444 const bool bRTL = (nDir == UBIDI_RTL);
445 AddRun( nPos0, nPos1, bRTL );
448 // cleanup BiDi engine
449 if( pLineBidi != pParaBidi )
450 ubidi_close( pLineBidi );
451 ubidi_close( pParaBidi );
454 // prepare calls to GetNextPos/GetNextRun
455 maRuns.ResetPos();
458 // add a run after splitting it up to get rid of control chars
459 void ImplLayoutArgs::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
461 SAL_WARN_IF( nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1" );
463 // remove control characters from runs by splitting them up
464 if( !bRTL )
466 for( int i = nCharPos0; i < nCharPos1; ++i )
467 if( IsControlChar( mrStr[i] ) )
469 // add run until control char
470 maRuns.AddRun( nCharPos0, i, bRTL );
471 nCharPos0 = i + 1;
474 else
476 for( int i = nCharPos1; --i >= nCharPos0; )
477 if( IsControlChar( mrStr[i] ) )
479 // add run until control char
480 maRuns.AddRun( i+1, nCharPos1, bRTL );
481 nCharPos1 = i;
485 // add remainder of run
486 maRuns.AddRun( nCharPos0, nCharPos1, bRTL );
489 bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl)
491 // Generate runs with pre-calculated glyph items instead maFallbackRuns.
492 if( pGlyphsImpl != nullptr )
494 maRuns.Clear();
495 maFallbackRuns.Clear();
497 for (auto const& aGlyphItem : *pGlyphsImpl)
499 for(int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount(); ++i)
500 maRuns.AddPos(i, aGlyphItem.IsRTLGlyph());
503 return !maRuns.IsEmpty();
506 // short circuit if no fallback is needed
507 if( maFallbackRuns.IsEmpty() )
509 maRuns.Clear();
510 return false;
513 // convert the fallback requests to layout requests
514 bool bRTL;
515 int nMin, nEnd;
517 // get the individual fallback requests
518 std::vector<int> aPosVector;
519 aPosVector.reserve(mrStr.getLength());
520 maFallbackRuns.ResetPos();
521 for(; maFallbackRuns.GetRun( &nMin, &nEnd, &bRTL ); maFallbackRuns.NextRun() )
522 for( int i = nMin; i < nEnd; ++i )
523 aPosVector.push_back( i );
524 maFallbackRuns.Clear();
526 // sort the individual fallback requests
527 std::sort( aPosVector.begin(), aPosVector.end() );
529 // adjust fallback runs to have the same order and limits of the original runs
530 ImplLayoutRuns aNewRuns;
531 maRuns.ResetPos();
532 for(; maRuns.GetRun( &nMin, &nEnd, &bRTL ); maRuns.NextRun() )
534 if( !bRTL) {
535 auto it = std::lower_bound( aPosVector.begin(), aPosVector.end(), nMin );
536 for(; (it != aPosVector.end()) && (*it < nEnd); ++it )
537 aNewRuns.AddPos( *it, bRTL );
538 } else {
539 auto it = std::upper_bound( aPosVector.begin(), aPosVector.end(), nEnd );
540 while( (it != aPosVector.begin()) && (*--it >= nMin) )
541 aNewRuns.AddPos( *it, bRTL );
545 maRuns = aNewRuns; // TODO: use vector<>::swap()
546 maRuns.ResetPos();
547 return true;
550 bool ImplLayoutArgs::GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL )
552 bool bValid = maRuns.GetRun( nMinRunPos, nEndRunPos, bRTL );
553 maRuns.NextRun();
554 return bValid;
557 SalLayout::SalLayout()
558 : mnMinCharPos( -1 ),
559 mnEndCharPos( -1 ),
560 mnUnitsPerPixel( 1 ),
561 mnOrientation( 0 ),
562 maDrawOffset( 0, 0 )
565 SalLayout::~SalLayout()
568 void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
570 mnMinCharPos = rArgs.mnMinCharPos;
571 mnEndCharPos = rArgs.mnEndCharPos;
572 mnOrientation = rArgs.mnOrientation;
575 Point SalLayout::GetDrawPosition( const Point& rRelative ) const
577 Point aPos = maDrawBase;
578 Point aOfs = rRelative + maDrawOffset;
580 if( mnOrientation == 0_deg10 )
581 aPos += aOfs;
582 else
584 // cache trigonometric results
585 static Degree10 nOldOrientation(0);
586 static double fCos = 1.0, fSin = 0.0;
587 if( nOldOrientation != mnOrientation )
589 nOldOrientation = mnOrientation;
590 double fRad = toRadians(mnOrientation);
591 fCos = cos( fRad );
592 fSin = sin( fRad );
595 double fX = aOfs.X();
596 double fY = aOfs.Y();
597 tools::Long nX = static_cast<tools::Long>( +fCos * fX + fSin * fY );
598 tools::Long nY = static_cast<tools::Long>( +fCos * fY - fSin * fX );
599 aPos += Point( nX, nY );
602 return aPos;
605 bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rVector) const
607 bool bAllOk = true;
608 bool bOneOk = false;
610 basegfx::B2DPolyPolygon aGlyphOutline;
612 Point aPos;
613 const GlyphItem* pGlyph;
614 int nStart = 0;
615 while (GetNextGlyph(&pGlyph, aPos, nStart))
617 // get outline of individual glyph, ignoring "empty" glyphs
618 bool bSuccess = pGlyph->GetGlyphOutline(aGlyphOutline);
619 bAllOk &= bSuccess;
620 bOneOk |= bSuccess;
621 // only add non-empty outlines
622 if( bSuccess && (aGlyphOutline.count() > 0) )
624 if( aPos.X() || aPos.Y() )
626 aGlyphOutline.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos.X(), aPos.Y()));
629 // insert outline at correct position
630 rVector.push_back( aGlyphOutline );
634 return (bAllOk && bOneOk);
637 bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
639 bool bRet = false;
640 rRect.SetEmpty();
642 tools::Rectangle aRectangle;
644 Point aPos;
645 const GlyphItem* pGlyph;
646 int nStart = 0;
647 while (GetNextGlyph(&pGlyph, aPos, nStart))
649 // get bounding rectangle of individual glyph
650 if (pGlyph->GetGlyphBoundRect(aRectangle))
652 // merge rectangle
653 aRectangle += aPos;
654 if (rRect.IsEmpty())
655 rRect = aRectangle;
656 else
657 rRect.Union(aRectangle);
658 bRet = true;
662 return bRet;
665 SalLayoutGlyphs SalLayout::GetGlyphs() const
667 return SalLayoutGlyphs(); // invalid
670 DeviceCoordinate GenericSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
672 if (pCharWidths)
673 GetCharWidths(pCharWidths);
675 return GetTextWidth();
678 // the text width is the maximum logical extent of all glyphs
679 DeviceCoordinate GenericSalLayout::GetTextWidth() const
681 if (!m_GlyphItems.IsValid())
682 return 0;
684 // initialize the extent
685 DeviceCoordinate nMinPos = 0;
686 DeviceCoordinate nMaxPos = 0;
688 for (auto const& aGlyphItem : m_GlyphItems)
690 // update the text extent with the glyph extent
691 DeviceCoordinate nXPos = aGlyphItem.m_aLinearPos.getX();
692 if( nMinPos > nXPos )
693 nMinPos = nXPos;
694 nXPos += aGlyphItem.m_nNewWidth - aGlyphItem.xOffset();
695 if( nMaxPos < nXPos )
696 nMaxPos = nXPos;
699 DeviceCoordinate nWidth = nMaxPos - nMinPos;
700 return nWidth;
703 void GenericSalLayout::Justify( DeviceCoordinate nNewWidth )
705 nNewWidth *= mnUnitsPerPixel;
706 DeviceCoordinate nOldWidth = GetTextWidth();
707 if( !nOldWidth || nNewWidth==nOldWidth )
708 return;
710 if (!m_GlyphItems.IsValid())
712 return;
714 // find rightmost glyph, it won't get stretched
715 std::vector<GlyphItem>::iterator pGlyphIterRight = m_GlyphItems.begin();
716 pGlyphIterRight += m_GlyphItems.size() - 1;
717 std::vector<GlyphItem>::iterator pGlyphIter;
718 // count stretchable glyphs
719 int nStretchable = 0;
720 int nMaxGlyphWidth = 0;
721 for(pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter)
723 if( !pGlyphIter->IsDiacritic() )
724 ++nStretchable;
725 if (nMaxGlyphWidth < pGlyphIter->origWidth())
726 nMaxGlyphWidth = pGlyphIter->origWidth();
729 // move rightmost glyph to requested position
730 nOldWidth -= pGlyphIterRight->origWidth();
731 if( nOldWidth <= 0 )
732 return;
733 if( nNewWidth < nMaxGlyphWidth)
734 nNewWidth = nMaxGlyphWidth;
735 nNewWidth -= pGlyphIterRight->origWidth();
736 pGlyphIterRight->m_aLinearPos.setX( nNewWidth );
738 // justify glyph widths and positions
739 int nDiffWidth = nNewWidth - nOldWidth;
740 if( nDiffWidth >= 0) // expanded case
742 // expand width by distributing space between glyphs evenly
743 int nDeltaSum = 0;
744 for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
746 // move glyph to justified position
747 pGlyphIter->m_aLinearPos.AdjustX(nDeltaSum );
749 // do not stretch non-stretchable glyphs
750 if( pGlyphIter->IsDiacritic() || (nStretchable <= 0) )
751 continue;
753 // distribute extra space equally to stretchable glyphs
754 int nDeltaWidth = nDiffWidth / nStretchable--;
755 nDiffWidth -= nDeltaWidth;
756 pGlyphIter->m_nNewWidth += nDeltaWidth;
757 nDeltaSum += nDeltaWidth;
760 else // condensed case
762 // squeeze width by moving glyphs proportionally
763 double fSqueeze = static_cast<double>(nNewWidth) / nOldWidth;
764 if(m_GlyphItems.size() > 1)
766 for( pGlyphIter = m_GlyphItems.begin(); ++pGlyphIter != pGlyphIterRight;)
768 int nX = pGlyphIter->m_aLinearPos.getX();
769 nX = static_cast<int>(nX * fSqueeze);
770 pGlyphIter->m_aLinearPos.setX( nX );
773 // adjust glyph widths to new positions
774 for( pGlyphIter = m_GlyphItems.begin(); pGlyphIter != pGlyphIterRight; ++pGlyphIter )
775 pGlyphIter->m_nNewWidth = pGlyphIter[1].m_aLinearPos.getX() - pGlyphIter[0].m_aLinearPos.getX();
779 // returns asian kerning values in quarter of character width units
780 // to enable automatic halfwidth substitution for fullwidth punctuation
781 // return value is negative for l, positive for r, zero for neutral
782 // TODO: handle vertical layout as proposed in commit 43bf2ad49c2b3989bbbe893e4fee2e032a3920f5?
783 static int lcl_CalcAsianKerning(sal_UCS4 c, bool bLeft)
785 // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
786 static const signed char nTable[0x30] =
788 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
789 +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
790 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
793 int nResult = 0;
794 if( (c >= 0x3000) && (c < 0x3030) )
795 nResult = nTable[ c - 0x3000 ];
796 else switch( c )
798 case 0x30FB:
799 nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom
800 break;
801 case 0x2019: case 0x201D:
802 case 0xFF01: case 0xFF09: case 0xFF0C:
803 case 0xFF1A: case 0xFF1B:
804 nResult = -2;
805 break;
806 case 0x2018: case 0x201C:
807 case 0xFF08:
808 nResult = +2;
809 break;
810 default:
811 break;
814 return nResult;
817 static bool lcl_CanApplyAsianKerning(sal_Unicode cp)
819 return (0x3000 == (cp & 0xFF00)) || (0xFF00 == (cp & 0xFF00)) || (0x2010 == (cp & 0xFFF0));
822 void GenericSalLayout::ApplyAsianKerning(const OUString& rStr)
824 const int nLength = rStr.getLength();
825 tools::Long nOffset = 0;
827 for (std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin(),
828 pGlyphIterEnd = m_GlyphItems.end();
829 pGlyphIter != pGlyphIterEnd; ++pGlyphIter)
831 const int n = pGlyphIter->charPos();
832 if (n < nLength - 1)
834 // ignore code ranges that are not affected by asian punctuation compression
835 const sal_Unicode cCurrent = rStr[n];
836 if (!lcl_CanApplyAsianKerning(cCurrent))
837 continue;
838 const sal_Unicode cNext = rStr[n + 1];
839 if (!lcl_CanApplyAsianKerning(cNext))
840 continue;
842 // calculate compression values
843 const int nKernCurrent = +lcl_CalcAsianKerning(cCurrent, true);
844 if (nKernCurrent == 0)
845 continue;
846 const int nKernNext = -lcl_CalcAsianKerning(cNext, false);
847 if (nKernNext == 0)
848 continue;
850 // apply punctuation compression to logical glyph widths
851 int nDelta = (nKernCurrent < nKernNext) ? nKernCurrent : nKernNext;
852 if (nDelta < 0)
854 nDelta = (nDelta * pGlyphIter->origWidth() + 2) / 4;
855 if( pGlyphIter+1 == pGlyphIterEnd )
856 pGlyphIter->m_nNewWidth += nDelta;
857 nOffset += nDelta;
861 // adjust the glyph positions to the new glyph widths
862 if( pGlyphIter+1 != pGlyphIterEnd )
863 pGlyphIter->m_aLinearPos.AdjustX(nOffset);
867 void GenericSalLayout::GetCaretPositions( int nMaxIndex, tools::Long* pCaretXArray ) const
869 // initialize result array
870 for (int i = 0; i < nMaxIndex; ++i)
871 pCaretXArray[i] = -1;
873 // calculate caret positions using glyph array
874 for (auto const& aGlyphItem : m_GlyphItems)
876 tools::Long nXPos = aGlyphItem.m_aLinearPos.getX();
877 tools::Long nXRight = nXPos + aGlyphItem.origWidth();
878 int n = aGlyphItem.charPos();
879 int nCurrIdx = 2 * (n - mnMinCharPos);
880 // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
881 if (aGlyphItem.IsInCluster() && pCaretXArray[nCurrIdx] != -1)
882 continue;
883 if (!aGlyphItem.IsRTLGlyph() )
885 // normal positions for LTR case
886 pCaretXArray[ nCurrIdx ] = nXPos;
887 pCaretXArray[ nCurrIdx+1 ] = nXRight;
889 else
891 // reverse positions for RTL case
892 pCaretXArray[ nCurrIdx ] = nXRight;
893 pCaretXArray[ nCurrIdx+1 ] = nXPos;
898 sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
900 int nCharCapacity = mnEndCharPos - mnMinCharPos;
901 std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCapacity]);
902 GetCharWidths(pCharWidths.get());
904 DeviceCoordinate nWidth = 0;
905 for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
907 nWidth += pCharWidths[ i - mnMinCharPos ] * nFactor;
908 if( nWidth > nMaxWidth )
909 return i;
910 nWidth += nCharExtra;
913 return -1;
916 bool GenericSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
917 Point& rPos, int& nStart,
918 const PhysicalFontFace**) const
920 std::vector<GlyphItem>::const_iterator pGlyphIter = m_GlyphItems.begin();
921 std::vector<GlyphItem>::const_iterator pGlyphIterEnd = m_GlyphItems.end();
922 pGlyphIter += nStart;
924 // find next glyph in substring
925 for(; pGlyphIter != pGlyphIterEnd; ++nStart, ++pGlyphIter )
927 int n = pGlyphIter->charPos();
928 if( (mnMinCharPos <= n) && (n < mnEndCharPos) )
929 break;
932 // return zero if no more glyph found
933 if( nStart >= static_cast<int>(m_GlyphItems.size()) )
934 return false;
936 if( pGlyphIter == pGlyphIterEnd )
937 return false;
939 // update return data with glyph info
940 *pGlyph = &(*pGlyphIter);
941 ++nStart;
943 // calculate absolute position in pixel units
944 Point aRelativePos = pGlyphIter->m_aLinearPos;
946 aRelativePos.setX( aRelativePos.X() / mnUnitsPerPixel );
947 aRelativePos.setY( aRelativePos.Y() / mnUnitsPerPixel );
948 rPos = GetDrawPosition( aRelativePos );
950 return true;
953 void GenericSalLayout::MoveGlyph( int nStart, tools::Long nNewXPos )
955 if( nStart >= static_cast<int>(m_GlyphItems.size()) )
956 return;
958 std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
959 pGlyphIter += nStart;
961 // the nNewXPos argument determines the new cell position
962 // as RTL-glyphs are right justified in their cell
963 // the cell position needs to be adjusted to the glyph position
964 if( pGlyphIter->IsRTLGlyph() )
965 nNewXPos += pGlyphIter->m_nNewWidth - pGlyphIter->origWidth();
966 // calculate the x-offset to the old position
967 tools::Long nXDelta = nNewXPos - pGlyphIter->m_aLinearPos.getX() + pGlyphIter->xOffset();
968 // adjust all following glyph positions if needed
969 if( nXDelta != 0 )
971 for( std::vector<GlyphItem>::iterator pGlyphIterEnd = m_GlyphItems.end(); pGlyphIter != pGlyphIterEnd; ++pGlyphIter )
973 pGlyphIter->m_aLinearPos.AdjustX(nXDelta );
978 void GenericSalLayout::DropGlyph( int nStart )
980 if( nStart >= static_cast<int>(m_GlyphItems.size()))
981 return;
983 std::vector<GlyphItem>::iterator pGlyphIter = m_GlyphItems.begin();
984 pGlyphIter += nStart;
985 pGlyphIter->dropGlyph();
988 void GenericSalLayout::Simplify( bool bIsBase )
990 // remove dropped glyphs inplace
991 size_t j = 0;
992 for(size_t i = 0; i < m_GlyphItems.size(); i++ )
994 if (bIsBase && m_GlyphItems[i].IsDropped())
995 continue;
996 if (!bIsBase && m_GlyphItems[i].glyphId() == 0)
997 continue;
999 if( i != j )
1001 m_GlyphItems[j] = m_GlyphItems[i];
1003 j += 1;
1005 m_GlyphItems.erase(m_GlyphItems.begin() + j, m_GlyphItems.end());
1008 MultiSalLayout::MultiSalLayout( std::unique_ptr<SalLayout> pBaseLayout )
1009 : SalLayout()
1010 , mnLevel( 1 )
1011 , mbIncomplete( false )
1013 assert(dynamic_cast<GenericSalLayout*>(pBaseLayout.get()));
1015 mpLayouts[ 0 ].reset(static_cast<GenericSalLayout*>(pBaseLayout.release()));
1016 mnUnitsPerPixel = mpLayouts[ 0 ]->GetUnitsPerPixel();
1019 std::unique_ptr<SalLayout> MultiSalLayout::ReleaseBaseLayout()
1021 return std::move(mpLayouts[0]);
1024 void MultiSalLayout::SetIncomplete(bool bIncomplete)
1026 mbIncomplete = bIncomplete;
1027 maFallbackRuns[mnLevel-1] = ImplLayoutRuns();
1030 MultiSalLayout::~MultiSalLayout()
1034 void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
1035 ImplLayoutRuns const & rFallbackRuns)
1037 assert(dynamic_cast<GenericSalLayout*>(pFallback.get()));
1038 if( mnLevel >= MAX_FALLBACK )
1039 return;
1041 mpLayouts[ mnLevel ].reset(static_cast<GenericSalLayout*>(pFallback.release()));
1042 maFallbackRuns[ mnLevel-1 ] = rFallbackRuns;
1043 ++mnLevel;
1046 bool MultiSalLayout::LayoutText( ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
1048 if( mnLevel <= 1 )
1049 return false;
1050 if (!mbIncomplete)
1051 maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns;
1052 return true;
1055 void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
1057 SalLayout::AdjustLayout( rArgs );
1058 ImplLayoutArgs aMultiArgs = rArgs;
1059 std::unique_ptr<DeviceCoordinate[]> pJustificationArray;
1061 if( !rArgs.mpDXArray && rArgs.mnLayoutWidth )
1063 // for stretched text in a MultiSalLayout the target width needs to be
1064 // distributed by individually adjusting its virtual character widths
1065 DeviceCoordinate nTargetWidth = aMultiArgs.mnLayoutWidth;
1066 nTargetWidth *= mnUnitsPerPixel; // convert target width to base font units
1067 aMultiArgs.mnLayoutWidth = 0;
1069 // we need to get the original unmodified layouts ready
1070 for( int n = 0; n < mnLevel; ++n )
1071 mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs );
1072 // then we can measure the unmodified metrics
1073 int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
1074 pJustificationArray.reset(new DeviceCoordinate[nCharCount]);
1075 FillDXArray( pJustificationArray.get() );
1076 // #i17359# multilayout is not simplified yet, so calculating the
1077 // unjustified width needs handholding; also count the number of
1078 // stretchable virtual char widths
1079 DeviceCoordinate nOrigWidth = 0;
1080 int nStretchable = 0;
1081 for( int i = 0; i < nCharCount; ++i )
1083 // convert array from widths to sum of widths
1084 nOrigWidth += pJustificationArray[i];
1085 if( pJustificationArray[i] > 0 )
1086 ++nStretchable;
1089 // now we are able to distribute the extra width over the virtual char widths
1090 if( nOrigWidth && (nTargetWidth != nOrigWidth) )
1092 DeviceCoordinate nDiffWidth = nTargetWidth - nOrigWidth;
1093 DeviceCoordinate nWidthSum = 0;
1094 for( int i = 0; i < nCharCount; ++i )
1096 DeviceCoordinate nJustWidth = pJustificationArray[i];
1097 if( (nJustWidth > 0) && (nStretchable > 0) )
1099 DeviceCoordinate nDeltaWidth = nDiffWidth / nStretchable;
1100 nJustWidth += nDeltaWidth;
1101 nDiffWidth -= nDeltaWidth;
1102 --nStretchable;
1104 nWidthSum += nJustWidth;
1105 pJustificationArray[i] = nWidthSum;
1107 if( nWidthSum != nTargetWidth )
1108 pJustificationArray[ nCharCount-1 ] = nTargetWidth;
1110 // the justification array is still in base level units
1111 // => convert it to pixel units
1112 if( mnUnitsPerPixel > 1 )
1114 for( int i = 0; i < nCharCount; ++i )
1116 DeviceCoordinate nVal = pJustificationArray[ i ];
1117 nVal += (mnUnitsPerPixel + 1) / 2;
1118 pJustificationArray[ i ] = nVal / mnUnitsPerPixel;
1122 // change the mpDXArray temporarily (just for the justification)
1123 aMultiArgs.mpDXArray = pJustificationArray.get();
1127 // Compute rtl flags, since in some scripts glyphs/char order can be
1128 // reversed for a few character sequences e.g. Myanmar
1129 std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false);
1130 rArgs.ResetPos();
1131 bool bRtl;
1132 int nRunStart, nRunEnd;
1133 while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl))
1135 if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos),
1136 vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true);
1138 rArgs.ResetPos();
1140 // prepare "merge sort"
1141 int nStartOld[ MAX_FALLBACK ];
1142 int nStartNew[ MAX_FALLBACK ];
1143 const GlyphItem* pGlyphs[MAX_FALLBACK];
1144 bool bValid[MAX_FALLBACK] = { false };
1146 Point aPos;
1147 int nLevel = 0, n;
1148 for( n = 0; n < mnLevel; ++n )
1150 // now adjust the individual components
1151 if( n > 0 )
1153 aMultiArgs.maRuns = maFallbackRuns[ n-1 ];
1154 aMultiArgs.mnFlags |= SalLayoutFlags::ForFallback;
1156 mpLayouts[n]->AdjustLayout( aMultiArgs );
1158 // remove unused parts of component
1159 if( n > 0 )
1161 if (mbIncomplete && (n == mnLevel-1))
1162 mpLayouts[n]->Simplify( true );
1163 else
1164 mpLayouts[n]->Simplify( false );
1167 // prepare merging components
1168 nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0;
1169 bValid[nLevel] = mpLayouts[n]->GetNextGlyph(&pGlyphs[nLevel], aPos, nStartNew[nLevel]);
1171 if( (n > 0) && !bValid[ nLevel ] )
1173 // an empty fallback layout can be released
1174 mpLayouts[n].reset();
1176 else
1178 // reshuffle used fallbacks if needed
1179 if( nLevel != n )
1181 mpLayouts[ nLevel ] = std::move(mpLayouts[ n ]);
1182 maFallbackRuns[ nLevel ] = maFallbackRuns[ n ];
1184 ++nLevel;
1187 mnLevel = nLevel;
1189 // prepare merge the fallback levels
1190 tools::Long nXPos = 0;
1191 double fUnitMul = 1.0;
1192 for( n = 0; n < nLevel; ++n )
1193 maFallbackRuns[n].ResetPos();
1195 int nFirstValid = -1;
1196 for( n = 0; n < nLevel; ++n )
1198 if(bValid[n])
1200 nFirstValid = n;
1201 break;
1204 assert(nFirstValid >= 0);
1206 // get the next codepoint index that needs fallback
1207 int nActiveCharPos = pGlyphs[nFirstValid]->charPos();
1208 int nActiveCharIndex = nActiveCharPos - mnMinCharPos;
1209 // get the end index of the active run
1210 int nLastRunEndChar = (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex]) ?
1211 rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1;
1212 int nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
1213 // merge the fallback levels
1214 while( bValid[nFirstValid] && (nLevel > 0))
1216 // find best fallback level
1217 for( n = 0; n < nLevel; ++n )
1218 if( bValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) )
1219 // fallback level n wins when it requested no further fallback
1220 break;
1221 int nFBLevel = n;
1223 if( n < nLevel )
1225 // use base(n==0) or fallback(n>=1) level
1226 fUnitMul = mnUnitsPerPixel;
1227 fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
1228 tools::Long nNewPos = static_cast<tools::Long>(nXPos/fUnitMul + 0.5);
1229 mpLayouts[n]->MoveGlyph( nStartOld[n], nNewPos );
1231 else
1233 n = 0; // keep NotDef in base level
1234 fUnitMul = 1.0;
1237 if( n > 0 )
1239 // drop the NotDef glyphs in the base layout run if a fallback run exists
1240 while (
1241 (maFallbackRuns[n-1].PosIsInRun(pGlyphs[nFirstValid]->charPos())) &&
1242 (!maFallbackRuns[n].PosIsInAnyRun(pGlyphs[nFirstValid]->charPos()))
1245 mpLayouts[0]->DropGlyph( nStartOld[0] );
1246 nStartOld[0] = nStartNew[0];
1247 bValid[nFirstValid] = mpLayouts[0]->GetNextGlyph(&pGlyphs[nFirstValid], aPos, nStartNew[0]);
1249 if( !bValid[nFirstValid] )
1250 break;
1254 // skip to end of layout run and calculate its advance width
1255 DeviceCoordinate nRunAdvance = 0;
1256 bool bKeepNotDef = (nFBLevel >= nLevel);
1257 for(;;)
1259 nRunAdvance += pGlyphs[n]->m_nNewWidth;
1261 // proceed to next glyph
1262 nStartOld[n] = nStartNew[n];
1263 int nOrigCharPos = pGlyphs[n]->charPos();
1264 bValid[n] = mpLayouts[n]->GetNextGlyph(&pGlyphs[n], aPos, nStartNew[n]);
1265 // break after last glyph of active layout
1266 if( !bValid[n] )
1268 // performance optimization (when a fallback layout is no longer needed)
1269 if( n >= nLevel-1 )
1270 --nLevel;
1271 break;
1274 //If the next character is one which belongs to the next level, then we
1275 //are finished here for now, and we'll pick up after the next level has
1276 //been processed
1277 if ((n+1 < nLevel) && (pGlyphs[n]->charPos() != nOrigCharPos))
1279 if (nOrigCharPos < pGlyphs[n]->charPos())
1281 if (pGlyphs[n+1]->charPos() > nOrigCharPos && (pGlyphs[n+1]->charPos() < pGlyphs[n]->charPos()))
1282 break;
1284 else if (nOrigCharPos > pGlyphs[n]->charPos())
1286 if (pGlyphs[n+1]->charPos() > pGlyphs[n]->charPos() && (pGlyphs[n+1]->charPos() < nOrigCharPos))
1287 break;
1291 // break at end of layout run
1292 if( n > 0 )
1294 // skip until end of fallback run
1295 if (!maFallbackRuns[n-1].PosIsInRun(pGlyphs[n]->charPos()))
1296 break;
1298 else
1300 // break when a fallback is needed and available
1301 bool bNeedFallback = maFallbackRuns[0].PosIsInRun(pGlyphs[nFirstValid]->charPos());
1302 if( bNeedFallback )
1303 if (!maFallbackRuns[nLevel-1].PosIsInRun(pGlyphs[nFirstValid]->charPos()))
1304 break;
1305 // break when change from resolved to unresolved base layout run
1306 if( bKeepNotDef && !bNeedFallback )
1307 { maFallbackRuns[0].NextRun(); break; }
1308 bKeepNotDef = bNeedFallback;
1310 // check for reordered glyphs
1311 if (aMultiArgs.mpDXArray &&
1312 nRunVisibleEndChar < mnEndCharPos &&
1313 nRunVisibleEndChar >= mnMinCharPos &&
1314 pGlyphs[n]->charPos() < mnEndCharPos &&
1315 pGlyphs[n]->charPos() >= mnMinCharPos)
1317 if (vRtl[nActiveCharPos - mnMinCharPos])
1319 if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
1320 >= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
1322 nRunVisibleEndChar = pGlyphs[n]->charPos();
1325 else if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos]
1326 <= aMultiArgs.mpDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
1328 nRunVisibleEndChar = pGlyphs[n]->charPos();
1333 // if a justification array is available
1334 // => use it directly to calculate the corresponding run width
1335 if( aMultiArgs.mpDXArray )
1337 // the run advance is the width from the first char
1338 // in the run to the first char in the next run
1339 nRunAdvance = 0;
1340 nActiveCharIndex = nActiveCharPos - mnMinCharPos;
1341 if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
1343 if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos)
1344 nRunAdvance -= aMultiArgs.mpDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
1345 if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
1346 nRunAdvance += aMultiArgs.mpDXArray[nLastRunEndChar - 1 - mnMinCharPos];
1348 else
1350 if (nRunVisibleEndChar >= mnMinCharPos)
1351 nRunAdvance += aMultiArgs.mpDXArray[nRunVisibleEndChar - mnMinCharPos];
1352 if (nLastRunEndChar >= mnMinCharPos)
1353 nRunAdvance -= aMultiArgs.mpDXArray[nLastRunEndChar - mnMinCharPos];
1355 nLastRunEndChar = nRunVisibleEndChar;
1356 nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
1357 // the requested width is still in pixel units
1358 // => convert it to base level font units
1359 nRunAdvance *= mnUnitsPerPixel;
1361 else
1363 // the measured width is still in fallback font units
1364 // => convert it to base level font units
1365 if( n > 0 ) // optimization: because (fUnitMul==1.0) for (n==0)
1366 nRunAdvance = static_cast<tools::Long>(nRunAdvance*fUnitMul + 0.5);
1369 // calculate new x position (in base level units)
1370 nXPos += nRunAdvance;
1372 // prepare for next fallback run
1373 nActiveCharPos = pGlyphs[nFirstValid]->charPos();
1374 // it essential that the runs don't get ahead of themselves and in the
1375 // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
1376 // have already been reached on the base level
1377 for( int i = nFBLevel; --i >= 0;)
1379 if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl))
1381 if (bRtl)
1383 if (nRunStart > nActiveCharPos)
1384 maFallbackRuns[i].NextRun();
1386 else
1388 if (nRunEnd <= nActiveCharPos)
1389 maFallbackRuns[i].NextRun();
1395 mpLayouts[0]->Simplify( true );
1398 void MultiSalLayout::InitFont() const
1400 if( mnLevel > 0 )
1401 mpLayouts[0]->InitFont();
1404 void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const
1406 for( int i = mnLevel; --i >= 0; )
1408 SalLayout& rLayout = *mpLayouts[ i ];
1409 rLayout.DrawBase() += maDrawBase;
1410 rLayout.DrawOffset() += maDrawOffset;
1411 rLayout.InitFont();
1412 rLayout.DrawText( rGraphics );
1413 rLayout.DrawOffset() -= maDrawOffset;
1414 rLayout.DrawBase() -= maDrawBase;
1416 // NOTE: now the baselevel font is active again
1419 sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
1421 if( mnLevel <= 0 )
1422 return -1;
1423 if( mnLevel == 1 )
1424 return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor );
1426 int nCharCount = mnEndCharPos - mnMinCharPos;
1427 std::unique_ptr<DeviceCoordinate[]> const pCharWidths(new DeviceCoordinate[nCharCount]);
1428 std::unique_ptr<DeviceCoordinate[]> const pFallbackCharWidths(new DeviceCoordinate[nCharCount]);
1429 mpLayouts[0]->FillDXArray( pCharWidths.get() );
1431 for( int n = 1; n < mnLevel; ++n )
1433 SalLayout& rLayout = *mpLayouts[ n ];
1434 rLayout.FillDXArray( pFallbackCharWidths.get() );
1435 double fUnitMul = mnUnitsPerPixel;
1436 fUnitMul /= rLayout.GetUnitsPerPixel();
1437 for( int i = 0; i < nCharCount; ++i )
1439 if( pCharWidths[ i ] == 0 )
1441 DeviceCoordinate w = pFallbackCharWidths[i];
1442 w = static_cast<DeviceCoordinate>(w * fUnitMul + 0.5);
1443 pCharWidths[ i ] = w;
1448 DeviceCoordinate nWidth = 0;
1449 for( int i = 0; i < nCharCount; ++i )
1451 nWidth += pCharWidths[ i ] * nFactor;
1452 if( nWidth > nMaxWidth )
1453 return (i + mnMinCharPos);
1454 nWidth += nCharExtra;
1457 return -1;
1460 DeviceCoordinate MultiSalLayout::FillDXArray( DeviceCoordinate* pCharWidths ) const
1462 DeviceCoordinate nMaxWidth = 0;
1464 // prepare merging of fallback levels
1465 std::unique_ptr<DeviceCoordinate[]> pTempWidths;
1466 const int nCharCount = mnEndCharPos - mnMinCharPos;
1467 if( pCharWidths )
1469 for( int i = 0; i < nCharCount; ++i )
1470 pCharWidths[i] = 0;
1471 pTempWidths.reset(new DeviceCoordinate[nCharCount]);
1474 for( int n = mnLevel; --n >= 0; )
1476 // query every fallback level
1477 DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( pTempWidths.get() );
1478 if( !nTextWidth )
1479 continue;
1480 // merge results from current level
1481 double fUnitMul = mnUnitsPerPixel;
1482 fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
1483 nTextWidth = static_cast<DeviceCoordinate>(nTextWidth * fUnitMul + 0.5);
1484 if( nMaxWidth < nTextWidth )
1485 nMaxWidth = nTextWidth;
1486 if( !pCharWidths )
1487 continue;
1488 // calculate virtual char widths using most probable fallback layout
1489 for( int i = 0; i < nCharCount; ++i )
1491 // #i17359# restriction:
1492 // one char cannot be resolved from different fallbacks
1493 if( pCharWidths[i] != 0 )
1494 continue;
1495 DeviceCoordinate nCharWidth = pTempWidths[i];
1496 if( !nCharWidth )
1497 continue;
1498 nCharWidth = static_cast<DeviceCoordinate>(nCharWidth * fUnitMul + 0.5);
1499 pCharWidths[i] = nCharWidth;
1503 return nMaxWidth;
1506 void MultiSalLayout::GetCaretPositions( int nMaxIndex, tools::Long* pCaretXArray ) const
1508 SalLayout& rLayout = *mpLayouts[ 0 ];
1509 rLayout.GetCaretPositions( nMaxIndex, pCaretXArray );
1511 if( mnLevel <= 1 )
1512 return;
1514 std::unique_ptr<tools::Long[]> const pTempPos(new tools::Long[nMaxIndex]);
1515 for( int n = 1; n < mnLevel; ++n )
1517 mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos.get() );
1518 double fUnitMul = mnUnitsPerPixel;
1519 fUnitMul /= mpLayouts[n]->GetUnitsPerPixel();
1520 for( int i = 0; i < nMaxIndex; ++i )
1521 if( pTempPos[i] >= 0 )
1523 tools::Long w = pTempPos[i];
1524 w = static_cast<tools::Long>(w*fUnitMul + 0.5);
1525 pCaretXArray[i] = w;
1530 bool MultiSalLayout::GetNextGlyph(const GlyphItem** pGlyph,
1531 Point& rPos, int& nStart,
1532 const PhysicalFontFace** pFallbackFont) const
1534 // NOTE: nStart is tagged with current font index
1535 int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT;
1536 nStart &= ~GF_FONTMASK;
1537 for(; nLevel < mnLevel; ++nLevel, nStart=0 )
1539 GenericSalLayout& rLayout = *mpLayouts[ nLevel ];
1540 rLayout.InitFont();
1541 const PhysicalFontFace* pFontFace = rLayout.GetFont().GetFontFace();
1542 if (rLayout.GetNextGlyph(pGlyph, rPos, nStart))
1544 int nFontTag = nLevel << GF_FONTSHIFT;
1545 nStart |= nFontTag;
1546 if (pFallbackFont)
1547 *pFallbackFont = pFontFace;
1548 rPos += maDrawBase;
1549 rPos += maDrawOffset;
1550 return true;
1554 // #111016# reset to base level font when done
1555 mpLayouts[0]->InitFont();
1556 return false;
1559 bool MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
1561 bool bRet = false;
1563 for( int i = mnLevel; --i >= 0; )
1565 SalLayout& rLayout = *mpLayouts[ i ];
1566 rLayout.DrawBase() = maDrawBase;
1567 rLayout.DrawOffset() += maDrawOffset;
1568 rLayout.InitFont();
1569 bRet |= rLayout.GetOutline(rPPV);
1570 rLayout.DrawOffset() -= maDrawOffset;
1573 return bRet;
1576 bool MultiSalLayout::IsKashidaPosValid(int nCharPos) const
1578 // Check the base layout
1579 bool bValid = mpLayouts[0]->IsKashidaPosValid(nCharPos);
1581 // If base layout returned false, it might be because the character was not
1582 // supported there, so we check fallback layouts.
1583 if (!bValid)
1585 for (int i = 1; i < mnLevel; ++i)
1587 // - 1 because there is no fallback run for the base layout, IIUC.
1588 if (maFallbackRuns[i - 1].PosIsInAnyRun(nCharPos))
1590 bValid = mpLayouts[i]->IsKashidaPosValid(nCharPos);
1591 break;
1596 return bValid;
1599 SalLayoutGlyphs MultiSalLayout::GetGlyphs() const
1601 SalLayoutGlyphs glyphs;
1602 for( int n = 0; n < mnLevel; ++n )
1603 glyphs.AppendImpl(mpLayouts[n]->GlyphsImpl().clone());
1604 return glyphs;
1608 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */