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"
56 #include "qglyphs_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 visual 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 QTextCursor::Logical.
587 \sa setCursorMoveStyle()
589 void QTextLayout::setCursorMoveStyle(QTextCursor::MoveStyle style)
591 d->visualMovement = style == QTextCursor::Visual ? true : false;
595 The cursor movement style of this QTextLayout. The default is
596 QTextCursor::Logical.
598 \sa setCursorMoveStyle()
600 QTextCursor::MoveStyle QTextLayout::cursorMoveStyle() const
602 return d->visualMovement ? QTextCursor::Visual : QTextCursor::Logical;
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::drawGlyphs()
999 #if !defined(QT_NO_RAWFONT)
1000 QList<QGlyphs> QTextLayout::glyphs() const
1002 QList<QGlyphs> 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;
1214 QFixed pos_x = QFixed::fromReal(position.x());
1215 QFixed pos_y = QFixed::fromReal(position.y());
1217 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1218 int line = d->lineNumberForTextPosition(cursorPosition);
1221 if (line >= d->lines.size())
1224 QTextLine l(line, d);
1225 const QScriptLine &sl = d->lines[line];
1227 qreal x = position.x() + l.cursorToX(cursorPosition);
1231 if (d->visualCursorMovement()) {
1232 if (cursorPosition == sl.from + sl.length)
1234 itm = d->findItem(cursorPosition);
1236 itm = d->findItem(cursorPosition - 1);
1238 QFixed base = sl.base();
1239 QFixed descent = sl.descent;
1240 bool rightToLeft = d->isRightToLeft();
1242 const QScriptItem &si = d->layoutData->items.at(itm);
1246 descent = si.descent;
1247 rightToLeft = si.analysis.bidiLevel % 2;
1249 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1250 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1251 && (p->transform().type() > QTransform::TxTranslate);
1252 if (toggleAntialiasing)
1253 p->setRenderHint(QPainter::Antialiasing);
1254 #if defined(QT_MAC_USE_COCOA)
1255 // Always draw the cursor aligned to pixel boundary.
1258 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1259 if (toggleAntialiasing)
1260 p->setRenderHint(QPainter::Antialiasing, false);
1261 if (d->layoutData->hasBidi) {
1262 const int arrow_extent = 4;
1263 int sign = rightToLeft ? -1 : 1;
1264 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1265 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1274 \brief The QTextLine class represents a line of text inside a QTextLayout.
1276 \ingroup richtext-processing
1278 A text line is usually created by QTextLayout::createLine().
1280 After being created, the line can be filled using the setLineWidth()
1281 or setNumColumns() functions. A line has a number of attributes including the
1282 rectangle it occupies, rect(), its coordinates, x() and y(), its
1283 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1284 relative to the text. The position of the cursor in terms of the
1285 line is available from cursorToX() and its inverse from
1286 xToCursor(). A line can be moved with setPosition().
1290 \enum QTextLine::Edge
1297 \enum QTextLine::CursorPosition
1299 \value CursorBetweenCharacters
1300 \value CursorOnCharacter
1304 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1307 Constructs a new text line using the line at position \a line in
1308 the text engine \a e.
1312 \fn QTextLine::QTextLine()
1314 Creates an invalid line.
1318 \fn bool QTextLine::isValid() const
1320 Returns true if this text line is valid; otherwise returns false.
1324 \fn int QTextLine::lineNumber() const
1326 Returns the position of the line in the text engine.
1331 Returns the line's bounding rectangle.
1333 \sa x(), y(), textLength(), width()
1335 QRectF QTextLine::rect() const
1337 const QScriptLine& sl = eng->lines[i];
1338 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1342 Returns the rectangle covered by the line.
1344 QRectF QTextLine::naturalTextRect() const
1346 const QScriptLine& sl = eng->lines[i];
1347 QFixed x = sl.x + eng->alignLine(sl);
1349 QFixed width = sl.textWidth;
1353 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1357 Returns the line's x position.
1359 \sa rect(), y(), textLength(), width()
1361 qreal QTextLine::x() const
1363 return eng->lines[i].x.toReal();
1367 Returns the line's y position.
1369 \sa x(), rect(), textLength(), width()
1371 qreal QTextLine::y() const
1373 return eng->lines[i].y.toReal();
1377 Returns the line's width as specified by the layout() function.
1379 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1381 qreal QTextLine::width() const
1383 return eng->lines[i].width.toReal();
1388 Returns the line's ascent.
1390 \sa descent(), height()
1392 qreal QTextLine::ascent() const
1394 return eng->lines[i].ascent.toReal();
1398 Returns the line's descent.
1400 \sa ascent(), height()
1402 qreal QTextLine::descent() const
1404 return eng->lines[i].descent.toReal();
1408 Returns the line's height. This is equal to ascent() + descent() + 1
1409 if leading is not included. If leading is included, this equals to
1410 ascent() + descent() + leading() + 1.
1412 \sa ascent(), descent(), leading(), setLeadingIncluded()
1414 qreal QTextLine::height() const
1416 return eng->lines[i].height().toReal();
1422 Returns the line's leading.
1424 \sa ascent(), descent(), height()
1426 qreal QTextLine::leading() const
1428 return eng->lines[i].leading.toReal();
1434 Includes positive leading into the line's height if \a included is true;
1435 otherwise does not include leading.
1437 By default, leading is not included.
1439 Note that negative leading is ignored, it must be handled
1440 in the code using the text lines by letting the lines overlap.
1442 \sa leadingIncluded()
1445 void QTextLine::setLeadingIncluded(bool included)
1447 eng->lines[i].leadingIncluded= included;
1454 Returns true if positive leading is included into the line's height;
1455 otherwise returns false.
1457 By default, leading is not included.
1459 \sa setLeadingIncluded()
1461 bool QTextLine::leadingIncluded() const
1463 return eng->lines[i].leadingIncluded;
1467 Returns the width of the line that is occupied by text. This is
1468 always \<= to width(), and is the minimum width that could be used
1469 by layout() without changing the line break position.
1471 qreal QTextLine::naturalTextWidth() const
1473 return eng->lines[i].textWidth.toReal();
1478 Returns the horizontal advance of the text. The advance of the text
1479 is the distance from its position to the next position at which
1480 text would naturally be drawn.
1482 By adding the advance to the position of the text line and using this
1483 as the position of a second text line, you will be able to position
1484 the two lines side-by-side without gaps in-between.
1486 qreal QTextLine::horizontalAdvance() const
1488 return eng->lines[i].textAdvance.toReal();
1492 Lays out the line with the given \a width. The line is filled from
1493 its starting position with as many characters as will fit into
1494 the line. In case the text cannot be split at the end of the line,
1495 it will be filled with additional characters to the next whitespace
1498 void QTextLine::setLineWidth(qreal width)
1500 QScriptLine &line = eng->lines[i];
1501 if (!eng->layoutData) {
1502 qWarning("QTextLine: Can't set a line width while not layouting.");
1506 if (width > QFIXED_MAX)
1509 line.width = QFixed::fromReal(width);
1511 && line.textWidth <= line.width
1512 && line.from + line.length == eng->layoutData->string.length())
1513 // no need to do anything if the line is already layouted and the last one. This optimization helps
1514 // when using things in a single line layout.
1519 layout_helper(INT_MAX);
1523 Lays out the line. The line is filled from its starting position
1524 with as many characters as are specified by \a numColumns. In case
1525 the text cannot be split until \a numColumns characters, the line
1526 will be filled with as many characters to the next whitespace or
1529 void QTextLine::setNumColumns(int numColumns)
1531 QScriptLine &line = eng->lines[i];
1532 line.width = QFIXED_MAX;
1535 layout_helper(numColumns);
1539 Lays out the line. The line is filled from its starting position
1540 with as many characters as are specified by \a numColumns. In case
1541 the text cannot be split until \a numColumns characters, the line
1542 will be filled with as many characters to the next whitespace or
1543 end of the text. The provided \a alignmentWidth is used as reference
1544 width for alignment.
1546 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1548 QScriptLine &line = eng->lines[i];
1549 line.width = QFixed::fromReal(alignmentWidth);
1552 layout_helper(numColumns);
1556 #define LB_DEBUG qDebug
1558 #define LB_DEBUG if (0) qDebug
1563 struct LineBreakHelper
1566 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1567 manualWrap(false), whiteSpaceOrObject(true)
1572 QScriptLine tmpData;
1573 QScriptLine spaceData;
1575 QGlyphLayout glyphs;
1579 int currentPosition;
1580 glyph_t previousGlyph;
1583 QFixed softHyphenWidth;
1584 QFixed rightBearing;
1585 QFixed minimumRightBearing;
1587 QFontEngine *fontEngine;
1588 const unsigned short *logClusters;
1591 bool whiteSpaceOrObject;
1593 bool checkFullOtherwiseExtend(QScriptLine &line);
1595 QFixed calculateNewWidth(const QScriptLine &line) const {
1596 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1597 - qMin(rightBearing, QFixed());
1600 inline glyph_t currentGlyph() const
1602 Q_ASSERT(currentPosition > 0);
1603 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1605 return glyphs.glyphs[logClusters[currentPosition - 1]];
1608 inline void saveCurrentGlyph()
1611 if (currentPosition > 0 &&
1612 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1613 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1617 inline void adjustRightBearing(glyph_t glyph)
1620 fontEngine->getGlyphBearings(glyph, 0, &rb);
1621 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1624 inline void adjustRightBearing()
1626 if (currentPosition <= 0)
1628 adjustRightBearing(currentGlyph());
1631 inline void adjustPreviousRightBearing()
1633 if (previousGlyph > 0)
1634 adjustRightBearing(previousGlyph);
1637 inline void resetRightBearing()
1639 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1640 // negative right bearings are interesting to us.
1644 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1646 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1648 QFixed newWidth = calculateNewWidth(line);
1649 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1652 minw = qMax(minw, tmpData.textWidth);
1654 line.textWidth += spaceData.textWidth;
1656 line.length += spaceData.length;
1657 tmpData.textWidth = 0;
1659 spaceData.textWidth = 0;
1660 spaceData.length = 0;
1665 } // anonymous namespace
1668 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1669 const QScriptItem ¤t, const unsigned short *logClusters,
1670 const QGlyphLayout &glyphs)
1672 int glyphPosition = logClusters[pos];
1673 do { // got to the first next cluster
1676 } while (pos < end && logClusters[pos] == glyphPosition);
1677 do { // calculate the textWidth for the rest of the current cluster.
1678 if (!glyphs.attributes[glyphPosition].dontPrint)
1679 line.textWidth += glyphs.advances_x[glyphPosition];
1681 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1683 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1690 void QTextLine::layout_helper(int maxGlyphs)
1692 QScriptLine &line = eng->lines[i];
1695 line.hasTrailingSpaces = false;
1697 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1698 line.setDefaultHeight(eng);
1702 Q_ASSERT(line.from < eng->layoutData->string.length());
1704 LineBreakHelper lbh;
1706 lbh.maxGlyphs = maxGlyphs;
1708 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1709 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1710 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1713 int newItem = eng->findItem(line.from);
1715 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1717 Qt::Alignment alignment = eng->option.alignment();
1719 const HB_CharAttributes *attributes = eng->attributes();
1722 lbh.currentPosition = line.from;
1724 lbh.logClusters = eng->layoutData->logClustersPtr;
1725 lbh.previousGlyph = 0;
1727 while (newItem < eng->layoutData->items.size()) {
1728 lbh.resetRightBearing();
1729 lbh.softHyphenWidth = 0;
1730 if (newItem != item) {
1732 const QScriptItem ¤t = eng->layoutData->items[item];
1733 if (!current.num_glyphs) {
1735 attributes = eng->attributes();
1738 lbh.logClusters = eng->layoutData->logClustersPtr;
1740 lbh.currentPosition = qMax(line.from, current.position);
1741 end = current.position + eng->length(item);
1742 lbh.glyphs = eng->shapedGlyphs(¤t);
1743 QFontEngine *fontEngine = eng->fontEngine(current);
1744 if (lbh.fontEngine != fontEngine) {
1745 lbh.fontEngine = fontEngine;
1746 lbh.minimumRightBearing = qMin(QFixed(),
1747 QFixed::fromReal(fontEngine->minRightBearing()));
1750 const QScriptItem ¤t = eng->layoutData->items[item];
1752 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1753 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1755 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1756 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1758 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1759 lbh.whiteSpaceOrObject = true;
1760 if (lbh.checkFullOtherwiseExtend(line))
1763 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1764 QFixed tabWidth = eng->calculateTabWidth(item, x);
1766 lbh.spaceData.textWidth += tabWidth;
1767 lbh.spaceData.length++;
1770 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1771 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1773 if (lbh.checkFullOtherwiseExtend(line))
1775 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1776 lbh.whiteSpaceOrObject = true;
1777 // if the line consists only of the line separator make sure
1778 // we have a sane height
1779 if (!line.length && !lbh.tmpData.length)
1780 line.setDefaultHeight(eng);
1781 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1782 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1783 current, lbh.logClusters, lbh.glyphs);
1785 lbh.tmpData.length++;
1786 lbh.adjustPreviousRightBearing();
1788 line += lbh.tmpData;
1790 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1791 lbh.whiteSpaceOrObject = true;
1792 lbh.tmpData.length++;
1794 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1795 if (eng->block.docHandle())
1796 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1798 lbh.tmpData.textWidth += current.width;
1802 if (lbh.checkFullOtherwiseExtend(line))
1804 } else if (attributes[lbh.currentPosition].whiteSpace) {
1805 lbh.whiteSpaceOrObject = true;
1806 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1807 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1808 current, lbh.logClusters, lbh.glyphs);
1810 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1811 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1815 lbh.whiteSpaceOrObject = false;
1816 bool sb_or_ws = false;
1817 lbh.saveCurrentGlyph();
1819 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1820 current, lbh.logClusters, lbh.glyphs);
1822 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1825 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1828 } while (lbh.currentPosition < end);
1829 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1831 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1832 // if we are splitting up a word because of
1833 // a soft hyphen then we ...
1835 // a) have to take the width of the soft hyphen into
1836 // account to see if the first syllable(s) /and/
1837 // the soft hyphen fit into the line
1839 // b) if we are so short of available width that the
1840 // soft hyphen is the first breakable position, then
1841 // we don't want to show it. However we initially
1842 // have to take the width for it into account so that
1843 // the text document layout sees the overflow and
1844 // switch to break-anywhere mode, in which we
1845 // want the soft-hyphen to slip into the next line
1846 // and thus become invisible again.
1849 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1851 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1854 // The actual width of the text needs to take the right bearing into account. The
1855 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1856 // of the advance of the glyph, the bearing will be negative. We flip the sign
1857 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1858 // We ignore the right bearing if the minimum negative bearing is too little to
1859 // expand the text beyond the edge.
1860 if (sb_or_ws|breakany) {
1861 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1862 #if !defined(Q_WS_MAC)
1863 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1865 lbh.adjustRightBearing();
1866 if (lbh.checkFullOtherwiseExtend(line)) {
1867 // we are too wide, fix right bearing
1868 if (rightBearing <= 0)
1869 lbh.rightBearing = rightBearing; // take from cache
1871 lbh.adjustPreviousRightBearing();
1874 line.textWidth += lbh.softHyphenWidth;
1880 lbh.saveCurrentGlyph();
1882 if (lbh.currentPosition == end)
1885 LB_DEBUG("reached end of line");
1886 lbh.checkFullOtherwiseExtend(line);
1888 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1889 lbh.adjustRightBearing();
1890 line.textAdvance = line.textWidth;
1891 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1893 if (line.length == 0) {
1894 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1895 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1896 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1897 line += lbh.tmpData;
1900 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1901 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1902 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1904 if (lbh.manualWrap) {
1905 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1906 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1908 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1909 eng->maxWidth += line.textWidth;
1912 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1913 eng->maxWidth += lbh.spaceData.textWidth;
1914 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1915 line.textWidth += lbh.spaceData.textWidth;
1916 if (lbh.spaceData.length) {
1917 line.length += lbh.spaceData.length;
1918 line.hasTrailingSpaces = true;
1921 line.justified = false;
1922 line.gridfitted = false;
1924 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1925 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1926 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1928 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1931 layout_helper(lbh.maxGlyphs);
1932 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1938 Moves the line to position \a pos.
1940 void QTextLine::setPosition(const QPointF &pos)
1942 eng->lines[i].x = QFixed::fromReal(pos.x());
1943 eng->lines[i].y = QFixed::fromReal(pos.y());
1947 Returns the line's position relative to the text layout's position.
1949 QPointF QTextLine::position() const
1951 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1954 // ### DOC: I have no idea what this means/does.
1955 // You create a text layout with a string of text. Once you laid
1956 // it out, it contains a number of QTextLines. from() returns the position
1957 // inside the text string where this line starts. If you e.g. has a
1958 // text of "This is a string", laid out into two lines (the second
1959 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
1960 // layout.lineAt(1).from() == 8.
1962 Returns the start of the line from the beginning of the string
1963 passed to the QTextLayout.
1965 int QTextLine::textStart() const
1967 return eng->lines[i].from;
1971 Returns the length of the text in the line.
1973 \sa naturalTextWidth()
1975 int QTextLine::textLength() const
1977 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1978 && eng->block.isValid() && i == eng->lines.count()-1) {
1979 return eng->lines[i].length - 1;
1981 return eng->lines[i].length;
1984 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1985 int start, int glyph_start)
1987 int ge = glyph_start + gf.glyphs.numGlyphs;
1988 int gs = glyph_start;
1989 int end = start + gf.num_chars;
1990 unsigned short *logClusters = eng->logClusters(&si);
1991 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
1992 QFixed orig_width = gf.width;
1994 int *ul = eng->underlinePositions;
1996 while (*ul != -1 && *ul < start)
1998 bool rtl = si.analysis.bidiLevel % 2;
2005 if (ul && *ul != -1 && *ul < end) {
2007 gtmp = logClusters[*ul-si.position];
2010 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2011 gf.num_chars = stmp - start;
2012 gf.chars = eng->layoutData->string.unicode() + start;
2015 w += glyphs.effectiveAdvance(gs);
2023 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2026 if (ul && *ul != -1 && *ul < end) {
2028 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2030 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2031 gf.num_chars = stmp - start;
2032 gf.chars = eng->layoutData->string.unicode() + start;
2033 gf.logClusters = logClusters + start - si.position;
2036 w += glyphs.effectiveAdvance(gs);
2041 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2044 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2047 gf.underlineStyle = QTextCharFormat::NoUnderline;
2053 gf.width = orig_width;
2057 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2059 QBrush c = chf.foreground();
2060 if (c.style() == Qt::NoBrush) {
2061 p->setPen(defaultPen);
2064 QBrush bg = chf.background();
2065 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2067 if (c.style() != Qt::NoBrush) {
2068 p->setPen(QPen(c, 0));
2076 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2077 const QTextItemInt::RenderFlags &renderFlags)
2078 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2082 QGlyphLayout glyphLayout;
2083 QPointF itemPosition;
2084 QTextItem::RenderFlags flags;
2091 Returns the glyph indexes and positions for all glyphs in this QTextLine which reside in
2092 QScriptItems that overlap with the range defined by \a from and \a length. The arguments
2093 specify characters, relative to the text in the layout. Note that it is not possible to
2094 use this function to retrieve a subset of the glyphs in a QScriptItem.
2098 \sa QTextLayout::glyphs()
2100 #if !defined(QT_NO_RAWFONT)
2101 QList<QGlyphs> QTextLine::glyphs(int from, int length) const
2103 const QScriptLine &line = eng->lines[i];
2105 if (line.length == 0)
2106 return QList<QGlyphs>();
2108 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2110 QTextLineItemIterator iterator(eng, i);
2111 qreal y = line.y.toReal() + line.base().toReal();
2112 while (!iterator.atEnd()) {
2113 QScriptItem &si = iterator.next();
2114 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2117 QPointF pos(iterator.x.toReal(), y);
2118 if (from >= 0 && length >= 0 &&
2119 (from >= si.position + eng->length(&si) || from + length <= si.position))
2122 QFont font = eng->font(si);
2124 QTextItem::RenderFlags flags;
2125 if (font.overline())
2126 flags |= QTextItem::Overline;
2127 if (font.underline())
2128 flags |= QTextItem::Underline;
2129 if (font.strikeOut())
2130 flags |= QTextItem::StrikeOut;
2131 if (si.analysis.bidiLevel % 2)
2132 flags |= QTextItem::RightToLeft;
2134 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si).mid(iterator.glyphsStart,
2135 iterator.glyphsEnd - iterator.glyphsStart);
2137 if (glyphLayout.numGlyphs > 0) {
2138 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2139 if (mainFontEngine->type() == QFontEngine::Multi) {
2140 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2143 int which = glyphLayout.glyphs[0] >> 24;
2144 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2145 const int e = glyphLayout.glyphs[end] >> 24;
2149 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2150 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2151 GlyphInfo(subLayout, pos, flags));
2152 for (int i = 0; i < subLayout.numGlyphs; i++)
2153 pos += QPointF(subLayout.advances_x[i].toReal(),
2154 subLayout.advances_y[i].toReal());
2160 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2161 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2162 GlyphInfo(subLayout, pos, flags));
2165 glyphLayoutHash.insertMulti(mainFontEngine,
2166 GlyphInfo(glyphLayout, pos, flags));
2171 QHash<QPair<QFontEngine *, int>, QGlyphs> glyphsHash;
2173 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2174 for (int i=0; i<keys.size(); ++i) {
2175 QFontEngine *fontEngine = keys.at(i);
2177 // Make a font for this particular engine
2179 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2180 fontD->fontEngine = fontEngine;
2181 fontD->fontEngine->ref.ref();
2183 #if defined(Q_WS_WIN)
2184 if (fontEngine->supportsSubPixelPositions())
2185 fontD->hintingPreference = QFont::PreferVerticalHinting;
2187 fontD->hintingPreference = QFont::PreferFullHinting;
2188 #elif defined(Q_WS_MAC)
2189 fontD->hintingPreference = QFont::PreferNoHinting;
2190 #elif !defined(QT_NO_FREETYPE)
2191 if (fontEngine->type() == QFontEngine::Freetype) {
2192 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2193 switch (freeTypeEngine->defaultHintStyle()) {
2194 case QFontEngineFT::HintNone:
2195 fontD->hintingPreference = QFont::PreferNoHinting;
2197 case QFontEngineFT::HintLight:
2198 fontD->hintingPreference = QFont::PreferVerticalHinting;
2200 case QFontEngineFT::HintMedium:
2201 case QFontEngineFT::HintFull:
2202 fontD->hintingPreference = QFont::PreferFullHinting;
2208 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2209 for (int j=0; j<glyphLayouts.size(); ++j) {
2210 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2211 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2212 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2214 QVarLengthArray<glyph_t> glyphsArray;
2215 QVarLengthArray<QFixedPoint> positionsArray;
2217 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2219 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2221 QVector<quint32> glyphs;
2222 QVector<QPointF> positions;
2223 for (int i=0; i<glyphsArray.size(); ++i) {
2224 glyphs.append(glyphsArray.at(i) & 0xffffff);
2225 positions.append(positionsArray.at(i).toPointF() + pos);
2228 QGlyphs glyphIndexes;
2229 glyphIndexes.setGlyphIndexes(glyphs);
2230 glyphIndexes.setPositions(positions);
2232 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2233 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2234 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2235 glyphIndexes.setFont(font);
2237 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2238 if (!glyphsHash.contains(key))
2239 glyphsHash.insert(key, glyphIndexes);
2241 glyphsHash[key] += glyphIndexes;
2245 return glyphsHash.values();
2247 #endif // QT_NO_RAWFONT
2250 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2252 Draws a line on the given \a painter at the specified \a position.
2253 The \a selection is reserved for internal use.
2255 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2257 const QScriptLine &line = eng->lines[i];
2258 QPen pen = p->pen();
2260 bool noText = (selection && selection->format.property(SuppressText).toBool());
2264 && selection->start <= line.from
2265 && selection->start + selection->length > line.from) {
2267 const qreal lineHeight = line.height().toReal();
2268 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2269 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2270 setPenAndDrawBackground(p, QPen(), selection->format, r);
2277 QTextLineItemIterator iterator(eng, i, pos, selection);
2278 QFixed lineBase = line.base();
2280 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2282 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2283 while (!iterator.atEnd()) {
2284 QScriptItem &si = iterator.next();
2286 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2289 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2290 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2293 QFixed itemBaseLine = y;
2294 QFont f = eng->font(si);
2295 QTextCharFormat format;
2297 if (eng->hasFormats() || selection) {
2298 format = eng->format(&si);
2299 if (suppressColors) {
2300 format.clearForeground();
2301 format.clearBackground();
2302 format.clearProperty(QTextFormat::TextUnderlineColor);
2305 format.merge(selection->format);
2307 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2308 iterator.itemWidth.toReal(), line.height().toReal()));
2310 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2311 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2312 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2313 QFixed height = fe->ascent() + fe->descent();
2314 if (valign == QTextCharFormat::AlignSubScript)
2315 itemBaseLine += height / 6;
2316 else if (valign == QTextCharFormat::AlignSuperScript)
2317 itemBaseLine -= height / 2;
2321 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2323 if (eng->hasFormats()) {
2325 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2326 QFixed itemY = y - si.ascent;
2327 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2328 itemY = y - lineBase;
2331 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2333 eng->docLayout()->drawInlineObject(p, itemRect,
2334 QTextInlineObject(iterator.item, eng),
2335 si.position + eng->block.position(),
2338 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2339 if (bg.style() != Qt::NoBrush) {
2340 QColor c = bg.color();
2342 p->fillRect(itemRect, c);
2345 } else { // si.isTab
2346 QFont f = eng->font(si);
2347 QTextItemInt gf(si, &f, format);
2350 gf.width = iterator.itemWidth;
2351 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2352 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2353 QChar visualTab(0x2192);
2354 int w = QFontMetrics(f).width(visualTab);
2355 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2357 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2358 iterator.itemWidth.toReal(), line.height().toReal()),
2362 p->drawText(QPointF(iterator.x.toReal() + x,
2363 y.toReal()), visualTab);
2373 unsigned short *logClusters = eng->logClusters(&si);
2374 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2376 QTextItemInt gf(si, &f, format);
2377 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2378 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2379 gf.logClusters = logClusters + iterator.itemStart - si.position;
2380 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2381 gf.width = iterator.itemWidth;
2382 gf.justified = line.justified;
2384 Q_ASSERT(gf.fontEngine);
2386 if (eng->underlinePositions) {
2387 // can't have selections in this case
2388 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2390 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2391 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2393 path.setFillRule(Qt::WindingFill);
2395 if (gf.glyphs.numGlyphs)
2396 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2398 const QFontEngine *fe = gf.fontEngine;
2399 const qreal lw = fe->lineThickness().toReal();
2400 if (gf.flags & QTextItem::Underline) {
2401 qreal offs = fe->underlinePosition().toReal();
2402 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2404 if (gf.flags & QTextItem::Overline) {
2405 qreal offs = fe->ascent().toReal() + 1;
2406 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2408 if (gf.flags & QTextItem::StrikeOut) {
2409 qreal offs = fe->ascent().toReal() / 3;
2410 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2415 p->setRenderHint(QPainter::Antialiasing);
2416 //Currently QPen with a Qt::NoPen style still returns a default
2417 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2418 if (p->pen().style() == Qt::NoPen)
2419 p->setBrush(Qt::NoBrush);
2421 p->setBrush(p->pen().brush());
2423 p->setPen(format.textOutline());
2428 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2429 p->drawTextItem(pos, gf);
2432 if (si.analysis.flags == QScriptAnalysis::Space
2433 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2434 QBrush c = format.foreground();
2435 if (c.style() != Qt::NoBrush)
2436 p->setPen(c.color());
2437 QChar visualSpace((ushort)0xb7);
2438 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2444 if (eng->hasFormats())
2449 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2455 Converts the cursor position \a cursorPos to the corresponding x position
2456 inside the line, taking account of the \a edge.
2458 If \a cursorPos is not a valid cursor position, the nearest valid
2459 cursor position will be used instead, and cpos will be modified to
2460 point to this valid cursor position.
2464 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2466 if (!eng->layoutData)
2469 const QScriptLine &line = eng->lines[i];
2470 bool lastLine = i >= eng->lines.size() - 1;
2473 x += eng->alignLine(line);
2475 if (!i && !eng->layoutData->items.size()) {
2480 int pos = *cursorPos;
2482 if (pos == line.from + (int)line.length) {
2483 // end of line ensure we have the last item on the line
2484 itm = eng->findItem(pos-1);
2487 itm = eng->findItem(pos);
2488 eng->shapeLine(line);
2490 const QScriptItem *si = &eng->layoutData->items[itm];
2491 if (!si->num_glyphs)
2493 pos -= si->position;
2495 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2496 unsigned short *logClusters = eng->logClusters(si);
2497 Q_ASSERT(logClusters);
2499 int l = eng->length(itm);
2505 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2506 if (edge == Trailing) {
2507 // trailing edge is leading edge of next cluster
2508 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2512 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2514 int lineEnd = line.from + line.length;
2516 // add the items left of the cursor
2518 int firstItem = eng->findItem(line.from);
2519 int lastItem = eng->findItem(lineEnd - 1);
2520 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2522 QVarLengthArray<int> visualOrder(nItems);
2523 QVarLengthArray<uchar> levels(nItems);
2524 for (int i = 0; i < nItems; ++i)
2525 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2526 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2528 for (int i = 0; i < nItems; ++i) {
2529 int item = visualOrder[i]+firstItem;
2532 QScriptItem &si = eng->layoutData->items[item];
2536 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2540 int start = qMax(line.from, si.position);
2541 int end = qMin(lineEnd, si.position + eng->length(item));
2543 logClusters = eng->logClusters(&si);
2545 int gs = logClusters[start-si.position];
2546 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2548 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2551 x += glyphs.effectiveAdvance(gs);
2556 logClusters = eng->logClusters(si);
2557 glyphs = eng->shapedGlyphs(si);
2558 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2559 if (pos == (reverse ? 0 : l))
2562 bool rtl = eng->isRightToLeft();
2563 bool visual = eng->visualCursorMovement();
2565 int end = qMin(lineEnd, si->position + l) - si->position;
2566 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2567 int glyph_start = glyph_pos;
2568 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
2570 for (int i = glyph_end - 1; i >= glyph_start; i--)
2571 x += glyphs.effectiveAdvance(i);
2573 int start = qMax(line.from - si->position, 0);
2574 int glyph_start = logClusters[start];
2575 int glyph_end = glyph_pos;
2576 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
2578 for (int i = glyph_start; i <= glyph_end; i++)
2579 x += glyphs.effectiveAdvance(i);
2581 x += eng->offsetInLigature(si, pos, line.length, glyph_pos);
2584 *cursorPos = pos + si->position;
2589 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2591 Converts the x-coordinate \a x, to the nearest matching cursor
2592 position, depending on the cursor position type, \a cpos.
2596 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2598 QFixed x = QFixed::fromReal(_x);
2599 const QScriptLine &line = eng->lines[i];
2600 bool lastLine = i >= eng->lines.size() - 1;
2603 if (!eng->layoutData)
2606 int line_length = textLength();
2611 int firstItem = eng->findItem(line.from);
2612 int lastItem = eng->findItem(line.from + line_length - 1);
2613 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2619 x -= eng->alignLine(line);
2620 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2622 QVarLengthArray<int> visualOrder(nItems);
2623 QVarLengthArray<unsigned char> levels(nItems);
2624 for (int i = 0; i < nItems; ++i)
2625 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2626 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2628 bool visual = eng->visualCursorMovement();
2630 // left of first item
2631 int item = visualOrder[0]+firstItem;
2632 QScriptItem &si = eng->layoutData->items[item];
2635 int pos = si.position;
2636 if (si.analysis.bidiLevel % 2)
2637 pos += eng->length(item);
2638 pos = qMax(line.from, pos);
2639 pos = qMin(line.from + line_length, pos);
2641 } else if (x < line.textWidth
2642 || (line.justified && x < line.width)) {
2643 // has to be in one of the runs
2645 bool rtl = eng->isRightToLeft();
2647 eng->shapeLine(line);
2648 QVector<int> insertionPoints;
2650 eng->insertionPointsForLine(lineNum, insertionPoints);
2652 for (int i = 0; i < nItems; ++i) {
2653 int item = visualOrder[i]+firstItem;
2654 QScriptItem &si = eng->layoutData->items[item];
2655 int item_length = eng->length(item);
2656 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2658 int start = qMax(line.from - si.position, 0);
2659 int end = qMin(line.from + line_length - si.position, item_length);
2661 unsigned short *logClusters = eng->logClusters(&si);
2663 int gs = logClusters[start];
2664 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2665 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2667 QFixed item_width = 0;
2668 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2669 item_width = si.width;
2673 item_width += glyphs.effectiveAdvance(g);
2677 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2679 if (pos + item_width < x) {
2684 // qDebug(" inside run");
2685 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2686 if (cpos == QTextLine::CursorOnCharacter)
2688 bool left_half = (x - pos) < item_width/2;
2690 if (bool(si.analysis.bidiLevel % 2) != left_half)
2692 return si.position + 1;
2696 // has to be inside run
2697 if (cpos == QTextLine::CursorOnCharacter) {
2698 if (si.analysis.bidiLevel % 2) {
2702 if (glyphs.attributes[gs].clusterStart) {
2708 pos -= glyphs.effectiveAdvance(gs);
2714 if (glyphs.attributes[gs].clusterStart) {
2719 pos += glyphs.effectiveAdvance(gs);
2724 QFixed dist = INT_MAX/256;
2725 if (si.analysis.bidiLevel % 2) {
2726 if (!visual || rtl || (lastLine && i == nItems - 1)) {
2729 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2733 pos -= glyphs.effectiveAdvance(gs);
2738 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
2742 pos += glyphs.effectiveAdvance(ge);
2747 if (!visual || !rtl || (lastLine && i == 0)) {
2749 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2753 pos += glyphs.effectiveAdvance(gs);
2757 QFixed oldPos = pos;
2759 pos += glyphs.effectiveAdvance(gs);
2760 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2769 if (qAbs(x-pos) < dist) {
2771 if (!rtl && i < nItems - 1) {
2775 if (rtl && nchars > 0)
2776 return insertionPoints[lastLine ? nchars : nchars - 1];
2778 return si.position + end;
2781 Q_ASSERT(glyph_pos != -1);
2783 for (j = 0; j < eng->length(item); ++j)
2784 if (logClusters[j] == glyph_pos)
2786 // qDebug("at pos %d (in run: %d)", si.position + j, j);
2787 return si.position + j;
2790 // right of last item
2791 // qDebug() << "right of last";
2792 int item = visualOrder[nItems-1]+firstItem;
2793 QScriptItem &si = eng->layoutData->items[item];
2796 int pos = si.position;
2797 if (!(si.analysis.bidiLevel % 2))
2798 pos += eng->length(item);
2799 pos = qMax(line.from, pos);
2801 int maxPos = line.from + line_length;
2803 // except for the last line we assume that the
2804 // character between lines is a space and we want
2805 // to position the cursor to the left of that
2807 // ###### breaks with japanese for example
2808 if (this->i < eng->lines.count() - 1)
2811 pos = qMin(pos, maxPos);