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