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