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

source code of qtbase/src/gui/text/qtextlayout.cpp