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

source code of qtbase/src/gui/text/qtextlayout.cpp