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(int from, int length) const
1005 length = text().length();
1007 QList<QGlyphRun> glyphs;
1008 for (int i=0; i<d->lines.size(); ++i) {
1009 if (d->lines[i].from > from + length)
1011 else if (d->lines[i].from + d->lines[i].length >= from)
1012 glyphs += QTextLine(i, d).glyphRuns(from, length);
1017 #endif // QT_NO_RAWFONT
1020 Draws the whole layout on the painter \a p at the position specified by \a pos.
1021 The rendered layout includes the given \a selections and is clipped within
1022 the rectangle specified by \a clip.
1024 void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
1026 if (d->lines.isEmpty())
1032 QPointF position = pos + d->position;
1034 QFixed clipy = (INT_MIN/256);
1035 QFixed clipe = (INT_MAX/256);
1036 if (clip.isValid()) {
1037 clipy = QFixed::fromReal(clip.y() - position.y());
1038 clipe = clipy + QFixed::fromReal(clip.height());
1042 int lastLine = d->lines.size();
1043 for (int i = 0; i < d->lines.size(); ++i) {
1045 const QScriptLine &sl = d->lines[i];
1051 if ((sl.y + sl.height()) < clipy) {
1057 QPainterPath excludedRegion;
1058 QPainterPath textDoneRegion;
1059 for (int i = 0; i < selections.size(); ++i) {
1060 FormatRange selection = selections.at(i);
1061 const QBrush bg = selection.format.background();
1063 QPainterPath region;
1064 region.setFillRule(Qt::WindingFill);
1066 for (int line = firstLine; line < lastLine; ++line) {
1067 const QScriptLine &sl = d->lines[line];
1068 QTextLine tl(line, d);
1070 QRectF lineRect(tl.naturalTextRect());
1071 lineRect.translate(position);
1072 lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0);
1074 bool isLastLineInBlock = (line == d->lines.size()-1);
1075 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1078 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1079 continue; // no actual intersection
1081 const bool selectionStartInLine = sl.from <= selection.start;
1082 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1084 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1085 addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip));
1087 region.addRect(clipIfValid(lineRect, clip));
1090 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1091 QRectF fullLineRect(tl.rect());
1092 fullLineRect.translate(position);
1093 fullLineRect.setRight(QFIXED_MAX);
1094 if (!selectionEndInLine)
1095 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1096 if (!selectionStartInLine)
1097 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1098 } else if (!selectionEndInLine
1099 && isLastLineInBlock
1100 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1101 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1102 lineRect.height()/4, lineRect.height()), clip));
1107 const QPen oldPen = p->pen();
1108 const QBrush oldBrush = p->brush();
1110 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1111 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1112 p->drawPath(region);
1115 p->setBrush(oldBrush);
1120 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1121 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1123 if (hasBackground) {
1124 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1125 // don't just clear the property, set an empty brush that overrides a potential
1126 // background brush specified in the text
1127 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1128 selection.format.clearProperty(QTextFormat::OutlinePen);
1131 selection.format.setProperty(SuppressText, !hasText);
1133 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1137 p->setClipPath(region, Qt::IntersectClip);
1139 for (int line = firstLine; line < lastLine; ++line) {
1140 QTextLine l(line, d);
1141 l.draw(p, position, &selection);
1146 textDoneRegion += region;
1149 textDoneRegion -= region;
1152 excludedRegion += region;
1155 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1156 if (!needsTextButNoBackground.isEmpty()){
1158 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1159 FormatRange selection;
1160 selection.start = 0;
1161 selection.length = INT_MAX;
1162 selection.format.setProperty(SuppressBackground, true);
1163 for (int line = firstLine; line < lastLine; ++line) {
1164 QTextLine l(line, d);
1165 l.draw(p, position, &selection);
1170 if (!excludedRegion.isEmpty()) {
1173 QRectF br = boundingRect().translated(position);
1174 br.setRight(QFIXED_MAX);
1176 br = br.intersected(clip);
1178 path -= excludedRegion;
1179 p->setClipPath(path, Qt::IntersectClip);
1182 for (int i = firstLine; i < lastLine; ++i) {
1184 l.draw(p, position);
1186 if (!excludedRegion.isEmpty())
1190 if (!d->cacheGlyphs)
1195 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1198 Draws a text cursor with the current pen at the given \a position using the
1199 \a painter specified.
1200 The corresponding position within the text is specified by \a cursorPosition.
1202 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1204 drawCursor(p, pos, cursorPosition, 1);
1208 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1210 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1211 \a painter specified.
1212 The corresponding position within the text is specified by \a cursorPosition.
1214 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1216 if (d->lines.isEmpty())
1222 QPointF position = pos + d->position;
1224 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1225 int line = d->lineNumberForTextPosition(cursorPosition);
1228 if (line >= d->lines.size())
1231 QTextLine l(line, d);
1232 const QScriptLine &sl = d->lines[line];
1234 qreal x = position.x() + l.cursorToX(cursorPosition);
1238 if (d->visualCursorMovement()) {
1239 if (cursorPosition == sl.from + sl.length)
1241 itm = d->findItem(cursorPosition);
1243 itm = d->findItem(cursorPosition - 1);
1245 QFixed base = sl.base();
1246 QFixed descent = sl.descent;
1247 bool rightToLeft = d->isRightToLeft();
1249 const QScriptItem &si = d->layoutData->items.at(itm);
1253 descent = si.descent;
1254 rightToLeft = si.analysis.bidiLevel % 2;
1256 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1257 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1258 && (p->transform().type() > QTransform::TxTranslate);
1259 if (toggleAntialiasing)
1260 p->setRenderHint(QPainter::Antialiasing);
1261 #if defined(QT_MAC_USE_COCOA)
1262 // Always draw the cursor aligned to pixel boundary.
1265 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1266 if (toggleAntialiasing)
1267 p->setRenderHint(QPainter::Antialiasing, false);
1268 if (d->layoutData->hasBidi) {
1269 const int arrow_extent = 4;
1270 int sign = rightToLeft ? -1 : 1;
1271 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1272 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1281 \brief The QTextLine class represents a line of text inside a QTextLayout.
1283 \ingroup richtext-processing
1285 A text line is usually created by QTextLayout::createLine().
1287 After being created, the line can be filled using the setLineWidth()
1288 or setNumColumns() functions. A line has a number of attributes including the
1289 rectangle it occupies, rect(), its coordinates, x() and y(), its
1290 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1291 relative to the text. The position of the cursor in terms of the
1292 line is available from cursorToX() and its inverse from
1293 xToCursor(). A line can be moved with setPosition().
1297 \enum QTextLine::Edge
1304 \enum QTextLine::CursorPosition
1306 \value CursorBetweenCharacters
1307 \value CursorOnCharacter
1311 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1314 Constructs a new text line using the line at position \a line in
1315 the text engine \a e.
1319 \fn QTextLine::QTextLine()
1321 Creates an invalid line.
1325 \fn bool QTextLine::isValid() const
1327 Returns true if this text line is valid; otherwise returns false.
1331 \fn int QTextLine::lineNumber() const
1333 Returns the position of the line in the text engine.
1338 Returns the line's bounding rectangle.
1340 \sa x(), y(), textLength(), width()
1342 QRectF QTextLine::rect() const
1344 const QScriptLine& sl = eng->lines[i];
1345 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1349 Returns the rectangle covered by the line.
1351 QRectF QTextLine::naturalTextRect() const
1353 const QScriptLine& sl = eng->lines[i];
1354 QFixed x = sl.x + eng->alignLine(sl);
1356 QFixed width = sl.textWidth;
1360 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1364 Returns the line's x position.
1366 \sa rect(), y(), textLength(), width()
1368 qreal QTextLine::x() const
1370 return eng->lines[i].x.toReal();
1374 Returns the line's y position.
1376 \sa x(), rect(), textLength(), width()
1378 qreal QTextLine::y() const
1380 return eng->lines[i].y.toReal();
1384 Returns the line's width as specified by the layout() function.
1386 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1388 qreal QTextLine::width() const
1390 return eng->lines[i].width.toReal();
1395 Returns the line's ascent.
1397 \sa descent(), height()
1399 qreal QTextLine::ascent() const
1401 return eng->lines[i].ascent.toReal();
1405 Returns the line's descent.
1407 \sa ascent(), height()
1409 qreal QTextLine::descent() const
1411 return eng->lines[i].descent.toReal();
1415 Returns the line's height. This is equal to ascent() + descent() + 1
1416 if leading is not included. If leading is included, this equals to
1417 ascent() + descent() + leading() + 1.
1419 \sa ascent(), descent(), leading(), setLeadingIncluded()
1421 qreal QTextLine::height() const
1423 return eng->lines[i].height().toReal();
1429 Returns the line's leading.
1431 \sa ascent(), descent(), height()
1433 qreal QTextLine::leading() const
1435 return eng->lines[i].leading.toReal();
1441 Includes positive leading into the line's height if \a included is true;
1442 otherwise does not include leading.
1444 By default, leading is not included.
1446 Note that negative leading is ignored, it must be handled
1447 in the code using the text lines by letting the lines overlap.
1449 \sa leadingIncluded()
1452 void QTextLine::setLeadingIncluded(bool included)
1454 eng->lines[i].leadingIncluded= included;
1461 Returns true if positive leading is included into the line's height;
1462 otherwise returns false.
1464 By default, leading is not included.
1466 \sa setLeadingIncluded()
1468 bool QTextLine::leadingIncluded() const
1470 return eng->lines[i].leadingIncluded;
1474 Returns the width of the line that is occupied by text. This is
1475 always \<= to width(), and is the minimum width that could be used
1476 by layout() without changing the line break position.
1478 qreal QTextLine::naturalTextWidth() const
1480 return eng->lines[i].textWidth.toReal();
1485 Returns the horizontal advance of the text. The advance of the text
1486 is the distance from its position to the next position at which
1487 text would naturally be drawn.
1489 By adding the advance to the position of the text line and using this
1490 as the position of a second text line, you will be able to position
1491 the two lines side-by-side without gaps in-between.
1493 qreal QTextLine::horizontalAdvance() const
1495 return eng->lines[i].textAdvance.toReal();
1499 Lays out the line with the given \a width. The line is filled from
1500 its starting position with as many characters as will fit into
1501 the line. In case the text cannot be split at the end of the line,
1502 it will be filled with additional characters to the next whitespace
1505 void QTextLine::setLineWidth(qreal width)
1507 QScriptLine &line = eng->lines[i];
1508 if (!eng->layoutData) {
1509 qWarning("QTextLine: Can't set a line width while not layouting.");
1513 if (width > QFIXED_MAX)
1516 line.width = QFixed::fromReal(width);
1518 && line.textWidth <= line.width
1519 && line.from + line.length == eng->layoutData->string.length())
1520 // no need to do anything if the line is already layouted and the last one. This optimization helps
1521 // when using things in a single line layout.
1526 layout_helper(INT_MAX);
1530 Lays out the line. The line is filled from its starting position
1531 with as many characters as are specified by \a numColumns. In case
1532 the text cannot be split until \a numColumns characters, the line
1533 will be filled with as many characters to the next whitespace or
1536 void QTextLine::setNumColumns(int numColumns)
1538 QScriptLine &line = eng->lines[i];
1539 line.width = QFIXED_MAX;
1542 layout_helper(numColumns);
1546 Lays out the line. The line is filled from its starting position
1547 with as many characters as are specified by \a numColumns. In case
1548 the text cannot be split until \a numColumns characters, the line
1549 will be filled with as many characters to the next whitespace or
1550 end of the text. The provided \a alignmentWidth is used as reference
1551 width for alignment.
1553 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1555 QScriptLine &line = eng->lines[i];
1556 line.width = QFixed::fromReal(alignmentWidth);
1559 layout_helper(numColumns);
1563 #define LB_DEBUG qDebug
1565 #define LB_DEBUG if (0) qDebug
1570 struct LineBreakHelper
1573 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1574 manualWrap(false), whiteSpaceOrObject(true)
1579 QScriptLine tmpData;
1580 QScriptLine spaceData;
1582 QGlyphLayout glyphs;
1586 int currentPosition;
1587 glyph_t previousGlyph;
1590 QFixed softHyphenWidth;
1591 QFixed rightBearing;
1592 QFixed minimumRightBearing;
1594 QFontEngine *fontEngine;
1595 const unsigned short *logClusters;
1598 bool whiteSpaceOrObject;
1600 bool checkFullOtherwiseExtend(QScriptLine &line);
1602 QFixed calculateNewWidth(const QScriptLine &line) const {
1603 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1604 - qMin(rightBearing, QFixed());
1607 inline glyph_t currentGlyph() const
1609 Q_ASSERT(currentPosition > 0);
1610 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1612 return glyphs.glyphs[logClusters[currentPosition - 1]];
1615 inline void saveCurrentGlyph()
1618 if (currentPosition > 0 &&
1619 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1620 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1624 inline void adjustRightBearing(glyph_t glyph)
1627 fontEngine->getGlyphBearings(glyph, 0, &rb);
1628 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1631 inline void adjustRightBearing()
1633 if (currentPosition <= 0)
1635 adjustRightBearing(currentGlyph());
1638 inline void adjustPreviousRightBearing()
1640 if (previousGlyph > 0)
1641 adjustRightBearing(previousGlyph);
1644 inline void resetRightBearing()
1646 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1647 // negative right bearings are interesting to us.
1651 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1653 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1655 QFixed newWidth = calculateNewWidth(line);
1656 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1659 minw = qMax(minw, tmpData.textWidth);
1661 line.textWidth += spaceData.textWidth;
1663 line.length += spaceData.length;
1664 tmpData.textWidth = 0;
1666 spaceData.textWidth = 0;
1667 spaceData.length = 0;
1672 } // anonymous namespace
1675 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1676 const QScriptItem ¤t, const unsigned short *logClusters,
1677 const QGlyphLayout &glyphs)
1679 int glyphPosition = logClusters[pos];
1680 do { // got to the first next cluster
1683 } while (pos < end && logClusters[pos] == glyphPosition);
1684 do { // calculate the textWidth for the rest of the current cluster.
1685 if (!glyphs.attributes[glyphPosition].dontPrint)
1686 line.textWidth += glyphs.advances_x[glyphPosition];
1688 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1690 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1697 void QTextLine::layout_helper(int maxGlyphs)
1699 QScriptLine &line = eng->lines[i];
1702 line.hasTrailingSpaces = false;
1704 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1705 line.setDefaultHeight(eng);
1709 Q_ASSERT(line.from < eng->layoutData->string.length());
1711 LineBreakHelper lbh;
1713 lbh.maxGlyphs = maxGlyphs;
1715 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1716 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1717 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1720 int newItem = eng->findItem(line.from);
1722 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1724 Qt::Alignment alignment = eng->option.alignment();
1726 const HB_CharAttributes *attributes = eng->attributes();
1729 lbh.currentPosition = line.from;
1731 lbh.logClusters = eng->layoutData->logClustersPtr;
1732 lbh.previousGlyph = 0;
1734 while (newItem < eng->layoutData->items.size()) {
1735 lbh.resetRightBearing();
1736 lbh.softHyphenWidth = 0;
1737 if (newItem != item) {
1739 const QScriptItem ¤t = eng->layoutData->items[item];
1740 if (!current.num_glyphs) {
1742 attributes = eng->attributes();
1745 lbh.logClusters = eng->layoutData->logClustersPtr;
1747 lbh.currentPosition = qMax(line.from, current.position);
1748 end = current.position + eng->length(item);
1749 lbh.glyphs = eng->shapedGlyphs(¤t);
1750 QFontEngine *fontEngine = eng->fontEngine(current);
1751 if (lbh.fontEngine != fontEngine) {
1752 lbh.fontEngine = fontEngine;
1753 lbh.minimumRightBearing = qMin(QFixed(),
1754 QFixed::fromReal(fontEngine->minRightBearing()));
1757 const QScriptItem ¤t = eng->layoutData->items[item];
1759 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1760 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1762 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1763 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1765 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1766 lbh.whiteSpaceOrObject = true;
1767 if (lbh.checkFullOtherwiseExtend(line))
1770 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1771 QFixed tabWidth = eng->calculateTabWidth(item, x);
1773 lbh.spaceData.textWidth += tabWidth;
1774 lbh.spaceData.length++;
1777 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1778 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1780 if (lbh.checkFullOtherwiseExtend(line))
1782 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1783 lbh.whiteSpaceOrObject = true;
1784 // if the line consists only of the line separator make sure
1785 // we have a sane height
1786 if (!line.length && !lbh.tmpData.length)
1787 line.setDefaultHeight(eng);
1788 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1789 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1790 current, lbh.logClusters, lbh.glyphs);
1792 lbh.tmpData.length++;
1793 lbh.adjustPreviousRightBearing();
1795 line += lbh.tmpData;
1797 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1798 lbh.whiteSpaceOrObject = true;
1799 lbh.tmpData.length++;
1801 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1802 if (eng->block.docHandle())
1803 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1805 lbh.tmpData.textWidth += current.width;
1809 if (lbh.checkFullOtherwiseExtend(line))
1811 } else if (attributes[lbh.currentPosition].whiteSpace) {
1812 lbh.whiteSpaceOrObject = true;
1813 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1814 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1815 current, lbh.logClusters, lbh.glyphs);
1817 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1818 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1822 lbh.whiteSpaceOrObject = false;
1823 bool sb_or_ws = false;
1824 lbh.saveCurrentGlyph();
1826 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1827 current, lbh.logClusters, lbh.glyphs);
1829 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1832 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1835 } while (lbh.currentPosition < end);
1836 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1838 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1839 // if we are splitting up a word because of
1840 // a soft hyphen then we ...
1842 // a) have to take the width of the soft hyphen into
1843 // account to see if the first syllable(s) /and/
1844 // the soft hyphen fit into the line
1846 // b) if we are so short of available width that the
1847 // soft hyphen is the first breakable position, then
1848 // we don't want to show it. However we initially
1849 // have to take the width for it into account so that
1850 // the text document layout sees the overflow and
1851 // switch to break-anywhere mode, in which we
1852 // want the soft-hyphen to slip into the next line
1853 // and thus become invisible again.
1856 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1858 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1861 // The actual width of the text needs to take the right bearing into account. The
1862 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1863 // of the advance of the glyph, the bearing will be negative. We flip the sign
1864 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1865 // We ignore the right bearing if the minimum negative bearing is too little to
1866 // expand the text beyond the edge.
1867 if (sb_or_ws|breakany) {
1868 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1869 #if !defined(Q_WS_MAC)
1870 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1872 lbh.adjustRightBearing();
1873 if (lbh.checkFullOtherwiseExtend(line)) {
1874 // we are too wide, fix right bearing
1875 if (rightBearing <= 0)
1876 lbh.rightBearing = rightBearing; // take from cache
1878 lbh.adjustPreviousRightBearing();
1881 line.textWidth += lbh.softHyphenWidth;
1887 lbh.saveCurrentGlyph();
1889 if (lbh.currentPosition == end)
1892 LB_DEBUG("reached end of line");
1893 lbh.checkFullOtherwiseExtend(line);
1895 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1896 lbh.adjustRightBearing();
1897 line.textAdvance = line.textWidth;
1898 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1900 if (line.length == 0) {
1901 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1902 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1903 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1904 line += lbh.tmpData;
1907 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1908 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1909 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1911 if (lbh.manualWrap) {
1912 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1913 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1915 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1916 eng->maxWidth += line.textWidth;
1919 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1920 eng->maxWidth += lbh.spaceData.textWidth;
1921 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1922 line.textWidth += lbh.spaceData.textWidth;
1923 if (lbh.spaceData.length) {
1924 line.length += lbh.spaceData.length;
1925 line.hasTrailingSpaces = true;
1928 line.justified = false;
1929 line.gridfitted = false;
1931 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1932 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1933 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1935 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1938 layout_helper(lbh.maxGlyphs);
1939 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1945 Moves the line to position \a pos.
1947 void QTextLine::setPosition(const QPointF &pos)
1949 eng->lines[i].x = QFixed::fromReal(pos.x());
1950 eng->lines[i].y = QFixed::fromReal(pos.y());
1954 Returns the line's position relative to the text layout's position.
1956 QPointF QTextLine::position() const
1958 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1961 // ### DOC: I have no idea what this means/does.
1962 // You create a text layout with a string of text. Once you laid
1963 // it out, it contains a number of QTextLines. from() returns the position
1964 // inside the text string where this line starts. If you e.g. has a
1965 // text of "This is a string", laid out into two lines (the second
1966 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
1967 // layout.lineAt(1).from() == 8.
1969 Returns the start of the line from the beginning of the string
1970 passed to the QTextLayout.
1972 int QTextLine::textStart() const
1974 return eng->lines[i].from;
1978 Returns the length of the text in the line.
1980 \sa naturalTextWidth()
1982 int QTextLine::textLength() const
1984 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1985 && eng->block.isValid() && i == eng->lines.count()-1) {
1986 return eng->lines[i].length - 1;
1988 return eng->lines[i].length;
1991 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1992 int start, int glyph_start)
1994 int ge = glyph_start + gf.glyphs.numGlyphs;
1995 int gs = glyph_start;
1996 int end = start + gf.num_chars;
1997 unsigned short *logClusters = eng->logClusters(&si);
1998 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
1999 QFixed orig_width = gf.width;
2001 int *ul = eng->underlinePositions;
2003 while (*ul != -1 && *ul < start)
2005 bool rtl = si.analysis.bidiLevel % 2;
2012 if (ul && *ul != -1 && *ul < end) {
2014 gtmp = logClusters[*ul-si.position];
2017 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2018 gf.num_chars = stmp - start;
2019 gf.chars = eng->layoutData->string.unicode() + start;
2022 w += glyphs.effectiveAdvance(gs);
2030 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2033 if (ul && *ul != -1 && *ul < end) {
2035 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2037 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2038 gf.num_chars = stmp - start;
2039 gf.chars = eng->layoutData->string.unicode() + start;
2040 gf.logClusters = logClusters + start - si.position;
2043 w += glyphs.effectiveAdvance(gs);
2048 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2051 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2054 gf.underlineStyle = QTextCharFormat::NoUnderline;
2060 gf.width = orig_width;
2064 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2066 QBrush c = chf.foreground();
2067 if (c.style() == Qt::NoBrush) {
2068 p->setPen(defaultPen);
2071 QBrush bg = chf.background();
2072 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2074 if (c.style() != Qt::NoBrush) {
2075 p->setPen(QPen(c, 0));
2083 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2084 const QTextItemInt::RenderFlags &renderFlags)
2085 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2089 QGlyphLayout glyphLayout;
2090 QPointF itemPosition;
2091 QTextItem::RenderFlags flags;
2096 Returns the glyph indexes and positions for all glyphs in this QTextLine for characters
2097 in the range defined by \a from and \a length. The \a from index is relative to the beginning
2098 of the text in the containing QTextLayout, and the range must be within the range of QTextLine
2099 as given by functions textStart() and textLength().
2101 If \a from is negative, it will default to textStart(), and if \a length is negative it will
2102 default to the return value of textLength().
2106 \sa QTextLayout::glyphRuns()
2108 #if !defined(QT_NO_RAWFONT)
2109 QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const
2111 const QScriptLine &line = eng->lines[i];
2113 if (line.length == 0)
2114 return QList<QGlyphRun>();
2120 length = textLength();
2122 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2124 QTextLineItemIterator iterator(eng, i);
2125 qreal y = line.y.toReal() + line.base().toReal();
2126 while (!iterator.atEnd()) {
2127 QScriptItem &si = iterator.next();
2128 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2131 QPointF pos(iterator.x.toReal(), y);
2132 if (from >= 0 && length >= 0 &&
2133 (from >= si.position + eng->length(&si) || from + length <= si.position)) {
2137 QFont font = eng->font(si);
2139 QTextItem::RenderFlags flags;
2140 if (font.overline())
2141 flags |= QTextItem::Overline;
2142 if (font.underline())
2143 flags |= QTextItem::Underline;
2144 if (font.strikeOut())
2145 flags |= QTextItem::StrikeOut;
2148 if (si.analysis.bidiLevel % 2) {
2149 flags |= QTextItem::RightToLeft;
2153 int relativeFrom = qMax(iterator.itemStart, from) - si.position;
2154 int relativeTo = qMin(iterator.itemEnd, from + length - 1) - si.position;
2156 unsigned short *logClusters = eng->logClusters(&si);
2157 int glyphsStart = logClusters[relativeFrom];
2158 int glyphsEnd = (relativeTo == eng->length(&si))
2160 : logClusters[relativeTo];
2162 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si);
2164 // Calculate new x position of glyph layout for a subset. This becomes somewhat complex
2165 // when we're breaking a RTL script item, since the expected position passed into
2166 // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run.
2167 if (relativeFrom != (iterator.itemStart - si.position) && !rtl) {
2168 for (int i=0; i<glyphsStart; ++i) {
2169 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2170 pos += QPointF((glyphLayout.advances_x[i] + justification).toReal(),
2171 glyphLayout.advances_y[i].toReal());
2173 } else if (relativeTo != (iterator.itemEnd - si.position) && rtl) {
2174 for (int i=glyphLayout.numGlyphs - 1; i>glyphsEnd; --i) {
2175 QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6);
2176 pos += QPointF((glyphLayout.advances_x[i] + justification).toReal(),
2177 glyphLayout.advances_y[i].toReal());
2181 glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1);
2183 if (glyphLayout.numGlyphs > 0) {
2184 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2185 if (mainFontEngine->type() == QFontEngine::Multi) {
2186 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2189 int which = glyphLayout.glyphs[0] >> 24;
2190 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2191 const int e = glyphLayout.glyphs[end] >> 24;
2195 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2196 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2197 GlyphInfo(subLayout, pos, flags));
2198 for (int i = 0; i < subLayout.numGlyphs; i++) {
2199 pos += QPointF(subLayout.advances_x[i].toReal(),
2200 subLayout.advances_y[i].toReal());
2207 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2208 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2209 GlyphInfo(subLayout, pos, flags));
2212 glyphLayoutHash.insertMulti(mainFontEngine,
2213 GlyphInfo(glyphLayout, pos, flags));
2218 QHash<QPair<QFontEngine *, int>, QGlyphRun> glyphsHash;
2220 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2221 for (int i=0; i<keys.size(); ++i) {
2222 QFontEngine *fontEngine = keys.at(i);
2224 // Make a font for this particular engine
2226 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2227 fontD->fontEngine = fontEngine;
2228 fontD->fontEngine->ref.ref();
2230 #if defined(Q_WS_WIN)
2231 if (fontEngine->supportsSubPixelPositions())
2232 fontD->hintingPreference = QFont::PreferVerticalHinting;
2234 fontD->hintingPreference = QFont::PreferFullHinting;
2235 #elif defined(Q_WS_MAC)
2236 fontD->hintingPreference = QFont::PreferNoHinting;
2237 #elif !defined(QT_NO_FREETYPE)
2238 if (fontEngine->type() == QFontEngine::Freetype) {
2239 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2240 switch (freeTypeEngine->defaultHintStyle()) {
2241 case QFontEngineFT::HintNone:
2242 fontD->hintingPreference = QFont::PreferNoHinting;
2244 case QFontEngineFT::HintLight:
2245 fontD->hintingPreference = QFont::PreferVerticalHinting;
2247 case QFontEngineFT::HintMedium:
2248 case QFontEngineFT::HintFull:
2249 fontD->hintingPreference = QFont::PreferFullHinting;
2255 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2256 for (int j=0; j<glyphLayouts.size(); ++j) {
2257 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2258 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2259 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2261 QVarLengthArray<glyph_t> glyphsArray;
2262 QVarLengthArray<QFixedPoint> positionsArray;
2264 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2266 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2268 QVector<quint32> glyphs;
2269 QVector<QPointF> positions;
2270 for (int i=0; i<glyphsArray.size(); ++i) {
2271 glyphs.append(glyphsArray.at(i) & 0xffffff);
2272 positions.append(positionsArray.at(i).toPointF() + pos);
2275 QGlyphRun glyphIndexes;
2276 glyphIndexes.setGlyphIndexes(glyphs);
2277 glyphIndexes.setPositions(positions);
2279 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2280 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2281 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2282 glyphIndexes.setRawFont(font);
2284 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2285 if (!glyphsHash.contains(key))
2286 glyphsHash.insert(key, glyphIndexes);
2288 glyphsHash[key] += glyphIndexes;
2292 return glyphsHash.values();
2294 #endif // QT_NO_RAWFONT
2297 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2299 Draws a line on the given \a painter at the specified \a position.
2300 The \a selection is reserved for internal use.
2302 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2304 const QScriptLine &line = eng->lines[i];
2305 QPen pen = p->pen();
2307 bool noText = (selection && selection->format.property(SuppressText).toBool());
2311 && selection->start <= line.from
2312 && selection->start + selection->length > line.from) {
2314 const qreal lineHeight = line.height().toReal();
2315 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2316 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2317 setPenAndDrawBackground(p, QPen(), selection->format, r);
2324 QTextLineItemIterator iterator(eng, i, pos, selection);
2325 QFixed lineBase = line.base();
2327 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2329 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2330 while (!iterator.atEnd()) {
2331 QScriptItem &si = iterator.next();
2333 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2336 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2337 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2340 QFixed itemBaseLine = y;
2341 QFont f = eng->font(si);
2342 QTextCharFormat format;
2344 if (eng->hasFormats() || selection) {
2345 format = eng->format(&si);
2346 if (suppressColors) {
2347 format.clearForeground();
2348 format.clearBackground();
2349 format.clearProperty(QTextFormat::TextUnderlineColor);
2352 format.merge(selection->format);
2354 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2355 iterator.itemWidth.toReal(), line.height().toReal()));
2357 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2358 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2359 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2360 QFixed height = fe->ascent() + fe->descent();
2361 if (valign == QTextCharFormat::AlignSubScript)
2362 itemBaseLine += height / 6;
2363 else if (valign == QTextCharFormat::AlignSuperScript)
2364 itemBaseLine -= height / 2;
2368 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2370 if (eng->hasFormats()) {
2372 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2373 QFixed itemY = y - si.ascent;
2374 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2375 itemY = y - lineBase;
2378 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2380 eng->docLayout()->drawInlineObject(p, itemRect,
2381 QTextInlineObject(iterator.item, eng),
2382 si.position + eng->block.position(),
2385 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2386 if (bg.style() != Qt::NoBrush) {
2387 QColor c = bg.color();
2389 p->fillRect(itemRect, c);
2392 } else { // si.isTab
2393 QFont f = eng->font(si);
2394 QTextItemInt gf(si, &f, format);
2397 gf.width = iterator.itemWidth;
2398 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2399 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2400 QChar visualTab(0x2192);
2401 int w = QFontMetrics(f).width(visualTab);
2402 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2404 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2405 iterator.itemWidth.toReal(), line.height().toReal()),
2409 p->drawText(QPointF(iterator.x.toReal() + x,
2410 y.toReal()), visualTab);
2420 unsigned short *logClusters = eng->logClusters(&si);
2421 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2423 QTextItemInt gf(si, &f, format);
2424 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2425 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2426 gf.logClusters = logClusters + iterator.itemStart - si.position;
2427 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2428 gf.width = iterator.itemWidth;
2429 gf.justified = line.justified;
2431 Q_ASSERT(gf.fontEngine);
2433 if (eng->underlinePositions) {
2434 // can't have selections in this case
2435 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2437 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2438 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2440 path.setFillRule(Qt::WindingFill);
2442 if (gf.glyphs.numGlyphs)
2443 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2445 const QFontEngine *fe = gf.fontEngine;
2446 const qreal lw = fe->lineThickness().toReal();
2447 if (gf.flags & QTextItem::Underline) {
2448 qreal offs = fe->underlinePosition().toReal();
2449 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2451 if (gf.flags & QTextItem::Overline) {
2452 qreal offs = fe->ascent().toReal() + 1;
2453 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2455 if (gf.flags & QTextItem::StrikeOut) {
2456 qreal offs = fe->ascent().toReal() / 3;
2457 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2462 p->setRenderHint(QPainter::Antialiasing);
2463 //Currently QPen with a Qt::NoPen style still returns a default
2464 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2465 if (p->pen().style() == Qt::NoPen)
2466 p->setBrush(Qt::NoBrush);
2468 p->setBrush(p->pen().brush());
2470 p->setPen(format.textOutline());
2475 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2476 p->drawTextItem(pos, gf);
2479 if (si.analysis.flags == QScriptAnalysis::Space
2480 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2481 QBrush c = format.foreground();
2482 if (c.style() != Qt::NoBrush)
2483 p->setPen(c.color());
2484 QChar visualSpace((ushort)0xb7);
2485 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2491 if (eng->hasFormats())
2496 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2502 Converts the cursor position \a cursorPos to the corresponding x position
2503 inside the line, taking account of the \a edge.
2505 If \a cursorPos is not a valid cursor position, the nearest valid
2506 cursor position will be used instead, and cpos will be modified to
2507 point to this valid cursor position.
2511 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2513 if (!eng->layoutData)
2516 const QScriptLine &line = eng->lines[i];
2517 bool lastLine = i >= eng->lines.size() - 1;
2520 x += eng->alignLine(line);
2522 if (!i && !eng->layoutData->items.size()) {
2527 int pos = *cursorPos;
2529 if (pos == line.from + (int)line.length) {
2530 // end of line ensure we have the last item on the line
2531 itm = eng->findItem(pos-1);
2534 itm = eng->findItem(pos);
2535 eng->shapeLine(line);
2537 const QScriptItem *si = &eng->layoutData->items[itm];
2538 if (!si->num_glyphs)
2540 pos -= si->position;
2542 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2543 unsigned short *logClusters = eng->logClusters(si);
2544 Q_ASSERT(logClusters);
2546 int l = eng->length(itm);
2552 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2553 if (edge == Trailing && glyph_pos < si->num_glyphs) {
2554 // trailing edge is leading edge of next cluster
2556 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2560 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2562 int lineEnd = line.from + line.length;
2564 // add the items left of the cursor
2566 int firstItem = eng->findItem(line.from);
2567 int lastItem = eng->findItem(lineEnd - 1);
2568 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2570 QVarLengthArray<int> visualOrder(nItems);
2571 QVarLengthArray<uchar> levels(nItems);
2572 for (int i = 0; i < nItems; ++i)
2573 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2574 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2576 for (int i = 0; i < nItems; ++i) {
2577 int item = visualOrder[i]+firstItem;
2580 QScriptItem &si = eng->layoutData->items[item];
2584 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2588 int start = qMax(line.from, si.position);
2589 int end = qMin(lineEnd, si.position + eng->length(item));
2591 logClusters = eng->logClusters(&si);
2593 int gs = logClusters[start-si.position];
2594 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2596 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2599 x += glyphs.effectiveAdvance(gs);
2604 logClusters = eng->logClusters(si);
2605 glyphs = eng->shapedGlyphs(si);
2606 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2607 if (pos == (reverse ? 0 : l))
2610 bool rtl = eng->isRightToLeft();
2611 bool visual = eng->visualCursorMovement();
2613 int end = qMin(lineEnd, si->position + l) - si->position;
2614 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2615 int glyph_start = glyph_pos;
2616 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
2618 for (int i = glyph_end - 1; i >= glyph_start; i--)
2619 x += glyphs.effectiveAdvance(i);
2621 int start = qMax(line.from - si->position, 0);
2622 int glyph_start = logClusters[start];
2623 int glyph_end = glyph_pos;
2624 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
2626 for (int i = glyph_start; i <= glyph_end; i++)
2627 x += glyphs.effectiveAdvance(i);
2629 x += eng->offsetInLigature(si, pos, line.length, glyph_pos);
2632 *cursorPos = pos + si->position;
2637 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2639 Converts the x-coordinate \a x, to the nearest matching cursor
2640 position, depending on the cursor position type, \a cpos.
2644 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2646 QFixed x = QFixed::fromReal(_x);
2647 const QScriptLine &line = eng->lines[i];
2648 bool lastLine = i >= eng->lines.size() - 1;
2651 if (!eng->layoutData)
2654 int line_length = textLength();
2659 int firstItem = eng->findItem(line.from);
2660 int lastItem = eng->findItem(line.from + line_length - 1);
2661 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2667 x -= eng->alignLine(line);
2668 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2670 QVarLengthArray<int> visualOrder(nItems);
2671 QVarLengthArray<unsigned char> levels(nItems);
2672 for (int i = 0; i < nItems; ++i)
2673 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2674 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2676 bool visual = eng->visualCursorMovement();
2678 // left of first item
2679 int item = visualOrder[0]+firstItem;
2680 QScriptItem &si = eng->layoutData->items[item];
2683 int pos = si.position;
2684 if (si.analysis.bidiLevel % 2)
2685 pos += eng->length(item);
2686 pos = qMax(line.from, pos);
2687 pos = qMin(line.from + line_length, pos);
2689 } else if (x < line.textWidth
2690 || (line.justified && x < line.width)) {
2691 // has to be in one of the runs
2693 bool rtl = eng->isRightToLeft();
2695 eng->shapeLine(line);
2696 QVector<int> insertionPoints;
2698 eng->insertionPointsForLine(lineNum, insertionPoints);
2700 for (int i = 0; i < nItems; ++i) {
2701 int item = visualOrder[i]+firstItem;
2702 QScriptItem &si = eng->layoutData->items[item];
2703 int item_length = eng->length(item);
2704 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2706 int start = qMax(line.from - si.position, 0);
2707 int end = qMin(line.from + line_length - si.position, item_length);
2709 unsigned short *logClusters = eng->logClusters(&si);
2711 int gs = logClusters[start];
2712 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2713 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2715 QFixed item_width = 0;
2716 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2717 item_width = si.width;
2721 item_width += glyphs.effectiveAdvance(g);
2725 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2727 if (pos + item_width < x) {
2732 // qDebug(" inside run");
2733 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2734 if (cpos == QTextLine::CursorOnCharacter)
2736 bool left_half = (x - pos) < item_width/2;
2738 if (bool(si.analysis.bidiLevel % 2) != left_half)
2740 return si.position + 1;
2744 // has to be inside run
2745 if (cpos == QTextLine::CursorOnCharacter) {
2746 if (si.analysis.bidiLevel % 2) {
2750 if (glyphs.attributes[gs].clusterStart) {
2756 pos -= glyphs.effectiveAdvance(gs);
2762 if (glyphs.attributes[gs].clusterStart) {
2767 pos += glyphs.effectiveAdvance(gs);
2772 QFixed dist = INT_MAX/256;
2773 if (si.analysis.bidiLevel % 2) {
2774 if (!visual || rtl || (lastLine && i == nItems - 1)) {
2777 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2781 pos -= glyphs.effectiveAdvance(gs);
2786 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
2790 pos += glyphs.effectiveAdvance(ge);
2795 if (!visual || !rtl || (lastLine && i == 0)) {
2797 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2801 pos += glyphs.effectiveAdvance(gs);
2805 QFixed oldPos = pos;
2807 pos += glyphs.effectiveAdvance(gs);
2808 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2817 if (qAbs(x-pos) < dist) {
2819 if (!rtl && i < nItems - 1) {
2823 if (rtl && nchars > 0)
2824 return insertionPoints[lastLine ? nchars : nchars - 1];
2826 return si.position + end;
2829 Q_ASSERT(glyph_pos != -1);
2831 for (j = 0; j < eng->length(item); ++j)
2832 if (logClusters[j] == glyph_pos)
2834 // qDebug("at pos %d (in run: %d)", si.position + j, j);
2835 return si.position + j;
2838 // right of last item
2839 // qDebug() << "right of last";
2840 int item = visualOrder[nItems-1]+firstItem;
2841 QScriptItem &si = eng->layoutData->items[item];
2844 int pos = si.position;
2845 if (!(si.analysis.bidiLevel % 2))
2846 pos += eng->length(item);
2847 pos = qMax(line.from, pos);
2849 int maxPos = line.from + line_length;
2851 // except for the last line we assume that the
2852 // character between lines is a space and we want
2853 // to position the cursor to the left of that
2855 // ###### breaks with japanese for example
2856 if (this->i < eng->lines.count() - 1)
2859 pos = qMin(pos, maxPos);