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
75 static QFixed alignLine(QTextEngine *eng, const QScriptLine &line)
79 // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
80 if (!line.justified && line.width != QFIXED_MAX) {
81 int align = eng->option.alignment();
82 if (align & Qt::AlignJustify && eng->isRightToLeft())
83 align = Qt::AlignRight;
84 if (align & Qt::AlignRight)
85 x = line.width - (line.textAdvance + eng->leadingSpaceWidth(line));
86 else if (align & Qt::AlignHCenter)
87 x = (line.width - line.textAdvance)/2;
93 \class QTextLayout::FormatRange
96 \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
97 for a specified area in the text layout's content.
99 \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw()
103 \variable QTextLayout::FormatRange::start
104 Specifies the beginning of the format range within the text layout's text.
108 \variable QTextLayout::FormatRange::length
109 Specifies the numer of characters the format range spans.
113 \variable QTextLayout::FormatRange::format
114 Specifies the format to apply.
118 \class QTextInlineObject
121 \brief The QTextInlineObject class represents an inline object in
124 \ingroup richtext-processing
126 This class is only used if the text layout is used to lay out
127 parts of a QTextDocument.
129 The inline object has various attributes that can be set, for
130 example using, setWidth(), setAscent(), and setDescent(). The
131 rectangle it occupies is given by rect(), and its direction by
132 isRightToLeft(). Its position in the text layout is given by at(),
133 and its format is given by format().
137 \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
139 Creates a new inline object for the item at position \a i in the
144 \fn QTextInlineObject::QTextInlineObject()
150 \fn bool QTextInlineObject::isValid() const
152 Returns true if this inline object is valid; otherwise returns
157 Returns the inline object's rectangle.
159 \sa ascent(), descent(), width()
161 QRectF QTextInlineObject::rect() const
163 QScriptItem& si = eng->layoutData->items[itm];
164 return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
168 Returns the inline object's width.
170 \sa ascent(), descent(), rect()
172 qreal QTextInlineObject::width() const
174 return eng->layoutData->items[itm].width.toReal();
178 Returns the inline object's ascent.
180 \sa descent(), width(), rect()
182 qreal QTextInlineObject::ascent() const
184 return eng->layoutData->items[itm].ascent.toReal();
188 Returns the inline object's descent.
190 \sa ascent(), width(), rect()
192 qreal QTextInlineObject::descent() const
194 return eng->layoutData->items[itm].descent.toReal();
198 Returns the inline object's total height. This is equal to
199 ascent() + descent() + 1.
201 \sa ascent(), descent(), width(), rect()
203 qreal QTextInlineObject::height() const
205 return eng->layoutData->items[itm].height().toReal();
209 Sets the inline object's width to \a w.
211 \sa width(), ascent(), descent(), rect()
213 void QTextInlineObject::setWidth(qreal w)
215 eng->layoutData->items[itm].width = QFixed::fromReal(w);
219 Sets the inline object's ascent to \a a.
221 \sa ascent(), setDescent(), width(), rect()
223 void QTextInlineObject::setAscent(qreal a)
225 eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
229 Sets the inline object's decent to \a d.
231 \sa descent(), setAscent(), width(), rect()
233 void QTextInlineObject::setDescent(qreal d)
235 eng->layoutData->items[itm].descent = QFixed::fromReal(d);
239 The position of the inline object within the text layout.
241 int QTextInlineObject::textPosition() const
243 return eng->layoutData->items[itm].position;
247 Returns an integer describing the format of the inline object
248 within the text layout.
250 int QTextInlineObject::formatIndex() const
252 return eng->formatIndex(&eng->layoutData->items[itm]);
256 Returns format of the inline object within the text layout.
258 QTextFormat QTextInlineObject::format() const
260 if (!eng->block.docHandle())
261 return QTextFormat();
262 return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
266 Returns if the object should be laid out right-to-left or left-to-right.
268 Qt::LayoutDirection QTextInlineObject::textDirection() const
270 return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
277 \brief The QTextLayout class is used to lay out and render text.
279 \ingroup richtext-processing
281 It offers many features expected from a modern text layout
282 engine, including Unicode compliant rendering, line breaking and
283 handling of cursor positioning. It can also produce and render
284 device independent layout, something that is important for WYSIWYG
287 The class has a rather low level API and unless you intend to
288 implement your own text rendering for some specialized widget, you
289 probably won't need to use it directly.
291 QTextLayout can be used with both plain and rich text.
293 QTextLayout can be used to create a sequence of QTextLine
294 instances with given widths and can position them independently
295 on the screen. Once the layout is done, these lines can be drawn
298 The text to be laid out can be provided in the constructor or set with
301 The layout can be seen as a sequence of QTextLine objects; use createLine()
302 to create a QTextLine instance, and lineAt() or lineForTextPosition() to retrieve
305 Here is a code snippet that demonstrates the layout phase:
306 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 0
308 The text can then be rendered by calling the layout's draw() function:
309 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 1
311 For a given position in the text you can find a valid cursor position with
312 isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition().
314 The QTextLayout itself can be positioned with setPosition(); it has a
315 boundingRect(), and a minimumWidth() and a maximumWidth().
321 \enum QTextLayout::CursorMode
323 \value SkipCharacters
328 \fn QTextEngine *QTextLayout::engine() const
331 Returns the text engine used to render the text layout.
335 Constructs an empty text layout.
339 QTextLayout::QTextLayout()
340 { d = new QTextEngine(); }
343 Constructs a text layout to lay out the given \a text.
345 QTextLayout::QTextLayout(const QString& text)
347 d = new QTextEngine();
352 Constructs a text layout to lay out the given \a text with the specified
355 All the metric and layout calculations will be done in terms of
356 the paint device, \a paintdevice. If \a paintdevice is 0 the
357 calculations will be done in screen metrics.
359 QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
363 f = QFont(font, paintdevice);
364 d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f.d.data());
369 Constructs a text layout to lay out the given \a block.
371 QTextLayout::QTextLayout(const QTextBlock &block)
373 d = new QTextEngine();
378 Destructs the layout.
380 QTextLayout::~QTextLayout()
387 Sets the layout's font to the given \a font. The layout is
388 invalidated and must be laid out again.
392 void QTextLayout::setFont(const QFont &font)
399 Returns the current font that is used for the layout, or a default
404 QFont QTextLayout::font() const
410 Sets the layout's text to the given \a string. The layout is
411 invalidated and must be laid out again.
413 Notice that when using this QTextLayout as part of a QTextDocument this
414 method will have no effect.
418 void QTextLayout::setText(const QString& string)
426 Returns the layout's text.
430 QString QTextLayout::text() const
436 Sets the text option structure that controls the layout process to the
441 void QTextLayout::setTextOption(const QTextOption &option)
447 Returns the current text option used to control the layout process.
451 QTextOption QTextLayout::textOption() const
457 Sets the \a position and \a text of the area in the layout that is
458 processed before editing occurs.
460 \sa preeditAreaPosition(), preeditAreaText()
462 void QTextLayout::setPreeditArea(int position, const QString &text)
464 if (text.isEmpty()) {
467 if (d->specialData->addFormats.isEmpty()) {
468 delete d->specialData;
471 d->specialData->preeditText = QString();
472 d->specialData->preeditPosition = -1;
476 d->specialData = new QTextEngine::SpecialData;
477 d->specialData->preeditPosition = position;
478 d->specialData->preeditText = text;
482 if (d->block.docHandle())
483 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
487 Returns the position of the area in the text layout that will be
488 processed before editing occurs.
490 \sa preeditAreaText()
492 int QTextLayout::preeditAreaPosition() const
494 return d->specialData ? d->specialData->preeditPosition : -1;
498 Returns the text that is inserted in the layout before editing occurs.
500 \sa preeditAreaPosition()
502 QString QTextLayout::preeditAreaText() const
504 return d->specialData ? d->specialData->preeditText : QString();
509 Sets the additional formats supported by the text layout to \a formatList.
511 \sa additionalFormats(), clearAdditionalFormats()
513 void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
515 if (formatList.isEmpty()) {
518 if (d->specialData->preeditText.isEmpty()) {
519 delete d->specialData;
522 d->specialData->addFormats = formatList;
523 d->specialData->addFormatIndices.clear();
526 if (!d->specialData) {
527 d->specialData = new QTextEngine::SpecialData;
528 d->specialData->preeditPosition = -1;
530 d->specialData->addFormats = formatList;
531 d->indexAdditionalFormats();
533 if (d->block.docHandle())
534 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
539 Returns the list of additional formats supported by the text layout.
541 \sa setAdditionalFormats(), clearAdditionalFormats()
543 QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
545 QList<FormatRange> formats;
549 formats = d->specialData->addFormats;
551 if (d->specialData->addFormatIndices.isEmpty())
554 const QTextFormatCollection *collection = d->formats();
556 for (int i = 0; i < d->specialData->addFormatIndices.count(); ++i)
557 formats[i].format = collection->charFormat(d->specialData->addFormatIndices.at(i));
563 Clears the list of additional formats supported by the text layout.
565 \sa additionalFormats(), setAdditionalFormats()
567 void QTextLayout::clearAdditionalFormats()
569 setAdditionalFormats(QList<FormatRange>());
573 Enables caching of the complete layout information if \a enable is
574 true; otherwise disables layout caching. Usually
575 QTextLayout throws most of the layouting information away after a
576 call to endLayout() to reduce memory consumption. If you however
577 want to draw the laid out text directly afterwards enabling caching
578 might speed up drawing significantly.
582 void QTextLayout::setCacheEnabled(bool enable)
584 d->cacheGlyphs = enable;
588 Returns true if the complete layout information is cached; otherwise
591 \sa setCacheEnabled()
593 bool QTextLayout::cacheEnabled() const
595 return d->cacheGlyphs;
599 Begins the layout process.
603 void QTextLayout::beginLayout()
606 if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) {
607 qWarning("QTextLayout::beginLayout: Called while already doing layout");
614 d->layoutData->layoutState = QTextEngine::InLayout;
618 Ends the layout process.
622 void QTextLayout::endLayout()
625 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
626 qWarning("QTextLayout::endLayout: Called without beginLayout()");
630 int l = d->lines.size();
631 if (l && d->lines.at(l-1).length < 0) {
632 QTextLine(l-1, d).setNumColumns(INT_MAX);
634 d->layoutData->layoutState = QTextEngine::LayoutEmpty;
642 Clears the line information in the layout. After having called
643 this function, lineCount() returns 0.
645 void QTextLayout::clearLayout()
651 Returns the next valid cursor position after \a oldPos that
652 respects the given cursor \a mode.
653 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
655 \sa isValidCursorPosition(), previousCursorPosition()
657 int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
659 const HB_CharAttributes *attributes = d->attributes();
660 int len = d->block.isValid() ? d->block.length() - 1
661 : d->layoutData->string.length();
662 Q_ASSERT(len <= d->layoutData->string.length());
663 if (!attributes || oldPos < 0 || oldPos >= len)
666 if (mode == SkipCharacters) {
668 while (oldPos < len && !attributes[oldPos].charStop)
671 if (oldPos < len && d->atWordSeparator(oldPos)) {
673 while (oldPos < len && d->atWordSeparator(oldPos))
676 while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
679 while (oldPos < len && d->atSpace(oldPos))
687 Returns the first valid cursor position before \a oldPos that
688 respects the given cursor \a mode.
689 Returns value of \a oldPos, if \a oldPos is not a valid cursor position.
691 \sa isValidCursorPosition(), nextCursorPosition()
693 int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
695 const HB_CharAttributes *attributes = d->attributes();
696 if (!attributes || oldPos <= 0 || oldPos > d->layoutData->string.length())
699 if (mode == SkipCharacters) {
701 while (oldPos && !attributes[oldPos].charStop)
704 while (oldPos && d->atSpace(oldPos-1))
707 if (oldPos && d->atWordSeparator(oldPos-1)) {
709 while (oldPos && d->atWordSeparator(oldPos-1))
712 while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
721 Returns true if position \a pos is a valid cursor position.
723 In a Unicode context some positions in the text are not valid
724 cursor positions, because the position is inside a Unicode
725 surrogate or a grapheme cluster.
727 A grapheme cluster is a sequence of two or more Unicode characters
728 that form one indivisible entity on the screen. For example the
729 latin character `\Auml' can be represented in Unicode by two
730 characters, `A' (0x41), and the combining diaresis (0x308). A text
731 cursor can only validly be positioned before or after these two
732 characters, never between them since that wouldn't make sense. In
733 indic languages every syllable forms a grapheme cluster.
735 bool QTextLayout::isValidCursorPosition(int pos) const
737 const HB_CharAttributes *attributes = d->attributes();
738 if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
740 return attributes[pos].charStop;
744 Returns a new text line to be laid out if there is text to be
745 inserted into the layout; otherwise returns an invalid text line.
747 The text layout creates a new line object that starts after the
748 last line in the layout, or at the beginning if the layout is empty.
749 The layout maintains an internal cursor, and each line is filled
750 with text from the cursor position onwards when the
751 QTextLine::setLineWidth() function is called.
753 Once QTextLine::setLineWidth() is called, a new line can be created and
754 filled with text. Repeating this process will lay out the whole block
755 of text contained in the QTextLayout. If there is no text left to be
756 inserted into the layout, the QTextLine returned will not be valid
757 (isValid() will return false).
759 QTextLine QTextLayout::createLine()
762 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
763 qWarning("QTextLayout::createLine: Called without layouting");
767 if (d->layoutData->layoutState == QTextEngine::LayoutFailed)
770 int l = d->lines.size();
771 if (l && d->lines.at(l-1).length < 0) {
772 QTextLine(l-1, d).setNumColumns(INT_MAX);
774 int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length : 0;
775 int strlen = d->layoutData->string.length();
776 if (l && from >= strlen) {
777 if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
784 line.justified = false;
785 line.gridfitted = false;
787 d->lines.append(line);
788 return QTextLine(l, d);
792 Returns the number of lines in this text layout.
796 int QTextLayout::lineCount() const
798 return d->lines.size();
802 Returns the \a{i}-th line of text in this text layout.
804 \sa lineCount(), lineForTextPosition()
806 QTextLine QTextLayout::lineAt(int i) const
808 return QTextLine(i, d);
812 Returns the line that contains the cursor position specified by \a pos.
814 \sa isValidCursorPosition(), lineAt()
816 QTextLine QTextLayout::lineForTextPosition(int pos) const
818 for (int i = 0; i < d->lines.size(); ++i) {
819 const QScriptLine& line = d->lines[i];
820 if (line.from + (int)line.length > pos)
821 return QTextLine(i, d);
825 if (pos == d->layoutData->string.length() && d->lines.size())
826 return QTextLine(d->lines.size()-1, d);
833 The global position of the layout. This is independent of the
834 bounding rectangle and of the layout process.
838 QPointF QTextLayout::position() const
844 Moves the text layout to point \a p.
848 void QTextLayout::setPosition(const QPointF &p)
854 The smallest rectangle that contains all the lines in the layout.
856 QRectF QTextLayout::boundingRect() const
858 if (d->lines.isEmpty())
862 QFixed xmin = d->lines.at(0).x;
863 QFixed ymin = d->lines.at(0).y;
865 for (int i = 0; i < d->lines.size(); ++i) {
866 const QScriptLine &si = d->lines[i];
867 xmin = qMin(xmin, si.x);
868 ymin = qMin(ymin, si.y);
869 QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth;
870 xmax = qMax(xmax, si.x+lineWidth);
871 // ### shouldn't the ascent be used in ymin???
872 ymax = qMax(ymax, si.y+si.height());
874 return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
878 The minimum width the layout needs. This is the width of the
879 layout's smallest non-breakable substring.
881 \warning This function only returns a valid value after the layout
886 qreal QTextLayout::minimumWidth() const
888 return d->minWidth.toReal();
892 The maximum width the layout could expand to; this is essentially
893 the width of the entire text.
895 \warning This function only returns a valid value after the layout
900 qreal QTextLayout::maximumWidth() const
902 return d->maxWidth.toReal();
909 void QTextLayout::setFlags(int flags)
911 if (flags & Qt::TextJustificationForced) {
912 d->option.setAlignment(Qt::AlignJustify);
913 d->forceJustification = true;
916 if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
917 d->ignoreBidi = true;
918 d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
922 struct QTextLineItemIterator
924 QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
925 const QTextLayout::FormatRange *_selection = 0);
927 inline bool atEnd() const { return logicalItem >= nItems - 1; }
930 bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
931 inline bool isOutsideSelection() const {
933 return !getSelectionBounds(&tmp1, &tmp2);
940 const QScriptLine &line;
958 QVarLengthArray<int> visualOrder;
959 QVarLengthArray<uchar> levels;
961 const QTextLayout::FormatRange *selection;
964 QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int lineNum, const QPointF &pos,
965 const QTextLayout::FormatRange *_selection)
967 line(eng->lines[lineNum]),
969 lineEnd(line.from + line.length),
970 firstItem(eng->findItem(line.from)),
971 lastItem(eng->findItem(lineEnd - 1)),
972 nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
977 selection(_selection)
979 pos_x = x = QFixed::fromReal(pos.x());
983 x += alignLine(eng, line);
985 for (int i = 0; i < nItems; ++i)
986 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
987 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
989 eng->shapeLine(line);
992 QScriptItem &QTextLineItemIterator::next()
997 item = visualOrder[logicalItem] + firstItem;
998 itemLength = eng->length(item);
999 si = &eng->layoutData->items[item];
1000 if (!si->num_glyphs)
1003 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
1004 itemWidth = si->width;
1008 unsigned short *logClusters = eng->logClusters(si);
1009 QGlyphLayout glyphs = eng->shapedGlyphs(si);
1011 itemStart = qMax(line.from, si->position);
1012 glyphsStart = logClusters[itemStart - si->position];
1013 if (lineEnd < si->position + itemLength) {
1015 glyphsEnd = logClusters[itemEnd-si->position];
1017 itemEnd = si->position + itemLength;
1018 glyphsEnd = si->num_glyphs;
1020 // show soft-hyphen at line-break
1021 if (si->position + itemLength >= lineEnd
1022 && eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
1023 glyphs.attributes[glyphsEnd - 1].dontPrint = false;
1026 for (int g = glyphsStart; g < glyphsEnd; ++g)
1027 itemWidth += glyphs.effectiveAdvance(g);
1032 static QFixed offsetInLigature(const unsigned short *logClusters,
1033 const QGlyphLayout &glyphs,
1034 int pos, int max, int glyph_pos)
1036 int offsetInCluster = 0;
1037 for (int i = pos - 1; i >= 0; i--) {
1038 if (logClusters[i] == glyph_pos)
1044 // in the case that the offset is inside a (multi-character) glyph,
1045 // interpolate the position.
1046 if (offsetInCluster > 0) {
1047 int clusterLength = 0;
1048 for (int i = pos - offsetInCluster; i < max; i++) {
1049 if (logClusters[i] == glyph_pos)
1055 return glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
1061 bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
1063 *selectionX = *selectionWidth = 0;
1068 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
1069 if (si->position >= selection->start + selection->length
1070 || si->position + itemLength <= selection->start)
1074 *selectionWidth = itemWidth;
1076 unsigned short *logClusters = eng->logClusters(si);
1077 QGlyphLayout glyphs = eng->shapedGlyphs(si);
1079 int from = qMax(itemStart, selection->start) - si->position;
1080 int to = qMin(itemEnd, selection->start + selection->length) - si->position;
1084 int start_glyph = logClusters[from];
1085 int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
1088 if (si->analysis.bidiLevel %2) {
1089 for (int g = glyphsEnd - 1; g >= end_glyph; --g)
1090 soff += glyphs.effectiveAdvance(g);
1091 for (int g = end_glyph - 1; g >= start_glyph; --g)
1092 swidth += glyphs.effectiveAdvance(g);
1094 for (int g = glyphsStart; g < start_glyph; ++g)
1095 soff += glyphs.effectiveAdvance(g);
1096 for (int g = start_glyph; g < end_glyph; ++g)
1097 swidth += glyphs.effectiveAdvance(g);
1100 // If the starting character is in the middle of a ligature,
1101 // selection should only contain the right part of that ligature
1102 // glyph, so we need to get the width of the left part here and
1103 // add it to *selectionX
1104 QFixed leftOffsetInLigature = offsetInLigature(logClusters, glyphs, from,
1106 *selectionX = x + soff + leftOffsetInLigature;
1107 *selectionWidth = swidth - leftOffsetInLigature;
1108 // If the ending character is also part of a ligature, swidth does
1109 // not contain that part yet, we also need to find out the width of
1111 *selectionWidth += offsetInLigature(logClusters, glyphs, to,
1112 eng->length(item), end_glyph);
1117 static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
1118 QPainterPath *region, QRectF boundingRect)
1120 const QScriptLine &line = eng->lines[lineNumber];
1122 QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
1126 const qreal selectionY = pos.y() + line.y.toReal();
1127 const qreal lineHeight = line.height().toReal();
1129 QFixed lastSelectionX = iterator.x;
1130 QFixed lastSelectionWidth;
1132 while (!iterator.atEnd()) {
1135 QFixed selectionX, selectionWidth;
1136 if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
1137 if (selectionX == lastSelectionX + lastSelectionWidth) {
1138 lastSelectionWidth += selectionWidth;
1142 if (lastSelectionWidth > 0)
1143 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
1145 lastSelectionX = selectionX;
1146 lastSelectionWidth = selectionWidth;
1149 if (lastSelectionWidth > 0)
1150 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
1153 static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
1155 return clip.isValid() ? (rect & clip) : rect;
1160 Returns the glyph indexes and positions for all glyphs in this QTextLayout. This is an
1161 expensive function, and should not be called in a time sensitive context.
1165 \sa draw(), QPainter::drawGlyphs()
1167 #if !defined(QT_NO_RAWFONT)
1168 QList<QGlyphs> QTextLayout::glyphs() const
1170 QList<QGlyphs> glyphs;
1171 for (int i=0; i<d->lines.size(); ++i)
1172 glyphs += QTextLine(i, d).glyphs(-1, -1);
1176 #endif // QT_NO_RAWFONT
1179 Draws the whole layout on the painter \a p at the position specified by \a pos.
1180 The rendered layout includes the given \a selections and is clipped within
1181 the rectangle specified by \a clip.
1183 void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
1185 if (d->lines.isEmpty())
1191 QPointF position = pos + d->position;
1193 QFixed clipy = (INT_MIN/256);
1194 QFixed clipe = (INT_MAX/256);
1195 if (clip.isValid()) {
1196 clipy = QFixed::fromReal(clip.y() - position.y());
1197 clipe = clipy + QFixed::fromReal(clip.height());
1201 int lastLine = d->lines.size();
1202 for (int i = 0; i < d->lines.size(); ++i) {
1204 const QScriptLine &sl = d->lines[i];
1210 if ((sl.y + sl.height()) < clipy) {
1216 QPainterPath excludedRegion;
1217 QPainterPath textDoneRegion;
1218 for (int i = 0; i < selections.size(); ++i) {
1219 FormatRange selection = selections.at(i);
1220 const QBrush bg = selection.format.background();
1222 QPainterPath region;
1223 region.setFillRule(Qt::WindingFill);
1225 for (int line = firstLine; line < lastLine; ++line) {
1226 const QScriptLine &sl = d->lines[line];
1227 QTextLine tl(line, d);
1229 QRectF lineRect(tl.naturalTextRect());
1230 lineRect.translate(position);
1232 bool isLastLineInBlock = (line == d->lines.size()-1);
1233 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1236 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1237 continue; // no actual intersection
1239 const bool selectionStartInLine = sl.from <= selection.start;
1240 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1242 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1243 addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip));
1245 region.addRect(clipIfValid(lineRect, clip));
1248 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1249 QRectF fullLineRect(tl.rect());
1250 fullLineRect.translate(position);
1251 fullLineRect.setRight(QFIXED_MAX);
1252 if (!selectionEndInLine)
1253 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1254 if (!selectionStartInLine)
1255 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1256 } else if (!selectionEndInLine
1257 && isLastLineInBlock
1258 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1259 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1260 lineRect.height()/4, lineRect.height()), clip));
1265 const QPen oldPen = p->pen();
1266 const QBrush oldBrush = p->brush();
1268 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1269 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1270 p->drawPath(region);
1273 p->setBrush(oldBrush);
1278 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1279 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1281 if (hasBackground) {
1282 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1283 // don't just clear the property, set an empty brush that overrides a potential
1284 // background brush specified in the text
1285 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1286 selection.format.clearProperty(QTextFormat::OutlinePen);
1289 selection.format.setProperty(SuppressText, !hasText);
1291 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1295 p->setClipPath(region, Qt::IntersectClip);
1297 for (int line = firstLine; line < lastLine; ++line) {
1298 QTextLine l(line, d);
1299 l.draw(p, position, &selection);
1304 textDoneRegion += region;
1307 textDoneRegion -= region;
1310 excludedRegion += region;
1313 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1314 if (!needsTextButNoBackground.isEmpty()){
1316 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1317 FormatRange selection;
1318 selection.start = 0;
1319 selection.length = INT_MAX;
1320 selection.format.setProperty(SuppressBackground, true);
1321 for (int line = firstLine; line < lastLine; ++line) {
1322 QTextLine l(line, d);
1323 l.draw(p, position, &selection);
1328 if (!excludedRegion.isEmpty()) {
1331 QRectF br = boundingRect().translated(position);
1332 br.setRight(QFIXED_MAX);
1334 br = br.intersected(clip);
1336 path -= excludedRegion;
1337 p->setClipPath(path, Qt::IntersectClip);
1340 for (int i = firstLine; i < lastLine; ++i) {
1342 l.draw(p, position);
1344 if (!excludedRegion.isEmpty())
1348 if (!d->cacheGlyphs)
1353 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1356 Draws a text cursor with the current pen at the given \a position using the
1357 \a painter specified.
1358 The corresponding position within the text is specified by \a cursorPosition.
1360 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1362 drawCursor(p, pos, cursorPosition, 1);
1366 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1368 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1369 \a painter specified.
1370 The corresponding position within the text is specified by \a cursorPosition.
1372 void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1374 if (d->lines.isEmpty())
1380 QPointF position = pos + d->position;
1381 QFixed pos_x = QFixed::fromReal(position.x());
1382 QFixed pos_y = QFixed::fromReal(position.y());
1384 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1386 if (cursorPosition == d->layoutData->string.length()) {
1387 line = d->lines.size() - 1;
1389 // ### binary search
1390 for (line = 0; line < d->lines.size(); line++) {
1391 const QScriptLine &sl = d->lines[line];
1392 if (sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
1397 if (line >= d->lines.size())
1400 QTextLine l(line, d);
1401 const QScriptLine &sl = d->lines[line];
1403 qreal x = position.x() + l.cursorToX(cursorPosition);
1405 int itm = d->findItem(cursorPosition - 1);
1406 QFixed base = sl.base();
1407 QFixed descent = sl.descent;
1408 bool rightToLeft = d->isRightToLeft();
1410 const QScriptItem &si = d->layoutData->items.at(itm);
1414 descent = si.descent;
1415 rightToLeft = si.analysis.bidiLevel % 2;
1417 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1418 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1419 && (p->transform().type() > QTransform::TxTranslate);
1420 if (toggleAntialiasing)
1421 p->setRenderHint(QPainter::Antialiasing);
1422 #if defined(QT_MAC_USE_COCOA)
1423 // Always draw the cursor aligned to pixel boundary.
1426 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1427 if (toggleAntialiasing)
1428 p->setRenderHint(QPainter::Antialiasing, false);
1429 if (d->layoutData->hasBidi) {
1430 const int arrow_extent = 4;
1431 int sign = rightToLeft ? -1 : 1;
1432 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1433 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1442 \brief The QTextLine class represents a line of text inside a QTextLayout.
1444 \ingroup richtext-processing
1446 A text line is usually created by QTextLayout::createLine().
1448 After being created, the line can be filled using the setLineWidth()
1449 or setNumColumns() functions. A line has a number of attributes including the
1450 rectangle it occupies, rect(), its coordinates, x() and y(), its
1451 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1452 relative to the text. The position of the cursor in terms of the
1453 line is available from cursorToX() and its inverse from
1454 xToCursor(). A line can be moved with setPosition().
1458 \enum QTextLine::Edge
1465 \enum QTextLine::CursorPosition
1467 \value CursorBetweenCharacters
1468 \value CursorOnCharacter
1472 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1475 Constructs a new text line using the line at position \a line in
1476 the text engine \a e.
1480 \fn QTextLine::QTextLine()
1482 Creates an invalid line.
1486 \fn bool QTextLine::isValid() const
1488 Returns true if this text line is valid; otherwise returns false.
1492 \fn int QTextLine::lineNumber() const
1494 Returns the position of the line in the text engine.
1499 Returns the line's bounding rectangle.
1501 \sa x(), y(), textLength(), width()
1503 QRectF QTextLine::rect() const
1505 const QScriptLine& sl = eng->lines[i];
1506 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1510 Returns the rectangle covered by the line.
1512 QRectF QTextLine::naturalTextRect() const
1514 const QScriptLine& sl = eng->lines[i];
1515 QFixed x = sl.x + alignLine(eng, sl);
1517 QFixed width = sl.textWidth;
1521 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1525 Returns the line's x position.
1527 \sa rect(), y(), textLength(), width()
1529 qreal QTextLine::x() const
1531 return eng->lines[i].x.toReal();
1535 Returns the line's y position.
1537 \sa x(), rect(), textLength(), width()
1539 qreal QTextLine::y() const
1541 return eng->lines[i].y.toReal();
1545 Returns the line's width as specified by the layout() function.
1547 \sa naturalTextWidth(), x(), y(), textLength(), rect()
1549 qreal QTextLine::width() const
1551 return eng->lines[i].width.toReal();
1556 Returns the line's ascent.
1558 \sa descent(), height()
1560 qreal QTextLine::ascent() const
1562 return eng->lines[i].ascent.toReal();
1566 Returns the line's descent.
1568 \sa ascent(), height()
1570 qreal QTextLine::descent() const
1572 return eng->lines[i].descent.toReal();
1576 Returns the line's height. This is equal to ascent() + descent() + 1
1577 if leading is not included. If leading is included, this equals to
1578 ascent() + descent() + leading() + 1.
1580 \sa ascent(), descent(), leading(), setLeadingIncluded()
1582 qreal QTextLine::height() const
1584 return eng->lines[i].height().toReal();
1590 Returns the line's leading.
1592 \sa ascent(), descent(), height()
1594 qreal QTextLine::leading() const
1596 return eng->lines[i].leading.toReal();
1602 Includes positive leading into the line's height if \a included is true;
1603 otherwise does not include leading.
1605 By default, leading is not included.
1607 Note that negative leading is ignored, it must be handled
1608 in the code using the text lines by letting the lines overlap.
1610 \sa leadingIncluded()
1613 void QTextLine::setLeadingIncluded(bool included)
1615 eng->lines[i].leadingIncluded= included;
1622 Returns true if positive leading is included into the line's height;
1623 otherwise returns false.
1625 By default, leading is not included.
1627 \sa setLeadingIncluded()
1629 bool QTextLine::leadingIncluded() const
1631 return eng->lines[i].leadingIncluded;
1635 Returns the width of the line that is occupied by text. This is
1636 always \<= to width(), and is the minimum width that could be used
1637 by layout() without changing the line break position.
1639 qreal QTextLine::naturalTextWidth() const
1641 return eng->lines[i].textWidth.toReal();
1646 Returns the horizontal advance of the text. The advance of the text
1647 is the distance from its position to the next position at which
1648 text would naturally be drawn.
1650 By adding the advance to the position of the text line and using this
1651 as the position of a second text line, you will be able to position
1652 the two lines side-by-side without gaps in-between.
1654 qreal QTextLine::horizontalAdvance() const
1656 return eng->lines[i].textAdvance.toReal();
1660 Lays out the line with the given \a width. The line is filled from
1661 its starting position with as many characters as will fit into
1662 the line. In case the text cannot be split at the end of the line,
1663 it will be filled with additional characters to the next whitespace
1666 void QTextLine::setLineWidth(qreal width)
1668 QScriptLine &line = eng->lines[i];
1669 if (!eng->layoutData) {
1670 qWarning("QTextLine: Can't set a line width while not layouting.");
1674 if (width > QFIXED_MAX)
1677 line.width = QFixed::fromReal(width);
1679 && line.textWidth <= line.width
1680 && line.from + line.length == eng->layoutData->string.length())
1681 // no need to do anything if the line is already layouted and the last one. This optimization helps
1682 // when using things in a single line layout.
1687 layout_helper(INT_MAX);
1691 Lays out the line. The line is filled from its starting position
1692 with as many characters as are specified by \a numColumns. In case
1693 the text cannot be split until \a numColumns characters, the line
1694 will be filled with as many characters to the next whitespace or
1697 void QTextLine::setNumColumns(int numColumns)
1699 QScriptLine &line = eng->lines[i];
1700 line.width = QFIXED_MAX;
1703 layout_helper(numColumns);
1707 Lays out the line. The line is filled from its starting position
1708 with as many characters as are specified by \a numColumns. In case
1709 the text cannot be split until \a numColumns characters, the line
1710 will be filled with as many characters to the next whitespace or
1711 end of the text. The provided \a alignmentWidth is used as reference
1712 width for alignment.
1714 void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1716 QScriptLine &line = eng->lines[i];
1717 line.width = QFixed::fromReal(alignmentWidth);
1720 layout_helper(numColumns);
1724 #define LB_DEBUG qDebug
1726 #define LB_DEBUG if (0) qDebug
1731 struct LineBreakHelper
1734 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1735 manualWrap(false), whiteSpaceOrObject(true)
1740 QScriptLine tmpData;
1741 QScriptLine spaceData;
1743 QGlyphLayout glyphs;
1747 int currentPosition;
1748 glyph_t previousGlyph;
1751 QFixed softHyphenWidth;
1752 QFixed rightBearing;
1753 QFixed minimumRightBearing;
1755 QFontEngine *fontEngine;
1756 const unsigned short *logClusters;
1759 bool whiteSpaceOrObject;
1761 bool checkFullOtherwiseExtend(QScriptLine &line);
1763 QFixed calculateNewWidth(const QScriptLine &line) const {
1764 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1765 - qMin(rightBearing, QFixed());
1768 inline glyph_t currentGlyph() const
1770 Q_ASSERT(currentPosition > 0);
1771 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1773 return glyphs.glyphs[logClusters[currentPosition - 1]];
1776 inline void saveCurrentGlyph()
1779 if (currentPosition > 0 &&
1780 logClusters[currentPosition - 1] < glyphs.numGlyphs) {
1781 previousGlyph = currentGlyph(); // needed to calculate right bearing later
1785 inline void adjustRightBearing(glyph_t glyph)
1788 fontEngine->getGlyphBearings(glyph, 0, &rb);
1789 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1792 inline void adjustRightBearing()
1794 if (currentPosition <= 0)
1796 adjustRightBearing(currentGlyph());
1799 inline void adjustPreviousRightBearing()
1801 if (previousGlyph > 0)
1802 adjustRightBearing(previousGlyph);
1805 inline void resetRightBearing()
1807 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1808 // negative right bearings are interesting to us.
1812 inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1814 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1816 QFixed newWidth = calculateNewWidth(line);
1817 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1820 minw = qMax(minw, tmpData.textWidth);
1822 line.textWidth += spaceData.textWidth;
1824 line.length += spaceData.length;
1825 tmpData.textWidth = 0;
1827 spaceData.textWidth = 0;
1828 spaceData.length = 0;
1833 } // anonymous namespace
1836 static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1837 const QScriptItem ¤t, const unsigned short *logClusters,
1838 const QGlyphLayout &glyphs)
1840 int glyphPosition = logClusters[pos];
1841 do { // got to the first next cluster
1844 } while (pos < end && logClusters[pos] == glyphPosition);
1845 do { // calculate the textWidth for the rest of the current cluster.
1846 if (!glyphs.attributes[glyphPosition].dontPrint)
1847 line.textWidth += glyphs.advances_x[glyphPosition];
1849 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1851 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1858 void QTextLine::layout_helper(int maxGlyphs)
1860 QScriptLine &line = eng->lines[i];
1863 line.hasTrailingSpaces = false;
1865 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1866 line.setDefaultHeight(eng);
1870 Q_ASSERT(line.from < eng->layoutData->string.length());
1872 LineBreakHelper lbh;
1874 lbh.maxGlyphs = maxGlyphs;
1876 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1877 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1878 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1881 int newItem = eng->findItem(line.from);
1883 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1885 Qt::Alignment alignment = eng->option.alignment();
1887 const HB_CharAttributes *attributes = eng->attributes();
1890 lbh.currentPosition = line.from;
1892 lbh.logClusters = eng->layoutData->logClustersPtr;
1893 lbh.previousGlyph = 0;
1895 while (newItem < eng->layoutData->items.size()) {
1896 lbh.resetRightBearing();
1897 lbh.softHyphenWidth = 0;
1898 if (newItem != item) {
1900 const QScriptItem ¤t = eng->layoutData->items[item];
1901 if (!current.num_glyphs) {
1903 attributes = eng->attributes();
1906 lbh.logClusters = eng->layoutData->logClustersPtr;
1908 lbh.currentPosition = qMax(line.from, current.position);
1909 end = current.position + eng->length(item);
1910 lbh.glyphs = eng->shapedGlyphs(¤t);
1911 QFontEngine *fontEngine = eng->fontEngine(current);
1912 if (lbh.fontEngine != fontEngine) {
1913 lbh.fontEngine = fontEngine;
1914 lbh.minimumRightBearing = qMin(QFixed(),
1915 QFixed::fromReal(fontEngine->minRightBearing()));
1918 const QScriptItem ¤t = eng->layoutData->items[item];
1920 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1921 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1923 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1924 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1926 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1927 lbh.whiteSpaceOrObject = true;
1928 if (lbh.checkFullOtherwiseExtend(line))
1931 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1932 QFixed tabWidth = eng->calculateTabWidth(item, x);
1934 lbh.spaceData.textWidth += tabWidth;
1935 lbh.spaceData.length++;
1938 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1939 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1941 if (lbh.checkFullOtherwiseExtend(line))
1943 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1944 lbh.whiteSpaceOrObject = true;
1945 // if the line consists only of the line separator make sure
1946 // we have a sane height
1947 if (!line.length && !lbh.tmpData.length)
1948 line.setDefaultHeight(eng);
1949 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1950 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1951 current, lbh.logClusters, lbh.glyphs);
1953 lbh.tmpData.length++;
1954 lbh.adjustPreviousRightBearing();
1956 line += lbh.tmpData;
1958 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1959 lbh.whiteSpaceOrObject = true;
1960 lbh.tmpData.length++;
1962 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1963 if (eng->block.docHandle())
1964 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1966 lbh.tmpData.textWidth += current.width;
1970 if (lbh.checkFullOtherwiseExtend(line))
1972 } else if (attributes[lbh.currentPosition].whiteSpace) {
1973 lbh.whiteSpaceOrObject = true;
1974 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1975 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1976 current, lbh.logClusters, lbh.glyphs);
1978 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1979 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1983 lbh.whiteSpaceOrObject = false;
1984 bool sb_or_ws = false;
1985 lbh.saveCurrentGlyph();
1987 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1988 current, lbh.logClusters, lbh.glyphs);
1990 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1993 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1996 } while (lbh.currentPosition < end);
1997 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1999 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
2000 // if we are splitting up a word because of
2001 // a soft hyphen then we ...
2003 // a) have to take the width of the soft hyphen into
2004 // account to see if the first syllable(s) /and/
2005 // the soft hyphen fit into the line
2007 // b) if we are so short of available width that the
2008 // soft hyphen is the first breakable position, then
2009 // we don't want to show it. However we initially
2010 // have to take the width for it into account so that
2011 // the text document layout sees the overflow and
2012 // switch to break-anywhere mode, in which we
2013 // want the soft-hyphen to slip into the next line
2014 // and thus become invisible again.
2017 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
2019 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
2022 // The actual width of the text needs to take the right bearing into account. The
2023 // right bearing is left-ward, which means that if the rightmost pixel is to the right
2024 // of the advance of the glyph, the bearing will be negative. We flip the sign
2025 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
2026 // We ignore the right bearing if the minimum negative bearing is too little to
2027 // expand the text beyond the edge.
2028 if (sb_or_ws|breakany) {
2029 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
2030 #if !defined(Q_WS_MAC)
2031 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
2033 lbh.adjustRightBearing();
2034 if (lbh.checkFullOtherwiseExtend(line)) {
2035 // we are too wide, fix right bearing
2036 if (rightBearing <= 0)
2037 lbh.rightBearing = rightBearing; // take from cache
2039 lbh.adjustPreviousRightBearing();
2042 line.textWidth += lbh.softHyphenWidth;
2048 lbh.saveCurrentGlyph();
2050 if (lbh.currentPosition == end)
2053 LB_DEBUG("reached end of line");
2054 lbh.checkFullOtherwiseExtend(line);
2056 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
2057 lbh.adjustRightBearing();
2058 line.textAdvance = line.textWidth;
2059 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
2061 if (line.length == 0) {
2062 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
2063 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
2064 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
2065 line += lbh.tmpData;
2068 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2069 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2070 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2072 if (lbh.manualWrap) {
2073 eng->minWidth = qMax(eng->minWidth, line.textWidth);
2074 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2076 eng->minWidth = qMax(eng->minWidth, lbh.minw);
2077 eng->maxWidth += line.textWidth;
2080 if (line.textWidth > 0 && item < eng->layoutData->items.size())
2081 eng->maxWidth += lbh.spaceData.textWidth;
2082 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
2083 line.textWidth += lbh.spaceData.textWidth;
2084 if (lbh.spaceData.length) {
2085 line.length += lbh.spaceData.length;
2086 line.hasTrailingSpaces = true;
2089 line.justified = false;
2090 line.gridfitted = false;
2092 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2093 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2094 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
2096 eng->option.setWrapMode(QTextOption::WrapAnywhere);
2099 layout_helper(lbh.maxGlyphs);
2100 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2106 Moves the line to position \a pos.
2108 void QTextLine::setPosition(const QPointF &pos)
2110 eng->lines[i].x = QFixed::fromReal(pos.x());
2111 eng->lines[i].y = QFixed::fromReal(pos.y());
2115 Returns the line's position relative to the text layout's position.
2117 QPointF QTextLine::position() const
2119 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
2122 // ### DOC: I have no idea what this means/does.
2123 // You create a text layout with a string of text. Once you laid
2124 // it out, it contains a number of QTextLines. from() returns the position
2125 // inside the text string where this line starts. If you e.g. has a
2126 // text of "This is a string", laid out into two lines (the second
2127 // starting at the word 'a'), layout.lineAt(0).from() == 0 and
2128 // layout.lineAt(1).from() == 8.
2130 Returns the start of the line from the beginning of the string
2131 passed to the QTextLayout.
2133 int QTextLine::textStart() const
2135 return eng->lines[i].from;
2139 Returns the length of the text in the line.
2141 \sa naturalTextWidth()
2143 int QTextLine::textLength() const
2145 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2146 && eng->block.isValid() && i == eng->lines.count()-1) {
2147 return eng->lines[i].length - 1;
2149 return eng->lines[i].length;
2152 static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
2153 int start, int glyph_start)
2155 int ge = glyph_start + gf.glyphs.numGlyphs;
2156 int gs = glyph_start;
2157 int end = start + gf.num_chars;
2158 unsigned short *logClusters = eng->logClusters(&si);
2159 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2160 QFixed orig_width = gf.width;
2162 int *ul = eng->underlinePositions;
2164 while (*ul != -1 && *ul < start)
2166 bool rtl = si.analysis.bidiLevel % 2;
2173 if (ul && *ul != -1 && *ul < end) {
2175 gtmp = logClusters[*ul-si.position];
2178 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2179 gf.num_chars = stmp - start;
2180 gf.chars = eng->layoutData->string.unicode() + start;
2183 w += glyphs.effectiveAdvance(gs);
2191 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2194 if (ul && *ul != -1 && *ul < end) {
2196 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2198 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2199 gf.num_chars = stmp - start;
2200 gf.chars = eng->layoutData->string.unicode() + start;
2201 gf.logClusters = logClusters + start - si.position;
2204 w += glyphs.effectiveAdvance(gs);
2209 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2212 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2215 gf.underlineStyle = QTextCharFormat::NoUnderline;
2221 gf.width = orig_width;
2225 static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2227 QBrush c = chf.foreground();
2228 if (c.style() == Qt::NoBrush) {
2229 p->setPen(defaultPen);
2232 QBrush bg = chf.background();
2233 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2235 if (c.style() != Qt::NoBrush) {
2236 p->setPen(QPen(c, 0));
2244 GlyphInfo(const QGlyphLayout &layout, const QPointF &position,
2245 const QTextItemInt::RenderFlags &renderFlags)
2246 : glyphLayout(layout), itemPosition(position), flags(renderFlags)
2250 QGlyphLayout glyphLayout;
2251 QPointF itemPosition;
2252 QTextItem::RenderFlags flags;
2259 Returns the glyph indexes and positions for all glyphs in this QTextLine which reside in
2260 QScriptItems that overlap with the range defined by \a from and \a length. The arguments
2261 specify characters, relative to the text in the layout. Note that it is not possible to
2262 use this function to retrieve a subset of the glyphs in a QScriptItem.
2266 \sa QTextLayout::glyphs()
2268 #if !defined(QT_NO_RAWFONT)
2269 QList<QGlyphs> QTextLine::glyphs(int from, int length) const
2271 const QScriptLine &line = eng->lines[i];
2273 if (line.length == 0)
2274 return QList<QGlyphs>();
2276 QHash<QFontEngine *, GlyphInfo> glyphLayoutHash;
2278 QTextLineItemIterator iterator(eng, i);
2279 qreal y = line.y.toReal() + line.base().toReal();
2280 while (!iterator.atEnd()) {
2281 QScriptItem &si = iterator.next();
2282 if (si.analysis.flags >= QScriptAnalysis::TabOrObject)
2285 QPointF pos(iterator.x.toReal(), y);
2286 if (from >= 0 && length >= 0 &&
2287 (from >= si.position + eng->length(&si) || from + length <= si.position))
2290 QFont font = eng->font(si);
2292 QTextItem::RenderFlags flags;
2293 if (font.overline())
2294 flags |= QTextItem::Overline;
2295 if (font.underline())
2296 flags |= QTextItem::Underline;
2297 if (font.strikeOut())
2298 flags |= QTextItem::StrikeOut;
2299 if (si.analysis.bidiLevel % 2)
2300 flags |= QTextItem::RightToLeft;
2302 QGlyphLayout glyphLayout = eng->shapedGlyphs(&si).mid(iterator.glyphsStart,
2303 iterator.glyphsEnd - iterator.glyphsStart);
2305 if (glyphLayout.numGlyphs > 0) {
2306 QFontEngine *mainFontEngine = font.d->engineForScript(si.analysis.script);
2307 if (mainFontEngine->type() == QFontEngine::Multi) {
2308 QFontEngineMulti *multiFontEngine = static_cast<QFontEngineMulti *>(mainFontEngine);
2311 int which = glyphLayout.glyphs[0] >> 24;
2312 for (end = 0; end < glyphLayout.numGlyphs; ++end) {
2313 const int e = glyphLayout.glyphs[end] >> 24;
2317 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2318 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2319 GlyphInfo(subLayout, pos, flags));
2325 QGlyphLayout subLayout = glyphLayout.mid(start, end - start);
2326 glyphLayoutHash.insertMulti(multiFontEngine->engine(which),
2327 GlyphInfo(subLayout, pos, flags));
2330 glyphLayoutHash.insertMulti(mainFontEngine,
2331 GlyphInfo(glyphLayout, pos, flags));
2336 QHash<QPair<QFontEngine *, int>, QGlyphs> glyphsHash;
2338 QList<QFontEngine *> keys = glyphLayoutHash.uniqueKeys();
2339 for (int i=0; i<keys.size(); ++i) {
2340 QFontEngine *fontEngine = keys.at(i);
2342 // Make a font for this particular engine
2344 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
2345 fontD->fontEngine = fontEngine;
2346 fontD->fontEngine->ref.ref();
2348 #if defined(Q_WS_WIN)
2349 if (fontEngine->supportsSubPixelPositions())
2350 fontD->hintingPreference = QFont::PreferVerticalHinting;
2352 fontD->hintingPreference = QFont::PreferFullHinting;
2353 #elif defined(Q_WS_MAC)
2354 fontD->hintingPreference = QFont::PreferNoHinting;
2355 #elif !defined(QT_NO_FREETYPE)
2356 if (fontEngine->type() == QFontEngine::Freetype) {
2357 QFontEngineFT *freeTypeEngine = static_cast<QFontEngineFT *>(fontEngine);
2358 switch (freeTypeEngine->defaultHintStyle()) {
2359 case QFontEngineFT::HintNone:
2360 fontD->hintingPreference = QFont::PreferNoHinting;
2362 case QFontEngineFT::HintLight:
2363 fontD->hintingPreference = QFont::PreferVerticalHinting;
2365 case QFontEngineFT::HintMedium:
2366 case QFontEngineFT::HintFull:
2367 fontD->hintingPreference = QFont::PreferFullHinting;
2373 QList<GlyphInfo> glyphLayouts = glyphLayoutHash.values(fontEngine);
2374 for (int j=0; j<glyphLayouts.size(); ++j) {
2375 const QPointF &pos = glyphLayouts.at(j).itemPosition;
2376 const QGlyphLayout &glyphLayout = glyphLayouts.at(j).glyphLayout;
2377 const QTextItem::RenderFlags &flags = glyphLayouts.at(j).flags;
2379 QVarLengthArray<glyph_t> glyphsArray;
2380 QVarLengthArray<QFixedPoint> positionsArray;
2382 fontEngine->getGlyphPositions(glyphLayout, QTransform(), flags, glyphsArray,
2384 Q_ASSERT(glyphsArray.size() == positionsArray.size());
2386 QVector<quint32> glyphs;
2387 QVector<QPointF> positions;
2388 for (int i=0; i<glyphsArray.size(); ++i) {
2389 glyphs.append(glyphsArray.at(i) & 0xffffff);
2390 positions.append(positionsArray.at(i).toPointF() + pos);
2393 QGlyphs glyphIndexes;
2394 glyphIndexes.setGlyphIndexes(glyphs);
2395 glyphIndexes.setPositions(positions);
2397 glyphIndexes.setOverline(flags.testFlag(QTextItem::Overline));
2398 glyphIndexes.setUnderline(flags.testFlag(QTextItem::Underline));
2399 glyphIndexes.setStrikeOut(flags.testFlag(QTextItem::StrikeOut));
2400 glyphIndexes.setFont(font);
2402 QPair<QFontEngine *, int> key(fontEngine, int(flags));
2403 if (!glyphsHash.contains(key))
2404 glyphsHash.insert(key, glyphIndexes);
2406 glyphsHash[key] += glyphIndexes;
2410 return glyphsHash.values();
2412 #endif // QT_NO_RAWFONT
2415 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2417 Draws a line on the given \a painter at the specified \a position.
2418 The \a selection is reserved for internal use.
2420 void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2422 const QScriptLine &line = eng->lines[i];
2423 QPen pen = p->pen();
2425 bool noText = (selection && selection->format.property(SuppressText).toBool());
2429 && selection->start <= line.from
2430 && selection->start + selection->length > line.from) {
2432 const qreal lineHeight = line.height().toReal();
2433 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2434 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2435 setPenAndDrawBackground(p, QPen(), selection->format, r);
2442 QTextLineItemIterator iterator(eng, i, pos, selection);
2443 QFixed lineBase = line.base();
2445 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2447 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2448 while (!iterator.atEnd()) {
2449 QScriptItem &si = iterator.next();
2451 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2454 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2455 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2458 QFixed itemBaseLine = y;
2459 QFont f = eng->font(si);
2460 QTextCharFormat format;
2462 if (eng->hasFormats() || selection) {
2463 format = eng->format(&si);
2464 if (suppressColors) {
2465 format.clearForeground();
2466 format.clearBackground();
2467 format.clearProperty(QTextFormat::TextUnderlineColor);
2470 format.merge(selection->format);
2472 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2473 iterator.itemWidth.toReal(), line.height().toReal()));
2475 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2476 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2477 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2478 QFixed height = fe->ascent() + fe->descent();
2479 if (valign == QTextCharFormat::AlignSubScript)
2480 itemBaseLine += height / 6;
2481 else if (valign == QTextCharFormat::AlignSuperScript)
2482 itemBaseLine -= height / 2;
2486 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2488 if (eng->hasFormats()) {
2490 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2491 QFixed itemY = y - si.ascent;
2492 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2493 itemY = y - lineBase;
2496 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2498 eng->docLayout()->drawInlineObject(p, itemRect,
2499 QTextInlineObject(iterator.item, eng),
2500 si.position + eng->block.position(),
2503 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2504 if (bg.style() != Qt::NoBrush) {
2505 QColor c = bg.color();
2507 p->fillRect(itemRect, c);
2510 } else { // si.isTab
2511 QFont f = eng->font(si);
2512 QTextItemInt gf(si, &f, format);
2515 gf.width = iterator.itemWidth;
2516 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2517 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2518 QChar visualTab(0x2192);
2519 int w = QFontMetrics(f).width(visualTab);
2520 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2522 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2523 iterator.itemWidth.toReal(), line.height().toReal()),
2527 p->drawText(QPointF(iterator.x.toReal() + x,
2528 y.toReal()), visualTab);
2538 unsigned short *logClusters = eng->logClusters(&si);
2539 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2541 QTextItemInt gf(si, &f, format);
2542 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2543 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2544 gf.logClusters = logClusters + iterator.itemStart - si.position;
2545 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2546 gf.width = iterator.itemWidth;
2547 gf.justified = line.justified;
2549 Q_ASSERT(gf.fontEngine);
2551 if (eng->underlinePositions) {
2552 // can't have selections in this case
2553 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2555 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2556 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2558 path.setFillRule(Qt::WindingFill);
2560 if (gf.glyphs.numGlyphs)
2561 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2563 const QFontEngine *fe = gf.fontEngine;
2564 const qreal lw = fe->lineThickness().toReal();
2565 if (gf.flags & QTextItem::Underline) {
2566 qreal offs = fe->underlinePosition().toReal();
2567 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2569 if (gf.flags & QTextItem::Overline) {
2570 qreal offs = fe->ascent().toReal() + 1;
2571 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2573 if (gf.flags & QTextItem::StrikeOut) {
2574 qreal offs = fe->ascent().toReal() / 3;
2575 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2580 p->setRenderHint(QPainter::Antialiasing);
2581 //Currently QPen with a Qt::NoPen style still returns a default
2582 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2583 if (p->pen().style() == Qt::NoPen)
2584 p->setBrush(Qt::NoBrush);
2586 p->setBrush(p->pen().brush());
2588 p->setPen(format.textOutline());
2593 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2594 p->drawTextItem(pos, gf);
2597 if (si.analysis.flags == QScriptAnalysis::Space
2598 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2599 QBrush c = format.foreground();
2600 if (c.style() != Qt::NoBrush)
2601 p->setPen(c.color());
2602 QChar visualSpace((ushort)0xb7);
2603 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2609 if (eng->hasFormats())
2614 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2620 Converts the cursor position \a cursorPos to the corresponding x position
2621 inside the line, taking account of the \a edge.
2623 If \a cursorPos is not a valid cursor position, the nearest valid
2624 cursor position will be used instead, and cpos will be modified to
2625 point to this valid cursor position.
2629 qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2631 if (!eng->layoutData)
2634 const QScriptLine &line = eng->lines[i];
2637 x += alignLine(eng, line);
2639 if (!i && !eng->layoutData->items.size()) {
2644 int pos = *cursorPos;
2646 if (pos == line.from + (int)line.length) {
2647 // end of line ensure we have the last item on the line
2648 itm = eng->findItem(pos-1);
2651 itm = eng->findItem(pos);
2652 eng->shapeLine(line);
2654 const QScriptItem *si = &eng->layoutData->items[itm];
2655 if (!si->num_glyphs)
2657 pos -= si->position;
2659 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2660 unsigned short *logClusters = eng->logClusters(si);
2661 Q_ASSERT(logClusters);
2663 int l = eng->length(itm);
2669 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2670 if (edge == Trailing) {
2671 // trailing edge is leading edge of next cluster
2672 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2676 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2678 int lineEnd = line.from + line.length;
2680 // add the items left of the cursor
2682 int firstItem = eng->findItem(line.from);
2683 int lastItem = eng->findItem(lineEnd - 1);
2684 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2686 QVarLengthArray<int> visualOrder(nItems);
2687 QVarLengthArray<uchar> levels(nItems);
2688 for (int i = 0; i < nItems; ++i)
2689 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2690 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2692 for (int i = 0; i < nItems; ++i) {
2693 int item = visualOrder[i]+firstItem;
2696 QScriptItem &si = eng->layoutData->items[item];
2700 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2704 int start = qMax(line.from, si.position);
2705 int end = qMin(lineEnd, si.position + eng->length(item));
2707 logClusters = eng->logClusters(&si);
2709 int gs = logClusters[start-si.position];
2710 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2712 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2715 x += glyphs.effectiveAdvance(gs);
2720 logClusters = eng->logClusters(si);
2721 glyphs = eng->shapedGlyphs(si);
2722 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2727 int end = qMin(lineEnd, si->position + l) - si->position;
2728 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2729 for (int i = glyph_end - 1; i >= glyph_pos; i--)
2730 x += glyphs.effectiveAdvance(i);
2732 int start = qMax(line.from - si->position, 0);
2733 int glyph_start = logClusters[start];
2734 for (int i = glyph_start; i < glyph_pos; i++)
2735 x += glyphs.effectiveAdvance(i);
2737 x += offsetInLigature(logClusters, glyphs, pos, line.length, glyph_pos);
2740 *cursorPos = pos + si->position;
2745 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2747 Converts the x-coordinate \a x, to the nearest matching cursor
2748 position, depending on the cursor position type, \a cpos.
2752 int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2754 QFixed x = QFixed::fromReal(_x);
2755 const QScriptLine &line = eng->lines[i];
2757 if (!eng->layoutData)
2760 int line_length = textLength();
2765 int firstItem = eng->findItem(line.from);
2766 int lastItem = eng->findItem(line.from + line_length - 1);
2767 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2773 x -= alignLine(eng, line);
2774 // qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2776 QVarLengthArray<int> visualOrder(nItems);
2777 QVarLengthArray<unsigned char> levels(nItems);
2778 for (int i = 0; i < nItems; ++i)
2779 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2780 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2783 // left of first item
2784 int item = visualOrder[0]+firstItem;
2785 QScriptItem &si = eng->layoutData->items[item];
2788 int pos = si.position;
2789 if (si.analysis.bidiLevel % 2)
2790 pos += eng->length(item);
2791 pos = qMax(line.from, pos);
2792 pos = qMin(line.from + line_length, pos);
2794 } else if (x < line.textWidth
2795 || (line.justified && x < line.width)) {
2796 // has to be in one of the runs
2799 eng->shapeLine(line);
2800 for (int i = 0; i < nItems; ++i) {
2801 int item = visualOrder[i]+firstItem;
2802 QScriptItem &si = eng->layoutData->items[item];
2803 int item_length = eng->length(item);
2804 // qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2806 int start = qMax(line.from - si.position, 0);
2807 int end = qMin(line.from + line_length - si.position, item_length);
2809 unsigned short *logClusters = eng->logClusters(&si);
2811 int gs = logClusters[start];
2812 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2813 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2815 QFixed item_width = 0;
2816 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2817 item_width = si.width;
2821 item_width += glyphs.effectiveAdvance(g);
2825 // qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2827 if (pos + item_width < x) {
2831 // qDebug(" inside run");
2832 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2833 if (cpos == QTextLine::CursorOnCharacter)
2835 bool left_half = (x - pos) < item_width/2;
2837 if (bool(si.analysis.bidiLevel % 2) != left_half)
2839 return si.position + 1;
2843 // has to be inside run
2844 if (cpos == QTextLine::CursorOnCharacter) {
2845 if (si.analysis.bidiLevel % 2) {
2849 if (glyphs.attributes[gs].clusterStart) {
2855 pos -= glyphs.effectiveAdvance(gs);
2861 if (glyphs.attributes[gs].clusterStart) {
2866 pos += glyphs.effectiveAdvance(gs);
2871 QFixed dist = INT_MAX/256;
2872 if (si.analysis.bidiLevel % 2) {
2875 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2879 pos -= glyphs.effectiveAdvance(gs);
2884 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2888 pos += glyphs.effectiveAdvance(gs);
2892 if (qAbs(x-pos) < dist)
2893 return si.position + end;
2895 Q_ASSERT(glyph_pos != -1);
2897 for (j = 0; j < eng->length(item); ++j)
2898 if (logClusters[j] == glyph_pos)
2900 // qDebug("at pos %d (in run: %d)", si.position + j, j);
2901 return si.position + j;
2904 // right of last item
2905 // qDebug() << "right of last";
2906 int item = visualOrder[nItems-1]+firstItem;
2907 QScriptItem &si = eng->layoutData->items[item];
2910 int pos = si.position;
2911 if (!(si.analysis.bidiLevel % 2))
2912 pos += eng->length(item);
2913 pos = qMax(line.from, pos);
2915 int maxPos = line.from + line_length;
2917 // except for the last line we assume that the
2918 // character between lines is a space and we want
2919 // to position the cursor to the left of that
2921 // ###### breaks with japanese for example
2922 if (this->i < eng->lines.count() - 1)
2925 pos = qMin(pos, maxPos);