Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/gdi/CommonSalLayout.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 <sal/log.hxx>
23
#include <comphelper/configuration.hxx>
24
#include <o3tl/temporary.hxx>
25
26
#include <vcl/glyphitem.hxx>
27
#include <vcl/unohelp.hxx>
28
#include <vcl/font/Feature.hxx>
29
#include <vcl/font/FeatureParser.hxx>
30
#include <vcl/svapp.hxx>
31
32
#include <ImplLayoutArgs.hxx>
33
#include <TextLayoutCache.hxx>
34
#include <font/FontSelectPattern.hxx>
35
#include <salgdi.hxx>
36
#include <sallayout.hxx>
37
38
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39
40
#include <unicode/uchar.h>
41
#include <hb-ot.h>
42
#include <hb-graphite2.h>
43
#include <hb-icu.h>
44
#include <hb-aat.h>
45
46
#include <map>
47
#include <memory>
48
#include <set>
49
50
GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
51
14.2M
    : m_GlyphItems(rFont)
52
14.2M
    , mpVertGlyphs(nullptr)
53
14.2M
    , mbFuzzing(comphelper::IsFuzzing())
54
14.2M
{
55
14.2M
}
56
57
GenericSalLayout::~GenericSalLayout()
58
14.2M
{
59
14.2M
    if (mpVertGlyphs)
60
458
        hb_set_destroy(mpVertGlyphs);
61
14.2M
}
62
63
void GenericSalLayout::ParseFeatures(std::u16string_view aName)
64
6.98M
{
65
6.98M
    vcl::font::FeatureParser aParser(aName);
66
6.98M
    const OUString& sLanguage = aParser.getLanguage();
67
6.98M
    if (!sLanguage.isEmpty())
68
0
        msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
69
70
6.98M
    for (auto const &rFeat : aParser.getFeatures())
71
289k
    {
72
289k
        hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
73
289k
        maFeatures.push_back(aFeature);
74
289k
    }
75
6.98M
}
76
77
namespace {
78
79
struct SubRun
80
{
81
    int32_t mnMin;
82
    int32_t mnEnd;
83
    hb_script_t maScript;
84
    hb_direction_t maDirection;
85
};
86
87
struct UnclusteredGlyphData
88
{
89
    sal_Int32 m_nGlyphId;
90
    bool m_bUsed = false;
91
92
    explicit UnclusteredGlyphData(sal_Int32 nGlyphId)
93
3.06M
        : m_nGlyphId(nGlyphId)
94
3.06M
    {
95
3.06M
    }
96
};
97
98
// This is a helper class to enable correct styling and glyph placement when a grapheme cluster is
99
// split across multiple adjoining layouts.
100
//
101
// In order to justify text, we need glyphs grouped into grapheme clusters so diacritics will stay
102
// attached to characters under adjustment. However, in order to correctly position and style
103
// grapheme clusters that span multiple layouts, we need best-effort character-level position data.
104
//
105
// At time of writing, HarfBuzz cannot provide both types of information simultaneously. As a work-
106
// around, this helper class runs HarfBuzz a second time to get the missing information. Should a
107
// future version of HarfBuzz support this use case directly, this helper code should be deleted.
108
//
109
// See tdf#61444, tdf#71956, tdf#124116
110
class UnclusteredGlyphMapper
111
{
112
private:
113
    hb_buffer_t* m_pHbBuffer = nullptr;
114
    std::multimap<sal_Int32, UnclusteredGlyphData> m_aGlyphs;
115
    bool m_bEnable = false;
116
117
public:
118
    UnclusteredGlyphMapper(bool bEnable, int nGlyphCapacity)
119
6.98M
        : m_bEnable(bEnable)
120
6.98M
    {
121
6.98M
        if (!m_bEnable)
122
6.93M
        {
123
6.93M
            return;
124
6.93M
        }
125
126
42.2k
        m_pHbBuffer = hb_buffer_create();
127
42.2k
        hb_buffer_pre_allocate(m_pHbBuffer, nGlyphCapacity);
128
42.2k
    }
129
130
    ~UnclusteredGlyphMapper()
131
6.98M
    {
132
6.98M
        if (m_bEnable)
133
42.2k
        {
134
42.2k
            hb_buffer_destroy(m_pHbBuffer);
135
42.2k
        }
136
6.98M
    }
137
138
    [[nodiscard]] sal_Int32 RemapGlyph(sal_Int32 nClusterId, sal_Int32 nGlyphId)
139
454M
    {
140
454M
        if (auto it = m_aGlyphs.lower_bound(nClusterId); it != m_aGlyphs.end())
141
6.13M
        {
142
6.14M
            for (; it != m_aGlyphs.end(); ++it)
143
6.14M
            {
144
6.14M
                if (it->second.m_nGlyphId == nGlyphId && !it->second.m_bUsed)
145
6.13M
                {
146
6.13M
                    it->second.m_bUsed = true;
147
6.13M
                    return it->first;
148
6.13M
                }
149
6.14M
            }
150
6.13M
        }
151
152
448M
        return nClusterId;
153
454M
    }
154
155
    void Reset()
156
19.9M
    {
157
19.9M
        for (auto& rElement : m_aGlyphs)
158
3.06M
        {
159
3.06M
            rElement.second.m_bUsed = false;
160
3.06M
        }
161
19.9M
    }
162
163
    void ShapeSubRun(const sal_Unicode* pStr, const int nLength, const SubRun& aSubRun,
164
                     hb_font_t* pHbFont, const std::vector<hb_feature_t>& maFeatures,
165
                     hb_language_t oHbLanguage)
166
19.9M
    {
167
19.9M
        if (!m_bEnable)
168
19.8M
        {
169
19.8M
            return;
170
19.8M
        }
171
172
67.9k
        m_aGlyphs.clear();
173
174
67.9k
        hb_buffer_clear_contents(m_pHbBuffer);
175
176
67.9k
        const int nMinRunPos = aSubRun.mnMin;
177
67.9k
        const int nEndRunPos = aSubRun.mnEnd;
178
67.9k
        const int nRunLen = nEndRunPos - nMinRunPos;
179
180
67.9k
        int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
181
67.9k
        nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
182
183
67.9k
        if (nMinRunPos == 0)
184
1.23k
        {
185
1.23k
            nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
186
1.23k
        }
187
188
67.9k
        if (nEndRunPos == nLength)
189
1.84k
        {
190
1.84k
            nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
191
1.84k
        }
192
193
67.9k
        hb_buffer_set_flags(m_pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
194
195
67.9k
        hb_buffer_set_cluster_level(m_pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
196
197
67.9k
        hb_buffer_set_direction(m_pHbBuffer, aSubRun.maDirection);
198
67.9k
        hb_buffer_set_script(m_pHbBuffer, aSubRun.maScript);
199
67.9k
        hb_buffer_set_language(m_pHbBuffer, oHbLanguage);
200
201
67.9k
        hb_buffer_add_utf16(m_pHbBuffer, reinterpret_cast<uint16_t const*>(pStr), nLength,
202
67.9k
                            nMinRunPos, nRunLen);
203
204
        // The shapers that we want HarfBuzz to use, in the order of
205
        // preference.
206
67.9k
        const char* const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
207
67.9k
        if (!hb_shape_full(pHbFont, m_pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers))
208
0
        {
209
0
            SAL_WARN("vcl.harfbuzz", "hb_shape_full failed");
210
0
            hb_buffer_set_length(m_pHbBuffer, 0);
211
0
        }
212
213
67.9k
        int nRunGlyphCount = hb_buffer_get_length(m_pHbBuffer);
214
67.9k
        hb_glyph_info_t* pHbGlyphInfos = hb_buffer_get_glyph_infos(m_pHbBuffer, nullptr);
215
216
3.13M
        for (int i = 0; i < nRunGlyphCount; ++i)
217
3.06M
        {
218
3.06M
            int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
219
3.06M
            int32_t nCharPos = pHbGlyphInfos[i].cluster;
220
221
3.06M
            m_aGlyphs.emplace(nCharPos, UnclusteredGlyphData{ nGlyphIndex });
222
3.06M
        }
223
67.9k
    }
224
};
225
}
226
227
namespace {
228
    int32_t GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
229
7.80M
    {
230
        // Override orientation of fullwidth colon , semi-colon,
231
        // and Bopomofo tonal marks.
232
7.80M
        if ((cCh == 0xff1a || cCh == 0xff1b
233
7.80M
           || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
234
3.42k
                && rTag.getLanguage() == "zh")
235
7
            return U_VO_TRANSFORMED_UPRIGHT;
236
237
7.80M
        return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
238
7.80M
    }
239
} // namespace
240
241
SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
242
3.77M
{
243
3.77M
    SalLayoutGlyphs glyphs;
244
3.77M
    glyphs.AppendImpl(m_GlyphItems.clone());
245
3.77M
    return glyphs;
246
3.77M
}
247
248
void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos,
249
                                       sal_Int32 nCharEnd, bool bRightToLeft)
250
8.80M
{
251
8.80M
    if (nCharPos < 0 || nCharPos == nCharEnd || mbFuzzing)
252
8.80M
        return;
253
254
0
    if (!mxBreak.is())
255
0
        mxBreak = vcl::unohelper::CreateBreakIterator();
256
257
0
    const css::lang::Locale& rLocale(rArgs.maLanguageTag.getLocale());
258
259
    //if position nCharPos is missing in the font, grab the entire grapheme and
260
    //mark all glyphs as missing so the whole thing is rendered with the same
261
    //font
262
0
    sal_Int32 nDone;
263
0
    int nGraphemeEndPos = mxBreak->nextCharacters(rArgs.mrStr, nCharEnd - 1, rLocale,
264
0
                                                  css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
265
    // Safely advance nCharPos in case it is a non-BMP character.
266
0
    rArgs.mrStr.iterateCodePoints(&nCharPos);
267
0
    int nGraphemeStartPos =
268
0
        mxBreak->previousCharacters(rArgs.mrStr, nCharPos, rLocale,
269
0
            css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
270
271
    // tdf#107612
272
    // If the start of the fallback run is Mongolian character and the previous
273
    // character is NNBSP, we want to include the NNBSP in the fallback since
274
    // it has special uses in Mongolian and have to be in the same text run to
275
    // work.
276
0
    sal_Int32 nTempPos = nGraphemeStartPos;
277
0
    if (nGraphemeStartPos > 0)
278
0
    {
279
0
        auto nCurrChar = rArgs.mrStr.iterateCodePoints(&nTempPos, 0);
280
0
        auto nPrevChar = rArgs.mrStr.iterateCodePoints(&nTempPos, -1);
281
0
        if (nPrevChar == 0x202F
282
0
            && u_getIntPropertyValue(nCurrChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)
283
0
            nGraphemeStartPos = nTempPos;
284
0
    }
285
286
    //stay inside the Layout range (e.g. with tdf124116-1.odt)
287
0
    nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos);
288
0
    nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos);
289
290
0
    rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
291
0
}
292
293
void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
294
10.5M
{
295
10.5M
    SalLayout::AdjustLayout(rArgs);
296
297
10.5M
    if (!rArgs.mstJustification.empty())
298
504k
    {
299
504k
        ApplyJustificationData(rArgs.mstJustification);
300
504k
    }
301
10.0M
    else if (rArgs.mnLayoutWidth)
302
309
    {
303
309
        Justify(rArgs.mnLayoutWidth);
304
309
    }
305
10.0M
    else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
306
8.07k
         && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
307
773
    {
308
        // apply asian kerning if the glyphs are not already formatted
309
773
        ApplyAsianKerning(rArgs.mrStr);
310
773
    }
311
10.5M
}
312
313
void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
314
13.9k
{
315
    //call platform dependent DrawText functions
316
13.9k
    rSalGraphics.DrawTextLayout( *this );
317
13.9k
}
318
319
// Find if the nominal glyph of the character is an input to “vert” feature.
320
// We don’t check for a specific script or language as it shouldn’t matter
321
// here; if the glyph would be the result from applying “vert” for any
322
// script/language then we want to always treat it as upright glyph.
323
bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
324
25.3k
{
325
25.3k
    sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector);
326
25.3k
    if (!nGlyphIndex)
327
4.89k
        return false;
328
329
20.4k
    if (!mpVertGlyphs)
330
458
    {
331
458
        hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont());
332
458
        mpVertGlyphs = hb_set_create();
333
334
        // Find all GSUB lookups for “vert” feature.
335
458
        hb_set_t* pLookups = hb_set_create();
336
458
        hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
337
458
        hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
338
458
        if (!hb_set_is_empty(pLookups))
339
0
        {
340
            // Find the input glyphs in each lookup (i.e. the glyphs that
341
            // this lookup applies to).
342
0
            hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
343
0
            while (hb_set_next(pLookups, &nIdx))
344
0
            {
345
0
                hb_set_t* pGlyphs = hb_set_create();
346
0
                hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
347
0
                        nullptr,  // glyphs before
348
0
                        pGlyphs,  // glyphs input
349
0
                        nullptr,  // glyphs after
350
0
                        nullptr); // glyphs out
351
0
                hb_set_union(mpVertGlyphs, pGlyphs);
352
0
            }
353
0
        }
354
458
        hb_set_destroy(pLookups);
355
458
    }
356
357
20.4k
    return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
358
25.3k
}
359
360
bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
361
14.2M
{
362
    // No need to touch m_GlyphItems at all for an empty string.
363
14.2M
    if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
364
9.94k
        return true;
365
366
14.2M
    ImplLayoutRuns aFallbackRuns;
367
368
14.2M
    if (pGlyphs)
369
7.29M
    {
370
        // Work with pre-computed glyph items.
371
7.29M
        m_GlyphItems = *pGlyphs;
372
373
7.29M
        for(const GlyphItem& item : m_GlyphItems)
374
332M
        {
375
332M
            if(!item.glyphId())
376
19.7M
            {
377
19.7M
                sal_Int32 nCurrCharPos = item.charPos();
378
19.7M
                auto aCurrChar = rArgs.mrStr.iterateCodePoints(&nCurrCharPos, 0);
379
                // tdf#126111: fallback is meaningless for PUA codepoints
380
19.7M
                if (u_charType(aCurrChar) != U_PRIVATE_USE_CHAR)
381
19.1M
                    aFallbackRuns.AddPos(item.charPos(), item.IsRTLGlyph());
382
19.7M
            }
383
332M
        }
384
385
7.29M
        for (const auto& rRun : aFallbackRuns)
386
3.16M
        {
387
3.16M
            SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL);
388
3.16M
        }
389
390
        // Some flags are set as a side effect of text layout, restore them here.
391
7.29M
        rArgs.mnFlags |= pGlyphs->GetFlags();
392
7.29M
        return true;
393
7.29M
    }
394
395
6.98M
    hb_font_t *pHbFont = GetFont().GetHbFont();
396
6.98M
    bool isGraphite = GetFont().IsGraphiteFont();
397
398
    // tdf#163215: Identify layouts that don't have strict kashida position validation.
399
6.98M
    m_bHasFontKashidaPositions = false;
400
6.98M
    if (!(rArgs.mnFlags & SalLayoutFlags::DisableKashidaValidation))
401
6.98M
    {
402
6.98M
        hb_face_t* pHbFace = hb_font_get_face(pHbFont);
403
6.98M
        m_bHasFontKashidaPositions = !hb_aat_layout_has_substitution(pHbFace);
404
6.98M
    }
405
406
6.98M
    int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
407
6.98M
    m_GlyphItems.reserve(nGlyphCapacity);
408
409
6.98M
    const int nLength = rArgs.mrStr.getLength();
410
6.98M
    const sal_Unicode *pStr = rArgs.mrStr.getStr();
411
412
6.98M
    std::shared_ptr<const vcl::text::TextLayoutCache> pNewScriptRun;
413
6.98M
    vcl::text::TextLayoutCache const* pTextLayout;
414
6.98M
    if (rArgs.m_pTextLayoutCache)
415
3.77M
    {
416
3.77M
        pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
417
3.77M
    }
418
3.20M
    else
419
3.20M
    {
420
        // tdf#92064, tdf#162663:
421
        // Also use the global LRU cache for full string script runs.
422
        // This obviates O(n^2) calls to vcl::ScriptRun::next() when laying out large paragraphs.
423
3.20M
        pNewScriptRun = vcl::text::TextLayoutCache::Create(rArgs.mrStr);
424
3.20M
        pTextLayout = pNewScriptRun.get();
425
3.20M
    }
426
427
    // nBaseOffset is used to align vertical text to the center of rotated
428
    // horizontal text. That is the offset from original baseline to
429
    // the center of EM box. Maybe we can use OpenType base table to improve this
430
    // in the future.
431
6.98M
    double nBaseOffset = 0;
432
6.98M
    if (rArgs.mnFlags & SalLayoutFlags::Vertical)
433
237k
    {
434
237k
        hb_font_extents_t extents;
435
237k
        if (hb_font_get_h_extents(pHbFont, &extents))
436
237k
            nBaseOffset = ( extents.ascender + extents.descender ) / 2.0;
437
237k
    }
438
439
6.98M
    UnclusteredGlyphMapper stClusterMapper{
440
6.98M
        bool{ rArgs.mnFlags & SalLayoutFlags::UnclusteredGlyphs }, nGlyphCapacity
441
6.98M
    };
442
443
6.98M
    hb_buffer_t* pHbBuffer = hb_buffer_create();
444
6.98M
    hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
445
446
6.98M
    const vcl::font::FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
447
6.98M
    if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
448
2.52M
    {
449
2.52M
        SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
450
2.52M
        maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
451
2.52M
    }
452
453
6.98M
    if (rArgs.mnFlags & SalLayoutFlags::DisableLigatures)
454
106k
    {
455
106k
        SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
456
457
        // Both of these are optional ligatures, enabled by default but not for
458
        // orthographically-required ligatures.
459
106k
        maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
460
106k
        maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
461
106k
    }
462
463
6.98M
    ParseFeatures(rFontSelData.maTargetName);
464
465
6.98M
    double nXScale = 0;
466
6.98M
    double nYScale = 0;
467
6.98M
    GetFont().GetScale(&nXScale, &nYScale);
468
469
6.98M
    double nCurrX = 0.0;
470
19.9M
    while (true)
471
19.9M
    {
472
19.9M
        int nBidiMinRunPos, nBidiEndRunPos;
473
19.9M
        bool bRightToLeft;
474
19.9M
        if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
475
6.98M
            break;
476
477
        // Find script subruns.
478
12.9M
        std::vector<SubRun> aSubRuns;
479
12.9M
        int nCurrentPos = nBidiMinRunPos;
480
12.9M
        size_t k = 0;
481
6.49G
        for (; k < pTextLayout->runs.size(); ++k)
482
6.49G
        {
483
6.49G
            vcl::text::Run const& rRun(pTextLayout->runs[k]);
484
6.49G
            if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
485
12.9M
            {
486
12.9M
                break;
487
12.9M
            }
488
6.49G
        }
489
490
12.9M
        if (isGraphite)
491
0
        {
492
0
            hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
493
0
            aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
494
0
        }
495
12.9M
        else
496
12.9M
        {
497
32.5M
            while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
498
19.5M
            {
499
19.5M
                int32_t nMinRunPos = nCurrentPos;
500
19.5M
                int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
501
19.5M
                hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
502
19.5M
                hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
503
                // For vertical text, further divide the runs based on character
504
                // orientation.
505
19.5M
                if (rArgs.mnFlags & SalLayoutFlags::Vertical)
506
3.88M
                {
507
3.88M
                    sal_Int32 nIdx = nMinRunPos;
508
11.6M
                    while (nIdx < nEndRunPos)
509
7.80M
                    {
510
7.80M
                        sal_Int32 nPrevIdx = nIdx;
511
7.80M
                        sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
512
7.80M
                        int32_t aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
513
514
7.80M
                        sal_UCS4 aVariationSelector = 0;
515
7.80M
                        if (nIdx < nEndRunPos)
516
3.93M
                        {
517
3.93M
                            sal_Int32 nNextIdx = nIdx;
518
3.93M
                            sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
519
3.93M
                            if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
520
8.61k
                            {
521
8.61k
                                nIdx = nNextIdx;
522
8.61k
                                aVariationSelector = aNextChar;
523
8.61k
                            }
524
3.93M
                        }
525
526
                        // Characters with U and Tu vertical orientation should
527
                        // be shaped in vertical direction. But characters
528
                        // with Tr should be shaped in vertical direction
529
                        // only if they have vertical alternates, otherwise
530
                        // they should be shaped in horizontal direction
531
                        // and then rotated.
532
                        // See http://unicode.org/reports/tr50/#vo
533
7.80M
                        if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT ||
534
3.74M
                            (aVo == U_VO_TRANSFORMED_ROTATED &&
535
25.3k
                             HasVerticalAlternate(aChar, aVariationSelector)))
536
4.06M
                        {
537
4.06M
                            aDirection = HB_DIRECTION_TTB;
538
4.06M
                        }
539
3.74M
                        else
540
3.74M
                        {
541
3.74M
                            aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
542
3.74M
                        }
543
544
7.80M
                        if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection || aSubRuns.back().maScript != aScript)
545
4.25M
                            aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
546
3.55M
                        else
547
3.55M
                            aSubRuns.back().mnEnd = nIdx;
548
7.80M
                    }
549
3.88M
                }
550
15.6M
                else
551
15.6M
                {
552
15.6M
                    aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
553
15.6M
                }
554
555
19.5M
                nCurrentPos = nEndRunPos;
556
19.5M
                ++k;
557
19.5M
            }
558
12.9M
        }
559
560
        // RTL subruns should be reversed to ensure that final glyph order is
561
        // correct.
562
12.9M
        if (bRightToLeft)
563
1.11M
            std::reverse(aSubRuns.begin(), aSubRuns.end());
564
565
12.9M
        for (const auto& aSubRun : aSubRuns)
566
19.9M
        {
567
19.9M
            hb_buffer_clear_contents(pHbBuffer);
568
569
19.9M
            const int nMinRunPos = aSubRun.mnMin;
570
19.9M
            const int nEndRunPos = aSubRun.mnEnd;
571
19.9M
            const int nRunLen = nEndRunPos - nMinRunPos;
572
573
19.9M
            int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
574
575
            // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
576
19.9M
            nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
577
            // Produce the unsafe-to-concat flag, which marks the cluster boundaries at which a
578
            // glyph subset cannot be cut out and reused without reshaping (see isSafeToBreak).
579
19.9M
            nHbFlags |= HB_BUFFER_FLAG_PRODUCE_UNSAFE_TO_CONCAT;
580
581
19.9M
            if (nMinRunPos == 0)
582
3.27M
                nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
583
19.9M
            if (nEndRunPos == nLength)
584
3.40M
                nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
585
586
19.9M
            hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
587
19.9M
            hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
588
589
19.9M
            hb_language_t oHbLanguage = nullptr;
590
19.9M
            if (!msLanguage.isEmpty())
591
0
            {
592
0
                oHbLanguage = hb_language_from_string(msLanguage.getStr(), msLanguage.getLength());
593
0
            }
594
19.9M
            else
595
19.9M
            {
596
19.9M
                OString sLanguage
597
19.9M
                    = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
598
19.9M
                oHbLanguage = hb_language_from_string(sLanguage.getStr(), sLanguage.getLength());
599
19.9M
            }
600
601
19.9M
            hb_buffer_set_language(pHbBuffer, oHbLanguage);
602
603
19.9M
            hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
604
19.9M
            hb_buffer_add_utf16(
605
19.9M
                pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
606
19.9M
                nMinRunPos, nRunLen);
607
608
            // The shapers that we want HarfBuzz to use, in the order of
609
            // preference.
610
19.9M
            const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
611
19.9M
            if (!hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers))
612
0
            {
613
0
                SAL_WARN("vcl.harfbuzz", "hb_shape_full failed");
614
0
                hb_buffer_set_length(pHbBuffer, 0);
615
0
            }
616
617
            // Populate glyph cluster remapping data
618
19.9M
            stClusterMapper.ShapeSubRun(pStr, nLength, aSubRun, pHbFont, maFeatures, oHbLanguage);
619
620
19.9M
            int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
621
19.9M
            hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
622
19.9M
            hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
623
624
            // tdf#164106: Grapheme clusters can be split across multiple layouts. To do this,
625
            // the complete string is laid out, and only the necessary glyphs are extracted.
626
            // These sub-layouts are positioned side-by-side to form the complete text.
627
            // This approach is good enough for most diacritic cases, but it cannot handle cases
628
            // where a glyph with an advance is reordered into a different sub-layout.
629
19.9M
            bool bStartClusterOutOfOrder = false;
630
19.9M
            bool bEndClusterOutOfOrder = false;
631
19.9M
            {
632
19.9M
                double nNormalAdvance = 0.0;
633
19.9M
                double nStartAdvance = 0.0;
634
19.9M
                double nEndAdvance = 0.0;
635
636
19.9M
                auto fnHandleGlyph = [&](int i)
637
227M
                {
638
227M
                    int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
639
227M
                    int32_t nCluster = pHbGlyphInfos[i].cluster;
640
227M
                    auto nOrigCharPos = stClusterMapper.RemapGlyph(nCluster, nGlyphIndex);
641
642
227M
                    double nAdvance = 0.0;
643
227M
                    if (aSubRun.maDirection == HB_DIRECTION_TTB)
644
4.06M
                    {
645
4.06M
                        nAdvance = -pHbPositions[i].y_advance;
646
4.06M
                    }
647
223M
                    else
648
223M
                    {
649
223M
                        nAdvance = pHbPositions[i].x_advance;
650
223M
                    }
651
652
227M
                    nNormalAdvance += nAdvance;
653
654
227M
                    if (nOrigCharPos < rArgs.mnDrawMinCharPos)
655
153k
                    {
656
153k
                        nStartAdvance += nAdvance;
657
153k
                        if (nStartAdvance != nNormalAdvance)
658
0
                        {
659
0
                            bStartClusterOutOfOrder = true;
660
0
                        }
661
153k
                    }
662
663
227M
                    if (nOrigCharPos < rArgs.mnDrawEndCharPos)
664
214M
                    {
665
214M
                        nEndAdvance += nAdvance;
666
214M
                        if (nEndAdvance != nNormalAdvance)
667
0
                        {
668
0
                            bEndClusterOutOfOrder = true;
669
0
                        }
670
214M
                    }
671
227M
                };
672
673
19.9M
                if (bRightToLeft)
674
1.43M
                {
675
50.6M
                    for (int i = nRunGlyphCount - 1; i >= 0; --i)
676
49.2M
                    {
677
49.2M
                        fnHandleGlyph(i);
678
49.2M
                    }
679
1.43M
                }
680
18.4M
                else
681
18.4M
                {
682
196M
                    for (int i = 0; i < nRunGlyphCount; ++i)
683
178M
                    {
684
178M
                        fnHandleGlyph(i);
685
178M
                    }
686
18.4M
                }
687
688
19.9M
                stClusterMapper.Reset();
689
19.9M
            }
690
691
247M
            for (int i = 0; i < nRunGlyphCount; ++i) {
692
227M
                int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
693
227M
                int32_t nCharPos = pHbGlyphInfos[i].cluster;
694
227M
                int32_t nCharCount = 0;
695
227M
                bool bInCluster = false;
696
227M
                bool bClusterStart = false;
697
698
                // Find the number of characters that make up this glyph.
699
227M
                if (!bRightToLeft)
700
178M
                {
701
                    // If the cluster is the same as previous glyph, then this
702
                    // already consumed, skip.
703
178M
                    if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
704
992k
                    {
705
992k
                        nCharCount = 0;
706
992k
                        bInCluster = true;
707
992k
                    }
708
177M
                    else
709
177M
                    {
710
                        // Find the next glyph with a different cluster, or the
711
                        // end of text.
712
177M
                        int j = i;
713
177M
                        int32_t nNextCharPos = nCharPos;
714
514M
                        while (nNextCharPos == nCharPos && j < nRunGlyphCount)
715
336M
                            nNextCharPos = pHbGlyphInfos[j++].cluster;
716
717
177M
                        if (nNextCharPos == nCharPos)
718
18.4M
                            nNextCharPos = nEndRunPos;
719
177M
                        nCharCount = nNextCharPos - nCharPos;
720
177M
                        if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
721
177M
                            (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
722
605k
                            bClusterStart = true;
723
177M
                    }
724
178M
                }
725
49.2M
                else
726
49.2M
                {
727
                    // If the cluster is the same as previous glyph, then this
728
                    // will be consumed later, skip.
729
49.2M
                    if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
730
136k
                    {
731
136k
                        nCharCount = 0;
732
136k
                        bInCluster = true;
733
136k
                    }
734
49.0M
                    else
735
49.0M
                    {
736
                        // Find the previous glyph with a different cluster, or
737
                        // the end of text.
738
49.0M
                        int j = i;
739
49.0M
                        int32_t nNextCharPos = nCharPos;
740
145M
                        while (nNextCharPos == nCharPos && j >= 0)
741
96.8M
                            nNextCharPos = pHbGlyphInfos[j--].cluster;
742
743
49.0M
                        if (nNextCharPos == nCharPos)
744
1.43M
                            nNextCharPos = nEndRunPos;
745
49.0M
                        nCharCount = nNextCharPos - nCharPos;
746
49.0M
                        if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
747
49.0M
                            (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
748
79.8k
                            bClusterStart = true;
749
49.0M
                    }
750
49.2M
                }
751
752
                // if needed request glyph fallback by updating LayoutArgs
753
227M
                auto nOrigCharPos = stClusterMapper.RemapGlyph(nCharPos, nGlyphIndex);
754
227M
                if (!nGlyphIndex)
755
25.6M
                {
756
                    // Only request fallback for grapheme clusters that are drawn
757
25.6M
                    if (nOrigCharPos >= rArgs.mnDrawMinCharPos
758
25.6M
                        && nOrigCharPos < rArgs.mnDrawEndCharPos)
759
25.6M
                    {
760
25.6M
                        sal_Int32 nCurrCharPos = nOrigCharPos;
761
25.6M
                        auto aCurrChar = rArgs.mrStr.iterateCodePoints(&nCurrCharPos, 0);
762
                        // tdf#126111: fallback is meaningless for PUA codepoints
763
25.6M
                        if (u_charType(aCurrChar) != U_PRIVATE_USE_CHAR)
764
23.8M
                        {
765
23.8M
                            aFallbackRuns.AddPos(nOrigCharPos, bRightToLeft);
766
23.8M
                            if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
767
0
                                continue;
768
23.8M
                        }
769
25.6M
                    }
770
25.6M
                }
771
772
227M
                GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
773
227M
                if (bRightToLeft)
774
49.2M
                    nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
775
776
227M
                if (bClusterStart)
777
685k
                    nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
778
779
227M
                if (bInCluster)
780
1.12M
                    nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
781
782
227M
                sal_UCS4 aChar
783
227M
                    = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
784
785
227M
                if (u_isUWhiteSpace(aChar))
786
4.79M
                    nGlyphFlags |= GlyphItemFlags::IS_SPACING;
787
788
227M
                hb_glyph_flags_t const eHbGlyphFlags
789
227M
                    = hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]);
790
227M
                if (eHbGlyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_CONCAT)
791
98.6M
                    nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_CONCAT;
792
793
227M
                if (!m_bHasFontKashidaPositions
794
227M
                    || (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i])
795
227M
                        & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL))
796
32.5k
                    nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
797
798
227M
                double nAdvance, nXOffset, nYOffset;
799
227M
                if (aSubRun.maDirection == HB_DIRECTION_TTB)
800
4.06M
                {
801
4.06M
                    nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
802
803
4.06M
                    nAdvance = -pHbPositions[i].y_advance;
804
4.06M
                    nXOffset = -pHbPositions[i].y_offset;
805
4.06M
                    nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
806
4.06M
                }
807
223M
                else
808
223M
                {
809
223M
                    nAdvance =  pHbPositions[i].x_advance;
810
223M
                    nXOffset =  pHbPositions[i].x_offset;
811
223M
                    nYOffset = -pHbPositions[i].y_offset;
812
223M
                }
813
814
227M
                nAdvance = nAdvance * nXScale;
815
227M
                nXOffset = nXOffset * nXScale;
816
227M
                nYOffset = nYOffset * nYScale;
817
227M
                if (!GetSubpixelPositioning())
818
18.0M
                {
819
18.0M
                    nAdvance = std::round(nAdvance);
820
18.0M
                    nXOffset = std::round(nXOffset);
821
18.0M
                    nYOffset = std::round(nYOffset);
822
18.0M
                }
823
824
227M
                basegfx::B2DPoint aNewPos(nCurrX + nXOffset, nYOffset);
825
227M
                const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
826
227M
                                    nAdvance, nXOffset, nYOffset, nOrigCharPos);
827
828
227M
                auto nLowerBound = (bStartClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos());
829
227M
                auto nUpperBound = (bEndClusterOutOfOrder ? aGI.charPos() : aGI.origCharPos());
830
227M
                if (nLowerBound >= rArgs.mnDrawMinCharPos && nUpperBound < rArgs.mnDrawEndCharPos)
831
214M
                {
832
214M
                    m_GlyphItems.push_back(aGI);
833
214M
                }
834
835
227M
                if (nLowerBound >= rArgs.mnDrawOriginCluster
836
227M
                    && nUpperBound < rArgs.mnDrawEndCharPos)
837
214M
                {
838
214M
                    nCurrX += nAdvance;
839
214M
                }
840
227M
            }
841
19.9M
        }
842
12.9M
    }
843
844
6.98M
    hb_buffer_destroy(pHbBuffer);
845
846
6.98M
    for (const auto& rRun : aFallbackRuns)
847
5.63M
    {
848
5.63M
        SetNeedFallback(rArgs, rRun.m_nMinRunPos, rRun.m_nEndRunPos, rRun.m_bRTL);
849
5.63M
    }
850
851
    // Some flags are set as a side effect of text layout, save them here.
852
6.98M
    if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
853
3.77M
        m_GlyphItems.SetFlags(rArgs.mnFlags);
854
855
6.98M
    return true;
856
6.98M
}
857
858
void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUString& rStr) const
859
7.41M
{
860
7.41M
    const int nCharCount = mnEndCharPos - mnMinCharPos;
861
862
7.41M
    rCharWidths.clear();
863
7.41M
    rCharWidths.resize(nCharCount, 0);
864
865
7.41M
    css::uno::Reference<css::i18n::XBreakIterator> xBreak;
866
7.41M
    const css::lang::Locale& rLocale(maLanguageTag.getLocale());
867
868
7.41M
    for (auto const& aGlyphItem : m_GlyphItems)
869
339M
    {
870
339M
        if (aGlyphItem.charPos() >= mnEndCharPos)
871
0
            continue;
872
873
339M
        unsigned int nGraphemeCount = 0;
874
339M
        if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
875
491k
        {
876
            // We are calculating DX array for cursor positions and this is a
877
            // ligature, find out how many grapheme clusters are in it.
878
491k
            if (!xBreak.is())
879
266k
                xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator();
880
881
            // Count grapheme clusters in the ligature.
882
491k
            sal_Int32 nDone;
883
491k
            sal_Int32 nPos = aGlyphItem.charPos();
884
990k
            while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
885
498k
            {
886
498k
                nPos = xBreak->nextCharacters(rStr, nPos, rLocale,
887
498k
                    css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
888
498k
                nGraphemeCount++;
889
498k
            }
890
491k
        }
891
892
339M
        if (nGraphemeCount > 1)
893
4.88k
        {
894
            // More than one grapheme cluster, we want to distribute the glyph
895
            // width over them.
896
4.88k
            std::vector<double> aWidths(nGraphemeCount);
897
898
            // Check if the glyph has ligature caret positions.
899
4.88k
            unsigned int nCarets = nGraphemeCount;
900
4.88k
            std::vector<hb_position_t> aCarets(nGraphemeCount);
901
4.88k
            hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
902
4.88k
                aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
903
4.88k
                aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
904
905
            // Carets are 1-less than the grapheme count (since the last
906
            // position is defined by glyph width), if the count does not
907
            // match, ignore it.
908
4.88k
            if (nCarets == nGraphemeCount - 1)
909
0
            {
910
                // Scale the carets and apply glyph offset to them since they
911
                // are based on the default glyph metrics.
912
0
                double fScale = 0;
913
0
                GetFont().GetScale(&fScale, nullptr);
914
0
                for (size_t i = 0; i < nCarets; i++)
915
0
                    aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
916
917
                // Use the glyph width for the last caret.
918
0
                aCarets[nCarets] = aGlyphItem.newWidth();
919
920
                // Carets are absolute from the X origin of the glyph, turn
921
                // them to relative widths that we need below.
922
0
                for (size_t i = 0; i < nGraphemeCount; i++)
923
0
                    aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
924
925
                // Carets are in visual order, but we want widths in logical
926
                // order.
927
0
                if (aGlyphItem.IsRTLGlyph())
928
0
                    std::reverse(aWidths.begin(), aWidths.end());
929
0
            }
930
4.88k
            else
931
4.88k
            {
932
                // The glyph has no carets, distribute the width evenly.
933
4.88k
                auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
934
4.88k
                std::fill(aWidths.begin(), aWidths.end(), nWidth);
935
936
                // Add rounding difference to the last component to maintain
937
                // ligature width.
938
4.88k
                aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
939
4.88k
            }
940
941
            // Set the width of each grapheme cluster.
942
4.88k
            sal_Int32 nDone;
943
4.88k
            sal_Int32 nPos = aGlyphItem.charPos();
944
4.88k
            for (auto nWidth : aWidths)
945
12.4k
            {
946
12.4k
                rCharWidths[nPos - mnMinCharPos] += nWidth;
947
12.4k
                nPos = xBreak->nextCharacters(rStr, nPos, rLocale,
948
12.4k
                    css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
949
12.4k
            }
950
4.88k
        }
951
339M
        else
952
339M
            rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
953
339M
    }
954
7.41M
}
955
956
// - stJustification:
957
//   - contains adjustments to glyph advances (usually due to justification).
958
//   - contains kashida insertion positions, for Arabic script justification.
959
//     - The number of kashidas is calculated from the adjusted advances.
960
void GenericSalLayout::ApplyJustificationData(const JustificationData& rstJustification)
961
504k
{
962
504k
    int nCharCount = mnEndCharPos - mnMinCharPos;
963
504k
    std::vector<double> aOldCharWidths;
964
504k
    std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]);
965
966
    // Get the natural character widths (i.e. before applying DX adjustments).
967
504k
    GetCharWidths(aOldCharWidths, {});
968
969
    // Calculate the character widths after DX adjustments.
970
20.6M
    for (int i = 0; i < nCharCount; ++i)
971
20.1M
    {
972
20.1M
        if (i == 0)
973
504k
        {
974
504k
            pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i);
975
504k
        }
976
19.6M
        else
977
19.6M
        {
978
19.6M
            pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i)
979
19.6M
                                - rstJustification.GetTotalAdvance(mnMinCharPos + i - 1);
980
19.6M
        }
981
20.1M
    }
982
983
    // Map of Kashida insertion points (in the glyph items vector) and the
984
    // requested width.
985
504k
    std::map<size_t, std::pair<double, double>> pKashidas;
986
987
    // The accumulated difference in X position.
988
504k
    double nDelta = 0;
989
990
    // Apply the DX adjustments to glyph positions and widths.
991
504k
    size_t i = 0;
992
11.8M
    while (i < m_GlyphItems.size())
993
11.3M
    {
994
        // Accumulate the width difference for all characters corresponding to
995
        // this glyph.
996
11.3M
        int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
997
11.3M
        double nDiff = 0;
998
22.6M
        for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
999
11.3M
            nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
1000
1001
11.3M
        if (!m_GlyphItems[i].IsRTLGlyph())
1002
9.34M
        {
1003
            // Adjust the width and position of the first (leftmost) glyph in
1004
            // the cluster.
1005
9.34M
            m_GlyphItems[i].addNewWidth(nDiff);
1006
9.34M
            m_GlyphItems[i].adjustLinearPosX(nDelta);
1007
1008
            // Adjust the position of the rest of the glyphs in the cluster.
1009
9.34M
            while (++i < m_GlyphItems.size())
1010
8.88M
            {
1011
8.88M
                if (!m_GlyphItems[i].IsInCluster())
1012
8.88M
                    break;
1013
2.11k
                m_GlyphItems[i].adjustLinearPosX(nDelta);
1014
2.11k
            }
1015
9.34M
        }
1016
1.96M
        else if (m_GlyphItems[i].IsInCluster())
1017
1.31k
        {
1018
            // RTL glyph in the middle of the cluster, will be handled in the
1019
            // loop below.
1020
1.31k
            i++;
1021
1.31k
        }
1022
1.96M
        else // RTL
1023
1.96M
        {
1024
            // Adjust the width and position of the first (rightmost) glyph in
1025
            // the cluster. This is RTL, so we put all the adjustment to the
1026
            // left of the glyph.
1027
1.96M
            m_GlyphItems[i].addNewWidth(nDiff);
1028
1.96M
            m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
1029
1030
            // Adjust the X position of the rest of the glyphs in the cluster.
1031
            // We iterate backwards since this is an RTL glyph.
1032
1.96M
            for (size_t j = i; j >= 1 && m_GlyphItems[j - 1].IsInCluster(); --j)
1033
1.32k
                m_GlyphItems[j - 1].adjustLinearPosX(nDelta + nDiff);
1034
1035
            // This is a Kashida insertion position, mark it. Kashida glyphs
1036
            // will be inserted below.
1037
1.96M
            if (rstJustification.GetPositionHasKashida(mnMinCharPos + nCharPos).value_or(false))
1038
1.84k
            {
1039
1.84k
                pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
1040
1.84k
            }
1041
1042
1.96M
            i++;
1043
1.96M
        }
1044
1045
        // Increment the delta, the loop above makes sure we do so only once
1046
        // for every character (cluster) not for every glyph (otherwise we
1047
        // would apply it multiple times for each glyph belonging to the same
1048
        // character which is wrong as DX adjustments are character based).
1049
11.3M
        nDelta += nDiff;
1050
11.3M
    }
1051
1052
    // Insert Kashida glyphs.
1053
504k
    if (pKashidas.empty())
1054
504k
        return;
1055
1056
    // Find Kashida glyph width and index.
1057
15
    sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
1058
15
    double nKashidaWidth = GetFont().GetKashidaWidth();
1059
15
    if (!GetSubpixelPositioning())
1060
1
        nKashidaWidth = std::ceil(nKashidaWidth);
1061
1062
15
    if (nKashidaWidth <= 0)
1063
15
    {
1064
15
        SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
1065
15
        return;
1066
15
    }
1067
1068
0
    size_t nInserted = 0;
1069
0
    for (auto const& pKashida : pKashidas)
1070
0
    {
1071
0
        auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
1072
1073
        // The total Kashida width.
1074
0
        auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
1075
1076
        // Number of times to repeat each Kashida.
1077
0
        int nCopies = 1;
1078
0
        if (nTotalWidth > nKashidaWidth)
1079
0
            nCopies = nTotalWidth / nKashidaWidth;
1080
1081
        // See if we can improve the fit by adding an extra Kashidas and
1082
        // squeezing them together a bit.
1083
0
        double nOverlap = 0;
1084
0
        double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
1085
0
        if (nShortfall > 0)
1086
0
        {
1087
0
            ++nCopies;
1088
0
            double nExcess = nCopies * nKashidaWidth - nTotalWidth;
1089
0
            if (nExcess > 0)
1090
0
                nOverlap = nExcess / (nCopies - 1);
1091
0
        }
1092
1093
0
        basegfx::B2DPoint aPos = pGlyphIter->linearPos();
1094
0
        int nCharPos = pGlyphIter->charPos();
1095
0
        GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
1096
        // Move to the left side of the adjusted width and start inserting
1097
        // glyphs there.
1098
0
        aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
1099
0
        while (nCopies--)
1100
0
        {
1101
0
            GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0, nCharPos);
1102
0
            pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
1103
0
            aPos.adjustX(nKashidaWidth - nOverlap);
1104
0
            ++pGlyphIter;
1105
0
            ++nInserted;
1106
0
        }
1107
0
    }
1108
0
}
1109
1110
0
bool GenericSalLayout::HasFontKashidaPositions() const { return m_bHasFontKashidaPositions; }
1111
1112
// Kashida will be inserted between nCharPos and nNextCharPos.
1113
bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
1114
0
{
1115
    // Search for glyph items corresponding to nCharPos and nNextCharPos.
1116
0
    auto const aGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
1117
0
                                      [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
1118
0
    auto const aNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
1119
0
                                          [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
1120
1121
    // If either is not found then a ligature is created at this position, we
1122
    // can’t insert Kashida here.
1123
0
    if (aGlyph == m_GlyphItems.end() || aNextGlyph == m_GlyphItems.end())
1124
0
        return false;
1125
1126
    // If the either character is not supported by this layout, return false so
1127
    // that fallback layouts would be checked for it.
1128
0
    if (aGlyph->glyphId() == 0 || aNextGlyph->glyphId() == 0)
1129
0
        return false;
1130
1131
    // Lastly check if this position is kashida-safe.
1132
0
    return aNextGlyph->IsSafeToInsertKashida();
1133
0
}
1134
1135
void GenericSalLayout::drawSalLayout(void* pSurface, const basegfx::BColor& rTextColor, bool bAntiAliased) const
1136
0
{
1137
0
    Application::GetDefaultDevice()->GetGraphics()->DrawSalLayout(*this, pSurface, rTextColor, bAntiAliased);
1138
0
}
1139
1140
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */