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