Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtextlayout.cpp
Go to the documentation of this file.
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"
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>
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
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(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(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(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(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(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(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(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(&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(&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("") : 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(d->block) != nullptr)
471 QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), 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(d->block) != nullptr)
508 QTextDocumentPrivate::get(d->block)->documentChange(d->block.position(), 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("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("QTextLayout::endLayout: Called without beginLayout()");
617 return;
618 }
619#endif
620 int l = d->lines.size();
621 if (l && d->lines.at(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(oldPos)) {
665 oldPos++;
666 while (oldPos < len && d->atWordSeparator(oldPos))
667 oldPos++;
668 } else {
669 while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(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(oldPos-1)) {
704 oldPos--;
705 while (oldPos && d->atWordSeparator(oldPos-1))
706 oldPos--;
707 } else {
708 while (oldPos > 0 && !attributes[oldPos - 1].whiteSpace && !d->atWordSeparator(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, 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, 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("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(l-1).length < 0) {
796 QTextLine(l-1, d).setNumColumns(INT_MAX);
797 if (d->maxWidth > QFIXED_MAX / 2) {
798 qWarning("QTextLayout: text too long, truncated.");
799 return QTextLine();
800 }
801 }
802 int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length + d->lines.at(l-1).trailingSpaces : 0;
803 int strlen = d->layoutData->string.size();
804 if (l && from >= strlen) {
805 if (!d->lines.at(l-1).length || d->layoutData->string.at(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(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(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(0).x;
883 QFixed ymin = d->lines.at(0).y;
884
885 for (int i = 0; i < d->lines.size(); ++i) {
886 const QScriptLine &si = d->lines.at(i);
887 xmin = qMin(xmin, si.x);
888 ymin = qMin(ymin, si.y);
889 QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
890 xmax = qMax(xmax, si.x+lineWidth);
891 // ### shouldn't the ascent be used in ymin???
892 ymax = qMax(ymax, 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, &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.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.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, 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<std::pair<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, 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 std::pair<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(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(clip.y() - position.y());
1101 clipe = clipy + QFixed::fromReal(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(line);
1128 QTextLine tl(line, d);
1129
1130 QRectF lineRect(tl.naturalTextRect());
1131 lineRect.translate(position);
1132 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1133 lineRect.setBottom(qCeil(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(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
1147 } else {
1148 region.addRect(clipIfValid(lineRect, clip));
1149 }
1150
1151 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1152 QRectF fullLineRect(tl.rect());
1153 fullLineRect.translate(position);
1154 fullLineRect.setRight(QFIXED_MAX);
1155 fullLineRect.setBottom(qCeil(fullLineRect.bottom()));
1156
1157 const bool rightToLeft = d->isRightToLeft();
1158
1159 if (!selectionEndInLine) {
1160 region.addRect(clipIfValid(rightToLeft ? QRectF(fullLineRect.topLeft(), lineRect.bottomLeft())
1161 : QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1162 }
1163 if (!selectionStartInLine) {
1164 region.addRect(clipIfValid(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(clipIfValid(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(QTextFormat::OutlinePen));
1180 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1181 p->drawPath(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, selection.format.property(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(QTextFormat::BackgroundBrush, QBrush());
1197 selection.format.clearProperty(QTextFormat::OutlinePen);
1198 }
1199
1200 selection.format.setProperty(SuppressText, !hasText);
1201
1202 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1203 continue;
1204
1205 p->save();
1206 p->setClipPath(region, Qt::IntersectClip);
1207
1208 for (int line = firstLine; line < lastLine; ++line) {
1209 QTextLine l(line, d);
1210 l.draw_internal(p, position, &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(needsTextButNoBackground, Qt::IntersectClip);
1228 FormatRange selection;
1229 selection.start = 0;
1230 selection.length = INT_MAX;
1231 selection.format.setProperty(SuppressBackground, true);
1232 for (int line = firstLine; line < lastLine; ++line) {
1233 QTextLine l(line, d);
1234 l.draw_internal(p, position, &selection);
1235 }
1236 p->restore();
1237 }
1238
1239 if (!excludedRegion.isEmpty()) {
1240 p->save();
1241 QPainterPath path;
1242 QRectF br = boundingRect().translated(position);
1243 br.setRight(QFIXED_MAX);
1244 if (!clip.isNull())
1245 br = br.intersected(clip);
1246 path.addRect(br);
1247 path -= excludedRegion;
1248 p->setClipPath(path, Qt::IntersectClip);
1249 }
1250
1251 for (int i = firstLine; i < lastLine; ++i) {
1252 QTextLine l(i, d);
1253 l.draw(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, 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(0, cursorPosition, d->layoutData->string.size());
1294 int line = d->lineNumberForTextPosition(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(line);
1302
1303 qreal x = position.x() + l.cursorToX(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(cursorPosition);
1317
1318 if (itm >= 0) {
1319 const QScriptItem *si = &d->layoutData->items.at(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(QPainter::Antialiasing);
1349 QPainter::CompositionMode origCompositionMode = p->compositionMode();
1350 if (p->paintEngine()->hasFeature(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(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(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(QPointF(center, y), QPointF(center, qCeil(y + (base + descent).toReal())));
1364 p->setPen(origPen);
1365 }
1366 p->setCompositionMode(origCompositionMode);
1367 if (toggleAntialiasing)
1368 p->setRenderHint(QPainter::Antialiasing, false);
1369 if (d->layoutData->hasBidi) {
1370 const int arrow_extent = 4;
1371 int sign = rightToLeft ? -1 : 1;
1372 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1373 p->drawLine(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(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(index);
1456 QFixed x = sl.x + eng->alignLine(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(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(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(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(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(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(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(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(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(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(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("QTextLine: Can't set a line width while not layouting.");
1612 return;
1613 }
1614
1615 line.width = QFixed::fromReal(qBound(0.0, width, 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(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(qBound(0.0, alignmentWidth, qreal(QFIXED_MAX)));
1656 line.length = 0;
1657 line.textWidth = 0;
1658 layout_helper(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, nullptr, &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(QFixed::fromReal(rb), QFixed(0));
1732 }
1733
1734 inline void calculateRightBearing()
1735 {
1736 if (currentPosition <= 0)
1737 return;
1738 calculateRightBearing(fontEngine.data(), currentGlyph());
1739 }
1740
1741 inline void calculateRightBearingForPreviousGlyph()
1742 {
1743 if (previousGlyph > 0)
1744 calculateRightBearing(previousGlyphFontEngine.data(), 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(rightBearing);
1762 }
1763 };
1764
1765Q_CONSTINIT const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
1766
1767inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1768{
1769 LB_DEBUG("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(line.from);
1847 Q_ASSERT(newItem >= 0);
1848
1849 LB_DEBUG("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 bool reachedEndOfLine = false;
1864 QFixed maxInlineObjectHeight = 0;
1865
1866 const bool includeTrailingSpaces = eng->option.flags() & QTextOption::IncludeTrailingSpaces;
1867
1868 while (newItem < eng->layoutData->items.size()) {
1869 lbh.resetRightBearing();
1870 if (newItem != item) {
1871 item = newItem;
1872 const QScriptItem &current = eng->layoutData->items.at(item);
1873 if (!current.num_glyphs) {
1874 eng->shape(item);
1875 attributes = eng->attributes();
1876 if (!attributes)
1877 return;
1878 lbh.logClusters = eng->layoutData->logClustersPtr;
1879 }
1880 lbh.currentPosition = qMax(line.from, current.position);
1881 end = current.position + eng->length(item);
1882 lbh.glyphs = eng->shapedGlyphs(&current);
1883 QFontEngine *fontEngine = eng->fontEngine(current);
1884 if (lbh.fontEngine != fontEngine) {
1885 lbh.fontEngine = fontEngine;
1886 lbh.minimumRightBearing = qMin(QFixed(),
1887 QFixed::fromReal(fontEngine->minRightBearing()));
1888 }
1889 }
1890 const QScriptItem &current = eng->layoutData->items.at(item);
1891
1892 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1893 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1894 current.ascent);
1895 if (current.analysis.flags != QScriptAnalysis::Object) {
1896 // objects need some special treatment as they can special alignment or be floating
1897 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1898 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1899 }
1900
1901 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1902 lbh.whiteSpaceOrObject = true;
1903 if (lbh.checkFullOtherwiseExtend(line))
1904 goto found;
1905
1906 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1907 QFixed tabWidth = eng->calculateTabWidth(item, x);
1908 attributes = eng->attributes();
1909 if (!attributes)
1910 return;
1911 lbh.logClusters = eng->layoutData->logClustersPtr;
1912 lbh.glyphs = eng->shapedGlyphs(&current);
1913
1914 lbh.spaceData.textWidth += tabWidth;
1915 lbh.spaceData.length++;
1916 newItem = item + 1;
1917
1918 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1919 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1920
1921 if (lbh.checkFullOtherwiseExtend(line))
1922 goto found;
1923 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1924 lbh.whiteSpaceOrObject = true;
1925 // if the line consists only of the line separator make sure
1926 // we have a sane height
1927 if (!line.length && !lbh.tmpData.length)
1928 line.setDefaultHeight(eng);
1929 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1930 if (lbh.checkFullOtherwiseExtend(line))
1931 goto found;
1932
1933 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1934 current, lbh.logClusters, lbh.glyphs);
1935 } else {
1936 lbh.tmpData.length++;
1937 lbh.calculateRightBearingForPreviousGlyph();
1938 }
1939 line += lbh.tmpData;
1940 manuallyWrapped = true;
1941 goto found;
1942 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1943 lbh.whiteSpaceOrObject = true;
1944 lbh.tmpData.length++;
1945
1946 if (QTextDocumentPrivate::get(eng->block) != nullptr) {
1947 QTextInlineObject inlineObject(item, eng);
1948 QTextFormat f = inlineObject.format();
1949 eng->docLayout()->positionInlineObject(inlineObject, eng->block.position() + current.position, f);
1950 QTextCharFormat::VerticalAlignment valign = f.toCharFormat().verticalAlignment();
1951 if (valign != QTextCharFormat::AlignTop && valign != QTextCharFormat::AlignBottom) {
1952 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1953 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1954 }
1955 }
1956
1957 lbh.tmpData.textWidth += current.width;
1958
1959 newItem = item + 1;
1960 ++lbh.glyphCount;
1961 if (lbh.checkFullOtherwiseExtend(line))
1962 goto found;
1963
1964 hasInlineObject = true;
1965 maxInlineObjectHeight = qMax(maxInlineObjectHeight, current.ascent + current.descent);
1966
1967 } else if (attributes[lbh.currentPosition].whiteSpace
1968 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1969 // If we are adding a space block, we save the last non-whitespace glyph for calculating
1970 // the right bearing later
1971 if (lbh.currentPosition > 0 && !attributes[lbh.currentPosition - 1].whiteSpace)
1972 lbh.saveCurrentGlyph();
1973 lbh.whiteSpaceOrObject = true;
1974 while (lbh.currentPosition < end
1975 && attributes[lbh.currentPosition].whiteSpace
1976 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak) {
1977 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1978 current, lbh.logClusters, lbh.glyphs);
1979 }
1980 } else {
1981 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width)
1982 goto found;
1983
1984 lbh.whiteSpaceOrObject = false;
1985 bool sb_or_ws = false;
1986 // We save the previous glyph so we can use it for calculating the right bearing
1987 // later. If we are trimming trailing spaces, the previous glyph is whitespace
1988 // and we have already recorded a non-whitespace glyph, we keep that one instead.
1989 if (lbh.currentPosition == 0
1990 || lbh.previousGlyph == 0
1991 || includeTrailingSpaces
1992 || !attributes[lbh.currentPosition - 1].whiteSpace) {
1993 lbh.saveCurrentGlyph();
1994 }
1995 QFixed accumulatedTextWidth;
1996 do {
1997 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1998 current, lbh.logClusters, lbh.glyphs, &accumulatedTextWidth);
1999
2000 // This is a hack to fix a regression caused by the introduction of the
2001 // whitespace flag to non-breakable spaces and will cause the non-breakable
2002 // spaces to behave as in previous Qt versions in the line breaking algorithm.
2003 // The line breaks do not currently follow the Unicode specs, but fixing this would
2004 // require refactoring the code and would cause behavioral regressions.
2005 const bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.size()
2006 && attributes[lbh.currentPosition].whiteSpace
2007 && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak;
2008
2009 if (lbh.currentPosition >= eng->layoutData->string.size()
2010 || isBreakableSpace
2011 || attributes[lbh.currentPosition].lineBreak
2012 || lbh.tmpData.textWidth >= QFIXED_MAX) {
2013 sb_or_ws = true;
2014 break;
2015 } else if (attributes[lbh.currentPosition].graphemeBoundary) {
2016 if (breakWordOrAny) {
2017 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2018 accumulatedTextWidth = 0;
2019 }
2020 if (breakany)
2021 break;
2022 }
2023 } while (lbh.currentPosition < end);
2024 lbh.minw = qMax(accumulatedTextWidth, lbh.minw);
2025
2026 if (lbh.currentPosition > 0 && lbh.currentPosition <= end
2027 && (lbh.currentPosition == end || attributes[lbh.currentPosition].lineBreak)
2028 && eng->layoutData->string.at(lbh.currentPosition - 1) == QChar::SoftHyphen) {
2029 // if we are splitting up a word because of
2030 // a soft hyphen then we ...
2031 //
2032 // a) have to take the width of the soft hyphen into
2033 // account to see if the first syllable(s) /and/
2034 // the soft hyphen fit into the line
2035 //
2036 // b) if we are so short of available width that the
2037 // soft hyphen is the first breakable position, then
2038 // we don't want to show it. However we initially
2039 // have to take the width for it into account so that
2040 // the text document layout sees the overflow and
2041 // switch to break-anywhere mode, in which we
2042 // want the soft-hyphen to slip into the next line
2043 // and thus become invisible again.
2044 //
2045 lbh.currentSoftHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]];
2046 }
2047
2048 if (sb_or_ws|breakany) {
2049 // To compute the final width of the text we need to take negative right bearing
2050 // into account (negative right bearing means the glyph has pixel data past the
2051 // advance length). Note that the negative right bearing is an absolute number,
2052 // so that we can apply it to the width using straight forward addition.
2053
2054 // Store previous right bearing (for the already accepted glyph) in case we
2055 // end up breaking due to the current glyph being too wide.
2056 QFixed previousRightBearing = lbh.rightBearing;
2057
2058 // We skip calculating the right bearing if the minimum negative bearing is too
2059 // small to possibly expand the text beyond the edge. Note that this optimization
2060 // will in some cases fail, as the minimum right bearing reported by the font
2061 // engine may not cover all the glyphs in the font. The result is that we think
2062 // we don't need to break at the current glyph (because the right bearing is 0),
2063 // and when we then end up breaking on the next glyph we compute the right bearing
2064 // and end up with a line width that is slightly larger width than what was requested.
2065 // Unfortunately we can't remove this optimization as it will slow down text
2066 // layouting significantly, so we accept the slight correctness issue.
2067 if ((lbh.calculateNewWidth(line) + qAbs(lbh.minimumRightBearing)) > line.width)
2068 lbh.calculateRightBearing();
2069
2070 if (lbh.checkFullOtherwiseExtend(line)) {
2071
2072 // We are too wide to accept the next glyph with its bearing, so we restore the
2073 // right bearing to that of the previous glyph (the one that was already accepted),
2074 // so that the bearing can be be applied to the final width of the text below.
2075 if (previousRightBearing != LineBreakHelper::RightBearingNotCalculated)
2076 lbh.rightBearing = previousRightBearing;
2077 else
2078 lbh.calculateRightBearingForPreviousGlyph();
2079
2080 line.textWidth += lbh.commitedSoftHyphenWidth;
2081
2082 goto found;
2083 }
2084 }
2085 lbh.saveCurrentGlyph();
2086 }
2087 if (lbh.currentPosition == end)
2088 newItem = item + 1;
2089 }
2090 LB_DEBUG("reached end of line");
2091 reachedEndOfLine = true;
2092 lbh.checkFullOtherwiseExtend(line);
2093 line.textWidth += lbh.commitedSoftHyphenWidth;
2094found:
2095 line.textAdvance = line.textWidth;
2096
2097 // If right bearing has not been calculated yet, do that now
2098 if (lbh.rightBearing == LineBreakHelper::RightBearingNotCalculated && !lbh.whiteSpaceOrObject)
2099 lbh.calculateRightBearing();
2100
2101 // Then apply any negative right bearing
2102 const QFixed textWidthWithoutBearing = line.textWidth;
2103 line.textWidth += lbh.negativeRightBearing();
2104
2105 if (line.length == 0) {
2106 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2107 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2108 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2109 line += lbh.tmpData;
2110 }
2111
2112 if (hasInlineObject && QTextDocumentPrivate::get(eng->block) != nullptr) {
2113 // position top/bottom aligned inline objects
2114 if (maxInlineObjectHeight > line.ascent + line.descent) {
2115 // extend line height if required
2116 QFixed toAdd = (maxInlineObjectHeight - line.ascent - line.descent)/2;
2117 line.ascent += toAdd;
2118 line.descent = maxInlineObjectHeight - line.ascent;
2119 }
2120 int startItem = eng->findItem(line.from);
2121 int endItem = eng->findItem(line.from + line.length);
2122 if (endItem < 0)
2123 endItem = eng->layoutData->items.size();
2124 for (int item = startItem; item < endItem; ++item) {
2125 QScriptItem &current = eng->layoutData->items[item];
2126 if (current.analysis.flags == QScriptAnalysis::Object) {
2127 QTextInlineObject inlineObject(item, eng);
2128 QTextCharFormat::VerticalAlignment align = inlineObject.format().toCharFormat().verticalAlignment();
2129 QFixed height = current.ascent + current.descent;
2130 switch (align) {
2131 case QTextCharFormat::AlignTop:
2132 current.ascent = line.ascent;
2133 current.descent = height - line.ascent;
2134 break;
2135 case QTextCharFormat::AlignMiddle:
2136 current.ascent = (line.ascent + line.descent) / 2 - line.descent + height / 2;
2137 current.descent = height - line.ascent;
2138 break;
2139 case QTextCharFormat::AlignBottom:
2140 current.descent = line.descent;
2141 current.ascent = height - line.descent;
2142 break;
2143 default:
2144 break;
2145 }
2146 Q_ASSERT(line.ascent >= current.ascent);
2147 Q_ASSERT(line.descent >= current.descent);
2148 }
2149 }
2150 }
2151
2152
2153 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2154 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2155 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2156
2157 const QFixed trailingSpace = (includeTrailingSpaces ? lbh.spaceData.textWidth : QFixed(0));
2158 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2159 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2160 || (lbh.maxGlyphs == INT_MAX && line.textWidth > (line.width - trailingSpace))) {
2161
2162 eng->option.setWrapMode(QTextOption::WrapAnywhere);
2163 layout_helper(lbh.maxGlyphs);
2164 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2165 return;
2166 }
2167 }
2168
2169 if (lbh.manualWrap) {
2170 eng->minWidth = qMax(eng->minWidth, line.textWidth);
2171 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2172 } else {
2173 eng->minWidth = qMax(eng->minWidth, lbh.minw);
2174
2175 const QFixed actualTextWidth = manuallyWrapped || reachedEndOfLine
2176 ? line.textWidth
2177 : textWidthWithoutBearing;
2178 if (qAddOverflow(eng->layoutData->currentMaxWidth, actualTextWidth, &eng->layoutData->currentMaxWidth))
2179 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2180 if (!manuallyWrapped) {
2181 if (qAddOverflow(eng->layoutData->currentMaxWidth, lbh.spaceData.textWidth, &eng->layoutData->currentMaxWidth))
2182 eng->layoutData->currentMaxWidth = QFIXED_MAX;
2183 }
2184 eng->maxWidth = qMax(eng->maxWidth, eng->layoutData->currentMaxWidth);
2185 if (manuallyWrapped)
2186 eng->layoutData->currentMaxWidth = 0;
2187 }
2188
2189 line.textWidth += trailingSpace;
2190 if (lbh.spaceData.length) {
2191 line.trailingSpaces = lbh.spaceData.length;
2192 line.hasTrailingSpaces = true;
2193 }
2194
2195 line.justified = false;
2196 line.gridfitted = false;
2197}
2198
2199/*!
2200 Moves the line to position \a pos.
2201*/
2202void QTextLine::setPosition(const QPointF &pos)
2203{
2204 eng->lines[index].x = QFixed::fromReal(pos.x());
2205 eng->lines[index].y = QFixed::fromReal(pos.y());
2206}
2207
2208/*!
2209 Returns the line's position relative to the text layout's position.
2210*/
2211QPointF QTextLine::position() const
2212{
2213 return QPointF(eng->lines.at(index).x.toReal(), eng->lines.at(index).y.toReal());
2214}
2215
2216// ### DOC: I have no idea what this means/does.
2217// You create a text layout with a string of text. Once you laid
2218// it out, it contains a number of QTextLines. from() returns the position
2219// inside the text string where this line starts. If you e.g. has a
2220// text of "This is a string", laid out into two lines (the second
2221// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2222// layout.lineAt(1).from() == 8.
2223/*!
2224 Returns the start of the line from the beginning of the string
2225 passed to the QTextLayout.
2226*/
2227int QTextLine::textStart() const
2228{
2229 return eng->lines.at(index).from;
2230}
2231
2232/*!
2233 Returns the length of the text in the line.
2234
2235 \sa naturalTextWidth()
2236*/
2237int QTextLine::textLength() const
2238{
2239 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2240 && eng->block.isValid() && index == eng->lines.size()-1) {
2241 return eng->lines.at(index).length - 1;
2242 }
2243 return eng->lines.at(index).length + eng->lines.at(index).trailingSpaces;
2244}
2245
2246static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
2247{
2248 QBrush bg = chf.background();
2249 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2250 p->fillRect(r.toAlignedRect(), bg);
2251}
2252
2253static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
2254{
2255 QBrush c = chf.foreground();
2256 if (c.style() == Qt::NoBrush)
2257 p->setPen(defaultPen);
2258 else
2259 p->setPen(QPen(c, 0));
2260}
2261
2262#if !defined(QT_NO_RAWFONT)
2263static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine,
2264 const QString &text,
2265 const QGlyphLayout &glyphLayout,
2266 const QPointF &pos,
2267 const QGlyphRun::GlyphRunFlags &flags,
2268 QTextLayout::GlyphRunRetrievalFlags retrievalFlags,
2269 QFixed selectionX,
2270 QFixed selectionWidth,
2271 int glyphsStart,
2272 int glyphsEnd,
2273 unsigned short *logClusters,
2274 int textPosition,
2275 int textLength)
2276{
2277 Q_ASSERT(logClusters != nullptr);
2278
2279 QGlyphRun glyphRun;
2280
2281 QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun);
2282
2283 int rangeStart = textPosition;
2284 int logClusterIndex = 0;
2285 while (logClusters[logClusterIndex] != glyphsStart && rangeStart < textPosition + textLength) {
2286 ++logClusterIndex;
2287 ++rangeStart;
2288 }
2289
2290 int rangeEnd = rangeStart;
2291 while (logClusters[logClusterIndex] != glyphsEnd && rangeEnd < textPosition + textLength) {
2292 ++logClusterIndex;
2293 ++rangeEnd;
2294 }
2295
2296 d->textRangeStart = rangeStart;
2297 d->textRangeEnd = rangeEnd;
2298
2299 // Make a font for this particular engine
2300 QRawFont font;
2301 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2302 fontD->setFontEngine(fontEngine);
2303
2304 QVarLengthArray<glyph_t> glyphsArray;
2305 QVarLengthArray<QFixedPoint> positionsArray;
2306
2307 QTextItem::RenderFlags renderFlags;
2308 if (flags.testFlag(QGlyphRun::Overline))
2309 renderFlags |= QTextItem::Overline;
2310 if (flags.testFlag(QGlyphRun::Underline))
2311 renderFlags |= QTextItem::Underline;
2312 if (flags.testFlag(QGlyphRun::StrikeOut))
2313 renderFlags |= QTextItem::StrikeOut;
2314 if (flags.testFlag(QGlyphRun::RightToLeft))
2315 renderFlags |= QTextItem::RightToLeft;
2316
2317 fontEngine->getGlyphPositions(glyphLayout, QTransform(), renderFlags, glyphsArray,
2318 positionsArray);
2319 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2320
2321 qreal fontHeight = font.ascent() + font.descent();
2322 qreal minY = 0;
2323 qreal maxY = 0;
2324 QList<quint32> glyphs;
2325 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2326 glyphs.reserve(glyphsArray.size());
2327 QList<QPointF> positions;
2328 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2329 positions.reserve(glyphsArray.size());
2330 QList<qsizetype> stringIndexes;
2331 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2332 stringIndexes.reserve(glyphsArray.size());
2333
2334 int nextClusterIndex = 0;
2335 int currentClusterIndex = 0;
2336 for (int i = 0; i < glyphsArray.size(); ++i) {
2337 const int glyphArrayIndex = i + glyphsStart;
2338 // Search for the next cluster in the string (or the end of string if there are no
2339 // more clusters)
2340 if (retrievalFlags & QTextLayout::RetrieveStringIndexes) {
2341 if (nextClusterIndex < textLength && logClusters[nextClusterIndex] == glyphArrayIndex) {
2342 currentClusterIndex = nextClusterIndex; // Store current cluster
2343 while (logClusters[nextClusterIndex] == glyphArrayIndex && nextClusterIndex < textLength)
2344 ++nextClusterIndex;
2345 }
2346
2347 // We are now either at end of string (no more clusters) or we are not yet at the
2348 // next cluster in glyph array. We fill in current cluster so that there is always one
2349 // entry in stringIndexes for each glyph.
2350 Q_ASSERT(nextClusterIndex == textLength || logClusters[nextClusterIndex] != glyphArrayIndex);
2351 stringIndexes.append(textPosition + currentClusterIndex);
2352 }
2353
2354 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes) {
2355 glyph_t glyphIndex = glyphsArray.at(i) & 0xffffff;
2356 glyphs.append(glyphIndex);
2357 }
2358
2359 QPointF position = positionsArray.at(i).toPointF() + pos;
2360 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2361 positions.append(position);
2362
2363 if (i == 0) {
2364 maxY = minY = position.y();
2365 } else {
2366 minY = qMin(minY, position.y());
2367 maxY = qMax(maxY, position.y());
2368 }
2369 }
2370
2371 qreal height = maxY + fontHeight - minY;
2372
2373 if (retrievalFlags & QTextLayout::RetrieveGlyphIndexes)
2374 glyphRun.setGlyphIndexes(glyphs);
2375 if (retrievalFlags & QTextLayout::RetrieveGlyphPositions)
2376 glyphRun.setPositions(positions);
2377 if (retrievalFlags & QTextLayout::RetrieveStringIndexes)
2378 glyphRun.setStringIndexes(stringIndexes);
2379 if (retrievalFlags & QTextLayout::RetrieveString)
2380 glyphRun.setSourceString(text);
2381 glyphRun.setFlags(flags);
2382 glyphRun.setRawFont(font);
2383
2384 glyphRun.setBoundingRect(QRectF(selectionX.toReal(), minY - font.ascent(),
2385 selectionWidth.toReal(), height));
2386
2387 return glyphRun;
2388}
2389
2390# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
2391/*!
2392 \overload
2393 Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2394 in the range defined by \a from and \a length. The \a from index is relative to the beginning
2395 of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2396 as given by functions textStart() and textLength().
2397
2398 If \a from is negative, it will default to textStart(), and if \a length is negative it will
2399 default to the return value of textLength().
2400
2401 \note This is equivalent to calling
2402 glyphRuns(from,
2403 length,
2404 QTextLayout::GlyphRunRetrievalFlag::GlyphIndexes |
2405 QTextLayout::GlyphRunRetrievalFlag::GlyphPositions).
2406
2407 \since 5.0
2408
2409 \sa QTextLayout::glyphRuns()
2410*/
2411QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2412{
2413 return glyphRuns(from, length, QTextLayout::GlyphRunRetrievalFlag::DefaultRetrievalFlags);
2414}
2415# endif
2416
2417/*!
2418 Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2419 in the range defined by \a from and \a length. The \a from index is relative to the beginning
2420 of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2421 as given by functions textStart() and textLength().
2422
2423 The \a retrievalFlags specifies which properties of the QGlyphRun will be retrieved from the
2424 layout. To minimize allocations and memory consumption, this should be set to include only the
2425 properties that you need to access later.
2426
2427 If \a from is negative, it will default to textStart(), and if \a length is negative it will
2428 default to the return value of textLength().
2429
2430 \since 6.5
2431
2432 \sa QTextLayout::glyphRuns()
2433*/
2434QList<QGlyphRun> QTextLine::glyphRuns(int from,
2435 int length,
2436 QTextLayout::GlyphRunRetrievalFlags retrievalFlags) const
2437{
2438 const QScriptLine &line = eng->lines.at(index);
2439
2440 if (line.length == 0)
2441 return QList<QGlyphRun>();
2442
2443 if (from < 0)
2444 from = textStart();
2445
2446 if (length < 0)
2447 length = textLength();
2448
2449 if (length == 0)
2450 return QList<QGlyphRun>();
2451
2452 QTextLayout::FormatRange selection;
2453 selection.start = from;
2454 selection.length = length;
2455
2456 QTextLineItemIterator iterator(eng, index, QPointF(), &selection);
2457 qreal y = line.y.toReal() + line.base().toReal();
2458 QList<QGlyphRun> glyphRuns;
2459 while (!iterator.atEnd()) {
2460 QScriptItem &si = iterator.next();
2461 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2462 continue;
2463
2464 if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart))
2465 continue;
2466
2467 QPointF pos(iterator.x.toReal(), y);
2468
2469 QFont font;
2470 QGlyphRun::GlyphRunFlags flags;
2471 if (!eng->useRawFont) {
2472 font = eng->font(si);
2473 if (font.overline())
2474 flags |= QGlyphRun::Overline;
2475 if (font.underline())
2476 flags |= QGlyphRun::Underline;
2477 if (font.strikeOut())
2478 flags |= QGlyphRun::StrikeOut;
2479 }
2480
2481 bool rtl = false;
2482 if (si.analysis.bidiLevel % 2) {
2483 flags |= QGlyphRun::RightToLeft;
2484 rtl = true;
2485 }
2486
2487 int relativeFrom = qMax(iterator.itemStart, from) - si.position;
2488 int relativeTo = qMin(iterator.itemEnd, from + length) - 1 - si.position;
2489
2490 unsigned short *logClusters = eng->logClusters(&si);
2491 int glyphsStart = logClusters[relativeFrom];
2492 int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo];
2493 // the glyph index right next to the requested range
2494 int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs;
2495 if (nextGlyphIndex - 1 > glyphsEnd)
2496 glyphsEnd = nextGlyphIndex - 1;
2497 bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart;
2498 bool endsInsideLigature = nextGlyphIndex == glyphsEnd;
2499
2500 int itemGlyphsStart = logClusters[iterator.itemStart - si.position];
2501 int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position];
2502
2503 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
2504
2505 // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2506 // when we're breaking a RTL script item, since the expected position passed into
2507 // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2508 if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2509 for (int i = itemGlyphsStart; i < glyphsStart; ++i) {
2510 if (!glyphLayout.attributes[i].dontPrint) {
2511 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2512 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2513 }
2514 }
2515 } else if (relativeTo != (iterator.itemEnd - si.position - 1) && rtl) {
2516 for (int i = itemGlyphsEnd; i > glyphsEnd; --i) {
2517 if (!glyphLayout.attributes[i].dontPrint) {
2518 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2519 pos.rx() += (glyphLayout.advances[i] + justification).toReal();
2520 }
2521 }
2522 }
2523
2524 glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
2525
2526 QFixed x;
2527 QFixed width;
2528 iterator.getSelectionBounds(&x, &width);
2529
2530 if (glyphLayout.numGlyphs > 0) {
2531 QFontEngine *mainFontEngine;
2532#ifndef QT_NO_RAWFONT
2533 if (eng->useRawFont && eng->rawFont.isValid())
2534 mainFontEngine= eng->fontEngine(si);
2535 else
2536#endif
2537 mainFontEngine = font.d->engineForScript(si.analysis.script);
2538
2539 if (mainFontEngine->type() == QFontEngine::Multi) {
2540 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2541 int start = rtl ? glyphLayout.numGlyphs : 0;
2542 int end = start - 1;
2543 int which = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2544 for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs - 1);
2545 rtl ? --start : ++end) {
2546 const int e = glyphLayout.glyphs[rtl ? start - 1 : end + 1] >> 24;
2547 if (e == which)
2548 continue;
2549
2550 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2551 multiFontEngine->ensureEngineAt(which);
2552
2553 QGlyphRun::GlyphRunFlags subFlags = flags;
2554 if (start == 0 && startsInsideLigature)
2555 subFlags |= QGlyphRun::SplitLigature;
2556
2557 {
2558 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2559 eng->text,
2560 subLayout,
2561 pos,
2562 subFlags,
2563 retrievalFlags,
2564 x,
2565 width,
2566 glyphsStart + start,
2567 glyphsStart + end,
2568 logClusters + relativeFrom,
2569 relativeFrom + si.position,
2570 relativeTo - relativeFrom + 1);
2571 if (!glyphRun.isEmpty())
2572 glyphRuns.append(glyphRun);
2573 }
2574 for (int i = 0; i < subLayout.numGlyphs; ++i) {
2575 if (!subLayout.attributes[i].dontPrint) {
2576 QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6);
2577 pos.rx() += (subLayout.advances[i] + justification).toReal();
2578 }
2579 }
2580
2581 if (rtl)
2582 end = start - 1;
2583 else
2584 start = end + 1;
2585 which = e;
2586 }
2587
2588 QGlyphLayout subLayout = glyphLayout.mid(start, end - start + 1);
2589 multiFontEngine->ensureEngineAt(which);
2590
2591 QGlyphRun::GlyphRunFlags subFlags = flags;
2592 if ((start == 0 && startsInsideLigature) || endsInsideLigature)
2593 subFlags |= QGlyphRun::SplitLigature;
2594
2595 QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which),
2596 eng->text,
2597 subLayout,
2598 pos,
2599 subFlags,
2600 retrievalFlags,
2601 x,
2602 width,
2603 glyphsStart + start,
2604 glyphsStart + end,
2605 logClusters + relativeFrom,
2606 relativeFrom + si.position,
2607 relativeTo - relativeFrom + 1);
2608 if (!glyphRun.isEmpty())
2609 glyphRuns.append(glyphRun);
2610 } else {
2611 if (startsInsideLigature || endsInsideLigature)
2612 flags |= QGlyphRun::SplitLigature;
2613 QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine,
2614 eng->text,
2615 glyphLayout,
2616 pos,
2617 flags,
2618 retrievalFlags,
2619 x,
2620 width,
2621 glyphsStart,
2622 glyphsEnd,
2623 logClusters + relativeFrom,
2624 relativeFrom + si.position,
2625 relativeTo - relativeFrom + 1);
2626 if (!glyphRun.isEmpty())
2627 glyphRuns.append(glyphRun);
2628 }
2629 }
2630 }
2631
2632 return glyphRuns;
2633}
2634#endif // QT_NO_RAWFONT
2635
2636/*!
2637 \fn void QTextLine::draw(QPainter *painter, const QPointF &position) const
2638
2639 Draws a line on the given \a painter at the specified \a position.
2640*/
2641void QTextLine::draw(QPainter *painter, const QPointF &position) const
2642{
2643 draw_internal(painter, position, nullptr);
2644}
2645
2646void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
2647 const QTextLayout::FormatRange *selection) const
2648{
2649#ifndef QT_NO_RAWFONT
2650 // Not intended to work with rawfont
2651 Q_ASSERT(!eng->useRawFont);
2652#endif
2653 const QScriptLine &line = eng->lines[index];
2654
2655 bool noText = (selection && selection->format.property(SuppressText).toBool());
2656
2657 if (!line.length) {
2658 if (selection
2659 && selection->start <= line.from
2660 && selection->start + selection->length > line.from) {
2661
2662 const qreal lineHeight = line.height().toReal();
2663 QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
2664 lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
2665 drawBackground(p, selection->format, r);
2666 }
2667 return;
2668 }
2669
2670 Q_CONSTINIT static QRectF maxFixedRect(-QFIXED_MAX / 2, -QFIXED_MAX / 2, QFIXED_MAX, QFIXED_MAX);
2671 const bool xlateToFixedRange = !maxFixedRect.contains(origPos);
2672 QPointF pos;
2673 if (Q_LIKELY(!xlateToFixedRange))
2674 pos = origPos;
2675 else
2676 p->translate(origPos);
2677
2678
2679 QFixed lineBase = line.base();
2680 eng->clearDecorations();
2681 eng->enableDelayDecorations();
2682
2683 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2684
2685 const QTextFormatCollection *formatCollection = eng->formatCollection();
2686
2687 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2688
2689 auto prepareFormat = [suppressColors, selection, this](QTextCharFormat &format,
2690 QScriptItem *si) {
2691 format.merge(eng->format(si));
2692
2693 if (suppressColors) {
2694 format.clearForeground();
2695 format.clearBackground();
2696 format.clearProperty(QTextFormat::TextUnderlineColor);
2697 }
2698 if (selection)
2699 format.merge(selection->format);
2700 };
2701
2702 {
2703 QTextLineItemIterator iterator(eng, index, pos, selection);
2704 while (!iterator.atEnd()) {
2705 QScriptItem &si = iterator.next();
2706
2707 if (eng->hasFormats() || selection || formatCollection) {
2708 QTextCharFormat format;
2709 if (formatCollection != nullptr)
2710 format = formatCollection->defaultTextFormat();
2711 prepareFormat(format, &si);
2712 drawBackground(p, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2713 iterator.itemWidth.toReal(), line.height().toReal()));
2714 }
2715 }
2716 }
2717
2718 QPen pen = p->pen();
2719 {
2720 QTextLineItemIterator iterator(eng, index, pos, selection);
2721 while (!iterator.atEnd()) {
2722 QScriptItem &si = iterator.next();
2723
2724 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2725 continue;
2726
2727 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2728 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2729 continue;
2730
2731 QFixed itemBaseLine = y;
2732 QFont f = eng->font(si);
2733 QTextCharFormat format;
2734 if (formatCollection != nullptr)
2735 format = formatCollection->defaultTextFormat();
2736
2737 if (eng->hasFormats() || selection || formatCollection) {
2738 prepareFormat(format, &si);
2739 setPen(p, pen, format);
2740
2741 const qreal baseLineOffset = format.baselineOffset() / 100.0;
2742 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2743 if (valign == QTextCharFormat::AlignSuperScript
2744 || valign == QTextCharFormat::AlignSubScript
2745 || !qFuzzyIsNull(baseLineOffset))
2746 {
2747 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2748 QFixed height = fe->ascent() + fe->descent();
2749 itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
2750
2751 if (valign == QTextCharFormat::AlignSubScript)
2752 itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
2753 else if (valign == QTextCharFormat::AlignSuperScript)
2754 itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
2755 }
2756 }
2757
2758 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2759
2760 if (eng->hasFormats()) {
2761 p->save();
2762 if (si.analysis.flags == QScriptAnalysis::Object && QTextDocumentPrivate::get(eng->block)) {
2763 QFixed itemY = y - si.ascent;
2764 switch (format.verticalAlignment()) {
2765 case QTextCharFormat::AlignTop:
2766 itemY = y - lineBase;
2767 break;
2768 case QTextCharFormat::AlignMiddle:
2769 itemY = y - lineBase + (line.height() - si.height()) / 2;
2770 break;
2771 case QTextCharFormat::AlignBottom:
2772 itemY = y - lineBase + line.height() - si.height();
2773 break;
2774 default:
2775 break;
2776 }
2777
2778 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2779
2780 eng->docLayout()->drawInlineObject(p, itemRect,
2781 QTextInlineObject(iterator.item, eng),
2782 si.position + eng->block.position(),
2783 format);
2784 if (selection) {
2785 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2786 if (bg.style() != Qt::NoBrush) {
2787 QColor c = bg.color();
2788 c.setAlpha(128);
2789 p->fillRect(itemRect, c);
2790 }
2791 }
2792 } else { // si.isTab
2793 QFont f = eng->font(si);
2794 QTextItemInt gf(si, &f, format);
2795 gf.chars = nullptr;
2796 gf.num_chars = 0;
2797 gf.width = iterator.itemWidth;
2798 QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
2799 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2800 const QChar visualTab = QChar(QChar::VisualTabCharacter);
2801 int w = QFontMetrics(f).horizontalAdvance(visualTab);
2802 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2803 if (x < 0)
2804 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2805 iterator.itemWidth.toReal(), line.height().toReal()),
2806 Qt::IntersectClip);
2807 else
2808 x /= 2; // Centered
2809 p->setFont(f);
2810 p->drawText(QPointF(iterator.x.toReal() + x,
2811 y.toReal()), visualTab);
2812 }
2813
2814 }
2815 p->restore();
2816 }
2817
2818 continue;
2819 }
2820
2821 unsigned short *logClusters = eng->logClusters(&si);
2822 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2823
2824 QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
2825 &f, eng->layoutData->string.unicode() + iterator.itemStart,
2826 iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2827 gf.logClusters = logClusters + iterator.itemStart - si.position;
2828 gf.width = iterator.itemWidth;
2829 gf.justified = line.justified;
2830 gf.initWithScriptItem(si);
2831
2832 Q_ASSERT(gf.fontEngine);
2833
2834 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2835 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2836 QPainterPath path;
2837 path.setFillRule(Qt::WindingFill);
2838
2839 if (gf.glyphs.numGlyphs)
2840 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2841 if (gf.flags) {
2842 const QFontEngine *fe = gf.fontEngine;
2843 const qreal lw = fe->lineThickness().toReal();
2844 if (gf.flags & QTextItem::Underline) {
2845 qreal offs = fe->underlinePosition().toReal();
2846 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2847 }
2848 if (gf.flags & QTextItem::Overline) {
2849 qreal offs = fe->ascent().toReal() + 1;
2850 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2851 }
2852 if (gf.flags & QTextItem::StrikeOut) {
2853 qreal offs = fe->ascent().toReal() / 3;
2854 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2855 }
2856 }
2857
2858 p->save();
2859 p->setRenderHint(QPainter::Antialiasing);
2860 //Currently QPen with a Qt::NoPen style still returns a default
2861 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2862 if (p->pen().style() == Qt::NoPen)
2863 p->setBrush(Qt::NoBrush);
2864 else
2865 p->setBrush(p->pen().brush());
2866
2867 p->setPen(format.textOutline());
2868 p->drawPath(path);
2869 p->restore();
2870 } else {
2871 if (noText)
2872 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2873 QPainterPrivate::get(p)->drawTextItem(pos, gf, eng);
2874 }
2875
2876 if ((si.analysis.flags == QScriptAnalysis::Space
2877 || si.analysis.flags == QScriptAnalysis::Nbsp)
2878 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2879 QBrush c = format.foreground();
2880 if (c.style() != Qt::NoBrush)
2881 p->setPen(c.color());
2882 const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
2883 QFont oldFont = p->font();
2884 p->setFont(eng->font(si));
2885 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2886 p->setPen(pen);
2887 p->setFont(oldFont);
2888 }
2889 }
2890 }
2891 eng->drawDecorations(p);
2892
2893 if (xlateToFixedRange)
2894 p->translate(-origPos);
2895
2896 if (eng->hasFormats())
2897 p->setPen(pen);
2898}
2899
2900/*!
2901 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2902
2903 \overload
2904*/
2905
2906/*!
2907 Converts the cursor position \a cursorPos to the corresponding x position
2908 inside the line, taking account of the \a edge.
2909
2910 If \a cursorPos is not a valid cursor position, the nearest valid
2911 cursor position will be used instead, and \a cursorPos will be modified to
2912 point to this valid cursor position.
2913
2914 \sa xToCursor()
2915*/
2916qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2917{
2918 const QScriptLine &line = eng->lines[index];
2919 bool lastLine = index >= eng->lines.size() - 1;
2920
2921 QFixed x = line.x + eng->alignLine(line) - eng->leadingSpaceWidth(line);
2922
2923 if (!eng->layoutData)
2924 eng->itemize();
2925 if (!eng->layoutData->items.size()) {
2926 *cursorPos = line.from;
2927 return x.toReal();
2928 }
2929
2930 int lineEnd = line.from + line.length + line.trailingSpaces;
2931 int pos = qBound(line.from, *cursorPos, lineEnd);
2932 const QCharAttributes *attributes = eng->attributes();
2933 if (!attributes) {
2934 *cursorPos = line.from;
2935 return x.toReal();
2936 }
2937 while (pos < lineEnd && !attributes[pos].graphemeBoundary)
2938 pos++;
2939 // end of line ensure we have the last item on the line
2940 int itm = pos == lineEnd ? eng->findItem(pos-1) : eng->findItem(pos);
2941 if (itm < 0) {
2942 *cursorPos = line.from;
2943 return x.toReal();
2944 }
2945 eng->shapeLine(line);
2946
2947 const QScriptItem *scriptItem = &eng->layoutData->items[itm];
2948 if (!scriptItem->num_glyphs)
2949 eng->shape(itm);
2950
2951 if ((scriptItem->analysis.bidiLevel % 2 != eng->isRightToLeft()) && !eng->visualCursorMovement()) {
2952 // If the item we found has a different writing direction than the engine,
2953 // check if the cursor is between two items with different writing direction
2954 int neighborItem = itm;
2955 if (neighborItem > 0 && scriptItem->position == pos)
2956 --neighborItem;
2957 else if (neighborItem < eng->layoutData->items.size() - 1 && scriptItem->position + scriptItem->num_glyphs == pos)
2958 ++neighborItem;
2959 const bool onBoundary = neighborItem != itm && scriptItem->analysis.bidiLevel != eng->layoutData->items[neighborItem].analysis.bidiLevel;
2960 // If we are, prioritise the neighbor item that has the same direction as the engine
2961 if (onBoundary) {
2962 if (eng->isRightToLeft() != scriptItem->analysis.bidiLevel % 2) {
2963 itm = neighborItem;
2964 scriptItem = &eng->layoutData->items[itm];
2965 if (!scriptItem->num_glyphs)
2966 eng->shape(itm);
2967 }
2968 }
2969 }
2970
2971 const int l = eng->length(itm);
2972 pos = qBound(0, pos - scriptItem->position, l);
2973
2974 QGlyphLayout glyphs = eng->shapedGlyphs(scriptItem);
2975 unsigned short *logClusters = eng->logClusters(scriptItem);
2976 Q_ASSERT(logClusters);
2977
2978 int glyph_pos = pos == l ? scriptItem->num_glyphs : logClusters[pos];
2979 if (edge == Trailing && glyph_pos < scriptItem->num_glyphs) {
2980 // trailing edge is leading edge of next cluster
2981 glyph_pos++;
2982 while (glyph_pos < scriptItem->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2983 glyph_pos++;
2984 }
2985
2986 bool reverse = scriptItem->analysis.bidiLevel % 2;
2987
2988
2989 // add the items left of the cursor
2990
2991 int firstItem = eng->findItem(line.from);
2992 int lastItem = eng->findItem(lineEnd - 1, itm);
2993 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2994
2995 QVarLengthArray<int> visualOrder(nItems);
2996 QVarLengthArray<uchar> levels(nItems);
2997 for (int i = 0; i < nItems; ++i)
2998 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2999 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3000
3001 for (int i = 0; i < nItems; ++i) {
3002 int item = visualOrder[i]+firstItem;
3003 if (item == itm)
3004 break;
3005 QScriptItem &si = eng->layoutData->items[item];
3006 if (!si.num_glyphs)
3007 eng->shape(item);
3008
3009 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3010 x += si.width;
3011 continue;
3012 }
3013
3014 const int itemLength = eng->length(item);
3015 int start = qMax(line.from, si.position);
3016 int end = qMin(lineEnd, si.position + itemLength);
3017
3018 logClusters = eng->logClusters(&si);
3019
3020 int gs = logClusters[start-si.position];
3021 int ge = (end == si.position + itemLength) ? si.num_glyphs-1 : logClusters[end-si.position-1];
3022
3023 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3024
3025 while (gs <= ge) {
3026 x += glyphs.effectiveAdvance(gs);
3027 ++gs;
3028 }
3029 }
3030
3031 logClusters = eng->logClusters(scriptItem);
3032 glyphs = eng->shapedGlyphs(scriptItem);
3033 if (scriptItem->analysis.flags >= QScriptAnalysis::TabOrObject) {
3034 if (pos == (reverse ? 0 : l))
3035 x += scriptItem->width;
3036 } else {
3037 bool rtl = eng->isRightToLeft();
3038 bool visual = eng->visualCursorMovement();
3039 int end = qMin(lineEnd, scriptItem->position + l) - scriptItem->position;
3040 if (reverse) {
3041 int glyph_end = end == l ? scriptItem->num_glyphs : logClusters[end];
3042 int glyph_start = glyph_pos;
3043 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
3044 glyph_start++;
3045 for (int i = glyph_end - 1; i >= glyph_start; i--)
3046 x += glyphs.effectiveAdvance(i);
3047 x -= eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3048 } else {
3049 int start = qMax(line.from - scriptItem->position, 0);
3050 int glyph_start = logClusters[start];
3051 int glyph_end = glyph_pos;
3052 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
3053 glyph_end--;
3054 for (int i = glyph_start; i <= glyph_end; i++)
3055 x += glyphs.effectiveAdvance(i);
3056 x += eng->offsetInLigature(scriptItem, pos, end, glyph_pos);
3057 }
3058 }
3059
3060 if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width)
3061 x = line.x + line.width;
3062 if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0)
3063 x = 0;
3064
3065 *cursorPos = pos + scriptItem->position;
3066 return x.toReal();
3067}
3068
3069/*!
3070 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
3071
3072 Converts the x-coordinate \a x, to the nearest matching cursor
3073 position, depending on the cursor position type, \a cpos.
3074 Note that result cursor position includes possible preedit area text.
3075
3076 \sa cursorToX()
3077*/
3078int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
3079{
3080 QFixed x = QFixed::fromReal(_x);
3081 const QScriptLine &line = eng->lines[index];
3082 bool lastLine = index >= eng->lines.size() - 1;
3083 int lineNum = index;
3084
3085 if (!eng->layoutData)
3086 eng->itemize();
3087
3088 int line_length = textLength();
3089
3090 if (!line_length)
3091 return line.from;
3092
3093 int firstItem = eng->findItem(line.from);
3094 int lastItem = eng->findItem(line.from + line_length - 1, firstItem);
3095 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
3096
3097 if (!nItems)
3098 return 0;
3099
3100 x -= line.x;
3101 x -= eng->alignLine(line);
3102// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
3103
3104 QVarLengthArray<int> visualOrder(nItems);
3105 QVarLengthArray<unsigned char> levels(nItems);
3106 for (int i = 0; i < nItems; ++i)
3107 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
3108 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
3109
3110 bool visual = eng->visualCursorMovement();
3111 if (x <= 0) {
3112 // left of first item
3113 if (eng->isRightToLeft())
3114 return line.from + line_length;
3115 return line.from;
3116 } else if (x < line.textWidth || (line.justified && x < line.width)) {
3117 // has to be in one of the runs
3118 QFixed pos;
3119 bool rtl = eng->isRightToLeft();
3120
3121 eng->shapeLine(line);
3122 const auto insertionPoints = (visual && rtl) ? eng->insertionPointsForLine(lineNum) : std::vector<int>();
3123 int nchars = 0;
3124 for (int i = 0; i < nItems; ++i) {
3125 int item = visualOrder[i]+firstItem;
3126 QScriptItem &si = eng->layoutData->items[item];
3127 int item_length = eng->length(item);
3128// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
3129
3130 int start = qMax(line.from - si.position, 0);
3131 int end = qMin(line.from + line_length - si.position, item_length);
3132
3133 unsigned short *logClusters = eng->logClusters(&si);
3134
3135 int gs = logClusters[start];
3136 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
3137 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
3138
3139 QFixed item_width = 0;
3140 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3141 item_width = si.width;
3142 } else {
3143 int g = gs;
3144 while (g <= ge) {
3145 item_width += glyphs.effectiveAdvance(g);
3146 ++g;
3147 }
3148 }
3149// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
3150
3151 if (pos + item_width < x) {
3152 pos += item_width;
3153 nchars += end;
3154 continue;
3155 }
3156// qDebug(" inside run");
3157 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
3158 if (cpos == QTextLine::CursorOnCharacter)
3159 return si.position;
3160 bool left_half = (x - pos) < item_width/2;
3161
3162 if (bool(si.analysis.bidiLevel % 2) != left_half)
3163 return si.position;
3164 return si.position + 1;
3165 }
3166
3167 int glyph_pos = -1;
3168 QFixed edge;
3169 // has to be inside run
3170 if (cpos == QTextLine::CursorOnCharacter) {
3171 if (si.analysis.bidiLevel % 2) {
3172 pos += item_width;
3173 glyph_pos = gs;
3174 while (gs <= ge) {
3175 if (glyphs.attributes[gs].clusterStart) {
3176 if (pos < x)
3177 break;
3178 glyph_pos = gs;
3179 edge = pos;
3180 }
3181 pos -= glyphs.effectiveAdvance(gs);
3182 ++gs;
3183 }
3184 } else {
3185 glyph_pos = gs;
3186 while (gs <= ge) {
3187 if (glyphs.attributes[gs].clusterStart) {
3188 if (pos > x)
3189 break;
3190 glyph_pos = gs;
3191 edge = pos;
3192 }
3193 pos += glyphs.effectiveAdvance(gs);
3194 ++gs;
3195 }
3196 }
3197 } else {
3198 QFixed dist = INT_MAX/256;
3199 if (si.analysis.bidiLevel % 2) {
3200 if (!visual || rtl || (lastLine && i == nItems - 1)) {
3201 pos += item_width;
3202 while (gs <= ge) {
3203 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3204 glyph_pos = gs;
3205 edge = pos;
3206 dist = qAbs(x-pos);
3207 }
3208 pos -= glyphs.effectiveAdvance(gs);
3209 ++gs;
3210 }
3211 } else {
3212 while (ge >= gs) {
3213 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
3214 glyph_pos = ge;
3215 edge = pos;
3216 dist = qAbs(x-pos);
3217 }
3218 pos += glyphs.effectiveAdvance(ge);
3219 --ge;
3220 }
3221 }
3222 } else {
3223 if (!visual || !rtl || (lastLine && i == 0)) {
3224 while (gs <= ge) {
3225 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3226 glyph_pos = gs;
3227 edge = pos;
3228 dist = qAbs(x-pos);
3229 }
3230 pos += glyphs.effectiveAdvance(gs);
3231 ++gs;
3232 }
3233 } else {
3234 QFixed oldPos = pos;
3235 while (gs <= ge) {
3236 pos += glyphs.effectiveAdvance(gs);
3237 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
3238 glyph_pos = gs;
3239 edge = pos;
3240 dist = qAbs(x-pos);
3241 }
3242 ++gs;
3243 }
3244 pos = oldPos;
3245 }
3246 }
3247 if (qAbs(x-pos) < dist) {
3248 if (visual) {
3249 if (!rtl && i < nItems - 1) {
3250 nchars += end;
3251 continue;
3252 }
3253 if (rtl && nchars > 0)
3254 return insertionPoints[size_t(lastLine ? nchars : nchars - 1)];
3255 }
3256 return eng->positionInLigature(&si, end, x, pos, -1,
3257 cpos == QTextLine::CursorOnCharacter);
3258 }
3259 }
3260 Q_ASSERT(glyph_pos != -1);
3261 return eng->positionInLigature(&si, end, x, edge, glyph_pos,
3262 cpos == QTextLine::CursorOnCharacter);
3263 }
3264 }
3265 // right of last item
3266 int pos = line.from;
3267 if (!eng->isRightToLeft())
3268 pos += line_length;
3269
3270 // except for the last line we assume that the
3271 // character between lines is a space and we want
3272 // to position the cursor to the left of that
3273 // character.
3274 if (index < eng->lines.size() - 1)
3275 pos = qMin(eng->previousLogicalPosition(pos), pos);
3276
3277 return pos;
3278}
3279
3280QT_END_NAMESPACE
friend class QPainter
friend class QFontEngine
Definition qpainter.h:424
friend class QTextEngine
Definition qpainter.h:437
Combined button and popup list for selecting options.
static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection, QPainterPath *region, const QRectF &boundingRect)
static void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount, const QScriptItem &current, const unsigned short *logClusters, const QGlyphLayout &glyphs, QFixed *clusterWidth=nullptr)
#define SuppressText
static QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, const QString &text, const QGlyphLayout &glyphLayout, const QPointF &pos, const QGlyphRun::GlyphRunFlags &flags, QTextLayout::GlyphRunRetrievalFlags retrievalFlags, QFixed selectionX, QFixed selectionWidth, int glyphsStart, int glyphsEnd, unsigned short *logClusters, int textPosition, int textLength)
static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
#define SuppressBackground
#define LB_DEBUG
static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
#define ObjectSelectionBrush
QGlyphAttributes * attributes
unsigned short num_glyphs