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