Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/text/ImplLayoutArgs.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 <ImplLayoutArgs.hxx>
21
22
#include <unicode/ubidi.h>
23
#include <unicode/uchar.h>
24
25
#include <algorithm>
26
#include <memory>
27
#include <utility>
28
29
namespace vcl::text
30
{
31
ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCharPos,
32
                               SalLayoutFlags nFlags, LanguageTag aLanguageTag,
33
                               vcl::text::TextLayoutCache const* const pLayoutCache)
34
14.0M
    : maLanguageTag(std::move(aLanguageTag))
35
14.0M
    , mnFlags(nFlags)
36
14.0M
    , mrStr(rStr)
37
14.0M
    , mnMinCharPos(nMinCharPos)
38
14.0M
    , mnEndCharPos(nEndCharPos)
39
14.0M
    , m_pTextLayoutCache(pLayoutCache)
40
14.0M
    , mnLayoutWidth(0)
41
14.0M
    , mnOrientation(0)
42
14.0M
{
43
14.0M
    if (mnFlags & SalLayoutFlags::BiDiStrong)
44
11.0M
    {
45
        // handle strong BiDi mode
46
47
        // do not bother to BiDi analyze strong LTR/RTL
48
        // TODO: can we assume these strings do not have unicode control chars?
49
        //       if not remove the control characters from the runs
50
11.0M
        bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
51
11.0M
        AddRun(mnMinCharPos, mnEndCharPos, bRTL);
52
11.0M
    }
53
2.91M
    else
54
2.91M
    {
55
        // handle weak BiDi mode
56
2.91M
        UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl) ? 1 : 0;
57
58
        // prepare substring for BiDi analysis
59
        // TODO: reuse allocated pParaBidi
60
2.91M
        UErrorCode rcI18n = U_ZERO_ERROR;
61
2.91M
        const int nLength = mnEndCharPos - mnMinCharPos;
62
2.91M
        UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
63
2.91M
        if (!pParaBidi)
64
0
            return;
65
2.91M
        ubidi_setPara(pParaBidi, reinterpret_cast<const UChar*>(mrStr.getStr()) + mnMinCharPos,
66
2.91M
                      nLength, nLevel, nullptr, &rcI18n);
67
68
        // run BiDi algorithm
69
2.91M
        const int nRunCount = ubidi_countRuns(pParaBidi, &rcI18n);
70
7.45M
        for (int i = 0; i < nRunCount; ++i)
71
4.53M
        {
72
4.53M
            int32_t nMinPos, nRunLength;
73
4.53M
            const UBiDiDirection nDir = ubidi_getVisualRun(pParaBidi, i, &nMinPos, &nRunLength);
74
4.53M
            const int nPos0 = nMinPos + mnMinCharPos;
75
4.53M
            const int nPos1 = nPos0 + nRunLength;
76
77
4.53M
            const bool bRTL = (nDir == UBIDI_RTL);
78
4.53M
            AddRun(nPos0, nPos1, bRTL);
79
4.53M
        }
80
81
        // cleanup BiDi engine
82
2.91M
        ubidi_close(pParaBidi);
83
2.91M
    }
84
85
    // prepare calls to GetNextPos/GetNextRun
86
14.0M
    maRuns.ResetPos();
87
14.0M
}
88
89
14.0M
void ImplLayoutArgs::SetLayoutWidth(double nWidth) { mnLayoutWidth = nWidth; }
90
91
void ImplLayoutArgs::SetJustificationData(JustificationData stJustification)
92
658k
{
93
658k
    mstJustification = std::move(stJustification);
94
658k
}
95
96
14.0M
void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
97
98
0
void ImplLayoutArgs::ResetPos() { maRuns.ResetPos(); }
99
100
bool ImplLayoutArgs::GetNextPos(int* nCharPos, bool* bRTL)
101
55.7k
{
102
55.7k
    return maRuns.GetNextPos(nCharPos, bRTL);
103
55.7k
}
104
105
void ImplLayoutArgs::AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
106
0
{
107
0
    maFallbackRuns.AddRun(nMinRunPos, nEndRunPos, bRTL);
108
0
}
109
110
13.9M
bool ImplLayoutArgs::HasFallbackRun() const { return !maFallbackRuns.IsEmpty(); }
111
112
static bool IsControlChar(sal_Unicode cChar)
113
651M
{
114
    // C0 control characters
115
651M
    if ((0x0001 <= cChar) && (cChar <= 0x001F))
116
35.4M
        return true;
117
    // formatting characters
118
616M
    if ((0x200E <= cChar) && (cChar <= 0x200F))
119
67.1k
        return true;
120
616M
    if ((0x2028 <= cChar) && (cChar <= 0x202E))
121
62.1k
        return true;
122
    // deprecated formatting characters
123
616M
    if ((0x206A <= cChar) && (cChar <= 0x206F))
124
22.7k
        return true;
125
616M
    if (0x2060 == cChar)
126
2.47k
        return true;
127
    // byte order markers and invalid unicode
128
616M
    if ((cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF))
129
2.69M
        return true;
130
    // drop null character too, broken documents may contain it (ofz34898-1.doc)
131
613M
    if (cChar == 0)
132
54.7M
        return true;
133
558M
    return false;
134
613M
}
135
136
// add a run after splitting it up to get rid of control chars
137
void ImplLayoutArgs::AddRun(int nCharPos0, int nCharPos1, bool bRTL)
138
15.6M
{
139
15.6M
    SAL_WARN_IF(nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1");
140
141
    // remove control characters from runs by splitting them up
142
15.6M
    if (!bRTL)
143
13.3M
    {
144
511M
        for (int i = nCharPos0; i < nCharPos1; ++i)
145
498M
            if (IsControlChar(mrStr[i]))
146
90.8M
            {
147
                // add run until control char
148
90.8M
                maRuns.AddRun(nCharPos0, i, bRTL);
149
90.8M
                nCharPos0 = i + 1;
150
90.8M
            }
151
13.3M
    }
152
2.26M
    else
153
2.26M
    {
154
155M
        for (int i = nCharPos1; --i >= nCharPos0;)
155
153M
            if (IsControlChar(mrStr[i]))
156
2.15M
            {
157
                // add run until control char
158
2.15M
                maRuns.AddRun(i + 1, nCharPos1, bRTL);
159
2.15M
                nCharPos1 = i;
160
2.15M
            }
161
2.26M
    }
162
163
    // add remainder of run
164
15.6M
    maRuns.AddRun(nCharPos0, nCharPos1, bRTL);
165
15.6M
}
166
167
bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl)
168
0
{
169
    // Generate runs with pre-calculated glyph items instead maFallbackRuns.
170
0
    if (pGlyphsImpl != nullptr)
171
0
    {
172
0
        maRuns.Clear();
173
0
        maFallbackRuns.Clear();
174
175
0
        for (auto const& aGlyphItem : *pGlyphsImpl)
176
0
        {
177
0
            for (int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount();
178
0
                 ++i)
179
0
                maRuns.AddPos(i, aGlyphItem.IsRTLGlyph());
180
0
        }
181
182
0
        return !maRuns.IsEmpty();
183
0
    }
184
185
    // short circuit if no fallback is needed
186
0
    if (maFallbackRuns.IsEmpty())
187
0
    {
188
0
        maRuns.Clear();
189
0
        return false;
190
0
    }
191
192
0
    ImplLayoutRuns::PrepareFallbackRuns(&maRuns, &maFallbackRuns);
193
194
0
    return true;
195
0
}
196
197
bool ImplLayoutArgs::GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL)
198
19.5M
{
199
19.5M
    bool bValid = maRuns.GetRun(nMinRunPos, nEndRunPos, bRTL);
200
19.5M
    maRuns.NextRun();
201
19.5M
    return bValid;
202
19.5M
}
203
}
204
205
std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs)
206
0
{
207
0
#ifndef SAL_LOG_INFO
208
0
    (void)rArgs;
209
#else
210
    s << "ImplLayoutArgs{";
211
212
    s << "Flags=";
213
    if (rArgs.mnFlags == SalLayoutFlags::NONE)
214
        s << 0;
215
    else
216
    {
217
        bool need_or = false;
218
        s << "{";
219
#define TEST(x)                                                                                    \
220
    if (rArgs.mnFlags & SalLayoutFlags::x)                                                         \
221
    {                                                                                              \
222
        if (need_or)                                                                               \
223
            s << "|";                                                                              \
224
        s << #x;                                                                                   \
225
        need_or = true;                                                                            \
226
    }
227
        TEST(BiDiRtl);
228
        TEST(BiDiStrong);
229
        TEST(RightAlign);
230
        TEST(DisableKerning);
231
        TEST(KerningAsian);
232
        TEST(Vertical);
233
        TEST(DisableLigatures);
234
        TEST(ForFallback);
235
#undef TEST
236
        s << "}";
237
    }
238
239
    const int nLength = rArgs.mrStr.getLength();
240
241
    s << ",Length=" << nLength;
242
    s << ",MinCharPos=" << rArgs.mnMinCharPos;
243
    s << ",EndCharPos=" << rArgs.mnEndCharPos;
244
245
    s << ",Str=\"";
246
    int lim = nLength;
247
    if (lim > 10)
248
        lim = 7;
249
    for (int i = 0; i < lim; i++)
250
    {
251
        if (rArgs.mrStr[i] == '\n')
252
            s << "\\n";
253
        else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
254
            s << "\\0x" << std::hex << std::setw(2) << std::setfill('0')
255
              << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
256
        else if (rArgs.mrStr[i] < 0x7F)
257
            s << static_cast<char>(rArgs.mrStr[i]);
258
        else
259
            s << "\\u" << std::hex << std::setw(4) << std::setfill('0')
260
              << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
261
    }
262
    if (nLength > lim)
263
        s << "...";
264
    s << "\"";
265
266
    s << ",DXArray=";
267
    if (!rArgs.mstJustification.empty())
268
    {
269
        s << "[";
270
        int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
271
        lim = count;
272
        if (lim > 10)
273
            lim = 7;
274
        for (int i = 0; i < lim; i++)
275
        {
276
            s << rArgs.mstJustification.GetTotalAdvance(rArgs.mnMinCharPos + i);
277
            if (i < lim - 1)
278
                s << ",";
279
        }
280
        if (count > lim)
281
        {
282
            if (count > lim + 1)
283
                s << "...";
284
            s << rArgs.mstJustification.GetTotalAdvance(rArgs.mnMinCharPos + count - 1);
285
        }
286
        s << "]";
287
    }
288
    else
289
        s << "NULL";
290
291
    s << ",KashidaArray=";
292
    if (!rArgs.mstJustification.empty() && rArgs.mstJustification.ContainsKashidaPositions())
293
    {
294
        s << "[";
295
        int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
296
        lim = count;
297
        if (lim > 10)
298
            lim = 7;
299
        for (int i = 0; i < lim; i++)
300
        {
301
            s << rArgs.mstJustification.GetPositionHasKashida(rArgs.mnMinCharPos + i)
302
                     .value_or(false);
303
            if (i < lim - 1)
304
                s << ",";
305
        }
306
        if (count > lim)
307
        {
308
            if (count > lim + 1)
309
                s << "...";
310
            s << rArgs.mstJustification.GetPositionHasKashida(rArgs.mnMinCharPos + count - 1)
311
                     .value_or(false);
312
        }
313
        s << "]";
314
    }
315
    else
316
        s << "NULL";
317
318
    s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
319
320
    s << "}";
321
322
#endif
323
0
    return s;
324
0
}
325
326
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */