Coverage Report

Created: 2025-12-31 10:39

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