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, ®ion, 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 ¤t, 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 ¤t = 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(¤t);
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 ¤t = 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(¤t);
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 ¤t = 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