1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
23 #include "sal/config.h"
30 #include <sallayout.hxx>
31 #include <basegfx/polygon/b2dpolypolygon.hxx>
32 #include <basegfx/matrix/b2dhommatrix.hxx>
33 #include <basegfx/matrix/b2dhommatrixtools.hxx>
35 #include <i18nlangtag/lang.h>
37 #include <vcl/svapp.hxx>
41 #include <unicode/ubidi.h>
42 #include <unicode/uchar.h>
48 #define GF_FONTMASK 0xF0000000
49 #define GF_FONTSHIFT 28
50 #define GF_DROPPED 0xFFFFFFFF
53 std::ostream
&operator <<(std::ostream
& s
, ImplLayoutArgs
&rArgs
)
58 s
<< "ImplLayoutArgs{";
61 if (rArgs
.mnFlags
== SalLayoutFlags::NONE
)
66 #define TEST(x) if (rArgs.mnFlags & SalLayoutFlags::x) { if (need_or) s << "|"; s << #x; need_or = true; }
73 TEST(EnableLigatures
);
74 TEST(SubstituteDigits
);
75 TEST(KashidaJustification
);
81 const int nLength
= rArgs
.mrStr
.getLength();
83 s
<< ",Length=" << nLength
;
84 s
<< ",MinCharPos=" << rArgs
.mnMinCharPos
;
85 s
<< ",EndCharPos=" << rArgs
.mnEndCharPos
;
91 for (int i
= 0; i
< lim
; i
++) {
92 if (rArgs
.mrStr
[i
] == '\n')
94 else if (rArgs
.mrStr
[i
] < ' ' || (rArgs
.mrStr
[i
] >= 0x7F && rArgs
.mrStr
[i
] <= 0xFF))
95 s
<< "\\0x" << std::hex
<< std::setw(2) << std::setfill('0') << (int) rArgs
.mrStr
[i
] << std::setfill(' ') << std::setw(1) << std::dec
;
96 else if (rArgs
.mrStr
[i
] < 0x7F)
97 s
<< (char) rArgs
.mrStr
[i
];
99 s
<< "\\u" << std::hex
<< std::setw(4) << std::setfill('0') << (int) rArgs
.mrStr
[i
] << std::setfill(' ') << std::setw(1) << std::dec
;
106 if (rArgs
.mpDXArray
) {
108 int count
= rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
;
112 for (int i
= 0; i
< lim
; i
++) {
113 s
<< rArgs
.mpDXArray
[i
];
120 s
<< rArgs
.mpDXArray
[count
-1];
126 s
<< ",LayoutWidth=" << rArgs
.mnLayoutWidth
;
134 sal_UCS4
GetMirroredChar( sal_UCS4 nChar
)
136 nChar
= u_charMirror( nChar
);
140 sal_UCS4
GetLocalizedChar( sal_UCS4 nChar
, LanguageType eLang
)
142 // currently only conversion from ASCII digits is interesting
143 if( (nChar
< '0') || ('9' < nChar
) )
147 // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region.
148 // CAVEAT! To some like Mongolian MS assigned the same primary language
149 // although the script type is different!
150 LanguageType pri
= primary(eLang
);
151 if( pri
== primary(LANGUAGE_ARABIC_SAUDI_ARABIA
) )
152 nOffset
= 0x0660 - '0'; // arabic-indic digits
154 primary(LANGUAGE_FARSI
),
155 primary(LANGUAGE_URDU_PAKISTAN
),
156 primary(LANGUAGE_PUNJABI
), //???
157 primary(LANGUAGE_SINDHI
)))
158 nOffset
= 0x06F0 - '0'; // eastern arabic-indic digits
159 else if ( pri
== primary(LANGUAGE_BENGALI
) )
160 nOffset
= 0x09E6 - '0'; // bengali
161 else if ( pri
== primary(LANGUAGE_HINDI
) )
162 nOffset
= 0x0966 - '0'; // devanagari
164 primary(LANGUAGE_AMHARIC_ETHIOPIA
),
165 primary(LANGUAGE_TIGRIGNA_ETHIOPIA
)))
167 nOffset
= 0x1369 - '0'; // ethiopic
168 else if ( pri
== primary(LANGUAGE_GUJARATI
) )
169 nOffset
= 0x0AE6 - '0'; // gujarati
170 #ifdef LANGUAGE_GURMUKHI // TODO case:
171 else if ( pri
== primary(LANGUAGE_GURMUKHI
) )
172 nOffset
= 0x0A66 - '0'; // gurmukhi
174 else if ( pri
== primary(LANGUAGE_KANNADA
) )
175 nOffset
= 0x0CE6 - '0'; // kannada
176 else if ( pri
== primary(LANGUAGE_KHMER
))
177 nOffset
= 0x17E0 - '0'; // khmer
178 else if ( pri
== primary(LANGUAGE_LAO
) )
179 nOffset
= 0x0ED0 - '0'; // lao
180 else if ( pri
== primary(LANGUAGE_MALAYALAM
) )
181 nOffset
= 0x0D66 - '0'; // malayalam
182 else if ( pri
== primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO
))
185 LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
,
186 LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
,
187 LANGUAGE_MONGOLIAN_MONGOLIAN_LSO
))
188 nOffset
= 0x1810 - '0'; // mongolian
190 nOffset
= 0; // mongolian cyrillic
192 else if ( pri
== primary(LANGUAGE_BURMESE
) )
193 nOffset
= 0x1040 - '0'; // myanmar
194 else if ( pri
== primary(LANGUAGE_ODIA
) )
195 nOffset
= 0x0B66 - '0'; // odia
196 else if ( pri
== primary(LANGUAGE_TAMIL
) )
197 nOffset
= 0x0BE7 - '0'; // tamil
198 else if ( pri
== primary(LANGUAGE_TELUGU
) )
199 nOffset
= 0x0C66 - '0'; // telugu
200 else if ( pri
== primary(LANGUAGE_THAI
) )
201 nOffset
= 0x0E50 - '0'; // thai
202 else if ( pri
== primary(LANGUAGE_TIBETAN
) )
203 nOffset
= 0x0F20 - '0'; // tibetan
213 inline bool IsControlChar( sal_UCS4 cChar
)
215 // C0 control characters
216 if( (0x0001 <= cChar
) && (cChar
<= 0x001F) )
218 // formatting characters
219 if( (0x200E <= cChar
) && (cChar
<= 0x200F) )
221 if( (0x2028 <= cChar
) && (cChar
<= 0x202E) )
223 // deprecated formatting characters
224 if( (0x206A <= cChar
) && (cChar
<= 0x206F) )
226 if( (0x2060 == cChar
) )
228 // byte order markers and invalid unicode
229 if( (cChar
== 0xFEFF) || (cChar
== 0xFFFE) || (cChar
== 0xFFFF) )
234 void ImplLayoutRuns::AddPos( int nCharPos
, bool bRTL
)
236 // check if charpos could extend current run
237 int nIndex
= maRuns
.size();
240 int nRunPos0
= maRuns
[ nIndex
-2 ];
241 int nRunPos1
= maRuns
[ nIndex
-1 ];
242 if( ((nCharPos
+ int(bRTL
)) == nRunPos1
) && ((nRunPos0
> nRunPos1
) == bRTL
) )
244 // extend current run by new charpos
245 maRuns
[ nIndex
-1 ] = nCharPos
+ int(!bRTL
);
248 // ignore new charpos when it is in current run
249 if( (nRunPos0
<= nCharPos
) && (nCharPos
< nRunPos1
) )
251 if( (nRunPos1
<= nCharPos
) && (nCharPos
< nRunPos0
) )
255 // else append a new run consisting of the new charpos
256 maRuns
.push_back( nCharPos
+ (bRTL
? 1 : 0) );
257 maRuns
.push_back( nCharPos
+ (bRTL
? 0 : 1) );
260 void ImplLayoutRuns::AddRun( int nCharPos0
, int nCharPos1
, bool bRTL
)
262 if( nCharPos0
== nCharPos1
)
266 if( bRTL
== (nCharPos0
< nCharPos1
) )
268 int nTemp
= nCharPos0
;
269 nCharPos0
= nCharPos1
;
274 maRuns
.push_back( nCharPos0
);
275 maRuns
.push_back( nCharPos1
);
278 bool ImplLayoutRuns::PosIsInRun( int nCharPos
) const
280 if( mnRunIndex
>= (int)maRuns
.size() )
283 int nMinCharPos
= maRuns
[ mnRunIndex
+0 ];
284 int nEndCharPos
= maRuns
[ mnRunIndex
+1 ];
285 if( nMinCharPos
> nEndCharPos
) // reversed in RTL case
287 int nTemp
= nMinCharPos
;
288 nMinCharPos
= nEndCharPos
;
292 if( nCharPos
< nMinCharPos
)
294 if( nCharPos
>= nEndCharPos
)
299 bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos
) const
302 int nRunIndex
= mnRunIndex
;
304 ImplLayoutRuns
*pThis
= const_cast<ImplLayoutRuns
*>(this);
308 for (size_t i
= 0; i
< maRuns
.size(); i
+=2)
310 bRet
= PosIsInRun( nCharPos
);
316 pThis
->mnRunIndex
= nRunIndex
;
320 bool ImplLayoutRuns::GetNextPos( int* nCharPos
, bool* bRightToLeft
)
322 // negative nCharPos => reset to first run
326 // return false when all runs completed
327 if( mnRunIndex
>= (int)maRuns
.size() )
330 int nRunPos0
= maRuns
[ mnRunIndex
+0 ];
331 int nRunPos1
= maRuns
[ mnRunIndex
+1 ];
332 *bRightToLeft
= (nRunPos0
> nRunPos1
);
336 // get first valid nCharPos in run
337 *nCharPos
= nRunPos0
;
341 // advance to next nCharPos for LTR case
345 // advance to next run if current run is completed
346 if( *nCharPos
== nRunPos1
)
348 if( (mnRunIndex
+= 2) >= (int)maRuns
.size() )
350 nRunPos0
= maRuns
[ mnRunIndex
+0 ];
351 nRunPos1
= maRuns
[ mnRunIndex
+1 ];
352 *bRightToLeft
= (nRunPos0
> nRunPos1
);
353 *nCharPos
= nRunPos0
;
357 // advance to next nCharPos for RTL case
364 bool ImplLayoutRuns::GetRun( int* nMinRunPos
, int* nEndRunPos
, bool* bRightToLeft
) const
366 if( mnRunIndex
>= (int)maRuns
.size() )
369 int nRunPos0
= maRuns
[ mnRunIndex
+0 ];
370 int nRunPos1
= maRuns
[ mnRunIndex
+1 ];
371 *bRightToLeft
= (nRunPos1
< nRunPos0
) ;
374 *nMinRunPos
= nRunPos0
;
375 *nEndRunPos
= nRunPos1
;
379 *nMinRunPos
= nRunPos1
;
380 *nEndRunPos
= nRunPos0
;
385 ImplLayoutArgs::ImplLayoutArgs(const OUString
& rStr
,
386 int nMinCharPos
, int nEndCharPos
, SalLayoutFlags nFlags
, const LanguageTag
& rLanguageTag
,
387 vcl::TextLayoutCache
const*const pLayoutCache
)
389 maLanguageTag( rLanguageTag
),
392 mnMinCharPos( nMinCharPos
),
393 mnEndCharPos( nEndCharPos
),
394 m_pTextLayoutCache(pLayoutCache
),
395 mpDXArray( nullptr ),
399 if( mnFlags
& SalLayoutFlags::BiDiStrong
)
401 // handle strong BiDi mode
403 // do not bother to BiDi analyze strong LTR/RTL
404 // TODO: can we assume these strings do not have unicode control chars?
405 // if not remove the control characters from the runs
406 bool bRTL(mnFlags
& SalLayoutFlags::BiDiRtl
);
407 AddRun( mnMinCharPos
, mnEndCharPos
, bRTL
);
411 // handle weak BiDi mode
413 UBiDiLevel nLevel
= UBIDI_DEFAULT_LTR
;
414 if( mnFlags
& SalLayoutFlags::BiDiRtl
)
415 nLevel
= UBIDI_DEFAULT_RTL
;
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
);
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
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
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
);
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
);
485 // add remainder of run
486 maRuns
.AddRun( nCharPos0
, nCharPos1
, bRTL
);
489 bool ImplLayoutArgs::PrepareFallback()
491 // short circuit if no fallback is needed
492 if( maFallbackRuns
.IsEmpty() )
498 // convert the fallback requests to layout requests
502 // get the individual fallback requests
503 std::vector
<int> aPosVector
;
504 aPosVector
.reserve(mrStr
.getLength());
505 maFallbackRuns
.ResetPos();
506 for(; maFallbackRuns
.GetRun( &nMin
, &nEnd
, &bRTL
); maFallbackRuns
.NextRun() )
507 for( int i
= nMin
; i
< nEnd
; ++i
)
508 aPosVector
.push_back( i
);
509 maFallbackRuns
.Clear();
511 // sort the individual fallback requests
512 std::sort( aPosVector
.begin(), aPosVector
.end() );
514 // adjust fallback runs to have the same order and limits of the original runs
515 ImplLayoutRuns aNewRuns
;
517 for(; maRuns
.GetRun( &nMin
, &nEnd
, &bRTL
); maRuns
.NextRun() )
520 auto it
= std::lower_bound( aPosVector
.begin(), aPosVector
.end(), nMin
);
521 for(; (it
!= aPosVector
.end()) && (*it
< nEnd
); ++it
)
522 aNewRuns
.AddPos( *it
, bRTL
);
524 auto it
= std::upper_bound( aPosVector
.begin(), aPosVector
.end(), nEnd
);
525 while( (it
!= aPosVector
.begin()) && (*--it
>= nMin
) )
526 aNewRuns
.AddPos( *it
, bRTL
);
530 maRuns
= aNewRuns
; // TODO: use vector<>::swap()
535 bool ImplLayoutArgs::GetNextRun( int* nMinRunPos
, int* nEndRunPos
, bool* bRTL
)
537 bool bValid
= maRuns
.GetRun( nMinRunPos
, nEndRunPos
, bRTL
);
542 SalLayout::SalLayout()
543 : mnMinCharPos( -1 ),
545 mnLayoutFlags( SalLayoutFlags::NONE
),
546 mnUnitsPerPixel( 1 ),
552 SalLayout::~SalLayout()
555 void SalLayout::AdjustLayout( ImplLayoutArgs
& rArgs
)
557 mnMinCharPos
= rArgs
.mnMinCharPos
;
558 mnEndCharPos
= rArgs
.mnEndCharPos
;
559 mnLayoutFlags
= rArgs
.mnFlags
;
560 mnOrientation
= rArgs
.mnOrientation
;
563 void SalLayout::Release() const
565 // TODO: protect when multiple threads can access this
566 if( --mnRefCount
> 0 )
568 // const_cast because some compilers violate ANSI C++ spec
572 Point
SalLayout::GetDrawPosition( const Point
& rRelative
) const
574 Point aPos
= maDrawBase
;
575 Point aOfs
= rRelative
+ maDrawOffset
;
577 if( mnOrientation
== 0 )
581 // cache trigonometric results
582 static int nOldOrientation
= 0;
583 static double fCos
= 1.0, fSin
= 0.0;
584 if( nOldOrientation
!= mnOrientation
)
586 nOldOrientation
= mnOrientation
;
587 double fRad
= mnOrientation
* (M_PI
/ 1800.0);
592 double fX
= aOfs
.X();
593 double fY
= aOfs
.Y();
594 long nX
= static_cast<long>( +fCos
* fX
+ fSin
* fY
);
595 long nY
= static_cast<long>( +fCos
* fY
- fSin
* fX
);
596 aPos
+= Point( nX
, nY
);
602 // returns asian kerning values in quarter of character width units
603 // to enable automatic halfwidth substitution for fullwidth punctuation
604 // return value is negative for l, positive for r, zero for neutral
606 // If the range doesn't match in 0x3000 and 0x30FB, please change
607 // also ImplCalcKerning.
609 int SalLayout::CalcAsianKerning( sal_UCS4 c
, bool bLeft
, bool /*TODO:? bVertical*/ )
611 // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html
612 static const signed char nTable
[0x30] =
614 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2,
615 +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2,
616 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2
620 if( (c
>= 0x3000) && (c
< 0x3030) )
621 nResult
= nTable
[ c
- 0x3000 ];
625 nResult
= bLeft
? -1 : +1; // 25% left/right/top/bottom
627 case 0x2019: case 0x201D:
628 case 0xFF01: case 0xFF09: case 0xFF0C:
629 case 0xFF1A: case 0xFF1B:
632 case 0x2018: case 0x201C:
643 bool SalLayout::GetOutline( SalGraphics
& rSalGraphics
,
644 basegfx::B2DPolyPolygonVector
& rVector
) const
649 basegfx::B2DPolyPolygon aGlyphOutline
;
652 const GlyphItem
* pGlyph
;
654 while (GetNextGlyphs(1, &pGlyph
, aPos
, nStart
))
656 // get outline of individual glyph, ignoring "empty" glyphs
657 bool bSuccess
= rSalGraphics
.GetGlyphOutline(*pGlyph
, aGlyphOutline
);
660 // only add non-empty outlines
661 if( bSuccess
&& (aGlyphOutline
.count() > 0) )
663 if( aPos
.X() || aPos
.Y() )
665 aGlyphOutline
.transform(basegfx::tools::createTranslateB2DHomMatrix(aPos
.X(), aPos
.Y()));
668 // insert outline at correct position
669 rVector
.push_back( aGlyphOutline
);
673 return (bAllOk
&& bOneOk
);
676 bool SalLayout::GetBoundRect( SalGraphics
& rSalGraphics
, tools::Rectangle
& rRect
) const
681 tools::Rectangle aRectangle
;
684 const GlyphItem
* pGlyph
;
686 while (GetNextGlyphs(1, &pGlyph
, aPos
, nStart
))
688 // get bounding rectangle of individual glyph
689 if (rSalGraphics
.GetGlyphBoundRect(*pGlyph
, aRectangle
))
696 rRect
.Union(aRectangle
);
704 GenericSalLayout::GenericSalLayout()
707 GenericSalLayout::~GenericSalLayout()
710 void GenericSalLayout::AppendGlyph( const GlyphItem
& rGlyphItem
)
712 m_GlyphItems
.push_back(rGlyphItem
);
715 DeviceCoordinate
GenericSalLayout::FillDXArray( DeviceCoordinate
* pCharWidths
) const
718 if( !GetCharWidths( pCharWidths
) )
721 return GetTextWidth();
724 // the text width is the maximum logical extent of all glyphs
725 DeviceCoordinate
GenericSalLayout::GetTextWidth() const
727 if( m_GlyphItems
.empty() )
730 // initialize the extent
731 DeviceCoordinate nMinPos
= 0;
732 DeviceCoordinate nMaxPos
= 0;
734 for (auto const& aGlyphItem
: m_GlyphItems
)
736 // update the text extent with the glyph extent
737 DeviceCoordinate nXPos
= aGlyphItem
.maLinearPos
.X();
738 if( nMinPos
> nXPos
)
740 nXPos
+= aGlyphItem
.mnNewWidth
- aGlyphItem
.mnXOffset
;
741 if( nMaxPos
< nXPos
)
745 DeviceCoordinate nWidth
= nMaxPos
- nMinPos
;
749 void GenericSalLayout::Justify( DeviceCoordinate nNewWidth
)
751 nNewWidth
*= mnUnitsPerPixel
;
752 DeviceCoordinate nOldWidth
= GetTextWidth();
753 if( !nOldWidth
|| nNewWidth
==nOldWidth
)
756 if(m_GlyphItems
.empty())
760 // find rightmost glyph, it won't get stretched
761 std::vector
<GlyphItem
>::iterator pGlyphIterRight
= m_GlyphItems
.begin();
762 pGlyphIterRight
+= m_GlyphItems
.size() - 1;
763 std::vector
<GlyphItem
>::iterator pGlyphIter
;
764 // count stretchable glyphs
765 int nStretchable
= 0;
766 int nMaxGlyphWidth
= 0;
767 for(pGlyphIter
= m_GlyphItems
.begin(); pGlyphIter
!= pGlyphIterRight
; ++pGlyphIter
)
769 if( !pGlyphIter
->IsDiacritic() )
771 if( nMaxGlyphWidth
< pGlyphIter
->mnOrigWidth
)
772 nMaxGlyphWidth
= pGlyphIter
->mnOrigWidth
;
775 // move rightmost glyph to requested position
776 nOldWidth
-= pGlyphIterRight
->mnOrigWidth
;
779 if( nNewWidth
< nMaxGlyphWidth
)
780 nNewWidth
= nMaxGlyphWidth
;
781 nNewWidth
-= pGlyphIterRight
->mnOrigWidth
;
782 pGlyphIterRight
->maLinearPos
.X() = maBasePoint
.X() + nNewWidth
;
784 // justify glyph widths and positions
785 int nDiffWidth
= nNewWidth
- nOldWidth
;
786 if( nDiffWidth
>= 0) // expanded case
788 // expand width by distributing space between glyphs evenly
790 for( pGlyphIter
= m_GlyphItems
.begin(); pGlyphIter
!= pGlyphIterRight
; ++pGlyphIter
)
792 // move glyph to justified position
793 pGlyphIter
->maLinearPos
.X() += nDeltaSum
;
795 // do not stretch non-stretchable glyphs
796 if( pGlyphIter
->IsDiacritic() || (nStretchable
<= 0) )
799 // distribute extra space equally to stretchable glyphs
800 int nDeltaWidth
= nDiffWidth
/ nStretchable
--;
801 nDiffWidth
-= nDeltaWidth
;
802 pGlyphIter
->mnNewWidth
+= nDeltaWidth
;
803 nDeltaSum
+= nDeltaWidth
;
806 else // condensed case
808 // squeeze width by moving glyphs proportionally
809 double fSqueeze
= (double)nNewWidth
/ nOldWidth
;
810 if(m_GlyphItems
.size() > 1)
812 for( pGlyphIter
= m_GlyphItems
.begin(); ++pGlyphIter
!= pGlyphIterRight
;)
814 int nX
= pGlyphIter
->maLinearPos
.X() - maBasePoint
.X();
815 nX
= (int)(nX
* fSqueeze
);
816 pGlyphIter
->maLinearPos
.X() = nX
+ maBasePoint
.X();
819 // adjust glyph widths to new positions
820 for( pGlyphIter
= m_GlyphItems
.begin(); pGlyphIter
!= pGlyphIterRight
; ++pGlyphIter
)
821 pGlyphIter
->mnNewWidth
= pGlyphIter
[1].maLinearPos
.X() - pGlyphIter
[0].maLinearPos
.X();
825 void GenericSalLayout::ApplyAsianKerning(const OUString
& rStr
)
827 const int nLength
= rStr
.getLength();
830 for( std::vector
<GlyphItem
>::iterator pGlyphIter
= m_GlyphItems
.begin(), pGlyphIterEnd
= m_GlyphItems
.end(); pGlyphIter
!= pGlyphIterEnd
; ++pGlyphIter
)
832 const int n
= pGlyphIter
->mnCharPos
;
835 // ignore code ranges that are not affected by asian punctuation compression
836 const sal_Unicode cHere
= rStr
[n
];
837 if( ((0x3000 != (cHere
& 0xFF00)) && (0x2010 != (cHere
& 0xFFF0))) || (0xFF00 != (cHere
& 0xFF00)) )
839 const sal_Unicode cNext
= rStr
[n
+1];
840 if( ((0x3000 != (cNext
& 0xFF00)) && (0x2010 != (cNext
& 0xFFF0))) || (0xFF00 != (cNext
& 0xFF00)) )
843 // calculate compression values
844 const bool bVertical
= false;
845 long nKernFirst
= +CalcAsianKerning( cHere
, true, bVertical
);
846 long nKernNext
= -CalcAsianKerning( cNext
, false, bVertical
);
848 // apply punctuation compression to logical glyph widths
849 long nDelta
= (nKernFirst
< nKernNext
) ? nKernFirst
: nKernNext
;
850 if( nDelta
<0 && nKernFirst
!=0 && nKernNext
!=0 )
852 int nGlyphWidth
= pGlyphIter
->mnOrigWidth
;
853 nDelta
= (nDelta
* nGlyphWidth
+ 2) / 4;
854 if( pGlyphIter
+1 == pGlyphIterEnd
)
855 pGlyphIter
->mnNewWidth
+= nDelta
;
860 // adjust the glyph positions to the new glyph widths
861 if( pGlyphIter
+1 != pGlyphIterEnd
)
862 pGlyphIter
->maLinearPos
.X() += nOffset
;
866 void GenericSalLayout::GetCaretPositions( int nMaxIndex
, long* pCaretXArray
) const
868 // initialize result array
869 for (int i
= 0; i
< nMaxIndex
; ++i
)
870 pCaretXArray
[i
] = -1;
872 // calculate caret positions using glyph array
873 for (auto const& aGlyphItem
: m_GlyphItems
)
875 long nXPos
= aGlyphItem
.maLinearPos
.X();
876 long nXRight
= nXPos
+ aGlyphItem
.mnOrigWidth
;
877 int n
= aGlyphItem
.mnCharPos
;
878 int nCurrIdx
= 2 * (n
- mnMinCharPos
);
879 // tdf#86399 if this is not the start of a cluster, don't overwrite the caret bounds of the cluster start
880 if (!aGlyphItem
.IsClusterStart() && pCaretXArray
[nCurrIdx
] != -1)
882 if (!aGlyphItem
.IsRTLGlyph() )
884 // normal positions for LTR case
885 pCaretXArray
[ nCurrIdx
] = nXPos
;
886 pCaretXArray
[ nCurrIdx
+1 ] = nXRight
;
890 // reverse positions for RTL case
891 pCaretXArray
[ nCurrIdx
] = nXRight
;
892 pCaretXArray
[ nCurrIdx
+1 ] = nXPos
;
897 sal_Int32
GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth
, DeviceCoordinate nCharExtra
, int nFactor
) const
899 int nCharCapacity
= mnEndCharPos
- mnMinCharPos
;
900 std::unique_ptr
<DeviceCoordinate
[]> const pCharWidths(new DeviceCoordinate
[nCharCapacity
]);
901 if (!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
)
910 nWidth
+= nCharExtra
;
916 int GenericSalLayout::GetNextGlyphs(int nLen
, const GlyphItem
** pGlyphs
,
917 Point
& rPos
, int& nStart
,
918 const PhysicalFontFace
** /*pFallbackFonts*/) 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
->mnCharPos
;
928 if( (mnMinCharPos
<= n
) && (n
< mnEndCharPos
) )
932 // return zero if no more glyph found
933 if( nStart
>= (int)m_GlyphItems
.size() )
936 if( pGlyphIter
== pGlyphIterEnd
)
939 // calculate absolute position in pixel units
940 Point aRelativePos
= pGlyphIter
->maLinearPos
- maBasePoint
;
942 // find more glyphs which can be merged into one drawing instruction
944 long nYPos
= pGlyphIter
->maLinearPos
.Y();
947 // update return data with glyph info
949 *(pGlyphs
++) = &(*pGlyphIter
);
951 // break at end of glyph list
952 if( ++nStart
>= (int)m_GlyphItems
.size() )
954 // break when enough glyphs
958 long nGlyphAdvance
= pGlyphIter
[1].maLinearPos
.X() - pGlyphIter
->maLinearPos
.X();
959 // stop when next x-position is unexpected
960 if( pGlyphIter
->mnOrigWidth
!= nGlyphAdvance
)
963 // advance to next glyph
966 // stop when next y-position is unexpected
967 if( nYPos
!= pGlyphIter
->maLinearPos
.Y() )
970 // stop when no longer in string
971 int n
= pGlyphIter
->mnCharPos
;
972 if( (n
< mnMinCharPos
) || (mnEndCharPos
<= n
) )
976 aRelativePos
.X() /= mnUnitsPerPixel
;
977 aRelativePos
.Y() /= mnUnitsPerPixel
;
978 rPos
= GetDrawPosition( aRelativePos
);
983 void GenericSalLayout::MoveGlyph( int nStart
, long nNewXPos
)
985 if( nStart
>= (int)m_GlyphItems
.size() )
988 std::vector
<GlyphItem
>::iterator pGlyphIter
= m_GlyphItems
.begin();
989 pGlyphIter
+= nStart
;
991 // the nNewXPos argument determines the new cell position
992 // as RTL-glyphs are right justified in their cell
993 // the cell position needs to be adjusted to the glyph position
994 if( pGlyphIter
->IsRTLGlyph() )
995 nNewXPos
+= pGlyphIter
->mnNewWidth
- pGlyphIter
->mnOrigWidth
;
996 // calculate the x-offset to the old position
997 long nXDelta
= nNewXPos
- pGlyphIter
->maLinearPos
.X();
998 // adjust all following glyph positions if needed
1001 for( std::vector
<GlyphItem
>::iterator pGlyphIterEnd
= m_GlyphItems
.end(); pGlyphIter
!= pGlyphIterEnd
; ++pGlyphIter
)
1003 pGlyphIter
->maLinearPos
.X() += nXDelta
;
1008 void GenericSalLayout::DropGlyph( int nStart
)
1010 if( nStart
>= (int)m_GlyphItems
.size())
1013 std::vector
<GlyphItem
>::iterator pGlyphIter
= m_GlyphItems
.begin();
1014 pGlyphIter
+= nStart
;
1015 pGlyphIter
->maGlyphId
= GF_DROPPED
;
1016 pGlyphIter
->mnCharPos
= -1;
1019 void GenericSalLayout::Simplify( bool bIsBase
)
1021 const sal_GlyphId nDropMarker
= bIsBase
? GF_DROPPED
: 0;
1023 // remove dropped glyphs inplace
1025 for(size_t i
= 0; i
< m_GlyphItems
.size(); i
++ )
1027 if( m_GlyphItems
[i
].maGlyphId
== nDropMarker
)
1032 m_GlyphItems
[j
] = m_GlyphItems
[i
];
1036 m_GlyphItems
.erase(m_GlyphItems
.begin() + j
, m_GlyphItems
.end());
1039 MultiSalLayout::MultiSalLayout( SalLayout
& rBaseLayout
)
1042 , mbIncomplete( false )
1044 //maFallbackRuns[0].Clear();
1045 mpFallbackFonts
[ 0 ] = nullptr;
1046 mpLayouts
[ 0 ] = &rBaseLayout
;
1047 mnUnitsPerPixel
= rBaseLayout
.GetUnitsPerPixel();
1050 void MultiSalLayout::SetIncomplete(bool bIncomplete
)
1052 mbIncomplete
= bIncomplete
;
1053 maFallbackRuns
[mnLevel
-1] = ImplLayoutRuns();
1056 MultiSalLayout::~MultiSalLayout()
1058 for( int i
= 0; i
< mnLevel
; ++i
)
1059 mpLayouts
[ i
]->Release();
1062 void MultiSalLayout::AddFallback( SalLayout
& rFallback
,
1063 ImplLayoutRuns
& rFallbackRuns
, const PhysicalFontFace
* pFallbackFont
)
1065 if( mnLevel
>= MAX_FALLBACK
)
1068 mpFallbackFonts
[ mnLevel
] = pFallbackFont
;
1069 mpLayouts
[ mnLevel
] = &rFallback
;
1070 maFallbackRuns
[ mnLevel
-1 ] = rFallbackRuns
;
1074 bool MultiSalLayout::LayoutText( ImplLayoutArgs
& rArgs
)
1079 maFallbackRuns
[ mnLevel
-1 ] = rArgs
.maRuns
;
1083 void MultiSalLayout::AdjustLayout( ImplLayoutArgs
& rArgs
)
1085 SalLayout::AdjustLayout( rArgs
);
1086 ImplLayoutArgs aMultiArgs
= rArgs
;
1087 std::unique_ptr
<DeviceCoordinate
[]> pJustificationArray
;
1089 if( !rArgs
.mpDXArray
&& rArgs
.mnLayoutWidth
)
1091 // for stretched text in a MultiSalLayout the target width needs to be
1092 // distributed by individually adjusting its virtual character widths
1093 DeviceCoordinate nTargetWidth
= aMultiArgs
.mnLayoutWidth
;
1094 nTargetWidth
*= mnUnitsPerPixel
; // convert target width to base font units
1095 aMultiArgs
.mnLayoutWidth
= 0;
1097 // we need to get the original unmodified layouts ready
1098 for( int n
= 0; n
< mnLevel
; ++n
)
1099 mpLayouts
[n
]->SalLayout::AdjustLayout( aMultiArgs
);
1100 // then we can measure the unmodified metrics
1101 int nCharCount
= rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
;
1102 pJustificationArray
.reset(new DeviceCoordinate
[nCharCount
]);
1103 FillDXArray( pJustificationArray
.get() );
1104 // #i17359# multilayout is not simplified yet, so calculating the
1105 // unjustified width needs handholding; also count the number of
1106 // stretchable virtual char widths
1107 DeviceCoordinate nOrigWidth
= 0;
1108 int nStretchable
= 0;
1109 for( int i
= 0; i
< nCharCount
; ++i
)
1111 // convert array from widths to sum of widths
1112 nOrigWidth
+= pJustificationArray
[i
];
1113 if( pJustificationArray
[i
] > 0 )
1117 // now we are able to distribute the extra width over the virtual char widths
1118 if( nOrigWidth
&& (nTargetWidth
!= nOrigWidth
) )
1120 DeviceCoordinate nDiffWidth
= nTargetWidth
- nOrigWidth
;
1121 DeviceCoordinate nWidthSum
= 0;
1122 for( int i
= 0; i
< nCharCount
; ++i
)
1124 DeviceCoordinate nJustWidth
= pJustificationArray
[i
];
1125 if( (nJustWidth
> 0) && (nStretchable
> 0) )
1127 DeviceCoordinate nDeltaWidth
= nDiffWidth
/ nStretchable
;
1128 nJustWidth
+= nDeltaWidth
;
1129 nDiffWidth
-= nDeltaWidth
;
1132 nWidthSum
+= nJustWidth
;
1133 pJustificationArray
[i
] = nWidthSum
;
1135 if( nWidthSum
!= nTargetWidth
)
1136 pJustificationArray
[ nCharCount
-1 ] = nTargetWidth
;
1138 // the justification array is still in base level units
1139 // => convert it to pixel units
1140 if( mnUnitsPerPixel
> 1 )
1142 for( int i
= 0; i
< nCharCount
; ++i
)
1144 DeviceCoordinate nVal
= pJustificationArray
[ i
];
1145 nVal
+= (mnUnitsPerPixel
+ 1) / 2;
1146 pJustificationArray
[ i
] = nVal
/ mnUnitsPerPixel
;
1150 // change the mpDXArray temporarily (just for the justification)
1151 aMultiArgs
.mpDXArray
= pJustificationArray
.get();
1155 // Compute rtl flags, since in some scripts glyphs/char order can be
1156 // reversed for a few character sequences e.g. Myanmar
1157 std::vector
<bool> vRtl(rArgs
.mnEndCharPos
- rArgs
.mnMinCharPos
, false);
1160 int nRunStart
, nRunEnd
;
1161 while (rArgs
.GetNextRun(&nRunStart
, &nRunEnd
, &bRtl
))
1163 if (bRtl
) std::fill(vRtl
.begin() + (nRunStart
- rArgs
.mnMinCharPos
),
1164 vRtl
.begin() + (nRunEnd
- rArgs
.mnMinCharPos
), true);
1168 // prepare "merge sort"
1169 int nStartOld
[ MAX_FALLBACK
];
1170 int nStartNew
[ MAX_FALLBACK
];
1171 const GlyphItem
* pGlyphs
[MAX_FALLBACK
];
1172 int nValid
[ MAX_FALLBACK
] = {0};
1176 for( n
= 0; n
< mnLevel
; ++n
)
1178 // now adjust the individual components
1181 aMultiArgs
.maRuns
= maFallbackRuns
[ n
-1 ];
1182 aMultiArgs
.mnFlags
|= SalLayoutFlags::ForFallback
;
1184 mpLayouts
[n
]->AdjustLayout( aMultiArgs
);
1186 // remove unused parts of component
1189 if (mbIncomplete
&& (n
== mnLevel
-1))
1190 mpLayouts
[n
]->Simplify( true );
1192 mpLayouts
[n
]->Simplify( false );
1195 // prepare merging components
1196 nStartNew
[ nLevel
] = nStartOld
[ nLevel
] = 0;
1197 nValid
[nLevel
] = mpLayouts
[n
]->GetNextGlyphs(1, &pGlyphs
[nLevel
], aPos
, nStartNew
[nLevel
]);
1199 if( (n
> 0) && !nValid
[ nLevel
] )
1201 // an empty fallback layout can be released
1202 mpLayouts
[n
]->Release();
1206 // reshuffle used fallbacks if needed
1209 mpLayouts
[ nLevel
] = mpLayouts
[ n
];
1210 mpFallbackFonts
[ nLevel
] = mpFallbackFonts
[ n
];
1211 maFallbackRuns
[ nLevel
] = maFallbackRuns
[ n
];
1218 // prepare merge the fallback levels
1220 double fUnitMul
= 1.0;
1221 for( n
= 0; n
< nLevel
; ++n
)
1222 maFallbackRuns
[n
].ResetPos();
1223 // get the next codepoint index that needs fallback
1224 int nActiveCharPos
= pGlyphs
[0]->mnCharPos
;
1225 int nActiveCharIndex
= nActiveCharPos
- mnMinCharPos
;
1226 // get the end index of the active run
1227 int nLastRunEndChar
= (nActiveCharIndex
>= 0 && vRtl
[nActiveCharIndex
]) ?
1228 rArgs
.mnEndCharPos
: rArgs
.mnMinCharPos
- 1;
1229 int nRunVisibleEndChar
= pGlyphs
[0]->mnCharPos
;
1230 // merge the fallback levels
1231 while( nValid
[0] && (nLevel
> 0))
1233 // find best fallback level
1234 for( n
= 0; n
< nLevel
; ++n
)
1235 if( nValid
[n
] && !maFallbackRuns
[n
].PosIsInAnyRun( nActiveCharPos
) )
1236 // fallback level n wins when it requested no further fallback
1242 // use base(n==0) or fallback(n>=1) level
1243 fUnitMul
= mnUnitsPerPixel
;
1244 fUnitMul
/= mpLayouts
[n
]->GetUnitsPerPixel();
1245 long nNewPos
= static_cast<long>(nXPos
/fUnitMul
+ 0.5);
1246 mpLayouts
[n
]->MoveGlyph( nStartOld
[n
], nNewPos
);
1250 n
= 0; // keep NotDef in base level
1256 // drop the NotDef glyphs in the base layout run if a fallback run exists
1258 (maFallbackRuns
[n
-1].PosIsInRun(pGlyphs
[0]->mnCharPos
)) &&
1259 (!maFallbackRuns
[n
].PosIsInAnyRun(pGlyphs
[0]->mnCharPos
))
1262 mpLayouts
[0]->DropGlyph( nStartOld
[0] );
1263 nStartOld
[0] = nStartNew
[0];
1264 nValid
[0] = mpLayouts
[0]->GetNextGlyphs(1, &pGlyphs
[0], aPos
, nStartNew
[0]);
1271 // skip to end of layout run and calculate its advance width
1272 DeviceCoordinate nRunAdvance
= 0;
1273 bool bKeepNotDef
= (nFBLevel
>= nLevel
);
1276 nRunAdvance
+= pGlyphs
[n
]->mnNewWidth
;
1278 // proceed to next glyph
1279 nStartOld
[n
] = nStartNew
[n
];
1280 int nOrigCharPos
= pGlyphs
[n
]->mnCharPos
;
1281 nValid
[n
] = mpLayouts
[n
]->GetNextGlyphs(1, &pGlyphs
[n
], aPos
, nStartNew
[n
]);
1282 // break after last glyph of active layout
1285 // performance optimization (when a fallback layout is no longer needed)
1291 //If the next character is one which belongs to the next level, then we
1292 //are finished here for now, and we'll pick up after the next level has
1294 if ((n
+1 < nLevel
) && (pGlyphs
[n
]->mnCharPos
!= nOrigCharPos
))
1296 if (nOrigCharPos
< pGlyphs
[n
]->mnCharPos
)
1298 if (pGlyphs
[n
+1]->mnCharPos
> nOrigCharPos
&& (pGlyphs
[n
+1]->mnCharPos
< pGlyphs
[n
]->mnCharPos
))
1301 else if (nOrigCharPos
> pGlyphs
[n
]->mnCharPos
)
1303 if (pGlyphs
[n
+1]->mnCharPos
> pGlyphs
[n
]->mnCharPos
&& (pGlyphs
[n
+1]->mnCharPos
< nOrigCharPos
))
1308 // break at end of layout run
1311 // skip until end of fallback run
1312 if (!maFallbackRuns
[n
-1].PosIsInRun(pGlyphs
[n
]->mnCharPos
))
1317 // break when a fallback is needed and available
1318 bool bNeedFallback
= maFallbackRuns
[0].PosIsInRun(pGlyphs
[0]->mnCharPos
);
1320 if (!maFallbackRuns
[nLevel
-1].PosIsInRun(pGlyphs
[0]->mnCharPos
))
1322 // break when change from resolved to unresolved base layout run
1323 if( bKeepNotDef
&& !bNeedFallback
)
1324 { maFallbackRuns
[0].NextRun(); break; }
1325 bKeepNotDef
= bNeedFallback
;
1327 // check for reordered glyphs
1328 if (aMultiArgs
.mpDXArray
&&
1329 nRunVisibleEndChar
< mnEndCharPos
&&
1330 nRunVisibleEndChar
>= mnMinCharPos
&&
1331 pGlyphs
[n
]->mnCharPos
< mnEndCharPos
&&
1332 pGlyphs
[n
]->mnCharPos
>= mnMinCharPos
)
1334 if (vRtl
[nActiveCharPos
- mnMinCharPos
])
1336 if (aMultiArgs
.mpDXArray
[nRunVisibleEndChar
-mnMinCharPos
]
1337 >= aMultiArgs
.mpDXArray
[pGlyphs
[n
]->mnCharPos
- mnMinCharPos
])
1339 nRunVisibleEndChar
= pGlyphs
[n
]->mnCharPos
;
1342 else if (aMultiArgs
.mpDXArray
[nRunVisibleEndChar
-mnMinCharPos
]
1343 <= aMultiArgs
.mpDXArray
[pGlyphs
[n
]->mnCharPos
- mnMinCharPos
])
1345 nRunVisibleEndChar
= pGlyphs
[n
]->mnCharPos
;
1350 // if a justification array is available
1351 // => use it directly to calculate the corresponding run width
1352 if( aMultiArgs
.mpDXArray
)
1354 // the run advance is the width from the first char
1355 // in the run to the first char in the next run
1357 nActiveCharIndex
= nActiveCharPos
- mnMinCharPos
;
1358 if (nActiveCharIndex
>= 0 && vRtl
[nActiveCharIndex
])
1360 if (nRunVisibleEndChar
> mnMinCharPos
&& nRunVisibleEndChar
<= mnEndCharPos
)
1361 nRunAdvance
-= aMultiArgs
.mpDXArray
[nRunVisibleEndChar
- 1 - mnMinCharPos
];
1362 if (nLastRunEndChar
> mnMinCharPos
&& nLastRunEndChar
<= mnEndCharPos
)
1363 nRunAdvance
+= aMultiArgs
.mpDXArray
[nLastRunEndChar
- 1 - mnMinCharPos
];
1367 if (nRunVisibleEndChar
>= mnMinCharPos
)
1368 nRunAdvance
+= aMultiArgs
.mpDXArray
[nRunVisibleEndChar
- mnMinCharPos
];
1369 if (nLastRunEndChar
>= mnMinCharPos
)
1370 nRunAdvance
-= aMultiArgs
.mpDXArray
[nLastRunEndChar
- mnMinCharPos
];
1372 nLastRunEndChar
= nRunVisibleEndChar
;
1373 nRunVisibleEndChar
= pGlyphs
[0]->mnCharPos
;
1374 // the requested width is still in pixel units
1375 // => convert it to base level font units
1376 nRunAdvance
*= mnUnitsPerPixel
;
1380 // the measured width is still in fallback font units
1381 // => convert it to base level font units
1382 if( n
> 0 ) // optimization: because (fUnitMul==1.0) for (n==0)
1383 nRunAdvance
= static_cast<long>(nRunAdvance
*fUnitMul
+ 0.5);
1386 // calculate new x position (in base level units)
1387 nXPos
+= nRunAdvance
;
1389 // prepare for next fallback run
1390 nActiveCharPos
= pGlyphs
[0]->mnCharPos
;
1391 // it essential that the runs don't get ahead of themselves and in the
1392 // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may
1393 // have already been reached on the base level
1394 for( int i
= nFBLevel
; --i
>= 0;)
1396 if (maFallbackRuns
[i
].GetRun(&nRunStart
, &nRunEnd
, &bRtl
))
1400 if (nRunStart
> nActiveCharPos
)
1401 maFallbackRuns
[i
].NextRun();
1405 if (nRunEnd
<= nActiveCharPos
)
1406 maFallbackRuns
[i
].NextRun();
1412 mpLayouts
[0]->Simplify( true );
1415 void MultiSalLayout::InitFont() const
1418 mpLayouts
[0]->InitFont();
1421 void MultiSalLayout::DrawText( SalGraphics
& rGraphics
) const
1423 for( int i
= mnLevel
; --i
>= 0; )
1425 SalLayout
& rLayout
= *mpLayouts
[ i
];
1426 rLayout
.DrawBase() += maDrawBase
;
1427 rLayout
.DrawOffset() += maDrawOffset
;
1429 rLayout
.DrawText( rGraphics
);
1430 rLayout
.DrawOffset() -= maDrawOffset
;
1431 rLayout
.DrawBase() -= maDrawBase
;
1433 // NOTE: now the baselevel font is active again
1436 sal_Int32
MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth
, DeviceCoordinate nCharExtra
, int nFactor
) const
1441 return mpLayouts
[0]->GetTextBreak( nMaxWidth
, nCharExtra
, nFactor
);
1443 int nCharCount
= mnEndCharPos
- mnMinCharPos
;
1444 std::unique_ptr
<DeviceCoordinate
[]> const pCharWidths(new DeviceCoordinate
[2 * nCharCount
]);
1445 mpLayouts
[0]->FillDXArray( pCharWidths
.get() );
1447 for( int n
= 1; n
< mnLevel
; ++n
)
1449 SalLayout
& rLayout
= *mpLayouts
[ n
];
1450 rLayout
.FillDXArray( &pCharWidths
[nCharCount
] );
1451 double fUnitMul
= mnUnitsPerPixel
;
1452 fUnitMul
/= rLayout
.GetUnitsPerPixel();
1453 for( int i
= 0; i
< nCharCount
; ++i
)
1455 if( pCharWidths
[ i
] == 0 )
1457 DeviceCoordinate w
= pCharWidths
[ i
+ nCharCount
];
1458 w
= (DeviceCoordinate
)(w
* fUnitMul
+ 0.5);
1459 pCharWidths
[ i
] = w
;
1464 DeviceCoordinate nWidth
= 0;
1465 for( int i
= 0; i
< nCharCount
; ++i
)
1467 nWidth
+= pCharWidths
[ i
] * nFactor
;
1468 if( nWidth
> nMaxWidth
)
1469 return (i
+ mnMinCharPos
);
1470 nWidth
+= nCharExtra
;
1476 DeviceCoordinate
MultiSalLayout::FillDXArray( DeviceCoordinate
* pCharWidths
) const
1478 DeviceCoordinate nMaxWidth
= 0;
1480 // prepare merging of fallback levels
1481 std::unique_ptr
<DeviceCoordinate
[]> pTempWidths
;
1482 const int nCharCount
= mnEndCharPos
- mnMinCharPos
;
1485 for( int i
= 0; i
< nCharCount
; ++i
)
1487 pTempWidths
.reset(new DeviceCoordinate
[nCharCount
]);
1490 for( int n
= mnLevel
; --n
>= 0; )
1492 // query every fallback level
1493 DeviceCoordinate nTextWidth
= mpLayouts
[n
]->FillDXArray( pTempWidths
.get() );
1496 // merge results from current level
1497 double fUnitMul
= mnUnitsPerPixel
;
1498 fUnitMul
/= mpLayouts
[n
]->GetUnitsPerPixel();
1499 nTextWidth
= (DeviceCoordinate
)(nTextWidth
* fUnitMul
+ 0.5);
1500 if( nMaxWidth
< nTextWidth
)
1501 nMaxWidth
= nTextWidth
;
1504 // calculate virtual char widths using most probable fallback layout
1505 for( int i
= 0; i
< nCharCount
; ++i
)
1507 // #i17359# restriction:
1508 // one char cannot be resolved from different fallbacks
1509 if( pCharWidths
[i
] != 0 )
1511 DeviceCoordinate nCharWidth
= pTempWidths
[i
];
1514 nCharWidth
= (DeviceCoordinate
)(nCharWidth
* fUnitMul
+ 0.5);
1515 pCharWidths
[i
] = nCharWidth
;
1522 void MultiSalLayout::GetCaretPositions( int nMaxIndex
, long* pCaretXArray
) const
1524 SalLayout
& rLayout
= *mpLayouts
[ 0 ];
1525 rLayout
.GetCaretPositions( nMaxIndex
, pCaretXArray
);
1529 std::unique_ptr
<long[]> const pTempPos(new long[nMaxIndex
]);
1530 for( int n
= 1; n
< mnLevel
; ++n
)
1532 mpLayouts
[ n
]->GetCaretPositions( nMaxIndex
, pTempPos
.get() );
1533 double fUnitMul
= mnUnitsPerPixel
;
1534 fUnitMul
/= mpLayouts
[n
]->GetUnitsPerPixel();
1535 for( int i
= 0; i
< nMaxIndex
; ++i
)
1536 if( pTempPos
[i
] >= 0 )
1538 long w
= pTempPos
[i
];
1539 w
= static_cast<long>(w
*fUnitMul
+ 0.5);
1540 pCaretXArray
[i
] = w
;
1546 int MultiSalLayout::GetNextGlyphs(int nLen
, const GlyphItem
** pGlyphs
,
1547 Point
& rPos
, int& nStart
,
1548 const PhysicalFontFace
** pFallbackFonts
) const
1550 // for multi-level fallback only single glyphs should be used
1551 if( mnLevel
> 1 && nLen
> 1 )
1554 // NOTE: nStart is tagged with current font index
1555 int nLevel
= static_cast<unsigned>(nStart
) >> GF_FONTSHIFT
;
1556 nStart
&= ~GF_FONTMASK
;
1557 for(; nLevel
< mnLevel
; ++nLevel
, nStart
=0 )
1559 SalLayout
& rLayout
= *mpLayouts
[ nLevel
];
1561 int nRetVal
= rLayout
.GetNextGlyphs(nLen
, pGlyphs
, rPos
, nStart
);
1564 int nFontTag
= nLevel
<< GF_FONTSHIFT
;
1566 for( int i
= 0; i
< nRetVal
; ++i
)
1568 // FIXME: This cast is ugly!
1569 const_cast<GlyphItem
*>(pGlyphs
[i
])->mnFallbackLevel
= nLevel
;
1570 if( pFallbackFonts
)
1572 pFallbackFonts
[ i
] = mpFallbackFonts
[ nLevel
];
1576 rPos
+= maDrawOffset
;
1581 // #111016# reset to base level font when done
1582 mpLayouts
[0]->InitFont();
1586 bool MultiSalLayout::GetOutline( SalGraphics
& rGraphics
,
1587 basegfx::B2DPolyPolygonVector
& rPPV
) const
1591 for( int i
= mnLevel
; --i
>= 0; )
1593 SalLayout
& rLayout
= *mpLayouts
[ i
];
1594 rLayout
.DrawBase() = maDrawBase
;
1595 rLayout
.DrawOffset() += maDrawOffset
;
1597 bRet
|= rLayout
.GetOutline( rGraphics
, rPPV
);
1598 rLayout
.DrawOffset() -= maDrawOffset
;
1604 bool MultiSalLayout::IsKashidaPosValid(int nCharPos
) const
1606 // Check the base layout
1607 bool bValid
= mpLayouts
[0]->IsKashidaPosValid(nCharPos
);
1609 // If base layout returned false, it might be because the character was not
1610 // supported there, so we check fallback layouts.
1613 for (int i
= 1; i
< mnLevel
; ++i
)
1615 // - 1 because there is no fallback run for the base layout, IIUC.
1616 if (maFallbackRuns
[i
- 1].PosIsInAnyRun(nCharPos
))
1618 bValid
= mpLayouts
[i
]->IsKashidaPosValid(nCharPos
);
1627 std::shared_ptr
<vcl::TextLayoutCache
> SalLayout::CreateTextLayoutCache(
1628 OUString
const&) const
1630 return nullptr; // by default, nothing to cache
1633 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */