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