Coverage Report

Created: 2026-05-16 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/text/qtextengine.cpp
Line
Count
Source
1
// Copyright (C) 2021 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:data-parser
4
5
#include <QtCore/private/qflatmap_p.h>
6
#include <QtGui/private/qtguiglobal_p.h>
7
#include "qdebug.h"
8
#include "qtextformat.h"
9
#include "qtextformat_p.h"
10
#include "qtextengine_p.h"
11
#include "qabstracttextdocumentlayout.h"
12
#include "qabstracttextdocumentlayout_p.h"
13
#include "qtextlayout.h"
14
#include "qtextboundaryfinder.h"
15
#include <QtCore/private/qunicodetables_p.h>
16
#include "qvarlengtharray.h"
17
#include "qfont.h"
18
#include "qfont_p.h"
19
#include "qfontengine_p.h"
20
#include "qstring.h"
21
#include "qtextdocument_p.h"
22
#include "qrawfont.h"
23
#include "qrawfont_p.h"
24
#include <qguiapplication.h>
25
#include <qinputmethod.h>
26
#include <qlocale.h>
27
#include <algorithm>
28
#include <stdlib.h>
29
30
QT_BEGIN_NAMESPACE
31
32
#if !defined(QT_NO_EMOJISEGMENTER)
33
Q_STATIC_LOGGING_CATEGORY(lcEmojiSegmenter, "qt.text.emojisegmenter")
34
#endif
35
36
static const float smallCapsFraction = 0.7f;
37
38
namespace {
39
// Helper class used in QTextEngine::itemize
40
// keep it out here to allow us to keep supporting various compilers.
41
class Itemizer {
42
public:
43
    Itemizer(const QString &string, const QScriptAnalysis *analysis, QScriptItemArray &items)
44
0
        : m_string(string),
45
0
        m_analysis(analysis),
46
0
        m_items(items)
47
0
    {
48
0
    }
49
0
    ~Itemizer() = default;
50
    /// generate the script items
51
    /// The caps parameter is used to choose the algorithm of splitting text and assigning roles to the textitems
52
    void generate(int start, int length, QFont::Capitalization caps)
53
0
    {
54
0
        if (caps == QFont::SmallCaps)
55
0
            generateScriptItemsSmallCaps(reinterpret_cast<const ushort *>(m_string.unicode()), start, length);
56
0
        else if (caps == QFont::Capitalize)
57
0
            generateScriptItemsCapitalize(start, length);
58
0
        else if (caps != QFont::MixedCase) {
59
0
            generateScriptItemsAndChangeCase(start, length,
60
0
                caps == QFont::AllLowercase ? QScriptAnalysis::Lowercase : QScriptAnalysis::Uppercase);
61
0
        }
62
0
        else
63
0
            generateScriptItems(start, length);
64
0
    }
65
66
private:
67
    enum { MaxItemLength = 4096 };
68
69
    void generateScriptItemsAndChangeCase(int start, int length, QScriptAnalysis::Flags flags)
70
0
    {
71
0
        generateScriptItems(start, length);
72
0
        if (m_items.isEmpty()) // the next loop won't work in that case
73
0
            return;
74
0
        QScriptItemArray::Iterator iter = m_items.end();
75
0
        do {
76
0
            iter--;
77
0
            if (iter->analysis.flags < QScriptAnalysis::LineOrParagraphSeparator)
78
0
                iter->analysis.flags = flags;
79
0
        } while (iter->position > start);
80
0
    }
81
82
    void generateScriptItems(int start, int length)
83
0
    {
84
0
        if (!length)
85
0
            return;
86
0
        const int end = start + length;
87
0
        for (int i = start + 1; i < end; ++i) {
88
0
            if (m_analysis[i].bidiLevel == m_analysis[start].bidiLevel
89
0
                && m_analysis[i].flags == m_analysis[start].flags
90
0
                && (m_analysis[i].script == m_analysis[start].script || m_string[i] == u'.')
91
0
                && m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject
92
0
                && i - start < MaxItemLength)
93
0
                continue;
94
0
            m_items.append(QScriptItem(start, m_analysis[start]));
95
0
            start = i;
96
0
        }
97
0
        m_items.append(QScriptItem(start, m_analysis[start]));
98
0
    }
99
100
    void generateScriptItemsCapitalize(int start, int length)
101
0
    {
102
0
        if (!length)
103
0
            return;
104
105
0
        if (!m_splitter)
106
0
            m_splitter = std::make_unique<QTextBoundaryFinder>(QTextBoundaryFinder::Word,
107
0
                                                 m_string.constData(), m_string.size(),
108
0
                                                 /*buffer*/nullptr, /*buffer size*/0);
109
110
0
        m_splitter->setPosition(start);
111
0
        QScriptAnalysis itemAnalysis = m_analysis[start];
112
113
0
        if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartOfItem)
114
0
            itemAnalysis.flags = QScriptAnalysis::Uppercase;
115
116
0
        m_splitter->toNextBoundary();
117
118
0
        const int end = start + length;
119
0
        for (int i = start + 1; i < end; ++i) {
120
0
            bool atWordStart = false;
121
122
0
            if (i == m_splitter->position()) {
123
0
                if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartOfItem) {
124
0
                    Q_ASSERT(m_analysis[i].flags < QScriptAnalysis::TabOrObject);
125
0
                    atWordStart = true;
126
0
                }
127
128
0
                m_splitter->toNextBoundary();
129
0
            }
130
131
0
            if (m_analysis[i] == itemAnalysis
132
0
                && m_analysis[i].flags < QScriptAnalysis::TabOrObject
133
0
                && !atWordStart
134
0
                && i - start < MaxItemLength)
135
0
                continue;
136
137
0
            m_items.append(QScriptItem(start, itemAnalysis));
138
0
            start = i;
139
0
            itemAnalysis = m_analysis[start];
140
141
0
            if (atWordStart)
142
0
                itemAnalysis.flags = QScriptAnalysis::Uppercase;
143
0
        }
144
0
        m_items.append(QScriptItem(start, itemAnalysis));
145
0
    }
146
147
    void generateScriptItemsSmallCaps(const ushort *uc, int start, int length)
148
0
    {
149
0
        if (!length)
150
0
            return;
151
0
        bool lower = (QChar::category(uc[start]) == QChar::Letter_Lowercase);
152
0
        const int end = start + length;
153
        // split text into parts that are already uppercase and parts that are lowercase, and mark the latter to be uppercased later.
154
0
        for (int i = start + 1; i < end; ++i) {
155
0
            bool l = (QChar::category(uc[i]) == QChar::Letter_Lowercase);
156
0
            if ((m_analysis[i] == m_analysis[start])
157
0
                && m_analysis[i].flags < QScriptAnalysis::TabOrObject
158
0
                && l == lower
159
0
                && i - start < MaxItemLength)
160
0
                continue;
161
0
            m_items.append(QScriptItem(start, m_analysis[start]));
162
0
            if (lower)
163
0
                m_items.last().analysis.flags = QScriptAnalysis::SmallCaps;
164
165
0
            start = i;
166
0
            lower = l;
167
0
        }
168
0
        m_items.append(QScriptItem(start, m_analysis[start]));
169
0
        if (lower)
170
0
            m_items.last().analysis.flags = QScriptAnalysis::SmallCaps;
171
0
    }
172
173
    const QString &m_string;
174
    const QScriptAnalysis * const m_analysis;
175
    QScriptItemArray &m_items;
176
    std::unique_ptr<QTextBoundaryFinder> m_splitter;
177
};
178
179
// -----------------------------------------------------------------------------------------------------
180
//
181
// The Unicode Bidi algorithm.
182
// See http://www.unicode.org/reports/tr9/tr9-37.html
183
//
184
// -----------------------------------------------------------------------------------------------------
185
186
// #define DEBUG_BIDI
187
#ifndef DEBUG_BIDI
188
enum { BidiDebugEnabled = false };
189
0
#define BIDI_DEBUG if (1) ; else qDebug
190
#else
191
enum { BidiDebugEnabled = true };
192
static const char *directions[] = {
193
    "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON",
194
    "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN",
195
    "DirLRI", "DirRLI", "DirFSI", "DirPDI"
196
};
197
#define BIDI_DEBUG qDebug
198
QDebug operator<<(QDebug d, QChar::Direction dir) {
199
    return (d << directions[dir]);
200
}
201
#endif
202
203
struct QBidiAlgorithm {
204
    template<typename T> using Vector = QVarLengthArray<T, 64>;
205
206
    QBidiAlgorithm(const QChar *text, QScriptAnalysis *analysis, int length, bool baseDirectionIsRtl)
207
0
        : text(text),
208
0
          analysis(analysis),
209
0
          length(length),
210
0
          baseLevel(baseDirectionIsRtl ? 1 : 0)
211
0
    {
212
213
0
    }
214
215
    struct IsolatePair {
216
        int start;
217
        int end;
218
    };
219
220
    void initScriptAnalysisAndIsolatePairs(Vector<IsolatePair> &isolatePairs)
221
0
    {
222
0
        int isolateStack[128];
223
0
        int isolateLevel = 0;
224
        // load directions of string, and determine isolate pairs
225
0
        for (int i = 0; i < length; ++i) {
226
0
            int pos = i;
227
0
            char32_t uc = text[i].unicode();
228
0
            if (QChar::isHighSurrogate(uc) && i < length - 1 && text[i + 1].isLowSurrogate()) {
229
0
                ++i;
230
0
                analysis[i].bidiDirection = QChar::DirNSM;
231
0
                uc = QChar::surrogateToUcs4(ushort(uc), text[i].unicode());
232
0
            }
233
0
            const QUnicodeTables::Properties *p = QUnicodeTables::properties(uc);
234
0
            analysis[pos].bidiDirection = QChar::Direction(p->direction);
235
0
            switch (QChar::Direction(p->direction)) {
236
0
            case QChar::DirON:
237
                // all mirrored chars are DirON
238
0
                if (p->mirrorDiff)
239
0
                    analysis[pos].bidiFlags = QScriptAnalysis::BidiMirrored;
240
0
                break;
241
0
            case QChar::DirLRE:
242
0
            case QChar::DirRLE:
243
0
            case QChar::DirLRO:
244
0
            case QChar::DirRLO:
245
0
            case QChar::DirPDF:
246
0
            case QChar::DirBN:
247
0
                analysis[pos].bidiFlags = QScriptAnalysis::BidiMaybeResetToParagraphLevel|QScriptAnalysis::BidiBN;
248
0
                break;
249
0
            case QChar::DirLRI:
250
0
            case QChar::DirRLI:
251
0
            case QChar::DirFSI:
252
0
                if (isolateLevel < 128) {
253
0
                    isolateStack[isolateLevel] = isolatePairs.size();
254
0
                    isolatePairs.append({ pos, length });
255
0
                }
256
0
                ++isolateLevel;
257
0
                analysis[pos].bidiFlags = QScriptAnalysis::BidiMaybeResetToParagraphLevel;
258
0
                break;
259
0
            case QChar::DirPDI:
260
0
                if (isolateLevel > 0) {
261
0
                    --isolateLevel;
262
0
                    if (isolateLevel < 128)
263
0
                        isolatePairs[isolateStack[isolateLevel]].end = pos;
264
0
                }
265
0
                Q_FALLTHROUGH();
266
0
            case QChar::DirWS:
267
0
                analysis[pos].bidiFlags = QScriptAnalysis::BidiMaybeResetToParagraphLevel;
268
0
                break;
269
0
            case QChar::DirS:
270
0
            case QChar::DirB:
271
0
                analysis[pos].bidiFlags = QScriptAnalysis::BidiResetToParagraphLevel;
272
0
                if (uc == QChar::ParagraphSeparator) {
273
                    // close all open isolates as we start a new paragraph
274
0
                    while (isolateLevel > 0) {
275
0
                        --isolateLevel;
276
0
                        if (isolateLevel < 128)
277
0
                            isolatePairs[isolateStack[isolateLevel]].end = pos;
278
0
                    }
279
0
                }
280
0
                break;
281
0
            default:
282
0
                break;
283
0
            }
284
0
        }
285
0
    }
286
287
    struct DirectionalRun {
288
        int start;
289
        int end;
290
        int continuation;
291
        ushort level;
292
        bool isContinuation;
293
        bool hasContent;
294
    };
295
296
    void generateDirectionalRuns(const Vector<IsolatePair> &isolatePairs, Vector<DirectionalRun> &runs)
297
0
    {
298
0
        struct DirectionalStack {
299
0
            enum { MaxDepth = 125 };
300
0
            struct Item {
301
0
                ushort level;
302
0
                bool isOverride;
303
0
                bool isIsolate;
304
0
                int runBeforeIsolate;
305
0
            };
306
0
            Item items[128];
307
0
            int counter = 0;
308
309
0
            void push(Item i) {
310
0
                items[counter] = i;
311
0
                ++counter;
312
0
            }
313
0
            void pop() {
314
0
                --counter;
315
0
            }
316
0
            int depth() const {
317
0
                return counter;
318
0
            }
319
0
            const Item &top() const {
320
0
                return items[counter - 1];
321
0
            }
322
0
        } stack;
323
0
        int overflowIsolateCount = 0;
324
0
        int overflowEmbeddingCount = 0;
325
0
        int validIsolateCount = 0;
326
327
0
        ushort level = baseLevel;
328
0
        bool override = false;
329
0
        stack.push({ level, false, false, -1 });
330
331
0
        BIDI_DEBUG() << "resolving explicit levels";
332
0
        int runStart = 0;
333
0
        int continuationFrom = -1;
334
0
        int lastRunWithContent = -1;
335
0
        bool runHasContent = false;
336
337
0
        auto appendRun = [&](int runEnd) {
338
0
            if (runEnd < runStart)
339
0
                return;
340
0
            bool isContinuation = false;
341
0
            if (continuationFrom != -1) {
342
0
                runs[continuationFrom].continuation = runs.size();
343
0
                isContinuation = true;
344
0
            } else if (lastRunWithContent != -1 && level == runs.at(lastRunWithContent).level) {
345
0
                runs[lastRunWithContent].continuation = runs.size();
346
0
                isContinuation = true;
347
0
            }
348
0
            if (runHasContent)
349
0
                lastRunWithContent = runs.size();
350
0
            BIDI_DEBUG() << "   appending run start/end" << runStart << runEnd << "level" << level;
351
0
            runs.append({ runStart, runEnd, -1, level, isContinuation, runHasContent });
352
0
            runHasContent = false;
353
0
            runStart = runEnd + 1;
354
0
            continuationFrom = -1;
355
0
        };
356
357
0
        int isolatePairPosition = 0;
358
359
0
        for (int i = 0; i < length; ++i) {
360
0
            QChar::Direction dir = analysis[i].bidiDirection;
361
362
363
0
            auto doEmbed = [&](bool isRtl, bool isOverride, bool isIsolate) {
364
0
                if (isIsolate) {
365
0
                    if (override)
366
0
                        analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
367
0
                    runHasContent = true;
368
0
                    lastRunWithContent = -1;
369
0
                    ++isolatePairPosition;
370
0
                }
371
0
                int runBeforeIsolate = runs.size();
372
0
                ushort newLevel = isRtl ? ((stack.top().level + 1) | 1) : ((stack.top().level + 2) & ~1);
373
0
                if (newLevel <= DirectionalStack::MaxDepth && !overflowEmbeddingCount && !overflowIsolateCount) {
374
0
                    if (isIsolate)
375
0
                        ++validIsolateCount;
376
0
                    else
377
0
                        runBeforeIsolate = -1;
378
0
                    appendRun(isIsolate ? i : i - 1);
379
0
                    BIDI_DEBUG() << "pushing new item on stack: level" << (int)newLevel << "isOverride" << isOverride << "isIsolate" << isIsolate << runBeforeIsolate;
380
0
                    stack.push({ newLevel, isOverride, isIsolate, runBeforeIsolate });
381
0
                    override = isOverride;
382
0
                    level = newLevel;
383
0
                } else {
384
0
                    if (isIsolate)
385
0
                        ++overflowIsolateCount;
386
0
                    else if (!overflowIsolateCount)
387
0
                        ++overflowEmbeddingCount;
388
0
                }
389
0
                if (!isIsolate) {
390
0
                    if (override)
391
0
                        analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
392
0
                    else
393
0
                        analysis[i].bidiDirection = QChar::DirBN;
394
0
                }
395
0
            };
396
397
0
            switch (dir) {
398
0
            case QChar::DirLRE:
399
0
                doEmbed(false, false, false);
400
0
                break;
401
0
            case QChar::DirRLE:
402
0
                doEmbed(true, false, false);
403
0
                break;
404
0
            case QChar::DirLRO:
405
0
                doEmbed(false, true, false);
406
0
                break;
407
0
            case QChar::DirRLO:
408
0
                doEmbed(true, true, false);
409
0
                break;
410
0
            case QChar::DirLRI:
411
0
                doEmbed(false, false, true);
412
0
                break;
413
0
            case QChar::DirRLI:
414
0
                doEmbed(true, false, true);
415
0
                break;
416
0
            case QChar::DirFSI: {
417
0
                bool isRtl = false;
418
0
                if (isolatePairPosition < isolatePairs.size()) {
419
0
                    const auto &pair = isolatePairs.at(isolatePairPosition);
420
0
                    Q_ASSERT(pair.start == i);
421
0
                    isRtl = QStringView(text + pair.start + 1, pair.end - pair.start - 1).isRightToLeft();
422
0
                }
423
0
                doEmbed(isRtl, false, true);
424
0
                break;
425
0
            }
426
427
0
            case QChar::DirPDF:
428
0
                if (override)
429
0
                    analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
430
0
                else
431
0
                    analysis[i].bidiDirection = QChar::DirBN;
432
0
                if (overflowIsolateCount) {
433
0
                    ; // do nothing
434
0
                } else if (overflowEmbeddingCount) {
435
0
                    --overflowEmbeddingCount;
436
0
                } else if (!stack.top().isIsolate && stack.depth() >= 2) {
437
0
                    appendRun(i);
438
0
                    stack.pop();
439
0
                    override = stack.top().isOverride;
440
0
                    level = stack.top().level;
441
0
                    BIDI_DEBUG() << "popped PDF from stack, level now" << (int)stack.top().level;
442
0
                }
443
0
                break;
444
0
            case QChar::DirPDI:
445
0
                runHasContent = true;
446
0
                if (overflowIsolateCount) {
447
0
                    --overflowIsolateCount;
448
0
                } else if (validIsolateCount == 0) {
449
0
                    ; // do nothing
450
0
                } else {
451
0
                    appendRun(i - 1);
452
0
                    overflowEmbeddingCount = 0;
453
0
                    while (!stack.top().isIsolate)
454
0
                        stack.pop();
455
0
                    continuationFrom = stack.top().runBeforeIsolate;
456
0
                    BIDI_DEBUG() << "popped PDI from stack, level now" << (int)stack.top().level << "continuation from" << continuationFrom;
457
0
                    stack.pop();
458
0
                    override = stack.top().isOverride;
459
0
                    level = stack.top().level;
460
0
                    lastRunWithContent = -1;
461
0
                    --validIsolateCount;
462
0
                }
463
0
                if (override)
464
0
                    analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
465
0
                break;
466
0
            case QChar::DirB:
467
                // paragraph separator, go down to base direction, reset all state
468
0
                if (text[i].unicode() == QChar::ParagraphSeparator) {
469
0
                    appendRun(i - 1);
470
0
                    while (stack.counter > 1) {
471
                        // there might be remaining isolates on the stack that are missing a PDI. Those need to get
472
                        // a continuation indicating to take the eos from the end of the string (ie. the paragraph level)
473
0
                        const auto &t = stack.top();
474
0
                        if (t.isIsolate) {
475
0
                            runs[t.runBeforeIsolate].continuation = -2;
476
0
                        }
477
0
                        --stack.counter;
478
0
                    }
479
0
                    continuationFrom = -1;
480
0
                    lastRunWithContent = -1;
481
0
                    validIsolateCount = 0;
482
0
                    overflowIsolateCount = 0;
483
0
                    overflowEmbeddingCount = 0;
484
0
                    level = baseLevel;
485
0
                }
486
0
                break;
487
0
            default:
488
0
                runHasContent = true;
489
0
                Q_FALLTHROUGH();
490
0
            case QChar::DirBN:
491
0
                if (override)
492
0
                    analysis[i].bidiDirection = (level & 1) ? QChar::DirR : QChar::DirL;
493
0
                break;
494
0
            }
495
0
        }
496
0
        appendRun(length - 1);
497
0
        while (stack.counter > 1) {
498
            // there might be remaining isolates on the stack that are missing a PDI. Those need to get
499
            // a continuation indicating to take the eos from the end of the string (ie. the paragraph level)
500
0
            const auto &t = stack.top();
501
0
            if (t.isIsolate) {
502
0
                runs[t.runBeforeIsolate].continuation = -2;
503
0
            }
504
0
            --stack.counter;
505
0
        }
506
0
    }
507
508
    void resolveExplicitLevels(Vector<DirectionalRun> &runs)
509
0
    {
510
0
        Vector<IsolatePair> isolatePairs;
511
512
0
        initScriptAnalysisAndIsolatePairs(isolatePairs);
513
0
        generateDirectionalRuns(isolatePairs, runs);
514
0
    }
515
516
    struct IsolatedRunSequenceIterator {
517
        struct Position {
518
            int current = -1;
519
            int pos = -1;
520
521
0
            Position() = default;
522
0
            Position(int current, int pos) : current(current), pos(pos) {}
523
524
0
            bool isValid() const { return pos != -1; }
525
0
            void clear() { pos = -1; }
526
        };
527
        IsolatedRunSequenceIterator(const Vector<DirectionalRun> &runs, int i)
528
0
            : runs(runs),
529
0
              current(i)
530
0
        {
531
0
            pos = runs.at(current).start;
532
0
        }
533
0
        int operator *() const { return pos; }
534
0
        bool atEnd() const { return pos < 0; }
535
0
        void operator++() {
536
0
            ++pos;
537
0
            if (pos > runs.at(current).end) {
538
0
                current = runs.at(current).continuation;
539
0
                if (current > -1)
540
0
                    pos = runs.at(current).start;
541
0
                else
542
0
                    pos = -1;
543
0
            }
544
0
        }
545
0
        void setPosition(Position p) {
546
0
            current = p.current;
547
0
            pos = p.pos;
548
0
        }
549
0
        Position position() const {
550
0
            return Position(current, pos);
551
0
        }
552
0
        bool operator !=(int position) const {
553
0
            return pos != position;
554
0
        }
555
556
        const Vector<DirectionalRun> &runs;
557
        int current;
558
        int pos;
559
    };
560
561
562
    void resolveW1W2W3(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
563
0
    {
564
0
        QChar::Direction last = sos;
565
0
        QChar::Direction lastStrong = sos;
566
0
        IsolatedRunSequenceIterator it(runs, i);
567
0
        while (!it.atEnd()) {
568
0
            int pos = *it;
569
570
            // Rule W1: Resolve NSM
571
0
            QChar::Direction current = analysis[pos].bidiDirection;
572
0
            if (current == QChar::DirNSM) {
573
0
                current = last;
574
0
                analysis[pos].bidiDirection = current;
575
0
            } else if (current >= QChar::DirLRI) {
576
0
                last = QChar::DirON;
577
0
            } else if (current == QChar::DirBN) {
578
0
                current = last;
579
0
            } else {
580
                // there shouldn't be any explicit embedding marks here
581
0
                Q_ASSERT(current != QChar::DirLRE);
582
0
                Q_ASSERT(current != QChar::DirRLE);
583
0
                Q_ASSERT(current != QChar::DirLRO);
584
0
                Q_ASSERT(current != QChar::DirRLO);
585
0
                Q_ASSERT(current != QChar::DirPDF);
586
587
0
                last = current;
588
0
            }
589
590
            // Rule W2
591
0
            if (current == QChar::DirEN && lastStrong == QChar::DirAL) {
592
0
                current = QChar::DirAN;
593
0
                analysis[pos].bidiDirection = current;
594
0
            }
595
596
            // remember last strong char for rule W2
597
0
            if (current == QChar::DirL || current == QChar::DirR) {
598
0
                lastStrong = current;
599
0
            } else if (current == QChar::DirAL) {
600
                // Rule W3
601
0
                lastStrong = current;
602
0
                analysis[pos].bidiDirection = QChar::DirR;
603
0
            }
604
0
            last = current;
605
0
            ++it;
606
0
        }
607
0
    }
608
609
610
    void resolveW4(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
611
0
    {
612
        // Rule W4
613
0
        QChar::Direction secondLast = sos;
614
615
0
        IsolatedRunSequenceIterator it(runs, i);
616
0
        int lastPos = *it;
617
0
        QChar::Direction last = analysis[lastPos].bidiDirection;
618
619
//            BIDI_DEBUG() << "Applying rule W4/W5";
620
0
        ++it;
621
0
        while (!it.atEnd()) {
622
0
            int pos = *it;
623
0
            QChar::Direction current = analysis[pos].bidiDirection;
624
0
            if (current == QChar::DirBN) {
625
0
                ++it;
626
0
                continue;
627
0
            }
628
//                BIDI_DEBUG() << pos << secondLast << last << current;
629
0
            if (last == QChar::DirES && current == QChar::DirEN && secondLast == QChar::DirEN) {
630
0
                last = QChar::DirEN;
631
0
                analysis[lastPos].bidiDirection = last;
632
0
            } else if (last == QChar::DirCS) {
633
0
                if (current == QChar::DirEN && secondLast == QChar::DirEN) {
634
0
                    last = QChar::DirEN;
635
0
                    analysis[lastPos].bidiDirection = last;
636
0
                } else if (current == QChar::DirAN && secondLast == QChar::DirAN) {
637
0
                    last = QChar::DirAN;
638
0
                    analysis[lastPos].bidiDirection = last;
639
0
                }
640
0
            }
641
0
            secondLast = last;
642
0
            last = current;
643
0
            lastPos = pos;
644
0
            ++it;
645
0
        }
646
0
    }
647
648
    void resolveW5(const Vector<DirectionalRun> &runs, int i)
649
0
    {
650
        // Rule W5
651
0
        IsolatedRunSequenceIterator::Position lastETPosition;
652
653
0
        IsolatedRunSequenceIterator it(runs, i);
654
0
        int lastPos = *it;
655
0
        QChar::Direction last = analysis[lastPos].bidiDirection;
656
0
        if (last == QChar::DirET || last == QChar::DirBN)
657
0
            lastETPosition = it.position();
658
659
0
        ++it;
660
0
        while (!it.atEnd()) {
661
0
            int pos = *it;
662
0
            QChar::Direction current = analysis[pos].bidiDirection;
663
0
            if (current == QChar::DirBN) {
664
0
                ++it;
665
0
                continue;
666
0
            }
667
0
            if (current == QChar::DirET) {
668
0
                if (last == QChar::DirEN) {
669
0
                    current = QChar::DirEN;
670
0
                    analysis[pos].bidiDirection = current;
671
0
                } else if (!lastETPosition.isValid()) {
672
0
                    lastETPosition = it.position();
673
0
                }
674
0
            } else if (lastETPosition.isValid()) {
675
0
                if (current == QChar::DirEN) {
676
0
                    it.setPosition(lastETPosition);
677
0
                    while (it != pos) {
678
0
                        int pos = *it;
679
0
                        analysis[pos].bidiDirection = QChar::DirEN;
680
0
                        ++it;
681
0
                    }
682
0
                }
683
0
                lastETPosition.clear();
684
0
            }
685
0
            last = current;
686
0
            lastPos = pos;
687
0
            ++it;
688
0
        }
689
0
    }
690
691
    void resolveW6W7(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
692
0
    {
693
0
        QChar::Direction lastStrong = sos;
694
0
        IsolatedRunSequenceIterator it(runs, i);
695
0
        while (!it.atEnd()) {
696
0
            int pos = *it;
697
698
            // Rule W6
699
0
            QChar::Direction current = analysis[pos].bidiDirection;
700
0
            if (current == QChar::DirBN) {
701
0
                ++it;
702
0
                continue;
703
0
            }
704
0
            if (current == QChar::DirET || current == QChar::DirES || current == QChar::DirCS) {
705
0
                analysis[pos].bidiDirection = QChar::DirON;
706
0
            }
707
708
            // Rule W7
709
0
            else if (current == QChar::DirL || current == QChar::DirR) {
710
0
                lastStrong = current;
711
0
            } else if (current == QChar::DirEN && lastStrong == QChar::DirL) {
712
0
                analysis[pos].bidiDirection = lastStrong;
713
0
            }
714
0
            ++it;
715
0
        }
716
0
    }
717
718
    struct BracketPair {
719
        int first;
720
        int second;
721
722
0
        bool isValid() const { return second > 0; }
723
724
0
        QChar::Direction containedDirection(const QScriptAnalysis *analysis, QChar::Direction embeddingDir) const {
725
0
            int isolateCounter = 0;
726
0
            QChar::Direction containedDir = QChar::DirON;
727
0
            for (int i = first + 1; i < second; ++i) {
728
0
                QChar::Direction dir = analysis[i].bidiDirection;
729
0
                if (isolateCounter) {
730
0
                    if (dir == QChar::DirPDI)
731
0
                        --isolateCounter;
732
0
                    continue;
733
0
                }
734
0
                if (dir == QChar::DirL) {
735
0
                    containedDir = dir;
736
0
                    if (embeddingDir == dir)
737
0
                        break;
738
0
                } else if (dir == QChar::DirR || dir == QChar::DirAN || dir == QChar::DirEN) {
739
0
                    containedDir = QChar::DirR;
740
0
                    if (embeddingDir == QChar::DirR)
741
0
                        break;
742
0
                } else if (dir == QChar::DirLRI || dir == QChar::DirRLI || dir == QChar::DirFSI)
743
0
                    ++isolateCounter;
744
0
            }
745
0
            BIDI_DEBUG() << "    contained dir for backet pair" << first << "/" << second << "is" << containedDir;
746
0
            return containedDir;
747
0
        }
748
    };
749
750
751
    struct BracketStack {
752
        struct Item {
753
0
            Item() = default;
754
0
            Item(uint pairedBracked, int position) : pairedBracked(pairedBracked), position(position) {}
755
            uint pairedBracked = 0;
756
            int position = 0;
757
        };
758
759
0
        void push(uint closingUnicode, int pos) {
760
0
            if (position < MaxDepth)
761
0
                stack[position] = Item(closingUnicode, pos);
762
0
            ++position;
763
0
        }
764
0
        int match(uint unicode) {
765
0
            Q_ASSERT(!overflowed());
766
0
            int p = position;
767
0
            while (--p >= 0) {
768
0
                if (stack[p].pairedBracked == unicode ||
769
                    // U+3009 and U+2329 are canonical equivalents of each other. Fortunately it's the only pair in Unicode 10
770
0
                    (stack[p].pairedBracked == 0x3009 && unicode == 0x232a) ||
771
0
                    (stack[p].pairedBracked == 0x232a && unicode == 0x3009)) {
772
0
                    position = p;
773
0
                    return stack[p].position;
774
0
                }
775
776
0
            }
777
0
            return -1;
778
0
        }
779
780
        enum { MaxDepth = 63 };
781
        Item stack[MaxDepth];
782
        int position = 0;
783
784
0
        bool overflowed() const { return position > MaxDepth; }
785
    };
786
787
    void resolveN0(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos)
788
0
    {
789
0
        ushort level = runs.at(i).level;
790
791
0
        Vector<BracketPair> bracketPairs;
792
0
        {
793
0
            BracketStack bracketStack;
794
0
            IsolatedRunSequenceIterator it(runs, i);
795
0
            while (!it.atEnd()) {
796
0
                int pos = *it;
797
0
                QChar::Direction dir = analysis[pos].bidiDirection;
798
0
                if (dir == QChar::DirON) {
799
                    // assumes no mirrored pirs outside BMP (util/unicode guarantees this):
800
0
                    const QUnicodeTables::Properties *p = QUnicodeTables::properties(char16_t{text[pos].unicode()});
801
0
                    if (p->mirrorDiff) {
802
                        // either opening or closing bracket
803
0
                        if (p->category == QChar::Punctuation_Open) {
804
                            // opening bracked
805
0
                            uint closingBracked = text[pos].unicode() + p->mirrorDiff;
806
0
                            bracketStack.push(closingBracked, bracketPairs.size());
807
0
                            if (bracketStack.overflowed()) {
808
0
                                bracketPairs.clear();
809
0
                                break;
810
0
                            }
811
0
                            bracketPairs.append({ pos, -1 });
812
0
                        } else if (p->category == QChar::Punctuation_Close) {
813
0
                            int pairPos = bracketStack.match(text[pos].unicode());
814
0
                            if (pairPos != -1)
815
0
                                bracketPairs[pairPos].second = pos;
816
0
                        }
817
0
                    }
818
0
                }
819
0
                ++it;
820
0
            }
821
0
        }
822
823
0
        if (BidiDebugEnabled && bracketPairs.size()) {
824
0
            BIDI_DEBUG() << "matched bracket pairs:";
825
0
            for (int i = 0; i < bracketPairs.size(); ++i)
826
0
                BIDI_DEBUG() << "   " << bracketPairs.at(i).first << bracketPairs.at(i).second;
827
0
        }
828
829
0
        QChar::Direction lastStrong = sos;
830
0
        IsolatedRunSequenceIterator it(runs, i);
831
0
        QChar::Direction embeddingDir = (level & 1) ? QChar::DirR : QChar::DirL;
832
0
        for (int i = 0; i < bracketPairs.size(); ++i) {
833
0
            const auto &pair = bracketPairs.at(i);
834
0
            if (!pair.isValid())
835
0
                continue;
836
0
            QChar::Direction containedDir = pair.containedDirection(analysis, embeddingDir);
837
0
            if (containedDir == QChar::DirON) {
838
0
                BIDI_DEBUG() << "    3: resolve bracket pair" << i << "to DirON";
839
0
                continue;
840
0
            } else if (containedDir == embeddingDir) {
841
0
                analysis[pair.first].bidiDirection = embeddingDir;
842
0
                analysis[pair.second].bidiDirection = embeddingDir;
843
0
                BIDI_DEBUG() << "    1: resolve bracket pair" << i << "to" << embeddingDir;
844
0
            } else {
845
                // case c.
846
0
                while (it.pos < pair.first) {
847
0
                    int pos = *it;
848
0
                    switch (analysis[pos].bidiDirection) {
849
0
                    case QChar::DirR:
850
0
                    case QChar::DirEN:
851
0
                    case QChar::DirAN:
852
0
                        lastStrong = QChar::DirR;
853
0
                        break;
854
0
                    case QChar::DirL:
855
0
                        lastStrong = QChar::DirL;
856
0
                        break;
857
0
                    default:
858
0
                        break;
859
0
                    }
860
0
                    ++it;
861
0
                }
862
0
                analysis[pair.first].bidiDirection = lastStrong;
863
0
                analysis[pair.second].bidiDirection = lastStrong;
864
0
                BIDI_DEBUG() << "    2: resolve bracket pair" << i << "to" << lastStrong;
865
0
            }
866
0
            for (int i = pair.second + 1; i < length; ++i) {
867
0
                if (text[i].direction() == QChar::DirNSM)
868
0
                    analysis[i].bidiDirection = analysis[pair.second].bidiDirection;
869
0
                else
870
0
                    break;
871
0
            }
872
0
        }
873
0
    }
874
875
    void resolveN1N2(const Vector<DirectionalRun> &runs, int i, QChar::Direction sos, QChar::Direction eos)
876
0
    {
877
        // Rule N1 & N2
878
0
        QChar::Direction lastStrong = sos;
879
0
        IsolatedRunSequenceIterator::Position niPos;
880
0
        IsolatedRunSequenceIterator it(runs, i);
881
//            QChar::Direction last = QChar::DirON;
882
0
        while (1) {
883
0
            int pos = *it;
884
885
0
            QChar::Direction current = pos >= 0 ? analysis[pos].bidiDirection : eos;
886
0
            QChar::Direction currentStrong = current;
887
0
            switch (current) {
888
0
            case QChar::DirEN:
889
0
            case QChar::DirAN:
890
0
                currentStrong = QChar::DirR;
891
0
                Q_FALLTHROUGH();
892
0
            case QChar::DirL:
893
0
            case QChar::DirR:
894
0
                if (niPos.isValid()) {
895
0
                    QChar::Direction dir = currentStrong;
896
0
                    if (lastStrong != currentStrong)
897
0
                        dir = (runs.at(i).level) & 1 ? QChar::DirR : QChar::DirL;
898
0
                    it.setPosition(niPos);
899
0
                    while (*it != pos) {
900
0
                        if (analysis[*it].bidiDirection != QChar::DirBN)
901
0
                            analysis[*it].bidiDirection = dir;
902
0
                        ++it;
903
0
                    }
904
0
                    niPos.clear();
905
0
                }
906
0
                lastStrong = currentStrong;
907
0
                break;
908
909
0
            case QChar::DirBN:
910
0
            case QChar::DirS:
911
0
            case QChar::DirWS:
912
0
            case QChar::DirON:
913
0
            case QChar::DirFSI:
914
0
            case QChar::DirLRI:
915
0
            case QChar::DirRLI:
916
0
            case QChar::DirPDI:
917
0
            case QChar::DirB:
918
0
                if (!niPos.isValid())
919
0
                    niPos = it.position();
920
0
                break;
921
922
0
            default:
923
0
                Q_UNREACHABLE();
924
0
            }
925
0
            if (it.atEnd())
926
0
                break;
927
//                last = current;
928
0
            ++it;
929
0
        }
930
0
    }
931
932
    void resolveImplicitLevelsForIsolatedRun(const Vector<DirectionalRun> &runs, int i)
933
0
    {
934
        // Rule X10
935
0
        int level = runs.at(i).level;
936
0
        int before = i - 1;
937
0
        while (before >= 0 && !runs.at(before).hasContent)
938
0
            --before;
939
0
        int level_before = (before >= 0) ? runs.at(before).level : baseLevel;
940
0
        int after = i;
941
0
        while (runs.at(after).continuation >= 0)
942
0
            after = runs.at(after).continuation;
943
0
        if (runs.at(after).continuation == -2) {
944
0
            after = runs.size();
945
0
        } else {
946
0
            ++after;
947
0
            while (after < runs.size() && !runs.at(after).hasContent)
948
0
                ++after;
949
0
        }
950
0
        int level_after = (after == runs.size()) ? baseLevel : runs.at(after).level;
951
0
        QChar::Direction sos = (qMax(level_before, level) & 1) ? QChar::DirR : QChar::DirL;
952
0
        QChar::Direction eos = (qMax(level_after, level) & 1) ? QChar::DirR : QChar::DirL;
953
954
0
        if (BidiDebugEnabled) {
955
0
            BIDI_DEBUG() << "Isolated run starting at" << i << "sos/eos" << sos << eos;
956
0
            BIDI_DEBUG() << "before implicit level processing:";
957
0
            IsolatedRunSequenceIterator it(runs, i);
958
0
            while (!it.atEnd()) {
959
0
                BIDI_DEBUG() << "    " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection;
960
0
                ++it;
961
0
            }
962
0
        }
963
964
0
        resolveW1W2W3(runs, i, sos);
965
0
        resolveW4(runs, i, sos);
966
0
        resolveW5(runs, i);
967
968
0
        if (BidiDebugEnabled) {
969
0
            BIDI_DEBUG() << "after W4/W5";
970
0
            IsolatedRunSequenceIterator it(runs, i);
971
0
            while (!it.atEnd()) {
972
0
                BIDI_DEBUG() << "    " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection;
973
0
                ++it;
974
0
            }
975
0
        }
976
977
0
        resolveW6W7(runs, i, sos);
978
979
        // Resolve neutral types
980
981
        // Rule N0
982
0
        resolveN0(runs, i, sos);
983
0
        resolveN1N2(runs, i, sos, eos);
984
985
0
        BIDI_DEBUG() << "setting levels (run at" << level << ")";
986
        // Rules I1 & I2: set correct levels
987
0
        {
988
0
            ushort level = runs.at(i).level;
989
0
            IsolatedRunSequenceIterator it(runs, i);
990
0
            while (!it.atEnd()) {
991
0
                int pos = *it;
992
993
0
                QChar::Direction current = analysis[pos].bidiDirection;
994
0
                switch (current) {
995
0
                case QChar::DirBN:
996
0
                    break;
997
0
                case QChar::DirL:
998
0
                    analysis[pos].bidiLevel = (level + 1) & ~1;
999
0
                    break;
1000
0
                case QChar::DirR:
1001
0
                    analysis[pos].bidiLevel = level | 1;
1002
0
                    break;
1003
0
                case QChar::DirAN:
1004
0
                case QChar::DirEN:
1005
0
                    analysis[pos].bidiLevel = (level + 2) & ~1;
1006
0
                    break;
1007
0
                default:
1008
0
                    Q_UNREACHABLE();
1009
0
                }
1010
0
                BIDI_DEBUG() << "    " << pos << current << analysis[pos].bidiLevel;
1011
0
                ++it;
1012
0
            }
1013
0
        }
1014
0
    }
1015
1016
    void resolveImplicitLevels(const Vector<DirectionalRun> &runs)
1017
0
    {
1018
0
        for (int i = 0; i < runs.size(); ++i) {
1019
0
            if (runs.at(i).isContinuation)
1020
0
                continue;
1021
1022
0
            resolveImplicitLevelsForIsolatedRun(runs, i);
1023
0
        }
1024
0
    }
1025
1026
    bool checkForBidi() const
1027
0
    {
1028
0
        if (baseLevel != 0)
1029
0
            return true;
1030
0
        for (int i = 0; i < length; ++i) {
1031
0
            if (text[i].unicode() >= 0x590) {
1032
0
                switch (text[i].direction()) {
1033
0
                case QChar::DirR: case QChar::DirAN:
1034
0
                case QChar::DirLRE: case QChar::DirLRO: case QChar::DirAL:
1035
0
                case QChar::DirRLE: case QChar::DirRLO: case QChar::DirPDF:
1036
0
                case QChar::DirLRI: case QChar::DirRLI: case QChar::DirFSI: case QChar::DirPDI:
1037
0
                    return true;
1038
0
                default:
1039
0
                    break;
1040
0
                }
1041
0
            }
1042
0
        }
1043
0
        return false;
1044
0
    }
1045
1046
    bool process()
1047
0
    {
1048
0
        memset(analysis, 0, length * sizeof(QScriptAnalysis));
1049
1050
0
        bool hasBidi = checkForBidi();
1051
1052
0
        if (!hasBidi)
1053
0
            return false;
1054
1055
0
        if (BidiDebugEnabled) {
1056
0
            BIDI_DEBUG() << ">>>> start bidi, text length" << length;
1057
0
            for (int i = 0; i < length; ++i)
1058
0
                BIDI_DEBUG() << Qt::hex << "    (" << i << ")" << text[i].unicode() << text[i].direction();
1059
0
        }
1060
1061
0
        {
1062
0
            Vector<DirectionalRun> runs;
1063
0
            resolveExplicitLevels(runs);
1064
1065
0
            if (BidiDebugEnabled) {
1066
0
                BIDI_DEBUG() << "resolved explicit levels, nruns" << runs.size();
1067
0
                for (int i = 0; i < runs.size(); ++i)
1068
0
                    BIDI_DEBUG() << "    " << i << "start/end" << runs.at(i).start << runs.at(i).end << "level" << (int)runs.at(i).level << "continuation" << runs.at(i).continuation;
1069
0
            }
1070
1071
            // now we have a list of isolated run sequences inside the vector of runs, that can be fed
1072
            // through the implicit level resolving
1073
1074
0
            resolveImplicitLevels(runs);
1075
0
        }
1076
1077
0
        BIDI_DEBUG() << "Rule L1:";
1078
        // Rule L1:
1079
0
        bool resetLevel = true;
1080
0
        for (int i = length - 1; i >= 0; --i) {
1081
0
            if (analysis[i].bidiFlags & QScriptAnalysis::BidiResetToParagraphLevel) {
1082
0
                BIDI_DEBUG() << "resetting pos" << i << "to baselevel";
1083
0
                analysis[i].bidiLevel = baseLevel;
1084
0
                resetLevel = true;
1085
0
            } else if (resetLevel && analysis[i].bidiFlags & QScriptAnalysis::BidiMaybeResetToParagraphLevel) {
1086
0
                BIDI_DEBUG() << "resetting pos" << i << "to baselevel (maybereset flag)";
1087
0
                analysis[i].bidiLevel = baseLevel;
1088
0
            } else {
1089
0
                resetLevel = false;
1090
0
            }
1091
0
        }
1092
1093
        // set directions for BN to the minimum of adjacent chars
1094
        // This makes is possible to be conformant with the Bidi algorithm even though we don't
1095
        // remove BN and explicit embedding chars from the stream of characters to reorder
1096
0
        int lastLevel = baseLevel;
1097
0
        int lastBNPos = -1;
1098
0
        for (int i = 0; i < length; ++i) {
1099
0
            if (analysis[i].bidiFlags & QScriptAnalysis::BidiBN) {
1100
0
                if (lastBNPos < 0)
1101
0
                    lastBNPos = i;
1102
0
                analysis[i].bidiLevel = lastLevel;
1103
0
            } else {
1104
0
                int l = analysis[i].bidiLevel;
1105
0
                if (lastBNPos >= 0) {
1106
0
                    if (l < lastLevel) {
1107
0
                        while (lastBNPos < i) {
1108
0
                            analysis[lastBNPos].bidiLevel = l;
1109
0
                            ++lastBNPos;
1110
0
                        }
1111
0
                    }
1112
0
                    lastBNPos = -1;
1113
0
                }
1114
0
                lastLevel = l;
1115
0
            }
1116
0
        }
1117
0
        if (lastBNPos >= 0 && baseLevel < lastLevel) {
1118
0
            while (lastBNPos < length) {
1119
0
                analysis[lastBNPos].bidiLevel = baseLevel;
1120
0
                ++lastBNPos;
1121
0
            }
1122
0
        }
1123
1124
0
        if (BidiDebugEnabled) {
1125
0
            BIDI_DEBUG() << "final resolved levels:";
1126
0
            for (int i = 0; i < length; ++i)
1127
0
                BIDI_DEBUG() << "    " << i << Qt::hex << text[i].unicode() << Qt::dec << (int)analysis[i].bidiLevel;
1128
0
        }
1129
1130
0
        return true;
1131
0
    }
1132
1133
1134
    const QChar *text;
1135
    QScriptAnalysis *analysis;
1136
    int length;
1137
    char baseLevel;
1138
};
1139
1140
} // namespace
1141
1142
void QTextEngine::bidiReorder(int numItems, const quint8 *levels, int *visualOrder)
1143
0
{
1144
1145
    // first find highest and lowest levels
1146
0
    quint8 levelLow = 128;
1147
0
    quint8 levelHigh = 0;
1148
0
    int i = 0;
1149
0
    while (i < numItems) {
1150
        //printf("level = %d\n", r->level);
1151
0
        if (levels[i] > levelHigh)
1152
0
            levelHigh = levels[i];
1153
0
        if (levels[i] < levelLow)
1154
0
            levelLow = levels[i];
1155
0
        i++;
1156
0
    }
1157
1158
    // implements reordering of the line (L2 according to BiDi spec):
1159
    // L2. From the highest level found in the text to the lowest odd level on each line,
1160
    // reverse any contiguous sequence of characters that are at that level or higher.
1161
1162
    // reversing is only done up to the lowest odd level
1163
0
    if (!(levelLow%2)) levelLow++;
1164
1165
0
    BIDI_DEBUG() << "reorderLine: lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh;
1166
1167
0
    int count = numItems - 1;
1168
0
    for (i = 0; i < numItems; i++)
1169
0
        visualOrder[i] = i;
1170
1171
0
    while(levelHigh >= levelLow) {
1172
0
        int i = 0;
1173
0
        while (i < count) {
1174
0
            while(i < count && levels[i] < levelHigh) i++;
1175
0
            int start = i;
1176
0
            while(i <= count && levels[i] >= levelHigh) i++;
1177
0
            int end = i-1;
1178
1179
0
            if (start != end) {
1180
                //qDebug() << "reversing from " << start << " to " << end;
1181
0
                for(int j = 0; j < (end-start+1)/2; j++) {
1182
0
                    int tmp = visualOrder[start+j];
1183
0
                    visualOrder[start+j] = visualOrder[end-j];
1184
0
                    visualOrder[end-j] = tmp;
1185
0
                }
1186
0
            }
1187
0
            i++;
1188
0
        }
1189
0
        levelHigh--;
1190
0
    }
1191
1192
//     BIDI_DEBUG("visual order is:");
1193
//     for (i = 0; i < numItems; i++)
1194
//         BIDI_DEBUG() << visualOrder[i];
1195
0
}
1196
1197
1198
enum JustificationClass {
1199
    Justification_Prohibited      = 0,   // Justification can not be applied after this glyph
1200
    Justification_Arabic_Space    = 1,   // This glyph represents a space inside arabic text
1201
    Justification_Character       = 2,   // Inter-character justification point follows this glyph
1202
    Justification_Space           = 4,   // This glyph represents a blank outside an Arabic run
1203
    Justification_Arabic_Normal   = 7,   // Normal Middle-Of-Word glyph that connects to the right (begin)
1204
    Justification_Arabic_Waw      = 8,   // Next character is final form of Waw/Ain/Qaf/Feh
1205
    Justification_Arabic_BaRa     = 9,   // Next two characters are Ba + Ra/Ya/AlefMaksura
1206
    Justification_Arabic_Alef     = 10,  // Next character is final form of Alef/Tah/Lam/Kaf/Gaf
1207
    Justification_Arabic_HahDal   = 11,  // Next character is final form of Hah/Dal/Teh Marbuta
1208
    Justification_Arabic_Seen     = 12,  // Initial or medial form of Seen/Sad
1209
    Justification_Arabic_Kashida  = 13   // User-inserted Kashida(U+0640)
1210
};
1211
1212
#if QT_CONFIG(harfbuzz)
1213
1214
/*
1215
    Adds an inter character justification opportunity after the number or letter
1216
    character and a space justification opportunity after the space character.
1217
*/
1218
static inline void qt_getDefaultJustificationOpportunities(const ushort *string, qsizetype length, const QGlyphLayout &g, ushort *log_clusters, int spaceAs)
1219
0
{
1220
0
    qsizetype str_pos = 0;
1221
0
    while (str_pos < length) {
1222
0
        int glyph_pos = log_clusters[str_pos];
1223
1224
0
        Q_ASSERT(glyph_pos < g.numGlyphs && g.attributes[glyph_pos].clusterStart);
1225
1226
0
        uint ucs4 = string[str_pos];
1227
0
        if (QChar::isHighSurrogate(ucs4) && str_pos + 1 < length) {
1228
0
            ushort low = string[str_pos + 1];
1229
0
            if (QChar::isLowSurrogate(low)) {
1230
0
                ++str_pos;
1231
0
                ucs4 = QChar::surrogateToUcs4(ucs4, low);
1232
0
            }
1233
0
        }
1234
1235
        // skip whole cluster
1236
0
        do {
1237
0
            ++str_pos;
1238
0
        } while (str_pos < length && log_clusters[str_pos] == glyph_pos);
1239
0
        do {
1240
0
            ++glyph_pos;
1241
0
        } while (glyph_pos < g.numGlyphs && !g.attributes[glyph_pos].clusterStart);
1242
0
        --glyph_pos;
1243
1244
        // justification opportunity at the end of cluster
1245
0
        if (Q_LIKELY(QChar::isLetterOrNumber(ucs4)))
1246
0
            g.attributes[glyph_pos].justification = Justification_Character;
1247
0
        else if (Q_LIKELY(QChar::isSpace(ucs4)))
1248
0
            g.attributes[glyph_pos].justification = spaceAs;
1249
0
    }
1250
0
}
1251
1252
static inline void qt_getJustificationOpportunities(const ushort *string, qsizetype length, const QScriptItem &si, const QGlyphLayout &g, ushort *log_clusters)
1253
0
{
1254
0
    Q_ASSERT(length > 0 && g.numGlyphs > 0);
1255
1256
0
    for (int glyph_pos = 0; glyph_pos < g.numGlyphs; ++glyph_pos)
1257
0
        g.attributes[glyph_pos].justification = Justification_Prohibited;
1258
1259
0
    int spaceAs;
1260
1261
0
    switch (si.analysis.script) {
1262
0
    case QChar::Script_Arabic:
1263
0
    case QChar::Script_Syriac:
1264
0
    case QChar::Script_Nko:
1265
0
    case QChar::Script_Mandaic:
1266
0
    case QChar::Script_Mongolian:
1267
0
    case QChar::Script_PhagsPa:
1268
0
    case QChar::Script_Manichaean:
1269
0
    case QChar::Script_PsalterPahlavi:
1270
        // same as default but inter character justification takes precedence
1271
0
        spaceAs = Justification_Arabic_Space;
1272
0
        break;
1273
1274
0
    case QChar::Script_Tibetan:
1275
0
    case QChar::Script_Hiragana:
1276
0
    case QChar::Script_Katakana:
1277
0
    case QChar::Script_Bopomofo:
1278
0
    case QChar::Script_Han:
1279
        // same as default but inter character justification is the only option
1280
0
        spaceAs = Justification_Character;
1281
0
        break;
1282
1283
0
    default:
1284
0
        spaceAs = Justification_Space;
1285
0
        break;
1286
0
    }
1287
1288
0
    qt_getDefaultJustificationOpportunities(string, length, g, log_clusters, spaceAs);
1289
0
}
1290
1291
#endif // harfbuzz
1292
1293
1294
// shape all the items that intersect with the line, taking tab widths into account to find out what text actually fits in the line.
1295
void QTextEngine::shapeLine(const QScriptLine &line)
1296
0
{
1297
0
    QFixed x;
1298
0
    bool first = true;
1299
0
    int item = findItem(line.from);
1300
0
    if (item == -1)
1301
0
        return;
1302
1303
0
    const int end = findItem(line.from + line.length + line.trailingSpaces - 1, item);
1304
0
    for ( ; item <= end; ++item) {
1305
0
        QScriptItem &si = layoutData->items[item];
1306
0
        if (si.analysis.flags == QScriptAnalysis::Tab) {
1307
0
            ensureSpace(1);
1308
0
            si.width = calculateTabWidth(item, x);
1309
0
        } else {
1310
0
            shape(item);
1311
0
        }
1312
0
        if (first && si.position != line.from) { // that means our x position has to be offset
1313
0
            QGlyphLayout glyphs = shapedGlyphs(&si);
1314
0
            Q_ASSERT(line.from > si.position);
1315
0
            for (int i = line.from - si.position - 1; i >= 0; i--) {
1316
0
                x -= glyphs.effectiveAdvance(i);
1317
0
            }
1318
0
        }
1319
0
        first = false;
1320
1321
0
        x += si.width;
1322
0
    }
1323
0
}
1324
1325
static void applyVisibilityRules(ushort ucs, QGlyphLayout *glyphs, uint glyphPosition, QFontEngine *fontEngine)
1326
0
{
1327
    // hide characters that should normally be invisible
1328
0
    switch (ucs) {
1329
0
    case QChar::LineFeed:
1330
0
    case 0x000c: // FormFeed
1331
0
    case QChar::CarriageReturn:
1332
0
    case QChar::LineSeparator:
1333
0
    case QChar::ParagraphSeparator:
1334
0
        glyphs->attributes[glyphPosition].dontPrint = true;
1335
0
        break;
1336
0
    case QChar::SoftHyphen:
1337
0
        if (!fontEngine->symbol) {
1338
            // U+00AD [SOFT HYPHEN] is a default ignorable codepoint,
1339
            // so we replace its glyph and metrics with ones for
1340
            // U+002D [HYPHEN-MINUS] or U+2010 [HYPHEN] and make
1341
            // it visible if it appears at line-break
1342
0
            const uint engineIndex = glyphs->glyphs[glyphPosition] & 0xff000000;
1343
0
            glyph_t glyph = fontEngine->glyphIndex(0x002d);
1344
0
            if (glyph == 0)
1345
0
                glyph = fontEngine->glyphIndex(0x2010);
1346
0
            if (glyph == 0)
1347
0
                glyph = fontEngine->glyphIndex(0x00ad);
1348
0
            glyphs->glyphs[glyphPosition] = glyph;
1349
0
            if (Q_LIKELY(glyphs->glyphs[glyphPosition] != 0)) {
1350
0
                glyphs->glyphs[glyphPosition] |= engineIndex;
1351
0
                QGlyphLayout tmp = glyphs->mid(glyphPosition, 1);
1352
0
                fontEngine->recalcAdvances(&tmp, { });
1353
0
            }
1354
0
            glyphs->attributes[glyphPosition].dontPrint = true;
1355
0
        }
1356
0
        break;
1357
0
    default:
1358
0
        break;
1359
0
    }
1360
0
}
1361
1362
void QTextEngine::shapeText(int item) const
1363
0
{
1364
0
    Q_ASSERT(item < layoutData->items.size());
1365
0
    QScriptItem &si = layoutData->items[item];
1366
1367
0
    if (si.num_glyphs)
1368
0
        return;
1369
1370
0
    si.width = 0;
1371
0
    si.glyph_data_offset = layoutData->used;
1372
1373
0
    const ushort *string = reinterpret_cast<const ushort *>(layoutData->string.constData()) + si.position;
1374
0
    const ushort *baseString = reinterpret_cast<const ushort *>(layoutData->string.constData());
1375
0
    int baseStringStart = si.position;
1376
0
    int baseStringLength = layoutData->string.length();
1377
0
    const int itemLength = length(item);
1378
1379
0
    QString casedString;
1380
0
    if (si.analysis.flags && si.analysis.flags <= QScriptAnalysis::SmallCaps) {
1381
0
        casedString.resize(itemLength);
1382
0
        ushort *uc = reinterpret_cast<ushort *>(casedString.data());
1383
0
        for (int i = 0; i < itemLength; ++i) {
1384
0
            uint ucs4 = string[i];
1385
0
            if (QChar::isHighSurrogate(ucs4) && i + 1 < itemLength) {
1386
0
                uint low = string[i + 1];
1387
0
                if (QChar::isLowSurrogate(low)) {
1388
                    // high part never changes in simple casing
1389
0
                    uc[i] = ucs4;
1390
0
                    ++i;
1391
0
                    ucs4 = QChar::surrogateToUcs4(ucs4, low);
1392
0
                    ucs4 = si.analysis.flags == QScriptAnalysis::Lowercase ? QChar::toLower(ucs4)
1393
0
                                                                           : QChar::toUpper(ucs4);
1394
0
                    uc[i] = QChar::lowSurrogate(ucs4);
1395
0
                }
1396
0
            } else {
1397
0
                uc[i] = si.analysis.flags == QScriptAnalysis::Lowercase ? QChar::toLower(ucs4)
1398
0
                                                                        : QChar::toUpper(ucs4);
1399
0
            }
1400
0
        }
1401
0
        string = reinterpret_cast<const ushort *>(casedString.constData());
1402
0
        baseString = string;
1403
0
        baseStringStart = 0;
1404
0
        baseStringLength = casedString.length();
1405
0
    }
1406
1407
0
    if (Q_UNLIKELY(!ensureSpace(itemLength))) {
1408
0
        Q_UNREACHABLE_RETURN(); // ### report OOM error somehow
1409
0
    }
1410
1411
0
    QFontEngine *fontEngine = this->fontEngine(si, &si.ascent, &si.descent, &si.leading);
1412
1413
0
#if QT_CONFIG(harfbuzz)
1414
0
    bool kerningEnabled;
1415
0
#endif
1416
0
    bool letterSpacingIsAbsolute;
1417
0
    bool shapingEnabled = false;
1418
0
    QMap<QFont::Tag, quint32> features;
1419
0
    QFixed letterSpacing, wordSpacing;
1420
0
#ifndef QT_NO_RAWFONT
1421
0
    if (useRawFont) {
1422
0
        QTextCharFormat f = format(&si);
1423
0
        QFont font = f.font();
1424
0
#  if QT_CONFIG(harfbuzz)
1425
0
        kerningEnabled = font.kerning();
1426
0
        shapingEnabled = (si.analysis.script < QChar::ScriptCount && QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)))
1427
0
                         || (font.styleStrategy() & QFont::PreferNoShaping) == 0;
1428
0
#  endif
1429
0
        wordSpacing = QFixed::fromReal(font.wordSpacing());
1430
0
        letterSpacing = QFixed::fromReal(font.letterSpacing());
1431
0
        letterSpacingIsAbsolute = true;
1432
0
        features = font.d->features;
1433
0
    } else
1434
0
#endif
1435
0
    {
1436
0
        QFont font = this->font(si);
1437
0
#if QT_CONFIG(harfbuzz)
1438
0
        kerningEnabled = font.d->kerning;
1439
0
        shapingEnabled = (si.analysis.script < QChar::ScriptCount && QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script)))
1440
0
                         || (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0;
1441
0
#endif
1442
0
        letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute;
1443
0
        letterSpacing = font.d->letterSpacing;
1444
0
        wordSpacing = font.d->wordSpacing;
1445
0
        features = font.d->features;
1446
1447
0
        if (letterSpacingIsAbsolute && letterSpacing.value())
1448
0
            letterSpacing *= font.d->dpi / qt_defaultDpiY();
1449
0
    }
1450
1451
    // split up the item into parts that come from different font engines
1452
    // k * 3 entries, array[k] == index in string, array[k + 1] == index in glyphs, array[k + 2] == engine index
1453
0
    QVarLengthArray<uint, 24> itemBoundaries;
1454
1455
0
    QGlyphLayout initialGlyphs = availableGlyphs(&si);
1456
0
    int nGlyphs = initialGlyphs.numGlyphs;
1457
0
    if (fontEngine->type() == QFontEngine::Multi || !shapingEnabled) {
1458
        // ask the font engine to find out which glyphs (as an index in the specific font)
1459
        // to use for the text in one item.
1460
0
        QFontEngine::ShaperFlags shaperFlags =
1461
0
                shapingEnabled
1462
0
                    ? QFontEngine::GlyphIndicesOnly
1463
0
                    : QFontEngine::ShaperFlag(0);
1464
0
        if (fontEngine->stringToCMap(reinterpret_cast<const QChar *>(string), itemLength, &initialGlyphs, &nGlyphs, shaperFlags) < 0)
1465
0
            Q_UNREACHABLE();
1466
0
    }
1467
1468
0
    if (fontEngine->type() == QFontEngine::Multi) {
1469
0
        uint lastEngine = ~0u;
1470
0
        for (int i = 0, glyph_pos = 0; i < itemLength; ++i, ++glyph_pos) {
1471
0
            const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24;
1472
0
            if (lastEngine != engineIdx) {
1473
0
                itemBoundaries.push_back(i);
1474
0
                itemBoundaries.push_back(glyph_pos);
1475
0
                itemBoundaries.push_back(engineIdx);
1476
1477
0
                if (engineIdx != 0) {
1478
0
                    QFontEngine *actualFontEngine = static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1479
0
                    si.ascent = qMax(actualFontEngine->ascent(), si.ascent);
1480
0
                    si.descent = qMax(actualFontEngine->descent(), si.descent);
1481
0
                    si.leading = qMax(actualFontEngine->leading(), si.leading);
1482
0
                }
1483
1484
0
                lastEngine = engineIdx;
1485
0
            }
1486
1487
0
            if (QChar::isHighSurrogate(string[i]) && i + 1 < itemLength && QChar::isLowSurrogate(string[i + 1]))
1488
0
                ++i;
1489
0
        }
1490
0
    } else {
1491
0
        itemBoundaries.push_back(0);
1492
0
        itemBoundaries.push_back(0);
1493
0
        itemBoundaries.push_back(0);
1494
0
    }
1495
1496
0
#if QT_CONFIG(harfbuzz)
1497
0
    if (Q_LIKELY(shapingEnabled)) {
1498
0
        si.num_glyphs = shapeTextWithHarfbuzzNG(si, baseString, baseStringStart, baseStringLength,
1499
0
                                                itemLength, fontEngine, itemBoundaries,
1500
0
                                                kerningEnabled, letterSpacing != 0, features);
1501
0
    } else
1502
0
#endif
1503
0
    {
1504
0
        ushort *log_clusters = logClusters(&si);
1505
1506
0
        int glyph_pos = 0;
1507
0
        for (int i = 0; i < itemLength; ++i, ++glyph_pos) {
1508
0
            log_clusters[i] = glyph_pos;
1509
0
            initialGlyphs.attributes[glyph_pos].clusterStart = true;
1510
1511
0
            bool is_print_char;
1512
0
            if (QChar::isHighSurrogate(string[i])
1513
0
                    && i + 1 < itemLength
1514
0
                    && QChar::isLowSurrogate(string[i + 1])) {
1515
0
                is_print_char = QChar::isPrint(QChar::surrogateToUcs4(string[i], string[i + 1]));
1516
0
                ++i;
1517
0
                log_clusters[i] = glyph_pos;
1518
1519
0
            } else {
1520
0
                is_print_char = QChar::isPrint(string[i]);
1521
0
            }
1522
0
            initialGlyphs.attributes[glyph_pos].dontPrint =
1523
0
                    !is_print_char && !(option.flags() & QTextOption::ShowDefaultIgnorables);
1524
1525
0
            if (Q_UNLIKELY(!initialGlyphs.attributes[glyph_pos].dontPrint)) {
1526
0
                QFontEngine *actualFontEngine = fontEngine;
1527
0
                if (actualFontEngine->type() == QFontEngine::Multi) {
1528
0
                    const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24;
1529
0
                    actualFontEngine = static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1530
0
                }
1531
1532
0
                applyVisibilityRules(string[i], &initialGlyphs, glyph_pos, actualFontEngine);
1533
0
            }
1534
0
        }
1535
1536
0
        si.num_glyphs = glyph_pos;
1537
0
    }
1538
1539
0
    if (Q_UNLIKELY(si.num_glyphs == 0)) {
1540
0
        if (Q_UNLIKELY(!ensureSpace(si.glyph_data_offset + 1))) {
1541
0
            qWarning() << "Unable to allocate space for place-holder glyph";
1542
0
            return;
1543
0
        }
1544
1545
0
        si.num_glyphs = 1;
1546
1547
        // Overwrite with 0 token to indicate failure
1548
0
        QGlyphLayout g = availableGlyphs(&si);
1549
0
        g.glyphs[0] = 0;
1550
0
        g.attributes[0].clusterStart = true;
1551
1552
0
        ushort *log_clusters = logClusters(&si);
1553
0
        for (int i = 0; i < itemLength; ++i)
1554
0
            log_clusters[i] = 0;
1555
1556
0
        return;
1557
0
    }
1558
1559
0
    layoutData->used += si.num_glyphs;
1560
1561
0
    QGlyphLayout glyphs = shapedGlyphs(&si);
1562
1563
0
#if QT_CONFIG(harfbuzz)
1564
0
    qt_getJustificationOpportunities(string, itemLength, si, glyphs, logClusters(&si));
1565
0
#endif
1566
1567
0
    if (letterSpacing != 0) {
1568
0
        for (int i = 1; i < si.num_glyphs; ++i) {
1569
0
            if (glyphs.attributes[i].clusterStart) {
1570
0
                if (letterSpacingIsAbsolute)
1571
0
                    glyphs.advances[i - 1] += letterSpacing;
1572
0
                else {
1573
0
                    QFixed &advance = glyphs.advances[i - 1];
1574
0
                    advance += (letterSpacing - 100) * advance / 100;
1575
0
                }
1576
0
            }
1577
0
        }
1578
0
        if (letterSpacingIsAbsolute)
1579
0
            glyphs.advances[si.num_glyphs - 1] += letterSpacing;
1580
0
        else {
1581
0
            QFixed &advance = glyphs.advances[si.num_glyphs - 1];
1582
0
            advance += (letterSpacing - 100) * advance / 100;
1583
0
        }
1584
0
    }
1585
0
    if (wordSpacing != 0) {
1586
0
        for (int i = 0; i < si.num_glyphs; ++i) {
1587
0
            if (glyphs.attributes[i].justification == Justification_Space
1588
0
                || glyphs.attributes[i].justification == Justification_Arabic_Space) {
1589
                // word spacing only gets added once to a consecutive run of spaces (see CSS spec)
1590
0
                if (i + 1 == si.num_glyphs
1591
0
                    ||(glyphs.attributes[i+1].justification != Justification_Space
1592
0
                       && glyphs.attributes[i+1].justification != Justification_Arabic_Space))
1593
0
                    glyphs.advances[i] += wordSpacing;
1594
0
            }
1595
0
        }
1596
0
    }
1597
1598
0
    for (int i = 0; i < si.num_glyphs; ++i)
1599
0
        si.width += glyphs.advances[i] * !glyphs.attributes[i].dontPrint;
1600
0
}
1601
1602
#if QT_CONFIG(harfbuzz)
1603
1604
QT_BEGIN_INCLUDE_NAMESPACE
1605
1606
#include "qharfbuzzng_p.h"
1607
1608
QT_END_INCLUDE_NAMESPACE
1609
1610
int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *string,
1611
                                         int stringBaseIndex, int stringLength, int itemLength,
1612
                                         QFontEngine *fontEngine, QSpan<uint> itemBoundaries,
1613
                                         bool kerningEnabled, bool hasLetterSpacing,
1614
                                         const QMap<QFont::Tag, quint32> &fontFeatures) const
1615
0
{
1616
0
    uint glyphs_shaped = 0;
1617
1618
0
    if (!buffer) {
1619
0
        buffer = hb_buffer_create();
1620
0
        hb_buffer_set_unicode_funcs(buffer, hb_qt_get_unicode_funcs());
1621
0
    }
1622
1623
0
    hb_buffer_pre_allocate(buffer, itemLength);
1624
0
    if (Q_UNLIKELY(!hb_buffer_allocation_successful(buffer))) {
1625
0
        hb_buffer_destroy(buffer);
1626
0
        buffer = nullptr;
1627
0
        return 0;
1628
0
    }
1629
1630
0
    hb_segment_properties_t props = HB_SEGMENT_PROPERTIES_DEFAULT;
1631
0
    props.direction = si.analysis.bidiLevel % 2 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
1632
0
    QChar::Script script = si.analysis.script < QChar::ScriptCount
1633
0
                               ? QChar::Script(si.analysis.script)
1634
0
                               : QChar::Script_Common;
1635
0
    props.script = hb_qt_script_to_script(script);
1636
1637
    // set harfbuzz language from QLocale
1638
0
    const QByteArray localeBcp47 = QLocale().bcp47Name().toUtf8();
1639
0
    const hb_language_t hb_language = hb_language_from_string(localeBcp47.constData(), localeBcp47.size());
1640
0
    if (hb_language == HB_LANGUAGE_INVALID)
1641
0
        props.language = hb_language_get_default();
1642
0
    else
1643
0
        props.language = hb_language;
1644
1645
0
    for (qsizetype k = 0; k < itemBoundaries.size(); k += 3) {
1646
0
        const uint item_pos = itemBoundaries[k];
1647
0
        const uint item_length = (k + 4 < itemBoundaries.size() ? itemBoundaries[k + 3] : itemLength) - item_pos;
1648
0
        const uint engineIdx = itemBoundaries[k + 2];
1649
1650
0
        QFontEngine *actualFontEngine = fontEngine->type() != QFontEngine::Multi ? fontEngine
1651
0
                                                                                 : static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
1652
1653
1654
        // prepare buffer
1655
0
        hb_buffer_clear_contents(buffer);
1656
1657
        // Populate the buffer using the base string pointer and length, so HarfBuzz can grab an
1658
        // enclosing context for proper shaping at item boundaries in certain languages (e.g.
1659
        // Arabic).
1660
0
        hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16_t *>(string), stringLength,
1661
0
                            stringBaseIndex + item_pos, item_length);
1662
1663
0
        hb_buffer_set_segment_properties(buffer, &props);
1664
1665
0
        uint buffer_flags = HB_BUFFER_FLAG_DEFAULT;
1666
        // Symbol encoding used to encode various crap in the 32..255 character code range,
1667
        // and thus might override U+00AD [SHY]; avoid hiding default ignorables
1668
0
        if (Q_UNLIKELY(actualFontEngine->symbol || (option.flags() & QTextOption::ShowDefaultIgnorables)))
1669
0
            buffer_flags |= HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES;
1670
0
        hb_buffer_set_flags(buffer, hb_buffer_flags_t(buffer_flags));
1671
1672
1673
        // shape
1674
0
        {
1675
0
            hb_font_t *hb_font = hb_qt_font_get_for_engine(actualFontEngine);
1676
0
            Q_ASSERT(hb_font);
1677
0
            hb_qt_font_set_use_design_metrics(hb_font, option.useDesignMetrics() ? uint(QFontEngine::DesignMetrics) : 0); // ###
1678
1679
            // Ligatures are incompatible with custom letter spacing, so when a letter spacing is set,
1680
            // we disable them for writing systems where they are purely cosmetic.
1681
0
            bool scriptRequiresOpenType = ((script >= QChar::Script_Syriac && script <= QChar::Script_Sinhala)
1682
0
                                         || script == QChar::Script_Khmer || script == QChar::Script_Nko);
1683
1684
0
            bool dontLigate = hasLetterSpacing && !scriptRequiresOpenType;
1685
1686
0
            QVarLengthFlatMap<QFont::Tag, hb_feature_t, 16> features;
1687
0
            auto insertFeature = [&features](QFont::Tag tag, quint32 value) {
1688
0
                features.insert(tag, { tag.value(),
1689
0
                                       value,
1690
0
                                       HB_FEATURE_GLOBAL_START,
1691
0
                                       HB_FEATURE_GLOBAL_END });
1692
0
            };
1693
            // fontFeatures have precedence
1694
0
            for (const auto &[tag, value]: fontFeatures.asKeyValueRange())
1695
0
                insertFeature(tag, value);
1696
0
            insertFeature(QFont::Tag("kern"), !!kerningEnabled);
1697
0
            if (dontLigate) {
1698
0
                insertFeature(QFont::Tag("liga"), false);
1699
0
                insertFeature(QFont::Tag("clig"), false);
1700
0
                insertFeature(QFont::Tag("dlig"), false);
1701
0
                insertFeature(QFont::Tag("hlig"), false);
1702
0
            }
1703
1704
            // whitelist cross-platforms shapers only
1705
0
            constexpr const char *shaper_list[] = {
1706
0
                "graphite2",
1707
0
                "ot",
1708
0
                "fallback",
1709
0
                nullptr
1710
0
            };
1711
1712
0
            bool shapedOk = hb_shape_full(hb_font,
1713
0
                                          buffer,
1714
0
                                          features.values().constData(),
1715
0
                                          features.values().size(),
1716
0
                                          shaper_list);
1717
0
            if (Q_UNLIKELY(!shapedOk))
1718
0
                return 0;
1719
1720
0
            if (Q_UNLIKELY(HB_DIRECTION_IS_BACKWARD(props.direction)))
1721
0
                hb_buffer_reverse(buffer);
1722
0
        }
1723
1724
0
        uint num_glyphs = hb_buffer_get_length(buffer);
1725
0
        const bool has_glyphs = num_glyphs > 0;
1726
        // If Harfbuzz returns zero glyphs, we have to manually add a missing glyph
1727
0
        if (Q_UNLIKELY(!has_glyphs))
1728
0
            num_glyphs = 1;
1729
1730
        // ensure we have enough space for shaped glyphs and metrics
1731
0
        if (Q_UNLIKELY(!ensureSpace(glyphs_shaped + num_glyphs)))
1732
0
            return 0;
1733
1734
        // fetch the shaped glyphs and metrics
1735
0
        QGlyphLayout g = availableGlyphs(&si).mid(glyphs_shaped, num_glyphs);
1736
0
        ushort *log_clusters = logClusters(&si) + item_pos;
1737
0
        if (Q_LIKELY(has_glyphs)) {
1738
0
            hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(buffer, nullptr);
1739
0
            hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer, nullptr);
1740
0
            uint str_pos = 0;
1741
0
            uint last_cluster = ~0u;
1742
0
            uint last_glyph_pos = glyphs_shaped;
1743
0
            for (uint i = 0; i < num_glyphs; ++i, ++infos, ++positions) {
1744
0
                g.glyphs[i] = infos->codepoint;
1745
1746
0
                g.advances[i] = QFixed::fromFixed(positions->x_advance);
1747
0
                g.offsets[i].x = QFixed::fromFixed(positions->x_offset);
1748
0
                g.offsets[i].y = QFixed::fromFixed(positions->y_offset);
1749
1750
0
                uint cluster = infos->cluster;
1751
0
                if (Q_LIKELY(last_cluster != cluster)) {
1752
0
                    g.attributes[i].clusterStart = true;
1753
1754
                    // fix up clusters so that the cluster indices will be monotonic
1755
                    // and thus we never return out-of-order indices
1756
0
                    for (uint j = last_cluster; j < cluster && str_pos < item_length; ++j)
1757
0
                        log_clusters[str_pos++] = last_glyph_pos;
1758
0
                    last_glyph_pos = i + glyphs_shaped;
1759
0
                    last_cluster = cluster;
1760
1761
0
                    applyVisibilityRules(string[stringBaseIndex + item_pos + str_pos], &g, i, actualFontEngine);
1762
0
                }
1763
0
            }
1764
0
            while (str_pos < item_length)
1765
0
                log_clusters[str_pos++] = last_glyph_pos;
1766
0
        } else { // Harfbuzz did not return a glyph for the character, so we add a placeholder
1767
0
            g.glyphs[0] = 0;
1768
0
            g.advances[0] = QFixed{};
1769
0
            g.offsets[0].x = QFixed{};
1770
0
            g.offsets[0].y = QFixed{};
1771
0
            g.attributes[0].clusterStart = true;
1772
0
            g.attributes[0].dontPrint = true;
1773
0
            for (uint str_pos = 0; str_pos < item_length; ++str_pos)
1774
0
                log_clusters[str_pos] = glyphs_shaped;
1775
0
        }
1776
1777
0
        if (Q_UNLIKELY(engineIdx != 0)) {
1778
0
            for (quint32 i = 0; i < num_glyphs; ++i)
1779
0
                g.glyphs[i] |= (engineIdx << 24);
1780
0
        }
1781
1782
0
        if (!actualFontEngine->supportsHorizontalSubPixelPositions()) {
1783
0
            for (uint i = 0; i < num_glyphs; ++i) {
1784
0
                g.advances[i] = g.advances[i].round();
1785
0
                g.offsets[i].x = g.offsets[i].x.round();
1786
0
            }
1787
0
        }
1788
1789
0
        glyphs_shaped += num_glyphs;
1790
0
    }
1791
1792
0
    return glyphs_shaped;
1793
0
}
1794
1795
#endif // harfbuzz
1796
1797
void QTextEngine::init(QTextEngine *e)
1798
0
{
1799
0
    e->ignoreBidi = false;
1800
0
    e->cacheGlyphs = false;
1801
0
    e->forceJustification = false;
1802
0
    e->visualMovement = false;
1803
0
    e->delayDecorations = false;
1804
1805
0
    e->layoutData = nullptr;
1806
1807
0
    e->minWidth = 0;
1808
0
    e->maxWidth = 0;
1809
1810
0
    e->specialData = nullptr;
1811
0
    e->stackEngine = false;
1812
0
#ifndef QT_NO_RAWFONT
1813
0
    e->useRawFont = false;
1814
0
#endif
1815
0
}
1816
1817
QTextEngine::QTextEngine()
1818
0
{
1819
0
    init(this);
1820
0
}
1821
1822
QTextEngine::QTextEngine(const QString &str, const QFont &f)
1823
0
    : text(str),
1824
0
      fnt(f)
1825
0
{
1826
0
    init(this);
1827
0
}
1828
1829
QTextEngine::~QTextEngine()
1830
0
{
1831
0
    if (!stackEngine)
1832
0
        delete layoutData;
1833
0
    delete specialData;
1834
0
    resetFontEngineCache();
1835
0
#if QT_CONFIG(harfbuzz)
1836
0
    if (buffer) {
1837
0
        hb_buffer_destroy(buffer);
1838
0
        buffer = nullptr;
1839
0
    }
1840
0
#endif
1841
0
}
1842
1843
const QCharAttributes *QTextEngine::attributes() const
1844
0
{
1845
0
    if (layoutData && layoutData->haveCharAttributes)
1846
0
        return (QCharAttributes *) layoutData->memory;
1847
1848
0
    itemize();
1849
0
    if (! ensureSpace(layoutData->string.size()))
1850
0
        return nullptr;
1851
1852
0
    QVarLengthArray<QUnicodeTools::ScriptItem> scriptItems(layoutData->items.size());
1853
0
    for (int i = 0; i < layoutData->items.size(); ++i) {
1854
0
        const QScriptItem &si = layoutData->items.at(i);
1855
0
        scriptItems[i].position = si.position;
1856
0
        scriptItems[i].script = QChar::Script(si.analysis.script);
1857
0
    }
1858
1859
0
    QUnicodeTools::initCharAttributes(
1860
0
        layoutData->string,
1861
0
        scriptItems.data(), scriptItems.size(),
1862
0
        reinterpret_cast<QCharAttributes *>(layoutData->memory),
1863
0
        QUnicodeTools::CharAttributeOptions(QUnicodeTools::GraphemeBreaks
1864
0
                                            | QUnicodeTools::LineBreaks
1865
0
                                            | QUnicodeTools::WhiteSpaces
1866
0
                                            | QUnicodeTools::HangulLineBreakTailoring));
1867
1868
1869
0
    layoutData->haveCharAttributes = true;
1870
0
    return (QCharAttributes *) layoutData->memory;
1871
0
}
1872
1873
void QTextEngine::shape(int item) const
1874
0
{
1875
0
    auto &li = layoutData->items[item];
1876
0
    if (li.analysis.flags == QScriptAnalysis::Object) {
1877
0
        ensureSpace(1);
1878
0
        if (QTextDocumentPrivate::get(block) != nullptr) {
1879
0
            docLayout()->resizeInlineObject(QTextInlineObject(item, const_cast<QTextEngine *>(this)),
1880
0
                                            li.position + block.position(),
1881
0
                                            format(&li));
1882
0
        } else {
1883
            // Standalone QTextLayout (no QTextDocument): read the
1884
            // object size from a QTextImageFormat set via setFormats().
1885
0
            QTextCharFormat fmt = format(&li);
1886
0
            if (fmt.isImageFormat()) {
1887
0
                QTextImageFormat imgFmt = fmt.toImageFormat();
1888
0
                const qreal w = imgFmt.width();
1889
0
                const qreal h = imgFmt.height();
1890
0
                if (w > 0 && h > 0) {
1891
0
                    QTextInlineObject obj(item, const_cast<QTextEngine *>(this));
1892
0
                    obj.setWidth(w);
1893
                    // Mirror QTextDocumentLayout::resizeInlineObject()
1894
                    // vertical alignment logic.
1895
0
                    const QFontMetricsF fm(fnt);
1896
0
                    switch (fmt.verticalAlignment()) {
1897
0
                    case QTextCharFormat::AlignMiddle: {
1898
0
                        const qreal halfX = fm.xHeight() / 2.0;
1899
0
                        obj.setAscent((h + halfX) / 2.0);
1900
0
                        obj.setDescent((h - halfX) / 2.0);
1901
0
                        break;
1902
0
                    }
1903
0
                    case QTextCharFormat::AlignBaseline: {
1904
0
                        const qreal descent = fm.descent();
1905
0
                        obj.setDescent(descent);
1906
0
                        obj.setAscent(h - descent);
1907
0
                        break;
1908
0
                    }
1909
0
                    default:
1910
0
                        obj.setDescent(0);
1911
0
                        obj.setAscent(h);
1912
0
                        break;
1913
0
                    }
1914
0
                }
1915
0
            }
1916
0
        }
1917
        // fix log clusters to point to the previous glyph, as the object doesn't have a glyph of it's own.
1918
        // This is required so that all entries in the array get initialized and are ordered correctly.
1919
0
        if (layoutData->logClustersPtr) {
1920
0
            ushort *lc = logClusters(&li);
1921
0
            *lc = (lc != layoutData->logClustersPtr) ? lc[-1] : 0;
1922
0
        }
1923
0
    } else if (li.analysis.flags == QScriptAnalysis::Tab) {
1924
        // set up at least the ascent/descent/leading of the script item for the tab
1925
0
        fontEngine(li, &li.ascent, &li.descent, &li.leading);
1926
        // see the comment above
1927
0
        if (layoutData->logClustersPtr) {
1928
0
            ushort *lc = logClusters(&li);
1929
0
            *lc = (lc != layoutData->logClustersPtr) ? lc[-1] : 0;
1930
0
        }
1931
0
    } else {
1932
0
        shapeText(item);
1933
0
    }
1934
0
}
1935
1936
static inline void releaseCachedFontEngine(QFontEngine *fontEngine)
1937
0
{
1938
0
    if (fontEngine && !fontEngine->ref.deref())
1939
0
        delete fontEngine;
1940
0
}
1941
1942
void QTextEngine::resetFontEngineCache()
1943
0
{
1944
0
    releaseCachedFontEngine(feCache.prevFontEngine);
1945
0
    releaseCachedFontEngine(feCache.prevScaledFontEngine);
1946
0
    feCache.reset();
1947
0
}
1948
1949
void QTextEngine::invalidate()
1950
0
{
1951
0
    freeMemory();
1952
0
    minWidth = 0;
1953
0
    maxWidth = 0;
1954
1955
0
    resetFontEngineCache();
1956
0
}
1957
1958
void QTextEngine::clearLineData()
1959
0
{
1960
0
    lines.clear();
1961
0
}
1962
1963
void QTextEngine::validate() const
1964
0
{
1965
0
    if (layoutData)
1966
0
        return;
1967
0
    layoutData = new LayoutData();
1968
0
    if (QTextDocumentPrivate::get(block) != nullptr) {
1969
0
        layoutData->string = block.text();
1970
0
        const bool nextBlockValid = block.next().isValid();
1971
0
        if (!nextBlockValid && option.flags() & QTextOption::ShowDocumentTerminator) {
1972
0
            layoutData->string += QLatin1Char('\xA7');
1973
0
        } else if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1974
0
            layoutData->string += QLatin1Char(nextBlockValid ? '\xB6' : '\x20');
1975
0
        }
1976
1977
0
    } else {
1978
0
        layoutData->string = text;
1979
0
    }
1980
0
    if (specialData && specialData->preeditPosition != -1)
1981
0
        layoutData->string.insert(specialData->preeditPosition, specialData->preeditText);
1982
0
}
1983
1984
#if !defined(QT_NO_EMOJISEGMENTER)
1985
namespace {
1986
1987
    enum CharacterCategory {
1988
        EMOJI = 0,
1989
        EMOJI_TEXT_PRESENTATION = 1,
1990
        EMOJI_EMOJI_PRESENTATION = 2,
1991
        EMOJI_MODIFIER_BASE = 3,
1992
        EMOJI_MODIFIER = 4,
1993
        EMOJI_VS_BASE = 5,
1994
        REGIONAL_INDICATOR = 6,
1995
        KEYCAP_BASE = 7,
1996
        COMBINING_ENCLOSING_KEYCAP = 8,
1997
        COMBINING_ENCLOSING_CIRCLE_BACKSLASH = 9,
1998
        ZWJ = 10,
1999
        VS15 = 11,
2000
        VS16 = 12,
2001
        TAG_BASE = 13,
2002
        TAG_SEQUENCE = 14,
2003
        TAG_TERM = 15,
2004
        OTHER = 16
2005
    };
2006
2007
    typedef CharacterCategory *emoji_text_iter_t;
2008
2009
    #include "../../3rdparty/emoji-segmenter/emoji_presentation_scanner.c"
2010
}
2011
#endif
2012
2013
void QTextEngine::itemize() const
2014
0
{
2015
0
    validate();
2016
0
    if (layoutData->items.size())
2017
0
        return;
2018
2019
0
    int length = layoutData->string.size();
2020
0
    if (!length)
2021
0
        return;
2022
2023
0
    const ushort *string = reinterpret_cast<const ushort *>(layoutData->string.unicode());
2024
2025
0
    bool rtl = isRightToLeft();
2026
2027
0
    QVarLengthArray<QScriptAnalysis, 4096> scriptAnalysis(length);
2028
0
    QScriptAnalysis *analysis = scriptAnalysis.data();
2029
2030
0
    QBidiAlgorithm bidi(layoutData->string.constData(), analysis, length, rtl);
2031
0
    layoutData->hasBidi = bidi.process();
2032
2033
0
    {
2034
0
        QUnicodeTools::ScriptItemArray scriptItems;
2035
0
        QUnicodeTools::initScripts(layoutData->string, &scriptItems);
2036
0
        for (int i = 0; i < scriptItems.size(); ++i) {
2037
0
            const auto &item = scriptItems.at(i);
2038
0
            int end = i < scriptItems.size() - 1 ? scriptItems.at(i + 1).position : length;
2039
0
            for (int j = item.position; j < end; ++j)
2040
0
                analysis[j].script = item.script;
2041
0
        }
2042
0
    }
2043
2044
0
#if !defined(QT_NO_EMOJISEGMENTER)
2045
0
    const bool disableEmojiSegmenter = QFontEngine::disableEmojiSegmenter() || option.flags().testFlag(QTextOption::DisableEmojiParsing);
2046
2047
0
    qCDebug(lcEmojiSegmenter) << "Emoji segmenter disabled:" << disableEmojiSegmenter;
2048
2049
0
    QVarLengthArray<CharacterCategory> categorizedString;
2050
0
    if (!disableEmojiSegmenter) {
2051
        // Parse emoji sequences
2052
0
        for (int i = 0; i < length; ++i) {
2053
0
            const QChar &c = string[i];
2054
0
            const bool isSurrogate = c.isHighSurrogate() && i < length - 1;
2055
0
            const char32_t ucs4 = isSurrogate
2056
0
                                    ? QChar::surrogateToUcs4(c, string[++i])
2057
0
                                    : c.unicode();
2058
0
            const QUnicodeTables::Properties *p = QUnicodeTables::properties(ucs4);
2059
2060
0
            if (ucs4 == 0x20E3)
2061
0
                categorizedString.append(CharacterCategory::COMBINING_ENCLOSING_KEYCAP);
2062
0
            else if (ucs4 == 0x20E0)
2063
0
                categorizedString.append(CharacterCategory::COMBINING_ENCLOSING_CIRCLE_BACKSLASH);
2064
0
            else if (ucs4 == 0xFE0E)
2065
0
                categorizedString.append(CharacterCategory::VS15);
2066
0
            else if (ucs4 == 0xFE0F)
2067
0
                categorizedString.append(CharacterCategory::VS16);
2068
0
            else if (ucs4 == 0x200D)
2069
0
                categorizedString.append(CharacterCategory::ZWJ);
2070
0
            else if (ucs4 == 0x1F3F4)
2071
0
                categorizedString.append(CharacterCategory::TAG_BASE);
2072
0
            else if (ucs4 == 0xE007F)
2073
0
                categorizedString.append(CharacterCategory::TAG_TERM);
2074
0
            else if ((ucs4 >= 0xE0030 && ucs4 <= 0xE0039) || (ucs4 >= 0xE0061 && ucs4 <= 0xE007A))
2075
0
                categorizedString.append(CharacterCategory::TAG_SEQUENCE);
2076
0
            else if (ucs4 >= 0x1F1E6 && ucs4 <= 0x1F1FF)
2077
0
                categorizedString.append(CharacterCategory::REGIONAL_INDICATOR);
2078
            // emoji_keycap_sequence = [0-9#*] \x{FE0F 20E3}
2079
0
            else if ((ucs4 >= 0x0030 && ucs4 <= 0x0039) || ucs4 == 0x0023 || ucs4 == 0x002A)
2080
0
                categorizedString.append(CharacterCategory::KEYCAP_BASE);
2081
0
            else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Modifier_Base))
2082
0
                categorizedString.append(CharacterCategory::EMOJI_MODIFIER_BASE);
2083
0
            else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Modifier))
2084
0
                categorizedString.append(CharacterCategory::EMOJI_MODIFIER);
2085
0
            else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji_Presentation))
2086
0
                categorizedString.append(CharacterCategory::EMOJI_EMOJI_PRESENTATION);
2087
            // If it's in the emoji list and doesn't have the emoji presentation, it is text
2088
            // presentation.
2089
0
            else if (p->emojiFlags & uchar(QUnicodeTables::EmojiFlags::Emoji))
2090
0
                categorizedString.append(CharacterCategory::EMOJI_TEXT_PRESENTATION);
2091
0
            else
2092
0
                categorizedString.append(CharacterCategory::OTHER);
2093
2094
0
            qCDebug(lcEmojiSegmenter) << "Checking character" << (isSurrogate ? (i - 1) : i)
2095
0
                                      << ", ucs4 ==" << ucs4
2096
0
                                      << ", category:" << categorizedString.last();
2097
0
        }
2098
0
    }
2099
0
#endif
2100
2101
0
    const ushort *uc = string;
2102
0
    const ushort *e = uc + length;
2103
2104
0
#if !defined(QT_NO_EMOJISEGMENTER)
2105
0
    const emoji_text_iter_t categoriesStart = categorizedString.data();
2106
0
    const emoji_text_iter_t categoriesEnd = categoriesStart + categorizedString.size();
2107
2108
0
    emoji_text_iter_t categoryIt = categoriesStart;
2109
2110
0
    bool isEmoji = false;
2111
0
    bool hasVs = false;
2112
0
    emoji_text_iter_t nextIt = categoryIt;
2113
0
#endif
2114
2115
0
    while (uc < e) {
2116
0
#if !defined(QT_NO_EMOJISEGMENTER)
2117
        // Find next emoji sequence
2118
0
        if (!disableEmojiSegmenter && categoryIt == nextIt) {
2119
0
            nextIt = scan_emoji_presentation(categoryIt, categoriesEnd, &isEmoji, &hasVs);
2120
2121
0
            qCDebug(lcEmojiSegmenter) << "Checking character" << (categoryIt - categoriesStart)
2122
0
                                      << ", sequence length:" << (nextIt - categoryIt)
2123
0
                                      << ", is emoji sequence:" << isEmoji;
2124
2125
0
        }
2126
0
#endif
2127
2128
0
        switch (*uc) {
2129
0
        case QChar::ObjectReplacementCharacter:
2130
0
            {
2131
0
                const QTextDocumentPrivate *doc_p = QTextDocumentPrivate::get(block);
2132
0
                if (doc_p != nullptr
2133
0
                        && doc_p->layout() != nullptr
2134
0
                        && QAbstractTextDocumentLayoutPrivate::get(doc_p->layout()) != nullptr
2135
0
                        && QAbstractTextDocumentLayoutPrivate::get(doc_p->layout())->hasHandlers()) {
2136
0
                    analysis->flags = QScriptAnalysis::Object;
2137
0
                } else if (specialData) {
2138
                    // Standalone QTextLayout: check if a QTextImageFormat
2139
                    // was set for this position via setFormats().
2140
0
                    const int pos = uc - string;
2141
0
                    analysis->flags = QScriptAnalysis::None;
2142
0
                    for (const auto &range : std::as_const(specialData->formats)) {
2143
0
                        if (range.start <= pos && pos < range.start + range.length
2144
0
                            && range.format.isImageFormat()) {
2145
0
                            analysis->flags = QScriptAnalysis::Object;
2146
0
                            break;
2147
0
                        }
2148
0
                    }
2149
0
                } else {
2150
0
                    analysis->flags = QScriptAnalysis::None;
2151
0
                }
2152
0
            }
2153
0
            break;
2154
0
        case QChar::LineSeparator:
2155
0
            analysis->flags = QScriptAnalysis::LineOrParagraphSeparator;
2156
0
            if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
2157
0
                const int offset = uc - string;
2158
0
                layoutData->string.detach();
2159
0
                string = reinterpret_cast<const ushort *>(layoutData->string.unicode());
2160
0
                uc = string + offset;
2161
0
                e = string + length;
2162
0
                *const_cast<ushort*>(uc) = 0x21B5; // visual line separator
2163
0
            }
2164
0
            break;
2165
0
        case QChar::Tabulation:
2166
0
            analysis->flags = QScriptAnalysis::Tab;
2167
0
            analysis->bidiLevel = bidi.baseLevel;
2168
0
            break;
2169
0
        case QChar::Space:
2170
0
        case QChar::Nbsp:
2171
0
            if (option.flags() & QTextOption::ShowTabsAndSpaces) {
2172
0
                analysis->flags = (*uc == QChar::Space) ? QScriptAnalysis::Space : QScriptAnalysis::Nbsp;
2173
0
                break;
2174
0
            }
2175
0
            Q_FALLTHROUGH();
2176
0
        default:
2177
0
            analysis->flags = QScriptAnalysis::None;
2178
0
            break;
2179
0
        };
2180
2181
0
#if !defined(QT_NO_EMOJISEGMENTER)
2182
0
        if (!disableEmojiSegmenter) {
2183
0
            if (isEmoji) {
2184
0
                static_assert(QChar::ScriptCount < USHRT_MAX);
2185
0
                analysis->script = QFontDatabasePrivate::Script_Emoji;
2186
0
            }
2187
2188
0
            if (QChar::isHighSurrogate(*uc) && (uc + 1) < e && QChar::isLowSurrogate(*(uc + 1))) {
2189
0
                if (isEmoji)
2190
0
                    (analysis + 1)->script = QFontDatabasePrivate::Script_Emoji;
2191
2192
0
                ++uc;
2193
0
                ++analysis;
2194
0
            }
2195
2196
0
            ++categoryIt;
2197
0
        }
2198
0
#endif
2199
2200
0
        ++uc;
2201
0
        ++analysis;
2202
0
    }
2203
0
    if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
2204
0
        (analysis-1)->flags = QScriptAnalysis::LineOrParagraphSeparator; // to exclude it from width
2205
0
    }
2206
2207
0
    Itemizer itemizer(layoutData->string, scriptAnalysis.data(), layoutData->items);
2208
2209
0
    const QTextDocumentPrivate *p = QTextDocumentPrivate::get(block);
2210
0
    if (p) {
2211
0
        SpecialData *s = specialData;
2212
2213
0
        QTextDocumentPrivate::FragmentIterator it = p->find(block.position());
2214
0
        QTextDocumentPrivate::FragmentIterator end = p->find(block.position() + block.length() - 1); // -1 to omit the block separator char
2215
0
        int format = it.value()->format;
2216
2217
0
        int preeditPosition = s ? s->preeditPosition : INT_MAX;
2218
0
        int prevPosition = 0;
2219
0
        int position = prevPosition;
2220
0
        while (1) {
2221
0
            const QTextFragmentData * const frag = it.value();
2222
0
            if (it == end || format != frag->format) {
2223
0
                if (s && position >= preeditPosition) {
2224
0
                    position += s->preeditText.size();
2225
0
                    preeditPosition = INT_MAX;
2226
0
                }
2227
0
                Q_ASSERT(position <= length);
2228
0
                QFont::Capitalization capitalization =
2229
0
                        formatCollection()->charFormat(format).hasProperty(QTextFormat::FontCapitalization)
2230
0
                        ? formatCollection()->charFormat(format).fontCapitalization()
2231
0
                        : formatCollection()->defaultFont().capitalization();
2232
0
                if (s) {
2233
0
                    for (const auto &range : std::as_const(s->formats)) {
2234
0
                        if (range.start + range.length <= prevPosition || range.start >= position)
2235
0
                            continue;
2236
0
                        if (range.format.hasProperty(QTextFormat::FontCapitalization)) {
2237
0
                            if (range.start > prevPosition)
2238
0
                                itemizer.generate(prevPosition, range.start - prevPosition, capitalization);
2239
0
                            int newStart = std::max(prevPosition, range.start);
2240
0
                            int newEnd = std::min(position, range.start + range.length);
2241
0
                            itemizer.generate(newStart, newEnd - newStart, range.format.fontCapitalization());
2242
0
                            prevPosition = newEnd;
2243
0
                        }
2244
0
                    }
2245
0
                }
2246
0
                itemizer.generate(prevPosition, position - prevPosition, capitalization);
2247
0
                if (it == end) {
2248
0
                    if (position < length)
2249
0
                        itemizer.generate(position, length - position, capitalization);
2250
0
                    break;
2251
0
                }
2252
0
                format = frag->format;
2253
0
                prevPosition = position;
2254
0
            }
2255
0
            position += frag->size_array[0];
2256
0
            ++it;
2257
0
        }
2258
0
    } else {
2259
0
#ifndef QT_NO_RAWFONT
2260
0
        if (useRawFont && specialData) {
2261
0
            int lastIndex = 0;
2262
0
            for (int i = 0; i < specialData->formats.size(); ++i) {
2263
0
                const QTextLayout::FormatRange &range = specialData->formats.at(i);
2264
0
                const QTextCharFormat &format = range.format;
2265
0
                if (format.hasProperty(QTextFormat::FontCapitalization)) {
2266
0
                    itemizer.generate(lastIndex, range.start - lastIndex, QFont::MixedCase);
2267
0
                    itemizer.generate(range.start, range.length, format.fontCapitalization());
2268
0
                    lastIndex = range.start + range.length;
2269
0
                }
2270
0
            }
2271
0
            itemizer.generate(lastIndex, length - lastIndex, QFont::MixedCase);
2272
0
        } else
2273
0
#endif
2274
0
            itemizer.generate(0, length, static_cast<QFont::Capitalization> (fnt.d->capital));
2275
0
    }
2276
2277
0
    addRequiredBoundaries();
2278
0
    resolveFormats();
2279
0
}
2280
2281
bool QTextEngine::isRightToLeft() const
2282
0
{
2283
0
    switch (option.textDirection()) {
2284
0
    case Qt::LeftToRight:
2285
0
        return false;
2286
0
    case Qt::RightToLeft:
2287
0
        return true;
2288
0
    default:
2289
0
        break;
2290
0
    }
2291
0
    if (!layoutData)
2292
0
        itemize();
2293
    // this places the cursor in the right position depending on the keyboard layout
2294
0
    if (layoutData->string.isEmpty())
2295
0
        return QGuiApplication::inputMethod()->inputDirection() == Qt::RightToLeft;
2296
0
    return layoutData->string.isRightToLeft();
2297
0
}
2298
2299
2300
int QTextEngine::findItem(int strPos, int firstItem) const
2301
0
{
2302
0
    itemize();
2303
0
    if (strPos < 0 || strPos >= layoutData->string.size() || firstItem < 0)
2304
0
        return -1;
2305
2306
0
    int left = firstItem + 1;
2307
0
    int right = layoutData->items.size()-1;
2308
0
    while(left <= right) {
2309
0
        int middle = ((right-left)/2)+left;
2310
0
        if (strPos > layoutData->items.at(middle).position)
2311
0
            left = middle+1;
2312
0
        else if (strPos < layoutData->items.at(middle).position)
2313
0
            right = middle-1;
2314
0
        else {
2315
0
            return middle;
2316
0
        }
2317
0
    }
2318
0
    return right;
2319
0
}
2320
2321
namespace {
2322
template<typename InnerFunc>
2323
void textIterator(const QTextEngine *textEngine, int from, int len, QFixed &width, InnerFunc &&innerFunc)
2324
0
{
2325
0
    for (int i = 0; i < textEngine->layoutData->items.size(); i++) {
2326
0
        const QScriptItem *si = textEngine->layoutData->items.constData() + i;
2327
0
        int pos = si->position;
2328
0
        int ilen = textEngine->length(i);
2329
//          qDebug("item %d: from %d len %d", i, pos, ilen);
2330
0
        if (pos >= from + len)
2331
0
            break;
2332
0
        if (pos + ilen > from) {
2333
0
            if (!si->num_glyphs)
2334
0
                textEngine->shape(i);
2335
2336
0
            if (si->analysis.flags == QScriptAnalysis::Object) {
2337
0
                width += si->width;
2338
0
                continue;
2339
0
            } else if (si->analysis.flags == QScriptAnalysis::Tab) {
2340
0
                width += textEngine->calculateTabWidth(i, width);
2341
0
                continue;
2342
0
            }
2343
2344
0
            unsigned short *logClusters = textEngine->logClusters(si);
2345
2346
//             fprintf(stderr, "  logclusters:");
2347
//             for (int k = 0; k < ilen; k++)
2348
//                 fprintf(stderr, " %d", logClusters[k]);
2349
//             fprintf(stderr, "\n");
2350
            // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0.
2351
0
            int charFrom = from - pos;
2352
0
            if (charFrom < 0)
2353
0
                charFrom = 0;
2354
0
            int glyphStart = logClusters[charFrom];
2355
0
            if (charFrom > 0 && logClusters[charFrom-1] == glyphStart)
2356
0
                while (charFrom < ilen && logClusters[charFrom] == glyphStart)
2357
0
                    charFrom++;
2358
0
            if (charFrom < ilen) {
2359
0
                glyphStart = logClusters[charFrom];
2360
0
                int charEnd = from + len - 1 - pos;
2361
0
                if (charEnd >= ilen)
2362
0
                    charEnd = ilen-1;
2363
0
                int glyphEnd = logClusters[charEnd];
2364
0
                while (charEnd < ilen && logClusters[charEnd] == glyphEnd)
2365
0
                    charEnd++;
2366
0
                glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd];
2367
2368
//                 qDebug("char: start=%d end=%d / glyph: start = %d, end = %d", charFrom, charEnd, glyphStart, glyphEnd);
2369
0
                innerFunc(glyphStart, glyphEnd, si);
2370
0
            }
2371
0
        }
2372
0
    }
2373
0
}
Unexecuted instantiation: qtextengine.cpp:void (anonymous namespace)::textIterator<QTextEngine::width(int, int) const::$_0>(QTextEngine const*, int, int, QFixed&, QTextEngine::width(int, int) const::$_0&&)
Unexecuted instantiation: qtextengine.cpp:void (anonymous namespace)::textIterator<QTextEngine::boundingBox(int, int) const::$_0>(QTextEngine const*, int, int, QFixed&, QTextEngine::boundingBox(int, int) const::$_0&&)
Unexecuted instantiation: qtextengine.cpp:void (anonymous namespace)::textIterator<QTextEngine::tightBoundingBox(int, int) const::$_0>(QTextEngine const*, int, int, QFixed&, QTextEngine::tightBoundingBox(int, int) const::$_0&&)
2374
} // namespace
2375
2376
QFixed QTextEngine::width(int from, int len) const
2377
0
{
2378
0
    itemize();
2379
2380
0
    QFixed w = 0;
2381
//     qDebug("QTextEngine::width(from = %d, len = %d), numItems=%d, strleng=%d", from,  len, items.size(), string.length());
2382
0
    textIterator(this, from, len, w, [this, &w](int glyphStart, int glyphEnd, const QScriptItem *si) {
2383
0
        QGlyphLayout glyphs = this->shapedGlyphs(si);
2384
0
        for (int j = glyphStart; j < glyphEnd; j++)
2385
0
            w += glyphs.advances[j] * !glyphs.attributes[j].dontPrint;
2386
0
    });
2387
//     qDebug("   --> w= %d ", w);
2388
0
    return w;
2389
0
}
2390
2391
glyph_metrics_t QTextEngine::boundingBox(int from,  int len) const
2392
0
{
2393
0
    itemize();
2394
2395
0
    glyph_metrics_t gm;
2396
2397
0
    textIterator(this, from, len, gm.width, [this, &gm](int glyphStart, int glyphEnd, const QScriptItem *si) {
2398
0
        if (glyphStart <= glyphEnd) {
2399
0
            QGlyphLayout glyphs = this->shapedGlyphs(si);
2400
0
            QFontEngine *fe = this->fontEngine(*si);
2401
0
            glyph_metrics_t m = fe->boundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart));
2402
0
            gm.x = qMin(gm.x, m.x + gm.xoff);
2403
0
            gm.y = qMin(gm.y, m.y + gm.yoff);
2404
0
            gm.width = qMax(gm.width, m.width + gm.xoff);
2405
0
            gm.height = qMax(gm.height, m.height + gm.yoff);
2406
0
            gm.xoff += m.xoff;
2407
0
            gm.yoff += m.yoff;
2408
0
        }
2409
0
    });
2410
2411
0
    return gm;
2412
0
}
2413
2414
glyph_metrics_t QTextEngine::tightBoundingBox(int from,  int len) const
2415
0
{
2416
0
    itemize();
2417
2418
0
    glyph_metrics_t gm;
2419
2420
0
    textIterator(this, from, len, gm.width, [this, &gm](int glyphStart, int glyphEnd, const QScriptItem *si) {
2421
0
        if (glyphStart <= glyphEnd) {
2422
0
            QGlyphLayout glyphs = this->shapedGlyphs(si);
2423
0
            QFontEngine *fe = fontEngine(*si);
2424
2425
0
            QTextItem::RenderFlags flags = si->analysis.bidiLevel % 2
2426
0
                                               ? QTextItem::RightToLeft
2427
0
                                               : QTextItem::RenderFlags();
2428
0
            glyph_metrics_t m = fe->tightBoundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart), flags);
2429
2430
0
            gm.x = qMin(gm.x, m.x + gm.xoff);
2431
0
            gm.y = qMin(gm.y, m.y + gm.yoff);
2432
0
            gm.width = qMax(gm.width, m.width + gm.xoff);
2433
0
            gm.height = qMax(gm.height, m.height + gm.yoff);
2434
0
            gm.xoff += m.xoff;
2435
0
            gm.yoff += m.yoff;
2436
0
        }
2437
0
    });
2438
0
    return gm;
2439
0
}
2440
2441
QFont QTextEngine::font(const QScriptItem &si) const
2442
0
{
2443
0
    QFont font = fnt;
2444
0
    if (hasFormats()) {
2445
0
        QTextCharFormat f = format(&si);
2446
0
        font = f.font();
2447
2448
0
        const QTextDocumentPrivate *document_d = QTextDocumentPrivate::get(block);
2449
0
        if (document_d != nullptr && document_d->layout() != nullptr) {
2450
            // Make sure we get the right dpi on printers
2451
0
            QPaintDevice *pdev = document_d->layout()->paintDevice();
2452
0
            if (pdev)
2453
0
                font = QFont(font, pdev);
2454
0
        } else {
2455
0
            font = font.resolve(fnt);
2456
0
        }
2457
0
        QTextCharFormat::VerticalAlignment valign = f.verticalAlignment();
2458
0
        if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2459
0
            if (font.pointSize() != -1)
2460
0
                font.setPointSize((font.pointSize() * 2) / 3);
2461
0
            else
2462
0
                font.setPixelSize((font.pixelSize() * 2) / 3);
2463
0
        }
2464
0
    }
2465
2466
0
    if (si.analysis.flags == QScriptAnalysis::SmallCaps)
2467
0
        font = font.d->smallCapsFont();
2468
2469
0
    return font;
2470
0
}
2471
2472
QTextEngine::FontEngineCache::FontEngineCache()
2473
0
{
2474
0
    reset();
2475
0
}
2476
2477
//we cache the previous results of this function, as calling it numerous times with the same effective
2478
//input is common (and hard to cache at a higher level)
2479
QFontEngine *QTextEngine::fontEngine(const QScriptItem &si, QFixed *ascent, QFixed *descent, QFixed *leading) const
2480
0
{
2481
0
    QFontEngine *engine = nullptr;
2482
0
    QFontEngine *scaledEngine = nullptr;
2483
0
    int script = si.analysis.script;
2484
2485
0
    QFont font = fnt;
2486
0
#ifndef QT_NO_RAWFONT
2487
0
    if (useRawFont && rawFont.isValid()) {
2488
0
        if (feCache.prevFontEngine && feCache.prevFontEngine->type() == QFontEngine::Multi && feCache.prevScript == script) {
2489
0
            engine = feCache.prevFontEngine;
2490
0
        } else {
2491
0
            engine = QFontEngineMulti::createMultiFontEngine(rawFont.d->fontEngine, script);
2492
0
            feCache.prevFontEngine = engine;
2493
0
            feCache.prevScript = script;
2494
0
            engine->ref.ref();
2495
0
            if (feCache.prevScaledFontEngine) {
2496
0
                releaseCachedFontEngine(feCache.prevScaledFontEngine);
2497
0
                feCache.prevScaledFontEngine = nullptr;
2498
0
            }
2499
0
        }
2500
0
        if (si.analysis.flags == QScriptAnalysis::SmallCaps) {
2501
0
            if (feCache.prevScaledFontEngine) {
2502
0
                scaledEngine = feCache.prevScaledFontEngine;
2503
0
            } else {
2504
                // GCC 12 gets confused about QFontEngine::ref, for some non-obvious reason
2505
                //  warning: ‘unsigned int __atomic_or_fetch_4(volatile void*, unsigned int, int)’ writing 4 bytes
2506
                //  into a region of size 0 overflows the destination [-Wstringop-overflow=]
2507
0
                QT_WARNING_PUSH
2508
0
                QT_WARNING_DISABLE_GCC("-Wstringop-overflow")
2509
2510
0
                QFontEngine *scEngine = rawFont.d->fontEngine->cloneWithSize(smallCapsFraction * rawFont.pixelSize());
2511
0
                scEngine->ref.ref();
2512
0
                scaledEngine = QFontEngineMulti::createMultiFontEngine(scEngine, script);
2513
0
                scaledEngine->ref.ref();
2514
0
                feCache.prevScaledFontEngine = scaledEngine;
2515
                // If scEngine is not ref'ed by scaledEngine, make sure it is deallocated and not leaked.
2516
0
                if (!scEngine->ref.deref())
2517
0
                    delete scEngine;
2518
2519
0
                QT_WARNING_POP
2520
0
            }
2521
0
        }
2522
0
    } else
2523
0
#endif
2524
0
    {
2525
0
        if (hasFormats()) {
2526
0
            if (feCache.prevFontEngine && feCache.prevPosition == si.position && feCache.prevLength == length(&si) && feCache.prevScript == script) {
2527
0
                engine = feCache.prevFontEngine;
2528
0
                scaledEngine = feCache.prevScaledFontEngine;
2529
0
            } else {
2530
0
                QTextCharFormat f = format(&si);
2531
0
                font = f.font();
2532
2533
0
                if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
2534
                    // Make sure we get the right dpi on printers
2535
0
                    QPaintDevice *pdev = QTextDocumentPrivate::get(block)->layout()->paintDevice();
2536
0
                    if (pdev)
2537
0
                        font = QFont(font, pdev);
2538
0
                } else {
2539
0
                    font = font.resolve(fnt);
2540
0
                }
2541
0
                engine = font.d->engineForScript(script);
2542
0
                Q_ASSERT(engine);
2543
0
                engine->ref.ref();
2544
2545
0
                QTextCharFormat::VerticalAlignment valign = f.verticalAlignment();
2546
0
                if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2547
0
                    if (font.pointSize() != -1)
2548
0
                        font.setPointSize((font.pointSize() * 2) / 3);
2549
0
                    else
2550
0
                        font.setPixelSize((font.pixelSize() * 2) / 3);
2551
0
                    scaledEngine = font.d->engineForScript(script);
2552
0
                    if (scaledEngine)
2553
0
                        scaledEngine->ref.ref();
2554
0
                }
2555
2556
0
                if (feCache.prevFontEngine)
2557
0
                    releaseCachedFontEngine(feCache.prevFontEngine);
2558
0
                feCache.prevFontEngine = engine;
2559
2560
0
                if (feCache.prevScaledFontEngine)
2561
0
                    releaseCachedFontEngine(feCache.prevScaledFontEngine);
2562
0
                feCache.prevScaledFontEngine = scaledEngine;
2563
2564
0
                feCache.prevScript = script;
2565
0
                feCache.prevPosition = si.position;
2566
0
                feCache.prevLength = length(&si);
2567
0
            }
2568
0
        } else {
2569
0
            if (feCache.prevFontEngine && feCache.prevScript == script && feCache.prevPosition == -1) {
2570
0
                engine = feCache.prevFontEngine;
2571
0
            } else {
2572
0
                engine = font.d->engineForScript(script);
2573
0
                Q_ASSERT(engine);
2574
0
                engine->ref.ref();
2575
0
                if (feCache.prevFontEngine)
2576
0
                    releaseCachedFontEngine(feCache.prevFontEngine);
2577
0
                feCache.prevFontEngine = engine;
2578
2579
0
                feCache.prevScript = script;
2580
0
                feCache.prevPosition = -1;
2581
0
                feCache.prevLength = -1;
2582
0
                feCache.prevScaledFontEngine = nullptr;
2583
0
            }
2584
0
        }
2585
2586
0
        if (si.analysis.flags == QScriptAnalysis::SmallCaps) {
2587
0
            QFontPrivate *p = font.d->smallCapsFontPrivate();
2588
0
            scaledEngine = p->engineForScript(script);
2589
0
        }
2590
0
    }
2591
2592
0
    if (leading) {
2593
0
        Q_ASSERT(engine);
2594
0
        Q_ASSERT(ascent);
2595
0
        Q_ASSERT(descent);
2596
0
        *ascent = engine->ascent();
2597
0
        *descent = engine->descent();
2598
0
        *leading = engine->leading();
2599
0
    }
2600
2601
0
    if (scaledEngine)
2602
0
        return scaledEngine;
2603
0
    return engine;
2604
0
}
2605
2606
struct QJustificationPoint {
2607
    int type;
2608
    QFixed kashidaWidth;
2609
    QGlyphLayout glyph;
2610
};
2611
2612
Q_DECLARE_TYPEINFO(QJustificationPoint, Q_PRIMITIVE_TYPE);
2613
2614
static void set(QJustificationPoint *point, int type, const QGlyphLayout &glyph, QFontEngine *fe)
2615
0
{
2616
0
    point->type = type;
2617
0
    point->glyph = glyph;
2618
2619
0
    if (type >= Justification_Arabic_Normal) {
2620
0
        const char32_t ch = U'\x640'; // Kashida character
2621
2622
0
        glyph_t kashidaGlyph = fe->glyphIndex(ch);
2623
0
        if (kashidaGlyph != 0) {
2624
0
            QGlyphLayout g;
2625
0
            g.numGlyphs = 1;
2626
0
            g.glyphs = &kashidaGlyph;
2627
0
            g.advances = &point->kashidaWidth;
2628
0
            fe->recalcAdvances(&g, { });
2629
2630
0
            if (point->kashidaWidth == 0)
2631
0
                point->type = Justification_Prohibited;
2632
0
        } else {
2633
0
            point->type = Justification_Prohibited;
2634
0
            point->kashidaWidth = 0;
2635
0
        }
2636
0
    }
2637
0
}
2638
2639
2640
void QTextEngine::justify(const QScriptLine &line)
2641
0
{
2642
//     qDebug("justify: line.gridfitted = %d, line.justified=%d", line.gridfitted, line.justified);
2643
0
    if (line.gridfitted && line.justified)
2644
0
        return;
2645
2646
0
    if (!line.gridfitted) {
2647
        // redo layout in device metrics, then adjust
2648
0
        const_cast<QScriptLine &>(line).gridfitted = true;
2649
0
    }
2650
2651
0
    if ((option.alignment() & Qt::AlignHorizontal_Mask) != Qt::AlignJustify)
2652
0
        return;
2653
2654
0
    itemize();
2655
2656
0
    if (!forceJustification) {
2657
0
        int end = line.from + (int)line.length + line.trailingSpaces;
2658
0
        if (end == layoutData->string.size())
2659
0
            return; // no justification at end of paragraph
2660
0
        if (end && layoutData->items.at(findItem(end - 1)).analysis.flags == QScriptAnalysis::LineOrParagraphSeparator)
2661
0
            return; // no justification at the end of an explicitly separated line
2662
0
    }
2663
2664
    // justify line
2665
0
    int maxJustify = 0;
2666
2667
    // don't include trailing white spaces when doing justification
2668
0
    int line_length = line.length;
2669
0
    const QCharAttributes *a = attributes();
2670
0
    if (! a)
2671
0
        return;
2672
0
    a += line.from;
2673
0
    while (line_length && a[line_length-1].whiteSpace)
2674
0
        --line_length;
2675
    // subtract one char more, as we can't justfy after the last character
2676
0
    --line_length;
2677
2678
0
    if (line_length <= 0)
2679
0
        return;
2680
2681
0
    int firstItem = findItem(line.from);
2682
0
    int lastItem = findItem(line.from + line_length - 1, firstItem);
2683
0
    int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2684
2685
0
    QVarLengthArray<QJustificationPoint> justificationPoints;
2686
0
    int nPoints = 0;
2687
//     qDebug("justifying from %d len %d, firstItem=%d, nItems=%d (%s)", line.from, line_length, firstItem, nItems, layoutData->string.mid(line.from, line_length).toUtf8().constData());
2688
0
    QFixed minKashida = 0x100000;
2689
2690
    // we need to do all shaping before we go into the next loop, as we there
2691
    // store pointers to the glyph data that could get reallocated by the shaping
2692
    // process.
2693
0
    for (int i = 0; i < nItems; ++i) {
2694
0
        const QScriptItem &si = layoutData->items.at(firstItem + i);
2695
0
        if (!si.num_glyphs)
2696
0
            shape(firstItem + i);
2697
0
    }
2698
2699
0
    for (int i = 0; i < nItems; ++i) {
2700
0
        const QScriptItem &si = layoutData->items.at(firstItem + i);
2701
2702
0
        int kashida_type = Justification_Arabic_Normal;
2703
0
        int kashida_pos = -1;
2704
2705
0
        int start = qMax(line.from - si.position, 0);
2706
0
        int end = qMin(line.from + line_length - (int)si.position, length(firstItem+i));
2707
2708
0
        unsigned short *log_clusters = logClusters(&si);
2709
2710
0
        int gs = log_clusters[start];
2711
0
        int ge = (end == length(firstItem+i) ? si.num_glyphs : log_clusters[end]);
2712
2713
0
        Q_ASSERT(ge <= si.num_glyphs);
2714
2715
0
        const QGlyphLayout g = shapedGlyphs(&si);
2716
2717
0
        for (int i = gs; i < ge; ++i) {
2718
0
            g.justifications[i].type = QGlyphJustification::JustifyNone;
2719
0
            g.justifications[i].nKashidas = 0;
2720
0
            g.justifications[i].space_18d6 = 0;
2721
2722
0
            justificationPoints.resize(nPoints+3);
2723
0
            int justification = g.attributes[i].justification;
2724
2725
0
            switch(justification) {
2726
0
            case Justification_Prohibited:
2727
0
                break;
2728
0
            case Justification_Space:
2729
0
            case Justification_Arabic_Space:
2730
0
                if (kashida_pos >= 0) {
2731
//                     qDebug("kashida position at %d in word", kashida_pos);
2732
0
                    set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si));
2733
0
                    if (justificationPoints[nPoints].kashidaWidth > 0) {
2734
0
                        minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth);
2735
0
                        maxJustify = qMax(maxJustify, justificationPoints[nPoints].type);
2736
0
                        ++nPoints;
2737
0
                    }
2738
0
                }
2739
0
                kashida_pos = -1;
2740
0
                kashida_type = Justification_Arabic_Normal;
2741
0
                Q_FALLTHROUGH();
2742
0
            case Justification_Character:
2743
0
                set(&justificationPoints[nPoints++], justification, g.mid(i), fontEngine(si));
2744
0
                maxJustify = qMax(maxJustify, justification);
2745
0
                break;
2746
0
            case Justification_Arabic_Normal:
2747
0
            case Justification_Arabic_Waw:
2748
0
            case Justification_Arabic_BaRa:
2749
0
            case Justification_Arabic_Alef:
2750
0
            case Justification_Arabic_HahDal:
2751
0
            case Justification_Arabic_Seen:
2752
0
            case Justification_Arabic_Kashida:
2753
0
                if (justification >= kashida_type) {
2754
0
                    kashida_pos = i;
2755
0
                    kashida_type = justification;
2756
0
                }
2757
0
            }
2758
0
        }
2759
0
        if (kashida_pos >= 0) {
2760
0
            set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si));
2761
0
            if (justificationPoints[nPoints].kashidaWidth > 0) {
2762
0
                minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth);
2763
0
                maxJustify = qMax(maxJustify, justificationPoints[nPoints].type);
2764
0
                ++nPoints;
2765
0
            }
2766
0
        }
2767
0
    }
2768
2769
0
    QFixed leading = leadingSpaceWidth(line);
2770
0
    QFixed need = line.width - line.textWidth - leading;
2771
0
    if (need < 0) {
2772
        // line overflows already!
2773
0
        const_cast<QScriptLine &>(line).justified = true;
2774
0
        return;
2775
0
    }
2776
2777
//     qDebug("doing justification: textWidth=%x, requested=%x, maxJustify=%d", line.textWidth.value(), line.width.value(), maxJustify);
2778
//     qDebug("     minKashida=%f, need=%f", minKashida.toReal(), need.toReal());
2779
2780
    // distribute in priority order
2781
0
    if (maxJustify >= Justification_Arabic_Normal) {
2782
0
        while (need >= minKashida) {
2783
0
            for (int type = maxJustify; need >= minKashida && type >= Justification_Arabic_Normal; --type) {
2784
0
                for (int i = 0; need >= minKashida && i < nPoints; ++i) {
2785
0
                    if (justificationPoints[i].type == type && justificationPoints[i].kashidaWidth <= need) {
2786
0
                        justificationPoints[i].glyph.justifications->nKashidas++;
2787
                        // ############
2788
0
                        justificationPoints[i].glyph.justifications->space_18d6 += justificationPoints[i].kashidaWidth.value();
2789
0
                        need -= justificationPoints[i].kashidaWidth;
2790
//                         qDebug("adding kashida type %d with width %x, neednow %x", type, justificationPoints[i].kashidaWidth, need.value());
2791
0
                    }
2792
0
                }
2793
0
            }
2794
0
        }
2795
0
    }
2796
0
    Q_ASSERT(need >= 0);
2797
0
    if (!need)
2798
0
        goto end;
2799
2800
0
    maxJustify = qMin(maxJustify, int(Justification_Space));
2801
0
    for (int type = maxJustify; need != 0 && type > 0; --type) {
2802
0
        int n = 0;
2803
0
        for (int i = 0; i < nPoints; ++i) {
2804
0
            if (justificationPoints[i].type == type)
2805
0
                ++n;
2806
0
        }
2807
//          qDebug("number of points for justification type %d: %d", type, n);
2808
2809
2810
0
        if (!n)
2811
0
            continue;
2812
2813
0
        for (int i = 0; i < nPoints; ++i) {
2814
0
            if (justificationPoints[i].type == type) {
2815
0
                QFixed add = need/n;
2816
//                  qDebug("adding %x to glyph %x", add.value(), justificationPoints[i].glyph->glyph);
2817
0
                justificationPoints[i].glyph.justifications[0].space_18d6 = add.value();
2818
0
                need -= add;
2819
0
                --n;
2820
0
            }
2821
0
        }
2822
2823
0
        Q_ASSERT(!need);
2824
0
    }
2825
0
 end:
2826
0
    const_cast<QScriptLine &>(line).justified = true;
2827
0
}
2828
2829
void QScriptLine::setDefaultHeight(QTextEngine *eng)
2830
0
{
2831
0
    QFont f;
2832
0
    QFontEngine *e;
2833
2834
0
    if (QTextDocumentPrivate::get(eng->block) != nullptr && QTextDocumentPrivate::get(eng->block)->layout() != nullptr) {
2835
0
        f = eng->block.charFormat().font();
2836
        // Make sure we get the right dpi on printers
2837
0
        QPaintDevice *pdev = QTextDocumentPrivate::get(eng->block)->layout()->paintDevice();
2838
0
        if (pdev)
2839
0
            f = QFont(f, pdev);
2840
0
        e = f.d->engineForScript(QChar::Script_Common);
2841
0
    } else {
2842
0
        e = eng->fnt.d->engineForScript(QChar::Script_Common);
2843
0
    }
2844
2845
0
    QFixed other_ascent = e->ascent();
2846
0
    QFixed other_descent = e->descent();
2847
0
    QFixed other_leading = e->leading();
2848
0
    leading = qMax(leading + ascent, other_leading + other_ascent) - qMax(ascent, other_ascent);
2849
0
    ascent = qMax(ascent, other_ascent);
2850
0
    descent = qMax(descent, other_descent);
2851
0
}
2852
2853
QTextEngine::LayoutData::LayoutData()
2854
0
{
2855
0
    memory = nullptr;
2856
0
    allocated = 0;
2857
0
    memory_on_stack = false;
2858
0
    used = 0;
2859
0
    hasBidi = false;
2860
0
    layoutState = LayoutEmpty;
2861
0
    haveCharAttributes = false;
2862
0
    logClustersPtr = nullptr;
2863
0
    available_glyphs = 0;
2864
0
    currentMaxWidth = 0;
2865
0
}
2866
2867
QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, qsizetype _allocated)
2868
0
    : string(str)
2869
0
{
2870
0
    allocated = _allocated;
2871
2872
0
    constexpr qsizetype voidSize = sizeof(void*);
2873
0
    qsizetype space_charAttributes = sizeof(QCharAttributes) * string.size() / voidSize + 1;
2874
0
    qsizetype space_logClusters = sizeof(unsigned short) * string.size() / voidSize + 1;
2875
0
    available_glyphs = (allocated - space_charAttributes - space_logClusters) * voidSize / QGlyphLayout::SpaceNeeded;
2876
2877
0
    if (available_glyphs < str.size()) {
2878
        // need to allocate on the heap
2879
0
        allocated = 0;
2880
2881
0
        memory_on_stack = false;
2882
0
        memory = nullptr;
2883
0
        logClustersPtr = nullptr;
2884
0
    } else {
2885
0
        memory_on_stack = true;
2886
0
        memory = stack_memory;
2887
0
        logClustersPtr = (unsigned short *)(memory + space_charAttributes);
2888
2889
0
        void *m = memory + space_charAttributes + space_logClusters;
2890
0
        glyphLayout = QGlyphLayout(reinterpret_cast<char *>(m), str.size());
2891
0
        glyphLayout.clear();
2892
0
        memset(memory, 0, space_charAttributes*sizeof(void *));
2893
0
    }
2894
0
    used = 0;
2895
0
    hasBidi = false;
2896
0
    layoutState = LayoutEmpty;
2897
0
    haveCharAttributes = false;
2898
0
    currentMaxWidth = 0;
2899
0
}
2900
2901
QTextEngine::LayoutData::~LayoutData()
2902
0
{
2903
0
    if (!memory_on_stack)
2904
0
        free(memory);
2905
0
    memory = nullptr;
2906
0
}
2907
2908
bool QTextEngine::LayoutData::reallocate(int totalGlyphs)
2909
0
{
2910
0
    Q_ASSERT(totalGlyphs >= glyphLayout.numGlyphs);
2911
0
    if (memory_on_stack && available_glyphs >= totalGlyphs) {
2912
0
        glyphLayout.grow(glyphLayout.data(), totalGlyphs);
2913
0
        return true;
2914
0
    }
2915
2916
0
    const qsizetype space_charAttributes = (sizeof(QCharAttributes) * string.size() / sizeof(void*) + 1);
2917
0
    const qsizetype space_logClusters = (sizeof(unsigned short) * string.size() / sizeof(void*) + 1);
2918
0
    const qsizetype space_glyphs = qsizetype(totalGlyphs) * QGlyphLayout::SpaceNeeded / sizeof(void *) + 2;
2919
2920
0
    const qsizetype newAllocated = space_charAttributes + space_glyphs + space_logClusters;
2921
    // Check if the length of string/glyphs causes int overflow,
2922
    // we can't layout such a long string all at once, so return false here to
2923
    // indicate there is a failure
2924
0
    if (size_t(space_charAttributes) > INT_MAX || size_t(space_logClusters) > INT_MAX || totalGlyphs < 0
2925
0
        || size_t(space_glyphs) > INT_MAX || size_t(newAllocated) > INT_MAX || newAllocated < allocated) {
2926
0
        layoutState = LayoutFailed;
2927
0
        return false;
2928
0
    }
2929
2930
0
    void **newMem = (void **)::realloc(memory_on_stack ? nullptr : memory, newAllocated*sizeof(void *));
2931
0
    if (!newMem) {
2932
0
        layoutState = LayoutFailed;
2933
0
        return false;
2934
0
    }
2935
0
    if (memory_on_stack)
2936
0
        memcpy(newMem, memory, allocated*sizeof(void *));
2937
0
    memory = newMem;
2938
0
    memory_on_stack = false;
2939
2940
0
    void **m = memory;
2941
0
    m += space_charAttributes;
2942
0
    logClustersPtr = (unsigned short *) m;
2943
0
    m += space_logClusters;
2944
2945
0
    const qsizetype space_preGlyphLayout = space_charAttributes + space_logClusters;
2946
0
    if (allocated < space_preGlyphLayout)
2947
0
        memset(memory + allocated, 0, (space_preGlyphLayout - allocated)*sizeof(void *));
2948
2949
0
    glyphLayout.grow(reinterpret_cast<char *>(m), totalGlyphs);
2950
2951
0
    allocated = newAllocated;
2952
0
    return true;
2953
0
}
2954
2955
void QGlyphLayout::copy(QGlyphLayout *oldLayout)
2956
0
{
2957
0
    Q_ASSERT(offsets != oldLayout->offsets);
2958
2959
0
    int n = std::min(numGlyphs, oldLayout->numGlyphs);
2960
2961
0
    memcpy(offsets, oldLayout->offsets, n * sizeof(QFixedPoint));
2962
0
    memcpy(attributes, oldLayout->attributes, n * sizeof(QGlyphAttributes));
2963
0
    memcpy(justifications, oldLayout->justifications, n * sizeof(QGlyphJustification));
2964
0
    memcpy(advances, oldLayout->advances, n * sizeof(QFixed));
2965
0
    memcpy(glyphs, oldLayout->glyphs, n * sizeof(glyph_t));
2966
2967
0
    numGlyphs = n;
2968
0
}
2969
2970
// grow to the new size, copying the existing data to the new layout
2971
void QGlyphLayout::grow(char *address, int totalGlyphs)
2972
0
{
2973
0
    QGlyphLayout oldLayout(address, numGlyphs);
2974
0
    QGlyphLayout newLayout(address, totalGlyphs);
2975
2976
0
    if (numGlyphs) {
2977
        // move the existing data
2978
0
        memmove(newLayout.attributes, oldLayout.attributes, numGlyphs * sizeof(QGlyphAttributes));
2979
0
        memmove(newLayout.justifications, oldLayout.justifications, numGlyphs * sizeof(QGlyphJustification));
2980
0
        memmove(newLayout.advances, oldLayout.advances, numGlyphs * sizeof(QFixed));
2981
0
        memmove(newLayout.glyphs, oldLayout.glyphs, numGlyphs * sizeof(glyph_t));
2982
0
    }
2983
2984
    // clear the new data
2985
0
    newLayout.clear(numGlyphs);
2986
2987
0
    *this = newLayout;
2988
0
}
2989
2990
void QTextEngine::freeMemory()
2991
0
{
2992
0
    if (!stackEngine) {
2993
0
        delete layoutData;
2994
0
        layoutData = nullptr;
2995
0
    } else {
2996
0
        layoutData->used = 0;
2997
0
        layoutData->hasBidi = false;
2998
0
        layoutData->layoutState = LayoutEmpty;
2999
0
        layoutData->haveCharAttributes = false;
3000
0
        layoutData->currentMaxWidth = 0;
3001
0
        layoutData->items.clear();
3002
0
    }
3003
0
    if (specialData)
3004
0
        specialData->resolvedFormats.clear();
3005
0
    for (int i = 0; i < lines.size(); ++i) {
3006
0
        lines[i].justified = 0;
3007
0
        lines[i].gridfitted = 0;
3008
0
    }
3009
0
}
3010
3011
int QTextEngine::formatIndex(const QScriptItem *si) const
3012
0
{
3013
0
    if (specialData && !specialData->resolvedFormats.isEmpty()) {
3014
0
        QTextFormatCollection *collection = formatCollection();
3015
0
        Q_ASSERT(collection);
3016
0
        return collection->indexForFormat(specialData->resolvedFormats.at(si - &layoutData->items.at(0)));
3017
0
    }
3018
3019
0
    const QTextDocumentPrivate *p = QTextDocumentPrivate::get(block);
3020
0
    if (!p)
3021
0
        return -1;
3022
0
    int pos = si->position;
3023
0
    if (specialData && si->position >= specialData->preeditPosition) {
3024
0
        if (si->position < specialData->preeditPosition + specialData->preeditText.size())
3025
0
            pos = qMax(qMin(block.length(), specialData->preeditPosition) - 1, 0);
3026
0
        else
3027
0
            pos -= specialData->preeditText.size();
3028
0
    }
3029
0
    QTextDocumentPrivate::FragmentIterator it = p->find(block.position() + pos);
3030
0
    return it.value()->format;
3031
0
}
3032
3033
3034
QTextCharFormat QTextEngine::format(const QScriptItem *si) const
3035
0
{
3036
0
    if (const QTextFormatCollection *collection = formatCollection())
3037
0
        return collection->charFormat(formatIndex(si));
3038
0
    return QTextCharFormat();
3039
0
}
3040
3041
void QTextEngine::addRequiredBoundaries() const
3042
0
{
3043
0
    if (specialData) {
3044
0
        for (int i = 0; i < specialData->formats.size(); ++i) {
3045
0
            const QTextLayout::FormatRange &r = specialData->formats.at(i);
3046
0
            setBoundary(r.start);
3047
0
            setBoundary(r.start + r.length);
3048
            //qDebug("adding boundaries %d %d", r.start, r.start+r.length);
3049
0
        }
3050
0
    }
3051
0
}
3052
3053
bool QTextEngine::atWordSeparator(int position) const
3054
0
{
3055
0
    const QChar c = layoutData->string.at(position);
3056
0
    switch (c.unicode()) {
3057
0
    case '.':
3058
0
    case ',':
3059
0
    case '?':
3060
0
    case '!':
3061
0
    case '@':
3062
0
    case '#':
3063
0
    case '$':
3064
0
    case ':':
3065
0
    case ';':
3066
0
    case '-':
3067
0
    case '<':
3068
0
    case '>':
3069
0
    case '[':
3070
0
    case ']':
3071
0
    case '(':
3072
0
    case ')':
3073
0
    case '{':
3074
0
    case '}':
3075
0
    case '=':
3076
0
    case '/':
3077
0
    case '+':
3078
0
    case '%':
3079
0
    case '&':
3080
0
    case '^':
3081
0
    case '*':
3082
0
    case '\'':
3083
0
    case '"':
3084
0
    case '`':
3085
0
    case '~':
3086
0
    case '|':
3087
0
    case '\\':
3088
0
        return true;
3089
0
    default:
3090
0
        break;
3091
0
    }
3092
0
    return false;
3093
0
}
3094
3095
void QTextEngine::setPreeditArea(int position, const QString &preeditText)
3096
0
{
3097
0
    if (preeditText.isEmpty()) {
3098
0
        if (!specialData)
3099
0
            return;
3100
0
        if (specialData->formats.isEmpty()) {
3101
0
            delete specialData;
3102
0
            specialData = nullptr;
3103
0
        } else {
3104
0
            specialData->preeditText = QString();
3105
0
            specialData->preeditPosition = -1;
3106
0
        }
3107
0
    } else {
3108
0
        if (!specialData)
3109
0
            specialData = new SpecialData;
3110
0
        specialData->preeditPosition = position;
3111
0
        specialData->preeditText = preeditText;
3112
0
    }
3113
0
    invalidate();
3114
0
    clearLineData();
3115
0
}
3116
3117
void QTextEngine::setFormats(const QList<QTextLayout::FormatRange> &formats)
3118
0
{
3119
0
    if (formats.isEmpty()) {
3120
0
        if (!specialData)
3121
0
            return;
3122
0
        if (specialData->preeditText.isEmpty()) {
3123
0
            delete specialData;
3124
0
            specialData = nullptr;
3125
0
        } else {
3126
0
            specialData->formats.clear();
3127
0
        }
3128
0
    } else {
3129
0
        if (!specialData) {
3130
0
            specialData = new SpecialData;
3131
0
            specialData->preeditPosition = -1;
3132
0
        }
3133
0
        specialData->formats = formats;
3134
0
        indexFormats();
3135
0
    }
3136
0
    invalidate();
3137
0
    clearLineData();
3138
0
}
3139
3140
void QTextEngine::indexFormats()
3141
0
{
3142
0
    QTextFormatCollection *collection = formatCollection();
3143
0
    if (!collection) {
3144
0
        Q_ASSERT(QTextDocumentPrivate::get(block) == nullptr);
3145
0
        specialData->formatCollection.reset(new QTextFormatCollection);
3146
0
        collection = specialData->formatCollection.data();
3147
0
    }
3148
3149
    // replace with shared copies
3150
0
    for (int i = 0; i < specialData->formats.size(); ++i) {
3151
0
        QTextCharFormat &format = specialData->formats[i].format;
3152
0
        format = collection->charFormat(collection->indexForFormat(format));
3153
0
    }
3154
0
}
3155
3156
/* These two helper functions are used to determine whether we need to insert a ZWJ character
3157
   between the text that gets truncated and the ellipsis. This is important to get
3158
   correctly shaped results for arabic text.
3159
*/
3160
static inline bool nextCharJoins(const QString &string, int pos)
3161
0
{
3162
0
    while (pos < string.size() && string.at(pos).category() == QChar::Mark_NonSpacing)
3163
0
        ++pos;
3164
0
    if (pos == string.size())
3165
0
        return false;
3166
0
    QChar::JoiningType joining = string.at(pos).joiningType();
3167
0
    return joining != QChar::Joining_None && joining != QChar::Joining_Transparent;
3168
0
}
3169
3170
static inline bool prevCharJoins(const QString &string, int pos)
3171
0
{
3172
0
    while (pos > 0 && string.at(pos - 1).category() == QChar::Mark_NonSpacing)
3173
0
        --pos;
3174
0
    if (pos == 0)
3175
0
        return false;
3176
0
    QChar::JoiningType joining = string.at(pos - 1).joiningType();
3177
0
    return joining == QChar::Joining_Dual || joining == QChar::Joining_Causing;
3178
0
}
3179
3180
static constexpr bool isRetainableControlCode(char16_t c) noexcept
3181
0
{
3182
0
    return (c >= 0x202a && c <= 0x202e) // LRE, RLE, PDF, LRO, RLO
3183
0
            || (c >= 0x200e && c <= 0x200f) // LRM, RLM
3184
0
            || (c >= 0x2066 && c <= 0x2069); // LRI, RLI, FSI, PDI
3185
0
}
3186
3187
static QString stringMidRetainingBidiCC(const QString &string,
3188
                                        const QString &ellidePrefix,
3189
                                        const QString &ellideSuffix,
3190
                                        int subStringFrom,
3191
                                        int subStringTo,
3192
                                        int midStart,
3193
                                        int midLength)
3194
0
{
3195
0
    QString prefix;
3196
0
    for (int i=subStringFrom; i<midStart; ++i) {
3197
0
        char16_t c = string.at(i).unicode();
3198
0
        if (isRetainableControlCode(c))
3199
0
            prefix += c;
3200
0
    }
3201
3202
0
    QString suffix;
3203
0
    for (int i=midStart + midLength; i<subStringTo; ++i) {
3204
0
        char16_t c = string.at(i).unicode();
3205
0
        if (isRetainableControlCode(c))
3206
0
            suffix += c;
3207
0
    }
3208
3209
0
    return prefix + ellidePrefix + QStringView{string}.mid(midStart, midLength) + ellideSuffix + suffix;
3210
0
}
3211
3212
QString QTextEngine::elidedText(Qt::TextElideMode mode, QFixed width, int flags, int from, int count) const
3213
0
{
3214
//    qDebug() << "elidedText; available width" << width.toReal() << "text width:" << this->width(0, layoutData->string.length()).toReal();
3215
3216
0
    if (flags & Qt::TextShowMnemonic) {
3217
0
        itemize();
3218
0
        QCharAttributes *attributes = const_cast<QCharAttributes *>(this->attributes());
3219
0
        if (!attributes)
3220
0
            return QString();
3221
0
        for (int i = 0; i < layoutData->items.size(); ++i) {
3222
0
            const QScriptItem &si = layoutData->items.at(i);
3223
0
            if (!si.num_glyphs)
3224
0
                shape(i);
3225
3226
0
            unsigned short *logClusters = this->logClusters(&si);
3227
0
            QGlyphLayout glyphs = shapedGlyphs(&si);
3228
3229
0
            const int end = si.position + length(&si);
3230
0
            for (int i = si.position; i < end - 1; ++i) {
3231
0
                if (layoutData->string.at(i) == u'&'
3232
0
                    && !attributes[i + 1].whiteSpace && attributes[i + 1].graphemeBoundary) {
3233
0
                    const int gp = logClusters[i - si.position];
3234
0
                    glyphs.attributes[gp].dontPrint = true;
3235
                    // emulate grapheme cluster
3236
0
                    attributes[i] = attributes[i + 1];
3237
0
                    memset(attributes + i + 1, 0, sizeof(QCharAttributes));
3238
0
                    if (layoutData->string.at(i + 1) == u'&')
3239
0
                        ++i;
3240
0
                }
3241
0
            }
3242
0
        }
3243
0
    }
3244
3245
0
    validate();
3246
3247
0
    const int to = count >= 0 && count <= layoutData->string.size() - from
3248
0
            ? from + count
3249
0
            : layoutData->string.size();
3250
3251
0
    if (mode == Qt::ElideNone
3252
0
        || this->width(from, layoutData->string.size()) <= width
3253
0
        || to - from <= 1)
3254
0
        return layoutData->string.mid(from, from - to);
3255
3256
0
    QFixed ellipsisWidth;
3257
0
    QString ellipsisText;
3258
0
    {
3259
0
        QFontEngine *engine = fnt.d->engineForScript(QChar::Script_Common);
3260
3261
0
        constexpr char16_t ellipsisChar = u'\x2026';
3262
3263
        // We only want to use the ellipsis character if it is from the main
3264
        // font (not one of the fallbacks), since using a fallback font
3265
        // will affect the metrics of the text, potentially causing it to shift
3266
        // when it is being elided.
3267
0
        if (engine->type() == QFontEngine::Multi) {
3268
0
            QFontEngineMulti *multiEngine = static_cast<QFontEngineMulti *>(engine);
3269
0
            multiEngine->ensureEngineAt(0);
3270
0
            engine = multiEngine->engine(0);
3271
0
        }
3272
3273
0
        glyph_t glyph = engine->glyphIndex(ellipsisChar);
3274
3275
0
        QGlyphLayout glyphs;
3276
0
        glyphs.numGlyphs = 1;
3277
0
        glyphs.glyphs = &glyph;
3278
0
        glyphs.advances = &ellipsisWidth;
3279
3280
0
        if (glyph != 0) {
3281
0
            engine->recalcAdvances(&glyphs, { });
3282
3283
0
            ellipsisText = ellipsisChar;
3284
0
        } else {
3285
0
            glyph = engine->glyphIndex('.');
3286
0
            if (glyph != 0) {
3287
0
                engine->recalcAdvances(&glyphs, { });
3288
3289
0
                ellipsisWidth *= 3;
3290
0
                ellipsisText = QStringLiteral("...");
3291
0
            } else {
3292
0
                engine = fnt.d->engineForScript(QChar::Script_Common);
3293
0
                glyph = engine->glyphIndex(ellipsisChar);
3294
0
                engine->recalcAdvances(&glyphs, { });
3295
0
                ellipsisText = ellipsisChar;
3296
0
            }
3297
0
        }
3298
0
    }
3299
3300
0
    const QFixed availableWidth = width - ellipsisWidth;
3301
0
    if (availableWidth < 0)
3302
0
        return QString();
3303
3304
0
    const QCharAttributes *attributes = this->attributes();
3305
0
    if (!attributes)
3306
0
        return QString();
3307
3308
0
    constexpr char16_t ZWJ = u'\x200d'; // ZERO-WIDTH JOINER
3309
3310
0
    if (mode == Qt::ElideRight) {
3311
0
        QFixed currentWidth;
3312
0
        int pos;
3313
0
        int nextBreak = from;
3314
3315
0
        do {
3316
0
            pos = nextBreak;
3317
3318
0
            ++nextBreak;
3319
0
            while (nextBreak < layoutData->string.size() && !attributes[nextBreak].graphemeBoundary)
3320
0
                ++nextBreak;
3321
3322
0
            currentWidth += this->width(pos, nextBreak - pos);
3323
0
        } while (nextBreak < to
3324
0
                 && currentWidth < availableWidth);
3325
3326
0
        if (nextCharJoins(layoutData->string, pos))
3327
0
            ellipsisText.prepend(ZWJ);
3328
3329
0
        return stringMidRetainingBidiCC(layoutData->string,
3330
0
                                        QString(), ellipsisText,
3331
0
                                        from, to,
3332
0
                                        from, pos - from);
3333
0
    } else if (mode == Qt::ElideLeft) {
3334
0
        QFixed currentWidth;
3335
0
        int pos;
3336
0
        int nextBreak = to;
3337
3338
0
        do {
3339
0
            pos = nextBreak;
3340
3341
0
            --nextBreak;
3342
0
            while (nextBreak > 0 && !attributes[nextBreak].graphemeBoundary)
3343
0
                --nextBreak;
3344
3345
0
            currentWidth += this->width(nextBreak, pos - nextBreak);
3346
0
        } while (nextBreak > from
3347
0
                 && currentWidth < availableWidth);
3348
3349
0
        if (prevCharJoins(layoutData->string, pos))
3350
0
            ellipsisText.append(ZWJ);
3351
3352
0
        return stringMidRetainingBidiCC(layoutData->string,
3353
0
                                        ellipsisText, QString(),
3354
0
                                        from, to,
3355
0
                                        pos, to - pos);
3356
0
    } else if (mode == Qt::ElideMiddle) {
3357
0
        QFixed leftWidth;
3358
0
        QFixed rightWidth;
3359
3360
0
        int leftPos = from;
3361
0
        int nextLeftBreak = from;
3362
3363
0
        int rightPos = to;
3364
0
        int nextRightBreak = to;
3365
3366
0
        do {
3367
0
            leftPos = nextLeftBreak;
3368
0
            rightPos = nextRightBreak;
3369
3370
0
            ++nextLeftBreak;
3371
0
            while (nextLeftBreak < layoutData->string.size() && !attributes[nextLeftBreak].graphemeBoundary)
3372
0
                ++nextLeftBreak;
3373
3374
0
            --nextRightBreak;
3375
0
            while (nextRightBreak > from && !attributes[nextRightBreak].graphemeBoundary)
3376
0
                --nextRightBreak;
3377
3378
0
            leftWidth += this->width(leftPos, nextLeftBreak - leftPos);
3379
0
            rightWidth += this->width(nextRightBreak, rightPos - nextRightBreak);
3380
0
        } while (nextLeftBreak < to
3381
0
                 && nextRightBreak > from
3382
0
                 && leftWidth + rightWidth < availableWidth);
3383
3384
0
        if (nextCharJoins(layoutData->string, leftPos))
3385
0
            ellipsisText.prepend(ZWJ);
3386
0
        if (prevCharJoins(layoutData->string, rightPos))
3387
0
            ellipsisText.append(ZWJ);
3388
3389
0
        return QStringView{layoutData->string}.mid(from, leftPos - from) + ellipsisText + QStringView{layoutData->string}.mid(rightPos, to - rightPos);
3390
0
    }
3391
3392
0
    return layoutData->string.mid(from, to - from);
3393
0
}
3394
3395
void QTextEngine::setBoundary(int strPos) const
3396
0
{
3397
0
    const int item = findItem(strPos);
3398
0
    if (item < 0)
3399
0
        return;
3400
3401
0
    QScriptItem newItem = layoutData->items.at(item);
3402
0
    if (newItem.position != strPos) {
3403
0
        newItem.position = strPos;
3404
0
        layoutData->items.insert(item + 1, newItem);
3405
0
    }
3406
0
}
3407
3408
QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const
3409
0
{
3410
0
    const QScriptItem &si = layoutData->items.at(item);
3411
3412
0
    QFixed dpiScale = 1;
3413
0
    if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
3414
0
        QPaintDevice *pdev = QTextDocumentPrivate::get(block)->layout()->paintDevice();
3415
0
        if (pdev)
3416
0
            dpiScale = QFixed::fromReal(pdev->logicalDpiY() / qreal(qt_defaultDpiY()));
3417
0
    } else {
3418
0
        dpiScale = QFixed::fromReal(fnt.d->dpi / qreal(qt_defaultDpiY()));
3419
0
    }
3420
3421
0
    QList<QTextOption::Tab> tabArray = option.tabs();
3422
0
    if (!tabArray.isEmpty()) {
3423
0
        if (isRightToLeft()) { // rebase the tabArray positions.
3424
0
            auto isLeftOrRightTab = [](const QTextOption::Tab &tab) {
3425
0
                return tab.type == QTextOption::LeftTab || tab.type == QTextOption::RightTab;
3426
0
            };
3427
0
            const auto cbegin = tabArray.cbegin();
3428
0
            const auto cend = tabArray.cend();
3429
0
            const auto cit = std::find_if(cbegin, cend, isLeftOrRightTab);
3430
0
            if (cit != cend) {
3431
0
                const int index = std::distance(cbegin, cit);
3432
0
                auto iter = tabArray.begin() + index;
3433
0
                const auto end = tabArray.end();
3434
0
                while (iter != end) {
3435
0
                    QTextOption::Tab &tab = *iter;
3436
0
                    if (tab.type == QTextOption::LeftTab)
3437
0
                        tab.type = QTextOption::RightTab;
3438
0
                    else if (tab.type == QTextOption::RightTab)
3439
0
                        tab.type = QTextOption::LeftTab;
3440
0
                    ++iter;
3441
0
                }
3442
0
            }
3443
0
        }
3444
0
        for (const QTextOption::Tab &tabSpec : std::as_const(tabArray)) {
3445
0
            QFixed tab = QFixed::fromReal(tabSpec.position) * dpiScale;
3446
0
            if (tab > x) {  // this is the tab we need.
3447
0
                int tabSectionEnd = layoutData->string.size();
3448
0
                if (tabSpec.type == QTextOption::RightTab || tabSpec.type == QTextOption::CenterTab) {
3449
                    // find next tab to calculate the width required.
3450
0
                    tab = QFixed::fromReal(tabSpec.position);
3451
0
                    for (int i=item + 1; i < layoutData->items.size(); i++) {
3452
0
                        const QScriptItem &item = layoutData->items.at(i);
3453
0
                        if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it.
3454
0
                            tabSectionEnd = item.position;
3455
0
                            break;
3456
0
                        }
3457
0
                    }
3458
0
                }
3459
0
                else if (tabSpec.type == QTextOption::DelimiterTab)
3460
                    // find delimiter character to calculate the width required
3461
0
                    tabSectionEnd = qMax(si.position, layoutData->string.indexOf(tabSpec.delimiter, si.position) + 1);
3462
3463
0
                if (tabSectionEnd > si.position) {
3464
0
                    QFixed length;
3465
                    // Calculate the length of text between this tab and the tabSectionEnd
3466
0
                    for (int i=item; i < layoutData->items.size(); i++) {
3467
0
                        const QScriptItem &item = layoutData->items.at(i);
3468
0
                        if (item.position > tabSectionEnd || item.position <= si.position)
3469
0
                            continue;
3470
0
                        shape(i); // first, lets make sure relevant text is already shaped
3471
0
                        if (item.analysis.flags == QScriptAnalysis::Object) {
3472
0
                            length += item.width;
3473
0
                            continue;
3474
0
                        }
3475
0
                        QGlyphLayout glyphs = this->shapedGlyphs(&item);
3476
0
                        const int end = qMin(item.position + item.num_glyphs, tabSectionEnd) - item.position;
3477
0
                        for (int i=0; i < end; i++)
3478
0
                            length += glyphs.advances[i] * !glyphs.attributes[i].dontPrint;
3479
0
                        if (end + item.position == tabSectionEnd && tabSpec.type == QTextOption::DelimiterTab) // remove half of matching char
3480
0
                            length -= glyphs.advances[end] / 2 * !glyphs.attributes[end].dontPrint;
3481
0
                    }
3482
3483
0
                    switch (tabSpec.type) {
3484
0
                    case QTextOption::CenterTab:
3485
0
                        length /= 2;
3486
0
                        Q_FALLTHROUGH();
3487
0
                    case QTextOption::DelimiterTab:
3488
0
                    case QTextOption::RightTab:
3489
0
                        tab = QFixed::fromReal(tabSpec.position) * dpiScale - length;
3490
0
                        if (tab < x) // default to tab taking no space
3491
0
                            return QFixed();
3492
0
                        break;
3493
0
                    case QTextOption::LeftTab:
3494
0
                        break;
3495
0
                    }
3496
0
                }
3497
0
                return tab - x;
3498
0
            }
3499
0
        }
3500
0
    }
3501
0
    QFixed tab = QFixed::fromReal(option.tabStopDistance());
3502
0
    if (tab <= 0)
3503
0
        tab = 80; // default
3504
0
    tab *= dpiScale;
3505
0
    QFixed nextTabPos = ((x / tab).truncate() + 1) * tab;
3506
0
    QFixed tabWidth = nextTabPos - x;
3507
3508
0
    return tabWidth;
3509
0
}
3510
3511
namespace {
3512
class FormatRangeComparatorByStart {
3513
    const QList<QTextLayout::FormatRange> &list;
3514
public:
3515
0
    FormatRangeComparatorByStart(const QList<QTextLayout::FormatRange> &list) : list(list) { }
3516
0
    bool operator()(int a, int b) {
3517
0
        return list.at(a).start < list.at(b).start;
3518
0
    }
3519
};
3520
class FormatRangeComparatorByEnd {
3521
    const QList<QTextLayout::FormatRange> &list;
3522
public:
3523
0
    FormatRangeComparatorByEnd(const QList<QTextLayout::FormatRange> &list) : list(list) { }
3524
0
    bool operator()(int a, int b) {
3525
0
        return list.at(a).start + list.at(a).length < list.at(b).start + list.at(b).length;
3526
0
    }
3527
};
3528
}
3529
3530
void QTextEngine::resolveFormats() const
3531
0
{
3532
0
    if (!specialData || specialData->formats.isEmpty())
3533
0
        return;
3534
0
    Q_ASSERT(specialData->resolvedFormats.isEmpty());
3535
3536
0
    QTextFormatCollection *collection = formatCollection();
3537
3538
0
    QList<QTextCharFormat> resolvedFormats(layoutData->items.size());
3539
3540
0
    QVarLengthArray<int, 64> formatsSortedByStart;
3541
0
    formatsSortedByStart.reserve(specialData->formats.size());
3542
0
    for (int i = 0; i < specialData->formats.size(); ++i) {
3543
0
        if (specialData->formats.at(i).length >= 0)
3544
0
            formatsSortedByStart.append(i);
3545
0
    }
3546
0
    QVarLengthArray<int, 64> formatsSortedByEnd = formatsSortedByStart;
3547
0
    std::sort(formatsSortedByStart.begin(), formatsSortedByStart.end(),
3548
0
              FormatRangeComparatorByStart(specialData->formats));
3549
0
    std::sort(formatsSortedByEnd.begin(), formatsSortedByEnd.end(),
3550
0
              FormatRangeComparatorByEnd(specialData->formats));
3551
3552
0
    QVarLengthArray<int, 16>  currentFormats;
3553
0
    const int *startIt = formatsSortedByStart.constBegin();
3554
0
    const int *endIt = formatsSortedByEnd.constBegin();
3555
3556
0
    for (int i = 0; i < layoutData->items.size(); ++i) {
3557
0
        const QScriptItem *si = &layoutData->items.at(i);
3558
0
        int end = si->position + length(si);
3559
3560
0
        while (startIt != formatsSortedByStart.constEnd() &&
3561
0
            specialData->formats.at(*startIt).start <= si->position) {
3562
0
            currentFormats.insert(std::upper_bound(currentFormats.begin(), currentFormats.end(), *startIt),
3563
0
                                  *startIt);
3564
0
            ++startIt;
3565
0
        }
3566
0
        while (endIt != formatsSortedByEnd.constEnd() &&
3567
0
            specialData->formats.at(*endIt).start + specialData->formats.at(*endIt).length < end) {
3568
0
            int *currentFormatIterator = std::lower_bound(currentFormats.begin(), currentFormats.end(), *endIt);
3569
0
            if (*endIt < *currentFormatIterator)
3570
0
                currentFormatIterator = currentFormats.end();
3571
0
            currentFormats.remove(currentFormatIterator - currentFormats.begin());
3572
0
            ++endIt;
3573
0
        }
3574
3575
0
        QTextCharFormat &format = resolvedFormats[i];
3576
0
        if (QTextDocumentPrivate::get(block) != nullptr) {
3577
            // when we have a QTextDocumentPrivate, formatIndex might still return a valid index based
3578
            // on the preeditPosition. for all other cases, we cleared the resolved format indices
3579
0
            format = collection->charFormat(formatIndex(si));
3580
0
        }
3581
0
        if (!currentFormats.isEmpty()) {
3582
0
            for (int cur : currentFormats) {
3583
0
                const QTextLayout::FormatRange &range = specialData->formats.at(cur);
3584
0
                Q_ASSERT(range.start <= si->position && range.start + range.length >= end);
3585
0
                format.merge(range.format);
3586
0
            }
3587
0
            format = collection->charFormat(collection->indexForFormat(format)); // get shared copy
3588
0
        }
3589
0
    }
3590
3591
0
    specialData->resolvedFormats = resolvedFormats;
3592
0
}
3593
3594
QFixed QTextEngine::leadingSpaceWidth(const QScriptLine &line)
3595
0
{
3596
0
    if (!line.hasTrailingSpaces
3597
0
        || (option.flags() & QTextOption::IncludeTrailingSpaces)
3598
0
        || !isRightToLeft())
3599
0
        return QFixed();
3600
3601
0
    return width(line.from + line.length, line.trailingSpaces);
3602
0
}
3603
3604
QFixed QTextEngine::alignLine(const QScriptLine &line)
3605
0
{
3606
0
    QFixed x = 0;
3607
0
    justify(line);
3608
    // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
3609
0
    if (!line.justified && line.width != QFIXED_MAX) {
3610
0
        int align = option.alignment();
3611
0
        if (align & Qt::AlignJustify && isRightToLeft())
3612
0
            align = Qt::AlignRight;
3613
0
        if (align & Qt::AlignRight)
3614
0
            x = line.width - (line.textAdvance);
3615
0
        else if (align & Qt::AlignHCenter)
3616
0
            x = (line.width - line.textAdvance)/2;
3617
0
    }
3618
0
    return x;
3619
0
}
3620
3621
QFixed QTextEngine::offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos)
3622
0
{
3623
0
    unsigned short *logClusters = this->logClusters(si);
3624
0
    const QGlyphLayout &glyphs = shapedGlyphs(si);
3625
3626
0
    int offsetInCluster = 0;
3627
0
    for (int i = pos - 1; i >= 0; i--) {
3628
0
        if (logClusters[i] == glyph_pos)
3629
0
            offsetInCluster++;
3630
0
        else
3631
0
            break;
3632
0
    }
3633
3634
    // in the case that the offset is inside a (multi-character) glyph,
3635
    // interpolate the position.
3636
0
    if (offsetInCluster > 0) {
3637
0
        int clusterLength = 0;
3638
0
        for (int i = pos - offsetInCluster; i < max; i++) {
3639
0
            if (logClusters[i] == glyph_pos)
3640
0
                clusterLength++;
3641
0
            else
3642
0
                break;
3643
0
        }
3644
0
        if (clusterLength)
3645
0
            return glyphs.advances[glyph_pos] * offsetInCluster / clusterLength;
3646
0
    }
3647
3648
0
    return 0;
3649
0
}
3650
3651
// Scan in logClusters[from..to-1] for glyph_pos
3652
int QTextEngine::getClusterLength(unsigned short *logClusters,
3653
                                  const QCharAttributes *attributes,
3654
                                  int from, int to, int glyph_pos, int *start)
3655
0
{
3656
0
    int clusterLength = 0;
3657
0
    for (int i = from; i < to; i++) {
3658
0
        if (logClusters[i] == glyph_pos && attributes[i].graphemeBoundary) {
3659
0
            if (*start < 0)
3660
0
                *start = i;
3661
0
            clusterLength++;
3662
0
        }
3663
0
        else if (clusterLength)
3664
0
            break;
3665
0
    }
3666
0
    return clusterLength;
3667
0
}
3668
3669
int QTextEngine::positionInLigature(const QScriptItem *si, int end,
3670
                                    QFixed x, QFixed edge, int glyph_pos,
3671
                                    bool cursorOnCharacter)
3672
0
{
3673
0
    unsigned short *logClusters = this->logClusters(si);
3674
0
    int clusterStart = -1;
3675
0
    int clusterLength = 0;
3676
3677
0
    if (si->analysis.script != QChar::Script_Common &&
3678
0
        si->analysis.script != QChar::Script_Greek &&
3679
0
        si->analysis.script != QChar::Script_Latin &&
3680
0
        si->analysis.script != QChar::Script_Hiragana &&
3681
0
        si->analysis.script != QChar::Script_Katakana &&
3682
0
        si->analysis.script != QChar::Script_Bopomofo &&
3683
0
        si->analysis.script != QChar::Script_Han) {
3684
0
        if (glyph_pos == -1)
3685
0
            return si->position + end;
3686
0
        else {
3687
0
            int i;
3688
0
            for (i = 0; i < end; i++)
3689
0
                if (logClusters[i] == glyph_pos)
3690
0
                    break;
3691
0
            return si->position + i;
3692
0
        }
3693
0
    }
3694
3695
0
    if (glyph_pos == -1 && end > 0)
3696
0
        glyph_pos = logClusters[end - 1];
3697
0
    else {
3698
0
        if (x <= edge)
3699
0
            glyph_pos--;
3700
0
    }
3701
3702
0
    const QCharAttributes *attrs = attributes() + si->position;
3703
0
    logClusters = this->logClusters(si);
3704
0
    clusterLength = getClusterLength(logClusters, attrs, 0, end, glyph_pos, &clusterStart);
3705
3706
0
    if (clusterLength) {
3707
0
        const QGlyphLayout &glyphs = shapedGlyphs(si);
3708
0
        QFixed glyphWidth = glyphs.effectiveAdvance(glyph_pos);
3709
        // the approximate width of each individual element of the ligature
3710
0
        QFixed perItemWidth = glyphWidth / clusterLength;
3711
0
        if (perItemWidth <= 0)
3712
0
            return si->position + clusterStart;
3713
0
        QFixed left = x > edge ? edge : edge - glyphWidth;
3714
0
        int n = ((x - left) / perItemWidth).floor().toInt();
3715
0
        QFixed dist = x - left - n * perItemWidth;
3716
0
        int closestItem = dist > (perItemWidth / 2) ? n + 1 : n;
3717
0
        if (cursorOnCharacter && closestItem > 0)
3718
0
            closestItem--;
3719
0
        int pos = clusterStart + closestItem;
3720
        // Jump to the next grapheme boundary
3721
0
        while (pos < end && !attrs[pos].graphemeBoundary)
3722
0
            pos++;
3723
0
        return si->position + pos;
3724
0
    }
3725
0
    return si->position + end;
3726
0
}
3727
3728
int QTextEngine::previousLogicalPosition(int oldPos) const
3729
0
{
3730
0
    const QCharAttributes *attrs = attributes();
3731
0
    int len = block.isValid() ? block.length() - 1
3732
0
                              : layoutData->string.size();
3733
0
    Q_ASSERT(len <= layoutData->string.size());
3734
0
    if (!attrs || oldPos <= 0 || oldPos > len)
3735
0
        return oldPos;
3736
3737
0
    oldPos--;
3738
0
    while (oldPos && !attrs[oldPos].graphemeBoundary)
3739
0
        oldPos--;
3740
0
    return oldPos;
3741
0
}
3742
3743
int QTextEngine::nextLogicalPosition(int oldPos) const
3744
0
{
3745
0
    const QCharAttributes *attrs = attributes();
3746
0
    int len = block.isValid() ? block.length() - 1
3747
0
                              : layoutData->string.size();
3748
0
    Q_ASSERT(len <= layoutData->string.size());
3749
0
    if (!attrs || oldPos < 0 || oldPos >= len)
3750
0
        return oldPos;
3751
3752
0
    oldPos++;
3753
0
    while (oldPos < len && !attrs[oldPos].graphemeBoundary)
3754
0
        oldPos++;
3755
0
    return oldPos;
3756
0
}
3757
3758
int QTextEngine::lineNumberForTextPosition(int pos)
3759
0
{
3760
0
    if (!layoutData)
3761
0
        itemize();
3762
0
    if (pos == layoutData->string.size() && lines.size())
3763
0
        return lines.size() - 1;
3764
0
    for (int i = 0; i < lines.size(); ++i) {
3765
0
        const QScriptLine& line = lines[i];
3766
0
        if (line.from + line.length + line.trailingSpaces > pos)
3767
0
            return i;
3768
0
    }
3769
0
    return -1;
3770
0
}
3771
3772
std::vector<int> QTextEngine::insertionPointsForLine(int lineNum)
3773
0
{
3774
0
    QTextLineItemIterator iterator(this, lineNum);
3775
3776
0
    std::vector<int> insertionPoints;
3777
0
    insertionPoints.reserve(size_t(iterator.line.length));
3778
3779
0
    bool lastLine = lineNum >= lines.size() - 1;
3780
3781
0
    while (!iterator.atEnd()) {
3782
0
        const QScriptItem &si = iterator.next();
3783
3784
0
        int end = iterator.itemEnd;
3785
0
        if (lastLine && iterator.item == iterator.lastItem)
3786
0
            ++end; // the last item in the last line -> insert eol position
3787
0
        if (si.analysis.bidiLevel % 2) {
3788
0
            for (int i = end - 1; i >= iterator.itemStart; --i)
3789
0
                insertionPoints.push_back(i);
3790
0
        } else {
3791
0
            for (int i = iterator.itemStart; i < end; ++i)
3792
0
                insertionPoints.push_back(i);
3793
0
        }
3794
0
    }
3795
0
    return insertionPoints;
3796
0
}
3797
3798
int QTextEngine::endOfLine(int lineNum)
3799
0
{
3800
0
    const auto insertionPoints = insertionPointsForLine(lineNum);
3801
0
    if (insertionPoints.size() > 0)
3802
0
        return insertionPoints.back();
3803
0
    return 0;
3804
0
}
3805
3806
int QTextEngine::beginningOfLine(int lineNum)
3807
0
{
3808
0
    const auto insertionPoints = insertionPointsForLine(lineNum);
3809
0
    if (insertionPoints.size() > 0)
3810
0
        return insertionPoints.front();
3811
0
    return 0;
3812
0
}
3813
3814
int QTextEngine::positionAfterVisualMovement(int pos, QTextCursor::MoveOperation op)
3815
0
{
3816
0
    itemize();
3817
3818
0
    bool moveRight = (op == QTextCursor::Right);
3819
0
    bool alignRight = isRightToLeft();
3820
0
    if (!layoutData->hasBidi)
3821
0
        return moveRight ^ alignRight ? nextLogicalPosition(pos) : previousLogicalPosition(pos);
3822
3823
0
    int lineNum = lineNumberForTextPosition(pos);
3824
0
    if (lineNum < 0)
3825
0
        return pos;
3826
3827
0
    const auto insertionPoints = insertionPointsForLine(lineNum);
3828
0
    for (size_t i = 0, max = insertionPoints.size(); i < max; ++i)
3829
0
        if (pos == insertionPoints[i]) {
3830
0
            if (moveRight) {
3831
0
                if (i + 1 < max)
3832
0
                    return insertionPoints[i + 1];
3833
0
            } else {
3834
0
                if (i > 0)
3835
0
                    return insertionPoints[i - 1];
3836
0
            }
3837
3838
0
            if (moveRight ^ alignRight) {
3839
0
                if (lineNum + 1 < lines.size())
3840
0
                    return alignRight ? endOfLine(lineNum + 1) : beginningOfLine(lineNum + 1);
3841
0
            }
3842
0
            else {
3843
0
                if (lineNum > 0)
3844
0
                    return alignRight ? beginningOfLine(lineNum - 1) : endOfLine(lineNum - 1);
3845
0
            }
3846
3847
0
            break;
3848
0
        }
3849
3850
0
    return pos;
3851
0
}
3852
3853
void QTextEngine::addItemDecoration(QPainter *painter, const QLineF &line, ItemDecorationList *decorationList)
3854
0
{
3855
0
    if (delayDecorations) {
3856
0
        decorationList->append(ItemDecoration(line.x1(), line.x2(), line.y1(), painter->pen()));
3857
0
    } else {
3858
0
        painter->drawLine(line);
3859
0
    }
3860
0
}
3861
3862
void QTextEngine::addUnderline(QPainter *painter, const QLineF &line)
3863
0
{
3864
    // qDebug() << "Adding underline:" << line;
3865
0
    addItemDecoration(painter, line, &underlineList);
3866
0
}
3867
3868
void QTextEngine::addStrikeOut(QPainter *painter, const QLineF &line)
3869
0
{
3870
0
    addItemDecoration(painter, line, &strikeOutList);
3871
0
}
3872
3873
void QTextEngine::addOverline(QPainter *painter, const QLineF &line)
3874
0
{
3875
0
    addItemDecoration(painter, line, &overlineList);
3876
0
}
3877
3878
void QTextEngine::drawItemDecorationList(QPainter *painter, const ItemDecorationList &decorationList)
3879
0
{
3880
    // qDebug() << "Drawing" << decorationList.size() << "decorations";
3881
0
    if (decorationList.isEmpty())
3882
0
        return;
3883
3884
0
    for (const ItemDecoration &decoration : decorationList) {
3885
0
        painter->setPen(decoration.pen);
3886
0
        painter->drawLine(QLineF(decoration.x1, decoration.y, decoration.x2, decoration.y));
3887
0
    }
3888
0
}
3889
3890
void QTextEngine::drawDecorations(QPainter *painter)
3891
0
{
3892
0
    QPen oldPen = painter->pen();
3893
3894
0
    adjustUnderlines();
3895
0
    drawItemDecorationList(painter, underlineList);
3896
0
    drawItemDecorationList(painter, strikeOutList);
3897
0
    drawItemDecorationList(painter, overlineList);
3898
3899
0
    clearDecorations();
3900
3901
0
    painter->setPen(oldPen);
3902
0
}
3903
3904
void QTextEngine::clearDecorations()
3905
0
{
3906
0
    underlineList.clear();
3907
0
    strikeOutList.clear();
3908
0
    overlineList.clear();
3909
0
}
3910
3911
void QTextEngine::adjustUnderlines()
3912
0
{
3913
    // qDebug() << __PRETTY_FUNCTION__ << underlineList.count() << "underlines";
3914
0
    if (underlineList.isEmpty())
3915
0
        return;
3916
3917
0
    ItemDecorationList::iterator start = underlineList.begin();
3918
0
    ItemDecorationList::iterator end   = underlineList.end();
3919
0
    ItemDecorationList::iterator it = start;
3920
0
    qreal underlinePos = start->y;
3921
0
    qreal penWidth = start->pen.widthF();
3922
0
    qreal lastLineEnd = start->x1;
3923
3924
0
    while (it != end) {
3925
0
        if (qFuzzyCompare(lastLineEnd, it->x1)) { // no gap between underlines
3926
0
            underlinePos = qMax(underlinePos, it->y);
3927
0
            penWidth = qMax(penWidth, it->pen.widthF());
3928
0
        } else { // gap between this and the last underline
3929
0
            adjustUnderlines(start, it, underlinePos, penWidth);
3930
0
            start = it;
3931
0
            underlinePos = start->y;
3932
0
            penWidth = start->pen.widthF();
3933
0
        }
3934
0
        lastLineEnd = it->x2;
3935
0
        ++it;
3936
0
    }
3937
3938
0
    adjustUnderlines(start, end, underlinePos, penWidth);
3939
0
}
3940
3941
void QTextEngine::adjustUnderlines(ItemDecorationList::iterator start,
3942
                                   ItemDecorationList::iterator end,
3943
                                   qreal underlinePos, qreal penWidth)
3944
0
{
3945
0
    for (ItemDecorationList::iterator it = start; it != end; ++it) {
3946
0
        it->y = underlinePos;
3947
0
        it->pen.setWidthF(penWidth);
3948
0
    }
3949
0
}
3950
3951
QStackTextEngine::QStackTextEngine(const QString &string, const QFont &f)
3952
0
    : QTextEngine(string, f),
3953
0
      _layoutData(string, _memory, MemSize)
3954
0
{
3955
0
    stackEngine = true;
3956
0
    layoutData = &_layoutData;
3957
0
}
3958
3959
QTextItemInt::QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format)
3960
0
    : charFormat(format),
3961
0
      f(font),
3962
0
      fontEngine(font->d->engineForScript(si.analysis.script))
3963
0
{
3964
0
    Q_ASSERT(fontEngine);
3965
3966
0
    initWithScriptItem(si);
3967
0
}
3968
3969
QTextItemInt::QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars_, int numChars, QFontEngine *fe, const QTextCharFormat &format)
3970
0
    : charFormat(format),
3971
0
      num_chars(numChars),
3972
0
      chars(chars_),
3973
0
      f(font),
3974
0
      glyphs(g),
3975
0
      fontEngine(fe)
3976
0
{
3977
0
}
3978
3979
// Fix up flags and underlineStyle with given info
3980
void QTextItemInt::initWithScriptItem(const QScriptItem &si)
3981
0
{
3982
    // explicitly initialize flags so that initFontAttributes can be called
3983
    // multiple times on the same TextItem
3984
0
    flags = { };
3985
0
    if (si.analysis.bidiLevel %2)
3986
0
        flags |= QTextItem::RightToLeft;
3987
0
    ascent = si.ascent;
3988
0
    descent = si.descent;
3989
3990
0
    if (charFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
3991
0
        underlineStyle = charFormat.underlineStyle();
3992
0
    } else if (charFormat.boolProperty(QTextFormat::FontUnderline)
3993
0
               || f->d->underline) {
3994
0
        underlineStyle = QTextCharFormat::SingleUnderline;
3995
0
    }
3996
3997
    // compat
3998
0
    if (underlineStyle == QTextCharFormat::SingleUnderline)
3999
0
        flags |= QTextItem::Underline;
4000
4001
0
    if (f->d->overline || charFormat.fontOverline())
4002
0
        flags |= QTextItem::Overline;
4003
0
    if (f->d->strikeOut || charFormat.fontStrikeOut())
4004
0
        flags |= QTextItem::StrikeOut;
4005
0
}
4006
4007
QTextItemInt QTextItemInt::midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const
4008
0
{
4009
0
    QTextItemInt ti = *this;
4010
0
    const int end = firstGlyphIndex + numGlyphs;
4011
0
    ti.glyphs = glyphs.mid(firstGlyphIndex, numGlyphs);
4012
0
    ti.fontEngine = fontEngine;
4013
4014
0
    if (logClusters && chars) {
4015
0
        const int logClusterOffset = logClusters[0];
4016
0
        while (logClusters[ti.chars - chars] - logClusterOffset < firstGlyphIndex)
4017
0
            ++ti.chars;
4018
4019
0
        ti.logClusters += (ti.chars - chars);
4020
4021
0
        ti.num_chars = 0;
4022
0
        int char_start = ti.chars - chars;
4023
0
        while (char_start + ti.num_chars < num_chars && ti.logClusters[ti.num_chars] - logClusterOffset < end)
4024
0
            ++ti.num_chars;
4025
0
    }
4026
0
    return ti;
4027
0
}
4028
4029
4030
QTransform qt_true_matrix(qreal w, qreal h, const QTransform &x)
4031
0
{
4032
0
    QRectF rect = x.mapRect(QRectF(0, 0, w, h));
4033
0
    return x * QTransform::fromTranslate(-rect.x(), -rect.y());
4034
0
}
4035
4036
4037
glyph_metrics_t glyph_metrics_t::transformed(const QTransform &matrix) const
4038
0
{
4039
0
    if (matrix.type() < QTransform::TxTranslate)
4040
0
        return *this;
4041
4042
0
    glyph_metrics_t m = *this;
4043
4044
0
    qreal w = width.toReal();
4045
0
    qreal h = height.toReal();
4046
0
    QTransform xform = qt_true_matrix(w, h, matrix);
4047
4048
0
    QRectF rect(0, 0, w, h);
4049
0
    rect = xform.mapRect(rect);
4050
0
    m.width = QFixed::fromReal(rect.width());
4051
0
    m.height = QFixed::fromReal(rect.height());
4052
4053
0
    QLineF l = xform.map(QLineF(x.toReal(), y.toReal(), xoff.toReal(), yoff.toReal()));
4054
4055
0
    m.x = QFixed::fromReal(l.x1());
4056
0
    m.y = QFixed::fromReal(l.y1());
4057
4058
    // The offset is relative to the baseline which is why we use dx/dy of the line
4059
0
    m.xoff = QFixed::fromReal(l.dx());
4060
0
    m.yoff = QFixed::fromReal(l.dy());
4061
4062
0
    return m;
4063
0
}
4064
4065
QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int _lineNum, const QPointF &pos,
4066
                                             const QTextLayout::FormatRange *_selection)
4067
0
    : eng(_eng),
4068
0
      line(eng->lines[_lineNum]),
4069
0
      si(nullptr),
4070
0
      lineNum(_lineNum),
4071
0
      lineEnd(line.from + line.length),
4072
0
      firstItem(eng->findItem(line.from)),
4073
0
      lastItem(eng->findItem(lineEnd - 1, firstItem)),
4074
0
      nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
4075
0
      logicalItem(-1),
4076
0
      item(-1),
4077
0
      visualOrder(nItems),
4078
0
      selection(_selection)
4079
0
{
4080
0
    x = QFixed::fromReal(pos.x());
4081
4082
0
    x += line.x;
4083
4084
0
    x += eng->alignLine(line);
4085
4086
0
    if (nItems > 0) {
4087
0
        QVarLengthArray<uchar> levels(nItems);
4088
0
        for (int i = 0; i < nItems; ++i)
4089
0
            levels[i] = eng->layoutData->items.at(i + firstItem).analysis.bidiLevel;
4090
0
        QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
4091
0
    }
4092
4093
0
    eng->shapeLine(line);
4094
0
}
4095
4096
QScriptItem &QTextLineItemIterator::next()
4097
0
{
4098
0
    x += itemWidth;
4099
4100
0
    ++logicalItem;
4101
0
    item = visualOrder[logicalItem] + firstItem;
4102
0
    itemLength = eng->length(item);
4103
0
    si = &eng->layoutData->items[item];
4104
0
    if (!si->num_glyphs)
4105
0
        eng->shape(item);
4106
4107
0
    itemStart = qMax(line.from, si->position);
4108
0
    itemEnd = qMin(lineEnd, si->position + itemLength);
4109
4110
0
    if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
4111
0
        glyphsStart = 0;
4112
0
        glyphsEnd = 1;
4113
0
        itemWidth = si->width;
4114
0
        return *si;
4115
0
    }
4116
4117
0
    unsigned short *logClusters = eng->logClusters(si);
4118
0
    QGlyphLayout glyphs = eng->shapedGlyphs(si);
4119
4120
0
    glyphsStart = logClusters[itemStart - si->position];
4121
0
    glyphsEnd = (itemEnd == si->position + itemLength) ? si->num_glyphs : logClusters[itemEnd - si->position];
4122
4123
    // show soft-hyphen at line-break
4124
0
    if (si->position + itemLength >= lineEnd
4125
0
        && eng->layoutData->string.at(lineEnd - 1).unicode() == QChar::SoftHyphen)
4126
0
        glyphs.attributes[glyphsEnd - 1].dontPrint = false;
4127
4128
0
    itemWidth = 0;
4129
0
    for (int g = glyphsStart; g < glyphsEnd; ++g)
4130
0
        itemWidth += glyphs.effectiveAdvance(g);
4131
4132
0
    return *si;
4133
0
}
4134
4135
bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
4136
0
{
4137
0
    *selectionX = *selectionWidth = 0;
4138
4139
0
    if (!selection)
4140
0
        return false;
4141
4142
0
    if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
4143
0
        if (si->position >= selection->start + selection->length
4144
0
            || si->position + itemLength <= selection->start)
4145
0
            return false;
4146
4147
0
        *selectionX = x;
4148
0
        *selectionWidth = itemWidth;
4149
0
    } else {
4150
0
        unsigned short *logClusters = eng->logClusters(si);
4151
0
        QGlyphLayout glyphs = eng->shapedGlyphs(si);
4152
4153
0
        int from = qMax(itemStart, selection->start) - si->position;
4154
0
        int to = qMin(itemEnd, selection->start + selection->length) - si->position;
4155
0
        if (from >= to)
4156
0
            return false;
4157
4158
0
        int start_glyph = logClusters[from];
4159
0
        int end_glyph = (to == itemLength) ? si->num_glyphs : logClusters[to];
4160
0
        QFixed soff;
4161
0
        QFixed swidth;
4162
0
        if (si->analysis.bidiLevel %2) {
4163
0
            for (int g = glyphsEnd - 1; g >= end_glyph; --g)
4164
0
                soff += glyphs.effectiveAdvance(g);
4165
0
            for (int g = end_glyph - 1; g >= start_glyph; --g)
4166
0
                swidth += glyphs.effectiveAdvance(g);
4167
0
        } else {
4168
0
            for (int g = glyphsStart; g < start_glyph; ++g)
4169
0
                soff += glyphs.effectiveAdvance(g);
4170
0
            for (int g = start_glyph; g < end_glyph; ++g)
4171
0
                swidth += glyphs.effectiveAdvance(g);
4172
0
        }
4173
4174
        // If the starting character is in the middle of a ligature,
4175
        // selection should only contain the right part of that ligature
4176
        // glyph, so we need to get the width of the left part here and
4177
        // add it to *selectionX
4178
0
        QFixed leftOffsetInLigature = eng->offsetInLigature(si, from, to, start_glyph);
4179
0
        *selectionX = x + soff + leftOffsetInLigature;
4180
0
        *selectionWidth = swidth - leftOffsetInLigature;
4181
        // If the ending character is also part of a ligature, swidth does
4182
        // not contain that part yet, we also need to find out the width of
4183
        // that left part
4184
0
        *selectionWidth += eng->offsetInLigature(si, to, itemLength, end_glyph);
4185
0
    }
4186
0
    return true;
4187
0
}
4188
4189
QT_END_NAMESPACE