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);
1064 bool isLastLineInBlock = (line == d->lines.size()-1);
1065 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1068 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1069 continue; // no actual intersection
1071 const bool selectionStartInLine = sl.from <= selection.start;
1072 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1074 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1075 addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip));
1077 region.addRect(clipIfValid(lineRect, clip));
1080 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1081 QRectF fullLineRect(tl.rect());
1082 fullLineRect.translate(position);
1083 fullLineRect.setRight(QFIXED_MAX);
1084 if (!selectionEndInLine)
1085 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1086 if (!selectionStartInLine)
1087 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1088 } else if (!selectionEndInLine
1089 && isLastLineInBlock
1090 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1091 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1092 lineRect.height()/4, lineRect.height()), clip));
1097 const QPen oldPen = p->pen();
1098 const QBrush oldBrush = p->brush();
1100 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1101 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1102 p->drawPath(region);
1105 p->setBrush(oldBrush);
1110 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1111 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1113 if (hasBackground) {
1114 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1115 // don't just clear the property, set an empty brush that overrides a potential
1116 // background brush specified in the text
1117 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1118 selection.format.clearProperty(QTextFormat::OutlinePen);
1121 selection.format.setProperty(SuppressText, !hasText);
1123 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1127 p->setClipPath(region, Qt::IntersectClip);
1129 for (int line = firstLine; line < lastLine; ++line) {
1130 QTextLine l(line, d);
1131 l.draw(p, position, &selection);
1136 textDoneRegion += region;
1139 textDoneRegion -= region;
1142 excludedRegion += region;
1145 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1146 if (!needsTextButNoBackground.isEmpty()){
1148 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1149 FormatRange selection;
1150 selection.start = 0;
1151 selection.length = INT_MAX;
1152 selection.format.setProperty(SuppressBackground, true);
1153 for (int line = firstLine; line < lastLine; ++line) {
1154 QTextLine l(line, d);
1155 l.draw(p, position, &selection);
1160 if (!excludedRegion.isEmpty()) {
1163 QRectF br = boundingRect().translated(position);
1164 br.setRight(QFIXED_MAX);
1166 br = br.intersected(clip);
1168 path -= excludedRegion;
1169 p->setClipPath(path, Qt::IntersectClip);
1172 for (int i = firstLine; i < lastLine; ++i) {
1174 l.draw(p, position);
1176 if (!excludedRegion.isEmpty())
1180 if (!d->cacheGlyphs)
1185 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1188 Draws a text cursor with the current pen at the given \a position using the
1189 \a painter specified.
1190 The corresponding position within the text is specified by \a cursorPosition.
1192 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1194 drawCursor(p, pos, cursorPosition, 1);
1198 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1200 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1201 \a painter specified.
1202 The corresponding position within the text is specified by \a cursorPosition.
1204 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1206 if (d->lines.isEmpty())
1212 QPointF position = pos + d->position;
1213 QFixed pos_x = QFixed::fromReal(position.x());
1214 QFixed pos_y = QFixed::fromReal(position.y());
1216 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1217 int line = d->lineNumberForTextPosition(cursorPosition);
1220 if (line >= d->lines.size())
1223 QTextLine l(line, d);
1224 const QScriptLine &sl = d->lines[line];
1226 qreal x = position.x() + l.cursorToX(cursorPosition);
1230 if (d->visualCursorMovement()) {
1231 if (cursorPosition == sl.from + sl.length)
1233 itm = d->findItem(cursorPosition);
1235 itm = d->findItem(cursorPosition - 1);
1237 QFixed base = sl.base();
1238 QFixed descent = sl.descent;
1239 bool rightToLeft = d->isRightToLeft();
1241 const QScriptItem &si = d->layoutData->items.at(itm);
1245 descent = si.descent;
1246 rightToLeft = si.analysis.bidiLevel % 2;
1248 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1249 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1250 && (p->transform().type() > QTransform::TxTranslate);
1251 if (toggleAntialiasing)
1252 p->setRenderHint(QPainter::Antialiasing);
1253 #if defined(QT_MAC_USE_COCOA)
1254 // Always draw the cursor aligned to pixel boundary.
1257 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1258 if (toggleAntialiasing)
1259 p->setRenderHint(QPainter::Antialiasing, false);
1260 if (d->layoutData->hasBidi) {
1261 const int arrow_extent = 4;
1262 int sign = rightToLeft ? -1 : 1;
1263 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1264 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1273 \brief The QTextLine class represents a line of text inside a QTextLayout.
1275 \ingroup richtext-processing
1277 A text line is usually created by QTextLayout::createLine().
1279 After being created, the line can be filled using the setLineWidth()
1280 or setNumColumns() functions. A line has a number of attributes including the
1281 rectangle it occupies, rect(), its coordinates, x() and y(), its
1282 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1283 relative to the text. The position of the cursor in terms of the
1284 line is available from cursorToX() and its inverse from
1285 xToCursor(). A line can be moved with setPosition().
1289 \enum QTextLine::Edge
1296 \enum QTextLine::CursorPosition
1298 \value CursorBetweenCharacters
1299 \value CursorOnCharacter
1303 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1306 Constructs a new text line using the line at position \a line in
1307 the text engine \a e.
1311 \fn QTextLine::QTextLine()
1313 Creates an invalid line.
1317 \fn bool QTextLine::isValid() const
1319 Returns true if this text line is valid; otherwise returns false.
1323 \fn int QTextLine::lineNumber() const
1325 Returns the position of the line in the text engine.
1330 Returns the line's bounding rectangle.
1332 \sa x(), y(), textLength(), width()
1334 QRectF QTextLine::rect() const
1336 const QScriptLine& sl = eng->lines[i];
1337 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1341 Returns the rectangle covered by the line.
1343 QRectF QTextLine::naturalTextRect() const
1345 const QScriptLine& sl = eng->lines[i];
1346 QFixed x = sl.x + eng->alignLine(sl);
1348 QFixed width = sl.textWidth;
1352 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1356 Returns the line's x position.
1358 \sa rect(), y(), textLength(), width()
1360 qreal QTextLine::x() const
1362 return eng->lines[i].x.toReal();
1366 Returns the line's y position.
1368 \sa x(), rect(), textLength(), width()
1370 qreal QTextLine::y() const
1372 return eng->lines[i].y.toReal();
1376 Returns the line's width as specified by the layout() function.
1378 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1380 qreal QTextLine::width() const
1382 return eng->lines[i].width.toReal();
1387 Returns the line's ascent.
1389 \sa descent(), height()
1391 qreal QTextLine::ascent() const
1393 return eng->lines[i].ascent.toReal();
1397 Returns the line's descent.
1399 \sa ascent(), height()
1401 qreal QTextLine::descent() const
1403 return eng->lines[i].descent.toReal();
1407 Returns the line's height. This is equal to ascent() + descent() + 1
1408 if leading is not included. If leading is included, this equals to
1409 ascent() + descent() + leading() + 1.
1411 \sa ascent(), descent(), leading(), setLeadingIncluded()
1413 qreal QTextLine::height() const
1415 return eng->lines[i].height().toReal();
1421 Returns the line's leading.
1423 \sa ascent(), descent(), height()
1425 qreal QTextLine::leading() const
1427 return eng->lines[i].leading.toReal();
1433 Includes positive leading into the line's height if \a included is true;
1434 otherwise does not include leading.
1436 By default, leading is not included.
1438 Note that negative leading is ignored, it must be handled
1439 in the code using the text lines by letting the lines overlap.
1441 \sa leadingIncluded()
1444 void QTextLine::setLeadingIncluded(bool included)
1446 eng->lines[i].leadingIncluded= included;
1453 Returns true if positive leading is included into the line's height;
1454 otherwise returns false.
1456 By default, leading is not included.
1458 \sa setLeadingIncluded()
1460 bool QTextLine::leadingIncluded() const
1462 return eng->lines[i].leadingIncluded;
1466 Returns the width of the line that is occupied by text. This is
1467 always \<= to width(), and is the minimum width that could be used
1468 by layout() without changing the line break position.
1470 qreal QTextLine::naturalTextWidth() const
1472 return eng->lines[i].textWidth.toReal();
1477 Returns the horizontal advance of the text. The advance of the text
1478 is the distance from its position to the next position at which
1479 text would naturally be drawn.
1481 By adding the advance to the position of the text line and using this
1482 as the position of a second text line, you will be able to position
1483 the two lines side-by-side without gaps in-between.
1485 qreal QTextLine::horizontalAdvance() const
1487 return eng->lines[i].textAdvance.toReal();
1491 Lays out the line with the given \a width. The line is filled from
1492 its starting position with as many characters as will fit into
1493 the line. In case the text cannot be split at the end of the line,
1494 it will be filled with additional characters to the next whitespace
1497 void QTextLine::setLineWidth(qreal width)
1499 QScriptLine &line = eng->lines[i];
1500 if (!eng->layoutData) {
1501 qWarning("QTextLine: Can't set a line width while not layouting.");
1505 if (width > QFIXED_MAX)
1508 line.width = QFixed::fromReal(width);
1510 && line.textWidth <= line.width
1511 && line.from + line.length == eng->layoutData->string.length())
1512 // no need to do anything if the line is already layouted and the last one. This optimization helps
1513 // when using things in a single line layout.
1518 layout_helper(INT_MAX);
1522 Lays out the line. The line is filled from its starting position
1523 with as many characters as are specified by \a numColumns. In case
1524 the text cannot be split until \a numColumns characters, the line
1525 will be filled with as many characters to the next whitespace or
1528 void QTextLine::setNumColumns(int numColumns)
1530 QScriptLine &line = eng->lines[i];
1531 line.width = QFIXED_MAX;
1534 layout_helper(numColumns);
1538 Lays out the line. The line is filled from its starting position
1539 with as many characters as are specified by \a numColumns. In case
1540 the text cannot be split until \a numColumns characters, the line
1541 will be filled with as many characters to the next whitespace or
1542 end of the text. The provided \a alignmentWidth is used as reference
1543 width for alignment.
1545 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1547 QScriptLine &line = eng->lines[i];
1548 line.width = QFixed::fromReal(alignmentWidth);
1551 layout_helper(numColumns);
1555 #define LB_DEBUG qDebug
1557 #define LB_DEBUG if (0) qDebug
1562 struct LineBreakHelper
1565 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1566 manualWrap(false), whiteSpaceOrObject(true)
1571 QScriptLine tmpData;
1572 QScriptLine spaceData;
1574 QGlyphLayout glyphs;
1578 int currentPosition;
1579 glyph_t previousGlyph;
1582 QFixed softHyphenWidth;
1583 QFixed rightBearing;
1584 QFixed minimumRightBearing;
1586 QFontEngine *fontEngine;
1587 const unsigned short *logClusters;
1590 bool whiteSpaceOrObject;
1592 bool checkFullOtherwiseExtend(QScriptLine &line);
1594 QFixed calculateNewWidth(const QScriptLine &line) const {
1595 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1596 - qMin(rightBearing, QFixed());
1599 inline glyph_t currentGlyph() const
1601 Q_ASSERT(currentPosition > 0);
1602 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1604 return glyphs.glyphs[logClusters[currentPosition - 1]];
1607 inline void saveCurrentGlyph()
1610 if (currentPosition > 0 &&
1611 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1612 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1616 inline void adjustRightBearing(glyph_t glyph)
1619 fontEngine->getGlyphBearings(glyph, 0, &rb);
1620 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1623 inline void adjustRightBearing()
1625 if (currentPosition <= 0)
1627 adjustRightBearing(currentGlyph());
1630 inline void adjustPreviousRightBearing()
1632 if (previousGlyph > 0)
1633 adjustRightBearing(previousGlyph);
1636 inline void resetRightBearing()
1638 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1639 // negative right bearings are interesting to us.
1643 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1645 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1647 QFixed newWidth = calculateNewWidth(line);
1648 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1651 minw = qMax(minw, tmpData.textWidth);
1653 line.textWidth += spaceData.textWidth;
1655 line.length += spaceData.length;
1656 tmpData.textWidth = 0;
1658 spaceData.textWidth = 0;
1659 spaceData.length = 0;
1664 } // anonymous namespace
1667 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1668 const QScriptItem ¤t, const unsigned short *logClusters,
1669 const QGlyphLayout &glyphs)
1671 int glyphPosition = logClusters[pos];
1672 do { // got to the first next cluster
1675 } while (pos < end && logClusters[pos] == glyphPosition);
1676 do { // calculate the textWidth for the rest of the current cluster.
1677 if (!glyphs.attributes[glyphPosition].dontPrint)
1678 line.textWidth += glyphs.advances_x[glyphPosition];
1680 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1682 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1689 void QTextLine::layout_helper(int maxGlyphs)
1691 QScriptLine &line = eng->lines[i];
1694 line.hasTrailingSpaces = false;
1696 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1697 line.setDefaultHeight(eng);
1701 Q_ASSERT(line.from < eng->layoutData->string.length());
1703 LineBreakHelper lbh;
1705 lbh.maxGlyphs = maxGlyphs;
1707 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1708 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1709 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1712 int newItem = eng->findItem(line.from);
1714 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1716 Qt::Alignment alignment = eng->option.alignment();
1718 const HB_CharAttributes *attributes = eng->attributes();
1721 lbh.currentPosition = line.from;
1723 lbh.logClusters = eng->layoutData->logClustersPtr;
1724 lbh.previousGlyph = 0;
1726 while (newItem < eng->layoutData->items.size()) {
1727 lbh.resetRightBearing();
1728 lbh.softHyphenWidth = 0;
1729 if (newItem != item) {
1731 const QScriptItem ¤t = eng->layoutData->items[item];
1732 if (!current.num_glyphs) {
1734 attributes = eng->attributes();
1737 lbh.logClusters = eng->layoutData->logClustersPtr;
1739 lbh.currentPosition = qMax(line.from, current.position);
1740 end = current.position + eng->length(item);
1741 lbh.glyphs = eng->shapedGlyphs(¤t);
1742 QFontEngine *fontEngine = eng->fontEngine(current);
1743 if (lbh.fontEngine != fontEngine) {
1744 lbh.fontEngine = fontEngine;
1745 lbh.minimumRightBearing = qMin(QFixed(),
1746 QFixed::fromReal(fontEngine->minRightBearing()));
1749 const QScriptItem ¤t = eng->layoutData->items[item];
1751 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1752 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1754 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1755 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1757 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1758 lbh.whiteSpaceOrObject = true;
1759 if (lbh.checkFullOtherwiseExtend(line))
1762 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1763 QFixed tabWidth = eng->calculateTabWidth(item, x);
1765 lbh.spaceData.textWidth += tabWidth;
1766 lbh.spaceData.length++;
1769 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1770 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1772 if (lbh.checkFullOtherwiseExtend(line))
1774 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1775 lbh.whiteSpaceOrObject = true;
1776 // if the line consists only of the line separator make sure
1777 // we have a sane height
1778 if (!line.length && !lbh.tmpData.length)
1779 line.setDefaultHeight(eng);
1780 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1781 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1782 current, lbh.logClusters, lbh.glyphs);
1784 lbh.tmpData.length++;
1785 lbh.adjustPreviousRightBearing();
1787 line += lbh.tmpData;
1789 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1790 lbh.whiteSpaceOrObject = true;
1791 lbh.tmpData.length++;
1793 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1794 if (eng->block.docHandle())
1795 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1797 lbh.tmpData.textWidth += current.width;
1801 if (lbh.checkFullOtherwiseExtend(line))
1803 } else if (attributes[lbh.currentPosition].whiteSpace) {
1804 lbh.whiteSpaceOrObject = true;
1805 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1806 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1807 current, lbh.logClusters, lbh.glyphs);
1809 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1810 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1814 lbh.whiteSpaceOrObject = false;
1815 bool sb_or_ws = false;
1816 lbh.saveCurrentGlyph();
1818 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1819 current, lbh.logClusters, lbh.glyphs);
1821 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1824 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1827 } while (lbh.currentPosition < end);
1828 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1830 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1831 // if we are splitting up a word because of
1832 // a soft hyphen then we ...
1834 // a) have to take the width of the soft hyphen into
1835 // account to see if the first syllable(s) /and/
1836 // the soft hyphen fit into the line
1838 // b) if we are so short of available width that the
1839 // soft hyphen is the first breakable position, then
1840 // we don't want to show it. However we initially
1841 // have to take the width for it into account so that
1842 // the text document layout sees the overflow and
1843 // switch to break-anywhere mode, in which we
1844 // want the soft-hyphen to slip into the next line
1845 // and thus become invisible again.
1848 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1850 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1853 // The actual width of the text needs to take the right bearing into account. The
1854 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1855 // of the advance of the glyph, the bearing will be negative. We flip the sign
1856 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1857 // We ignore the right bearing if the minimum negative bearing is too little to
1858 // expand the text beyond the edge.
1859 if (sb_or_ws|breakany) {
1860 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1861 #if !defined(Q_WS_MAC)
1862 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1864 lbh.adjustRightBearing();
1865 if (lbh.checkFullOtherwiseExtend(line)) {
1866 // we are too wide, fix right bearing
1867 if (rightBearing <= 0)
1868 lbh.rightBearing = rightBearing; // take from cache
1870 lbh.adjustPreviousRightBearing();
1873 line.textWidth += lbh.softHyphenWidth;
1879 lbh.saveCurrentGlyph();
1881 if (lbh.currentPosition == end)
1884 LB_DEBUG("reached end of line");
1885 lbh.checkFullOtherwiseExtend(line);
1887 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1888 lbh.adjustRightBearing();
1889 line.textAdvance = line.textWidth;
1890 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1892 if (line.length == 0) {
1893 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1894 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1895 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1896 line += lbh.tmpData;
1899 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1900 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1901 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1903 if (lbh.manualWrap) {
1904 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1905 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1907 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1908 eng->maxWidth += line.textWidth;
1911 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1912 eng->maxWidth += lbh.spaceData.textWidth;
1913 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1914 line.textWidth += lbh.spaceData.textWidth;
1915 if (lbh.spaceData.length) {
1916 line.length += lbh.spaceData.length;
1917 line.hasTrailingSpaces = true;
1920 line.justified = false;
1921 line.gridfitted = false;
1923 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1924 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1925 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1927 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1930 layout_helper(lbh.maxGlyphs);
1931 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1937 Moves the line to position \a pos.
1939 void QTextLine::setPosition(const QPointF &pos)
1941 eng->lines[i].x = QFixed::fromReal(pos.x());
1942 eng->lines[i].y = QFixed::fromReal(pos.y());
1946 Returns the line's position relative to the text layout's position.
1948 QPointF QTextLine::position() const
1950 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1953 // ### DOC: I have no idea what this means/does.
1954 // You create a text layout with a string of text. Once you laid
1955 // it out, it contains a number of QTextLines. from() returns the position
1956 // inside the text string where this line starts. If you e.g. has a
1957 // text of "This is a string", laid out into two lines (the second
1958 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
1959 // layout.lineAt(1).from() == 8.
1961 Returns the start of the line from the beginning of the string
1962 passed to the QTextLayout.
1964 int QTextLine::textStart() const
1966 return eng->lines[i].from;
1970 Returns the length of the text in the line.
1972 \sa naturalTextWidth()
1974 int QTextLine::textLength() const
1976 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1977 && eng->block.isValid() && i == eng->lines.count()-1) {
1978 return eng->lines[i].length - 1;
1980 return eng->lines[i].length;
1983 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1984 int start, int glyph_start)
1986 int ge = glyph_start + gf.glyphs.numGlyphs;
1987 int gs = glyph_start;
1988 int end = start + gf.num_chars;
1989 unsigned short *logClusters = eng->logClusters(&si);
1990 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
1991 QFixed orig_width = gf.width;
1993 int *ul = eng->underlinePositions;
1995 while (*ul != -1 && *ul < start)
1997 bool rtl = si.analysis.bidiLevel % 2;
2004 if (ul && *ul != -1 && *ul < end) {
2006 gtmp = logClusters[*ul-si.position];
2009 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2010 gf.num_chars = stmp - start;
2011 gf.chars = eng->layoutData->string.unicode() + start;
2014 w += glyphs.effectiveAdvance(gs);
2022 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2025 if (ul && *ul != -1 && *ul < end) {
2027 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2029 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2030 gf.num_chars = stmp - start;
2031 gf.chars = eng->layoutData->string.unicode() + start;
2032 gf.logClusters = logClusters + start - si.position;
2035 w += glyphs.effectiveAdvance(gs);
2040 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2043 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2046 gf.underlineStyle = QTextCharFormat::NoUnderline;
2052 gf.width = orig_width;
2056 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2058 QBrush c = chf.foreground();
2059 if (c.style() == Qt::NoBrush) {
2060 p->setPen(defaultPen);
2063 QBrush bg = chf.background();
2064 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2066 if (c.style() != Qt::NoBrush) {
2067 p->setPen(QPen(c, 0));
2075 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2076 const QTextItemInt::RenderFlags &renderFlags)
2077 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2081 QGlyphLayout glyphLayout;
2082 QPointF itemPosition;
2083 QTextItem::RenderFlags flags;
2090 Returns the glyph indexes and positions for all glyphs in this QTextLine which reside in
2091 QScriptItems that overlap with the range defined by \a from and \a length. The arguments
2092 specify characters, relative to the text in the layout. Note that it is not possible to
2093 use this function to retrieve a subset of the glyphs in a QScriptItem.
2097 \sa QTextLayout::glyphs()
2099 #if !defined(QT_NO_RAWFONT)
2100 QList<QGlyphs> QTextLine::glyphs(int from, int length) const
2102 const QScriptLine &line = eng->lines[i];
2104 if (line.length == 0)
2105 return QList<QGlyphs>();
2107 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2109 QTextLineItemIterator iterator(eng, i);
2110 qreal y = line.y.toReal() + line.base().toReal();
2111 while (!iterator.atEnd()) {
2112 QScriptItem &si = iterator.next();
2113 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2116 QPointF pos(iterator.x.toReal(), y);
2117 if (from >= 0 && length >= 0 &&
2118 (from >= si.position + eng->length(&si) || from + length <= si.position))
2121 QFont font = eng->font(si);
2123 QTextItem::RenderFlags flags;
2124 if (font.overline())
2125 flags |= QTextItem::Overline;
2126 if (font.underline())
2127 flags |= QTextItem::Underline;
2128 if (font.strikeOut())
2129 flags |= QTextItem::StrikeOut;
2130 if (si.analysis.bidiLevel % 2)
2131 flags |= QTextItem::RightToLeft;
2133 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si).mid(iterator.glyphsStart,
2134 iterator.glyphsEnd - iterator.glyphsStart);
2136 if (glyphLayout.numGlyphs > 0) {
2137 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2138 if (mainFontEngine->type() == QFontEngine::Multi) {
2139 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2142 int which = glyphLayout.glyphs[0] >> 24;
2143 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2144 const int e = glyphLayout.glyphs[end] >> 24;
2148 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2149 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2150 GlyphInfo(subLayout, pos, flags));
2151 for (int i = 0; i < subLayout.numGlyphs; i++)
2152 pos += QPointF(subLayout.advances_x[i].toReal(),
2153 subLayout.advances_y[i].toReal());
2159 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2160 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2161 GlyphInfo(subLayout, pos, flags));
2164 glyphLayoutHash.insertMulti(mainFontEngine,
2165 GlyphInfo(glyphLayout, pos, flags));
2170 QHash<QPair<QFontEngine *, int>, QGlyphs> glyphsHash;
2172 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2173 for (int i=0; i<keys.size(); ++i) {
2174 QFontEngine *fontEngine = keys.at(i);
2176 // Make a font for this particular engine
2178 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2179 fontD->fontEngine = fontEngine;
2180 fontD->fontEngine->ref.ref();
2182 #if defined(Q_WS_WIN)
2183 if (fontEngine->supportsSubPixelPositions())
2184 fontD->hintingPreference = QFont::PreferVerticalHinting;
2186 fontD->hintingPreference = QFont::PreferFullHinting;
2187 #elif defined(Q_WS_MAC)
2188 fontD->hintingPreference = QFont::PreferNoHinting;
2189 #elif !defined(QT_NO_FREETYPE)
2190 if (fontEngine->type() == QFontEngine::Freetype) {
2191 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2192 switch (freeTypeEngine->defaultHintStyle()) {
2193 case QFontEngineFT::HintNone:
2194 fontD->hintingPreference = QFont::PreferNoHinting;
2196 case QFontEngineFT::HintLight:
2197 fontD->hintingPreference = QFont::PreferVerticalHinting;
2199 case QFontEngineFT::HintMedium:
2200 case QFontEngineFT::HintFull:
2201 fontD->hintingPreference = QFont::PreferFullHinting;
2207 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2208 for (int j=0; j<glyphLayouts.size(); ++j) {
2209 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2210 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2211 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2213 QVarLengthArray<glyph_t> glyphsArray;
2214 QVarLengthArray<QFixedPoint> positionsArray;
2216 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2218 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2220 QVector<quint32> glyphs;
2221 QVector<QPointF> positions;
2222 for (int i=0; i<glyphsArray.size(); ++i) {
2223 glyphs.append(glyphsArray.at(i) & 0xffffff);
2224 positions.append(positionsArray.at(i).toPointF() + pos);
2227 QGlyphs glyphIndexes;
2228 glyphIndexes.setGlyphIndexes(glyphs);
2229 glyphIndexes.setPositions(positions);
2231 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2232 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2233 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2234 glyphIndexes.setFont(font);
2236 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2237 if (!glyphsHash.contains(key))
2238 glyphsHash.insert(key, glyphIndexes);
2240 glyphsHash[key] += glyphIndexes;
2244 return glyphsHash.values();
2246 #endif // QT_NO_RAWFONT
2249 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2251 Draws a line on the given \a painter at the specified \a position.
2252 The \a selection is reserved for internal use.
2254 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2256 const QScriptLine &line = eng->lines[i];
2257 QPen pen = p->pen();
2259 bool noText = (selection && selection->format.property(SuppressText).toBool());
2263 && selection->start <= line.from
2264 && selection->start + selection->length > line.from) {
2266 const qreal lineHeight = line.height().toReal();
2267 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2268 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2269 setPenAndDrawBackground(p, QPen(), selection->format, r);
2276 QTextLineItemIterator iterator(eng, i, pos, selection);
2277 QFixed lineBase = line.base();
2279 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2281 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2282 while (!iterator.atEnd()) {
2283 QScriptItem &si = iterator.next();
2285 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2288 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2289 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2292 QFixed itemBaseLine = y;
2293 QFont f = eng->font(si);
2294 QTextCharFormat format;
2296 if (eng->hasFormats() || selection) {
2297 format = eng->format(&si);
2298 if (suppressColors) {
2299 format.clearForeground();
2300 format.clearBackground();
2301 format.clearProperty(QTextFormat::TextUnderlineColor);
2304 format.merge(selection->format);
2306 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2307 iterator.itemWidth.toReal(), line.height().toReal()));
2309 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2310 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2311 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2312 QFixed height = fe->ascent() + fe->descent();
2313 if (valign == QTextCharFormat::AlignSubScript)
2314 itemBaseLine += height / 6;
2315 else if (valign == QTextCharFormat::AlignSuperScript)
2316 itemBaseLine -= height / 2;
2320 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2322 if (eng->hasFormats()) {
2324 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2325 QFixed itemY = y - si.ascent;
2326 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2327 itemY = y - lineBase;
2330 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2332 eng->docLayout()->drawInlineObject(p, itemRect,
2333 QTextInlineObject(iterator.item, eng),
2334 si.position + eng->block.position(),
2337 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2338 if (bg.style() != Qt::NoBrush) {
2339 QColor c = bg.color();
2341 p->fillRect(itemRect, c);
2344 } else { // si.isTab
2345 QFont f = eng->font(si);
2346 QTextItemInt gf(si, &f, format);
2349 gf.width = iterator.itemWidth;
2350 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2351 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2352 QChar visualTab(0x2192);
2353 int w = QFontMetrics(f).width(visualTab);
2354 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2356 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2357 iterator.itemWidth.toReal(), line.height().toReal()),
2361 p->drawText(QPointF(iterator.x.toReal() + x,
2362 y.toReal()), visualTab);
2372 unsigned short *logClusters = eng->logClusters(&si);
2373 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2375 QTextItemInt gf(si, &f, format);
2376 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2377 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2378 gf.logClusters = logClusters + iterator.itemStart - si.position;
2379 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2380 gf.width = iterator.itemWidth;
2381 gf.justified = line.justified;
2383 Q_ASSERT(gf.fontEngine);
2385 if (eng->underlinePositions) {
2386 // can't have selections in this case
2387 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2389 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2390 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2392 path.setFillRule(Qt::WindingFill);
2394 if (gf.glyphs.numGlyphs)
2395 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2397 const QFontEngine *fe = gf.fontEngine;
2398 const qreal lw = fe->lineThickness().toReal();
2399 if (gf.flags & QTextItem::Underline) {
2400 qreal offs = fe->underlinePosition().toReal();
2401 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2403 if (gf.flags & QTextItem::Overline) {
2404 qreal offs = fe->ascent().toReal() + 1;
2405 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2407 if (gf.flags & QTextItem::StrikeOut) {
2408 qreal offs = fe->ascent().toReal() / 3;
2409 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2414 p->setRenderHint(QPainter::Antialiasing);
2415 //Currently QPen with a Qt::NoPen style still returns a default
2416 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2417 if (p->pen().style() == Qt::NoPen)
2418 p->setBrush(Qt::NoBrush);
2420 p->setBrush(p->pen().brush());
2422 p->setPen(format.textOutline());
2427 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2428 p->drawTextItem(pos, gf);
2431 if (si.analysis.flags == QScriptAnalysis::Space
2432 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2433 QBrush c = format.foreground();
2434 if (c.style() != Qt::NoBrush)
2435 p->setPen(c.color());
2436 QChar visualSpace((ushort)0xb7);
2437 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2443 if (eng->hasFormats())
2448 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2454 Converts the cursor position \a cursorPos to the corresponding x position
2455 inside the line, taking account of the \a edge.
2457 If \a cursorPos is not a valid cursor position, the nearest valid
2458 cursor position will be used instead, and cpos will be modified to
2459 point to this valid cursor position.
2463 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2465 if (!eng->layoutData)
2468 const QScriptLine &line = eng->lines[i];
2469 bool lastLine = i >= eng->lines.size() - 1;
2472 x += eng->alignLine(line);
2474 if (!i && !eng->layoutData->items.size()) {
2479 int pos = *cursorPos;
2481 if (pos == line.from + (int)line.length) {
2482 // end of line ensure we have the last item on the line
2483 itm = eng->findItem(pos-1);
2486 itm = eng->findItem(pos);
2487 eng->shapeLine(line);
2489 const QScriptItem *si = &eng->layoutData->items[itm];
2490 if (!si->num_glyphs)
2492 pos -= si->position;
2494 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2495 unsigned short *logClusters = eng->logClusters(si);
2496 Q_ASSERT(logClusters);
2498 int l = eng->length(itm);
2504 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2505 if (edge == Trailing) {
2506 // trailing edge is leading edge of next cluster
2507 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2511 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2513 int lineEnd = line.from + line.length;
2515 // add the items left of the cursor
2517 int firstItem = eng->findItem(line.from);
2518 int lastItem = eng->findItem(lineEnd - 1);
2519 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2521 QVarLengthArray<int> visualOrder(nItems);
2522 QVarLengthArray<uchar> levels(nItems);
2523 for (int i = 0; i < nItems; ++i)
2524 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2525 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2527 for (int i = 0; i < nItems; ++i) {
2528 int item = visualOrder[i]+firstItem;
2531 QScriptItem &si = eng->layoutData->items[item];
2535 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2539 int start = qMax(line.from, si.position);
2540 int end = qMin(lineEnd, si.position + eng->length(item));
2542 logClusters = eng->logClusters(&si);
2544 int gs = logClusters[start-si.position];
2545 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2547 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2550 x += glyphs.effectiveAdvance(gs);
2555 logClusters = eng->logClusters(si);
2556 glyphs = eng->shapedGlyphs(si);
2557 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2558 if (pos == (reverse ? 0 : l))
2561 bool rtl = eng->isRightToLeft();
2562 bool visual = eng->visualCursorMovement();
2564 int end = qMin(lineEnd, si->position + l) - si->position;
2565 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2566 int glyph_start = glyph_pos;
2567 if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem)))
2569 for (int i = glyph_end - 1; i >= glyph_start; i--)
2570 x += glyphs.effectiveAdvance(i);
2572 int start = qMax(line.from - si->position, 0);
2573 int glyph_start = logClusters[start];
2574 int glyph_end = glyph_pos;
2575 if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem))
2577 for (int i = glyph_start; i <= glyph_end; i++)
2578 x += glyphs.effectiveAdvance(i);
2580 x += eng->offsetInLigature(si, pos, line.length, glyph_pos);
2583 *cursorPos = pos + si->position;
2588 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2590 Converts the x-coordinate \a x, to the nearest matching cursor
2591 position, depending on the cursor position type, \a cpos.
2595 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2597 QFixed x = QFixed::fromReal(_x);
2598 const QScriptLine &line = eng->lines[i];
2599 bool lastLine = i >= eng->lines.size() - 1;
2602 if (!eng->layoutData)
2605 int line_length = textLength();
2610 int firstItem = eng->findItem(line.from);
2611 int lastItem = eng->findItem(line.from + line_length - 1);
2612 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2618 x -= eng->alignLine(line);
2619 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2621 QVarLengthArray<int> visualOrder(nItems);
2622 QVarLengthArray<unsigned char> levels(nItems);
2623 for (int i = 0; i < nItems; ++i)
2624 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2625 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2627 bool visual = eng->visualCursorMovement();
2629 // left of first item
2630 int item = visualOrder[0]+firstItem;
2631 QScriptItem &si = eng->layoutData->items[item];
2634 int pos = si.position;
2635 if (si.analysis.bidiLevel % 2)
2636 pos += eng->length(item);
2637 pos = qMax(line.from, pos);
2638 pos = qMin(line.from + line_length, pos);
2640 } else if (x < line.textWidth
2641 || (line.justified && x < line.width)) {
2642 // has to be in one of the runs
2644 bool rtl = eng->isRightToLeft();
2646 eng->shapeLine(line);
2647 QVector<int> insertionPoints;
2649 eng->insertionPointsForLine(lineNum, insertionPoints);
2651 for (int i = 0; i < nItems; ++i) {
2652 int item = visualOrder[i]+firstItem;
2653 QScriptItem &si = eng->layoutData->items[item];
2654 int item_length = eng->length(item);
2655 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2657 int start = qMax(line.from - si.position, 0);
2658 int end = qMin(line.from + line_length - si.position, item_length);
2660 unsigned short *logClusters = eng->logClusters(&si);
2662 int gs = logClusters[start];
2663 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2664 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2666 QFixed item_width = 0;
2667 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2668 item_width = si.width;
2672 item_width += glyphs.effectiveAdvance(g);
2676 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2678 if (pos + item_width < x) {
2683 // qDebug(" inside run");
2684 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2685 if (cpos == QTextLine::CursorOnCharacter)
2687 bool left_half = (x - pos) < item_width/2;
2689 if (bool(si.analysis.bidiLevel % 2) != left_half)
2691 return si.position + 1;
2695 // has to be inside run
2696 if (cpos == QTextLine::CursorOnCharacter) {
2697 if (si.analysis.bidiLevel % 2) {
2701 if (glyphs.attributes[gs].clusterStart) {
2707 pos -= glyphs.effectiveAdvance(gs);
2713 if (glyphs.attributes[gs].clusterStart) {
2718 pos += glyphs.effectiveAdvance(gs);
2723 QFixed dist = INT_MAX/256;
2724 if (si.analysis.bidiLevel % 2) {
2725 if (!visual || rtl || (lastLine && i == nItems - 1)) {
2728 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2732 pos -= glyphs.effectiveAdvance(gs);
2737 if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) {
2741 pos += glyphs.effectiveAdvance(ge);
2746 if (!visual || !rtl || (lastLine && i == 0)) {
2748 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2752 pos += glyphs.effectiveAdvance(gs);
2756 QFixed oldPos = pos;
2758 pos += glyphs.effectiveAdvance(gs);
2759 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2768 if (qAbs(x-pos) < dist) {
2770 if (!rtl && i < nItems - 1) {
2774 if (rtl && nchars > 0)
2775 return insertionPoints[lastLine ? nchars : nchars - 1];
2777 return si.position + end;
2780 Q_ASSERT(glyph_pos != -1);
2782 for (j = 0; j < eng->length(item); ++j)
2783 if (logClusters[j] == glyph_pos)
2785 // qDebug("at pos %d (in run: %d)", si.position + j, j);
2786 return si.position + j;
2789 // right of last item
2790 // qDebug() << "right of last";
2791 int item = visualOrder[nItems-1]+firstItem;
2792 QScriptItem &si = eng->layoutData->items[item];
2795 int pos = si.position;
2796 if (!(si.analysis.bidiLevel % 2))
2797 pos += eng->length(item);
2798 pos = qMax(line.from, pos);
2800 int maxPos = line.from + line_length;
2802 // except for the last line we assume that the
2803 // character between lines is a space and we want
2804 // to position the cursor to the left of that
2806 // ###### breaks with japanese for example
2807 if (this->i < eng->lines.count() - 1)
2810 pos = qMin(pos, maxPos);