Coverage Report

Created: 2026-07-25 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/text/qtextlayout.cpp
Line
Count
Source
1
// Copyright (C) 2016 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 "qtextlayout.h"
6
#include "qtextengine_p.h"
7
8
#include <qthread.h>
9
#include <qfont.h>
10
#include <qmath.h>
11
#include <qpainter.h>
12
#include <qimage.h>
13
#include <qvarlengtharray.h>
14
#include <qtextformat.h>
15
#include <qabstracttextdocumentlayout.h>
16
#include "qtextdocument_p.h"
17
#include "qtextformat_p.h"
18
#include "qpainterpath.h"
19
#include "qglyphrun.h"
20
#include "qglyphrun_p.h"
21
#include "qrawfont.h"
22
#include "qrawfont_p.h"
23
#include <limits.h>
24
25
#include <qdebug.h>
26
27
#include "qfontengine_p.h"
28
#include <private/qpainter_p.h>
29
30
QT_BEGIN_NAMESPACE
31
32
0
#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
33
0
#define SuppressText 0x5012
34
0
#define SuppressBackground 0x513
35
36
/*!
37
    \class QTextLayout::FormatRange
38
    \reentrant
39
40
    \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
41
    for a specified area in the text layout's content.
42
    \inmodule QtGui
43
44
    \sa QTextLayout::setFormats(), QTextLayout::draw()
45
*/
46
47
/*!
48
    \variable QTextLayout::FormatRange::start
49
    Specifies the beginning of the format range within the text layout's text.
50
*/
51
52
/*!
53
    \variable QTextLayout::FormatRange::length
54
    Specifies the number of characters the format range spans.
55
*/
56
57
/*!
58
    \variable QTextLayout::FormatRange::format
59
    Specifies the format to apply.
60
*/
61
62
/*! \fn bool QTextLayout::FormatRange::operator==(const QTextLayout::FormatRange &lhs, const QTextLayout::FormatRange &rhs)
63
64
  Returns true if the \c {start}, \c {length}, and \c {format} fields
65
  in \a lhs and \a rhs contain the same values respectively.
66
 */
67
68
/*! \fn bool QTextLayout::FormatRange::operator!=(const QTextLayout::FormatRange &lhs, const QTextLayout::FormatRange &rhs)
69
70
  Returns true if any of the \c {start}, \c {length}, or \c {format} fields
71
  in \a lhs and \a rhs contain different values respectively.
72
 */
73
74
/*!
75
    \class QTextInlineObject
76
    \reentrant
77
78
    \brief The QTextInlineObject class represents an inline object in
79
    a QAbstractTextDocumentLayout and its implementations.
80
    \inmodule QtGui
81
82
    \ingroup richtext-processing
83
84
    Normally, you do not need to create a QTextInlineObject. It is
85
    used by QAbstractTextDocumentLayout to handle inline objects when
86
    implementing a custom layout.
87
88
    The inline object has various attributes that can be set, for
89
    example using, setWidth(), setAscent(), and setDescent(). The
90
    rectangle it occupies is given by rect(), and its direction by
91
    textDirection(). Its position in the text layout is given by
92
    textPosition(), and its format is given by format().
93
*/
94
95
/*!
96
    \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
97
    \internal
98
99
    Creates a new inline object for the item at position \a i in the
100
    text engine \a e.
101
*/
102
103
/*!
104
    \fn QTextInlineObject::QTextInlineObject()
105
106
    \internal
107
*/
108
109
/*!
110
    \fn bool QTextInlineObject::isValid() const
111
112
    Returns \c true if this inline object is valid; otherwise returns
113
    false.
114
*/
115
116
/*!
117
    Returns the inline object's rectangle.
118
119
    \sa ascent(), descent(), width()
120
*/
121
QRectF QTextInlineObject::rect() const
122
0
{
123
0
    QScriptItem& si = eng->layoutData->items[itm];
124
0
    return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
125
0
}
126
127
/*!
128
    Returns the inline object's width.
129
130
    \sa ascent(), descent(), rect()
131
*/
132
qreal QTextInlineObject::width() const
133
0
{
134
0
    return eng->layoutData->items.at(itm).width.toReal();
135
0
}
136
137
/*!
138
    Returns the inline object's ascent.
139
140
    \sa descent(), width(), rect()
141
*/
142
qreal QTextInlineObject::ascent() const
143
0
{
144
0
    return eng->layoutData->items.at(itm).ascent.toReal();
145
0
}
146
147
/*!
148
    Returns the inline object's descent.
149
150
    \sa ascent(), width(), rect()
151
*/
152
qreal QTextInlineObject::descent() const
153
0
{
154
0
    return eng->layoutData->items.at(itm).descent.toReal();
155
0
}
156
157
/*!
158
    Returns the inline object's total height. This is equal to
159
    ascent() + descent() + 1.
160
161
    \sa ascent(), descent(), width(), rect()
162
*/
163
qreal QTextInlineObject::height() const
164
0
{
165
0
    return eng->layoutData->items.at(itm).height().toReal();
166
0
}
167
168
/*!
169
    Sets the inline object's width to \a w.
170
171
    \sa width(), ascent(), descent(), rect()
172
*/
173
void QTextInlineObject::setWidth(qreal w)
174
0
{
175
0
    eng->layoutData->items[itm].width = QFixed::fromReal(w);
176
0
}
177
178
/*!
179
    Sets the inline object's ascent to \a a.
180
181
    \sa ascent(), setDescent(), width(), rect()
182
*/
183
void QTextInlineObject::setAscent(qreal a)
184
0
{
185
0
    eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
186
0
}
187
188
/*!
189
    Sets the inline object's descent to \a d.
190
191
    \sa descent(), setAscent(), width(), rect()
192
*/
193
void QTextInlineObject::setDescent(qreal d)
194
0
{
195
0
    eng->layoutData->items[itm].descent = QFixed::fromReal(d);
196
0
}
197
198
/*!
199
    The position of the inline object within the text layout.
200
*/
201
int QTextInlineObject::textPosition() const
202
0
{
203
0
    return eng->layoutData->items[itm].position;
204
0
}
205
206
/*!
207
    Returns an integer describing the format of the inline object
208
    within the text layout.
209
*/
210
int QTextInlineObject::formatIndex() const
211
0
{
212
0
    return eng->formatIndex(&eng->layoutData->items[itm]);
213
0
}
214
215
/*!
216
    Returns format of the inline object within the text layout.
217
*/
218
QTextFormat QTextInlineObject::format() const
219
0
{
220
0
    return eng->format(&eng->layoutData->items[itm]);
221
0
}
222
223
/*!
224
    Returns if the object should be laid out right-to-left or left-to-right.
225
*/
226
Qt::LayoutDirection QTextInlineObject::textDirection() const
227
0
{
228
0
    return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
229
0
}
230
231
/*!
232
    \class QTextLayout
233
    \reentrant
234
235
    \brief The QTextLayout class is used to lay out and render text.
236
    \inmodule QtGui
237
238
    \ingroup richtext-processing
239
240
    It offers many features expected from a modern text layout
241
    engine, including Unicode compliant rendering, line breaking and
242
    handling of cursor positioning. It can also produce and render
243
    device independent layout, something that is important for WYSIWYG
244
    applications.
245
246
    The class has a rather low level API and unless you intend to
247
    implement your own text rendering for some specialized widget, you
248
    probably won't need to use it directly.
249
250
    QTextLayout can be used with both plain and rich text.
251
252
    QTextLayout can be used to create a sequence of QTextLine
253
    instances with given widths and can position them independently
254
    on the screen. Once the layout is done, these lines can be drawn
255
    on a paint device.
256
257
    The text to be laid out can be provided in the constructor or set with
258
    setText().
259
260
    The layout can be seen as a sequence of QTextLine objects; use createLine()
261
    to create a QTextLine instance, and lineAt() or lineForTextPosition() to retrieve
262
    created lines.
263
264
    Here is a code snippet that demonstrates the layout phase:
265
    \snippet code/src_gui_text_qtextlayout.cpp 0
266
267
    The text can then be rendered by calling the layout's draw() function:
268
    \snippet code/src_gui_text_qtextlayout.cpp 1
269
270
    It is also possible to draw each line individually, for instance to draw
271
    the last line that fits into a widget elided:
272
    \snippet code/src_gui_text_qtextlayout.cpp elided
273
274
    For a given position in the text you can find a valid cursor position with
275
    isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition().
276
277
    The QTextLayout itself can be positioned with setPosition(); it has a
278
    boundingRect(), and a minimumWidth() and a maximumWidth().
279
280
    \sa QStaticText
281
*/
282
283
/*!
284
    \enum QTextLayout::CursorMode
285
286
    \value SkipCharacters
287
    \value SkipWords
288
*/
289
290
/*!
291
    \enum QTextLayout::GlyphRunRetrievalFlag
292
    \since 6.5
293
294
    GlyphRunRetrievalFlag specifies flags passed to the glyphRuns() functions to determine
295
    which properties of the layout are returned in the QGlyphRun objects. Since each property
296
    will consume memory and may require additional allocations, it is a good practice to only
297
    request the properties you will need to access later.
298
299
    \value RetrieveGlyphIndexes Retrieves the indexes in the font which correspond to the glyphs.
300
    \value RetrieveGlyphPositions Retrieves the relative positions of the glyphs in the layout.
301
    \value RetrieveStringIndexes Retrieves the indexes in the original string that correspond to
302
           each of the glyphs.
303
    \value RetrieveString Retrieves the original source string from the layout.
304
    \value RetrieveAll Retrieves all available properties of the layout.
305
    \omitvalue DefaultRetrievalFlags
306
307
    \sa glyphRuns(), QTextLine::glyphRuns()
308
*/
309
310
/*!
311
    \fn QTextEngine *QTextLayout::engine() const
312
    \internal
313
314
    Returns the text engine used to render the text layout.
315
*/
316
317
/*!
318
    Constructs an empty text layout.
319
320
    \sa setText()
321
*/
322
QTextLayout::QTextLayout()
323
0
{ d = new QTextEngine(); }
324
325
/*!
326
    Constructs a text layout to lay out the given \a text.
327
*/
328
QTextLayout::QTextLayout(const QString& text)
329
0
{
330
0
    d = new QTextEngine();
331
0
    d->text = text;
332
0
}
333
334
/*!
335
    \since 5.13
336
    \fn QTextLayout::QTextLayout(const QString &text, const QFont &font, const QPaintDevice *paintdevice)
337
    Constructs a text layout to lay out the given \a text with the specified
338
    \a font.
339
340
    All the metric and layout calculations will be done in terms of
341
    the paint device, \a paintdevice. If \a paintdevice is \nullptr the
342
    calculations will be done in screen metrics.
343
*/
344
345
QTextLayout::QTextLayout(const QString &text, const QFont &font, const QPaintDevice *paintdevice)
346
0
{
347
0
    const QFont f(paintdevice ? QFont(font, paintdevice) : font);
348
0
    d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f);
349
0
}
350
351
/*!
352
    \internal
353
    Constructs a text layout to lay out the given \a block.
354
*/
355
QTextLayout::QTextLayout(const QTextBlock &block)
356
0
{
357
0
    d = new QTextEngine();
358
0
    d->block = block;
359
0
}
360
361
/*!
362
    Destructs the layout.
363
*/
364
QTextLayout::~QTextLayout()
365
0
{
366
0
    if (!d->stackEngine)
367
0
        delete d;
368
0
}
369
370
#ifndef QT_NO_RAWFONT
371
/*!
372
    \internal
373
    Sets a raw font, to be used with QTextLayout::glyphRuns.
374
    Note that this only supports the needs of WebKit.
375
    Use of this function with e.g. QTextLayout::draw will result
376
    in undefined behaviour.
377
*/
378
void QTextLayout::setRawFont(const QRawFont &rawFont)
379
0
{
380
0
    d->rawFont = rawFont;
381
0
    d->useRawFont = true;
382
0
    d->resetFontEngineCache();
383
0
}
384
#endif
385
386
/*!
387
    Sets the layout's font to the given \a font. The layout is
388
    invalidated and must be laid out again.
389
390
    \sa font()
391
*/
392
void QTextLayout::setFont(const QFont &font)
393
0
{
394
0
    d->fnt = font;
395
0
#ifndef QT_NO_RAWFONT
396
0
    d->useRawFont = false;
397
0
#endif
398
0
    d->resetFontEngineCache();
399
0
}
400
401
/*!
402
    Returns the current font that is used for the layout, or a default
403
    font if none is set.
404
405
    \sa setFont()
406
*/
407
QFont QTextLayout::font() const
408
0
{
409
0
    return d->font();
410
0
}
411
412
/*!
413
    Sets the layout's text to the given \a string. The layout is
414
    invalidated and must be laid out again.
415
416
    Notice that when using this QTextLayout as part of a QTextDocument this
417
    method will have no effect.
418
419
    \sa text()
420
*/
421
void QTextLayout::setText(const QString& string)
422
0
{
423
0
    d->invalidate();
424
0
    d->clearLineData();
425
0
    d->text = string;
426
0
}
427
428
/*!
429
    Returns the layout's text.
430
431
    \sa setText()
432
*/
433
QString QTextLayout::text() const
434
0
{
435
0
    return d->text;
436
0
}
437
438
/*!
439
    Sets the text option structure that controls the layout process to the
440
    given \a option.
441
442
    \sa textOption()
443
*/
444
void QTextLayout::setTextOption(const QTextOption &option)
445
0
{
446
0
    d->option = option;
447
0
}
448
449
/*!
450
    Returns the current text option used to control the layout process.
451
452
    \sa setTextOption()
453
*/
454
const QTextOption &QTextLayout::textOption() const
455
0
{
456
0
    return d->option;
457
0
}
458
459
/*!
460
    Sets the \a position and \a text of the area in the layout that is
461
    processed before editing occurs. The layout is
462
    invalidated and must be laid out again.
463
464
    \sa preeditAreaPosition(), preeditAreaText()
465
*/
466
void QTextLayout::setPreeditArea(int position, const QString &text)
467
0
{
468
0
    if (d->preeditAreaPosition() == position && d->preeditAreaText() == text)
469
0
        return;
470
0
    d->setPreeditArea(position, text);
471
472
0
    if (QTextDocumentPrivate::get(d->block) != nullptr)
473
0
        QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), d->block.length());
474
0
}
475
476
/*!
477
    Returns the position of the area in the text layout that will be
478
    processed before editing occurs.
479
480
    \sa preeditAreaText()
481
*/
482
int QTextLayout::preeditAreaPosition() const
483
0
{
484
0
    return d->preeditAreaPosition();
485
0
}
486
487
/*!
488
    Returns the text that is inserted in the layout before editing occurs.
489
490
    \sa preeditAreaPosition()
491
*/
492
QString QTextLayout::preeditAreaText() const
493
0
{
494
0
    return d->preeditAreaText();
495
0
}
496
497
/*!
498
    \since 5.6
499
500
    Sets the additional formats supported by the text layout to \a formats.
501
    The formats are applied with preedit area text in place.
502
503
    \sa formats(), clearFormats()
504
*/
505
void QTextLayout::setFormats(const QList<FormatRange> &formats)
506
0
{
507
0
    d->setFormats(formats);
508
509
0
    if (QTextDocumentPrivate::get(d->block) != nullptr)
510
0
        QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), d->block.length());
511
0
}
512
513
/*!
514
    \since 5.6
515
516
    Returns the list of additional formats supported by the text layout.
517
518
    \sa setFormats(), clearFormats()
519
*/
520
QList<QTextLayout::FormatRange> QTextLayout::formats() const
521
0
{
522
0
    return d->formats();
523
0
}
524
525
/*!
526
    \since 5.6
527
528
    Clears the list of additional formats supported by the text layout.
529
530
    \sa formats(), setFormats()
531
*/
532
void QTextLayout::clearFormats()
533
0
{
534
0
    setFormats(QList<FormatRange>());
535
0
}
536
537
/*!
538
    Enables caching of the complete layout information if \a enable is
539
    true; otherwise disables layout caching. Usually
540
    QTextLayout throws most of the layouting information away after a
541
    call to endLayout() to reduce memory consumption. If you however
542
    want to draw the laid out text directly afterwards enabling caching
543
    might speed up drawing significantly.
544
545
    \sa cacheEnabled()
546
*/
547
void QTextLayout::setCacheEnabled(bool enable)
548
0
{
549
0
    d->cacheGlyphs = enable;
550
0
}
551
552
/*!
553
    Returns \c true if the complete layout information is cached; otherwise
554
    returns \c false.
555
556
    \sa setCacheEnabled()
557
*/
558
bool QTextLayout::cacheEnabled() const
559
0
{
560
0
    return d->cacheGlyphs;
561
0
}
562
563
/*!
564
    Sets the visual cursor movement style to the given \a style. If the
565
    QTextLayout is backed by a document, you can ignore this and use the option
566
    in QTextDocument, this option is for widgets like QLineEdit or custom
567
    widgets without a QTextDocument. Default value is Qt::LogicalMoveStyle.
568
569
    \sa cursorMoveStyle()
570
*/
571
void QTextLayout::setCursorMoveStyle(Qt::CursorMoveStyle style)
572
0
{
573
0
    d->visualMovement = style == Qt::VisualMoveStyle;
574
0
}
575
576
/*!
577
    The cursor movement style of this QTextLayout. The default is
578
    Qt::LogicalMoveStyle.
579
580
    \sa setCursorMoveStyle()
581
*/
582
Qt::CursorMoveStyle QTextLayout::cursorMoveStyle() const
583
0
{
584
0
    return d->visualMovement ? Qt::VisualMoveStyle : Qt::LogicalMoveStyle;
585
0
}
586
587
/*!
588
    Begins the layout process.
589
590
    \warning This will invalidate the layout, so all existing QTextLine objects
591
    that refer to the previous contents should now be discarded.
592
593
    \sa endLayout()
594
*/
595
void QTextLayout::beginLayout()
596
0
{
597
0
#ifndef QT_NO_DEBUG
598
0
    if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) {
599
0
        qWarning("QTextLayout::beginLayout: Called while already doing layout");
600
0
        return;
601
0
    }
602
0
#endif
603
0
    d->invalidate();
604
0
    d->clearLineData();
605
0
    d->itemize();
606
0
    d->layoutData->layoutState = QTextEngine::InLayout;
607
0
}
608
609
/*!
610
    Ends the layout process.
611
612
    \sa beginLayout()
613
*/
614
void QTextLayout::endLayout()
615
0
{
616
0
#ifndef QT_NO_DEBUG
617
0
    if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
618
0
        qWarning("QTextLayout::endLayout: Called without beginLayout()");
619
0
        return;
620
0
    }
621
0
#endif
622
0
    int l = d->lines.size();
623
0
    if (l && d->lines.at(l-1).length < 0) {
624
0
        QTextLine(l-1, d).setNumColumns(INT_MAX);
625
0
    }
626
0
    d->layoutData->layoutState = QTextEngine::LayoutEmpty;
627
0
    if (!d->cacheGlyphs)
628
0
        d->freeMemory();
629
0
}
630
631
/*!
632
    \since 4.4
633
634
    Clears the line information in the layout. After having called
635
    this function, lineCount() returns 0.
636
637
    \warning This will invalidate the layout, so all existing QTextLine objects
638
    that refer to the previous contents should now be discarded.
639
*/
640
void QTextLayout::clearLayout()
641
0
{
642
0
    d->clearLineData();
643
0
}
644
645
/*!
646
    Returns the next valid cursor position after \a oldPos that
647
    respects the given cursor \a mode.
648
    Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
649
650
    \sa isValidCursorPosition(), previousCursorPosition()
651
*/
652
int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
653
0
{
654
0
    const QCharAttributes *attributes = d->attributes();
655
0
    int len = d->block.isValid() ? d->block.length() - 1
656
0
                                 : d->layoutData->string.size();
657
0
    Q_ASSERT(len <= d->layoutData->string.size());
658
0
    if (!attributes || oldPos < 0 || oldPos >= len)
659
0
        return oldPos;
660
661
0
    if (mode == SkipCharacters) {
662
0
        oldPos++;
663
0
        while (oldPos < len && !attributes[oldPos].graphemeBoundary)
664
0
            oldPos++;
665
0
    } else {
666
0
        if (oldPos < len && d->atWordSeparator(oldPos)) {
667
0
            oldPos++;
668
0
            while (oldPos < len && d->atWordSeparator(oldPos))
669
0
                oldPos++;
670
0
        } else {
671
0
            while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(oldPos))
672
0
                oldPos++;
673
0
        }
674
0
        while (oldPos < len && attributes[oldPos].whiteSpace)
675
0
            oldPos++;
676
0
    }
677
678
0
    return oldPos;
679
0
}
680
681
/*!
682
    Returns the first valid cursor position before \a oldPos that
683
    respects the given cursor \a mode.
684
    Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
685
686
    \sa isValidCursorPosition(), nextCursorPosition()
687
*/
688
int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
689
0
{
690
0
    const QCharAttributes *attributes = d->attributes();
691
0
    int len = d->block.isValid() ? d->block.length() - 1
692
0
                                 : d->layoutData->string.size();
693
0
    Q_ASSERT(len <= d->layoutData->string.size());
694
0
    if (!attributes || oldPos <= 0 || oldPos > len)
695
0
        return oldPos;
696
697
0
    if (mode == SkipCharacters) {
698
0
        oldPos--;
699
0
        while (oldPos && !attributes[oldPos].graphemeBoundary)
700
0
            oldPos--;
701
0
    } else {
702
0
        while (oldPos > 0 && attributes[oldPos - 1].whiteSpace)
703
0
            oldPos--;
704
705
0
        if (oldPos && d->atWordSeparator(oldPos-1)) {
706
0
            oldPos--;
707
0
            while (oldPos && d->atWordSeparator(oldPos-1))
708
0
                oldPos--;
709
0
        } else {
710
0
            while (oldPos > 0 && !attributes[oldPos - 1].whiteSpace && !d->atWordSeparator(oldPos-1))
711
0
                oldPos--;
712
0
        }
713
0
    }
714
715
0
    return oldPos;
716
0
}
717
718
/*!
719
    Returns the cursor position to the right of \a oldPos, next to it.
720
    It's dependent on the visual position of characters, after bi-directional
721
    reordering.
722
723
    \sa leftCursorPosition(), nextCursorPosition()
724
*/
725
int QTextLayout::rightCursorPosition(int oldPos) const
726
0
{
727
0
    int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right);
728
//    qDebug("%d -> %d", oldPos, newPos);
729
0
    return newPos;
730
0
}
731
732
/*!
733
    Returns the cursor position to the left of \a oldPos, next to it.
734
    It's dependent on the visual position of characters, after bi-directional
735
    reordering.
736
737
    \sa rightCursorPosition(), previousCursorPosition()
738
*/
739
int QTextLayout::leftCursorPosition(int oldPos) const
740
0
{
741
0
    int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left);
742
//    qDebug("%d -> %d", oldPos, newPos);
743
0
    return newPos;
744
0
}
745
746
/*!
747
    Returns \c true if position \a pos is a valid cursor position.
748
749
    In a Unicode context some positions in the text are not valid
750
    cursor positions, because the position is inside a Unicode
751
    surrogate or a grapheme cluster.
752
753
    A grapheme cluster is a sequence of two or more Unicode characters
754
    that form one indivisible entity on the screen. For example the
755
    latin character `\unicode{0xC4}' can be represented in Unicode by two
756
    characters, `A' (0x41), and the combining diaeresis (0x308). A text
757
    cursor can only validly be positioned before or after these two
758
    characters, never between them since that wouldn't make sense. In
759
    indic languages every syllable forms a grapheme cluster.
760
*/
761
bool QTextLayout::isValidCursorPosition(int pos) const
762
0
{
763
0
    const QCharAttributes *attributes = d->attributes();
764
0
    if (!attributes || pos < 0 || pos > (int)d->layoutData->string.size())
765
0
        return false;
766
0
    return attributes[pos].graphemeBoundary;
767
0
}
768
769
/*!
770
    Returns a new text line to be laid out if there is text to be
771
    inserted into the layout; otherwise returns an invalid text line.
772
773
    The text layout creates a new line object that starts after the
774
    last line in the layout, or at the beginning if the layout is empty.
775
    The layout maintains an internal cursor, and each line is filled
776
    with text from the cursor position onwards when the
777
    QTextLine::setLineWidth() function is called.
778
779
    Once QTextLine::setLineWidth() is called, a new line can be created and
780
    filled with text. Repeating this process will lay out the whole block
781
    of text contained in the QTextLayout. If there is no text left to be
782
    inserted into the layout, the QTextLine returned will not be valid
783
    (isValid() will return false).
784
*/
785
QTextLine QTextLayout::createLine()
786
0
{
787
0
#ifndef QT_NO_DEBUG
788
0
    if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
789
0
        qWarning("QTextLayout::createLine: Called without layouting");
790
0
        return QTextLine();
791
0
    }
792
0
#endif
793
0
    if (d->layoutData->layoutState == QTextEngine::LayoutFailed)
794
0
        return QTextLine();
795
796
0
    int l = d->lines.size();
797
0
    if (l && d->lines.at(l-1).length < 0) {
798
0
        QTextLine(l-1, d).setNumColumns(INT_MAX);
799
0
        if (d->maxWidth > QFIXED_MAX / 2) {
800
0
            qWarning("QTextLayout: text too long, truncated.");
801
0
            return QTextLine();
802
0
        }
803
0
    }
804
0
    int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length + d->lines.at(l-1).trailingSpaces : 0;
805
0
    int strlen = d->layoutData->string.size();
806
0
    if (l && from >= strlen) {
807
0
        if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
808
0
            return QTextLine();
809
0
    }
810
811
0
    QScriptLine line;
812
0
    line.from = from;
813
0
    line.length = -1;
814
0
    line.justified = false;
815
0
    line.gridfitted = false;
816
817
0
    d->lines.append(line);
818
0
    return QTextLine(l, d);
819
0
}
820
821
/*!
822
    Returns the number of lines in this text layout.
823
824
    \sa lineAt()
825
*/
826
int QTextLayout::lineCount() const
827
0
{
828
0
    return d->lines.size();
829
0
}
830
831
/*!
832
    Returns the \a{i}-th line of text in this text layout.
833
834
    \sa lineCount(), lineForTextPosition()
835
*/
836
QTextLine QTextLayout::lineAt(int i) const
837
0
{
838
0
    return i < lineCount() ? QTextLine(i, d) : QTextLine();
839
0
}
840
841
/*!
842
    Returns the line that contains the cursor position specified by \a pos.
843
844
    \sa isValidCursorPosition(), lineAt()
845
*/
846
QTextLine QTextLayout::lineForTextPosition(int pos) const
847
0
{
848
0
    int lineNum = d->lineNumberForTextPosition(pos);
849
0
    return lineNum >= 0 ? lineAt(lineNum) : QTextLine();
850
0
}
851
852
/*!
853
    \since 4.2
854
855
    The global position of the layout. This is independent of the
856
    bounding rectangle and of the layout process.
857
858
    \sa setPosition()
859
*/
860
QPointF QTextLayout::position() const
861
0
{
862
0
    return d->position;
863
0
}
864
865
/*!
866
    Moves the text layout to point \a p.
867
868
    \sa position()
869
*/
870
void QTextLayout::setPosition(const QPointF &p)
871
0
{
872
0
    d->position = p;
873
0
}
874
875
/*!
876
    The smallest rectangle that contains all the lines in the layout.
877
*/
878
QRectF QTextLayout::boundingRect() const
879
0
{
880
0
    if (d->lines.isEmpty())
881
0
        return QRectF();
882
883
0
    QFixed xmax, ymax;
884
0
    QFixed xmin = d->lines.at(0).x;
885
0
    QFixed ymin = d->lines.at(0).y;
886
887
0
    for (int i = 0; i < d->lines.size(); ++i) {
888
0
        const QScriptLine &si = d->lines.at(i);
889
0
        xmin = qMin(xmin, si.x);
890
0
        ymin = qMin(ymin, si.y);
891
0
        QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
892
0
        xmax = qMax(xmax, si.x+lineWidth);
893
        // ### shouldn't the ascent be used in ymin???
894
0
        ymax = qMax(ymax, si.y+si.height().ceil());
895
0
    }
896
0
    return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
897
0
}
898
899
/*!
900
    The minimum width the layout needs. This is the width of the
901
    layout's smallest non-breakable substring.
902
903
    \warning This function only returns a valid value after the layout
904
    has been done.
905
906
    \sa maximumWidth()
907
*/
908
qreal QTextLayout::minimumWidth() const
909
0
{
910
0
    return d->minWidth.toReal();
911
0
}
912
913
/*!
914
    The maximum width the layout could expand to; this is essentially
915
    the width of the entire text.
916
917
    \warning This function only returns a valid value after the layout
918
    has been done.
919
920
    \sa minimumWidth()
921
*/
922
qreal QTextLayout::maximumWidth() const
923
0
{
924
0
    return d->maxWidth.toReal();
925
0
}
926
927
928
/*!
929
    \internal
930
*/
931
void QTextLayout::setFlags(int flags)
932
0
{
933
0
    if (flags & Qt::TextJustificationForced) {
934
0
        d->option.setAlignment(Qt::AlignJustify);
935
0
        d->forceJustification = true;
936
0
    }
937
938
0
    if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
939
0
        d->ignoreBidi = true;
940
0
        d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
941
0
    }
942
0
}
943
944
static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
945
                                     QPainterPath *region, const QRectF &boundingRect)
946
0
{
947
0
    const QScriptLine &line = eng->lines[lineNumber];
948
949
0
    QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
950
951
952
953
0
    const qreal selectionY = pos.y() + line.y.toReal();
954
0
    const qreal lineHeight = line.height().toReal();
955
956
0
    QFixed lastSelectionX = iterator.x;
957
0
    QFixed lastSelectionWidth;
958
959
0
    while (!iterator.atEnd()) {
960
0
        iterator.next();
961
962
0
        QFixed selectionX, selectionWidth;
963
0
        if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
964
0
            if (selectionX == lastSelectionX + lastSelectionWidth) {
965
0
                lastSelectionWidth += selectionWidth;
966
0
                continue;
967
0
            }
968
969
0
            if (lastSelectionWidth > 0) {
970
0
                const QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight);
971
0
                region->addRect(rect.toAlignedRect());
972
0
            }
973
974
0
            lastSelectionX = selectionX;
975
0
            lastSelectionWidth = selectionWidth;
976
0
        }
977
0
    }
978
0
    if (lastSelectionWidth > 0) {
979
0
        const QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight);
980
0
        region->addRect(rect.toAlignedRect());
981
0
    }
982
0
}
983
984
static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
985
0
{
986
0
    return clip.isValid() ? (rect & clip) : rect;
987
0
}
988
989
990
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
991
/*!
992
    \overload
993
    Returns the glyph indexes and positions for all glyphs corresponding to the \a length characters
994
    starting at the position \a from in this QTextLayout. This is an expensive function, and should
995
    not be called in a time sensitive context.
996
997
    If \a from is less than zero, then the glyph run will begin at the first character in the
998
    layout. If \a length is less than zero, it will span the entire string from the start position.
999
1000
    \note This is equivalent to calling
1001
    glyphRuns(from,
1002
              length,
1003
              QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
1004
                QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
1005
1006
    \since 4.8
1007
1008
    \sa draw(), QPainter::drawGlyphRun()
1009
*/
1010
#  if !defined(QT_NO_RAWFONT)
1011
QList<QGlyphRun> QTextLayout::glyphRuns(int from, int length) const
1012
0
{
1013
0
    return glyphRuns(from, length, QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
1014
0
}
1015
#  endif
1016
#endif
1017
1018
/*!
1019
    \overload
1020
    Returns the glyph indexes and positions for all glyphs corresponding to the \a length characters
1021
    starting at the position \a from in this QTextLayout. This is an expensive function, and should
1022
    not be called in a time sensitive context.
1023
1024
    If \a from is less than zero, then the glyph run will begin at the first character in the
1025
    layout. If \a length is less than zero, it will span the entire string from the start position.
1026
1027
    The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
1028
    layout. To minimize allocations and memory consumption, this should be set to include only the
1029
    properties that you need to access later.
1030
1031
    \since 6.5
1032
    \sa draw(), QPainter::drawGlyphRun()
1033
*/
1034
#if !defined(QT_NO_RAWFONT)
1035
QList<QGlyphRun> QTextLayout::glyphRuns(int from,
1036
                                        int length,
1037
                                        QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
1038
0
{
1039
0
    if (from < 0)
1040
0
        from = 0;
1041
0
    if (length < 0)
1042
0
        length = text().size();
1043
1044
0
    QHash<std::pair<QFontEngine *, int>, QGlyphRun> glyphRunHash;
1045
0
    for (int i=0; i<d->lines.size(); ++i) {
1046
0
        if (d->lines.at(i).from > from + length)
1047
0
            break;
1048
0
        else if (d->lines.at(i).from + d->lines.at(i).length >= from) {
1049
0
            const QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
1050
0
            for (const QGlyphRun &glyphRun : glyphRuns) {
1051
0
                QRawFont rawFont = glyphRun.rawFont();
1052
1053
0
                QFontEngine *fontEngine = rawFont.d->fontEngine;
1054
0
                QGlyphRun::GlyphRunFlags flags = glyphRun.flags();
1055
0
                std::pair<QFontEngine *, int> key(fontEngine, int(flags));
1056
                // merge the glyph runs using the same font
1057
0
                QGlyphRun &oldGlyphRun = glyphRunHash[key];
1058
0
                if (oldGlyphRun.isEmpty()) {
1059
0
                    oldGlyphRun = glyphRun;
1060
0
                } else {
1061
0
                    QList<quint32> indexes = oldGlyphRun.glyphIndexes();
1062
0
                    QList<QPointF> positions = oldGlyphRun.positions();
1063
0
                    QList<qsizetype> stringIndexes = oldGlyphRun.stringIndexes();
1064
0
                    QRectF boundingRect = oldGlyphRun.boundingRect();
1065
1066
0
                    indexes += glyphRun.glyphIndexes();
1067
0
                    positions += glyphRun.positions();
1068
0
                    stringIndexes += glyphRun.stringIndexes();
1069
0
                    boundingRect = boundingRect.united(glyphRun.boundingRect());
1070
1071
0
                    oldGlyphRun.setGlyphIndexes(indexes);
1072
0
                    oldGlyphRun.setPositions(positions);
1073
0
                    oldGlyphRun.setStringIndexes(stringIndexes);
1074
0
                    oldGlyphRun.setBoundingRect(boundingRect);
1075
0
                }
1076
0
            }
1077
0
        }
1078
0
    }
1079
1080
0
    return glyphRunHash.values();
1081
0
}
1082
#endif // QT_NO_RAWFONT
1083
1084
/*!
1085
    Draws the whole layout on the painter \a p at the position specified by \a pos.
1086
    The rendered layout includes the given \a selections and is clipped within
1087
    the rectangle specified by \a clip.
1088
*/
1089
void QTextLayout::draw(QPainter *p, const QPointF &pos, const QList<FormatRange> &selections, const QRectF &clip) const
1090
0
{
1091
0
    if (d->lines.isEmpty())
1092
0
        return;
1093
1094
0
    if (!d->layoutData)
1095
0
        d->itemize();
1096
1097
0
    QPointF position = pos + d->position;
1098
1099
0
    QFixed clipy = (INT_MIN/256);
1100
0
    QFixed clipe = (INT_MAX/256);
1101
0
    if (clip.isValid()) {
1102
0
        clipy = QFixed::fromReal(clip.y() - position.y());
1103
0
        clipe = clipy + QFixed::fromReal(clip.height());
1104
0
    }
1105
1106
0
    int firstLine = 0;
1107
0
    int lastLine = d->lines.size();
1108
0
    for (int i = 0; i < d->lines.size(); ++i) {
1109
0
        const QScriptLine &sl = d->lines.at(i);
1110
1111
0
        if (sl.y > clipe) {
1112
0
            lastLine = i;
1113
0
            break;
1114
0
        }
1115
0
        if ((sl.y + sl.height()) < clipy) {
1116
0
            firstLine = i;
1117
0
            continue;
1118
0
        }
1119
0
    }
1120
1121
0
    QPainterPath excludedRegion;
1122
0
    QPainterPath textDoneRegion;
1123
0
    for (int i = 0; i < selections.size(); ++i) {
1124
0
        FormatRange selection = selections.at(i);
1125
        // Don't clip the selection region when it has an outline pen,
1126
        // because clipping creates artificial edges that get stroked (QTBUG-115232)
1127
0
        const QPen pen = selection.format.penProperty(QTextFormat::OutlinePen);
1128
0
        const bool hasOutline = (pen.style() != Qt::NoPen && pen.widthF() > 0);
1129
0
        const QRectF selectionClip = hasOutline ? QRectF() : clip;
1130
0
        QPainterPath region;
1131
0
        region.setFillRule(Qt::WindingFill);
1132
1133
0
        for (int line = firstLine; line < lastLine; ++line) {
1134
0
            const QScriptLine &sl = d->lines.at(line);
1135
0
            QTextLine tl(line, d);
1136
1137
0
            QRectF lineRect(tl.naturalTextRect());
1138
0
            lineRect.translate(position);
1139
0
            lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1140
0
            lineRect.setBottom(qCeil(lineRect.bottom()));
1141
1142
0
            bool isLastLineInBlock = (line == d->lines.size()-1);
1143
0
            int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1144
1145
1146
0
            if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1147
0
                continue; // no actual intersection
1148
1149
0
            const bool selectionStartInLine = sl.from <= selection.start;
1150
0
            const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1151
1152
0
            if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1153
0
                addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, selectionClip));
1154
0
            } else {
1155
0
                region.addRect(clipIfValid(lineRect, selectionClip));
1156
0
            }
1157
1158
0
            if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1159
0
                QRectF fullLineRect(tl.rect());
1160
0
                fullLineRect.translate(position);
1161
0
                fullLineRect.setRight(QFIXED_MAX);
1162
0
                fullLineRect.setBottom(qCeil(fullLineRect.bottom()));
1163
1164
0
                const bool rightToLeft = d->isRightToLeft();
1165
1166
0
                if (!selectionEndInLine) {
1167
0
                    region.addRect(clipIfValid(rightToLeft ? QRectF(fullLineRect.topLeft(), lineRect.bottomLeft())
1168
0
                                                           : QRectF(lineRect.topRight(), fullLineRect.bottomRight()), selectionClip));
1169
0
                }
1170
0
                if (!selectionStartInLine) {
1171
0
                    region.addRect(clipIfValid(rightToLeft ? QRectF(lineRect.topRight(), fullLineRect.bottomRight())
1172
0
                                                           : QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), selectionClip));
1173
0
                }
1174
0
            } else if (!selectionEndInLine
1175
0
                && isLastLineInBlock
1176
0
                &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1177
0
                region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1178
0
                                                  lineRect.height()/4, lineRect.height()), selectionClip));
1179
0
            }
1180
1181
0
        }
1182
0
        {
1183
0
            const QPen oldPen = p->pen();
1184
0
            const QBrush oldBrush = p->brush();
1185
1186
0
            if (hasOutline) {
1187
0
                p->save();
1188
0
                p->setClipRect(clip, Qt::IntersectClip);
1189
0
            }
1190
1191
0
            p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1192
0
            p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1193
0
            p->drawPath(region);
1194
1195
0
            p->setPen(oldPen);
1196
0
            p->setBrush(oldBrush);
1197
1198
0
            if (hasOutline)
1199
0
                p->restore();
1200
0
        }
1201
1202
1203
1204
0
        bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1205
0
        bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1206
1207
0
        if (hasBackground) {
1208
0
            selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1209
            // don't just clear the property, set an empty brush that overrides a potential
1210
            // background brush specified in the text
1211
0
            selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1212
0
            selection.format.clearProperty(QTextFormat::OutlinePen);
1213
0
        }
1214
1215
0
        selection.format.setProperty(SuppressText, !hasText);
1216
1217
0
        if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1218
0
            continue;
1219
1220
0
        p->save();
1221
0
        p->setClipPath(region, Qt::IntersectClip);
1222
1223
0
        for (int line = firstLine; line < lastLine; ++line) {
1224
0
            QTextLine l(line, d);
1225
0
            l.draw_internal(p, position, &selection);
1226
0
        }
1227
0
        p->restore();
1228
1229
0
        if (hasText) {
1230
0
            textDoneRegion += region;
1231
0
        } else {
1232
0
            if (hasBackground)
1233
0
                textDoneRegion -= region;
1234
0
        }
1235
1236
0
        excludedRegion += region;
1237
0
    }
1238
1239
0
    QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1240
0
    if (!needsTextButNoBackground.isEmpty()){
1241
0
        p->save();
1242
0
        p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1243
0
        FormatRange selection;
1244
0
        selection.start = 0;
1245
0
        selection.length = INT_MAX;
1246
0
        selection.format.setProperty(SuppressBackground, true);
1247
0
        for (int line = firstLine; line < lastLine; ++line) {
1248
0
            QTextLine l(line, d);
1249
0
            l.draw_internal(p, position, &selection);
1250
0
        }
1251
0
        p->restore();
1252
0
    }
1253
1254
0
    if (!excludedRegion.isEmpty()) {
1255
0
        p->save();
1256
0
        QPainterPath path;
1257
0
        QRectF br = boundingRect().translated(position);
1258
0
        br.setRight(QFIXED_MAX);
1259
0
        if (!clip.isNull())
1260
0
            br = br.intersected(clip);
1261
0
        path.addRect(br);
1262
0
        path -= excludedRegion;
1263
0
        p->setClipPath(path, Qt::IntersectClip);
1264
0
    }
1265
1266
0
    for (int i = firstLine; i < lastLine; ++i) {
1267
0
        QTextLine l(i, d);
1268
0
        l.draw(p, position);
1269
0
    }
1270
0
    if (!excludedRegion.isEmpty())
1271
0
        p->restore();
1272
1273
1274
0
    if (!d->cacheGlyphs)
1275
0
        d->freeMemory();
1276
0
}
1277
1278
/*!
1279
    \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1280
    \overload
1281
1282
    Draws a text cursor with the current pen at the given \a position using the
1283
    \a painter specified.
1284
    The corresponding position within the text is specified by \a cursorPosition.
1285
*/
1286
void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1287
0
{
1288
0
    drawCursor(p, pos, cursorPosition, 1);
1289
0
}
1290
1291
/*!
1292
    \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1293
1294
    Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1295
    \a painter specified.
1296
    The corresponding position within the text is specified by \a cursorPosition.
1297
*/
1298
void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1299
0
{
1300
0
    if (d->lines.isEmpty())
1301
0
        return;
1302
1303
0
    if (!d->layoutData)
1304
0
        d->itemize();
1305
1306
0
    QPointF position = pos + d->position;
1307
1308
0
    cursorPosition = qBound(0, cursorPosition, d->layoutData->string.size());
1309
0
    int line = d->lineNumberForTextPosition(cursorPosition);
1310
0
    if (line < 0)
1311
0
        line = 0;
1312
0
    if (line >= d->lines.size())
1313
0
        return;
1314
1315
0
    QTextLine l(line, d);
1316
0
    const QScriptLine &sl = d->lines.at(line);
1317
1318
0
    qreal x = position.x() + l.cursorToX(cursorPosition);
1319
1320
0
    QFixed base = sl.base();
1321
0
    QFixed descent = sl.descent;
1322
0
    bool rightToLeft = d->isRightToLeft();
1323
1324
0
    const int realCursorPosition = cursorPosition;
1325
0
    if (d->visualCursorMovement()) {
1326
0
        if (cursorPosition == sl.from + sl.length)
1327
0
            --cursorPosition;
1328
0
    } else {
1329
0
        --cursorPosition;
1330
0
    }
1331
0
    int itm = d->findItem(cursorPosition);
1332
1333
0
    if (itm >= 0) {
1334
0
        const QScriptItem *si = &d->layoutData->items.at(itm);
1335
        // Same logic as in cursorToX to handle edges between writing directions to prioritise the script item
1336
        // that matches the writing direction of the paragraph.
1337
0
        if (d->layoutData->hasBidi && !d->visualCursorMovement() && si->analysis.bidiLevel % 2 != rightToLeft) {
1338
0
            int neighborItem = itm;
1339
0
            if (neighborItem > 0 && si->position == realCursorPosition)
1340
0
                --neighborItem;
1341
0
            else if (neighborItem < d->layoutData->items.size() - 1 && si->position + si->num_glyphs == realCursorPosition)
1342
0
                ++neighborItem;
1343
0
            const bool onBoundary = neighborItem != itm
1344
0
                                 && si->analysis.bidiLevel != d->layoutData->items[neighborItem].analysis.bidiLevel;
1345
0
            if (onBoundary && rightToLeft != si->analysis.bidiLevel % 2) {
1346
0
                itm = neighborItem;
1347
0
                si = &d->layoutData->items[itm];
1348
0
            }
1349
0
        }
1350
        // objects need some special treatment as they can have special alignment or be floating
1351
0
        if (si->analysis.flags != QScriptAnalysis::Object) {
1352
0
            if (si->ascent > 0)
1353
0
                base = si->ascent;
1354
0
            if (si->descent > 0)
1355
0
                descent = si->descent;
1356
0
        }
1357
0
        rightToLeft = si->analysis.bidiLevel % 2;
1358
0
    }
1359
0
    qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1360
0
    bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1361
0
                              && (p->transform().type() > QTransform::TxTranslate);
1362
0
    if (toggleAntialiasing)
1363
0
        p->setRenderHint(QPainter::Antialiasing);
1364
0
    QPainter::CompositionMode origCompositionMode = p->compositionMode();
1365
    // Raster ops are bitwise and break the premultiplied-alpha invariant when
1366
    // the target has an alpha channel (e.g. QGraphicsEffect's offscreen buffer,
1367
    // or a translucent top-level backing store), making the cursor vanish or
1368
    // punch an opaque hole. Fall back to SourceOver with the pen color in that
1369
    // case (QTBUG-109173).
1370
0
    QPaintDevice *targetDevice = p->paintEngine()->paintDevice();
1371
0
    const bool targetHasAlphaChannel = targetDevice
1372
0
            && targetDevice->devType() == QInternal::Image
1373
0
            && static_cast<QImage *>(targetDevice)->hasAlphaChannel();
1374
0
    if (!targetHasAlphaChannel && p->paintEngine()->hasFeature(QPaintEngine::RasterOpModes))
1375
0
        p->setCompositionMode(QPainter::RasterOp_NotDestination);
1376
0
    const QTransform &deviceTransform = p->deviceTransform();
1377
0
    const qreal xScale = deviceTransform.m11();
1378
0
    if (deviceTransform.type() != QTransform::TxScale || std::trunc(xScale) == xScale) {
1379
0
        p->fillRect(QRectF(x, y, qreal(width), (base + descent).toReal()), p->pen().brush());
1380
0
    } else {
1381
        // Ensure consistently rendered cursor width under fractional scaling
1382
0
        const QPen origPen = p->pen();
1383
0
        QPen pen(origPen.brush(), qRound(width * xScale), Qt::SolidLine, Qt::FlatCap);
1384
0
        pen.setCosmetic(true);
1385
0
        const qreal center = x + qreal(width) / 2;
1386
0
        p->setPen(pen);
1387
0
        p->drawLine(QPointF(center, y), QPointF(center, qCeil(y + (base + descent).toReal())));
1388
0
        p->setPen(origPen);
1389
0
    }
1390
0
    p->setCompositionMode(origCompositionMode);
1391
0
    if (toggleAntialiasing)
1392
0
        p->setRenderHint(QPainter::Antialiasing, false);
1393
0
    if (d->layoutData->hasBidi) {
1394
0
        const int arrow_extent = 4;
1395
0
        int sign = rightToLeft ? -1 : 1;
1396
0
        p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1397
0
        p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1398
0
    }
1399
0
    return;
1400
0
}
1401
1402
/*!
1403
    \class QTextLine
1404
    \reentrant
1405
1406
    \brief The QTextLine class represents a line of text inside a QTextLayout.
1407
    \inmodule QtGui
1408
1409
    \ingroup richtext-processing
1410
1411
    A text line is usually created by QTextLayout::createLine().
1412
1413
    After being created, the line can be filled using the setLineWidth()
1414
    or setNumColumns() functions. A line has a number of attributes including the
1415
    rectangle it occupies, rect(), its coordinates, x() and y(), its
1416
    textLength(), width() and naturalTextWidth(), and its ascent() and descent()
1417
    relative to the text. The position of the cursor in terms of the
1418
    line is available from cursorToX() and its inverse from
1419
    xToCursor(). A line can be moved with setPosition().
1420
*/
1421
1422
/*!
1423
    \enum QTextLine::Edge
1424
1425
    \value Leading
1426
    \value Trailing
1427
*/
1428
1429
/*!
1430
    \enum QTextLine::CursorPosition
1431
1432
    \value CursorBetweenCharacters
1433
    \value CursorOnCharacter
1434
*/
1435
1436
/*!
1437
    \fn QTextLine::QTextLine(int line, QTextEngine *e)
1438
    \internal
1439
1440
    Constructs a new text line using the line at position \a line in
1441
    the text engine \a e.
1442
*/
1443
1444
/*!
1445
    \fn QTextLine::QTextLine()
1446
1447
    Creates an invalid line.
1448
*/
1449
1450
/*!
1451
    \fn bool QTextLine::isValid() const
1452
1453
    Returns \c true if this text line is valid; otherwise returns \c false.
1454
*/
1455
1456
/*!
1457
    \fn int QTextLine::lineNumber() const
1458
1459
    Returns the position of the line in the text engine.
1460
*/
1461
1462
1463
/*!
1464
    Returns the line's bounding rectangle.
1465
1466
    \sa x(), y(), textLength(), width()
1467
*/
1468
QRectF QTextLine::rect() const
1469
0
{
1470
0
    const QScriptLine& sl = eng->lines.at(index);
1471
0
    return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1472
0
}
1473
1474
/*!
1475
    Returns the rectangle covered by the line.
1476
*/
1477
QRectF QTextLine::naturalTextRect() const
1478
0
{
1479
0
    const QScriptLine& sl = eng->lines.at(index);
1480
0
    QFixed x = sl.x + eng->alignLine(sl);
1481
1482
0
    QFixed width = sl.textWidth;
1483
0
    if (sl.justified)
1484
0
        width = sl.width;
1485
1486
0
    return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1487
0
}
1488
1489
/*!
1490
    Returns the line's x position.
1491
1492
    \sa rect(), y(), textLength(), width()
1493
*/
1494
qreal QTextLine::x() const
1495
0
{
1496
0
    return eng->lines.at(index).x.toReal();
1497
0
}
1498
1499
/*!
1500
    Returns the line's y position.
1501
1502
    \sa x(), rect(), textLength(), width()
1503
*/
1504
qreal QTextLine::y() const
1505
0
{
1506
0
    return eng->lines.at(index).y.toReal();
1507
0
}
1508
1509
/*!
1510
    Returns the line's width as specified by the layout() function.
1511
1512
    \sa naturalTextWidth(), x(), y(), textLength(), rect()
1513
*/
1514
qreal QTextLine::width() const
1515
0
{
1516
0
    return eng->lines.at(index).width.toReal();
1517
0
}
1518
1519
1520
/*!
1521
    Returns the line's ascent.
1522
1523
    \sa descent(), height()
1524
*/
1525
qreal QTextLine::ascent() const
1526
0
{
1527
0
    return eng->lines.at(index).ascent.toReal();
1528
0
}
1529
1530
/*!
1531
    Returns the line's descent.
1532
1533
    \sa ascent(), height()
1534
*/
1535
qreal QTextLine::descent() const
1536
0
{
1537
0
    return eng->lines.at(index).descent.toReal();
1538
0
}
1539
1540
/*!
1541
    Returns the line's height. This is equal to ascent() + descent()
1542
    if leading is not included. If leading is included, this equals to
1543
    ascent() + descent() + leading().
1544
1545
    \sa ascent(), descent(), leading(), setLeadingIncluded()
1546
*/
1547
qreal QTextLine::height() const
1548
0
{
1549
0
    return eng->lines.at(index).height().ceil().toReal();
1550
0
}
1551
1552
/*!
1553
    \since 4.6
1554
1555
    Returns the line's leading.
1556
1557
    \sa ascent(), descent(), height()
1558
*/
1559
qreal QTextLine::leading() const
1560
0
{
1561
0
    return eng->lines.at(index).leading.toReal();
1562
0
}
1563
1564
/*!
1565
    \since 4.6
1566
1567
    Includes positive leading into the line's height if \a included is true;
1568
    otherwise does not include leading.
1569
1570
    By default, leading is not included.
1571
1572
    Note that negative leading is ignored, it must be handled
1573
    in the code using the text lines by letting the lines overlap.
1574
1575
    \sa leadingIncluded()
1576
1577
*/
1578
void QTextLine::setLeadingIncluded(bool included)
1579
0
{
1580
0
    eng->lines[index].leadingIncluded= included;
1581
1582
0
}
1583
1584
/*!
1585
    \since 4.6
1586
1587
    Returns \c true if positive leading is included into the line's height;
1588
    otherwise returns \c false.
1589
1590
    By default, leading is not included.
1591
1592
    \sa setLeadingIncluded()
1593
*/
1594
bool QTextLine::leadingIncluded() const
1595
0
{
1596
0
    return eng->lines.at(index).leadingIncluded;
1597
0
}
1598
1599
/*!
1600
    Returns the width of the line that is occupied by text. This is
1601
    always \<= to width(), and is the minimum width that could be used
1602
    by layout() without changing the line break position.
1603
*/
1604
qreal QTextLine::naturalTextWidth() const
1605
0
{
1606
0
    return eng->lines.at(index).textWidth.toReal();
1607
0
}
1608
1609
/*!
1610
    \since 4.7
1611
    Returns the horizontal advance of the text. The advance of the text
1612
    is the distance from its position to the next position at which
1613
    text would naturally be drawn.
1614
1615
    By adding the advance to the position of the text line and using this
1616
    as the position of a second text line, you will be able to position
1617
    the two lines side-by-side without gaps in-between.
1618
*/
1619
qreal QTextLine::horizontalAdvance() const
1620
0
{
1621
0
    return eng->lines.at(index).textAdvance.toReal();
1622
0
}
1623
1624
/*!
1625
    Lays out the line with the given \a width. The line is filled from
1626
    its starting position with as many characters as will fit into
1627
    the line. In case the text cannot be split at the end of the line,
1628
    it will be filled with additional characters to the next whitespace
1629
    or end of the text.
1630
*/
1631
void QTextLine::setLineWidth(qreal width)
1632
0
{
1633
0
    QScriptLine &line = eng->lines[index];
1634
0
    if (!eng->layoutData) {
1635
0
        qWarning("QTextLine: Can't set a line width while not layouting.");
1636
0
        return;
1637
0
    }
1638
1639
0
    line.width = QFixed::fromReal(qBound(0.0, width, qreal(QFIXED_MAX)));
1640
0
    if (line.length
1641
0
        && line.textWidth <= line.width
1642
0
        && line.from + line.length == eng->layoutData->string.size())
1643
        // no need to do anything if the line is already layouted and the last one. This optimization helps
1644
        // when using things in a single line layout.
1645
0
        return;
1646
0
    line.length = 0;
1647
0
    line.textWidth = 0;
1648
1649
0
    layout_helper(INT_MAX);
1650
0
}
1651
1652
/*!
1653
    Lays out the line. The line is filled from its starting position
1654
    with as many characters as are specified by \a numColumns. In case
1655
    the text cannot be split until \a numColumns characters, the line
1656
    will be filled with as many characters to the next whitespace or
1657
    end of the text.
1658
*/
1659
void QTextLine::setNumColumns(int numColumns)
1660
0
{
1661
0
    QScriptLine &line = eng->lines[index];
1662
0
    line.width = QFIXED_MAX;
1663
0
    line.length = 0;
1664
0
    line.textWidth = 0;
1665
0
    layout_helper(numColumns);
1666
0
}
1667
1668
/*!
1669
    Lays out the line. The line is filled from its starting position
1670
    with as many characters as are specified by \a numColumns. In case
1671
    the text cannot be split until \a numColumns characters, the line
1672
    will be filled with as many characters to the next whitespace or
1673
    end of the text. The provided \a alignmentWidth is used as reference
1674
    width for alignment.
1675
*/
1676
void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1677
0
{
1678
0
    QScriptLine &line = eng->lines[index];
1679
0
    line.width = QFixed::fromReal(qBound(0.0, alignmentWidth, qreal(QFIXED_MAX)));
1680
0
    line.length = 0;
1681
0
    line.textWidth = 0;
1682
0
    layout_helper(numColumns);
1683
0
}
1684
1685
#if 0
1686
#define LB_DEBUG qDebug
1687
#else
1688
0
#define LB_DEBUG if (0) qDebug
1689
#endif
1690
1691
namespace {
1692
1693
    struct LineBreakHelper
1694
    {
1695
0
        LineBreakHelper() = default;
1696
1697
        QScriptLine tmpData;
1698
        QScriptLine spaceData;
1699
1700
        QGlyphLayout glyphs;
1701
1702
        int glyphCount = 0;
1703
        int maxGlyphs = 0;
1704
        int currentPosition = 0;
1705
        glyph_t previousGlyph = 0;
1706
        QExplicitlySharedDataPointer<QFontEngine> previousGlyphFontEngine;
1707
1708
        QFixed minw;
1709
        QFixed currentSoftHyphenWidth;
1710
        QFixed commitedSoftHyphenWidth;
1711
        QFixed rightBearing;
1712
        QFixed minimumRightBearing;
1713
1714
        QExplicitlySharedDataPointer<QFontEngine> fontEngine;
1715
        const unsigned short *logClusters = nullptr;
1716
1717
        bool manualWrap = false;
1718
        bool whiteSpaceOrObject = true;
1719
1720
        bool checkFullOtherwiseExtend(QScriptLine &line);
1721
1722
0
        QFixed calculateNewWidth(const QScriptLine &line) const {
1723
0
            return line.textWidth + tmpData.textWidth + spaceData.textWidth
1724
0
                    + (line.textWidth > 0 ? currentSoftHyphenWidth : QFixed()) + negativeRightBearing();
1725
0
        }
1726
1727
        inline glyph_t currentGlyph() const
1728
0
        {
1729
0
            Q_ASSERT(currentPosition > 0);
1730
0
            Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1731
1732
0
            return glyphs.glyphs[logClusters[currentPosition - 1]];
1733
0
        }
1734
1735
        inline void saveCurrentGlyph()
1736
0
        {
1737
0
            previousGlyph = 0;
1738
0
            if (currentPosition > 0 &&
1739
0
                logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1740
0
                previousGlyph = currentGlyph(); // needed to calculate right bearing later
1741
0
                previousGlyphFontEngine = fontEngine;
1742
0
            }
1743
0
        }
1744
1745
        inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph)
1746
0
        {
1747
0
            Q_ASSERT(engine);
1748
0
            qreal rb;
1749
0
            engine->getGlyphBearings(glyph, nullptr, &rb);
1750
1751
            // We only care about negative right bearings, so we limit the range
1752
            // of the bearing here so that we can assume it's negative in the rest
1753
            // of the code, as well ase use QFixed(1) as a sentinel to represent
1754
            // the state where we have yet to compute the right bearing.
1755
0
            rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
1756
0
        }
1757
1758
        inline void calculateRightBearing()
1759
0
        {
1760
0
            if (currentPosition <= 0)
1761
0
                return;
1762
0
            calculateRightBearing(fontEngine.data(), currentGlyph());
1763
0
        }
1764
1765
        inline void calculateRightBearingForPreviousGlyph()
1766
0
        {
1767
0
            if (previousGlyph > 0)
1768
0
                calculateRightBearing(previousGlyphFontEngine.data(), previousGlyph);
1769
0
        }
1770
1771
        static const QFixed RightBearingNotCalculated;
1772
1773
        inline void resetRightBearing()
1774
0
        {
1775
0
            rightBearing = RightBearingNotCalculated;
1776
0
        }
1777
1778
        // We express the negative right bearing as an absolute number
1779
        // so that it can be applied to the width using addition.
1780
        inline QFixed negativeRightBearing() const
1781
0
        {
1782
0
            if (rightBearing == RightBearingNotCalculated)
1783
0
                return QFixed(0);
1784
1785
0
            return qAbs(rightBearing);
1786
0
        }
1787
    };
1788
1789
Q_CONSTINIT const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
1790
1791
inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1792
0
{
1793
0
    LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1794
1795
0
    QFixed newWidth = calculateNewWidth(line);
1796
0
    if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1797
0
        return true;
1798
1799
0
    const QFixed oldTextWidth = line.textWidth;
1800
0
    line += tmpData;
1801
0
    line.textWidth += spaceData.textWidth;
1802
1803
0
    line.length += spaceData.length;
1804
0
    tmpData.textWidth = 0;
1805
0
    tmpData.length = 0;
1806
0
    spaceData.textWidth = 0;
1807
0
    spaceData.length = 0;
1808
1809
0
    if (oldTextWidth != line.textWidth || currentSoftHyphenWidth > 0) {
1810
0
        commitedSoftHyphenWidth = currentSoftHyphenWidth;
1811
0
        currentSoftHyphenWidth = 0;
1812
0
    }
1813
1814
0
    return false;
1815
0
}
1816
1817
} // anonymous namespace
1818
1819
1820
static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1821
                                  const QScriptItem &current, const unsigned short *logClusters,
1822
                                  const QGlyphLayout &glyphs, QFixed *clusterWidth = nullptr)
1823
0
{
1824
0
    int glyphPosition = logClusters[pos];
1825
0
    do { // got to the first next cluster
1826
0
        ++pos;
1827
0
        ++line.length;
1828
0
    } while (pos < end && logClusters[pos] == glyphPosition);
1829
0
    QFixed clusterWid = line.textWidth;
1830
0
    do { // calculate the textWidth for the rest of the current cluster.
1831
0
        if (!glyphs.attributes[glyphPosition].dontPrint)
1832
0
            line.textWidth += glyphs.advances[glyphPosition];
1833
0
        ++glyphPosition;
1834
0
    } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1835
1836
0
    Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1837
1838
0
    if (clusterWidth)
1839
0
        *clusterWidth += (line.textWidth - clusterWid);
1840
0
    ++glyphCount;
1841
0
}
1842
1843
1844
// fill QScriptLine
1845
void QTextLine::layout_helper(int maxGlyphs)
1846
0
{
1847
0
    QScriptLine &line = eng->lines[index];
1848
0
    line.length = 0;
1849
0
    line.trailingSpaces = 0;
1850
0
    line.textWidth = 0;
1851
0
    line.hasTrailingSpaces = false;
1852
1853
0
    if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.size()) {
1854
0
        line.setDefaultHeight(eng);
1855
0
        return;
1856
0
    }
1857
1858
0
    Q_ASSERT(line.from < eng->layoutData->string.size());
1859
1860
0
    LineBreakHelper lbh;
1861
1862
0
    lbh.maxGlyphs = maxGlyphs;
1863
1864
0
    QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1865
0
    bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1866
0
    const bool breakWordOrAny = breakany || (wrapMode == QTextOption::WrapAtWordBoundaryOrAnywhere);
1867
0
    lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1868
1869
0
    int item = -1;
1870
0
    int newItem = eng->findItem(line.from);
1871
0
    Q_ASSERT(newItem >= 0);
1872
1873
0
    LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, int(eng->layoutData->items.size()), line.width.toReal());
1874
1875
0
    Qt::Alignment alignment = eng->option.alignment();
1876
1877
0
    const QCharAttributes *attributes = eng->attributes();
1878
0
    if (!attributes)
1879
0
        return;
1880
0
    lbh.currentPosition = line.from;
1881
0
    int end = 0;
1882
0
    lbh.logClusters = eng->layoutData->logClustersPtr;
1883
0
    lbh.previousGlyph = 0;
1884
1885
0
    bool manuallyWrapped = false;
1886
0
    bool hasInlineObject = false;
1887
0
    bool reachedEndOfLine = false;
1888
0
    QFixed maxInlineObjectHeight = 0;
1889
1890
0
    const bool includeTrailingSpaces = eng->option.flags() & QTextOption::IncludeTrailingSpaces;
1891
1892
0
    while (newItem < eng->layoutData->items.size()) {
1893
0
        lbh.resetRightBearing();
1894
0
        if (newItem != item) {
1895
0
            item = newItem;
1896
0
            const QScriptItem &current = eng->layoutData->items.at(item);
1897
0
            if (!current.num_glyphs) {
1898
0
                eng->shape(item);
1899
0
                attributes = eng->attributes();
1900
0
                if (!attributes)
1901
0
                    return;
1902
0
                lbh.logClusters = eng->layoutData->logClustersPtr;
1903
0
            }
1904
0
            lbh.currentPosition = qMax(line.from, current.position);
1905
0
            end = current.position + eng->length(item);
1906
0
            lbh.glyphs = eng->shapedGlyphs(&current);
1907
0
            QFontEngine *fontEngine = eng->fontEngine(current);
1908
0
            if (lbh.fontEngine != fontEngine) {
1909
0
                lbh.fontEngine = fontEngine;
1910
0
                lbh.minimumRightBearing = qMin(QFixed(),
1911
0
                                               QFixed::fromReal(fontEngine->minRightBearing()));
1912
0
            }
1913
0
        }
1914
0
        const QScriptItem &current = eng->layoutData->items.at(item);
1915
1916
0
        lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1917
0
                                   current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1918
0
                                                                            current.ascent);
1919
0
        if (current.analysis.flags != QScriptAnalysis::Object
1920
0
            || QTextDocumentPrivate::get(eng->block) == nullptr) {
1921
            // Objects with a QTextDocument may need special vertical alignment
1922
            // or floating treatment handled later, so skip the ascent/descent
1923
            // update for them here.  Standalone objects (no document) are
1924
            // always included because no such post-processing exists.
1925
0
            lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1926
0
            lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1927
0
        }
1928
1929
0
        if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1930
0
            lbh.whiteSpaceOrObject = true;
1931
0
            if (lbh.checkFullOtherwiseExtend(line))
1932
0
                goto found;
1933
1934
0
            QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1935
0
            QFixed tabWidth = eng->calculateTabWidth(item, x);
1936
0
            attributes = eng->attributes();
1937
0
            if (!attributes)
1938
0
                return;
1939
0
            lbh.logClusters = eng->layoutData->logClustersPtr;
1940
0
            lbh.glyphs = eng->shapedGlyphs(&current);
1941
1942
0
            lbh.spaceData.textWidth += tabWidth;
1943
0
            lbh.spaceData.length++;
1944
0
            newItem = item + 1;
1945
1946
0
            QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1947
0
            lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1948
1949
0
            if (lbh.checkFullOtherwiseExtend(line))
1950
0
                goto found;
1951
0
        } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1952
0
            lbh.whiteSpaceOrObject = true;
1953
            // if the line consists only of the line separator make sure
1954
            // we have a sane height
1955
0
            if (!line.length && !lbh.tmpData.length)
1956
0
                line.setDefaultHeight(eng);
1957
0
            if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1958
0
                if (lbh.checkFullOtherwiseExtend(line))
1959
0
                    goto found;
1960
1961
0
                addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1962
0
                               current, lbh.logClusters, lbh.glyphs);
1963
0
            } else {
1964
0
                lbh.tmpData.length++;
1965
0
                lbh.calculateRightBearingForPreviousGlyph();
1966
0
            }
1967
0
            line += lbh.tmpData;
1968
0
            manuallyWrapped = true;
1969
0
            goto found;
1970
0
        } else if (current.analysis.flags == QScriptAnalysis::Object) {
1971
0
            lbh.whiteSpaceOrObject = true;
1972
0
            lbh.tmpData.length++;
1973
1974
0
            if (QTextDocumentPrivate::get(eng->block) != nullptr) {
1975
0
                QTextInlineObject inlineObject(item, eng);
1976
0
                QTextFormat f = inlineObject.format();
1977
0
                eng->docLayout()->positionInlineObject(inlineObject, eng->block.position() + current.position, f);
1978
0
                QTextCharFormat::VerticalAlignment valign = f.toCharFormat().verticalAlignment();
1979
0
                if (valign != QTextCharFormat::AlignTop && valign != QTextCharFormat::AlignBottom) {
1980
0
                    lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1981
0
                    lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1982
0
                }
1983
0
            }
1984
1985
0
            lbh.tmpData.textWidth += current.width;
1986
1987
0
            newItem = item + 1;
1988
0
            ++lbh.glyphCount;
1989
0
            if (lbh.checkFullOtherwiseExtend(line))
1990
0
                goto found;
1991
1992
0
            hasInlineObject = true;
1993
0
            maxInlineObjectHeight = qMax(maxInlineObjectHeight, current.ascent + current.descent);
1994
1995
0
        } else if (attributes[lbh.currentPosition].whiteSpace
1996
0
                   && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1997
            // If we are adding a space block, we save the last non-whitespace glyph for calculating
1998
            // the right bearing later
1999
0
            if (lbh.currentPosition > 0 && !attributes[lbh.currentPosition - 1].whiteSpace)
2000
0
                lbh.saveCurrentGlyph();
2001
0
            lbh.whiteSpaceOrObject = true;
2002
0
            while (lbh.currentPosition < end
2003
0
                   && attributes[lbh.currentPosition].whiteSpace
2004
0
                   && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
2005
0
                addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
2006
0
                               current, lbh.logClusters, lbh.glyphs);
2007
0
            }
2008
0
        } else {
2009
0
            if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width)
2010
0
                goto found;
2011
2012
0
            lbh.whiteSpaceOrObject = false;
2013
0
            bool sb_or_ws = false;
2014
            // We save the previous glyph so we can use it for calculating the right bearing
2015
            // later. If we are trimming trailing spaces, the previous glyph is whitespace
2016
            // and we have already recorded a non-whitespace glyph, we keep that one instead.
2017
0
            if (lbh.currentPosition == 0
2018
0
                || lbh.previousGlyph == 0
2019
0
                || includeTrailingSpaces
2020
0
                || !attributes[lbh.currentPosition - 1].whiteSpace) {
2021
0
                lbh.saveCurrentGlyph();
2022
0
            }
2023
0
            QFixed accumulatedTextWidth;
2024
0
            do {
2025
0
                addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
2026
0
                               current, lbh.logClusters, lbh.glyphs, &accumulatedTextWidth);
2027
2028
                // This is a hack to fix a regression caused by the introduction of the
2029
                // whitespace flag to non-breakable spaces and will cause the non-breakable
2030
                // spaces to behave as in previous Qt versions in the line breaking algorithm.
2031
                // The line breaks do not currently follow the Unicode specs, but fixing this would
2032
                // require refactoring the code and would cause behavioral regressions.
2033
0
                const bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.size()
2034
0
                                        && attributes[lbh.currentPosition].whiteSpace
2035
0
                                        && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak;
2036
2037
0
                if (lbh.currentPosition >= eng->layoutData->string.size()
2038
0
                    || isBreakableSpace
2039
0
                    || attributes[lbh.currentPosition].lineBreak
2040
0
                    || lbh.tmpData.textWidth >= QFIXED_MAX) {
2041
0
                    sb_or_ws = true;
2042
0
                    break;
2043
0
                } else if (attributes[lbh.currentPosition].graphemeBoundary) {
2044
0
                    if (breakWordOrAny) {
2045
0
                        lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2046
0
                        accumulatedTextWidth = 0;
2047
0
                    }
2048
0
                    if (breakany)
2049
0
                        break;
2050
0
                }
2051
0
            } while (lbh.currentPosition < end);
2052
0
            lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2053
2054
0
            if (lbh.currentPosition > 0 && lbh.currentPosition <= end
2055
0
                && (lbh.currentPosition == end || attributes[lbh.currentPosition].lineBreak)
2056
0
                && eng->layoutData->string.at(lbh.currentPosition - 1) == QChar::SoftHyphen) {
2057
                // if we are splitting up a word because of
2058
                // a soft hyphen then we ...
2059
                //
2060
                //  a) have to take the width of the soft hyphen into
2061
                //     account to see if the first syllable(s) /and/
2062
                //     the soft hyphen fit into the line
2063
                //
2064
                //  b) if we are so short of available width that the
2065
                //     soft hyphen is the first breakable position, then
2066
                //     we don't want to show it. However we initially
2067
                //     have to take the width for it into account so that
2068
                //     the text document layout sees the overflow and
2069
                //     switch to break-anywhere mode, in which we
2070
                //     want the soft-hyphen to slip into the next line
2071
                //     and thus become invisible again.
2072
                //
2073
0
                lbh.currentSoftHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]];
2074
0
            }
2075
2076
0
            if (sb_or_ws|breakany) {
2077
                // To compute the final width of the text we need to take negative right bearing
2078
                // into account (negative right bearing means the glyph has pixel data past the
2079
                // advance length). Note that the negative right bearing is an absolute number,
2080
                // so that we can apply it to the width using straight forward addition.
2081
2082
                // Store previous right bearing (for the already accepted glyph) in case we
2083
                // end up breaking due to the current glyph being too wide.
2084
0
                QFixed previousRightBearing = lbh.rightBearing;
2085
2086
                // We skip calculating the right bearing if the minimum negative bearing is too
2087
                // small to possibly expand the text beyond the edge. Note that this optimization
2088
                // will in some cases fail, as the minimum right bearing reported by the font
2089
                // engine may not cover all the glyphs in the font. The result is that we think
2090
                // we don't need to break at the current glyph (because the right bearing is 0),
2091
                // and when we then end up breaking on the next glyph we compute the right bearing
2092
                // and end up with a line width that is slightly larger width than what was requested.
2093
                // Unfortunately we can't remove this optimization as it will slow down text
2094
                // layouting significantly, so we accept the slight correctness issue.
2095
0
                if ((lbh.calculateNewWidth(line) + qAbs(lbh.minimumRightBearing)) > line.width)
2096
0
                    lbh.calculateRightBearing();
2097
2098
0
                if (lbh.checkFullOtherwiseExtend(line)) {
2099
2100
                    // We are too wide to accept the next glyph with its bearing, so we restore the
2101
                    // right bearing to that of the previous glyph (the one that was already accepted),
2102
                    // so that the bearing can be be applied to the final width of the text below.
2103
0
                    if (previousRightBearing != LineBreakHelper::RightBearingNotCalculated)
2104
0
                        lbh.rightBearing = previousRightBearing;
2105
0
                    else
2106
0
                        lbh.calculateRightBearingForPreviousGlyph();
2107
2108
0
                    line.textWidth += lbh.commitedSoftHyphenWidth;
2109
2110
0
                    goto found;
2111
0
                }
2112
0
            }
2113
0
            lbh.saveCurrentGlyph();
2114
0
        }
2115
0
        if (lbh.currentPosition == end)
2116
0
            newItem = item + 1;
2117
0
    }
2118
0
    LB_DEBUG("reached end of line");
2119
0
    reachedEndOfLine = true;
2120
0
    lbh.checkFullOtherwiseExtend(line);
2121
0
    line.textWidth += lbh.commitedSoftHyphenWidth;
2122
0
found:
2123
0
    line.textAdvance = line.textWidth;
2124
2125
    // If right bearing has not been calculated yet, do that now
2126
0
    if (lbh.rightBearing == LineBreakHelper::RightBearingNotCalculated && !lbh.whiteSpaceOrObject)
2127
0
        lbh.calculateRightBearing();
2128
2129
    // Then apply any negative right bearing
2130
0
    const QFixed textWidthWithoutBearing = line.textWidth;
2131
0
    line.textWidth += lbh.negativeRightBearing();
2132
2133
0
    if (line.length == 0) {
2134
0
        LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2135
0
               lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2136
0
               lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2137
0
        line += lbh.tmpData;
2138
0
    }
2139
2140
0
    if (hasInlineObject && QTextDocumentPrivate::get(eng->block) != nullptr) {
2141
        // position top/bottom aligned inline objects
2142
0
        if (maxInlineObjectHeight > line.ascent + line.descent) {
2143
            // extend line height if required
2144
0
            QFixed toAdd = (maxInlineObjectHeight - line.ascent - line.descent)/2;
2145
0
            line.ascent += toAdd;
2146
0
            line.descent = maxInlineObjectHeight - line.ascent;
2147
0
        }
2148
0
        int startItem = eng->findItem(line.from);
2149
0
        int endItem = eng->findItem(line.from + line.length);
2150
0
        if (endItem < 0)
2151
0
            endItem = eng->layoutData->items.size();
2152
0
        for (int item = startItem; item < endItem; ++item) {
2153
0
            QScriptItem &current = eng->layoutData->items[item];
2154
0
            if (current.analysis.flags == QScriptAnalysis::Object) {
2155
0
                QTextInlineObject inlineObject(item, eng);
2156
0
                QTextCharFormat::VerticalAlignment align = inlineObject.format().toCharFormat().verticalAlignment();
2157
0
                QFixed height = current.ascent + current.descent;
2158
0
                switch (align) {
2159
0
                case QTextCharFormat::AlignTop:
2160
0
                    current.ascent = line.ascent;
2161
0
                    current.descent = height - line.ascent;
2162
0
                    break;
2163
0
                case QTextCharFormat::AlignMiddle:
2164
0
                    current.ascent = (line.ascent + line.descent) / 2 - line.descent + height / 2;
2165
0
                    current.descent = height - line.ascent;
2166
0
                    break;
2167
0
                case QTextCharFormat::AlignBottom:
2168
0
                    current.descent = line.descent;
2169
0
                    current.ascent = height - line.descent;
2170
0
                    break;
2171
0
                default:
2172
0
                    break;
2173
0
                }
2174
0
                Q_ASSERT(line.ascent >= current.ascent);
2175
0
                Q_ASSERT(line.descent >= current.descent);
2176
0
            }
2177
0
        }
2178
0
    }
2179
2180
2181
0
    LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2182
0
           line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2183
0
    LB_DEBUG("        : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2184
2185
0
    const QFixed trailingSpace = (includeTrailingSpaces ? lbh.spaceData.textWidth : QFixed(0));
2186
0
    if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2187
0
        if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2188
0
            || (lbh.maxGlyphs == INT_MAX && line.textWidth > (line.width -  trailingSpace))) {
2189
2190
0
            eng->option.setWrapMode(QTextOption::WrapAnywhere);
2191
0
            layout_helper(lbh.maxGlyphs);
2192
0
            eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2193
0
            return;
2194
0
        }
2195
0
    }
2196
2197
0
    if (lbh.manualWrap) {
2198
0
        eng->minWidth = qMax(eng->minWidth, line.textWidth);
2199
0
        eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2200
0
    } else {
2201
0
        eng->minWidth = qMax(eng->minWidth, lbh.minw);
2202
2203
0
        const QFixed actualTextWidth = manuallyWrapped || reachedEndOfLine
2204
0
                                           ? line.textWidth
2205
0
                                           : textWidthWithoutBearing;
2206
0
        if (qAddOverflow(eng->layoutData->currentMaxWidth, actualTextWidth, &eng->layoutData->currentMaxWidth))
2207
0
            eng->layoutData->currentMaxWidth = QFIXED_MAX;
2208
0
        if (!manuallyWrapped) {
2209
0
            if (qAddOverflow(eng->layoutData->currentMaxWidth, lbh.spaceData.textWidth, &eng->layoutData->currentMaxWidth))
2210
0
                eng->layoutData->currentMaxWidth = QFIXED_MAX;
2211
0
        }
2212
0
        eng->maxWidth = qMax(eng->maxWidth, eng->layoutData->currentMaxWidth);
2213
0
        if (manuallyWrapped)
2214
0
            eng->layoutData->currentMaxWidth = 0;
2215
0
    }
2216
2217
0
    line.textWidth += trailingSpace;
2218
0
    if (lbh.spaceData.length) {
2219
0
        line.trailingSpaces = lbh.spaceData.length;
2220
0
        line.hasTrailingSpaces = true;
2221
0
    }
2222
2223
0
    line.justified = false;
2224
0
    line.gridfitted = false;
2225
0
}
2226
2227
/*!
2228
    Moves the line to position \a pos.
2229
*/
2230
void QTextLine::setPosition(const QPointF &pos)
2231
0
{
2232
0
    eng->lines[index].x = QFixed::fromReal(pos.x());
2233
0
    eng->lines[index].y = QFixed::fromReal(pos.y());
2234
0
}
2235
2236
/*!
2237
    Returns the line's position relative to the text layout's position.
2238
*/
2239
QPointF QTextLine::position() const
2240
0
{
2241
0
    return QPointF(eng->lines.at(index).x.toReal(), eng->lines.at(index).y.toReal());
2242
0
}
2243
2244
// ### DOC: I have no idea what this means/does.
2245
// You create a text layout with a string of text. Once you laid
2246
// it out, it contains a number of QTextLines. from() returns the position
2247
// inside the text string where this line starts. If you e.g. has a
2248
// text of "This is a string", laid out into two lines (the second
2249
// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2250
// layout.lineAt(1).from() == 8.
2251
/*!
2252
    Returns the start of the line from the beginning of the string
2253
    passed to the QTextLayout.
2254
*/
2255
int QTextLine::textStart() const
2256
0
{
2257
0
    return eng->lines.at(index).from;
2258
0
}
2259
2260
/*!
2261
    Returns the length of the text in the line.
2262
2263
    \sa naturalTextWidth()
2264
*/
2265
int QTextLine::textLength() const
2266
0
{
2267
0
    if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2268
0
        && eng->block.isValid() && index == eng->lines.size()-1) {
2269
0
        return eng->lines.at(index).length - 1;
2270
0
    }
2271
0
    return eng->lines.at(index).length + eng->lines.at(index).trailingSpaces;
2272
0
}
2273
2274
static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
2275
0
{
2276
0
    QBrush bg = chf.background();
2277
0
    if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2278
0
        p->fillRect(r.toAlignedRect(), bg);
2279
0
}
2280
2281
static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
2282
0
{
2283
0
    QBrush c = chf.foreground();
2284
0
    if (c.style() == Qt::NoBrush)
2285
0
        p->setPen(defaultPen);
2286
0
    else
2287
0
        p->setPen(QPen(c, 0));
2288
0
}
2289
2290
#if !defined(QT_NO_RAWFONT)
2291
static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
2292
                                  const QString &text,
2293
                                  const QGlyphLayout &glyphLayout,
2294
                                  const QPointF &pos,
2295
                                  const QGlyphRun::GlyphRunFlags &flags,
2296
                                  QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
2297
                                  QFixed selectionX,
2298
                                  QFixed selectionWidth,
2299
                                  int glyphsStart,
2300
                                  int glyphsEnd,
2301
                                  unsigned short *logClusters,
2302
                                  int textPosition,
2303
                                  int textLength)
2304
0
{
2305
0
    Q_ASSERT(logClusters != nullptr);
2306
2307
0
    QGlyphRun glyphRun;
2308
2309
0
    QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun);
2310
2311
0
    int rangeStart = textPosition;
2312
0
    int logClusterIndex = 0;
2313
0
    while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
2314
0
        ++logClusterIndex;
2315
0
        ++rangeStart;
2316
0
    }
2317
2318
0
    int rangeEnd = rangeStart;
2319
0
    while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
2320
0
        ++logClusterIndex;
2321
0
        ++rangeEnd;
2322
0
    }
2323
2324
0
    d->textRangeStart = rangeStart;
2325
0
    d->textRangeEnd = rangeEnd;
2326
2327
    // Make a font for this particular engine
2328
0
    QRawFont font;
2329
0
    QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2330
0
    fontD->setFontEngine(fontEngine);
2331
2332
0
    QVarLengthArray<glyph_t> glyphsArray;
2333
0
    QVarLengthArray<QFixedPoint> positionsArray;
2334
2335
0
    QTextItem::RenderFlags renderFlags;
2336
0
    if (flags.testFlag(QGlyphRun::Overline))
2337
0
        renderFlags |= QTextItem::Overline;
2338
0
    if (flags.testFlag(QGlyphRun::Underline))
2339
0
        renderFlags |= QTextItem::Underline;
2340
0
    if (flags.testFlag(QGlyphRun::StrikeOut))
2341
0
        renderFlags |= QTextItem::StrikeOut;
2342
0
    if (flags.testFlag(QGlyphRun::RightToLeft))
2343
0
        renderFlags |= QTextItem::RightToLeft;
2344
2345
0
    fontEngine->getGlyphPositions(glyphLayout, QTransform(), renderFlags, glyphsArray,
2346
0
                                  positionsArray);
2347
0
    Q_ASSERT(glyphsArray.size() == positionsArray.size());
2348
2349
0
    qreal fontHeight = font.ascent() + font.descent();
2350
0
    qreal minY = 0;
2351
0
    qreal maxY = 0;
2352
0
    QList<quint32> glyphs;
2353
0
    if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2354
0
        glyphs.reserve(glyphsArray.size());
2355
0
    QList<QPointF> positions;
2356
0
    if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2357
0
        positions.reserve(glyphsArray.size());
2358
0
    QList<qsizetype> stringIndexes;
2359
0
    if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2360
0
        stringIndexes.reserve(glyphsArray.size());
2361
2362
0
    int nextClusterIndex = 0;
2363
0
    int currentClusterIndex = 0;
2364
0
    for (int i = 0; i < glyphsArray.size(); ++i) {
2365
0
        const int glyphArrayIndex = i + glyphsStart;
2366
        // Search for the next cluster in the string (or the end of string if there are no
2367
        // more clusters)
2368
0
        if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
2369
0
            if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
2370
0
                currentClusterIndex = nextClusterIndex; // Store current cluster
2371
0
                while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
2372
0
                    ++nextClusterIndex;
2373
0
            }
2374
2375
            // We are now either at end of string (no more clusters) or we are not yet at the
2376
            // next cluster in glyph array. We fill in current cluster so that there is always one
2377
            // entry in stringIndexes for each glyph.
2378
0
            Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
2379
0
            stringIndexes.append(textPosition + currentClusterIndex);
2380
0
        }
2381
2382
0
        if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
2383
0
            glyph_t glyphIndex = glyphsArray.at(i) & 0xffffff;
2384
0
            glyphs.append(glyphIndex);
2385
0
        }
2386
2387
0
        QPointF position = positionsArray.at(i).toPointF() + pos;
2388
0
        if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2389
0
            positions.append(position);
2390
2391
0
        if (i == 0) {
2392
0
            maxY = minY = position.y();
2393
0
        } else {
2394
0
            minY = qMin(minY, position.y());
2395
0
            maxY = qMax(maxY, position.y());
2396
0
        }
2397
0
    }
2398
2399
0
    qreal height = maxY + fontHeight - minY;
2400
2401
0
    if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2402
0
        glyphRun.setGlyphIndexes(glyphs);
2403
0
    if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2404
0
        glyphRun.setPositions(positions);
2405
0
    if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2406
0
        glyphRun.setStringIndexes(stringIndexes);
2407
0
    if (retrievalFlags & QTextLayout::RetrieveString)
2408
0
        glyphRun.setSourceString(text);
2409
0
    glyphRun.setFlags(flags);
2410
0
    glyphRun.setRawFont(font);
2411
2412
0
    glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY - font.ascent(),
2413
0
                                    selectionWidth.toReal(), height));
2414
2415
0
    return glyphRun;
2416
0
}
2417
2418
#  if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
2419
/*!
2420
    \overload
2421
    Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2422
    in the range defined by \a from and \a length. The \a from index is relative to the beginning
2423
    of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2424
    as given by functions textStart() and textLength().
2425
2426
    If \a from is negative, it will default to textStart(), and if \a length is negative it will
2427
    default to the return value of textLength().
2428
2429
    \note This is equivalent to calling
2430
    glyphRuns(from,
2431
              length,
2432
              QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
2433
                QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
2434
2435
    \since 5.0
2436
2437
    \sa QTextLayout::glyphRuns()
2438
*/
2439
QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2440
0
{
2441
0
    return glyphRuns(from, length, QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
2442
0
}
2443
#  endif
2444
2445
/*!
2446
    Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2447
    in the range defined by \a from and \a length. The \a from index is relative to the beginning
2448
    of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2449
    as given by functions textStart() and textLength().
2450
2451
    The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
2452
    layout. To minimize allocations and memory consumption, this should be set to include only the
2453
    properties that you need to access later.
2454
2455
    If \a from is negative, it will default to textStart(), and if \a length is negative it will
2456
    default to the return value of textLength().
2457
2458
    \since 6.5
2459
2460
    \sa QTextLayout::glyphRuns()
2461
*/
2462
QList<QGlyphRun> QTextLine::glyphRuns(int from,
2463
                                      int length,
2464
                                      QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
2465
0
{
2466
0
    const QScriptLine &line = eng->lines.at(index);
2467
2468
0
    if (line.length == 0)
2469
0
        return QList<QGlyphRun>();
2470
2471
0
    if (from < 0)
2472
0
        from = textStart();
2473
2474
0
    if (length < 0)
2475
0
        length = textLength();
2476
2477
0
    if (length == 0)
2478
0
        return QList<QGlyphRun>();
2479
2480
0
    QTextLayout::FormatRange selection;
2481
0
    selection.start = from;
2482
0
    selection.length = length;
2483
2484
0
    QTextLineItemIterator iterator(eng, index, QPointF(), &selection);
2485
0
    qreal y = line.y.toReal() + line.base().toReal();
2486
0
    QList<QGlyphRun> glyphRuns;
2487
0
    while (!iterator.atEnd()) {
2488
0
        QScriptItem &si = iterator.next();
2489
0
        if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2490
0
            continue;
2491
2492
0
        if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart))
2493
0
            continue;
2494
2495
0
        QPointF pos(iterator.x.toReal(), y);
2496
2497
0
        QFont font;
2498
0
        QGlyphRun::GlyphRunFlags flags;
2499
0
        if (!eng->useRawFont) {
2500
0
            font = eng->font(si);
2501
0
            if (font.overline())
2502
0
                flags |= QGlyphRun::Overline;
2503
0
            if (font.underline())
2504
0
                flags |= QGlyphRun::Underline;
2505
0
            if (font.strikeOut())
2506
0
                flags |= QGlyphRun::StrikeOut;
2507
0
        }
2508
2509
0
        bool rtl = false;
2510
0
        if (si.analysis.bidiLevel % 2) {
2511
0
            flags |= QGlyphRun::RightToLeft;
2512
0
            rtl = true;
2513
0
        }
2514
2515
0
        int relativeFrom = qMax(iterator.itemStart, from) - si.position;
2516
0
        int relativeTo = qMin(iterator.itemEnd, from + length) - 1 - si.position;
2517
2518
0
        unsigned short *logClusters = eng->logClusters(&si);
2519
0
        int glyphsStart = logClusters[relativeFrom];
2520
0
        int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo];
2521
        // the glyph index right next to the requested range
2522
0
        int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs;
2523
0
        if (nextGlyphIndex - 1 > glyphsEnd)
2524
0
            glyphsEnd = nextGlyphIndex - 1;
2525
0
        bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart;
2526
0
        bool endsInsideLigature = nextGlyphIndex == glyphsEnd;
2527
2528
0
        int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
2529
0
        int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
2530
2531
0
        QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
2532
2533
        // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2534
        // when we're breaking a RTL script item, since the expected position passed into
2535
        // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2536
0
        if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2537
0
            for (int i = itemGlyphsStart; i < glyphsStart; ++i) {
2538
0
                if (!glyphLayout.attributes[i].dontPrint) {
2539
0
                    QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2540
0
                    pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2541
0
                }
2542
0
            }
2543
0
        } else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
2544
0
            for (int i = itemGlyphsEnd; i > glyphsEnd; --i) {
2545
0
                if (!glyphLayout.attributes[i].dontPrint) {
2546
0
                    QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2547
0
                    pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2548
0
                }
2549
0
            }
2550
0
        }
2551
2552
0
        glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
2553
2554
0
        QFixed x;
2555
0
        QFixed width;
2556
0
        iterator.getSelectionBounds(&x, &width);
2557
2558
0
        if (glyphLayout.numGlyphs > 0) {
2559
0
            QFontEngine *mainFontEngine;
2560
0
#ifndef QT_NO_RAWFONT
2561
0
            if (eng->useRawFont && eng->rawFont.isValid())
2562
0
                mainFontEngine= eng->fontEngine(si);
2563
0
            else
2564
0
#endif
2565
0
                mainFontEngine = font.d->engineForScript(si.analysis.script);
2566
2567
0
            if (mainFontEngine->type() == QFontEngine::Multi) {
2568
0
                QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2569
0
                int start = rtl ? glyphLayout.numGlyphs : 0;
2570
0
                int end = start - 1;
2571
0
                int which = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2572
0
                for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs - 1);
2573
0
                     rtl ? --start : ++end) {
2574
0
                    const int e = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2575
0
                    if (e == which)
2576
0
                        continue;
2577
2578
0
                    QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2579
0
                    multiFontEngine->ensureEngineAt(which);
2580
2581
0
                    QGlyphRun::GlyphRunFlags subFlags = flags;
2582
0
                    if (start == 0 && startsInsideLigature)
2583
0
                        subFlags |= QGlyphRun::SplitLigature;
2584
2585
0
                    {
2586
0
                        QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2587
0
                                                              eng->text,
2588
0
                                                              subLayout,
2589
0
                                                              pos,
2590
0
                                                              subFlags,
2591
0
                                                              retrievalFlags,
2592
0
                                                              x,
2593
0
                                                              width,
2594
0
                                                              glyphsStart + start,
2595
0
                                                              glyphsStart + end,
2596
0
                                                              logClusters + relativeFrom,
2597
0
                                                              relativeFrom + si.position,
2598
0
                                                              relativeTo - relativeFrom + 1);
2599
0
                        if (!glyphRun.isEmpty())
2600
0
                            glyphRuns.append(glyphRun);
2601
0
                    }
2602
0
                    for (int i = 0; i < subLayout.numGlyphs; ++i) {
2603
0
                        if (!subLayout.attributes[i].dontPrint) {
2604
0
                            QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6);
2605
0
                            pos.rx() += (subLayout.advances[i] + justification).toReal();
2606
0
                        }
2607
0
                    }
2608
2609
0
                    if (rtl)
2610
0
                        end = start - 1;
2611
0
                    else
2612
0
                        start = end + 1;
2613
0
                    which = e;
2614
0
                }
2615
2616
0
                QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2617
0
                multiFontEngine->ensureEngineAt(which);
2618
2619
0
                QGlyphRun::GlyphRunFlags subFlags = flags;
2620
0
                if ((start == 0 && startsInsideLigature) || endsInsideLigature)
2621
0
                    subFlags |= QGlyphRun::SplitLigature;
2622
2623
0
                QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2624
0
                                                      eng->text,
2625
0
                                                      subLayout,
2626
0
                                                      pos,
2627
0
                                                      subFlags,
2628
0
                                                      retrievalFlags,
2629
0
                                                      x,
2630
0
                                                      width,
2631
0
                                                      glyphsStart + start,
2632
0
                                                      glyphsStart + end,
2633
0
                                                      logClusters + relativeFrom,
2634
0
                                                      relativeFrom + si.position,
2635
0
                                                      relativeTo - relativeFrom + 1);
2636
0
                if (!glyphRun.isEmpty())
2637
0
                    glyphRuns.append(glyphRun);
2638
0
            } else {
2639
0
                if (startsInsideLigature || endsInsideLigature)
2640
0
                    flags |= QGlyphRun::SplitLigature;
2641
0
                QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine,
2642
0
                                                      eng->text,
2643
0
                                                      glyphLayout,
2644
0
                                                      pos,
2645
0
                                                      flags,
2646
0
                                                      retrievalFlags,
2647
0
                                                      x,
2648
0
                                                      width,
2649
0
                                                      glyphsStart,
2650
0
                                                      glyphsEnd,
2651
0
                                                      logClusters + relativeFrom,
2652
0
                                                      relativeFrom + si.position,
2653
0
                                                      relativeTo - relativeFrom + 1);
2654
0
                if (!glyphRun.isEmpty())
2655
0
                    glyphRuns.append(glyphRun);
2656
0
            }
2657
0
        }
2658
0
    }
2659
2660
0
    return glyphRuns;
2661
0
}
2662
#endif // QT_NO_RAWFONT
2663
2664
/*!
2665
    \fn void QTextLine::draw(QPainter *painter, const QPointF &position) const
2666
2667
    Draws a line on the given \a painter at the specified \a position.
2668
*/
2669
void QTextLine::draw(QPainter *painter, const QPointF &position) const
2670
0
{
2671
0
    draw_internal(painter, position, nullptr);
2672
0
}
2673
2674
void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
2675
                              const QTextLayout::FormatRange *selection) const
2676
0
{
2677
0
#ifndef QT_NO_RAWFONT
2678
    // Not intended to work with rawfont
2679
0
    Q_ASSERT(!eng->useRawFont);
2680
0
#endif
2681
0
    const QScriptLine &line = eng->lines[index];
2682
2683
0
    bool noText = (selection && selection->format.property(SuppressText).toBool());
2684
2685
0
    if (!line.length) {
2686
0
        if (selection
2687
0
            && selection->start <= line.from
2688
0
            && selection->start + selection->length > line.from) {
2689
2690
0
            const qreal lineHeight = line.height().toReal();
2691
0
            QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
2692
0
                     lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
2693
0
            drawBackground(p, selection->format, r);
2694
0
        }
2695
0
        return;
2696
0
    }
2697
2698
0
    Q_CONSTINIT static QRectF maxFixedRect(-QFIXED_MAX / 2, -QFIXED_MAX / 2, QFIXED_MAX, QFIXED_MAX);
2699
0
    const bool xlateToFixedRange = !maxFixedRect.contains(origPos);
2700
0
    QPointF pos;
2701
0
    if (Q_LIKELY(!xlateToFixedRange))
2702
0
        pos = origPos;
2703
0
    else
2704
0
        p->translate(origPos);
2705
2706
2707
0
    QFixed lineBase = line.base();
2708
0
    eng->clearDecorations();
2709
0
    eng->enableDelayDecorations();
2710
2711
0
    const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2712
2713
0
    const QTextFormatCollection *formatCollection = eng->formatCollection();
2714
2715
0
    bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2716
2717
0
    auto prepareFormat = [suppressColors, selection, this](QTextCharFormat &format,
2718
0
                                                           QScriptItem *si) {
2719
0
        format.merge(eng->format(si));
2720
2721
0
        if (suppressColors) {
2722
0
            format.clearForeground();
2723
0
            format.clearBackground();
2724
0
            format.clearProperty(QTextFormat::TextUnderlineColor);
2725
0
        }
2726
0
        if (selection)
2727
0
            format.merge(selection->format);
2728
0
    };
2729
2730
0
    {
2731
0
        QTextLineItemIterator iterator(eng, index, pos, selection);
2732
0
        while (!iterator.atEnd()) {
2733
0
            QScriptItem &si = iterator.next();
2734
2735
0
            if (eng->hasFormats() || selection || formatCollection) {
2736
0
                QTextCharFormat format;
2737
0
                if (formatCollection != nullptr)
2738
0
                    format = formatCollection->defaultTextFormat();
2739
0
                prepareFormat(format, &si);
2740
0
                drawBackground(p, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2741
0
                                                 iterator.itemWidth.toReal(), line.height().toReal()));
2742
0
            }
2743
0
        }
2744
0
    }
2745
2746
0
    QPen pen = p->pen();
2747
0
    {
2748
0
        QTextLineItemIterator iterator(eng, index, pos, selection);
2749
0
        while (!iterator.atEnd()) {
2750
0
            QScriptItem &si = iterator.next();
2751
2752
0
            if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2753
0
                continue;
2754
2755
0
            if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2756
0
                && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2757
0
                continue;
2758
2759
0
            QFixed itemBaseLine = y;
2760
0
            QFont f = eng->font(si);
2761
0
            QTextCharFormat format;
2762
0
            if (formatCollection != nullptr)
2763
0
                format = formatCollection->defaultTextFormat();
2764
2765
0
            if (eng->hasFormats() || selection || formatCollection) {
2766
0
                prepareFormat(format, &si);
2767
0
                setPen(p, pen, format);
2768
2769
0
                const qreal baseLineOffset = format.baselineOffset() / 100.0;
2770
0
                QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2771
0
                if (valign == QTextCharFormat::AlignSuperScript
2772
0
                    || valign == QTextCharFormat::AlignSubScript
2773
0
                    || !qFuzzyIsNull(baseLineOffset))
2774
0
                {
2775
0
                    QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2776
0
                    QFixed height = fe->ascent() + fe->descent();
2777
0
                    itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
2778
2779
0
                    if (valign == QTextCharFormat::AlignSubScript)
2780
0
                        itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
2781
0
                    else if (valign == QTextCharFormat::AlignSuperScript)
2782
0
                        itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
2783
0
                }
2784
0
            }
2785
2786
0
            if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2787
2788
0
                if (eng->hasFormats()) {
2789
0
                    p->save();
2790
0
                    if (si.analysis.flags == QScriptAnalysis::Object && QTextDocumentPrivate::get(eng->block)) {
2791
0
                        QFixed itemY = y - si.ascent;
2792
0
                        switch (format.verticalAlignment()) {
2793
0
                        case QTextCharFormat::AlignTop:
2794
0
                            itemY = y - lineBase;
2795
0
                            break;
2796
0
                        case QTextCharFormat::AlignMiddle:
2797
0
                            itemY = y - lineBase + (line.height() - si.height()) / 2;
2798
0
                            break;
2799
0
                        case QTextCharFormat::AlignBottom:
2800
0
                            itemY = y - lineBase + line.height() - si.height();
2801
0
                            break;
2802
0
                        default:
2803
0
                            break;
2804
0
                        }
2805
2806
0
                        QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2807
2808
0
                        eng->docLayout()->drawInlineObject(p, itemRect,
2809
0
                                                           QTextInlineObject(iterator.item, eng),
2810
0
                                                           si.position + eng->block.position(),
2811
0
                                                           format);
2812
0
                        if (selection) {
2813
0
                            QBrush bg = format.brushProperty(ObjectSelectionBrush);
2814
0
                            if (bg.style() != Qt::NoBrush) {
2815
0
                                QColor c = bg.color();
2816
0
                                c.setAlpha(128);
2817
0
                                p->fillRect(itemRect, c);
2818
0
                            }
2819
0
                        }
2820
0
                    } else { // si.isTab
2821
0
                        QFont f = eng->font(si);
2822
0
                        QTextItemInt gf(si, &f, format);
2823
0
                        gf.chars = nullptr;
2824
0
                        gf.num_chars = 0;
2825
0
                        gf.width = iterator.itemWidth;
2826
0
                        QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
2827
0
                        if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2828
0
                            const QChar visualTab = QChar(QChar::VisualTabCharacter);
2829
0
                            int w = QFontMetrics(f).horizontalAdvance(visualTab);
2830
0
                            qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2831
0
                            if (x < 0)
2832
0
                                 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2833
0
                                                       iterator.itemWidth.toReal(), line.height().toReal()),
2834
0
                                                Qt::IntersectClip);
2835
0
                            else
2836
0
                                 x /= 2; // Centered
2837
0
                            p->setFont(f);
2838
0
                            p->drawText(QPointF(iterator.x.toReal() + x,
2839
0
                                                y.toReal()), visualTab);
2840
0
                        }
2841
2842
0
                    }
2843
0
                    p->restore();
2844
0
                }
2845
2846
0
                continue;
2847
0
            }
2848
2849
0
            unsigned short *logClusters = eng->logClusters(&si);
2850
0
            QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2851
2852
0
            QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
2853
0
                            &f, eng->layoutData->string.unicode() + iterator.itemStart,
2854
0
                            iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2855
0
            gf.logClusters = logClusters + iterator.itemStart - si.position;
2856
0
            gf.width = iterator.itemWidth;
2857
0
            gf.justified = line.justified;
2858
0
            gf.initWithScriptItem(si);
2859
2860
0
            Q_ASSERT(gf.fontEngine);
2861
2862
0
            QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2863
0
            if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2864
0
                QPainterPath path;
2865
0
                path.setFillRule(Qt::WindingFill);
2866
2867
0
                if (gf.glyphs.numGlyphs)
2868
0
                    gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2869
0
                if (gf.flags) {
2870
0
                    const QFontEngine *fe = gf.fontEngine;
2871
0
                    const qreal lw = fe->lineThickness().toReal();
2872
0
                    if (gf.flags & QTextItem::Underline) {
2873
0
                        qreal offs = fe->underlinePosition().toReal();
2874
0
                        path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2875
0
                    }
2876
0
                    if (gf.flags & QTextItem::Overline) {
2877
0
                        qreal offs = fe->ascent().toReal() + 1;
2878
0
                        path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2879
0
                    }
2880
0
                    if (gf.flags & QTextItem::StrikeOut) {
2881
0
                        qreal offs = fe->ascent().toReal() / 3;
2882
0
                        path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2883
0
                    }
2884
0
                }
2885
2886
0
                p->save();
2887
0
                p->setRenderHint(QPainter::Antialiasing);
2888
                //Currently QPen with a Qt::NoPen style still returns a default
2889
                //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2890
0
                if (p->pen().style() == Qt::NoPen)
2891
0
                    p->setBrush(Qt::NoBrush);
2892
0
                else
2893
0
                    p->setBrush(p->pen().brush());
2894
2895
0
                p->setPen(format.textOutline());
2896
0
                p->drawPath(path);
2897
0
                p->restore();
2898
0
            } else {
2899
0
                if (noText)
2900
0
                    gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2901
0
                QPainterPrivate::get(p)->drawTextItem(pos, gf, eng);
2902
0
            }
2903
2904
0
            if ((si.analysis.flags == QScriptAnalysis::Space
2905
0
                 || si.analysis.flags == QScriptAnalysis::Nbsp)
2906
0
                && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2907
0
                QBrush c = format.foreground();
2908
0
                if (c.style() != Qt::NoBrush)
2909
0
                    p->setPen(c.color());
2910
0
                const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
2911
0
                QFont oldFont = p->font();
2912
0
                p->setFont(eng->font(si));
2913
0
                p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2914
0
                p->setPen(pen);
2915
0
                p->setFont(oldFont);
2916
0
            }
2917
0
        }
2918
0
    }
2919
0
    eng->drawDecorations(p);
2920
2921
0
    if (xlateToFixedRange)
2922
0
        p->translate(-origPos);
2923
2924
0
    if (eng->hasFormats())
2925
0
        p->setPen(pen);
2926
0
}
2927
2928
/*!
2929
    \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2930
2931
    \overload
2932
*/
2933
2934
/*!
2935
    Converts the cursor position \a cursorPos to the corresponding x position
2936
    inside the line, taking account of the \a edge.
2937
2938
    If \a cursorPos is not a valid cursor position, the nearest valid
2939
    cursor position will be used instead, and \a cursorPos will be modified to
2940
    point to this valid cursor position.
2941
2942
    \sa xToCursor()
2943
*/
2944
qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2945
0
{
2946
0
    const QScriptLine &line = eng->lines[index];
2947
0
    bool lastLine = index >= eng->lines.size() - 1;
2948
2949
0
    QFixed x = line.x + eng->alignLine(line) - eng->leadingSpaceWidth(line);
2950
2951
0
    if (!eng->layoutData)
2952
0
        eng->itemize();
2953
0
    if (!eng->layoutData->items.size()) {
2954
0
        *cursorPos = line.from;
2955
0
        return x.toReal();
2956
0
    }
2957
2958
0
    int lineEnd = line.from + line.length + line.trailingSpaces;
2959
0
    int pos = qBound(line.from, *cursorPos, lineEnd);
2960
0
    const QCharAttributes *attributes = eng->attributes();
2961
0
    if (!attributes) {
2962
0
        *cursorPos = line.from;
2963
0
        return x.toReal();
2964
0
    }
2965
0
    while (pos < lineEnd && !attributes[pos].graphemeBoundary)
2966
0
        pos++;
2967
    // end of line ensure we have the last item on the line
2968
0
    int itm = pos == lineEnd ? eng->findItem(pos-1) : eng->findItem(pos);
2969
0
    if (itm < 0) {
2970
0
        *cursorPos = line.from;
2971
0
        return x.toReal();
2972
0
    }
2973
0
    eng->shapeLine(line);
2974
2975
0
    const QScriptItem *scriptItem = &eng->layoutData->items[itm];
2976
0
    if (!scriptItem->num_glyphs)
2977
0
        eng->shape(itm);
2978
2979
0
    if ((scriptItem->analysis.bidiLevel % 2 != eng->isRightToLeft()) && !eng->visualCursorMovement()) {
2980
        // If the item we found has a different writing direction than the engine,
2981
        // check if the cursor is between two items with different writing direction
2982
0
        int neighborItem = itm;
2983
0
        if (neighborItem > 0 && scriptItem->position == pos)
2984
0
            --neighborItem;
2985
0
        else if (neighborItem < eng->layoutData->items.size() - 1 && scriptItem->position + scriptItem->num_glyphs == pos)
2986
0
            ++neighborItem;
2987
0
        const bool onBoundary = neighborItem != itm && scriptItem->analysis.bidiLevel != eng->layoutData->items[neighborItem].analysis.bidiLevel;
2988
        // If we are, prioritise the neighbor item that has the same direction as the engine
2989
0
        if (onBoundary) {
2990
0
            if (eng->isRightToLeft() != scriptItem->analysis.bidiLevel % 2) {
2991
0
                itm = neighborItem;
2992
0
                scriptItem = &eng->layoutData->items[itm];
2993
0
                if (!scriptItem->num_glyphs)
2994
0
                    eng->shape(itm);
2995
0
            }
2996
0
        }
2997
0
    }
2998
2999
0
    const int l = eng->length(itm);
3000
0
    pos = qBound(0, pos - scriptItem->position, l);
3001
3002
0
    QGlyphLayout glyphs = eng->shapedGlyphs(scriptItem);
3003
0
    unsigned short *logClusters = eng->logClusters(scriptItem);
3004
0
    Q_ASSERT(logClusters);
3005
3006
0
    int glyph_pos = pos == l ? scriptItem->num_glyphs : logClusters[pos];
3007
0
    if (edge == Trailing && glyph_pos < scriptItem->num_glyphs) {
3008
        // trailing edge is leading edge of next cluster
3009
0
        glyph_pos++;
3010
0
        while (glyph_pos < scriptItem->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
3011
0
            glyph_pos++;
3012
0
    }
3013
3014
0
    bool reverse = scriptItem->analysis.bidiLevel % 2;
3015
3016
3017
    // add the items left of the cursor
3018
3019
0
    int firstItem = eng->findItem(line.from);
3020
0
    int lastItem = eng->findItem(lineEnd - 1, itm);
3021
0
    int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3022
3023
0
    QVarLengthArray<int> visualOrder(nItems);
3024
0
    QVarLengthArray<uchar> levels(nItems);
3025
0
    for (int i = 0; i < nItems; ++i)
3026
0
        levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3027
0
    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3028
3029
0
    for (int i = 0; i < nItems; ++i) {
3030
0
        int item = visualOrder[i]+firstItem;
3031
0
        if (item == itm)
3032
0
            break;
3033
0
        QScriptItem &si = eng->layoutData->items[item];
3034
0
        if (!si.num_glyphs)
3035
0
            eng->shape(item);
3036
3037
0
        if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3038
0
            x += si.width;
3039
0
            continue;
3040
0
        }
3041
3042
0
        const int itemLength = eng->length(item);
3043
0
        int start = qMax(line.from, si.position);
3044
0
        int end = qMin(lineEnd, si.position + itemLength);
3045
3046
0
        logClusters = eng->logClusters(&si);
3047
3048
0
        int gs = logClusters[start-si.position];
3049
0
        int ge = (end == si.position + itemLength) ? si.num_glyphs-1 : logClusters[end-si.position-1];
3050
3051
0
        QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3052
3053
0
        while (gs <= ge) {
3054
0
            x += glyphs.effectiveAdvance(gs);
3055
0
            ++gs;
3056
0
        }
3057
0
    }
3058
3059
0
    logClusters = eng->logClusters(scriptItem);
3060
0
    glyphs = eng->shapedGlyphs(scriptItem);
3061
0
    if (scriptItem->analysis.flags >= QScriptAnalysis::TabOrObject) {
3062
0
        if (pos == (reverse ? 0 : l))
3063
0
            x += scriptItem->width;
3064
0
    } else {
3065
0
        bool rtl = eng->isRightToLeft();
3066
0
        bool visual = eng->visualCursorMovement();
3067
0
        int end = qMin(lineEnd, scriptItem->position + l) - scriptItem->position;
3068
0
        if (reverse) {
3069
0
            int glyph_end = end == l ? scriptItem->num_glyphs : logClusters[end];
3070
0
            int glyph_start = glyph_pos;
3071
0
            if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
3072
0
                glyph_start++;
3073
0
            for (int i = glyph_end - 1; i >= glyph_start; i--)
3074
0
                x += glyphs.effectiveAdvance(i);
3075
0
            x -= eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3076
0
        } else {
3077
0
            int start = qMax(line.from - scriptItem->position, 0);
3078
0
            int glyph_start = logClusters[start];
3079
0
            int glyph_end = glyph_pos;
3080
0
            if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
3081
0
                glyph_end--;
3082
0
            for (int i = glyph_start; i <= glyph_end; i++)
3083
0
                x += glyphs.effectiveAdvance(i);
3084
0
            x += eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3085
0
        }
3086
0
    }
3087
3088
0
    if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width)
3089
0
        x = line.x + line.width;
3090
0
    if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0)
3091
0
        x = 0;
3092
3093
0
    *cursorPos = pos + scriptItem->position;
3094
0
    return x.toReal();
3095
0
}
3096
3097
/*!
3098
    \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
3099
3100
    Converts the x-coordinate \a x, to the nearest matching cursor
3101
    position, depending on the cursor position type, \a cpos.
3102
    Note that result cursor position includes possible preedit area text.
3103
3104
    \sa cursorToX()
3105
*/
3106
int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
3107
0
{
3108
0
    QFixed x = QFixed::fromReal(_x);
3109
0
    const QScriptLine &line = eng->lines[index];
3110
0
    bool lastLine = index >= eng->lines.size() - 1;
3111
0
    int lineNum = index;
3112
3113
0
    if (!eng->layoutData)
3114
0
        eng->itemize();
3115
3116
0
    int line_length = textLength();
3117
3118
0
    if (!line_length)
3119
0
        return line.from;
3120
3121
0
    int firstItem = eng->findItem(line.from);
3122
0
    int lastItem = eng->findItem(line.from + line_length - 1, firstItem);
3123
0
    int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3124
3125
0
    if (!nItems)
3126
0
        return 0;
3127
3128
0
    x -= line.x;
3129
0
    x -= eng->alignLine(line);
3130
//     qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
3131
3132
0
    QVarLengthArray<int> visualOrder(nItems);
3133
0
    QVarLengthArray<unsigned char> levels(nItems);
3134
0
    for (int i = 0; i < nItems; ++i)
3135
0
        levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3136
0
    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3137
3138
0
    bool visual = eng->visualCursorMovement();
3139
0
    if (x <= 0) {
3140
        // left of first item
3141
0
        if (eng->isRightToLeft())
3142
0
            return line.from + line_length;
3143
0
        return line.from;
3144
0
    }   else if (x < line.textWidth || (line.justified && x < line.width)) {
3145
        // has to be in one of the runs
3146
0
        QFixed pos;
3147
0
        bool rtl = eng->isRightToLeft();
3148
3149
0
        eng->shapeLine(line);
3150
0
        const auto insertionPoints = (visual && rtl) ? eng->insertionPointsForLine(lineNum) : std::vector<int>();
3151
0
        int nchars = 0;
3152
0
        for (int i = 0; i < nItems; ++i) {
3153
0
            int item = visualOrder[i]+firstItem;
3154
0
            QScriptItem &si = eng->layoutData->items[item];
3155
0
            int item_length = eng->length(item);
3156
//             qDebug("    item %d, visual %d x_remain=%f", i, item, x.toReal());
3157
3158
0
            int start = qMax(line.from - si.position, 0);
3159
0
            int end = qMin(line.from + line_length - si.position, item_length);
3160
3161
0
            unsigned short *logClusters = eng->logClusters(&si);
3162
3163
0
            int gs = logClusters[start];
3164
0
            int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
3165
0
            QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3166
3167
0
            QFixed item_width = 0;
3168
0
            if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3169
0
                item_width = si.width;
3170
0
            } else {
3171
0
                int g = gs;
3172
0
                while (g <= ge) {
3173
0
                    item_width += glyphs.effectiveAdvance(g);
3174
0
                    ++g;
3175
0
                }
3176
0
            }
3177
//             qDebug("      start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
3178
3179
0
            if (pos + item_width < x) {
3180
0
                pos += item_width;
3181
0
                nchars += end;
3182
0
                continue;
3183
0
            }
3184
//             qDebug("      inside run");
3185
0
            if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3186
0
                if (cpos == QTextLine::CursorOnCharacter)
3187
0
                    return si.position;
3188
0
                bool left_half = (x - pos) < item_width/2;
3189
3190
0
                if (bool(si.analysis.bidiLevel % 2) != left_half)
3191
0
                    return si.position;
3192
0
                return si.position + 1;
3193
0
            }
3194
3195
0
            int glyph_pos = -1;
3196
0
            QFixed edge;
3197
            // has to be inside run
3198
0
            if (cpos == QTextLine::CursorOnCharacter) {
3199
0
                if (si.analysis.bidiLevel % 2) {
3200
0
                    pos += item_width;
3201
0
                    glyph_pos = gs;
3202
0
                    while (gs <= ge) {
3203
0
                        if (glyphs.attributes[gs].clusterStart) {
3204
0
                            if (pos < x)
3205
0
                                break;
3206
0
                            glyph_pos = gs;
3207
0
                            edge = pos;
3208
0
                        }
3209
0
                        pos -= glyphs.effectiveAdvance(gs);
3210
0
                        ++gs;
3211
0
                    }
3212
0
                } else {
3213
0
                    glyph_pos = gs;
3214
0
                    while (gs <= ge) {
3215
0
                        if (glyphs.attributes[gs].clusterStart) {
3216
0
                            if (pos > x)
3217
0
                                break;
3218
0
                            glyph_pos = gs;
3219
0
                            edge = pos;
3220
0
                        }
3221
0
                        pos += glyphs.effectiveAdvance(gs);
3222
0
                        ++gs;
3223
0
                    }
3224
0
                }
3225
0
            } else {
3226
0
                QFixed dist = INT_MAX/256;
3227
0
                if (si.analysis.bidiLevel % 2) {
3228
0
                    if (!visual || rtl || (lastLine && i == nItems - 1)) {
3229
0
                        pos += item_width;
3230
0
                        while (gs <= ge) {
3231
0
                            if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3232
0
                                glyph_pos = gs;
3233
0
                                edge = pos;
3234
0
                                dist = qAbs(x-pos);
3235
0
                            }
3236
0
                            pos -= glyphs.effectiveAdvance(gs);
3237
0
                            ++gs;
3238
0
                        }
3239
0
                    } else {
3240
0
                        while (ge >= gs) {
3241
0
                            if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
3242
0
                                glyph_pos = ge;
3243
0
                                edge = pos;
3244
0
                                dist = qAbs(x-pos);
3245
0
                            }
3246
0
                            pos += glyphs.effectiveAdvance(ge);
3247
0
                            --ge;
3248
0
                        }
3249
0
                    }
3250
0
                } else {
3251
0
                    if (!visual || !rtl || (lastLine && i == 0)) {
3252
0
                        while (gs <= ge) {
3253
0
                            if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3254
0
                                glyph_pos = gs;
3255
0
                                edge = pos;
3256
0
                                dist = qAbs(x-pos);
3257
0
                            }
3258
0
                            pos += glyphs.effectiveAdvance(gs);
3259
0
                            ++gs;
3260
0
                        }
3261
0
                    } else {
3262
0
                        QFixed oldPos = pos;
3263
0
                        while (gs <= ge) {
3264
0
                            pos += glyphs.effectiveAdvance(gs);
3265
0
                            if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3266
0
                                glyph_pos = gs;
3267
0
                                edge = pos;
3268
0
                                dist = qAbs(x-pos);
3269
0
                            }
3270
0
                            ++gs;
3271
0
                        }
3272
0
                        pos = oldPos;
3273
0
                    }
3274
0
                }
3275
0
                if (qAbs(x-pos) < dist) {
3276
0
                    if (visual) {
3277
0
                        if (!rtl && i < nItems - 1) {
3278
0
                            nchars += end;
3279
0
                            continue;
3280
0
                        }
3281
0
                        if (rtl && nchars > 0)
3282
0
                            return insertionPoints[size_t(lastLine ? nchars : nchars - 1)];
3283
0
                    }
3284
0
                    return eng->positionInLigature(&si, end, x, pos, -1,
3285
0
                                                   cpos == QTextLine::CursorOnCharacter);
3286
0
                }
3287
0
            }
3288
0
            Q_ASSERT(glyph_pos != -1);
3289
0
            return eng->positionInLigature(&si, end, x, edge, glyph_pos,
3290
0
                                           cpos == QTextLine::CursorOnCharacter);
3291
0
        }
3292
0
    }
3293
    // right of last item
3294
0
    int pos = line.from;
3295
0
    if (!eng->isRightToLeft())
3296
0
        pos += line_length;
3297
3298
    // except for the last line we assume that the
3299
    // character between lines is a space and we want
3300
    // to position the cursor to the left of that
3301
    // character.
3302
0
    if (index < eng->lines.size() - 1)
3303
0
        pos = qMin(eng->previousLogicalPosition(pos), pos);
3304
3305
0
    return pos;
3306
0
}
3307
3308
QT_END_NAMESPACE