Coverage Report

Created: 2026-06-30 11:14

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