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.at(i).length >= from) {
1047 const QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, flags: retrievalFlags);
1048 for (const QGlyphRun &glyphRun : glyphRuns) {
1049 QRawFont rawFont = glyphRun.rawFont();
1050
1051 QFontEngine *fontEngine = rawFont.d->fontEngine;
1052 QGlyphRun::GlyphRunFlags flags = glyphRun.flags();
1053 QPair<QFontEngine *, int> key(fontEngine, int(flags));
1054 // merge the glyph runs using the same font
1055 QGlyphRun &oldGlyphRun = glyphRunHash[key];
1056 if (oldGlyphRun.isEmpty()) {
1057 oldGlyphRun = glyphRun;
1058 } else {
1059 QList<quint32> indexes = oldGlyphRun.glyphIndexes();
1060 QList<QPointF> positions = oldGlyphRun.positions();
1061 QList<qsizetype> stringIndexes = oldGlyphRun.stringIndexes();
1062 QRectF boundingRect = oldGlyphRun.boundingRect();
1063
1064 indexes += glyphRun.glyphIndexes();
1065 positions += glyphRun.positions();
1066 stringIndexes += glyphRun.stringIndexes();
1067 boundingRect = boundingRect.united(r: glyphRun.boundingRect());
1068
1069 oldGlyphRun.setGlyphIndexes(indexes);
1070 oldGlyphRun.setPositions(positions);
1071 oldGlyphRun.setStringIndexes(stringIndexes);
1072 oldGlyphRun.setBoundingRect(boundingRect);
1073 }
1074 }
1075 }
1076 }
1077
1078 return glyphRunHash.values();
1079}
1080#endif // QT_NO_RAWFONT
1081
1082/*!
1083 Draws the whole layout on the painter \a p at the position specified by \a pos.
1084 The rendered layout includes the given \a selections and is clipped within
1085 the rectangle specified by \a clip.
1086*/
1087void QTextLayout::draw(QPainter *p, const QPointF &pos, const QList<FormatRange> &selections, const QRectF &clip) const
1088{
1089 if (d->lines.isEmpty())
1090 return;
1091
1092 if (!d->layoutData)
1093 d->itemize();
1094
1095 QPointF position = pos + d->position;
1096
1097 QFixed clipy = (INT_MIN/256);
1098 QFixed clipe = (INT_MAX/256);
1099 if (clip.isValid()) {
1100 clipy = QFixed::fromReal(r: clip.y() - position.y());
1101 clipe = clipy + QFixed::fromReal(r: clip.height());
1102 }
1103
1104 int firstLine = 0;
1105 int lastLine = d->lines.size();
1106 for (int i = 0; i < d->lines.size(); ++i) {
1107 const QScriptLine &sl = d->lines.at(i);
1108
1109 if (sl.y > clipe) {
1110 lastLine = i;
1111 break;
1112 }
1113 if ((sl.y + sl.height()) < clipy) {
1114 firstLine = i;
1115 continue;
1116 }
1117 }
1118
1119 QPainterPath excludedRegion;
1120 QPainterPath textDoneRegion;
1121 for (int i = 0; i < selections.size(); ++i) {
1122 FormatRange selection = selections.at(i);
1123 QPainterPath region;
1124 region.setFillRule(Qt::WindingFill);
1125
1126 for (int line = firstLine; line < lastLine; ++line) {
1127 const QScriptLine &sl = d->lines.at(i: line);
1128 QTextLine tl(line, d);
1129
1130 QRectF lineRect(tl.naturalTextRect());
1131 lineRect.translate(p: position);
1132 lineRect.adjust(xp1: 0, yp1: 0, xp2: d->leadingSpaceWidth(line: sl).toReal(), yp2: 0);
1133 lineRect.setBottom(qCeil(v: lineRect.bottom()));
1134
1135 bool isLastLineInBlock = (line == d->lines.size()-1);
1136 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1137
1138
1139 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1140 continue; // no actual intersection
1141
1142 const bool selectionStartInLine = sl.from <= selection.start;
1143 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1144
1145 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1146 addSelectedRegionsToPath(eng: d, lineNumber: line, pos: position, selection: &selection, region: &region, boundingRect: clipIfValid(rect: lineRect, clip));
1147 } else {
1148 region.addRect(rect: clipIfValid(rect: lineRect, clip));
1149 }
1150
1151 if (selection.format.boolProperty(propertyId: QTextFormat::FullWidthSelection)) {
1152 QRectF fullLineRect(tl.rect());
1153 fullLineRect.translate(p: position);
1154 fullLineRect.setRight(QFIXED_MAX);
1155 fullLineRect.setBottom(qCeil(v: fullLineRect.bottom()));
1156
1157 const bool rightToLeft = d->isRightToLeft();
1158
1159 if (!selectionEndInLine) {
1160 region.addRect(rect: clipIfValid(rect: rightToLeft ? QRectF(fullLineRect.topLeft(), lineRect.bottomLeft())
1161 : QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1162 }
1163 if (!selectionStartInLine) {
1164 region.addRect(rect: clipIfValid(rect: rightToLeft ? QRectF(lineRect.topRight(), fullLineRect.bottomRight())
1165 : QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1166 }
1167 } else if (!selectionEndInLine
1168 && isLastLineInBlock
1169 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1170 region.addRect(rect: clipIfValid(rect: QRectF(lineRect.right(), lineRect.top(),
1171 lineRect.height()/4, lineRect.height()), clip));
1172 }
1173
1174 }
1175 {
1176 const QPen oldPen = p->pen();
1177 const QBrush oldBrush = p->brush();
1178
1179 p->setPen(selection.format.penProperty(propertyId: QTextFormat::OutlinePen));
1180 p->setBrush(selection.format.brushProperty(propertyId: QTextFormat::BackgroundBrush));
1181 p->drawPath(path: region);
1182
1183 p->setPen(oldPen);
1184 p->setBrush(oldBrush);
1185 }
1186
1187
1188
1189 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1190 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1191
1192 if (hasBackground) {
1193 selection.format.setProperty(ObjectSelectionBrush, value: selection.format.property(propertyId: QTextFormat::BackgroundBrush));
1194 // don't just clear the property, set an empty brush that overrides a potential
1195 // background brush specified in the text
1196 selection.format.setProperty(propertyId: QTextFormat::BackgroundBrush, value: QBrush());
1197 selection.format.clearProperty(propertyId: QTextFormat::OutlinePen);
1198 }
1199
1200 selection.format.setProperty(SuppressText, value: !hasText);
1201
1202 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1203 continue;
1204
1205 p->save();
1206 p->setClipPath(path: region, op: Qt::IntersectClip);
1207
1208 for (int line = firstLine; line < lastLine; ++line) {
1209 QTextLine l(line, d);
1210 l.draw_internal(p, pos: position, selection: &selection);
1211 }
1212 p->restore();
1213
1214 if (hasText) {
1215 textDoneRegion += region;
1216 } else {
1217 if (hasBackground)
1218 textDoneRegion -= region;
1219 }
1220
1221 excludedRegion += region;
1222 }
1223
1224 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1225 if (!needsTextButNoBackground.isEmpty()){
1226 p->save();
1227 p->setClipPath(path: needsTextButNoBackground, op: Qt::IntersectClip);
1228 FormatRange selection;
1229 selection.start = 0;
1230 selection.length = INT_MAX;
1231 selection.format.setProperty(SuppressBackground, value: true);
1232 for (int line = firstLine; line < lastLine; ++line) {
1233 QTextLine l(line, d);
1234 l.draw_internal(p, pos: position, selection: &selection);
1235 }
1236 p->restore();
1237 }
1238
1239 if (!excludedRegion.isEmpty()) {
1240 p->save();
1241 QPainterPath path;
1242 QRectF br = boundingRect().translated(p: position);
1243 br.setRight(QFIXED_MAX);
1244 if (!clip.isNull())
1245 br = br.intersected(r: clip);
1246 path.addRect(rect: br);
1247 path -= excludedRegion;
1248 p->setClipPath(path, op: Qt::IntersectClip);
1249 }
1250
1251 for (int i = firstLine; i < lastLine; ++i) {
1252 QTextLine l(i, d);
1253 l.draw(painter: p, position);
1254 }
1255 if (!excludedRegion.isEmpty())
1256 p->restore();
1257
1258
1259 if (!d->cacheGlyphs)
1260 d->freeMemory();
1261}
1262
1263/*!
1264 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1265 \overload
1266
1267 Draws a text cursor with the current pen at the given \a position using the
1268 \a painter specified.
1269 The corresponding position within the text is specified by \a cursorPosition.
1270*/
1271void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1272{
1273 drawCursor(p, pos, cursorPosition, width: 1);
1274}
1275
1276/*!
1277 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1278
1279 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1280 \a painter specified.
1281 The corresponding position within the text is specified by \a cursorPosition.
1282*/
1283void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1284{
1285 if (d->lines.isEmpty())
1286 return;
1287
1288 if (!d->layoutData)
1289 d->itemize();
1290
1291 QPointF position = pos + d->position;
1292
1293 cursorPosition = qBound(min: 0, val: cursorPosition, max: d->layoutData->string.size());
1294 int line = d->lineNumberForTextPosition(pos: cursorPosition);
1295 if (line < 0)
1296 line = 0;
1297 if (line >= d->lines.size())
1298 return;
1299
1300 QTextLine l(line, d);
1301 const QScriptLine &sl = d->lines.at(i: line);
1302
1303 qreal x = position.x() + l.cursorToX(cursorPos: cursorPosition);
1304
1305 QFixed base = sl.base();
1306 QFixed descent = sl.descent;
1307 bool rightToLeft = d->isRightToLeft();
1308
1309 const int realCursorPosition = cursorPosition;
1310 if (d->visualCursorMovement()) {
1311 if (cursorPosition == sl.from + sl.length)
1312 --cursorPosition;
1313 } else {
1314 --cursorPosition;
1315 }
1316 int itm = d->findItem(strPos: cursorPosition);
1317
1318 if (itm >= 0) {
1319 const QScriptItem *si = &d->layoutData->items.at(i: itm);
1320 // Same logic as in cursorToX to handle edges between writing directions to prioritise the script item
1321 // that matches the writing direction of the paragraph.
1322 if (d->layoutData->hasBidi && !d->visualCursorMovement() && si->analysis.bidiLevel % 2 != rightToLeft) {
1323 int neighborItem = itm;
1324 if (neighborItem > 0 && si->position == realCursorPosition)
1325 --neighborItem;
1326 else if (neighborItem < d->layoutData->items.size() - 1 && si->position + si->num_glyphs == realCursorPosition)
1327 ++neighborItem;
1328 const bool onBoundary = neighborItem != itm
1329 && si->analysis.bidiLevel != d->layoutData->items[neighborItem].analysis.bidiLevel;
1330 if (onBoundary && rightToLeft != si->analysis.bidiLevel % 2) {
1331 itm = neighborItem;
1332 si = &d->layoutData->items[itm];
1333 }
1334 }
1335 // objects need some special treatment as they can have special alignment or be floating
1336 if (si->analysis.flags != QScriptAnalysis::Object) {
1337 if (si->ascent > 0)
1338 base = si->ascent;
1339 if (si->descent > 0)
1340 descent = si->descent;
1341 }
1342 rightToLeft = si->analysis.bidiLevel % 2;
1343 }
1344 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1345 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1346 && (p->transform().type() > QTransform::TxTranslate);
1347 if (toggleAntialiasing)
1348 p->setRenderHint(hint: QPainter::Antialiasing);
1349 QPainter::CompositionMode origCompositionMode = p->compositionMode();
1350 if (p->paintEngine()->hasFeature(feature: QPaintEngine::RasterOpModes))
1351 p->setCompositionMode(QPainter::RasterOp_NotDestination);
1352 const QTransform &deviceTransform = p->deviceTransform();
1353 const qreal xScale = deviceTransform.m11();
1354 if (deviceTransform.type() != QTransform::TxScale || std::trunc(x: xScale) == xScale) {
1355 p->fillRect(QRectF(x, y, qreal(width), (base + descent).toReal()), p->pen().brush());
1356 } else {
1357 // Ensure consistently rendered cursor width under fractional scaling
1358 const QPen origPen = p->pen();
1359 QPen pen(origPen.brush(), qRound(d: width * xScale), Qt::SolidLine, Qt::FlatCap);
1360 pen.setCosmetic(true);
1361 const qreal center = x + qreal(width) / 2;
1362 p->setPen(pen);
1363 p->drawLine(p1: QPointF(center, y), p2: QPointF(center, qCeil(v: y + (base + descent).toReal())));
1364 p->setPen(origPen);
1365 }
1366 p->setCompositionMode(origCompositionMode);
1367 if (toggleAntialiasing)
1368 p->setRenderHint(hint: QPainter::Antialiasing, on: false);
1369 if (d->layoutData->hasBidi) {
1370 const int arrow_extent = 4;
1371 int sign = rightToLeft ? -1 : 1;
1372 p->drawLine(l: QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1373 p->drawLine(l: QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1374 }
1375 return;
1376}
1377
1378/*!
1379 \class QTextLine
1380 \reentrant
1381
1382 \brief The QTextLine class represents a line of text inside a QTextLayout.
1383 \inmodule QtGui
1384
1385 \ingroup richtext-processing
1386
1387 A text line is usually created by QTextLayout::createLine().
1388
1389 After being created, the line can be filled using the setLineWidth()
1390 or setNumColumns() functions. A line has a number of attributes including the
1391 rectangle it occupies, rect(), its coordinates, x() and y(), its
1392 textLength(), width() and naturalTextWidth(), and its ascent() and descent()
1393 relative to the text. The position of the cursor in terms of the
1394 line is available from cursorToX() and its inverse from
1395 xToCursor(). A line can be moved with setPosition().
1396*/
1397
1398/*!
1399 \enum QTextLine::Edge
1400
1401 \value Leading
1402 \value Trailing
1403*/
1404
1405/*!
1406 \enum QTextLine::CursorPosition
1407
1408 \value CursorBetweenCharacters
1409 \value CursorOnCharacter
1410*/
1411
1412/*!
1413 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1414 \internal
1415
1416 Constructs a new text line using the line at position \a line in
1417 the text engine \a e.
1418*/
1419
1420/*!
1421 \fn QTextLine::QTextLine()
1422
1423 Creates an invalid line.
1424*/
1425
1426/*!
1427 \fn bool QTextLine::isValid() const
1428
1429 Returns \c true if this text line is valid; otherwise returns \c false.
1430*/
1431
1432/*!
1433 \fn int QTextLine::lineNumber() const
1434
1435 Returns the position of the line in the text engine.
1436*/
1437
1438
1439/*!
1440 Returns the line's bounding rectangle.
1441
1442 \sa x(), y(), textLength(), width()
1443*/
1444QRectF QTextLine::rect() const
1445{
1446 const QScriptLine& sl = eng->lines.at(i: index);
1447 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1448}
1449
1450/*!
1451 Returns the rectangle covered by the line.
1452*/
1453QRectF QTextLine::naturalTextRect() const
1454{
1455 const QScriptLine& sl = eng->lines.at(i: index);
1456 QFixed x = sl.x + eng->alignLine(line: sl);
1457
1458 QFixed width = sl.textWidth;
1459 if (sl.justified)
1460 width = sl.width;
1461
1462 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1463}
1464
1465/*!
1466 Returns the line's x position.
1467
1468 \sa rect(), y(), textLength(), width()
1469*/
1470qreal QTextLine::x() const
1471{
1472 return eng->lines.at(i: index).x.toReal();
1473}
1474
1475/*!
1476 Returns the line's y position.
1477
1478 \sa x(), rect(), textLength(), width()
1479*/
1480qreal QTextLine::y() const
1481{
1482 return eng->lines.at(i: index).y.toReal();
1483}
1484
1485/*!
1486 Returns the line's width as specified by the layout() function.
1487
1488 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1489*/
1490qreal QTextLine::width() const
1491{
1492 return eng->lines.at(i: index).width.toReal();
1493}
1494
1495
1496/*!
1497 Returns the line's ascent.
1498
1499 \sa descent(), height()
1500*/
1501qreal QTextLine::ascent() const
1502{
1503 return eng->lines.at(i: index).ascent.toReal();
1504}
1505
1506/*!
1507 Returns the line's descent.
1508
1509 \sa ascent(), height()
1510*/
1511qreal QTextLine::descent() const
1512{
1513 return eng->lines.at(i: index).descent.toReal();
1514}
1515
1516/*!
1517 Returns the line's height. This is equal to ascent() + descent()
1518 if leading is not included. If leading is included, this equals to
1519 ascent() + descent() + leading().
1520
1521 \sa ascent(), descent(), leading(), setLeadingIncluded()
1522*/
1523qreal QTextLine::height() const
1524{
1525 return eng->lines.at(i: index).height().ceil().toReal();
1526}
1527
1528/*!
1529 \since 4.6
1530
1531 Returns the line's leading.
1532
1533 \sa ascent(), descent(), height()
1534*/
1535qreal QTextLine::leading() const
1536{
1537 return eng->lines.at(i: index).leading.toReal();
1538}
1539
1540/*!
1541 \since 4.6
1542
1543 Includes positive leading into the line's height if \a included is true;
1544 otherwise does not include leading.
1545
1546 By default, leading is not included.
1547
1548 Note that negative leading is ignored, it must be handled
1549 in the code using the text lines by letting the lines overlap.
1550
1551 \sa leadingIncluded()
1552
1553*/
1554void QTextLine::setLeadingIncluded(bool included)
1555{
1556 eng->lines[index].leadingIncluded= included;
1557
1558}
1559
1560/*!
1561 \since 4.6
1562
1563 Returns \c true if positive leading is included into the line's height;
1564 otherwise returns \c false.
1565
1566 By default, leading is not included.
1567
1568 \sa setLeadingIncluded()
1569*/
1570bool QTextLine::leadingIncluded() const
1571{
1572 return eng->lines.at(i: index).leadingIncluded;
1573}
1574
1575/*!
1576 Returns the width of the line that is occupied by text. This is
1577 always \<= to width(), and is the minimum width that could be used
1578 by layout() without changing the line break position.
1579*/
1580qreal QTextLine::naturalTextWidth() const
1581{
1582 return eng->lines.at(i: index).textWidth.toReal();
1583}
1584
1585/*!
1586 \since 4.7
1587 Returns the horizontal advance of the text. The advance of the text
1588 is the distance from its position to the next position at which
1589 text would naturally be drawn.
1590
1591 By adding the advance to the position of the text line and using this
1592 as the position of a second text line, you will be able to position
1593 the two lines side-by-side without gaps in-between.
1594*/
1595qreal QTextLine::horizontalAdvance() const
1596{
1597 return eng->lines.at(i: index).textAdvance.toReal();
1598}
1599
1600/*!
1601 Lays out the line with the given \a width. The line is filled from
1602 its starting position with as many characters as will fit into
1603 the line. In case the text cannot be split at the end of the line,
1604 it will be filled with additional characters to the next whitespace
1605 or end of the text.
1606*/
1607void QTextLine::setLineWidth(qreal width)
1608{
1609 QScriptLine &line = eng->lines[index];
1610 if (!eng->layoutData) {
1611 qWarning(msg: "QTextLine: Can't set a line width while not layouting.");
1612 return;
1613 }
1614
1615 line.width = QFixed::fromReal(r: qBound(min: 0.0, val: width, max: qreal(QFIXED_MAX)));
1616 if (line.length
1617 && line.textWidth <= line.width
1618 && line.from + line.length == eng->layoutData->string.size())
1619 // no need to do anything if the line is already layouted and the last one. This optimization helps
1620 // when using things in a single line layout.
1621 return;
1622 line.length = 0;
1623 line.textWidth = 0;
1624
1625 layout_helper(INT_MAX);
1626}
1627
1628/*!
1629 Lays out the line. The line is filled from its starting position
1630 with as many characters as are specified by \a numColumns. In case
1631 the text cannot be split until \a numColumns characters, the line
1632 will be filled with as many characters to the next whitespace or
1633 end of the text.
1634*/
1635void QTextLine::setNumColumns(int numColumns)
1636{
1637 QScriptLine &line = eng->lines[index];
1638 line.width = QFIXED_MAX;
1639 line.length = 0;
1640 line.textWidth = 0;
1641 layout_helper(numGlyphs: numColumns);
1642}
1643
1644/*!
1645 Lays out the line. The line is filled from its starting position
1646 with as many characters as are specified by \a numColumns. In case
1647 the text cannot be split until \a numColumns characters, the line
1648 will be filled with as many characters to the next whitespace or
1649 end of the text. The provided \a alignmentWidth is used as reference
1650 width for alignment.
1651*/
1652void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1653{
1654 QScriptLine &line = eng->lines[index];
1655 line.width = QFixed::fromReal(r: qBound(min: 0.0, val: alignmentWidth, max: qreal(QFIXED_MAX)));
1656 line.length = 0;
1657 line.textWidth = 0;
1658 layout_helper(numGlyphs: numColumns);
1659}
1660
1661#if 0
1662#define LB_DEBUG qDebug
1663#else
1664#define LB_DEBUG if (0) qDebug
1665#endif
1666
1667namespace {
1668
1669 struct LineBreakHelper
1670 {
1671 LineBreakHelper() = default;
1672
1673 QScriptLine tmpData;
1674 QScriptLine spaceData;
1675
1676 QGlyphLayout glyphs;
1677
1678 int glyphCount = 0;
1679 int maxGlyphs = 0;
1680 int currentPosition = 0;
1681 glyph_t previousGlyph = 0;
1682 QExplicitlySharedDataPointer<QFontEngine> previousGlyphFontEngine;
1683
1684 QFixed minw;
1685 QFixed currentSoftHyphenWidth;
1686 QFixed commitedSoftHyphenWidth;
1687 QFixed rightBearing;
1688 QFixed minimumRightBearing;
1689
1690 QExplicitlySharedDataPointer<QFontEngine> fontEngine;
1691 const unsigned short *logClusters = nullptr;
1692
1693 bool manualWrap = false;
1694 bool whiteSpaceOrObject = true;
1695
1696 bool checkFullOtherwiseExtend(QScriptLine &line);
1697
1698 QFixed calculateNewWidth(const QScriptLine &line) const {
1699 return line.textWidth + tmpData.textWidth + spaceData.textWidth
1700 + (line.textWidth > 0 ? currentSoftHyphenWidth : QFixed()) + negativeRightBearing();
1701 }
1702
1703 inline glyph_t currentGlyph() const
1704 {
1705 Q_ASSERT(currentPosition > 0);
1706 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1707
1708 return glyphs.glyphs[logClusters[currentPosition - 1]];
1709 }
1710
1711 inline void saveCurrentGlyph()
1712 {
1713 previousGlyph = 0;
1714 if (currentPosition > 0 &&
1715 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1716 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1717 previousGlyphFontEngine = fontEngine;
1718 }
1719 }
1720
1721 inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph)
1722 {
1723 Q_ASSERT(engine);
1724 qreal rb;
1725 engine->getGlyphBearings(glyph, leftBearing: nullptr, rightBearing: &rb);
1726
1727 // We only care about negative right bearings, so we limit the range
1728 // of the bearing here so that we can assume it's negative in the rest
1729 // of the code, as well ase use QFixed(1) as a sentinel to represent
1730 // the state where we have yet to compute the right bearing.
1731 rightBearing = qMin(a: QFixed::fromReal(r: rb), b: QFixed(0));
1732 }
1733
1734 inline void calculateRightBearing()
1735 {
1736 if (currentPosition <= 0)
1737 return;
1738 calculateRightBearing(engine: fontEngine.data(), glyph: currentGlyph());
1739 }
1740
1741 inline void calculateRightBearingForPreviousGlyph()
1742 {
1743 if (previousGlyph > 0)
1744 calculateRightBearing(engine: previousGlyphFontEngine.data(), glyph: previousGlyph);
1745 }
1746
1747 static const QFixed RightBearingNotCalculated;
1748
1749 inline void resetRightBearing()
1750 {
1751 rightBearing = RightBearingNotCalculated;
1752 }
1753
1754 // We express the negative right bearing as an absolute number
1755 // so that it can be applied to the width using addition.
1756 inline QFixed negativeRightBearing() const
1757 {
1758 if (rightBearing == RightBearingNotCalculated)
1759 return QFixed(0);
1760
1761 return qAbs(t: rightBearing);
1762 }
1763 };
1764
1765Q_CONSTINIT const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
1766
1767inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1768{
1769 LB_DEBUG(msg: "possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1770
1771 QFixed newWidth = calculateNewWidth(line);
1772 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1773 return true;
1774
1775 const QFixed oldTextWidth = line.textWidth;
1776 line += tmpData;
1777 line.textWidth += spaceData.textWidth;
1778
1779 line.length += spaceData.length;
1780 tmpData.textWidth = 0;
1781 tmpData.length = 0;
1782 spaceData.textWidth = 0;
1783 spaceData.length = 0;
1784
1785 if (oldTextWidth != line.textWidth || currentSoftHyphenWidth > 0) {
1786 commitedSoftHyphenWidth = currentSoftHyphenWidth;
1787 currentSoftHyphenWidth = 0;
1788 }
1789
1790 return false;
1791}
1792
1793} // anonymous namespace
1794
1795
1796static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1797 const QScriptItem &current, const unsigned short *logClusters,
1798 const QGlyphLayout &glyphs, QFixed *clusterWidth = nullptr)
1799{
1800 int glyphPosition = logClusters[pos];
1801 do { // got to the first next cluster
1802 ++pos;
1803 ++line.length;
1804 } while (pos < end && logClusters[pos] == glyphPosition);
1805 QFixed clusterWid = line.textWidth;
1806 do { // calculate the textWidth for the rest of the current cluster.
1807 if (!glyphs.attributes[glyphPosition].dontPrint)
1808 line.textWidth += glyphs.advances[glyphPosition];
1809 ++glyphPosition;
1810 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1811
1812 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1813
1814 if (clusterWidth)
1815 *clusterWidth += (line.textWidth - clusterWid);
1816 ++glyphCount;
1817}
1818
1819
1820// fill QScriptLine
1821void QTextLine::layout_helper(int maxGlyphs)
1822{
1823 QScriptLine &line = eng->lines[index];
1824 line.length = 0;
1825 line.trailingSpaces = 0;
1826 line.textWidth = 0;
1827 line.hasTrailingSpaces = false;
1828
1829 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.size()) {
1830 line.setDefaultHeight(eng);
1831 return;
1832 }
1833
1834 Q_ASSERT(line.from < eng->layoutData->string.size());
1835
1836 LineBreakHelper lbh;
1837
1838 lbh.maxGlyphs = maxGlyphs;
1839
1840 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1841 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1842 const bool breakWordOrAny = breakany || (wrapMode == QTextOption::WrapAtWordBoundaryOrAnywhere);
1843 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1844
1845 int item = -1;
1846 int newItem = eng->findItem(strPos: line.from);
1847 Q_ASSERT(newItem >= 0);
1848
1849 LB_DEBUG(msg: "from: %d: item=%d, total %d, width available %f", line.from, newItem, int(eng->layoutData->items.size()), line.width.toReal());
1850
1851 Qt::Alignment alignment = eng->option.alignment();
1852
1853 const QCharAttributes *attributes = eng->attributes();
1854 if (!attributes)
1855 return;
1856 lbh.currentPosition = line.from;
1857 int end = 0;
1858 lbh.logClusters = eng->layoutData->logClustersPtr;
1859 lbh.previousGlyph = 0;
1860
1861 bool manuallyWrapped = false;
1862 bool hasInlineObject = false;
1863 QFixed maxInlineObjectHeight = 0;
1864
1865 while (newItem < eng->layoutData->items.size()) {
1866 lbh.resetRightBearing();
1867 if (newItem != item) {
1868 item = newItem;
1869 const QScriptItem &current = eng->layoutData->items.at(i: item);
1870 if (!current.num_glyphs) {
1871 eng->shape(item);
1872 attributes = eng->attributes();
1873 if (!attributes)
1874 return;
1875 lbh.logClusters = eng->layoutData->logClustersPtr;
1876 }
1877 lbh.currentPosition = qMax(a: line.from, b: current.position);
1878 end = current.position + eng->length(item);
1879 lbh.glyphs = eng->shapedGlyphs(si: &current);
1880 QFontEngine *fontEngine = eng->fontEngine(si: current);
1881 if (lbh.fontEngine != fontEngine) {
1882 lbh.fontEngine = fontEngine;
1883 lbh.minimumRightBearing = qMin(a: QFixed(),
1884 b: QFixed::fromReal(r: fontEngine->minRightBearing()));
1885 }
1886 }
1887 const QScriptItem &current = eng->layoutData->items.at(i: item);
1888
1889 lbh.tmpData.leading = qMax(a: lbh.tmpData.leading + lbh.tmpData.ascent,
1890 b: current.leading + current.ascent) - qMax(a: lbh.tmpData.ascent,
1891 b: current.ascent);
1892 if (current.analysis.flags != QScriptAnalysis::Object) {
1893 // objects need some special treatment as they can special alignment or be floating
1894 lbh.tmpData.ascent = qMax(a: lbh.tmpData.ascent, b: current.ascent);
1895 lbh.tmpData.descent = qMax(a: lbh.tmpData.descent, b: current.descent);
1896 }
1897
1898 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1899 lbh.whiteSpaceOrObject = true;
1900 if (lbh.checkFullOtherwiseExtend(line))
1901 goto found;
1902
1903 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1904 QFixed tabWidth = eng->calculateTabWidth(index: item, x);
1905 attributes = eng->attributes();
1906 if (!attributes)
1907 return;
1908 lbh.logClusters = eng->layoutData->logClustersPtr;
1909 lbh.glyphs = eng->shapedGlyphs(si: &current);
1910
1911 lbh.spaceData.textWidth += tabWidth;
1912 lbh.spaceData.length++;
1913 newItem = item + 1;
1914
1915 QFixed averageCharWidth = eng->fontEngine(si: current)->averageCharWidth();
1916 lbh.glyphCount += qRound(f: tabWidth / averageCharWidth);
1917
1918 if (lbh.checkFullOtherwiseExtend(line))
1919 goto found;
1920 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1921 lbh.whiteSpaceOrObject = true;
1922 // if the line consists only of the line separator make sure
1923 // we have a sane height
1924 if (!line.length && !lbh.tmpData.length)
1925 line.setDefaultHeight(eng);
1926 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1927 if (lbh.checkFullOtherwiseExtend(line))
1928 goto found;
1929
1930 addNextCluster(pos&: lbh.currentPosition, end, line&: lbh.tmpData, glyphCount&: lbh.glyphCount,
1931 current, logClusters: lbh.logClusters, glyphs: lbh.glyphs);
1932 } else {
1933 lbh.tmpData.length++;
1934 lbh.calculateRightBearingForPreviousGlyph();
1935 }
1936 line += lbh.tmpData;
1937 manuallyWrapped = true;
1938 goto found;
1939 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1940 lbh.whiteSpaceOrObject = true;
1941 lbh.tmpData.length++;
1942
1943 if (QTextDocumentPrivate::get(block&: eng->block) != nullptr) {
1944 QTextInlineObject inlineObject(item, eng);
1945 QTextFormat f = inlineObject.format();
1946 eng->docLayout()->positionInlineObject(item: inlineObject, posInDocument: eng->block.position() + current.position, format: f);
1947 QTextCharFormat::VerticalAlignment valign = f.toCharFormat().verticalAlignment();
1948 if (valign != QTextCharFormat::AlignTop && valign != QTextCharFormat::AlignBottom) {
1949 lbh.tmpData.ascent = qMax(a: lbh.tmpData.ascent, b: current.ascent);
1950 lbh.tmpData.descent = qMax(a: lbh.tmpData.descent, b: current.descent);
1951 }
1952 }
1953
1954 hasInlineObject = true;
1955 maxInlineObjectHeight = qMax(a: maxInlineObjectHeight, b: current.ascent + current.descent);
1956
1957 lbh.tmpData.textWidth += current.width;
1958
1959 newItem = item + 1;
1960 ++lbh.glyphCount;
1961 if (lbh.checkFullOtherwiseExtend(line))
1962 goto found;
1963 } else if (attributes[lbh.currentPosition].whiteSpace
1964 && eng->layoutData->string.at(i: lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1965 lbh.whiteSpaceOrObject = true;
1966 while (lbh.currentPosition < end
1967 && attributes[lbh.currentPosition].whiteSpace
1968 && eng->layoutData->string.at(i: lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1969 addNextCluster(pos&: lbh.currentPosition, end, line&: lbh.spaceData, glyphCount&: lbh.glyphCount,
1970 current, logClusters: lbh.logClusters, glyphs: lbh.glyphs);
1971 }
1972 } else {
1973 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width)
1974 goto found;
1975
1976 lbh.whiteSpaceOrObject = false;
1977 bool sb_or_ws = false;
1978 lbh.saveCurrentGlyph();
1979 QFixed accumulatedTextWidth;
1980 do {
1981 addNextCluster(pos&: lbh.currentPosition, end, line&: lbh.tmpData, glyphCount&: lbh.glyphCount,
1982 current, logClusters: lbh.logClusters, glyphs: lbh.glyphs, clusterWidth: &accumulatedTextWidth);
1983
1984 // This is a hack to fix a regression caused by the introduction of the
1985 // whitespace flag to non-breakable spaces and will cause the non-breakable
1986 // spaces to behave as in previous Qt versions in the line breaking algorithm.
1987 // The line breaks do not currently follow the Unicode specs, but fixing this would
1988 // require refactoring the code and would cause behavioral regressions.
1989 const bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.size()
1990 && attributes[lbh.currentPosition].whiteSpace
1991 && eng->layoutData->string.at(i: lbh.currentPosition).decompositionTag() != QChar::NoBreak;
1992
1993 if (lbh.currentPosition >= eng->layoutData->string.size()
1994 || isBreakableSpace
1995 || attributes[lbh.currentPosition].lineBreak
1996 || lbh.tmpData.textWidth >= QFIXED_MAX) {
1997 sb_or_ws = true;
1998 break;
1999 } else if (attributes[lbh.currentPosition].graphemeBoundary) {
2000 if (breakWordOrAny) {
2001 lbh.minw = qMax(a: accumulatedTextWidth, b: lbh.minw);
2002 accumulatedTextWidth = 0;
2003 }
2004 if (breakany)
2005 break;
2006 }
2007 } while (lbh.currentPosition < end);
2008 lbh.minw = qMax(a: accumulatedTextWidth, b: lbh.minw);
2009
2010 if (lbh.currentPosition > 0 && lbh.currentPosition <= end
2011 && (lbh.currentPosition == end || attributes[lbh.currentPosition].lineBreak)
2012 && eng->layoutData->string.at(i: lbh.currentPosition - 1) == QChar::SoftHyphen) {
2013 // if we are splitting up a word because of
2014 // a soft hyphen then we ...
2015 //
2016 // a) have to take the width of the soft hyphen into
2017 // account to see if the first syllable(s) /and/
2018 // the soft hyphen fit into the line
2019 //
2020 // b) if we are so short of available width that the
2021 // soft hyphen is the first breakable position, then
2022 // we don't want to show it. However we initially
2023 // have to take the width for it into account so that
2024 // the text document layout sees the overflow and
2025 // switch to break-anywhere mode, in which we
2026 // want the soft-hyphen to slip into the next line
2027 // and thus become invisible again.
2028 //
2029 lbh.currentSoftHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]];
2030 }
2031
2032 if (sb_or_ws|breakany) {
2033 // To compute the final width of the text we need to take negative right bearing
2034 // into account (negative right bearing means the glyph has pixel data past the
2035 // advance length). Note that the negative right bearing is an absolute number,
2036 // so that we can apply it to the width using straight forward addition.
2037
2038 // Store previous right bearing (for the already accepted glyph) in case we
2039 // end up breaking due to the current glyph being too wide.
2040 QFixed previousRightBearing = lbh.rightBearing;
2041
2042 // We skip calculating the right bearing if the minimum negative bearing is too
2043 // small to possibly expand the text beyond the edge. Note that this optimization
2044 // will in some cases fail, as the minimum right bearing reported by the font
2045 // engine may not cover all the glyphs in the font. The result is that we think
2046 // we don't need to break at the current glyph (because the right bearing is 0),
2047 // and when we then end up breaking on the next glyph we compute the right bearing
2048 // and end up with a line width that is slightly larger width than what was requested.
2049 // Unfortunately we can't remove this optimization as it will slow down text
2050 // layouting significantly, so we accept the slight correctness issue.
2051 if ((lbh.calculateNewWidth(line) + qAbs(t: lbh.minimumRightBearing)) > line.width)
2052 lbh.calculateRightBearing();
2053
2054 if (lbh.checkFullOtherwiseExtend(line)) {
2055
2056 // We are too wide to accept the next glyph with its bearing, so we restore the
2057 // right bearing to that of the previous glyph (the one that was already accepted),
2058 // so that the bearing can be be applied to the final width of the text below.
2059 if (previousRightBearing != LineBreakHelper::RightBearingNotCalculated)
2060 lbh.rightBearing = previousRightBearing;
2061 else
2062 lbh.calculateRightBearingForPreviousGlyph();
2063
2064 line.textWidth += lbh.commitedSoftHyphenWidth;
2065
2066 goto found;
2067 }
2068 }
2069 lbh.saveCurrentGlyph();
2070 }
2071 if (lbh.currentPosition == end)
2072 newItem = item + 1;
2073 }
2074 LB_DEBUG(msg: "reached end of line");
2075 lbh.checkFullOtherwiseExtend(line);
2076 line.textWidth += lbh.commitedSoftHyphenWidth;
2077found:
2078 line.textAdvance = line.textWidth;
2079
2080 // If right bearing has not been calculated yet, do that now
2081 if (lbh.rightBearing == LineBreakHelper::RightBearingNotCalculated && !lbh.whiteSpaceOrObject)
2082 lbh.calculateRightBearing();
2083
2084 // Then apply any negative right bearing
2085 line.textWidth += lbh.negativeRightBearing();
2086
2087 if (line.length == 0) {
2088 LB_DEBUG(msg: "no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2089 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2090 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2091 line += lbh.tmpData;
2092 }
2093
2094 if (hasInlineObject && QTextDocumentPrivate::get(block&: eng->block) != nullptr) {
2095 // position top/bottom aligned inline objects
2096 if (maxInlineObjectHeight > line.ascent + line.descent) {
2097 // extend line height if required
2098 QFixed toAdd = (maxInlineObjectHeight - line.ascent - line.descent)/2;
2099 line.ascent += toAdd;
2100 line.descent = maxInlineObjectHeight - line.ascent;
2101 }
2102 int startItem = eng->findItem(strPos: line.from);
2103 int endItem = eng->findItem(strPos: line.from + line.length);
2104 if (endItem < 0)
2105 endItem = eng->layoutData->items.size();
2106 for (int item = startItem; item < endItem; ++item) {
2107 QScriptItem &current = eng->layoutData->items[item];
2108 if (current.analysis.flags == QScriptAnalysis::Object) {
2109 QTextInlineObject inlineObject(item, eng);
2110 QTextCharFormat::VerticalAlignment align = inlineObject.format().toCharFormat().verticalAlignment();
2111 QFixed height = current.ascent + current.descent;
2112 switch (align) {
2113 case QTextCharFormat::AlignTop:
2114 current.ascent = line.ascent;
2115 current.descent = height - line.ascent;
2116 break;
2117 case QTextCharFormat::AlignMiddle:
2118 current.ascent = (line.ascent + line.descent) / 2 - line.descent + height / 2;
2119 current.descent = height - line.ascent;
2120 break;
2121 case QTextCharFormat::AlignBottom:
2122 current.descent = line.descent;
2123 current.ascent = height - line.descent;
2124 break;
2125 default:
2126 break;
2127 }
2128 Q_ASSERT(line.ascent >= current.ascent);
2129 Q_ASSERT(line.descent >= current.descent);
2130 }
2131 }
2132 }
2133
2134
2135 LB_DEBUG(msg: "line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2136 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2137 LB_DEBUG(msg: " : '%s'", eng->layoutData->string.mid(position: line.from, n: line.length).toUtf8().data());
2138
2139 const QFixed trailingSpace = (eng->option.flags() & QTextOption::IncludeTrailingSpaces
2140 ? lbh.spaceData.textWidth
2141 : QFixed(0));
2142 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2143 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2144 || (lbh.maxGlyphs == INT_MAX && line.textWidth > (line.width - trailingSpace))) {
2145
2146 eng->option.setWrapMode(QTextOption::WrapAnywhere);
2147 layout_helper(maxGlyphs: lbh.maxGlyphs);
2148 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2149 return;
2150 }
2151 }
2152
2153 if (lbh.manualWrap) {
2154 eng->minWidth = qMax(a: eng->minWidth, b: line.textWidth);
2155 eng->maxWidth = qMax(a: eng->maxWidth, b: line.textWidth);
2156 } else {
2157 eng->minWidth = qMax(a: eng->minWidth, b: lbh.minw);
2158 if (qAddOverflow(v1: eng->layoutData->currentMaxWidth, v2: line.textWidth, r: &eng->layoutData->currentMaxWidth))
2159 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2160 if (!manuallyWrapped) {
2161 if (qAddOverflow(v1: eng->layoutData->currentMaxWidth, v2: lbh.spaceData.textWidth, r: &eng->layoutData->currentMaxWidth))
2162 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2163 }
2164 eng->maxWidth = qMax(a: eng->maxWidth, b: eng->layoutData->currentMaxWidth);
2165 if (manuallyWrapped)
2166 eng->layoutData->currentMaxWidth = 0;
2167 }
2168
2169 line.textWidth += trailingSpace;
2170 if (lbh.spaceData.length) {
2171 line.trailingSpaces = lbh.spaceData.length;
2172 line.hasTrailingSpaces = true;
2173 }
2174
2175 line.justified = false;
2176 line.gridfitted = false;
2177}
2178
2179/*!
2180 Moves the line to position \a pos.
2181*/
2182void QTextLine::setPosition(const QPointF &pos)
2183{
2184 eng->lines[index].x = QFixed::fromReal(r: pos.x());
2185 eng->lines[index].y = QFixed::fromReal(r: pos.y());
2186}
2187
2188/*!
2189 Returns the line's position relative to the text layout's position.
2190*/
2191QPointF QTextLine::position() const
2192{
2193 return QPointF(eng->lines.at(i: index).x.toReal(), eng->lines.at(i: index).y.toReal());
2194}
2195
2196// ### DOC: I have no idea what this means/does.
2197// You create a text layout with a string of text. Once you laid
2198// it out, it contains a number of QTextLines. from() returns the position
2199// inside the text string where this line starts. If you e.g. has a
2200// text of "This is a string", laid out into two lines (the second
2201// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2202// layout.lineAt(1).from() == 8.
2203/*!
2204 Returns the start of the line from the beginning of the string
2205 passed to the QTextLayout.
2206*/
2207int QTextLine::textStart() const
2208{
2209 return eng->lines.at(i: index).from;
2210}
2211
2212/*!
2213 Returns the length of the text in the line.
2214
2215 \sa naturalTextWidth()
2216*/
2217int QTextLine::textLength() const
2218{
2219 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2220 && eng->block.isValid() && index == eng->lines.size()-1) {
2221 return eng->lines.at(i: index).length - 1;
2222 }
2223 return eng->lines.at(i: index).length + eng->lines.at(i: index).trailingSpaces;
2224}
2225
2226static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
2227{
2228 QBrush bg = chf.background();
2229 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2230 p->fillRect(r.toAlignedRect(), bg);
2231}
2232
2233static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
2234{
2235 QBrush c = chf.foreground();
2236 if (c.style() == Qt::NoBrush)
2237 p->setPen(defaultPen);
2238 else
2239 p->setPen(QPen(c, 0));
2240}
2241
2242#if !defined(QT_NO_RAWFONT)
2243static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
2244 const QString &text,
2245 const QGlyphLayout &glyphLayout,
2246 const QPointF &pos,
2247 const QGlyphRun::GlyphRunFlags &flags,
2248 QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
2249 QFixed selectionX,
2250 QFixed selectionWidth,
2251 int glyphsStart,
2252 int glyphsEnd,
2253 unsigned short *logClusters,
2254 int textPosition,
2255 int textLength)
2256{
2257 Q_ASSERT(logClusters != nullptr);
2258
2259 QGlyphRun glyphRun;
2260
2261 QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun);
2262
2263 int rangeStart = textPosition;
2264 int logClusterIndex = 0;
2265 while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
2266 ++logClusterIndex;
2267 ++rangeStart;
2268 }
2269
2270 int rangeEnd = rangeStart;
2271 while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
2272 ++logClusterIndex;
2273 ++rangeEnd;
2274 }
2275
2276 d->textRangeStart = rangeStart;
2277 d->textRangeEnd = rangeEnd;
2278
2279 // Make a font for this particular engine
2280 QRawFont font;
2281 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2282 fontD->setFontEngine(fontEngine);
2283
2284 QVarLengthArray<glyph_t> glyphsArray;
2285 QVarLengthArray<QFixedPoint> positionsArray;
2286
2287 QTextItem::RenderFlags renderFlags;
2288 if (flags.testFlag(flag: QGlyphRun::Overline))
2289 renderFlags |= QTextItem::Overline;
2290 if (flags.testFlag(flag: QGlyphRun::Underline))
2291 renderFlags |= QTextItem::Underline;
2292 if (flags.testFlag(flag: QGlyphRun::StrikeOut))
2293 renderFlags |= QTextItem::StrikeOut;
2294 if (flags.testFlag(flag: QGlyphRun::RightToLeft))
2295 renderFlags |= QTextItem::RightToLeft;
2296
2297 fontEngine->getGlyphPositions(glyphs: glyphLayout, matrix: QTransform(), flags: renderFlags, glyphs_out&: glyphsArray,
2298 positions&: positionsArray);
2299 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2300
2301 qreal fontHeight = font.ascent() + font.descent();
2302 qreal minY = 0;
2303 qreal maxY = 0;
2304 QList<quint32> glyphs;
2305 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2306 glyphs.reserve(asize: glyphsArray.size());
2307 QList<QPointF> positions;
2308 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2309 positions.reserve(asize: glyphsArray.size());
2310 QList<qsizetype> stringIndexes;
2311 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2312 stringIndexes.reserve(asize: glyphsArray.size());
2313
2314 int nextClusterIndex = 0;
2315 int currentClusterIndex = 0;
2316 for (int i = 0; i < glyphsArray.size(); ++i) {
2317 const int glyphArrayIndex = i + glyphsStart;
2318 // Search for the next cluster in the string (or the end of string if there are no
2319 // more clusters)
2320 if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
2321 if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
2322 currentClusterIndex = nextClusterIndex; // Store current cluster
2323 while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
2324 ++nextClusterIndex;
2325 }
2326
2327 // We are now either at end of string (no more clusters) or we are not yet at the
2328 // next cluster in glyph array. We fill in current cluster so that there is always one
2329 // entry in stringIndexes for each glyph.
2330 Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
2331 stringIndexes.append(t: textPosition + currentClusterIndex);
2332 }
2333
2334 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
2335 glyph_t glyphIndex = glyphsArray.at(idx: i) & 0xffffff;
2336 glyphs.append(t: glyphIndex);
2337 }
2338
2339 QPointF position = positionsArray.at(idx: i).toPointF() + pos;
2340 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2341 positions.append(t: position);
2342
2343 if (i == 0) {
2344 maxY = minY = position.y();
2345 } else {
2346 minY = qMin(a: minY, b: position.y());
2347 maxY = qMax(a: maxY, b: position.y());
2348 }
2349 }
2350
2351 qreal height = maxY + fontHeight - minY;
2352
2353 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2354 glyphRun.setGlyphIndexes(glyphs);
2355 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2356 glyphRun.setPositions(positions);
2357 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2358 glyphRun.setStringIndexes(stringIndexes);
2359 if (retrievalFlags & QTextLayout::RetrieveString)
2360 glyphRun.setSourceString(text);
2361 glyphRun.setFlags(flags);
2362 glyphRun.setRawFont(font);
2363
2364 glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY - font.ascent(),
2365 selectionWidth.toReal(), height));
2366
2367 return glyphRun;
2368}
2369
2370# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
2371/*!
2372 \overload
2373 Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2374 in the range defined by \a from and \a length. The \a from index is relative to the beginning
2375 of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2376 as given by functions textStart() and textLength().
2377
2378 If \a from is negative, it will default to textStart(), and if \a length is negative it will
2379 default to the return value of textLength().
2380
2381 \note This is equivalent to calling
2382 glyphRuns(from,
2383 length,
2384 QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
2385 QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
2386
2387 \since 5.0
2388
2389 \sa QTextLayout::glyphRuns()
2390*/
2391QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2392{
2393 return glyphRuns(from, length, flags: QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
2394}
2395# endif
2396
2397/*!
2398 Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2399 in the range defined by \a from and \a length. The \a from index is relative to the beginning
2400 of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2401 as given by functions textStart() and textLength().
2402
2403 The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
2404 layout. To minimize allocations and memory consumption, this should be set to include only the
2405 properties that you need to access later.
2406
2407 If \a from is negative, it will default to textStart(), and if \a length is negative it will
2408 default to the return value of textLength().
2409
2410 \since 6.5
2411
2412 \sa QTextLayout::glyphRuns()
2413*/
2414QList<QGlyphRun> QTextLine::glyphRuns(int from,
2415 int length,
2416 QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
2417{
2418 const QScriptLine &line = eng->lines.at(i: index);
2419
2420 if (line.length == 0)
2421 return QList<QGlyphRun>();
2422
2423 if (from < 0)
2424 from = textStart();
2425
2426 if (length < 0)
2427 length = textLength();
2428
2429 if (length == 0)
2430 return QList<QGlyphRun>();
2431
2432 QTextLayout::FormatRange selection;
2433 selection.start = from;
2434 selection.length = length;
2435
2436 QTextLineItemIterator iterator(eng, index, QPointF(), &selection);
2437 qreal y = line.y.toReal() + line.base().toReal();
2438 QList<QGlyphRun> glyphRuns;
2439 while (!iterator.atEnd()) {
2440 QScriptItem &si = iterator.next();
2441 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2442 continue;
2443
2444 if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart))
2445 continue;
2446
2447 QPointF pos(iterator.x.toReal(), y);
2448
2449 QFont font;
2450 QGlyphRun::GlyphRunFlags flags;
2451 if (!eng->useRawFont) {
2452 font = eng->font(si);
2453 if (font.overline())
2454 flags |= QGlyphRun::Overline;
2455 if (font.underline())
2456 flags |= QGlyphRun::Underline;
2457 if (font.strikeOut())
2458 flags |= QGlyphRun::StrikeOut;
2459 }
2460
2461 bool rtl = false;
2462 if (si.analysis.bidiLevel % 2) {
2463 flags |= QGlyphRun::RightToLeft;
2464 rtl = true;
2465 }
2466
2467 int relativeFrom = qMax(a: iterator.itemStart, b: from) - si.position;
2468 int relativeTo = qMin(a: iterator.itemEnd, b: from + length) - 1 - si.position;
2469
2470 unsigned short *logClusters = eng->logClusters(si: &si);
2471 int glyphsStart = logClusters[relativeFrom];
2472 int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo];
2473 // the glyph index right next to the requested range
2474 int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs;
2475 if (nextGlyphIndex - 1 > glyphsEnd)
2476 glyphsEnd = nextGlyphIndex - 1;
2477 bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart;
2478 bool endsInsideLigature = nextGlyphIndex == glyphsEnd;
2479
2480 int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
2481 int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
2482
2483 QGlyphLayout glyphLayout = eng->shapedGlyphs(si: &si);
2484
2485 // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2486 // when we're breaking a RTL script item, since the expected position passed into
2487 // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2488 if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2489 for (int i = itemGlyphsStart; i < glyphsStart; ++i) {
2490 if (!glyphLayout.attributes[i].dontPrint) {
2491 QFixed justification = QFixed::fromFixed(fixed: glyphLayout.justifications[i].space_18d6);
2492 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2493 }
2494 }
2495 } else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
2496 for (int i = itemGlyphsEnd; i > glyphsEnd; --i) {
2497 if (!glyphLayout.attributes[i].dontPrint) {
2498 QFixed justification = QFixed::fromFixed(fixed: glyphLayout.justifications[i].space_18d6);
2499 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2500 }
2501 }
2502 }
2503
2504 glyphLayout = glyphLayout.mid(position: glyphsStart, n: glyphsEnd - glyphsStart + 1);
2505
2506 QFixed x;
2507 QFixed width;
2508 iterator.getSelectionBounds(selectionX: &x, selectionWidth: &width);
2509
2510 if (glyphLayout.numGlyphs > 0) {
2511 QFontEngine *mainFontEngine;
2512#ifndef QT_NO_RAWFONT
2513 if (eng->useRawFont && eng->rawFont.isValid())
2514 mainFontEngine= eng->fontEngine(si);
2515 else
2516#endif
2517 mainFontEngine = font.d->engineForScript(script: si.analysis.script);
2518
2519 if (mainFontEngine->type() == QFontEngine::Multi) {
2520 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2521 int start = rtl ? glyphLayout.numGlyphs : 0;
2522 int end = start - 1;
2523 int which = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2524 for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs - 1);
2525 rtl ? --start : ++end) {
2526 const int e = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2527 if (e == which)
2528 continue;
2529
2530 QGlyphLayout subLayout = glyphLayout.mid(position: start, n: end - start + 1);
2531 multiFontEngine->ensureEngineAt(at: which);
2532
2533 QGlyphRun::GlyphRunFlags subFlags = flags;
2534 if (start == 0 && startsInsideLigature)
2535 subFlags |= QGlyphRun::SplitLigature;
2536
2537 {
2538 QGlyphRun glyphRun = glyphRunWithInfo(fontEngine: multiFontEngine->engine(at: which),
2539 text: eng->text,
2540 glyphLayout: subLayout,
2541 pos,
2542 flags: subFlags,
2543 retrievalFlags,
2544 selectionX: x,
2545 selectionWidth: width,
2546 glyphsStart: glyphsStart + start,
2547 glyphsEnd: glyphsStart + end,
2548 logClusters: logClusters + relativeFrom,
2549 textPosition: relativeFrom + si.position,
2550 textLength: relativeTo - relativeFrom + 1);
2551 if (!glyphRun.isEmpty())
2552 glyphRuns.append(t: glyphRun);
2553 }
2554 for (int i = 0; i < subLayout.numGlyphs; ++i) {
2555 if (!subLayout.attributes[i].dontPrint) {
2556 QFixed justification = QFixed::fromFixed(fixed: subLayout.justifications[i].space_18d6);
2557 pos.rx() += (subLayout.advances[i] + justification).toReal();
2558 }
2559 }
2560
2561 if (rtl)
2562 end = start - 1;
2563 else
2564 start = end + 1;
2565 which = e;
2566 }
2567
2568 QGlyphLayout subLayout = glyphLayout.mid(position: start, n: end - start + 1);
2569 multiFontEngine->ensureEngineAt(at: which);
2570
2571 QGlyphRun::GlyphRunFlags subFlags = flags;
2572 if ((start == 0 && startsInsideLigature) || endsInsideLigature)
2573 subFlags |= QGlyphRun::SplitLigature;
2574
2575 QGlyphRun glyphRun = glyphRunWithInfo(fontEngine: multiFontEngine->engine(at: which),
2576 text: eng->text,
2577 glyphLayout: subLayout,
2578 pos,
2579 flags: subFlags,
2580 retrievalFlags,
2581 selectionX: x,
2582 selectionWidth: width,
2583 glyphsStart: glyphsStart + start,
2584 glyphsEnd: glyphsStart + end,
2585 logClusters: logClusters + relativeFrom,
2586 textPosition: relativeFrom + si.position,
2587 textLength: relativeTo - relativeFrom + 1);
2588 if (!glyphRun.isEmpty())
2589 glyphRuns.append(t: glyphRun);
2590 } else {
2591 if (startsInsideLigature || endsInsideLigature)
2592 flags |= QGlyphRun::SplitLigature;
2593 QGlyphRun glyphRun = glyphRunWithInfo(fontEngine: mainFontEngine,
2594 text: eng->text,
2595 glyphLayout,
2596 pos,
2597 flags,
2598 retrievalFlags,
2599 selectionX: x,
2600 selectionWidth: width,
2601 glyphsStart,
2602 glyphsEnd,
2603 logClusters: logClusters + relativeFrom,
2604 textPosition: relativeFrom + si.position,
2605 textLength: relativeTo - relativeFrom + 1);
2606 if (!glyphRun.isEmpty())
2607 glyphRuns.append(t: glyphRun);
2608 }
2609 }
2610 }
2611
2612 return glyphRuns;
2613}
2614#endif // QT_NO_RAWFONT
2615
2616/*!
2617 \fn void QTextLine::draw(QPainter *painter, const QPointF &position) const
2618
2619 Draws a line on the given \a painter at the specified \a position.
2620*/
2621void QTextLine::draw(QPainter *painter, const QPointF &position) const
2622{
2623 draw_internal(p: painter, pos: position, selection: nullptr);
2624}
2625
2626void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
2627 const QTextLayout::FormatRange *selection) const
2628{
2629#ifndef QT_NO_RAWFONT
2630 // Not intended to work with rawfont
2631 Q_ASSERT(!eng->useRawFont);
2632#endif
2633 const QScriptLine &line = eng->lines[index];
2634
2635 bool noText = (selection && selection->format.property(SuppressText).toBool());
2636
2637 if (!line.length) {
2638 if (selection
2639 && selection->start <= line.from
2640 && selection->start + selection->length > line.from) {
2641
2642 const qreal lineHeight = line.height().toReal();
2643 QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
2644 lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
2645 drawBackground(p, chf: selection->format, r);
2646 }
2647 return;
2648 }
2649
2650 Q_CONSTINIT static QRectF maxFixedRect(-QFIXED_MAX / 2, -QFIXED_MAX / 2, QFIXED_MAX, QFIXED_MAX);
2651 const bool xlateToFixedRange = !maxFixedRect.contains(p: origPos);
2652 QPointF pos;
2653 if (Q_LIKELY(!xlateToFixedRange))
2654 pos = origPos;
2655 else
2656 p->translate(offset: origPos);
2657
2658
2659 QFixed lineBase = line.base();
2660 eng->clearDecorations();
2661 eng->enableDelayDecorations();
2662
2663 const QFixed y = QFixed::fromReal(r: pos.y()) + line.y + lineBase;
2664
2665 const QTextFormatCollection *formatCollection = eng->formatCollection();
2666
2667 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2668
2669 auto prepareFormat = [suppressColors, selection, this](QTextCharFormat &format,
2670 QScriptItem *si) {
2671 format.merge(other: eng->format(si));
2672
2673 if (suppressColors) {
2674 format.clearForeground();
2675 format.clearBackground();
2676 format.clearProperty(propertyId: QTextFormat::TextUnderlineColor);
2677 }
2678 if (selection)
2679 format.merge(other: selection->format);
2680 };
2681
2682 {
2683 QTextLineItemIterator iterator(eng, index, pos, selection);
2684 while (!iterator.atEnd()) {
2685 QScriptItem &si = iterator.next();
2686
2687 if (eng->hasFormats() || selection || formatCollection) {
2688 QTextCharFormat format;
2689 if (formatCollection != nullptr)
2690 format = formatCollection->defaultTextFormat();
2691 prepareFormat(format, &si);
2692 drawBackground(p, chf: format, r: QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2693 iterator.itemWidth.toReal(), line.height().toReal()));
2694 }
2695 }
2696 }
2697
2698 QPen pen = p->pen();
2699 {
2700 QTextLineItemIterator iterator(eng, index, pos, selection);
2701 while (!iterator.atEnd()) {
2702 QScriptItem &si = iterator.next();
2703
2704 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2705 continue;
2706
2707 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2708 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2709 continue;
2710
2711 QFixed itemBaseLine = y;
2712 QFont f = eng->font(si);
2713 QTextCharFormat format;
2714 if (formatCollection != nullptr)
2715 format = formatCollection->defaultTextFormat();
2716
2717 if (eng->hasFormats() || selection || formatCollection) {
2718 prepareFormat(format, &si);
2719 setPen(p, defaultPen: pen, chf: format);
2720
2721 const qreal baseLineOffset = format.baselineOffset() / 100.0;
2722 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2723 if (valign == QTextCharFormat::AlignSuperScript
2724 || valign == QTextCharFormat::AlignSubScript
2725 || !qFuzzyIsNull(d: baseLineOffset))
2726 {
2727 QFontEngine *fe = f.d->engineForScript(script: si.analysis.script);
2728 QFixed height = fe->ascent() + fe->descent();
2729 itemBaseLine -= height * QFixed::fromReal(r: baseLineOffset);
2730
2731 if (valign == QTextCharFormat::AlignSubScript)
2732 itemBaseLine += height * QFixed::fromReal(r: format.subScriptBaseline() / 100.0);
2733 else if (valign == QTextCharFormat::AlignSuperScript)
2734 itemBaseLine -= height * QFixed::fromReal(r: format.superScriptBaseline() / 100.0);
2735 }
2736 }
2737
2738 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2739
2740 if (eng->hasFormats()) {
2741 p->save();
2742 if (si.analysis.flags == QScriptAnalysis::Object && QTextDocumentPrivate::get(block&: eng->block)) {
2743 QFixed itemY = y - si.ascent;
2744 switch (format.verticalAlignment()) {
2745 case QTextCharFormat::AlignTop:
2746 itemY = y - lineBase;
2747 break;
2748 case QTextCharFormat::AlignMiddle:
2749 itemY = y - lineBase + (line.height() - si.height()) / 2;
2750 break;
2751 case QTextCharFormat::AlignBottom:
2752 itemY = y - lineBase + line.height() - si.height();
2753 break;
2754 default:
2755 break;
2756 }
2757
2758 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2759
2760 eng->docLayout()->drawInlineObject(painter: p, rect: itemRect,
2761 object: QTextInlineObject(iterator.item, eng),
2762 posInDocument: si.position + eng->block.position(),
2763 format);
2764 if (selection) {
2765 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2766 if (bg.style() != Qt::NoBrush) {
2767 QColor c = bg.color();
2768 c.setAlpha(128);
2769 p->fillRect(itemRect, color: c);
2770 }
2771 }
2772 } else { // si.isTab
2773 QFont f = eng->font(si);
2774 QTextItemInt gf(si, &f, format);
2775 gf.chars = nullptr;
2776 gf.num_chars = 0;
2777 gf.width = iterator.itemWidth;
2778 QPainterPrivate::get(painter: p)->drawTextItem(p: QPointF(iterator.x.toReal(), y.toReal()), ti: gf, textEngine: eng);
2779 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2780 const QChar visualTab = QChar(QChar::VisualTabCharacter);
2781 int w = QFontMetrics(f).horizontalAdvance(visualTab);
2782 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2783 if (x < 0)
2784 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2785 iterator.itemWidth.toReal(), line.height().toReal()),
2786 op: Qt::IntersectClip);
2787 else
2788 x /= 2; // Centered
2789 p->setFont(f);
2790 p->drawText(p: QPointF(iterator.x.toReal() + x,
2791 y.toReal()), s: visualTab);
2792 }
2793
2794 }
2795 p->restore();
2796 }
2797
2798 continue;
2799 }
2800
2801 unsigned short *logClusters = eng->logClusters(si: &si);
2802 QGlyphLayout glyphs = eng->shapedGlyphs(si: &si);
2803
2804 QTextItemInt gf(glyphs.mid(position: iterator.glyphsStart, n: iterator.glyphsEnd - iterator.glyphsStart),
2805 &f, eng->layoutData->string.unicode() + iterator.itemStart,
2806 iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2807 gf.logClusters = logClusters + iterator.itemStart - si.position;
2808 gf.width = iterator.itemWidth;
2809 gf.justified = line.justified;
2810 gf.initWithScriptItem(si);
2811
2812 Q_ASSERT(gf.fontEngine);
2813
2814 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2815 if (format.penProperty(propertyId: QTextFormat::TextOutline).style() != Qt::NoPen) {
2816 QPainterPath path;
2817 path.setFillRule(Qt::WindingFill);
2818
2819 if (gf.glyphs.numGlyphs)
2820 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, flags: gf.flags);
2821 if (gf.flags) {
2822 const QFontEngine *fe = gf.fontEngine;
2823 const qreal lw = fe->lineThickness().toReal();
2824 if (gf.flags & QTextItem::Underline) {
2825 qreal offs = fe->underlinePosition().toReal();
2826 path.addRect(x: pos.x(), y: pos.y() + offs, w: gf.width.toReal(), h: lw);
2827 }
2828 if (gf.flags & QTextItem::Overline) {
2829 qreal offs = fe->ascent().toReal() + 1;
2830 path.addRect(x: pos.x(), y: pos.y() - offs, w: gf.width.toReal(), h: lw);
2831 }
2832 if (gf.flags & QTextItem::StrikeOut) {
2833 qreal offs = fe->ascent().toReal() / 3;
2834 path.addRect(x: pos.x(), y: pos.y() - offs, w: gf.width.toReal(), h: lw);
2835 }
2836 }
2837
2838 p->save();
2839 p->setRenderHint(hint: QPainter::Antialiasing);
2840 //Currently QPen with a Qt::NoPen style still returns a default
2841 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2842 if (p->pen().style() == Qt::NoPen)
2843 p->setBrush(Qt::NoBrush);
2844 else
2845 p->setBrush(p->pen().brush());
2846
2847 p->setPen(format.textOutline());
2848 p->drawPath(path);
2849 p->restore();
2850 } else {
2851 if (noText)
2852 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2853 QPainterPrivate::get(painter: p)->drawTextItem(p: pos, ti: gf, textEngine: eng);
2854 }
2855
2856 if ((si.analysis.flags == QScriptAnalysis::Space
2857 || si.analysis.flags == QScriptAnalysis::Nbsp)
2858 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2859 QBrush c = format.foreground();
2860 if (c.style() != Qt::NoBrush)
2861 p->setPen(c.color());
2862 const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
2863 QFont oldFont = p->font();
2864 p->setFont(eng->font(si));
2865 p->drawText(p: QPointF(iterator.x.toReal(), itemBaseLine.toReal()), s: visualSpace);
2866 p->setPen(pen);
2867 p->setFont(oldFont);
2868 }
2869 }
2870 }
2871 eng->drawDecorations(painter: p);
2872
2873 if (xlateToFixedRange)
2874 p->translate(offset: -origPos);
2875
2876 if (eng->hasFormats())
2877 p->setPen(pen);
2878}
2879
2880/*!
2881 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2882
2883 \overload
2884*/
2885
2886/*!
2887 Converts the cursor position \a cursorPos to the corresponding x position
2888 inside the line, taking account of the \a edge.
2889
2890 If \a cursorPos is not a valid cursor position, the nearest valid
2891 cursor position will be used instead, and \a cursorPos will be modified to
2892 point to this valid cursor position.
2893
2894 \sa xToCursor()
2895*/
2896qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2897{
2898 const QScriptLine &line = eng->lines[index];
2899 bool lastLine = index >= eng->lines.size() - 1;
2900
2901 QFixed x = line.x + eng->alignLine(line) - eng->leadingSpaceWidth(line);
2902
2903 if (!eng->layoutData)
2904 eng->itemize();
2905 if (!eng->layoutData->items.size()) {
2906 *cursorPos = line.from;
2907 return x.toReal();
2908 }
2909
2910 int lineEnd = line.from + line.length + line.trailingSpaces;
2911 int pos = qBound(min: line.from, val: *cursorPos, max: lineEnd);
2912 const QCharAttributes *attributes = eng->attributes();
2913 if (!attributes) {
2914 *cursorPos = line.from;
2915 return x.toReal();
2916 }
2917 while (pos < lineEnd && !attributes[pos].graphemeBoundary)
2918 pos++;
2919 // end of line ensure we have the last item on the line
2920 int itm = pos == lineEnd ? eng->findItem(strPos: pos-1) : eng->findItem(strPos: pos);
2921 if (itm < 0) {
2922 *cursorPos = line.from;
2923 return x.toReal();
2924 }
2925 eng->shapeLine(line);
2926
2927 const QScriptItem *scriptItem = &eng->layoutData->items[itm];
2928 if (!scriptItem->num_glyphs)
2929 eng->shape(item: itm);
2930
2931 if ((scriptItem->analysis.bidiLevel % 2 != eng->isRightToLeft()) && !eng->visualCursorMovement()) {
2932 // If the item we found has a different writing direction than the engine,
2933 // check if the cursor is between two items with different writing direction
2934 int neighborItem = itm;
2935 if (neighborItem > 0 && scriptItem->position == pos)
2936 --neighborItem;
2937 else if (neighborItem < eng->layoutData->items.size() - 1 && scriptItem->position + scriptItem->num_glyphs == pos)
2938 ++neighborItem;
2939 const bool onBoundary = neighborItem != itm && scriptItem->analysis.bidiLevel != eng->layoutData->items[neighborItem].analysis.bidiLevel;
2940 // If we are, prioritise the neighbor item that has the same direction as the engine
2941 if (onBoundary) {
2942 if (eng->isRightToLeft() != scriptItem->analysis.bidiLevel % 2) {
2943 itm = neighborItem;
2944 scriptItem = &eng->layoutData->items[itm];
2945 if (!scriptItem->num_glyphs)
2946 eng->shape(item: itm);
2947 }
2948 }
2949 }
2950
2951 const int l = eng->length(item: itm);
2952 pos = qBound(min: 0, val: pos - scriptItem->position, max: l);
2953
2954 QGlyphLayout glyphs = eng->shapedGlyphs(si: scriptItem);
2955 unsigned short *logClusters = eng->logClusters(si: scriptItem);
2956 Q_ASSERT(logClusters);
2957
2958 int glyph_pos = pos == l ? scriptItem->num_glyphs : logClusters[pos];
2959 if (edge == Trailing && glyph_pos < scriptItem->num_glyphs) {
2960 // trailing edge is leading edge of next cluster
2961 glyph_pos++;
2962 while (glyph_pos < scriptItem->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2963 glyph_pos++;
2964 }
2965
2966 bool reverse = scriptItem->analysis.bidiLevel % 2;
2967
2968
2969 // add the items left of the cursor
2970
2971 int firstItem = eng->findItem(strPos: line.from);
2972 int lastItem = eng->findItem(strPos: lineEnd - 1, firstItem: itm);
2973 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2974
2975 QVarLengthArray<int> visualOrder(nItems);
2976 QVarLengthArray<uchar> levels(nItems);
2977 for (int i = 0; i < nItems; ++i)
2978 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2979 QTextEngine::bidiReorder(numRuns: nItems, levels: levels.data(), visualOrder: visualOrder.data());
2980
2981 for (int i = 0; i < nItems; ++i) {
2982 int item = visualOrder[i]+firstItem;
2983 if (item == itm)
2984 break;
2985 QScriptItem &si = eng->layoutData->items[item];
2986 if (!si.num_glyphs)
2987 eng->shape(item);
2988
2989 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2990 x += si.width;
2991 continue;
2992 }
2993
2994 const int itemLength = eng->length(item);
2995 int start = qMax(a: line.from, b: si.position);
2996 int end = qMin(a: lineEnd, b: si.position + itemLength);
2997
2998 logClusters = eng->logClusters(si: &si);
2999
3000 int gs = logClusters[start-si.position];
3001 int ge = (end == si.position + itemLength) ? si.num_glyphs-1 : logClusters[end-si.position-1];
3002
3003 QGlyphLayout glyphs = eng->shapedGlyphs(si: &si);
3004
3005 while (gs <= ge) {
3006 x += glyphs.effectiveAdvance(item: gs);
3007 ++gs;
3008 }
3009 }
3010
3011 logClusters = eng->logClusters(si: scriptItem);
3012 glyphs = eng->shapedGlyphs(si: scriptItem);
3013 if (scriptItem->analysis.flags >= QScriptAnalysis::TabOrObject) {
3014 if (pos == (reverse ? 0 : l))
3015 x += scriptItem->width;
3016 } else {
3017 bool rtl = eng->isRightToLeft();
3018 bool visual = eng->visualCursorMovement();
3019 int end = qMin(a: lineEnd, b: scriptItem->position + l) - scriptItem->position;
3020 if (reverse) {
3021 int glyph_end = end == l ? scriptItem->num_glyphs : logClusters[end];
3022 int glyph_start = glyph_pos;
3023 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
3024 glyph_start++;
3025 for (int i = glyph_end - 1; i >= glyph_start; i--)
3026 x += glyphs.effectiveAdvance(item: i);
3027 x -= eng->offsetInLigature(si: scriptItem, pos, max: end, glyph_pos);
3028 } else {
3029 int start = qMax(a: line.from - scriptItem->position, b: 0);
3030 int glyph_start = logClusters[start];
3031 int glyph_end = glyph_pos;
3032 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
3033 glyph_end--;
3034 for (int i = glyph_start; i <= glyph_end; i++)
3035 x += glyphs.effectiveAdvance(item: i);
3036 x += eng->offsetInLigature(si: scriptItem, pos, max: end, glyph_pos);
3037 }
3038 }
3039
3040 if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width)
3041 x = line.x + line.width;
3042 if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0)
3043 x = 0;
3044
3045 *cursorPos = pos + scriptItem->position;
3046 return x.toReal();
3047}
3048
3049/*!
3050 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
3051
3052 Converts the x-coordinate \a x, to the nearest matching cursor
3053 position, depending on the cursor position type, \a cpos.
3054 Note that result cursor position includes possible preedit area text.
3055
3056 \sa cursorToX()
3057*/
3058int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
3059{
3060 QFixed x = QFixed::fromReal(r: _x);
3061 const QScriptLine &line = eng->lines[index];
3062 bool lastLine = index >= eng->lines.size() - 1;
3063 int lineNum = index;
3064
3065 if (!eng->layoutData)
3066 eng->itemize();
3067
3068 int line_length = textLength();
3069
3070 if (!line_length)
3071 return line.from;
3072
3073 int firstItem = eng->findItem(strPos: line.from);
3074 int lastItem = eng->findItem(strPos: line.from + line_length - 1, firstItem);
3075 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3076
3077 if (!nItems)
3078 return 0;
3079
3080 x -= line.x;
3081 x -= eng->alignLine(line);
3082// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
3083
3084 QVarLengthArray<int> visualOrder(nItems);
3085 QVarLengthArray<unsigned char> levels(nItems);
3086 for (int i = 0; i < nItems; ++i)
3087 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3088 QTextEngine::bidiReorder(numRuns: nItems, levels: levels.data(), visualOrder: visualOrder.data());
3089
3090 bool visual = eng->visualCursorMovement();
3091 if (x <= 0) {
3092 // left of first item
3093 if (eng->isRightToLeft())
3094 return line.from + line_length;
3095 return line.from;
3096 } else if (x < line.textWidth || (line.justified && x < line.width)) {
3097 // has to be in one of the runs
3098 QFixed pos;
3099 bool rtl = eng->isRightToLeft();
3100
3101 eng->shapeLine(line);
3102 const auto insertionPoints = (visual && rtl) ? eng->insertionPointsForLine(lineNum) : std::vector<int>();
3103 int nchars = 0;
3104 for (int i = 0; i < nItems; ++i) {
3105 int item = visualOrder[i]+firstItem;
3106 QScriptItem &si = eng->layoutData->items[item];
3107 int item_length = eng->length(item);
3108// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
3109
3110 int start = qMax(a: line.from - si.position, b: 0);
3111 int end = qMin(a: line.from + line_length - si.position, b: item_length);
3112
3113 unsigned short *logClusters = eng->logClusters(si: &si);
3114
3115 int gs = logClusters[start];
3116 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
3117 QGlyphLayout glyphs = eng->shapedGlyphs(si: &si);
3118
3119 QFixed item_width = 0;
3120 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3121 item_width = si.width;
3122 } else {
3123 int g = gs;
3124 while (g <= ge) {
3125 item_width += glyphs.effectiveAdvance(item: g);
3126 ++g;
3127 }
3128 }
3129// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
3130
3131 if (pos + item_width < x) {
3132 pos += item_width;
3133 nchars += end;
3134 continue;
3135 }
3136// qDebug(" inside run");
3137 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3138 if (cpos == QTextLine::CursorOnCharacter)
3139 return si.position;
3140 bool left_half = (x - pos) < item_width/2;
3141
3142 if (bool(si.analysis.bidiLevel % 2) != left_half)
3143 return si.position;
3144 return si.position + 1;
3145 }
3146
3147 int glyph_pos = -1;
3148 QFixed edge;
3149 // has to be inside run
3150 if (cpos == QTextLine::CursorOnCharacter) {
3151 if (si.analysis.bidiLevel % 2) {
3152 pos += item_width;
3153 glyph_pos = gs;
3154 while (gs <= ge) {
3155 if (glyphs.attributes[gs].clusterStart) {
3156 if (pos < x)
3157 break;
3158 glyph_pos = gs;
3159 edge = pos;
3160 }
3161 pos -= glyphs.effectiveAdvance(item: gs);
3162 ++gs;
3163 }
3164 } else {
3165 glyph_pos = gs;
3166 while (gs <= ge) {
3167 if (glyphs.attributes[gs].clusterStart) {
3168 if (pos > x)
3169 break;
3170 glyph_pos = gs;
3171 edge = pos;
3172 }
3173 pos += glyphs.effectiveAdvance(item: gs);
3174 ++gs;
3175 }
3176 }
3177 } else {
3178 QFixed dist = INT_MAX/256;
3179 if (si.analysis.bidiLevel % 2) {
3180 if (!visual || rtl || (lastLine && i == nItems - 1)) {
3181 pos += item_width;
3182 while (gs <= ge) {
3183 if (glyphs.attributes[gs].clusterStart && qAbs(t: x-pos) < dist) {
3184 glyph_pos = gs;
3185 edge = pos;
3186 dist = qAbs(t: x-pos);
3187 }
3188 pos -= glyphs.effectiveAdvance(item: gs);
3189 ++gs;
3190 }
3191 } else {
3192 while (ge >= gs) {
3193 if (glyphs.attributes[ge].clusterStart && qAbs(t: x-pos) < dist) {
3194 glyph_pos = ge;
3195 edge = pos;
3196 dist = qAbs(t: x-pos);
3197 }
3198 pos += glyphs.effectiveAdvance(item: ge);
3199 --ge;
3200 }
3201 }
3202 } else {
3203 if (!visual || !rtl || (lastLine && i == 0)) {
3204 while (gs <= ge) {
3205 if (glyphs.attributes[gs].clusterStart && qAbs(t: x-pos) < dist) {
3206 glyph_pos = gs;
3207 edge = pos;
3208 dist = qAbs(t: x-pos);
3209 }
3210 pos += glyphs.effectiveAdvance(item: gs);
3211 ++gs;
3212 }
3213 } else {
3214 QFixed oldPos = pos;
3215 while (gs <= ge) {
3216 pos += glyphs.effectiveAdvance(item: gs);
3217 if (glyphs.attributes[gs].clusterStart && qAbs(t: x-pos) < dist) {
3218 glyph_pos = gs;
3219 edge = pos;
3220 dist = qAbs(t: x-pos);
3221 }
3222 ++gs;
3223 }
3224 pos = oldPos;
3225 }
3226 }
3227 if (qAbs(t: x-pos) < dist) {
3228 if (visual) {
3229 if (!rtl && i < nItems - 1) {
3230 nchars += end;
3231 continue;
3232 }
3233 if (rtl && nchars > 0)
3234 return insertionPoints[size_t(lastLine ? nchars : nchars - 1)];
3235 }
3236 return eng->positionInLigature(si: &si, end, x, edge: pos, glyph_pos: -1,
3237 cursorOnCharacter: cpos == QTextLine::CursorOnCharacter);
3238 }
3239 }
3240 Q_ASSERT(glyph_pos != -1);
3241 return eng->positionInLigature(si: &si, end, x, edge, glyph_pos,
3242 cursorOnCharacter: cpos == QTextLine::CursorOnCharacter);
3243 }
3244 }
3245 // right of last item
3246 int pos = line.from;
3247 if (!eng->isRightToLeft())
3248 pos += line_length;
3249
3250 // except for the last line we assume that the
3251 // character between lines is a space and we want
3252 // to position the cursor to the left of that
3253 // character.
3254 if (index < eng->lines.size() - 1)
3255 pos = qMin(a: eng->previousLogicalPosition(oldPos: pos), b: pos);
3256
3257 return pos;
3258}
3259
3260QT_END_NAMESPACE
3261

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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