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