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