Coverage Report

Created: 2025-07-16 07:53

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