1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtextlayout.h"
43 #include "qtextengine_p.h"
46 #include <qapplication.h>
48 #include <qvarlengtharray.h>
49 #include <qtextformat.h>
50 #include <qabstracttextdocumentlayout.h>
51 #include "qtextdocument_p.h"
52 #include "qtextformat_p.h"
53 #include "qstyleoption.h"
54 #include "qpainterpath.h"
55 #include "qglyphrun.h"
56 #include "qglyphrun_p.h"
58 #include "qrawfont_p.h"
63 #include "qfontengine_p.h"
65 #if !defined(QT_NO_FREETYPE)
66 # include "qfontengine_ft_p.h"
71 #define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
72 #define SuppressText 0x5012
73 #define SuppressBackground 0x513
76 \class QTextLayout::FormatRange
79 \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
80 for a specified area in the text layout's content.
82 \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw()
86 \variable QTextLayout::FormatRange::start
87 Specifies the beginning of the format range within the text layout's text.
91 \variable QTextLayout::FormatRange::length
92 Specifies the numer of characters the format range spans.
96 \variable QTextLayout::FormatRange::format
97 Specifies the format to apply.
101 \class QTextInlineObject
104 \brief The QTextInlineObject class represents an inline object in
107 \ingroup richtext-processing
109 This class is only used if the text layout is used to lay out
110 parts of a QTextDocument.
112 The inline object has various attributes that can be set, for
113 example using, setWidth(), setAscent(), and setDescent(). The
114 rectangle it occupies is given by rect(), and its direction by
115 isRightToLeft(). Its position in the text layout is given by at(),
116 and its format is given by format().
120 \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
122 Creates a new inline object for the item at position \a i in the
127 \fn QTextInlineObject::QTextInlineObject()
133 \fn bool QTextInlineObject::isValid() const
135 Returns true if this inline object is valid; otherwise returns
140 Returns the inline object's rectangle.
142 \sa ascent(), descent(), width()
144 QRectF QTextInlineObject::rect() const
146 QScriptItem& si = eng->layoutData->items[itm];
147 return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
151 Returns the inline object's width.
153 \sa ascent(), descent(), rect()
155 qreal QTextInlineObject::width() const
157 return eng->layoutData->items[itm].width.toReal();
161 Returns the inline object's ascent.
163 \sa descent(), width(), rect()
165 qreal QTextInlineObject::ascent() const
167 return eng->layoutData->items[itm].ascent.toReal();
171 Returns the inline object's descent.
173 \sa ascent(), width(), rect()
175 qreal QTextInlineObject::descent() const
177 return eng->layoutData->items[itm].descent.toReal();
181 Returns the inline object's total height. This is equal to
182 ascent() + descent() + 1.
184 \sa ascent(), descent(), width(), rect()
186 qreal QTextInlineObject::height() const
188 return eng->layoutData->items[itm].height().toReal();
192 Sets the inline object's width to \a w.
194 \sa width(), ascent(), descent(), rect()
196 void QTextInlineObject::setWidth(qreal w)
198 eng->layoutData->items[itm].width = QFixed::fromReal(w);
202 Sets the inline object's ascent to \a a.
204 \sa ascent(), setDescent(), width(), rect()
206 void QTextInlineObject::setAscent(qreal a)
208 eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
212 Sets the inline object's decent to \a d.
214 \sa descent(), setAscent(), width(), rect()
216 void QTextInlineObject::setDescent(qreal d)
218 eng->layoutData->items[itm].descent = QFixed::fromReal(d);
222 The position of the inline object within the text layout.
224 int QTextInlineObject::textPosition() const
226 return eng->layoutData->items[itm].position;
230 Returns an integer describing the format of the inline object
231 within the text layout.
233 int QTextInlineObject::formatIndex() const
235 return eng->formatIndex(&eng->layoutData->items[itm]);
239 Returns format of the inline object within the text layout.
241 QTextFormat QTextInlineObject::format() const
243 if (!eng->block.docHandle())
244 return QTextFormat();
245 return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
249 Returns if the object should be laid out right-to-left or left-to-right.
251 Qt::LayoutDirection QTextInlineObject::textDirection() const
253 return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
260 \brief The QTextLayout class is used to lay out and render text.
262 \ingroup richtext-processing
264 It offers many features expected from a modern text layout
265 engine, including Unicode compliant rendering, line breaking and
266 handling of cursor positioning. It can also produce and render
267 device independent layout, something that is important for WYSIWYG
270 The class has a rather low level API and unless you intend to
271 implement your own text rendering for some specialized widget, you
272 probably won't need to use it directly.
274 QTextLayout can be used with both plain and rich text.
276 QTextLayout can be used to create a sequence of QTextLine
277 instances with given widths and can position them independently
278 on the screen. Once the layout is done, these lines can be drawn
281 The text to be laid out can be provided in the constructor or set with
284 The layout can be seen as a sequence of QTextLine objects; use createLine()
285 to create a QTextLine instance, and lineAt() or lineForTextPosition() to retrieve
288 Here is a code snippet that demonstrates the layout phase:
289 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 0
291 The text can then be rendered by calling the layout's draw() function:
292 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 1
294 For a given position in the text you can find a valid cursor position with
295 isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition().
297 The QTextLayout itself can be positioned with setPosition(); it has a
298 boundingRect(), and a minimumWidth() and a maximumWidth().
304 \enum QTextLayout::CursorMode
306 \value SkipCharacters
311 \fn QTextEngine *QTextLayout::engine() const
314 Returns the text engine used to render the text layout.
318 Constructs an empty text layout.
322 QTextLayout::QTextLayout()
323 { d = new QTextEngine(); }
326 Constructs a text layout to lay out the given \a text.
328 QTextLayout::QTextLayout(const QString& text)
330 d = new QTextEngine();
335 Constructs a text layout to lay out the given \a text with the specified
338 All the metric and layout calculations will be done in terms of
339 the paint device, \a paintdevice. If \a paintdevice is 0 the
340 calculations will be done in screen metrics.
342 QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
346 f = QFont(font, paintdevice);
347 d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f.d.data());
352 Constructs a text layout to lay out the given \a block.
354 QTextLayout::QTextLayout(const QTextBlock &block)
356 d = new QTextEngine();
361 Destructs the layout.
363 QTextLayout::~QTextLayout()
370 Sets the layout's font to the given \a font. The layout is
371 invalidated and must be laid out again.
375 void QTextLayout::setFont(const QFont &font)
378 d->resetFontEngineCache();
382 Returns the current font that is used for the layout, or a default
387 QFont QTextLayout::font() const
393 Sets the layout's text to the given \a string. The layout is
394 invalidated and must be laid out again.
396 Notice that when using this QTextLayout as part of a QTextDocument this
397 method will have no effect.
401 void QTextLayout::setText(const QString& string)
409 Returns the layout's text.
413 QString QTextLayout::text() const
419 Sets the text option structure that controls the layout process to the
424 void QTextLayout::setTextOption(const QTextOption &option)
430 Returns the current text option used to control the layout process.
434 QTextOption QTextLayout::textOption() const
440 Sets the \a position and \a text of the area in the layout that is
441 processed before editing occurs.
443 \sa preeditAreaPosition(), preeditAreaText()
445 void QTextLayout::setPreeditArea(int position, const QString &text)
447 if (text.isEmpty()) {
450 if (d->specialData->addFormats.isEmpty()) {
451 delete d->specialData;
454 d->specialData->preeditText = QString();
455 d->specialData->preeditPosition = -1;
459 d->specialData = new QTextEngine::SpecialData;
460 d->specialData->preeditPosition = position;
461 d->specialData->preeditText = text;
465 if (d->block.docHandle())
466 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
470 Returns the position of the area in the text layout that will be
471 processed before editing occurs.
473 \sa preeditAreaText()
475 int QTextLayout::preeditAreaPosition() const
477 return d->specialData ? d->specialData->preeditPosition : -1;
481 Returns the text that is inserted in the layout before editing occurs.
483 \sa preeditAreaPosition()
485 QString QTextLayout::preeditAreaText() const
487 return d->specialData ? d->specialData->preeditText : QString();
492 Sets the additional formats supported by the text layout to \a formatList.
494 \sa additionalFormats(), clearAdditionalFormats()
496 void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
498 if (formatList.isEmpty()) {
501 if (d->specialData->preeditText.isEmpty()) {
502 delete d->specialData;
505 d->specialData->addFormats = formatList;
506 d->specialData->addFormatIndices.clear();
509 if (!d->specialData) {
510 d->specialData = new QTextEngine::SpecialData;
511 d->specialData->preeditPosition = -1;
513 d->specialData->addFormats = formatList;
514 d->indexAdditionalFormats();
516 if (d->block.docHandle())
517 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
518 d->resetFontEngineCache();
522 Returns the list of additional formats supported by the text layout.
524 \sa setAdditionalFormats(), clearAdditionalFormats()
526 QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
528 QList<FormatRange> formats;
532 formats = d->specialData->addFormats;
534 if (d->specialData->addFormatIndices.isEmpty())
537 const QTextFormatCollection *collection = d->formats();
539 for (int i = 0; i < d->specialData->addFormatIndices.count(); ++i)
540 formats[i].format = collection->charFormat(d->specialData->addFormatIndices.at(i));
546 Clears the list of additional formats supported by the text layout.
548 \sa additionalFormats(), setAdditionalFormats()
550 void QTextLayout::clearAdditionalFormats()
552 setAdditionalFormats(QList<FormatRange>());
556 Enables caching of the complete layout information if \a enable is
557 true; otherwise disables layout caching. Usually
558 QTextLayout throws most of the layouting information away after a
559 call to endLayout() to reduce memory consumption. If you however
560 want to draw the laid out text directly afterwards enabling caching
561 might speed up drawing significantly.
565 void QTextLayout::setCacheEnabled(bool enable)
567 d->cacheGlyphs = enable;
571 Returns true if the complete layout information is cached; otherwise
574 \sa setCacheEnabled()
576 bool QTextLayout::cacheEnabled() const
578 return d->cacheGlyphs;
584 Set the cursor movement style. If the QTextLayout is backed by
585 a document, you can ignore this and use the option in QTextDocument,
586 this option is for widgets like QLineEdit or custom widgets without
587 a QTextDocument. Default value is Qt::LogicalMoveStyle.
589 \sa cursorMoveStyle()
591 void QTextLayout::setCursorMoveStyle(Qt::CursorMoveStyle style)
593 d->visualMovement = style == Qt::VisualMoveStyle ? true : false;
599 The cursor movement style of this QTextLayout. The default is
600 Qt::LogicalMoveStyle.
602 \sa setCursorMoveStyle()
604 Qt::CursorMoveStyle QTextLayout::cursorMoveStyle() const
606 return d->visualMovement ? Qt::VisualMoveStyle : Qt::LogicalMoveStyle;
610 Begins the layout process.
614 void QTextLayout::beginLayout()
617 if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) {
618 qWarning("QTextLayout::beginLayout: Called while already doing layout");
625 d->layoutData->layoutState = QTextEngine::InLayout;
629 Ends the layout process.
633 void QTextLayout::endLayout()
636 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
637 qWarning("QTextLayout::endLayout: Called without beginLayout()");
641 int l = d->lines.size();
642 if (l && d->lines.at(l-1).length < 0) {
643 QTextLine(l-1, d).setNumColumns(INT_MAX);
645 d->layoutData->layoutState = QTextEngine::LayoutEmpty;
653 Clears the line information in the layout. After having called
654 this function, lineCount() returns 0.
656 void QTextLayout::clearLayout()
662 Returns the next valid cursor position after \a oldPos that
663 respects the given cursor \a mode.
664 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
666 \sa isValidCursorPosition(), previousCursorPosition()
668 int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
670 const HB_CharAttributes *attributes = d->attributes();
671 int len = d->block.isValid() ? d->block.length() - 1
672 : d->layoutData->string.length();
673 Q_ASSERT(len <= d->layoutData->string.length());
674 if (!attributes || oldPos < 0 || oldPos >= len)
677 if (mode == SkipCharacters) {
679 while (oldPos < len && !attributes[oldPos].charStop)
682 if (oldPos < len && d->atWordSeparator(oldPos)) {
684 while (oldPos < len && d->atWordSeparator(oldPos))
687 while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
690 while (oldPos < len && d->atSpace(oldPos))
698 Returns the first valid cursor position before \a oldPos that
699 respects the given cursor \a mode.
700 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
702 \sa isValidCursorPosition(), nextCursorPosition()
704 int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
706 const HB_CharAttributes *attributes = d->attributes();
707 if (!attributes || oldPos <= 0 || oldPos > d->layoutData->string.length())
710 if (mode == SkipCharacters) {
712 while (oldPos && !attributes[oldPos].charStop)
715 while (oldPos && d->atSpace(oldPos-1))
718 if (oldPos && d->atWordSeparator(oldPos-1)) {
720 while (oldPos && d->atWordSeparator(oldPos-1))
723 while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
734 Returns the cursor position to the right of \a oldPos, next to it.
735 The position is dependent on the visual position of characters, after
736 bi-directional reordering.
738 \sa leftCursorPosition(), nextCursorPosition()
740 int QTextLayout::rightCursorPosition(int oldPos) const
742 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right);
743 // qDebug("%d -> %d", oldPos, newPos);
750 Returns the cursor position to the left of \a oldPos, next to it.
751 The position is dependent on the visual position of characters, after
752 bi-directional reordering.
754 \sa rightCursorPosition(), previousCursorPosition()
756 int QTextLayout::leftCursorPosition(int oldPos) const
758 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left);
759 // qDebug("%d -> %d", oldPos, newPos);
764 Returns true if position \a pos is a valid cursor position.
766 In a Unicode context some positions in the text are not valid
767 cursor positions, because the position is inside a Unicode
768 surrogate or a grapheme cluster.
770 A grapheme cluster is a sequence of two or more Unicode characters
771 that form one indivisible entity on the screen. For example the
772 latin character `\Auml' can be represented in Unicode by two
773 characters, `A' (0x41), and the combining diaresis (0x308). A text
774 cursor can only validly be positioned before or after these two
775 characters, never between them since that wouldn't make sense. In
776 indic languages every syllable forms a grapheme cluster.
778 bool QTextLayout::isValidCursorPosition(int pos) const
780 const HB_CharAttributes *attributes = d->attributes();
781 if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
783 return attributes[pos].charStop;
787 Returns a new text line to be laid out if there is text to be
788 inserted into the layout; otherwise returns an invalid text line.
790 The text layout creates a new line object that starts after the
791 last line in the layout, or at the beginning if the layout is empty.
792 The layout maintains an internal cursor, and each line is filled
793 with text from the cursor position onwards when the
794 QTextLine::setLineWidth() function is called.
796 Once QTextLine::setLineWidth() is called, a new line can be created and
797 filled with text. Repeating this process will lay out the whole block
798 of text contained in the QTextLayout. If there is no text left to be
799 inserted into the layout, the QTextLine returned will not be valid
800 (isValid() will return false).
802 QTextLine QTextLayout::createLine()
805 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
806 qWarning("QTextLayout::createLine: Called without layouting");
810 if (d->layoutData->layoutState == QTextEngine::LayoutFailed)
813 int l = d->lines.size();
814 if (l && d->lines.at(l-1).length < 0) {
815 QTextLine(l-1, d).setNumColumns(INT_MAX);
817 int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length + d->lines.at(l-1).trailingSpaces : 0;
818 int strlen = d->layoutData->string.length();
819 if (l && from >= strlen) {
820 if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
827 line.justified = false;
828 line.gridfitted = false;
830 d->lines.append(line);
831 return QTextLine(l, d);
835 Returns the number of lines in this text layout.
839 int QTextLayout::lineCount() const
841 return d->lines.size();
845 Returns the \a{i}-th line of text in this text layout.
847 \sa lineCount(), lineForTextPosition()
849 QTextLine QTextLayout::lineAt(int i) const
851 return QTextLine(i, d);
855 Returns the line that contains the cursor position specified by \a pos.
857 \sa isValidCursorPosition(), lineAt()
859 QTextLine QTextLayout::lineForTextPosition(int pos) const
861 int lineNum = d->lineNumberForTextPosition(pos);
862 return lineNum >= 0 ? lineAt(lineNum) : QTextLine();
868 The global position of the layout. This is independent of the
869 bounding rectangle and of the layout process.
873 QPointF QTextLayout::position() const
879 Moves the text layout to point \a p.
883 void QTextLayout::setPosition(const QPointF &p)
889 The smallest rectangle that contains all the lines in the layout.
891 QRectF QTextLayout::boundingRect() const
893 if (d->lines.isEmpty())
897 QFixed xmin = d->lines.at(0).x;
898 QFixed ymin = d->lines.at(0).y;
900 for (int i = 0; i < d->lines.size(); ++i) {
901 const QScriptLine &si = d->lines[i];
902 xmin = qMin(xmin, si.x);
903 ymin = qMin(ymin, si.y);
904 QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
905 xmax = qMax(xmax, si.x+lineWidth);
906 // ### shouldn't the ascent be used in ymin???
907 ymax = qMax(ymax, si.y+si.height());
909 return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
913 The minimum width the layout needs. This is the width of the
914 layout's smallest non-breakable substring.
916 \warning This function only returns a valid value after the layout
921 qreal QTextLayout::minimumWidth() const
923 return d->minWidth.toReal();
927 The maximum width the layout could expand to; this is essentially
928 the width of the entire text.
930 \warning This function only returns a valid value after the layout
935 qreal QTextLayout::maximumWidth() const
937 return d->maxWidth.toReal();
944 void QTextLayout::setFlags(int flags)
946 if (flags & Qt::TextJustificationForced) {
947 d->option.setAlignment(Qt::AlignJustify);
948 d->forceJustification = true;
951 if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
952 d->ignoreBidi = true;
953 d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
957 static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
958 QPainterPath *region, QRectF boundingRect)
960 const QScriptLine &line = eng->lines[lineNumber];
962 QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
966 const qreal selectionY = pos.y() + line.y.toReal();
967 const qreal lineHeight = line.height().toReal();
969 QFixed lastSelectionX = iterator.x;
970 QFixed lastSelectionWidth;
972 while (!iterator.atEnd()) {
975 QFixed selectionX, selectionWidth;
976 if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
977 if (selectionX == lastSelectionX + lastSelectionWidth) {
978 lastSelectionWidth += selectionWidth;
982 if (lastSelectionWidth > 0)
983 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
985 lastSelectionX = selectionX;
986 lastSelectionWidth = selectionWidth;
989 if (lastSelectionWidth > 0)
990 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
993 static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
995 return clip.isValid() ? (rect & clip) : rect;
1000 Returns the glyph indexes and positions for all glyphs in this QTextLayout. This is an
1001 expensive function, and should not be called in a time sensitive context.
1005 \sa draw(), QPainter::drawGlyphRun()
1007 #if !defined(QT_NO_RAWFONT)
1008 QList<QGlyphRun> QTextLayout::glyphRuns() const
1010 QList<QGlyphRun> glyphs;
1011 for (int i=0; i<d->lines.size(); ++i)
1012 glyphs += QTextLine(i, d).glyphs(-1, -1);
1016 #endif // QT_NO_RAWFONT
1019 Draws the whole layout on the painter \a p at the position specified by \a pos.
1020 The rendered layout includes the given \a selections and is clipped within
1021 the rectangle specified by \a clip.
1023 void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
1025 if (d->lines.isEmpty())
1031 QPointF position = pos + d->position;
1033 QFixed clipy = (INT_MIN/256);
1034 QFixed clipe = (INT_MAX/256);
1035 if (clip.isValid()) {
1036 clipy = QFixed::fromReal(clip.y() - position.y());
1037 clipe = clipy + QFixed::fromReal(clip.height());
1041 int lastLine = d->lines.size();
1042 for (int i = 0; i < d->lines.size(); ++i) {
1044 const QScriptLine &sl = d->lines[i];
1050 if ((sl.y + sl.height()) < clipy) {
1056 QPainterPath excludedRegion;
1057 QPainterPath textDoneRegion;
1058 for (int i = 0; i < selections.size(); ++i) {
1059 FormatRange selection = selections.at(i);
1060 const QBrush bg = selection.format.background();
1062 QPainterPath region;
1063 region.setFillRule(Qt::WindingFill);
1065 for (int line = firstLine; line < lastLine; ++line) {
1066 const QScriptLine &sl = d->lines[line];
1067 QTextLine tl(line, d);
1069 QRectF lineRect(tl.naturalTextRect());
1070 lineRect.translate(position);
1071 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1073 bool isLastLineInBlock = (line == d->lines.size()-1);
1074 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1077 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1078 continue; // no actual intersection
1080 const bool selectionStartInLine = sl.from <= selection.start;
1081 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1083 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1084 addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip));
1086 region.addRect(clipIfValid(lineRect, clip));
1089 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1090 QRectF fullLineRect(tl.rect());
1091 fullLineRect.translate(position);
1092 fullLineRect.setRight(QFIXED_MAX);
1093 if (!selectionEndInLine)
1094 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1095 if (!selectionStartInLine)
1096 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1097 } else if (!selectionEndInLine
1098 && isLastLineInBlock
1099 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1100 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1101 lineRect.height()/4, lineRect.height()), clip));
1106 const QPen oldPen = p->pen();
1107 const QBrush oldBrush = p->brush();
1109 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1110 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1111 p->drawPath(region);
1114 p->setBrush(oldBrush);
1119 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1120 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1122 if (hasBackground) {
1123 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1124 // don't just clear the property, set an empty brush that overrides a potential
1125 // background brush specified in the text
1126 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1127 selection.format.clearProperty(QTextFormat::OutlinePen);
1130 selection.format.setProperty(SuppressText, !hasText);
1132 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1136 p->setClipPath(region, Qt::IntersectClip);
1138 for (int line = firstLine; line < lastLine; ++line) {
1139 QTextLine l(line, d);
1140 l.draw(p, position, &selection);
1145 textDoneRegion += region;
1148 textDoneRegion -= region;
1151 excludedRegion += region;
1154 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1155 if (!needsTextButNoBackground.isEmpty()){
1157 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1158 FormatRange selection;
1159 selection.start = 0;
1160 selection.length = INT_MAX;
1161 selection.format.setProperty(SuppressBackground, true);
1162 for (int line = firstLine; line < lastLine; ++line) {
1163 QTextLine l(line, d);
1164 l.draw(p, position, &selection);
1169 if (!excludedRegion.isEmpty()) {
1172 QRectF br = boundingRect().translated(position);
1173 br.setRight(QFIXED_MAX);
1175 br = br.intersected(clip);
1177 path -= excludedRegion;
1178 p->setClipPath(path, Qt::IntersectClip);
1181 for (int i = firstLine; i < lastLine; ++i) {
1183 l.draw(p, position);
1185 if (!excludedRegion.isEmpty())
1189 if (!d->cacheGlyphs)
1194 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1197 Draws a text cursor with the current pen at the given \a position using the
1198 \a painter specified.
1199 The corresponding position within the text is specified by \a cursorPosition.
1201 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1203 drawCursor(p, pos, cursorPosition, 1);
1207 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1209 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1210 \a painter specified.
1211 The corresponding position within the text is specified by \a cursorPosition.
1213 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1215 if (d->lines.isEmpty())
1221 QPointF position = pos + d->position;
1223 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1224 int line = d->lineNumberForTextPosition(cursorPosition);
1227 if (line >= d->lines.size())
1230 QTextLine l(line, d);
1231 const QScriptLine &sl = d->lines[line];
1233 qreal x = position.x() + l.cursorToX(cursorPosition);
1237 if (d->visualCursorMovement()) {
1238 if (cursorPosition == sl.from + sl.length)
1240 itm = d->findItem(cursorPosition);
1242 itm = d->findItem(cursorPosition - 1);
1244 QFixed base = sl.base();
1245 QFixed descent = sl.descent;
1246 bool rightToLeft = d->isRightToLeft();
1248 const QScriptItem &si = d->layoutData->items.at(itm);
1252 descent = si.descent;
1253 rightToLeft = si.analysis.bidiLevel % 2;
1255 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1256 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1257 && (p->transform().type() > QTransform::TxTranslate);
1258 if (toggleAntialiasing)
1259 p->setRenderHint(QPainter::Antialiasing);
1260 #if defined(QT_MAC_USE_COCOA)
1261 // Always draw the cursor aligned to pixel boundary.
1264 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1265 if (toggleAntialiasing)
1266 p->setRenderHint(QPainter::Antialiasing, false);
1267 if (d->layoutData->hasBidi) {
1268 const int arrow_extent = 4;
1269 int sign = rightToLeft ? -1 : 1;
1270 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1271 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1280 \brief The QTextLine class represents a line of text inside a QTextLayout.
1282 \ingroup richtext-processing
1284 A text line is usually created by QTextLayout::createLine().
1286 After being created, the line can be filled using the setLineWidth()
1287 or setNumColumns() functions. A line has a number of attributes including the
1288 rectangle it occupies, rect(), its coordinates, x() and y(), its
1289 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1290 relative to the text. The position of the cursor in terms of the
1291 line is available from cursorToX() and its inverse from
1292 xToCursor(). A line can be moved with setPosition().
1296 \enum QTextLine::Edge
1303 \enum QTextLine::CursorPosition
1305 \value CursorBetweenCharacters
1306 \value CursorOnCharacter
1310 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1313 Constructs a new text line using the line at position \a line in
1314 the text engine \a e.
1318 \fn QTextLine::QTextLine()
1320 Creates an invalid line.
1324 \fn bool QTextLine::isValid() const
1326 Returns true if this text line is valid; otherwise returns false.
1330 \fn int QTextLine::lineNumber() const
1332 Returns the position of the line in the text engine.
1337 Returns the line's bounding rectangle.
1339 \sa x(), y(), textLength(), width()
1341 QRectF QTextLine::rect() const
1343 const QScriptLine& sl = eng->lines[i];
1344 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1348 Returns the rectangle covered by the line.
1350 QRectF QTextLine::naturalTextRect() const
1352 const QScriptLine& sl = eng->lines[i];
1353 QFixed x = sl.x + eng->alignLine(sl);
1355 QFixed width = sl.textWidth;
1359 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1363 Returns the line's x position.
1365 \sa rect(), y(), textLength(), width()
1367 qreal QTextLine::x() const
1369 return eng->lines[i].x.toReal();
1373 Returns the line's y position.
1375 \sa x(), rect(), textLength(), width()
1377 qreal QTextLine::y() const
1379 return eng->lines[i].y.toReal();
1383 Returns the line's width as specified by the layout() function.
1385 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1387 qreal QTextLine::width() const
1389 return eng->lines[i].width.toReal();
1394 Returns the line's ascent.
1396 \sa descent(), height()
1398 qreal QTextLine::ascent() const
1400 return eng->lines[i].ascent.toReal();
1404 Returns the line's descent.
1406 \sa ascent(), height()
1408 qreal QTextLine::descent() const
1410 return eng->lines[i].descent.toReal();
1414 Returns the line's height. This is equal to ascent() + descent() + 1
1415 if leading is not included. If leading is included, this equals to
1416 ascent() + descent() + leading() + 1.
1418 \sa ascent(), descent(), leading(), setLeadingIncluded()
1420 qreal QTextLine::height() const
1422 return eng->lines[i].height().toReal();
1428 Returns the line's leading.
1430 \sa ascent(), descent(), height()
1432 qreal QTextLine::leading() const
1434 return eng->lines[i].leading.toReal();
1440 Includes positive leading into the line's height if \a included is true;
1441 otherwise does not include leading.
1443 By default, leading is not included.
1445 Note that negative leading is ignored, it must be handled
1446 in the code using the text lines by letting the lines overlap.
1448 \sa leadingIncluded()
1451 void QTextLine::setLeadingIncluded(bool included)
1453 eng->lines[i].leadingIncluded= included;
1460 Returns true if positive leading is included into the line's height;
1461 otherwise returns false.
1463 By default, leading is not included.
1465 \sa setLeadingIncluded()
1467 bool QTextLine::leadingIncluded() const
1469 return eng->lines[i].leadingIncluded;
1473 Returns the width of the line that is occupied by text. This is
1474 always \<= to width(), and is the minimum width that could be used
1475 by layout() without changing the line break position.
1477 qreal QTextLine::naturalTextWidth() const
1479 return eng->lines[i].textWidth.toReal();
1484 Returns the horizontal advance of the text. The advance of the text
1485 is the distance from its position to the next position at which
1486 text would naturally be drawn.
1488 By adding the advance to the position of the text line and using this
1489 as the position of a second text line, you will be able to position
1490 the two lines side-by-side without gaps in-between.
1492 qreal QTextLine::horizontalAdvance() const
1494 return eng->lines[i].textAdvance.toReal();
1498 Lays out the line with the given \a width. The line is filled from
1499 its starting position with as many characters as will fit into
1500 the line. In case the text cannot be split at the end of the line,
1501 it will be filled with additional characters to the next whitespace
1504 void QTextLine::setLineWidth(qreal width)
1506 QScriptLine &line = eng->lines[i];
1507 if (!eng->layoutData) {
1508 qWarning("QTextLine: Can't set a line width while not layouting.");
1512 if (width > QFIXED_MAX)
1515 line.width = QFixed::fromReal(width);
1517 && line.textWidth <= line.width
1518 && line.from + line.length == eng->layoutData->string.length())
1519 // no need to do anything if the line is already layouted and the last one. This optimization helps
1520 // when using things in a single line layout.
1525 layout_helper(INT_MAX);
1529 Lays out the line. The line is filled from its starting position
1530 with as many characters as are specified by \a numColumns. In case
1531 the text cannot be split until \a numColumns characters, the line
1532 will be filled with as many characters to the next whitespace or
1535 void QTextLine::setNumColumns(int numColumns)
1537 QScriptLine &line = eng->lines[i];
1538 line.width = QFIXED_MAX;
1541 layout_helper(numColumns);
1545 Lays out the line. The line is filled from its starting position
1546 with as many characters as are specified by \a numColumns. In case
1547 the text cannot be split until \a numColumns characters, the line
1548 will be filled with as many characters to the next whitespace or
1549 end of the text. The provided \a alignmentWidth is used as reference
1550 width for alignment.
1552 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1554 QScriptLine &line = eng->lines[i];
1555 line.width = QFixed::fromReal(alignmentWidth);
1558 layout_helper(numColumns);
1562 #define LB_DEBUG qDebug
1564 #define LB_DEBUG if (0) qDebug
1569 struct LineBreakHelper
1572 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1573 manualWrap(false), whiteSpaceOrObject(true)
1578 QScriptLine tmpData;
1579 QScriptLine spaceData;
1581 QGlyphLayout glyphs;
1585 int currentPosition;
1586 glyph_t previousGlyph;
1589 QFixed softHyphenWidth;
1590 QFixed rightBearing;
1591 QFixed minimumRightBearing;
1593 QFontEngine *fontEngine;
1594 QFontEngine *previousFontEngine;
1595 const unsigned short *logClusters;
1598 bool whiteSpaceOrObject;
1600 bool checkFullOtherwiseExtend(QScriptLine &line);
1602 QFixed calculateNewWidth(const QScriptLine &line) const {
1603 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1604 - qMin(rightBearing, QFixed());
1607 inline glyph_t currentGlyph() const
1609 Q_ASSERT(currentPosition > 0);
1610 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1612 return glyphs.glyphs[logClusters[currentPosition - 1]];
1615 inline void resetPreviousGlyph()
1618 previousFontEngine = 0;
1621 inline void saveCurrentGlyph()
1623 resetPreviousGlyph();
1624 if (currentPosition > 0 &&
1625 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1626 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1627 previousFontEngine = fontEngine;
1631 inline void adjustRightBearing(glyph_t glyph)
1634 fontEngine->getGlyphBearings(glyph, 0, &rb);
1635 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1638 inline void adjustRightBearing()
1640 if (currentPosition <= 0)
1642 adjustRightBearing(currentGlyph());
1645 inline void adjustPreviousRightBearing()
1647 if (previousGlyph > 0 && previousFontEngine) {
1649 previousFontEngine->getGlyphBearings(previousGlyph, 0, &rb);
1650 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1654 inline void resetRightBearing()
1656 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1657 // negative right bearings are interesting to us.
1661 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1663 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1665 QFixed newWidth = calculateNewWidth(line);
1666 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1669 minw = qMax(minw, tmpData.textWidth);
1671 line.textWidth += spaceData.textWidth;
1673 line.length += spaceData.length;
1674 tmpData.textWidth = 0;
1676 spaceData.textWidth = 0;
1677 spaceData.length = 0;
1682 } // anonymous namespace
1685 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1686 const QScriptItem ¤t, const unsigned short *logClusters,
1687 const QGlyphLayout &glyphs)
1689 int glyphPosition = logClusters[pos];
1690 do { // got to the first next cluster
1693 } while (pos < end && logClusters[pos] == glyphPosition);
1694 do { // calculate the textWidth for the rest of the current cluster.
1695 if (!glyphs.attributes[glyphPosition].dontPrint)
1696 line.textWidth += glyphs.advances_x[glyphPosition];
1698 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1700 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1707 void QTextLine::layout_helper(int maxGlyphs)
1709 QScriptLine &line = eng->lines[i];
1711 line.trailingSpaces = 0;
1713 line.hasTrailingSpaces = false;
1715 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1716 line.setDefaultHeight(eng);
1720 Q_ASSERT(line.from < eng->layoutData->string.length());
1722 LineBreakHelper lbh;
1724 lbh.maxGlyphs = maxGlyphs;
1726 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1727 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1728 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1731 int newItem = eng->findItem(line.from);
1733 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1735 Qt::Alignment alignment = eng->option.alignment();
1737 const HB_CharAttributes *attributes = eng->attributes();
1740 lbh.currentPosition = line.from;
1742 lbh.logClusters = eng->layoutData->logClustersPtr;
1743 lbh.resetPreviousGlyph();
1745 while (newItem < eng->layoutData->items.size()) {
1746 lbh.resetRightBearing();
1747 lbh.softHyphenWidth = 0;
1748 if (newItem != item) {
1750 const QScriptItem ¤t = eng->layoutData->items[item];
1751 if (!current.num_glyphs) {
1753 attributes = eng->attributes();
1756 lbh.logClusters = eng->layoutData->logClustersPtr;
1758 lbh.currentPosition = qMax(line.from, current.position);
1759 end = current.position + eng->length(item);
1760 lbh.glyphs = eng->shapedGlyphs(¤t);
1761 QFontEngine *fontEngine = eng->fontEngine(current);
1762 if (lbh.fontEngine != fontEngine) {
1763 lbh.fontEngine = fontEngine;
1764 lbh.minimumRightBearing = qMin(QFixed(),
1765 QFixed::fromReal(fontEngine->minRightBearing()));
1768 const QScriptItem ¤t = eng->layoutData->items[item];
1770 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1771 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1773 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1774 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1776 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1777 lbh.whiteSpaceOrObject = true;
1778 if (lbh.checkFullOtherwiseExtend(line))
1781 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1782 QFixed tabWidth = eng->calculateTabWidth(item, x);
1784 lbh.spaceData.textWidth += tabWidth;
1785 lbh.spaceData.length++;
1788 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1789 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1791 if (lbh.checkFullOtherwiseExtend(line))
1793 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1794 lbh.whiteSpaceOrObject = true;
1795 // if the line consists only of the line separator make sure
1796 // we have a sane height
1797 if (!line.length && !lbh.tmpData.length)
1798 line.setDefaultHeight(eng);
1799 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1800 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1801 current, lbh.logClusters, lbh.glyphs);
1803 lbh.tmpData.length++;
1804 lbh.adjustPreviousRightBearing();
1806 line += lbh.tmpData;
1808 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1809 lbh.whiteSpaceOrObject = true;
1810 lbh.tmpData.length++;
1812 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1813 if (eng->block.docHandle())
1814 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1816 lbh.tmpData.textWidth += current.width;
1820 if (lbh.checkFullOtherwiseExtend(line))
1822 } else if (attributes[lbh.currentPosition].whiteSpace) {
1823 lbh.whiteSpaceOrObject = true;
1824 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1825 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1826 current, lbh.logClusters, lbh.glyphs);
1828 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1829 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1833 lbh.whiteSpaceOrObject = false;
1834 bool sb_or_ws = false;
1835 lbh.saveCurrentGlyph();
1837 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1838 current, lbh.logClusters, lbh.glyphs);
1840 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1843 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1846 } while (lbh.currentPosition < end);
1847 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1849 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1850 // if we are splitting up a word because of
1851 // a soft hyphen then we ...
1853 // a) have to take the width of the soft hyphen into
1854 // account to see if the first syllable(s) /and/
1855 // the soft hyphen fit into the line
1857 // b) if we are so short of available width that the
1858 // soft hyphen is the first breakable position, then
1859 // we don't want to show it. However we initially
1860 // have to take the width for it into account so that
1861 // the text document layout sees the overflow and
1862 // switch to break-anywhere mode, in which we
1863 // want the soft-hyphen to slip into the next line
1864 // and thus become invisible again.
1867 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1869 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1872 // The actual width of the text needs to take the right bearing into account. The
1873 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1874 // of the advance of the glyph, the bearing will be negative. We flip the sign
1875 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1876 // We ignore the right bearing if the minimum negative bearing is too little to
1877 // expand the text beyond the edge.
1878 if (sb_or_ws|breakany) {
1879 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1880 #if !defined(Q_WS_MAC)
1881 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1883 lbh.adjustRightBearing();
1884 if (lbh.checkFullOtherwiseExtend(line)) {
1885 // we are too wide, fix right bearing
1886 if (rightBearing <= 0)
1887 lbh.rightBearing = rightBearing; // take from cache
1889 lbh.adjustPreviousRightBearing();
1892 line.textWidth += lbh.softHyphenWidth;
1898 lbh.saveCurrentGlyph();
1900 if (lbh.currentPosition == end)
1903 LB_DEBUG("reached end of line");
1904 lbh.checkFullOtherwiseExtend(line);
1906 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1907 lbh.adjustRightBearing();
1908 line.textAdvance = line.textWidth;
1909 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1911 if (line.length == 0) {
1912 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1913 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1914 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1915 line += lbh.tmpData;
1918 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1919 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1920 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1922 if (lbh.manualWrap) {
1923 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1924 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1926 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1927 eng->maxWidth += line.textWidth;
1930 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1931 eng->maxWidth += lbh.spaceData.textWidth;
1932 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1933 line.textWidth += lbh.spaceData.textWidth;
1934 if (lbh.spaceData.length) {
1935 line.trailingSpaces = lbh.spaceData.length;
1936 line.hasTrailingSpaces = true;
1939 line.justified = false;
1940 line.gridfitted = false;
1942 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1943 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1944 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1946 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1949 layout_helper(lbh.maxGlyphs);
1950 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1956 Moves the line to position \a pos.
1958 void QTextLine::setPosition(const QPointF &pos)
1960 eng->lines[i].x = QFixed::fromReal(pos.x());
1961 eng->lines[i].y = QFixed::fromReal(pos.y());
1965 Returns the line's position relative to the text layout's position.
1967 QPointF QTextLine::position() const
1969 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1972 // ### DOC: I have no idea what this means/does.
1973 // You create a text layout with a string of text. Once you laid
1974 // it out, it contains a number of QTextLines. from() returns the position
1975 // inside the text string where this line starts. If you e.g. has a
1976 // text of "This is a string", laid out into two lines (the second
1977 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
1978 // layout.lineAt(1).from() == 8.
1980 Returns the start of the line from the beginning of the string
1981 passed to the QTextLayout.
1983 int QTextLine::textStart() const
1985 return eng->lines[i].from;
1989 Returns the length of the text in the line.
1991 \sa naturalTextWidth()
1993 int QTextLine::textLength() const
1995 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1996 && eng->block.isValid() && i == eng->lines.count()-1) {
1997 return eng->lines[i].length - 1;
1999 return eng->lines[i].length + eng->lines[i].trailingSpaces;
2002 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
2003 int start, int glyph_start)
2005 int ge = glyph_start + gf.glyphs.numGlyphs;
2006 int gs = glyph_start;
2007 int end = start + gf.num_chars;
2008 unsigned short *logClusters = eng->logClusters(&si);
2009 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2010 QFixed orig_width = gf.width;
2012 int *ul = eng->underlinePositions;
2014 while (*ul != -1 && *ul < start)
2016 bool rtl = si.analysis.bidiLevel % 2;
2023 if (ul && *ul != -1 && *ul < end) {
2025 gtmp = logClusters[*ul-si.position];
2028 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2029 gf.num_chars = stmp - start;
2030 gf.chars = eng->layoutData->string.unicode() + start;
2033 w += glyphs.effectiveAdvance(gs);
2041 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2044 if (ul && *ul != -1 && *ul < end) {
2046 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2048 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2049 gf.num_chars = stmp - start;
2050 gf.chars = eng->layoutData->string.unicode() + start;
2051 gf.logClusters = logClusters + start - si.position;
2054 w += glyphs.effectiveAdvance(gs);
2059 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2062 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2065 gf.underlineStyle = QTextCharFormat::NoUnderline;
2071 gf.width = orig_width;
2075 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2077 QBrush c = chf.foreground();
2078 if (c.style() == Qt::NoBrush) {
2079 p->setPen(defaultPen);
2082 QBrush bg = chf.background();
2083 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2085 if (c.style() != Qt::NoBrush) {
2086 p->setPen(QPen(c, 0));
2094 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2095 const QTextItemInt::RenderFlags &renderFlags)
2096 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2100 QGlyphLayout glyphLayout;
2101 QPointF itemPosition;
2102 QTextItem::RenderFlags flags;
2109 Returns the glyph indexes and positions for all glyphs in this QTextLine which reside in
2110 QScriptItems that overlap with the range defined by \a from and \a length. The arguments
2111 specify characters, relative to the text in the layout. Note that it is not possible to
2112 use this function to retrieve a subset of the glyphs in a QScriptItem.
2116 \sa QTextLayout::glyphRuns()
2118 #if !defined(QT_NO_RAWFONT)
2119 QList<QGlyphRun> QTextLine::glyphs(int from, int length) const
2121 const QScriptLine &line = eng->lines[i];
2123 if (line.length == 0)
2124 return QList<QGlyphRun>();
2126 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2128 QTextLineItemIterator iterator(eng, i);
2129 qreal y = line.y.toReal() + line.base().toReal();
2130 while (!iterator.atEnd()) {
2131 QScriptItem &si = iterator.next();
2132 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2135 QPointF pos(iterator.x.toReal(), y);
2136 if (from >= 0 && length >= 0 &&
2137 (from >= si.position + eng->length(&si) || from + length <= si.position))
2140 QFont font = eng->font(si);
2142 QTextItem::RenderFlags flags;
2143 if (font.overline())
2144 flags |= QTextItem::Overline;
2145 if (font.underline())
2146 flags |= QTextItem::Underline;
2147 if (font.strikeOut())
2148 flags |= QTextItem::StrikeOut;
2149 if (si.analysis.bidiLevel % 2)
2150 flags |= QTextItem::RightToLeft;
2152 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si).mid(iterator.glyphsStart,
2153 iterator.glyphsEnd - iterator.glyphsStart);
2155 if (glyphLayout.numGlyphs > 0) {
2156 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2157 if (mainFontEngine->type() == QFontEngine::Multi) {
2158 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2161 int which = glyphLayout.glyphs[0] >> 24;
2162 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2163 const int e = glyphLayout.glyphs[end] >> 24;
2167 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2168 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2169 GlyphInfo(subLayout, pos, flags));
2170 for (int i = 0; i < subLayout.numGlyphs; i++)
2171 pos += QPointF(subLayout.advances_x[i].toReal(),
2172 subLayout.advances_y[i].toReal());
2178 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2179 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2180 GlyphInfo(subLayout, pos, flags));
2183 glyphLayoutHash.insertMulti(mainFontEngine,
2184 GlyphInfo(glyphLayout, pos, flags));
2189 QHash<QPair<QFontEngine *, int>, QGlyphRun> glyphsHash;
2191 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2192 for (int i=0; i<keys.size(); ++i) {
2193 QFontEngine *fontEngine = keys.at(i);
2195 // Make a font for this particular engine
2197 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2198 fontD->fontEngine = fontEngine;
2199 fontD->fontEngine->ref.ref();
2201 #if defined(Q_WS_WIN)
2202 if (fontEngine->supportsSubPixelPositions())
2203 fontD->hintingPreference = QFont::PreferVerticalHinting;
2205 fontD->hintingPreference = QFont::PreferFullHinting;
2206 #elif defined(Q_WS_MAC)
2207 fontD->hintingPreference = QFont::PreferNoHinting;
2208 #elif !defined(QT_NO_FREETYPE)
2209 if (fontEngine->type() == QFontEngine::Freetype) {
2210 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2211 switch (freeTypeEngine->defaultHintStyle()) {
2212 case QFontEngineFT::HintNone:
2213 fontD->hintingPreference = QFont::PreferNoHinting;
2215 case QFontEngineFT::HintLight:
2216 fontD->hintingPreference = QFont::PreferVerticalHinting;
2218 case QFontEngineFT::HintMedium:
2219 case QFontEngineFT::HintFull:
2220 fontD->hintingPreference = QFont::PreferFullHinting;
2226 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2227 for (int j=0; j<glyphLayouts.size(); ++j) {
2228 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2229 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2230 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2232 QVarLengthArray<glyph_t> glyphsArray;
2233 QVarLengthArray<QFixedPoint> positionsArray;
2235 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2237 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2239 QVector<quint32> glyphs;
2240 QVector<QPointF> positions;
2241 for (int i=0; i<glyphsArray.size(); ++i) {
2242 glyphs.append(glyphsArray.at(i) & 0xffffff);
2243 positions.append(positionsArray.at(i).toPointF() + pos);
2246 QGlyphRun glyphIndexes;
2247 glyphIndexes.setGlyphIndexes(glyphs);
2248 glyphIndexes.setPositions(positions);
2250 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2251 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2252 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2253 glyphIndexes.setRawFont(font);
2255 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2256 if (!glyphsHash.contains(key)) {
2257 glyphsHash.insert(key, glyphIndexes);
2259 QGlyphRun &glyphRun = glyphsHash[key];
2261 QVector<quint32> indexes = glyphRun.glyphIndexes();
2262 QVector<QPointF> positions = glyphRun.positions();
2264 indexes += glyphIndexes.glyphIndexes();
2265 positions += glyphIndexes.positions();
2267 glyphRun.setGlyphIndexes(indexes);
2268 glyphRun.setPositions(positions);
2273 return glyphsHash.values();
2275 #endif // QT_NO_RAWFONT
2278 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2280 Draws a line on the given \a painter at the specified \a position.
2281 The \a selection is reserved for internal use.
2283 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2285 const QScriptLine &line = eng->lines[i];
2286 QPen pen = p->pen();
2288 bool noText = (selection && selection->format.property(SuppressText).toBool());
2292 && selection->start <= line.from
2293 && selection->start + selection->length > line.from) {
2295 const qreal lineHeight = line.height().toReal();
2296 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2297 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2298 setPenAndDrawBackground(p, QPen(), selection->format, r);
2305 QTextLineItemIterator iterator(eng, i, pos, selection);
2306 QFixed lineBase = line.base();
2308 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2310 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2311 while (!iterator.atEnd()) {
2312 QScriptItem &si = iterator.next();
2314 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2317 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2318 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2321 QFixed itemBaseLine = y;
2322 QFont f = eng->font(si);
2323 QTextCharFormat format;
2325 if (eng->hasFormats() || selection) {
2326 format = eng->format(&si);
2327 if (suppressColors) {
2328 format.clearForeground();
2329 format.clearBackground();
2330 format.clearProperty(QTextFormat::TextUnderlineColor);
2333 format.merge(selection->format);
2335 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2336 iterator.itemWidth.toReal(), line.height().toReal()));
2338 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2339 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2340 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2341 QFixed height = fe->ascent() + fe->descent();
2342 if (valign == QTextCharFormat::AlignSubScript)
2343 itemBaseLine += height / 6;
2344 else if (valign == QTextCharFormat::AlignSuperScript)
2345 itemBaseLine -= height / 2;
2349 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2351 if (eng->hasFormats()) {
2353 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2354 QFixed itemY = y - si.ascent;
2355 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2356 itemY = y - lineBase;
2359 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2361 eng->docLayout()->drawInlineObject(p, itemRect,
2362 QTextInlineObject(iterator.item, eng),
2363 si.position + eng->block.position(),
2366 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2367 if (bg.style() != Qt::NoBrush) {
2368 QColor c = bg.color();
2370 p->fillRect(itemRect, c);
2373 } else { // si.isTab
2374 QFont f = eng->font(si);
2375 QTextItemInt gf(si, &f, format);
2378 gf.width = iterator.itemWidth;
2379 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2380 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2381 QChar visualTab(0x2192);
2382 int w = QFontMetrics(f).width(visualTab);
2383 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2385 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2386 iterator.itemWidth.toReal(), line.height().toReal()),
2390 p->drawText(QPointF(iterator.x.toReal() + x,
2391 y.toReal()), visualTab);
2401 unsigned short *logClusters = eng->logClusters(&si);
2402 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2404 QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
2405 &f, eng->layoutData->string.unicode() + iterator.itemStart,
2406 iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
2407 gf.logClusters = logClusters + iterator.itemStart - si.position;
2408 gf.width = iterator.itemWidth;
2409 gf.justified = line.justified;
2410 gf.initWithScriptItem(si);
2412 Q_ASSERT(gf.fontEngine);
2414 if (eng->underlinePositions) {
2415 // can't have selections in this case
2416 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2418 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2419 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2421 path.setFillRule(Qt::WindingFill);
2423 if (gf.glyphs.numGlyphs)
2424 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2426 const QFontEngine *fe = gf.fontEngine;
2427 const qreal lw = fe->lineThickness().toReal();
2428 if (gf.flags & QTextItem::Underline) {
2429 qreal offs = fe->underlinePosition().toReal();
2430 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2432 if (gf.flags & QTextItem::Overline) {
2433 qreal offs = fe->ascent().toReal() + 1;
2434 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2436 if (gf.flags & QTextItem::StrikeOut) {
2437 qreal offs = fe->ascent().toReal() / 3;
2438 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2443 p->setRenderHint(QPainter::Antialiasing);
2444 //Currently QPen with a Qt::NoPen style still returns a default
2445 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2446 if (p->pen().style() == Qt::NoPen)
2447 p->setBrush(Qt::NoBrush);
2449 p->setBrush(p->pen().brush());
2451 p->setPen(format.textOutline());
2456 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2457 p->drawTextItem(pos, gf);
2460 if (si.analysis.flags == QScriptAnalysis::Space
2461 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2462 QBrush c = format.foreground();
2463 if (c.style() != Qt::NoBrush)
2464 p->setPen(c.color());
2465 QChar visualSpace((ushort)0xb7);
2466 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2472 if (eng->hasFormats())
2477 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2483 Converts the cursor position \a cursorPos to the corresponding x position
2484 inside the line, taking account of the \a edge.
2486 If \a cursorPos is not a valid cursor position, the nearest valid
2487 cursor position will be used instead, and cpos will be modified to
2488 point to this valid cursor position.
2492 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2494 if (!eng->layoutData)
2497 const QScriptLine &line = eng->lines[i];
2498 bool lastLine = i >= eng->lines.size() - 1;
2501 x += eng->alignLine(line);
2503 if (!i && !eng->layoutData->items.size()) {
2508 int pos = *cursorPos;
2510 if (pos == line.from + (int)line.length) {
2511 // end of line ensure we have the last item on the line
2512 itm = eng->findItem(pos-1);
2515 itm = eng->findItem(pos);
2516 eng->shapeLine(line);
2518 const QScriptItem *si = &eng->layoutData->items[itm];
2519 if (!si->num_glyphs)
2521 pos -= si->position;
2523 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2524 unsigned short *logClusters = eng->logClusters(si);
2525 Q_ASSERT(logClusters);
2527 int l = eng->length(itm);
2533 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2534 if (edge == Trailing) {
2535 // trailing edge is leading edge of next cluster
2536 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2540 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2542 int lineEnd = line.from + line.length;
2544 // add the items left of the cursor
2546 int firstItem = eng->findItem(line.from);
2547 int lastItem = eng->findItem(lineEnd - 1);
2548 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2550 QVarLengthArray<int> visualOrder(nItems);
2551 QVarLengthArray<uchar> levels(nItems);
2552 for (int i = 0; i < nItems; ++i)
2553 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2554 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2556 for (int i = 0; i < nItems; ++i) {
2557 int item = visualOrder[i]+firstItem;
2560 QScriptItem &si = eng->layoutData->items[item];
2564 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2568 int start = qMax(line.from, si.position);
2569 int end = qMin(lineEnd, si.position + eng->length(item));
2571 logClusters = eng->logClusters(&si);
2573 int gs = logClusters[start-si.position];
2574 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2576 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2579 x += glyphs.effectiveAdvance(gs);
2584 logClusters = eng->logClusters(si);
2585 glyphs = eng->shapedGlyphs(si);
2586 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2587 if (pos == (reverse ? 0 : l))
2590 bool rtl = eng->isRightToLeft();
2591 bool visual = eng->visualCursorMovement();
2592 int end = qMin(lineEnd, si->position + l) - si->position;
2594 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2595 int glyph_start = glyph_pos;
2596 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
2598 for (int i = glyph_end - 1; i >= glyph_start; i--)
2599 x += glyphs.effectiveAdvance(i);
2601 int start = qMax(line.from - si->position, 0);
2602 int glyph_start = logClusters[start];
2603 int glyph_end = glyph_pos;
2604 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
2606 for (int i = glyph_start; i <= glyph_end; i++)
2607 x += glyphs.effectiveAdvance(i);
2609 x += eng->offsetInLigature(si, pos, end, glyph_pos);
2612 *cursorPos = pos + si->position;
2617 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2619 Converts the x-coordinate \a x, to the nearest matching cursor
2620 position, depending on the cursor position type, \a cpos.
2624 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2626 QFixed x = QFixed::fromReal(_x);
2627 const QScriptLine &line = eng->lines[i];
2628 bool lastLine = i >= eng->lines.size() - 1;
2631 if (!eng->layoutData)
2634 int line_length = textLength();
2639 int firstItem = eng->findItem(line.from);
2640 int lastItem = eng->findItem(line.from + line_length - 1);
2641 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2647 x -= eng->alignLine(line);
2648 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2650 QVarLengthArray<int> visualOrder(nItems);
2651 QVarLengthArray<unsigned char> levels(nItems);
2652 for (int i = 0; i < nItems; ++i)
2653 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2654 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2656 bool visual = eng->visualCursorMovement();
2658 // left of first item
2659 int item = visualOrder[0]+firstItem;
2660 QScriptItem &si = eng->layoutData->items[item];
2663 int pos = si.position;
2664 if (si.analysis.bidiLevel % 2)
2665 pos += eng->length(item);
2666 pos = qMax(line.from, pos);
2667 pos = qMin(line.from + line_length, pos);
2669 } else if (x < line.textWidth
2670 || (line.justified && x < line.width)) {
2671 // has to be in one of the runs
2673 bool rtl = eng->isRightToLeft();
2675 eng->shapeLine(line);
2676 QVector<int> insertionPoints;
2678 eng->insertionPointsForLine(lineNum, insertionPoints);
2680 for (int i = 0; i < nItems; ++i) {
2681 int item = visualOrder[i]+firstItem;
2682 QScriptItem &si = eng->layoutData->items[item];
2683 int item_length = eng->length(item);
2684 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2686 int start = qMax(line.from - si.position, 0);
2687 int end = qMin(line.from + line_length - si.position, item_length);
2689 unsigned short *logClusters = eng->logClusters(&si);
2691 int gs = logClusters[start];
2692 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2693 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2695 QFixed item_width = 0;
2696 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2697 item_width = si.width;
2701 item_width += glyphs.effectiveAdvance(g);
2705 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2707 if (pos + item_width < x) {
2712 // qDebug(" inside run");
2713 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2714 if (cpos == QTextLine::CursorOnCharacter)
2716 bool left_half = (x - pos) < item_width/2;
2718 if (bool(si.analysis.bidiLevel % 2) != left_half)
2720 return si.position + 1;
2725 // has to be inside run
2726 if (cpos == QTextLine::CursorOnCharacter) {
2727 if (si.analysis.bidiLevel % 2) {
2731 if (glyphs.attributes[gs].clusterStart) {
2738 pos -= glyphs.effectiveAdvance(gs);
2744 if (glyphs.attributes[gs].clusterStart) {
2750 pos += glyphs.effectiveAdvance(gs);
2755 QFixed dist = INT_MAX/256;
2756 if (si.analysis.bidiLevel % 2) {
2757 if (!visual || rtl || (lastLine && i == nItems - 1)) {
2760 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2765 pos -= glyphs.effectiveAdvance(gs);
2770 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
2775 pos += glyphs.effectiveAdvance(ge);
2780 if (!visual || !rtl || (lastLine && i == 0)) {
2782 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2787 pos += glyphs.effectiveAdvance(gs);
2791 QFixed oldPos = pos;
2793 pos += glyphs.effectiveAdvance(gs);
2794 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2804 if (qAbs(x-pos) < dist) {
2806 if (!rtl && i < nItems - 1) {
2810 if (rtl && nchars > 0)
2811 return insertionPoints[lastLine ? nchars : nchars - 1];
2813 return eng->positionInLigature(&si, end, x, pos, -1,
2814 cpos == QTextLine::CursorOnCharacter);
2817 Q_ASSERT(glyph_pos != -1);
2818 return eng->positionInLigature(&si, end, x, edge, glyph_pos,
2819 cpos == QTextLine::CursorOnCharacter);
2822 // right of last item
2823 // qDebug() << "right of last";
2824 int item = visualOrder[nItems-1]+firstItem;
2825 QScriptItem &si = eng->layoutData->items[item];
2828 int pos = si.position;
2829 if (!(si.analysis.bidiLevel % 2))
2830 pos += eng->length(item);
2831 pos = qMax(line.from, pos);
2833 int maxPos = line.from + line_length;
2835 // except for the last line we assume that the
2836 // character between lines is a space and we want
2837 // to position the cursor to the left of that
2839 // ###### breaks with japanese for example
2840 if (this->i < eng->lines.count() - 1)
2843 pos = qMin(pos, maxPos);