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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
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)
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());
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;
582 Set the cursor movement style. If the QTextLayout is backed by
583 a document, you can ignore this and use the option in QTextDocument,
584 this option is for widgets like QLineEdit or custom widgets without
585 a QTextDocument. Default value is Qt::LogicalMoveStyle.
587 \sa setCursorMoveStyle()
589 void QTextLayout::setCursorMoveStyle(Qt::CursorMoveStyle style)
591 d->visualMovement = style == Qt::VisualMoveStyle ? true : false;
595 The cursor movement style of this QTextLayout. The default is
596 Qt::LogicalMoveStyle.
598 \sa setCursorMoveStyle()
600 Qt::CursorMoveStyle QTextLayout::cursorMoveStyle() const
602 return d->visualMovement ? Qt::VisualMoveStyle : Qt::LogicalMoveStyle;
606 Begins the layout process.
610 void QTextLayout::beginLayout()
613 if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) {
614 qWarning("QTextLayout::beginLayout: Called while already doing layout");
621 d->layoutData->layoutState = QTextEngine::InLayout;
625 Ends the layout process.
629 void QTextLayout::endLayout()
632 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
633 qWarning("QTextLayout::endLayout: Called without beginLayout()");
637 int l = d->lines.size();
638 if (l && d->lines.at(l-1).length < 0) {
639 QTextLine(l-1, d).setNumColumns(INT_MAX);
641 d->layoutData->layoutState = QTextEngine::LayoutEmpty;
649 Clears the line information in the layout. After having called
650 this function, lineCount() returns 0.
652 void QTextLayout::clearLayout()
658 Returns the next valid cursor position after \a oldPos that
659 respects the given cursor \a mode.
660 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
662 \sa isValidCursorPosition(), previousCursorPosition()
664 int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
666 const HB_CharAttributes *attributes = d->attributes();
667 int len = d->block.isValid() ? d->block.length() - 1
668 : d->layoutData->string.length();
669 Q_ASSERT(len <= d->layoutData->string.length());
670 if (!attributes || oldPos < 0 || oldPos >= len)
673 if (mode == SkipCharacters) {
675 while (oldPos < len && !attributes[oldPos].charStop)
678 if (oldPos < len && d->atWordSeparator(oldPos)) {
680 while (oldPos < len && d->atWordSeparator(oldPos))
683 while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
686 while (oldPos < len && d->atSpace(oldPos))
694 Returns the first valid cursor position before \a oldPos that
695 respects the given cursor \a mode.
696 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
698 \sa isValidCursorPosition(), nextCursorPosition()
700 int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
702 const HB_CharAttributes *attributes = d->attributes();
703 if (!attributes || oldPos <= 0 || oldPos > d->layoutData->string.length())
706 if (mode == SkipCharacters) {
708 while (oldPos && !attributes[oldPos].charStop)
711 while (oldPos && d->atSpace(oldPos-1))
714 if (oldPos && d->atWordSeparator(oldPos-1)) {
716 while (oldPos && d->atWordSeparator(oldPos-1))
719 while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
728 Returns the cursor position to the right of \a oldPos, next to it.
729 It's dependent on the visual position of characters, after bi-directional
732 \sa leftCursorPosition(), nextCursorPosition()
734 int QTextLayout::rightCursorPosition(int oldPos) const
736 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right);
737 // qDebug("%d -> %d", oldPos, newPos);
742 Returns the cursor position to the left of \a oldPos, next to it.
743 It's dependent on the visual position of characters, after bi-directional
746 \sa rightCursorPosition(), previousCursorPosition()
748 int QTextLayout::leftCursorPosition(int oldPos) const
750 int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left);
751 // qDebug("%d -> %d", oldPos, newPos);
756 Returns true if position \a pos is a valid cursor position.
758 In a Unicode context some positions in the text are not valid
759 cursor positions, because the position is inside a Unicode
760 surrogate or a grapheme cluster.
762 A grapheme cluster is a sequence of two or more Unicode characters
763 that form one indivisible entity on the screen. For example the
764 latin character `\Auml' can be represented in Unicode by two
765 characters, `A' (0x41), and the combining diaresis (0x308). A text
766 cursor can only validly be positioned before or after these two
767 characters, never between them since that wouldn't make sense. In
768 indic languages every syllable forms a grapheme cluster.
770 bool QTextLayout::isValidCursorPosition(int pos) const
772 const HB_CharAttributes *attributes = d->attributes();
773 if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
775 return attributes[pos].charStop;
779 Returns a new text line to be laid out if there is text to be
780 inserted into the layout; otherwise returns an invalid text line.
782 The text layout creates a new line object that starts after the
783 last line in the layout, or at the beginning if the layout is empty.
784 The layout maintains an internal cursor, and each line is filled
785 with text from the cursor position onwards when the
786 QTextLine::setLineWidth() function is called.
788 Once QTextLine::setLineWidth() is called, a new line can be created and
789 filled with text. Repeating this process will lay out the whole block
790 of text contained in the QTextLayout. If there is no text left to be
791 inserted into the layout, the QTextLine returned will not be valid
792 (isValid() will return false).
794 QTextLine QTextLayout::createLine()
797 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
798 qWarning("QTextLayout::createLine: Called without layouting");
802 if (d->layoutData->layoutState == QTextEngine::LayoutFailed)
805 int l = d->lines.size();
806 if (l && d->lines.at(l-1).length < 0) {
807 QTextLine(l-1, d).setNumColumns(INT_MAX);
809 int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length : 0;
810 int strlen = d->layoutData->string.length();
811 if (l && from >= strlen) {
812 if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
819 line.justified = false;
820 line.gridfitted = false;
822 d->lines.append(line);
823 return QTextLine(l, d);
827 Returns the number of lines in this text layout.
831 int QTextLayout::lineCount() const
833 return d->lines.size();
837 Returns the \a{i}-th line of text in this text layout.
839 \sa lineCount(), lineForTextPosition()
841 QTextLine QTextLayout::lineAt(int i) const
843 return QTextLine(i, d);
847 Returns the line that contains the cursor position specified by \a pos.
849 \sa isValidCursorPosition(), lineAt()
851 QTextLine QTextLayout::lineForTextPosition(int pos) const
853 int lineNum = d->lineNumberForTextPosition(pos);
854 return lineNum >= 0 ? lineAt(lineNum) : QTextLine();
860 The global position of the layout. This is independent of the
861 bounding rectangle and of the layout process.
865 QPointF QTextLayout::position() const
871 Moves the text layout to point \a p.
875 void QTextLayout::setPosition(const QPointF &p)
881 The smallest rectangle that contains all the lines in the layout.
883 QRectF QTextLayout::boundingRect() const
885 if (d->lines.isEmpty())
889 QFixed xmin = d->lines.at(0).x;
890 QFixed ymin = d->lines.at(0).y;
892 for (int i = 0; i < d->lines.size(); ++i) {
893 const QScriptLine &si = d->lines[i];
894 xmin = qMin(xmin, si.x);
895 ymin = qMin(ymin, si.y);
896 QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
897 xmax = qMax(xmax, si.x+lineWidth);
898 // ### shouldn't the ascent be used in ymin???
899 ymax = qMax(ymax, si.y+si.height());
901 return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
905 The minimum width the layout needs. This is the width of the
906 layout's smallest non-breakable substring.
908 \warning This function only returns a valid value after the layout
913 qreal QTextLayout::minimumWidth() const
915 return d->minWidth.toReal();
919 The maximum width the layout could expand to; this is essentially
920 the width of the entire text.
922 \warning This function only returns a valid value after the layout
927 qreal QTextLayout::maximumWidth() const
929 return d->maxWidth.toReal();
936 void QTextLayout::setFlags(int flags)
938 if (flags & Qt::TextJustificationForced) {
939 d->option.setAlignment(Qt::AlignJustify);
940 d->forceJustification = true;
943 if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
944 d->ignoreBidi = true;
945 d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
949 static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
950 QPainterPath *region, QRectF boundingRect)
952 const QScriptLine &line = eng->lines[lineNumber];
954 QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
958 const qreal selectionY = pos.y() + line.y.toReal();
959 const qreal lineHeight = line.height().toReal();
961 QFixed lastSelectionX = iterator.x;
962 QFixed lastSelectionWidth;
964 while (!iterator.atEnd()) {
967 QFixed selectionX, selectionWidth;
968 if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
969 if (selectionX == lastSelectionX + lastSelectionWidth) {
970 lastSelectionWidth += selectionWidth;
974 if (lastSelectionWidth > 0)
975 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
977 lastSelectionX = selectionX;
978 lastSelectionWidth = selectionWidth;
981 if (lastSelectionWidth > 0)
982 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
985 static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
987 return clip.isValid() ? (rect & clip) : rect;
992 Returns the glyph indexes and positions for all glyphs in this QTextLayout. This is an
993 expensive function, and should not be called in a time sensitive context.
997 \sa draw(), QPainter::drawGlyphRun()
999 #if !defined(QT_NO_RAWFONT)
1000 QList<QGlyphRun> QTextLayout::glyphRuns() const
1002 QList<QGlyphRun> glyphs;
1003 for (int i=0; i<d->lines.size(); ++i)
1004 glyphs += QTextLine(i, d).glyphs(-1, -1);
1008 #endif // QT_NO_RAWFONT
1011 Draws the whole layout on the painter \a p at the position specified by \a pos.
1012 The rendered layout includes the given \a selections and is clipped within
1013 the rectangle specified by \a clip.
1015 void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
1017 if (d->lines.isEmpty())
1023 QPointF position = pos + d->position;
1025 QFixed clipy = (INT_MIN/256);
1026 QFixed clipe = (INT_MAX/256);
1027 if (clip.isValid()) {
1028 clipy = QFixed::fromReal(clip.y() - position.y());
1029 clipe = clipy + QFixed::fromReal(clip.height());
1033 int lastLine = d->lines.size();
1034 for (int i = 0; i < d->lines.size(); ++i) {
1036 const QScriptLine &sl = d->lines[i];
1042 if ((sl.y + sl.height()) < clipy) {
1048 QPainterPath excludedRegion;
1049 QPainterPath textDoneRegion;
1050 for (int i = 0; i < selections.size(); ++i) {
1051 FormatRange selection = selections.at(i);
1052 const QBrush bg = selection.format.background();
1054 QPainterPath region;
1055 region.setFillRule(Qt::WindingFill);
1057 for (int line = firstLine; line < lastLine; ++line) {
1058 const QScriptLine &sl = d->lines[line];
1059 QTextLine tl(line, d);
1061 QRectF lineRect(tl.naturalTextRect());
1062 lineRect.translate(position);
1063 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1065 bool isLastLineInBlock = (line == d->lines.size()-1);
1066 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1069 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1070 continue; // no actual intersection
1072 const bool selectionStartInLine = sl.from <= selection.start;
1073 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1075 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1076 addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip));
1078 region.addRect(clipIfValid(lineRect, clip));
1081 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1082 QRectF fullLineRect(tl.rect());
1083 fullLineRect.translate(position);
1084 fullLineRect.setRight(QFIXED_MAX);
1085 if (!selectionEndInLine)
1086 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1087 if (!selectionStartInLine)
1088 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1089 } else if (!selectionEndInLine
1090 && isLastLineInBlock
1091 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1092 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1093 lineRect.height()/4, lineRect.height()), clip));
1098 const QPen oldPen = p->pen();
1099 const QBrush oldBrush = p->brush();
1101 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1102 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1103 p->drawPath(region);
1106 p->setBrush(oldBrush);
1111 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1112 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1114 if (hasBackground) {
1115 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1116 // don't just clear the property, set an empty brush that overrides a potential
1117 // background brush specified in the text
1118 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1119 selection.format.clearProperty(QTextFormat::OutlinePen);
1122 selection.format.setProperty(SuppressText, !hasText);
1124 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1128 p->setClipPath(region, Qt::IntersectClip);
1130 for (int line = firstLine; line < lastLine; ++line) {
1131 QTextLine l(line, d);
1132 l.draw(p, position, &selection);
1137 textDoneRegion += region;
1140 textDoneRegion -= region;
1143 excludedRegion += region;
1146 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1147 if (!needsTextButNoBackground.isEmpty()){
1149 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1150 FormatRange selection;
1151 selection.start = 0;
1152 selection.length = INT_MAX;
1153 selection.format.setProperty(SuppressBackground, true);
1154 for (int line = firstLine; line < lastLine; ++line) {
1155 QTextLine l(line, d);
1156 l.draw(p, position, &selection);
1161 if (!excludedRegion.isEmpty()) {
1164 QRectF br = boundingRect().translated(position);
1165 br.setRight(QFIXED_MAX);
1167 br = br.intersected(clip);
1169 path -= excludedRegion;
1170 p->setClipPath(path, Qt::IntersectClip);
1173 for (int i = firstLine; i < lastLine; ++i) {
1175 l.draw(p, position);
1177 if (!excludedRegion.isEmpty())
1181 if (!d->cacheGlyphs)
1186 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1189 Draws a text cursor with the current pen at the given \a position using the
1190 \a painter specified.
1191 The corresponding position within the text is specified by \a cursorPosition.
1193 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1195 drawCursor(p, pos, cursorPosition, 1);
1199 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1201 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1202 \a painter specified.
1203 The corresponding position within the text is specified by \a cursorPosition.
1205 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1207 if (d->lines.isEmpty())
1213 QPointF position = pos + d->position;
1215 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1216 int line = d->lineNumberForTextPosition(cursorPosition);
1219 if (line >= d->lines.size())
1222 QTextLine l(line, d);
1223 const QScriptLine &sl = d->lines[line];
1225 qreal x = position.x() + l.cursorToX(cursorPosition);
1229 if (d->visualCursorMovement()) {
1230 if (cursorPosition == sl.from + sl.length)
1232 itm = d->findItem(cursorPosition);
1234 itm = d->findItem(cursorPosition - 1);
1236 QFixed base = sl.base();
1237 QFixed descent = sl.descent;
1238 bool rightToLeft = d->isRightToLeft();
1240 const QScriptItem &si = d->layoutData->items.at(itm);
1244 descent = si.descent;
1245 rightToLeft = si.analysis.bidiLevel % 2;
1247 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1248 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1249 && (p->transform().type() > QTransform::TxTranslate);
1250 if (toggleAntialiasing)
1251 p->setRenderHint(QPainter::Antialiasing);
1252 #if defined(QT_MAC_USE_COCOA)
1253 // Always draw the cursor aligned to pixel boundary.
1256 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1257 if (toggleAntialiasing)
1258 p->setRenderHint(QPainter::Antialiasing, false);
1259 if (d->layoutData->hasBidi) {
1260 const int arrow_extent = 4;
1261 int sign = rightToLeft ? -1 : 1;
1262 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1263 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1272 \brief The QTextLine class represents a line of text inside a QTextLayout.
1274 \ingroup richtext-processing
1276 A text line is usually created by QTextLayout::createLine().
1278 After being created, the line can be filled using the setLineWidth()
1279 or setNumColumns() functions. A line has a number of attributes including the
1280 rectangle it occupies, rect(), its coordinates, x() and y(), its
1281 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1282 relative to the text. The position of the cursor in terms of the
1283 line is available from cursorToX() and its inverse from
1284 xToCursor(). A line can be moved with setPosition().
1288 \enum QTextLine::Edge
1295 \enum QTextLine::CursorPosition
1297 \value CursorBetweenCharacters
1298 \value CursorOnCharacter
1302 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1305 Constructs a new text line using the line at position \a line in
1306 the text engine \a e.
1310 \fn QTextLine::QTextLine()
1312 Creates an invalid line.
1316 \fn bool QTextLine::isValid() const
1318 Returns true if this text line is valid; otherwise returns false.
1322 \fn int QTextLine::lineNumber() const
1324 Returns the position of the line in the text engine.
1329 Returns the line's bounding rectangle.
1331 \sa x(), y(), textLength(), width()
1333 QRectF QTextLine::rect() const
1335 const QScriptLine& sl = eng->lines[i];
1336 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1340 Returns the rectangle covered by the line.
1342 QRectF QTextLine::naturalTextRect() const
1344 const QScriptLine& sl = eng->lines[i];
1345 QFixed x = sl.x + eng->alignLine(sl);
1347 QFixed width = sl.textWidth;
1351 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1355 Returns the line's x position.
1357 \sa rect(), y(), textLength(), width()
1359 qreal QTextLine::x() const
1361 return eng->lines[i].x.toReal();
1365 Returns the line's y position.
1367 \sa x(), rect(), textLength(), width()
1369 qreal QTextLine::y() const
1371 return eng->lines[i].y.toReal();
1375 Returns the line's width as specified by the layout() function.
1377 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1379 qreal QTextLine::width() const
1381 return eng->lines[i].width.toReal();
1386 Returns the line's ascent.
1388 \sa descent(), height()
1390 qreal QTextLine::ascent() const
1392 return eng->lines[i].ascent.toReal();
1396 Returns the line's descent.
1398 \sa ascent(), height()
1400 qreal QTextLine::descent() const
1402 return eng->lines[i].descent.toReal();
1406 Returns the line's height. This is equal to ascent() + descent() + 1
1407 if leading is not included. If leading is included, this equals to
1408 ascent() + descent() + leading() + 1.
1410 \sa ascent(), descent(), leading(), setLeadingIncluded()
1412 qreal QTextLine::height() const
1414 return eng->lines[i].height().toReal();
1420 Returns the line's leading.
1422 \sa ascent(), descent(), height()
1424 qreal QTextLine::leading() const
1426 return eng->lines[i].leading.toReal();
1432 Includes positive leading into the line's height if \a included is true;
1433 otherwise does not include leading.
1435 By default, leading is not included.
1437 Note that negative leading is ignored, it must be handled
1438 in the code using the text lines by letting the lines overlap.
1440 \sa leadingIncluded()
1443 void QTextLine::setLeadingIncluded(bool included)
1445 eng->lines[i].leadingIncluded= included;
1452 Returns true if positive leading is included into the line's height;
1453 otherwise returns false.
1455 By default, leading is not included.
1457 \sa setLeadingIncluded()
1459 bool QTextLine::leadingIncluded() const
1461 return eng->lines[i].leadingIncluded;
1465 Returns the width of the line that is occupied by text. This is
1466 always \<= to width(), and is the minimum width that could be used
1467 by layout() without changing the line break position.
1469 qreal QTextLine::naturalTextWidth() const
1471 return eng->lines[i].textWidth.toReal();
1476 Returns the horizontal advance of the text. The advance of the text
1477 is the distance from its position to the next position at which
1478 text would naturally be drawn.
1480 By adding the advance to the position of the text line and using this
1481 as the position of a second text line, you will be able to position
1482 the two lines side-by-side without gaps in-between.
1484 qreal QTextLine::horizontalAdvance() const
1486 return eng->lines[i].textAdvance.toReal();
1490 Lays out the line with the given \a width. The line is filled from
1491 its starting position with as many characters as will fit into
1492 the line. In case the text cannot be split at the end of the line,
1493 it will be filled with additional characters to the next whitespace
1496 void QTextLine::setLineWidth(qreal width)
1498 QScriptLine &line = eng->lines[i];
1499 if (!eng->layoutData) {
1500 qWarning("QTextLine: Can't set a line width while not layouting.");
1504 if (width > QFIXED_MAX)
1507 line.width = QFixed::fromReal(width);
1509 && line.textWidth <= line.width
1510 && line.from + line.length == eng->layoutData->string.length())
1511 // no need to do anything if the line is already layouted and the last one. This optimization helps
1512 // when using things in a single line layout.
1517 layout_helper(INT_MAX);
1521 Lays out the line. The line is filled from its starting position
1522 with as many characters as are specified by \a numColumns. In case
1523 the text cannot be split until \a numColumns characters, the line
1524 will be filled with as many characters to the next whitespace or
1527 void QTextLine::setNumColumns(int numColumns)
1529 QScriptLine &line = eng->lines[i];
1530 line.width = QFIXED_MAX;
1533 layout_helper(numColumns);
1537 Lays out the line. The line is filled from its starting position
1538 with as many characters as are specified by \a numColumns. In case
1539 the text cannot be split until \a numColumns characters, the line
1540 will be filled with as many characters to the next whitespace or
1541 end of the text. The provided \a alignmentWidth is used as reference
1542 width for alignment.
1544 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1546 QScriptLine &line = eng->lines[i];
1547 line.width = QFixed::fromReal(alignmentWidth);
1550 layout_helper(numColumns);
1554 #define LB_DEBUG qDebug
1556 #define LB_DEBUG if (0) qDebug
1561 struct LineBreakHelper
1564 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1565 manualWrap(false), whiteSpaceOrObject(true)
1570 QScriptLine tmpData;
1571 QScriptLine spaceData;
1573 QGlyphLayout glyphs;
1577 int currentPosition;
1578 glyph_t previousGlyph;
1581 QFixed softHyphenWidth;
1582 QFixed rightBearing;
1583 QFixed minimumRightBearing;
1585 QFontEngine *fontEngine;
1586 const unsigned short *logClusters;
1589 bool whiteSpaceOrObject;
1591 bool checkFullOtherwiseExtend(QScriptLine &line);
1593 QFixed calculateNewWidth(const QScriptLine &line) const {
1594 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1595 - qMin(rightBearing, QFixed());
1598 inline glyph_t currentGlyph() const
1600 Q_ASSERT(currentPosition > 0);
1601 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1603 return glyphs.glyphs[logClusters[currentPosition - 1]];
1606 inline void saveCurrentGlyph()
1609 if (currentPosition > 0 &&
1610 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1611 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1615 inline void adjustRightBearing(glyph_t glyph)
1618 fontEngine->getGlyphBearings(glyph, 0, &rb);
1619 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1622 inline void adjustRightBearing()
1624 if (currentPosition <= 0)
1626 adjustRightBearing(currentGlyph());
1629 inline void adjustPreviousRightBearing()
1631 if (previousGlyph > 0)
1632 adjustRightBearing(previousGlyph);
1635 inline void resetRightBearing()
1637 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1638 // negative right bearings are interesting to us.
1642 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1644 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1646 QFixed newWidth = calculateNewWidth(line);
1647 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1650 minw = qMax(minw, tmpData.textWidth);
1652 line.textWidth += spaceData.textWidth;
1654 line.length += spaceData.length;
1655 tmpData.textWidth = 0;
1657 spaceData.textWidth = 0;
1658 spaceData.length = 0;
1663 } // anonymous namespace
1666 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1667 const QScriptItem ¤t, const unsigned short *logClusters,
1668 const QGlyphLayout &glyphs)
1670 int glyphPosition = logClusters[pos];
1671 do { // got to the first next cluster
1674 } while (pos < end && logClusters[pos] == glyphPosition);
1675 do { // calculate the textWidth for the rest of the current cluster.
1676 if (!glyphs.attributes[glyphPosition].dontPrint)
1677 line.textWidth += glyphs.advances_x[glyphPosition];
1679 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1681 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1688 void QTextLine::layout_helper(int maxGlyphs)
1690 QScriptLine &line = eng->lines[i];
1693 line.hasTrailingSpaces = false;
1695 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1696 line.setDefaultHeight(eng);
1700 Q_ASSERT(line.from < eng->layoutData->string.length());
1702 LineBreakHelper lbh;
1704 lbh.maxGlyphs = maxGlyphs;
1706 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1707 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1708 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1711 int newItem = eng->findItem(line.from);
1713 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1715 Qt::Alignment alignment = eng->option.alignment();
1717 const HB_CharAttributes *attributes = eng->attributes();
1720 lbh.currentPosition = line.from;
1722 lbh.logClusters = eng->layoutData->logClustersPtr;
1723 lbh.previousGlyph = 0;
1725 while (newItem < eng->layoutData->items.size()) {
1726 lbh.resetRightBearing();
1727 lbh.softHyphenWidth = 0;
1728 if (newItem != item) {
1730 const QScriptItem ¤t = eng->layoutData->items[item];
1731 if (!current.num_glyphs) {
1733 attributes = eng->attributes();
1736 lbh.logClusters = eng->layoutData->logClustersPtr;
1738 lbh.currentPosition = qMax(line.from, current.position);
1739 end = current.position + eng->length(item);
1740 lbh.glyphs = eng->shapedGlyphs(¤t);
1741 QFontEngine *fontEngine = eng->fontEngine(current);
1742 if (lbh.fontEngine != fontEngine) {
1743 lbh.fontEngine = fontEngine;
1744 lbh.minimumRightBearing = qMin(QFixed(),
1745 QFixed::fromReal(fontEngine->minRightBearing()));
1748 const QScriptItem ¤t = eng->layoutData->items[item];
1750 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1751 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1753 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1754 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1756 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1757 lbh.whiteSpaceOrObject = true;
1758 if (lbh.checkFullOtherwiseExtend(line))
1761 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1762 QFixed tabWidth = eng->calculateTabWidth(item, x);
1764 lbh.spaceData.textWidth += tabWidth;
1765 lbh.spaceData.length++;
1768 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1769 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1771 if (lbh.checkFullOtherwiseExtend(line))
1773 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1774 lbh.whiteSpaceOrObject = true;
1775 // if the line consists only of the line separator make sure
1776 // we have a sane height
1777 if (!line.length && !lbh.tmpData.length)
1778 line.setDefaultHeight(eng);
1779 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1780 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1781 current, lbh.logClusters, lbh.glyphs);
1783 lbh.tmpData.length++;
1784 lbh.adjustPreviousRightBearing();
1786 line += lbh.tmpData;
1788 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1789 lbh.whiteSpaceOrObject = true;
1790 lbh.tmpData.length++;
1792 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1793 if (eng->block.docHandle())
1794 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1796 lbh.tmpData.textWidth += current.width;
1800 if (lbh.checkFullOtherwiseExtend(line))
1802 } else if (attributes[lbh.currentPosition].whiteSpace) {
1803 lbh.whiteSpaceOrObject = true;
1804 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1805 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1806 current, lbh.logClusters, lbh.glyphs);
1808 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1809 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1813 lbh.whiteSpaceOrObject = false;
1814 bool sb_or_ws = false;
1815 lbh.saveCurrentGlyph();
1817 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1818 current, lbh.logClusters, lbh.glyphs);
1820 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1823 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1826 } while (lbh.currentPosition < end);
1827 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1829 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1830 // if we are splitting up a word because of
1831 // a soft hyphen then we ...
1833 // a) have to take the width of the soft hyphen into
1834 // account to see if the first syllable(s) /and/
1835 // the soft hyphen fit into the line
1837 // b) if we are so short of available width that the
1838 // soft hyphen is the first breakable position, then
1839 // we don't want to show it. However we initially
1840 // have to take the width for it into account so that
1841 // the text document layout sees the overflow and
1842 // switch to break-anywhere mode, in which we
1843 // want the soft-hyphen to slip into the next line
1844 // and thus become invisible again.
1847 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1849 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1852 // The actual width of the text needs to take the right bearing into account. The
1853 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1854 // of the advance of the glyph, the bearing will be negative. We flip the sign
1855 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1856 // We ignore the right bearing if the minimum negative bearing is too little to
1857 // expand the text beyond the edge.
1858 if (sb_or_ws|breakany) {
1859 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1860 #if !defined(Q_WS_MAC)
1861 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1863 lbh.adjustRightBearing();
1864 if (lbh.checkFullOtherwiseExtend(line)) {
1865 // we are too wide, fix right bearing
1866 if (rightBearing <= 0)
1867 lbh.rightBearing = rightBearing; // take from cache
1869 lbh.adjustPreviousRightBearing();
1872 line.textWidth += lbh.softHyphenWidth;
1878 lbh.saveCurrentGlyph();
1880 if (lbh.currentPosition == end)
1883 LB_DEBUG("reached end of line");
1884 lbh.checkFullOtherwiseExtend(line);
1886 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1887 lbh.adjustRightBearing();
1888 line.textAdvance = line.textWidth;
1889 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1891 if (line.length == 0) {
1892 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1893 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1894 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1895 line += lbh.tmpData;
1898 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1899 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1900 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1902 if (lbh.manualWrap) {
1903 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1904 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1906 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1907 eng->maxWidth += line.textWidth;
1910 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1911 eng->maxWidth += lbh.spaceData.textWidth;
1912 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1913 line.textWidth += lbh.spaceData.textWidth;
1914 if (lbh.spaceData.length) {
1915 line.length += lbh.spaceData.length;
1916 line.hasTrailingSpaces = true;
1919 line.justified = false;
1920 line.gridfitted = false;
1922 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1923 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1924 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1926 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1929 layout_helper(lbh.maxGlyphs);
1930 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1936 Moves the line to position \a pos.
1938 void QTextLine::setPosition(const QPointF &pos)
1940 eng->lines[i].x = QFixed::fromReal(pos.x());
1941 eng->lines[i].y = QFixed::fromReal(pos.y());
1945 Returns the line's position relative to the text layout's position.
1947 QPointF QTextLine::position() const
1949 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1952 // ### DOC: I have no idea what this means/does.
1953 // You create a text layout with a string of text. Once you laid
1954 // it out, it contains a number of QTextLines. from() returns the position
1955 // inside the text string where this line starts. If you e.g. has a
1956 // text of "This is a string", laid out into two lines (the second
1957 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
1958 // layout.lineAt(1).from() == 8.
1960 Returns the start of the line from the beginning of the string
1961 passed to the QTextLayout.
1963 int QTextLine::textStart() const
1965 return eng->lines[i].from;
1969 Returns the length of the text in the line.
1971 \sa naturalTextWidth()
1973 int QTextLine::textLength() const
1975 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1976 && eng->block.isValid() && i == eng->lines.count()-1) {
1977 return eng->lines[i].length - 1;
1979 return eng->lines[i].length;
1982 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1983 int start, int glyph_start)
1985 int ge = glyph_start + gf.glyphs.numGlyphs;
1986 int gs = glyph_start;
1987 int end = start + gf.num_chars;
1988 unsigned short *logClusters = eng->logClusters(&si);
1989 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
1990 QFixed orig_width = gf.width;
1992 int *ul = eng->underlinePositions;
1994 while (*ul != -1 && *ul < start)
1996 bool rtl = si.analysis.bidiLevel % 2;
2003 if (ul && *ul != -1 && *ul < end) {
2005 gtmp = logClusters[*ul-si.position];
2008 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2009 gf.num_chars = stmp - start;
2010 gf.chars = eng->layoutData->string.unicode() + start;
2013 w += glyphs.effectiveAdvance(gs);
2021 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2024 if (ul && *ul != -1 && *ul < end) {
2026 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2028 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2029 gf.num_chars = stmp - start;
2030 gf.chars = eng->layoutData->string.unicode() + start;
2031 gf.logClusters = logClusters + start - si.position;
2034 w += glyphs.effectiveAdvance(gs);
2039 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2042 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2045 gf.underlineStyle = QTextCharFormat::NoUnderline;
2051 gf.width = orig_width;
2055 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2057 QBrush c = chf.foreground();
2058 if (c.style() == Qt::NoBrush) {
2059 p->setPen(defaultPen);
2062 QBrush bg = chf.background();
2063 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2065 if (c.style() != Qt::NoBrush) {
2066 p->setPen(QPen(c, 0));
2074 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2075 const QTextItemInt::RenderFlags &renderFlags)
2076 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2080 QGlyphLayout glyphLayout;
2081 QPointF itemPosition;
2082 QTextItem::RenderFlags flags;
2089 Returns the glyph indexes and positions for all glyphs in this QTextLine which reside in
2090 QScriptItems that overlap with the range defined by \a from and \a length. The arguments
2091 specify characters, relative to the text in the layout. Note that it is not possible to
2092 use this function to retrieve a subset of the glyphs in a QScriptItem.
2096 \sa QTextLayout::glyphRuns()
2098 #if !defined(QT_NO_RAWFONT)
2099 QList<QGlyphRun> QTextLine::glyphs(int from, int length) const
2101 const QScriptLine &line = eng->lines[i];
2103 if (line.length == 0)
2104 return QList<QGlyphRun>();
2106 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2108 QTextLineItemIterator iterator(eng, i);
2109 qreal y = line.y.toReal() + line.base().toReal();
2110 while (!iterator.atEnd()) {
2111 QScriptItem &si = iterator.next();
2112 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2115 QPointF pos(iterator.x.toReal(), y);
2116 if (from >= 0 && length >= 0 &&
2117 (from >= si.position + eng->length(&si) || from + length <= si.position))
2120 QFont font = eng->font(si);
2122 QTextItem::RenderFlags flags;
2123 if (font.overline())
2124 flags |= QTextItem::Overline;
2125 if (font.underline())
2126 flags |= QTextItem::Underline;
2127 if (font.strikeOut())
2128 flags |= QTextItem::StrikeOut;
2129 if (si.analysis.bidiLevel % 2)
2130 flags |= QTextItem::RightToLeft;
2132 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si).mid(iterator.glyphsStart,
2133 iterator.glyphsEnd - iterator.glyphsStart);
2135 if (glyphLayout.numGlyphs > 0) {
2136 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2137 if (mainFontEngine->type() == QFontEngine::Multi) {
2138 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2141 int which = glyphLayout.glyphs[0] >> 24;
2142 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2143 const int e = glyphLayout.glyphs[end] >> 24;
2147 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2148 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2149 GlyphInfo(subLayout, pos, flags));
2150 for (int i = 0; i < subLayout.numGlyphs; i++)
2151 pos += QPointF(subLayout.advances_x[i].toReal(),
2152 subLayout.advances_y[i].toReal());
2158 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2159 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2160 GlyphInfo(subLayout, pos, flags));
2163 glyphLayoutHash.insertMulti(mainFontEngine,
2164 GlyphInfo(glyphLayout, pos, flags));
2169 QHash<QPair<QFontEngine *, int>, QGlyphRun> glyphsHash;
2171 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2172 for (int i=0; i<keys.size(); ++i) {
2173 QFontEngine *fontEngine = keys.at(i);
2175 // Make a font for this particular engine
2177 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2178 fontD->fontEngine = fontEngine;
2179 fontD->fontEngine->ref.ref();
2181 #if defined(Q_WS_WIN)
2182 if (fontEngine->supportsSubPixelPositions())
2183 fontD->hintingPreference = QFont::PreferVerticalHinting;
2185 fontD->hintingPreference = QFont::PreferFullHinting;
2186 #elif defined(Q_WS_MAC)
2187 fontD->hintingPreference = QFont::PreferNoHinting;
2188 #elif !defined(QT_NO_FREETYPE)
2189 if (fontEngine->type() == QFontEngine::Freetype) {
2190 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2191 switch (freeTypeEngine->defaultHintStyle()) {
2192 case QFontEngineFT::HintNone:
2193 fontD->hintingPreference = QFont::PreferNoHinting;
2195 case QFontEngineFT::HintLight:
2196 fontD->hintingPreference = QFont::PreferVerticalHinting;
2198 case QFontEngineFT::HintMedium:
2199 case QFontEngineFT::HintFull:
2200 fontD->hintingPreference = QFont::PreferFullHinting;
2206 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2207 for (int j=0; j<glyphLayouts.size(); ++j) {
2208 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2209 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2210 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2212 QVarLengthArray<glyph_t> glyphsArray;
2213 QVarLengthArray<QFixedPoint> positionsArray;
2215 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2217 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2219 QVector<quint32> glyphs;
2220 QVector<QPointF> positions;
2221 for (int i=0; i<glyphsArray.size(); ++i) {
2222 glyphs.append(glyphsArray.at(i) & 0xffffff);
2223 positions.append(positionsArray.at(i).toPointF() + pos);
2226 QGlyphRun glyphIndexes;
2227 glyphIndexes.setGlyphIndexes(glyphs);
2228 glyphIndexes.setPositions(positions);
2230 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2231 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2232 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2233 glyphIndexes.setRawFont(font);
2235 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2236 if (!glyphsHash.contains(key))
2237 glyphsHash.insert(key, glyphIndexes);
2239 glyphsHash[key] += glyphIndexes;
2243 return glyphsHash.values();
2245 #endif // QT_NO_RAWFONT
2248 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2250 Draws a line on the given \a painter at the specified \a position.
2251 The \a selection is reserved for internal use.
2253 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2255 const QScriptLine &line = eng->lines[i];
2256 QPen pen = p->pen();
2258 bool noText = (selection && selection->format.property(SuppressText).toBool());
2262 && selection->start <= line.from
2263 && selection->start + selection->length > line.from) {
2265 const qreal lineHeight = line.height().toReal();
2266 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2267 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2268 setPenAndDrawBackground(p, QPen(), selection->format, r);
2275 QTextLineItemIterator iterator(eng, i, pos, selection);
2276 QFixed lineBase = line.base();
2278 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2280 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2281 while (!iterator.atEnd()) {
2282 QScriptItem &si = iterator.next();
2284 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2287 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2288 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2291 QFixed itemBaseLine = y;
2292 QFont f = eng->font(si);
2293 QTextCharFormat format;
2295 if (eng->hasFormats() || selection) {
2296 format = eng->format(&si);
2297 if (suppressColors) {
2298 format.clearForeground();
2299 format.clearBackground();
2300 format.clearProperty(QTextFormat::TextUnderlineColor);
2303 format.merge(selection->format);
2305 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2306 iterator.itemWidth.toReal(), line.height().toReal()));
2308 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2309 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2310 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2311 QFixed height = fe->ascent() + fe->descent();
2312 if (valign == QTextCharFormat::AlignSubScript)
2313 itemBaseLine += height / 6;
2314 else if (valign == QTextCharFormat::AlignSuperScript)
2315 itemBaseLine -= height / 2;
2319 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2321 if (eng->hasFormats()) {
2323 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2324 QFixed itemY = y - si.ascent;
2325 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2326 itemY = y - lineBase;
2329 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2331 eng->docLayout()->drawInlineObject(p, itemRect,
2332 QTextInlineObject(iterator.item, eng),
2333 si.position + eng->block.position(),
2336 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2337 if (bg.style() != Qt::NoBrush) {
2338 QColor c = bg.color();
2340 p->fillRect(itemRect, c);
2343 } else { // si.isTab
2344 QFont f = eng->font(si);
2345 QTextItemInt gf(si, &f, format);
2348 gf.width = iterator.itemWidth;
2349 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2350 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2351 QChar visualTab(0x2192);
2352 int w = QFontMetrics(f).width(visualTab);
2353 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2355 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2356 iterator.itemWidth.toReal(), line.height().toReal()),
2360 p->drawText(QPointF(iterator.x.toReal() + x,
2361 y.toReal()), visualTab);
2371 unsigned short *logClusters = eng->logClusters(&si);
2372 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2374 QTextItemInt gf(si, &f, format);
2375 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2376 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2377 gf.logClusters = logClusters + iterator.itemStart - si.position;
2378 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2379 gf.width = iterator.itemWidth;
2380 gf.justified = line.justified;
2382 Q_ASSERT(gf.fontEngine);
2384 if (eng->underlinePositions) {
2385 // can't have selections in this case
2386 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2388 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2389 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2391 path.setFillRule(Qt::WindingFill);
2393 if (gf.glyphs.numGlyphs)
2394 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2396 const QFontEngine *fe = gf.fontEngine;
2397 const qreal lw = fe->lineThickness().toReal();
2398 if (gf.flags & QTextItem::Underline) {
2399 qreal offs = fe->underlinePosition().toReal();
2400 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2402 if (gf.flags & QTextItem::Overline) {
2403 qreal offs = fe->ascent().toReal() + 1;
2404 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2406 if (gf.flags & QTextItem::StrikeOut) {
2407 qreal offs = fe->ascent().toReal() / 3;
2408 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2413 p->setRenderHint(QPainter::Antialiasing);
2414 //Currently QPen with a Qt::NoPen style still returns a default
2415 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2416 if (p->pen().style() == Qt::NoPen)
2417 p->setBrush(Qt::NoBrush);
2419 p->setBrush(p->pen().brush());
2421 p->setPen(format.textOutline());
2426 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2427 p->drawTextItem(pos, gf);
2430 if (si.analysis.flags == QScriptAnalysis::Space
2431 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2432 QBrush c = format.foreground();
2433 if (c.style() != Qt::NoBrush)
2434 p->setPen(c.color());
2435 QChar visualSpace((ushort)0xb7);
2436 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2442 if (eng->hasFormats())
2447 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2453 Converts the cursor position \a cursorPos to the corresponding x position
2454 inside the line, taking account of the \a edge.
2456 If \a cursorPos is not a valid cursor position, the nearest valid
2457 cursor position will be used instead, and cpos will be modified to
2458 point to this valid cursor position.
2462 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2464 if (!eng->layoutData)
2467 const QScriptLine &line = eng->lines[i];
2468 bool lastLine = i >= eng->lines.size() - 1;
2471 x += eng->alignLine(line);
2473 if (!i && !eng->layoutData->items.size()) {
2478 int pos = *cursorPos;
2480 if (pos == line.from + (int)line.length) {
2481 // end of line ensure we have the last item on the line
2482 itm = eng->findItem(pos-1);
2485 itm = eng->findItem(pos);
2486 eng->shapeLine(line);
2488 const QScriptItem *si = &eng->layoutData->items[itm];
2489 if (!si->num_glyphs)
2491 pos -= si->position;
2493 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2494 unsigned short *logClusters = eng->logClusters(si);
2495 Q_ASSERT(logClusters);
2497 int l = eng->length(itm);
2503 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2504 if (edge == Trailing) {
2505 // trailing edge is leading edge of next cluster
2506 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2510 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2512 int lineEnd = line.from + line.length;
2514 // add the items left of the cursor
2516 int firstItem = eng->findItem(line.from);
2517 int lastItem = eng->findItem(lineEnd - 1);
2518 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2520 QVarLengthArray<int> visualOrder(nItems);
2521 QVarLengthArray<uchar> levels(nItems);
2522 for (int i = 0; i < nItems; ++i)
2523 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2524 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2526 for (int i = 0; i < nItems; ++i) {
2527 int item = visualOrder[i]+firstItem;
2530 QScriptItem &si = eng->layoutData->items[item];
2534 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2538 int start = qMax(line.from, si.position);
2539 int end = qMin(lineEnd, si.position + eng->length(item));
2541 logClusters = eng->logClusters(&si);
2543 int gs = logClusters[start-si.position];
2544 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2546 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2549 x += glyphs.effectiveAdvance(gs);
2554 logClusters = eng->logClusters(si);
2555 glyphs = eng->shapedGlyphs(si);
2556 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2557 if (pos == (reverse ? 0 : l))
2560 bool rtl = eng->isRightToLeft();
2561 bool visual = eng->visualCursorMovement();
2563 int end = qMin(lineEnd, si->position + l) - si->position;
2564 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2565 int glyph_start = glyph_pos;
2566 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
2568 for (int i = glyph_end - 1; i >= glyph_start; i--)
2569 x += glyphs.effectiveAdvance(i);
2571 int start = qMax(line.from - si->position, 0);
2572 int glyph_start = logClusters[start];
2573 int glyph_end = glyph_pos;
2574 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
2576 for (int i = glyph_start; i <= glyph_end; i++)
2577 x += glyphs.effectiveAdvance(i);
2579 x += eng->offsetInLigature(si, pos, line.length, glyph_pos);
2582 *cursorPos = pos + si->position;
2587 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2589 Converts the x-coordinate \a x, to the nearest matching cursor
2590 position, depending on the cursor position type, \a cpos.
2594 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2596 QFixed x = QFixed::fromReal(_x);
2597 const QScriptLine &line = eng->lines[i];
2598 bool lastLine = i >= eng->lines.size() - 1;
2601 if (!eng->layoutData)
2604 int line_length = textLength();
2609 int firstItem = eng->findItem(line.from);
2610 int lastItem = eng->findItem(line.from + line_length - 1);
2611 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2617 x -= eng->alignLine(line);
2618 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2620 QVarLengthArray<int> visualOrder(nItems);
2621 QVarLengthArray<unsigned char> levels(nItems);
2622 for (int i = 0; i < nItems; ++i)
2623 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2624 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2626 bool visual = eng->visualCursorMovement();
2628 // left of first item
2629 int item = visualOrder[0]+firstItem;
2630 QScriptItem &si = eng->layoutData->items[item];
2633 int pos = si.position;
2634 if (si.analysis.bidiLevel % 2)
2635 pos += eng->length(item);
2636 pos = qMax(line.from, pos);
2637 pos = qMin(line.from + line_length, pos);
2639 } else if (x < line.textWidth
2640 || (line.justified && x < line.width)) {
2641 // has to be in one of the runs
2643 bool rtl = eng->isRightToLeft();
2645 eng->shapeLine(line);
2646 QVector<int> insertionPoints;
2648 eng->insertionPointsForLine(lineNum, insertionPoints);
2650 for (int i = 0; i < nItems; ++i) {
2651 int item = visualOrder[i]+firstItem;
2652 QScriptItem &si = eng->layoutData->items[item];
2653 int item_length = eng->length(item);
2654 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2656 int start = qMax(line.from - si.position, 0);
2657 int end = qMin(line.from + line_length - si.position, item_length);
2659 unsigned short *logClusters = eng->logClusters(&si);
2661 int gs = logClusters[start];
2662 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2663 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2665 QFixed item_width = 0;
2666 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2667 item_width = si.width;
2671 item_width += glyphs.effectiveAdvance(g);
2675 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2677 if (pos + item_width < x) {
2682 // qDebug(" inside run");
2683 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2684 if (cpos == QTextLine::CursorOnCharacter)
2686 bool left_half = (x - pos) < item_width/2;
2688 if (bool(si.analysis.bidiLevel % 2) != left_half)
2690 return si.position + 1;
2694 // has to be inside run
2695 if (cpos == QTextLine::CursorOnCharacter) {
2696 if (si.analysis.bidiLevel % 2) {
2700 if (glyphs.attributes[gs].clusterStart) {
2706 pos -= glyphs.effectiveAdvance(gs);
2712 if (glyphs.attributes[gs].clusterStart) {
2717 pos += glyphs.effectiveAdvance(gs);
2722 QFixed dist = INT_MAX/256;
2723 if (si.analysis.bidiLevel % 2) {
2724 if (!visual || rtl || (lastLine && i == nItems - 1)) {
2727 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2731 pos -= glyphs.effectiveAdvance(gs);
2736 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
2740 pos += glyphs.effectiveAdvance(ge);
2745 if (!visual || !rtl || (lastLine && i == 0)) {
2747 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2751 pos += glyphs.effectiveAdvance(gs);
2755 QFixed oldPos = pos;
2757 pos += glyphs.effectiveAdvance(gs);
2758 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2767 if (qAbs(x-pos) < dist) {
2769 if (!rtl && i < nItems - 1) {
2773 if (rtl && nchars > 0)
2774 return insertionPoints[lastLine ? nchars : nchars - 1];
2776 return si.position + end;
2779 Q_ASSERT(glyph_pos != -1);
2781 for (j = 0; j < eng->length(item); ++j)
2782 if (logClusters[j] == glyph_pos)
2784 // qDebug("at pos %d (in run: %d)", si.position + j, j);
2785 return si.position + j;
2788 // right of last item
2789 // qDebug() << "right of last";
2790 int item = visualOrder[nItems-1]+firstItem;
2791 QScriptItem &si = eng->layoutData->items[item];
2794 int pos = si.position;
2795 if (!(si.analysis.bidiLevel % 2))
2796 pos += eng->length(item);
2797 pos = qMax(line.from, pos);
2799 int maxPos = line.from + line_length;
2801 // except for the last line we assume that the
2802 // character between lines is a space and we want
2803 // to position the cursor to the left of that
2805 // ###### breaks with japanese for example
2806 if (this->i < eng->lines.count() - 1)
2809 pos = qMin(pos, maxPos);