1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the text module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qtextlayout.h"
30
#include "qtextengine_p.h"
33
#include <qapplication.h>
35
#include <qvarlengtharray.h>
36
#include <qtextformat.h>
37
#include <qabstracttextdocumentlayout.h>
38
#include "qtextdocument_p.h"
39
#include "qtextformat_p.h"
40
#include "qstyleoption.h"
45
#include "qfontengine_p.h"
47
static qreal alignLine(QTextEngine *eng, const QScriptLine &line)
51
if (!line.justified) {
52
int align = eng->option.alignment();
53
if (align & Qt::AlignJustify && eng->option.textDirection() == Qt::RightToLeft)
54
align = Qt::AlignRight;
55
if (align & Qt::AlignRight)
56
x = line.width - line.textWidth;
57
else if (align & Qt::AlignHCenter)
58
x = (line.width - line.textWidth)/2;
64
\class QTextInlineObject
65
\brief The QTextInlineObject class represents an inline object in
70
This class is only used if the text layout is used to lay out
71
parts of a QTextDocument.
73
The inline object has various attributes that can be set, for
74
example using, setWidth(), setAscent(), and setDescent(). The
75
rectangle it occupies is given by rect(), and its direction by
76
isRightToLeft(). Its position in the text layout is given by at(),
77
and its format is given by format().
81
\fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
83
Creates a new inline object for the item at position \a i in the
88
\fn QTextInlineObject::QTextInlineObject()
94
\fn bool QTextInlineObject::isValid() const
96
Returns true if this inline object is valid; otherwise returns
101
Returns the inline object's rectangle.
103
\sa ascent() descent() width()
105
QRectF QTextInlineObject::rect() const
107
QScriptItem& si = eng->layoutData->items[itm];
108
return QRectF(0, -si.ascent, si.width, si.height());
112
Returns the inline object's width.
114
\sa ascent() descent() rect()
116
qreal QTextInlineObject::width() const
118
return eng->layoutData->items[itm].width;
122
Returns the inline object's ascent.
124
\sa descent() width() rect()
126
qreal QTextInlineObject::ascent() const
128
return eng->layoutData->items[itm].ascent;
132
Returns the inline object's descent.
134
\sa ascent() width() rect()
136
qreal QTextInlineObject::descent() const
138
return eng->layoutData->items[itm].descent;
142
Returns the inline object's total height. This is equal to
143
ascent() + descent() + 1.
145
\sa ascent() descent() width() rect()
147
qreal QTextInlineObject::height() const
149
return eng->layoutData->items[itm].height();
154
Sets the inline object's width to \a w.
156
\sa width() ascent() descent() rect()
158
void QTextInlineObject::setWidth(qreal w)
160
eng->layoutData->items[itm].width = w;
164
Sets the inline object's ascent to \a a.
166
\sa ascent() setDescent() width() rect()
168
void QTextInlineObject::setAscent(qreal a)
170
eng->layoutData->items[itm].ascent = a;
174
Sets the inline object's decent to \a d.
176
\sa descent() setAscent() width() rect()
178
void QTextInlineObject::setDescent(qreal d)
180
eng->layoutData->items[itm].descent = d;
184
The position of the inline object within the text layout.
186
int QTextInlineObject::textPosition() const
188
return eng->layoutData->items[itm].position;
192
Returns an integer describing the format of the inline object
193
within the text layout.
195
int QTextInlineObject::formatIndex() const
197
return eng->formatIndex(&eng->layoutData->items[itm]);
201
Returns format of the inline object within the text layout.
203
QTextFormat QTextInlineObject::format() const
205
if (!eng->block.docHandle())
206
return QTextFormat();
207
return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
211
Returns if the object should be laid out right-to-left or left-to-right.
213
Qt::LayoutDirection QTextInlineObject::textDirection() const
215
return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
220
\brief The QTextLayout class is used to lay out and paint a single
225
It offers most features expected from a modern text layout
226
engine, including Unicode compliant rendering, line breaking and
227
handling of cursor positioning. It can also produce and render
228
device independent layout, something that is important for WYSIWYG
231
The class has a rather low level API and unless you intend to
232
implement your own text rendering for some specialized widget, you
233
probably won't need to use it directly.
235
QTextLayout can currently deal with plain text and rich text
236
paragraphs that are part of a QTextDocument.
238
QTextLayout can be used to create a sequence of QTextLine's with
239
given widths and can position them independently on the screen.
240
Once the layout is done, these lines can be drawn on a paint
243
Here's some pseudo code that presents the layout phase:
245
int leading = fontMetrics.leading();
248
textLayout.beginLayout();
250
QTextLine line = textLayout.createLine();
254
line.layout(lineWidth);
256
line.setPosition(QPoint(0, height));
257
height += line.height();
258
widthUsed = qMax(widthUsed, line.naturalTextWidth());
260
textLayout.endLayout();
263
And here's some pseudo code that presents the painting phase:
265
for (int i = 0; i < textLayout.lineCount(); ++i) {
266
QTextLine line = textLayout.lineAt(i);
267
line.draw(painter, rect.x() + xoffset + line.x(), rect.y() + yoffset);
271
The text layout's text is set in the constructor or with
272
setText(). The layout can be seen as a sequence of QTextLine
273
objects; use lineAt() or lineForTextPosition() to get a QTextLine,
274
createLine() to create one. For a given position in the text you
275
can find a valid cursor position with isValidCursorPosition(),
276
nextCursorPosition(), and previousCursorPosition(). The layout
277
itself can be positioned with setPosition(); it has a
278
boundingRect(), and a minimumWidth() and a maximumWidth(). A text
279
layout can be drawn on a painter device using draw().
284
\enum QTextLayout::CursorMode
286
\value SkipCharacters
291
\fn QTextEngine *QTextLayout::engine() const
294
Returns the text engine used to render the text layout.
298
Constructs an empty text layout.
302
QTextLayout::QTextLayout()
303
{ d = new QTextEngine(); }
306
Constructs a text layout to lay out the given \a text.
308
QTextLayout::QTextLayout(const QString& text)
310
d = new QTextEngine();
315
Constructs a text layout to lay out the given \a text with the specified
318
All the metric and layout calculations will be done in terms of
319
the paint device, \a paintdevice. If \a paintdevice is 0 the
320
calculations will be done in screen metrics.
322
QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
324
QFontPrivate *f = paintdevice ? QFont(font, paintdevice).d : font.d;
325
d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f);
329
Constructs a text layout to lay out the given \a block.
331
QTextLayout::QTextLayout(const QTextBlock &block)
333
d = new QTextEngine();
338
Destructs the layout.
340
QTextLayout::~QTextLayout()
346
Sets the layout's font to the given \a font. The layout is
347
invalidated and must be laid out again.
351
void QTextLayout::setFont(const QFont &font)
353
if (d->fnt && !d->fnt->ref.deref())
360
Returns the current font that is used for the layout, or a default
363
QFont QTextLayout::font() const
365
return d->fnt ? QFont(d->fnt) : QFont();
369
Sets the layout's text to the given \a string. The layout is
370
invalidated and must be laid out again.
374
void QTextLayout::setText(const QString& string)
381
Returns the layout's text.
385
QString QTextLayout::text() const
391
Sets the text option structure that controls the layout process to the
394
\sa textOption() QTextOption
396
void QTextLayout::setTextOption(const QTextOption &option)
402
Returns the current text option used to control the layout process.
404
\sa setTextOption() QTextOption
406
QTextOption QTextLayout::textOption() const
412
Sets the \a position and \a text of the area in the layout that is
413
processed before editing occurs.
415
void QTextLayout::setPreeditArea(int position, const QString &text)
417
if (text.isEmpty()) {
420
if (d->specialData->addFormats.isEmpty()) {
421
delete d->specialData;
424
d->specialData->preeditText = QString();
425
d->specialData->preeditPosition = -1;
429
d->specialData = new QTextEngine::SpecialData;
430
d->specialData->preeditPosition = position;
431
d->specialData->preeditText = text;
434
if (d->block.docHandle())
435
d->block.docHandle()->documentChange(d->block.position(), d->block.length());
439
Returns the position of the area in the text layout that will be
440
processed before editing occurs.
442
int QTextLayout::preeditAreaPosition() const
444
return d->specialData ? d->specialData->preeditPosition : -1;
448
Returns the text that is inserted in the layout before editing occurs.
450
QString QTextLayout::preeditAreaText() const
452
return d->specialData ? d->specialData->preeditText : QString();
457
Sets the additional formats supported by the text layout to \a
460
\sa additionalFormats(), clearAdditionalFormats()
462
void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
464
if (formatList.isEmpty()) {
467
if (d->specialData->preeditText.isEmpty()) {
468
delete d->specialData;
471
d->specialData->addFormats = formatList;
475
if (!d->specialData) {
476
d->specialData = new QTextEngine::SpecialData;
477
d->specialData->preeditPosition = -1;
479
d->specialData->addFormats = formatList;
480
if (d->block.docHandle())
481
d->block.docHandle()->documentChange(d->block.position(), d->block.length());
485
Returns the list of additional formats supported by the text layout.
487
\sa setAdditionalFormats(), clearAdditionalFormats()
489
QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
491
return d->specialData ? d->specialData->addFormats : QList<FormatRange>();
495
Clears the list of additional formats supported by the text layout.
497
\sa additionalFormats(), setAdditionalFormats()
499
void QTextLayout::clearAdditionalFormats()
501
setAdditionalFormats(QList<FormatRange>());
505
Enables caching of the complete layout information if \a enable is
506
true; otherwise disables layout caching. Usually
507
QTextLayout throws most of the layouting information away after a
508
call to endLayout() to reduce memory consumption. If you however
509
want to draw the layouted text directly afterwards enabling caching
510
might speed up drawing significantly.
514
void QTextLayout::setCacheEnabled(bool enable)
516
d->cacheGlyphs = enable;
520
Returns true if the complete layout information is cached; otherwise
523
\sa setCacheEnabled()
525
bool QTextLayout::cacheEnabled() const
527
return d->cacheGlyphs;
531
Begins the layout process.
533
void QTextLayout::beginLayout()
536
if (d->layoutData && d->layoutData->inLayout) {
537
qWarning("QTextLayout::beginLayout() called while doing layout");
543
d->layoutData->inLayout = true;
547
Ends the layout process.
549
void QTextLayout::endLayout()
552
if (!d->layoutData || !d->layoutData->inLayout) {
553
qWarning("QTextLayout::endLayout() called without beginLayout()");
557
int l = d->lines.size();
558
if (l && d->lines.at(l-1).length < 0) {
559
QTextLine(l-1, d).setNumColumns(INT_MAX);
561
d->layoutData->inLayout = false;
567
Returns the next valid cursor position after \a oldPos that
568
respects the given cursor \a mode.
570
\sa isValidCursorPosition() previousCursorPosition()
572
int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
574
// qDebug("looking for next cursor pos for %d", oldPos);
575
const QCharAttributes *attributes = d->attributes();
578
int len = d->layoutData->string.length();
582
if (mode == SkipCharacters) {
583
while (oldPos < len && !attributes[oldPos].charStop)
586
while (oldPos < len && attributes[oldPos].whiteSpace)
589
while (oldPos < len && !attributes[oldPos].wordStop && !attributes[oldPos-1].whiteSpace
590
&& !d->atWordSeparator(oldPos))
593
// qDebug(" -> %d", oldPos);
598
Returns the first valid cursor position before \a oldPos that
599
respects the given cursor \a mode.
601
\sa isValidCursorPosition() nextCursorPosition()
603
int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
605
// qDebug("looking for previous cursor pos for %d", oldPos);
606
const QCharAttributes *attributes = d->attributes();
607
if (!attributes || oldPos <= 0)
610
if (mode == SkipCharacters) {
611
while (oldPos && !attributes[oldPos].charStop)
614
while (oldPos && attributes[oldPos].whiteSpace)
617
while (oldPos && !attributes[oldPos].wordStop && !attributes[oldPos-1].whiteSpace
618
&& !d->atWordSeparator(oldPos - 1))
621
// qDebug(" -> %d", oldPos);
626
Returns true if position \a pos is a valid cursor position.
628
In a Unicode context some positions in the text are not valid
629
cursor positions, because the position is inside a Unicode
630
surrogate or a grapheme cluster.
632
A grapheme cluster is a sequence of two or more Unicode characters
633
that form one indivisible entity on the screen. For example the
634
latin character `ļæ½' can be represented in Unicode by two
635
characters, `A' (0x41), and the combining diaresis (0x308). A text
636
cursor can only validly be positioned before or after these two
637
characters, never between them since that wouldn't make sense. In
638
indic languages every syllable forms a grapheme cluster.
640
bool QTextLayout::isValidCursorPosition(int pos) const
642
const QCharAttributes *attributes = d->attributes();
643
if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
645
return attributes[pos].charStop;
649
// ### DOC: Don't know what this really does.
650
// added a bit more description
652
Returns a new text line to be laid out if there is text to be
653
inserted into the layout; otherwise returns an invalid text line.
655
The text layout creates a new line object that starts after the
656
last line in the layout, or at the beginning if the layout is empty.
657
The layout maintains an internal cursor, and each line is filled
658
with text from the cursor position onwards when the
659
QTextLine::setLineWidth() function is called.
661
Once QTextLine::setLineWidth() is called, a new line can be created and
662
filled with text. Repeating this process will lay out the whole block
663
of text contained in the QTextLayout. If there is no text left to be
664
inserted into the layout, the QTextLine returned will not be valid
665
(isValid() will return false).
667
QTextLine QTextLayout::createLine()
670
if (!d->layoutData || !d->layoutData->inLayout) {
671
qWarning("QTextLayout::createLine() called without layouting");
675
int l = d->lines.size();
676
if (l && d->lines.at(l-1).length < 0) {
677
QTextLine(l-1, d).setNumColumns(INT_MAX);
679
int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length : 0;
680
if (l && from >= d->layoutData->string.length())
686
line.justified = false;
687
line.gridfitted = false;
689
d->lines.append(line);
690
return QTextLine(l, d);
694
Returns the number of lines in this text layout.
698
int QTextLayout::lineCount() const
700
return d->lines.size();
704
Returns the \a{i}-th line of text in this text layout.
706
\sa lineCount() lineForTextPosition()
708
QTextLine QTextLayout::lineAt(int i) const
710
return QTextLine(i, d);
714
Returns the line that contains the cursor position specified by \a pos.
716
\sa isValidCursorPosition() lineAt()
718
QTextLine QTextLayout::lineForTextPosition(int pos) const
720
for (int i = 0; i < d->lines.size(); ++i) {
721
const QScriptLine& line = d->lines[i];
722
if (line.from + (int)line.length > pos)
723
return QTextLine(i, d);
727
if (pos == d->layoutData->string.length() && d->lines.size())
728
return QTextLine(d->lines.size()-1, d);
733
The global position of the layout. This is independent of the
734
bounding rectangle and of the layout process.
738
QPointF QTextLayout::position() const
744
Moves the text layout to point \a p.
748
void QTextLayout::setPosition(const QPointF &p)
754
The smallest rectangle that contains all the lines in the layout.
756
QRectF QTextLayout::boundingRect() const
758
qreal xmin = 0, xmax = 0, ymin = 0, ymax = 0;
759
for (int i = 0; i < d->lines.size(); ++i) {
760
const QScriptLine &si = d->lines[i];
761
xmin = qMin(xmin, si.x);
762
ymin = qMin(ymin, si.y);
763
xmax = qMax(xmax, si.x+si.width);
764
// ### shouldn't the ascent be used in ymin???
765
ymax = qMax(ymax, si.y+si.ascent+si.descent+1);
767
return QRectF(xmin, ymin, xmax-xmin, ymax-ymin);
771
The minimum width the layout needs. This is the width of the
772
layout's smallest non-breakable substring.
774
\warning This function only returns a valid value after the layout
779
qreal QTextLayout::minimumWidth() const
785
The maximum width the layout could expand to; this is essentially
786
the width of the entire text.
788
\warning This function only returns a valid value after the layout
793
qreal QTextLayout::maximumWidth() const
799
Draws the whole layout on the painter \a p at the position specified by
801
The rendered layout includes the given \a selections and is clipped within
802
the rectangle specified by \a clip.
804
void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
806
Q_ASSERT(lineCount() != 0);
811
QPointF position = pos + d->position;
813
qreal clipy = qreal(INT_MIN/256);
814
qreal clipe = qreal(INT_MAX/256);
815
if (clip.isValid()) {
816
clipy = clip.y() - position.y();
817
clipe = clipy + clip.height();
820
for (int i = 0; i < d->lines.size(); i++) {
822
const QScriptLine &sl = d->lines[i];
824
if (sl.y > clipe || (sl.y + sl.height()) < clipy)
828
for (int i = 0; i < selections.size(); ++i)
829
l.draw(p, position, selections.constData()+i);
837
\fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
839
Draws a text cursor with the current pen at the given \a position using the
840
\a painter specified.
841
The corresponding position within the text is specified by \a cursorPosition.
843
void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
848
QPointF position = pos + d->position;
850
for (int i = 0; i < d->lines.size(); i++) {
852
const QScriptLine &sl = d->lines[i];
854
if ((sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
855
|| (sl.from + (int)sl.length == cursorPosition && cursorPosition == d->layoutData->string.length())) {
857
const qreal x = position.x() + l.cursorToX(cursorPosition);
859
int itm = d->findItem(cursorPosition - 1);
860
qreal ascent = sl.ascent;
861
qreal descent = sl.descent;
862
bool rightToLeft = (d->option.textDirection() == Qt::RightToLeft);
864
const QScriptItem &si = d->layoutData->items.at(itm);
867
if (si.descent > 0.0)
868
descent = si.descent;
869
rightToLeft = si.analysis.bidiLevel % 2;
871
qreal y = position.y() + sl.y + sl.ascent - ascent;
872
p->drawLine(QLineF(x, y, x, y + ascent + descent));
873
if (d->layoutData->hasBidi) {
874
const int arrow_extent = 4;
875
int sign = rightToLeft ? -1 : 1;
876
p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
877
p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
887
\brief The QTextLine class represents a line of text inside a QTextLayout.
891
A text line is usually created by QTextLayout::createLine().
893
After being created, the line can be filled using the layout()
894
function. A line has a number of attributes including the
895
rectangle it occupies, rect(), its coordinates, x() and y(), its
896
textLength(), width() and naturalTextWidth(), and its ascent() and decent()
897
relative to the text. The position of the cursor in terms of the
898
line is available from cursorToX() and its inverse from
899
xToCursor(). A line can be moved with setPosition().
903
\enum QTextLine::Edge
910
\enum QTextLine::CursorPosition
912
\value CursorBetweenCharacters
913
\value CursorOnCharacter
917
\fn QTextLine::QTextLine(int line, QTextEngine *e)
920
Constructs a new text line using the line at position \a line in
921
the text engine \a e.
925
\fn QTextLine::QTextLine()
927
Creates an invalid line.
931
\fn bool QTextLine::isValid() const
933
Returns true if this text line is valid; otherwise returns false.
937
\fn int QTextLine::lineNumber() const
939
Returns the position of the line in the text engine.
944
Returns the line's bounding rectangle.
946
\sa x() y() textLength() width()
948
QRectF QTextLine::rect() const
950
const QScriptLine& sl = eng->lines[i];
951
return QRectF(sl.x, sl.y, sl.width, sl.height());
955
Returns the rectangle covered by the line.
957
QRectF QTextLine::naturalTextRect() const
959
const QScriptLine& sl = eng->lines[i];
960
qreal x = sl.x + alignLine(eng, sl);
962
qreal width = sl.textWidth;
966
return QRectF(x, sl.y, width, sl.height());
970
Returns the line's x position.
972
\sa rect() y() textLength() width()
974
qreal QTextLine::x() const
976
return eng->lines[i].x;
980
Returns the line's y position.
982
\sa x() rect() textLength() width()
984
qreal QTextLine::y() const
986
return eng->lines[i].y;
990
Returns the line's width as specified by the layout() function.
992
\sa naturalTextWidth() x() y() textLength() rect()
994
qreal QTextLine::width() const
996
return eng->lines[i].width;
1001
Returns the line's ascent.
1003
\sa descent() height()
1005
qreal QTextLine::ascent() const
1007
return eng->lines[i].ascent;
1011
Returns the line's descent.
1013
\sa ascent() height()
1015
qreal QTextLine::descent() const
1017
return eng->lines[i].descent;
1021
Returns the line's height. This is equal to ascent() + descent() + 1.
1023
\sa ascent() descent()
1025
qreal QTextLine::height() const
1027
return eng->lines[i].height();
1031
Returns the width of the line that is occupied by text. This is
1032
always \<= to width(), and is the minimum width that could be used
1033
by layout() without changing the line break position.
1035
qreal QTextLine::naturalTextWidth() const
1037
return eng->lines[i].textWidth;
1041
Lays out the line with the given \a width. The line is filled from
1042
it's starting position with as many characters as will fit into
1045
void QTextLine::setLineWidth(qreal width)
1047
QScriptLine &line = eng->lines[i];
1051
layout_helper(INT_MAX);
1055
Lays out the line. The line is filled from it's starting position
1056
with as many characters as are specified by \a numColumns.
1058
void QTextLine::setNumColumns(int numColumns)
1060
QScriptLine &line = eng->lines[i];
1061
line.width = qreal(INT_MAX/256);
1064
layout_helper(numColumns);
1067
void QTextLine::layout_helper(int maxGlyphs)
1069
QScriptLine &line = eng->lines[i];
1071
if (!eng->layoutData->items.size()) {
1072
line.setDefaultHeight(eng);
1076
Q_ASSERT(line.from < eng->layoutData->string.length());
1078
bool breakany = (eng->option.wrapMode() == QTextOption::WrapAnywhere);
1080
// #### binary search!
1082
for (item = eng->layoutData->items.size()-1; item > 0; --item) {
1083
if (eng->layoutData->items[item].position <= line.from)
1087
qreal minw = 0, spacew = 0;
1090
// qDebug("from: %d: item=%d, total %d width available %f", line.from, item, eng->layoutData->items.size(), line.width);
1092
while (item < eng->layoutData->items.size()) {
1093
const QCharAttributes *attributes = eng->attributes();
1094
const QScriptItem ¤t = eng->layoutData->items[item];
1095
if (!current.num_glyphs)
1098
if (current.isObject) {
1099
QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1100
if (eng->block.docHandle())
1101
eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1102
if (line.length && eng->option.wrapMode() != QTextOption::ManualWrap) {
1103
if (line.textWidth + current.width > line.width || glyphCount > maxGlyphs)
1108
// the width of the linesep doesn't count into the textwidth
1109
if (eng->layoutData->string.at(current.position) == QChar::LineSeparator) {
1110
// if the line consists only of the line separator make sure
1111
// we have a sane height
1112
if (line.length == 1)
1113
line.setDefaultHeight(eng);
1116
line.textWidth += current.width;
1120
line.ascent = qMax(line.ascent, current.ascent);
1121
line.descent = qMax(line.descent, current.descent);
1123
} else if (current.isTab &&
1124
(eng->option.alignment() & Qt::AlignLeft)) {
1125
qreal x = line.x + line.textWidth;
1126
qreal nx = eng->nextTab(¤t, x);
1127
line.textWidth += nx - x;
1131
line.ascent = qMax(line.ascent, current.ascent);
1132
line.descent = qMax(line.descent, current.descent);
1136
int length = eng->length(item);
1138
const QCharAttributes *itemAttrs = attributes + current.position;
1139
QGlyphLayout *glyphs = eng->glyphs(¤t);
1140
unsigned short *logClusters = eng->logClusters(¤t);
1142
int pos = qMax(0, line.from - current.position);
1148
if (!itemAttrs[next].whiteSpace) {
1152
int gp = logClusters[next];
1155
} while (next < length && logClusters[next] == gp);
1157
tmpw += glyphs[gp].advance.x();
1159
} while (gp < current.num_glyphs && !glyphs[gp].attributes.clusterStart);
1161
Q_ASSERT((next == length && gp == current.num_glyphs) || logClusters[next] == gp);
1164
} while (next < length && !itemAttrs[next].whiteSpace && !itemAttrs[next].softBreak && !(breakany && itemAttrs[next].charStop));
1165
minw = qMax(tmpw, minw);
1168
if (itemAttrs[next].softBreak)
1171
while (next < length && itemAttrs[next].whiteSpace) {
1172
int gp = logClusters[next];
1175
} while (next < length && logClusters[next] == gp);
1177
spacew += glyphs[gp].advance.x();
1179
} while (gp < current.num_glyphs && !glyphs[gp].attributes.clusterStart);
1182
Q_ASSERT((next == length && gp == current.num_glyphs) || logClusters[next] == gp);
1185
// qDebug("possible break at %d, chars (%d-%d) / glyphs (%d-%d): width %f, spacew=%f",
1186
// current.position + next, pos, next, logClusters[pos], logClusters[next], tmpw, spacew);
1188
if (line.length && tmpw != qreal(0) && (line.textWidth + tmpw > line.width || glyphCount > maxGlyphs)
1189
&& eng->option.wrapMode() != QTextOption::ManualWrap)
1192
line.textWidth += tmpw;
1193
line.length += next - pos;
1194
line.ascent = qMax(line.ascent, current.ascent);
1195
line.descent = qMax(line.descent, current.descent);
1198
} while (pos < length);
1202
// qDebug("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent,
1203
// line.descent, line.textWidth, spacew);
1204
// qDebug(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1206
eng->minWidth = qMax(eng->minWidth, minw);
1207
eng->maxWidth += line.textWidth;
1208
if (line.textWidth > 0 && item < eng->layoutData->items.size())
1209
eng->maxWidth += spacew;
1210
if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1211
line.textWidth += spacew;
1213
line.justified = false;
1214
line.gridfitted = false;
1218
Moves the line to position \a pos.
1220
void QTextLine::setPosition(const QPointF &pos)
1222
eng->lines[i].x = pos.x();
1223
eng->lines[i].y = pos.y();
1226
// ### DOC: I have no idea what this means/does.
1227
// You create a text layout with a string of text. Once you layouted
1228
// it, it contains a number of QTextLines. from() returns the position
1229
// inside the text string where this line starts. If you e.g. has a
1230
// text of "This is a string", layouted into two lines (the second
1231
// starting at the word 'a'), layout.lineAt(0).from() == 0 and
1232
// layout.lineAt(1).from() == 8.
1234
Returns the start of the line from the beginning of the string
1235
passed to the QTextLayout.
1237
int QTextLine::textStart() const
1239
return eng->lines[i].from;
1243
Returns the length of the text in the line.
1245
\sa naturalTextWidth()
1247
int QTextLine::textLength() const
1249
return eng->lines[i].length;
1252
static void drawMenuText(QPainter *p, qreal x, qreal y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1253
int start, int glyph_start)
1255
int ge = glyph_start + gf.num_glyphs;
1256
int gs = glyph_start;
1257
int end = start + gf.num_chars;
1258
unsigned short *logClusters = eng->logClusters(&si);
1259
QGlyphLayout *glyphs = eng->glyphs(&si);
1260
qreal orig_width = gf.width;
1262
int *ul = eng->underlinePositions;
1264
while (*ul != -1 && *ul < start)
1266
bool rtl = si.analysis.bidiLevel % 2;
1273
if (ul && *ul != -1 && *ul < end) {
1275
gtmp = logClusters[*ul-si.position];
1278
gf.num_glyphs = gtmp - gs;
1279
gf.glyphs = glyphs + gs;
1280
gf.num_chars = stmp - start;
1281
gf.chars = eng->layoutData->string.unicode() + start;
1284
w += glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1292
p->drawTextItem(QPointF(x, y), gf);
1295
if (ul && *ul != -1 && *ul < end) {
1297
gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
1299
gf.num_glyphs = gtmp - gs;
1300
gf.glyphs = glyphs + gs;
1301
gf.num_chars = stmp - start;
1302
gf.chars = eng->layoutData->string.unicode() + start;
1305
w += glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1310
gf.flags |= QTextItem::Underline;
1313
p->drawTextItem(QPointF(x, y), gf);
1316
gf.flags &= ~QTextItem::Underline;
1322
gf.width = orig_width;
1326
static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
1328
QBrush c = chf.foreground();
1329
if (c == Qt::NoBrush)
1330
p->setPen(defaultPen);
1332
QBrush bg = chf.background();
1333
if (bg.style() != Qt::NoBrush)
1335
if (c != Qt::NoBrush)
1336
p->setPen(QPen(c, 0));
1340
\fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
1342
Draws a line on the given \a painter at the specified \a position.
1343
The \a selection is reserved for internal use.
1345
void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
1347
const QScriptLine &line = eng->lines[i];
1352
QPen pen = p->pen();
1354
int lineEnd = line.from + line.length;
1355
// don't draw trailing spaces or take them into the layout.
1356
if (!(eng->option.flags() & QTextOption::IncludeTrailingSpaces)) {
1357
const QCharAttributes *attributes = eng->attributes();
1358
while (lineEnd > line.from && attributes[lineEnd-1].whiteSpace)
1362
int firstItem = eng->findItem(line.from);
1363
int lastItem = eng->findItem(lineEnd - 1);
1364
int nItems = lastItem-firstItem+1;
1369
y += line.y + line.ascent;
1371
x += alignLine(eng, line);
1373
QVarLengthArray<int> visualOrder(nItems);
1374
QVarLengthArray<uchar> levels(nItems);
1375
for (int i = 0; i < nItems; ++i)
1376
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
1377
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
1380
QPen outlinePen(Qt::NoPen);
1382
QVariant outline = selection->format.property(QTextFormat::OutlinePen);
1383
if (outline.type() == QVariant::Pen)
1384
outlinePen = qVariantValue<QPen>(outline);
1387
QFont f = eng->font();
1388
for (int i = 0; i < nItems; ++i) {
1389
int item = visualOrder[i]+firstItem;
1390
QScriptItem &si = eng->layoutData->items[item];
1391
int si_len = eng->length(item);
1395
if (si.isObject || si.isTab) {
1396
if (eng->block.docHandle() &&
1397
(!selection || (si.position < selection->start + selection->length
1398
&& si.position + si_len > selection->start))) {
1400
QTextCharFormat format = eng->format(&si).toCharFormat();
1402
format.merge(selection->format);
1403
qreal width = si.width;
1405
width = eng->nextTab(&si, x - pos.x()) - (x - pos.x());
1407
setPenAndDrawBackground(p, pen, format, QRectF(x, y - line.ascent, width, line.height()));
1409
QRectF itemRect(x, y-si.ascent, width, si.height());
1410
eng->docLayout()->drawInlineObject(p, itemRect,
1411
QTextInlineObject(item, eng),
1412
si.position + eng->block.position(),
1415
QBrush bg = format.background();
1416
if (bg.style() != Qt::NoBrush) {
1417
QColor c = bg.color();
1419
p->fillRect(itemRect, c);
1421
if (outlinePen.style() != Qt::NoPen)
1422
outlineRect = outlineRect.unite(itemRect);
1429
x = eng->nextTab(&si, x - pos.x()) + pos.x();
1435
unsigned short *logClusters = eng->logClusters(&si);
1436
QGlyphLayout *glyphs = eng->glyphs(&si);
1438
int start = qMax(line.from, si.position);
1439
int gs = logClusters[start-si.position];
1442
if (lineEnd < si.position + eng->length(item)) {
1444
ge = logClusters[end-si.position];
1446
end = si.position + si_len;
1450
qreal itemBaseLine = y;
1453
if (si.analysis.bidiLevel %2)
1454
gf.flags |= QTextItem::RightToLeft;
1455
gf.ascent = si.ascent;
1456
gf.descent = si.descent;
1457
gf.num_glyphs = ge - gs;
1458
gf.glyphs = glyphs + gs;
1459
gf.chars = eng->layoutData->string.unicode() + start;
1460
gf.num_chars = end - start;
1464
gf.width += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1469
int from = qMax(start, selection->start) - si.position;
1470
int to = qMin(end, selection->start + selection->length) - si.position;
1475
int start_glyph = logClusters[from];
1476
int end_glyph = (to == eng->length(item)) ? si.num_glyphs : logClusters[to];
1479
if (si.analysis.bidiLevel %2) {
1480
for (int g = ge - 1; g >= end_glyph; --g)
1481
soff += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1482
for (int g = end_glyph - 1; g >= start_glyph; --g)
1483
swidth += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1485
for (int g = gs; g < start_glyph; ++g)
1486
soff += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1487
for (int g = start_glyph; g < end_glyph; ++g)
1488
swidth += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1491
QRectF rect(x + soff, y - line.ascent, swidth, line.height());
1492
if (outlinePen.style() != Qt::NoPen)
1493
outlineRect = outlineRect.unite(rect);
1495
p->setClipRect(rect);
1499
if (eng->block.docHandle() || selection) {
1500
QTextCharFormat chf;
1501
if (eng->block.docHandle())
1502
chf = eng->format(&si).toCharFormat();
1504
chf.merge(selection->format);
1506
setPenAndDrawBackground(p, pen, chf, QRectF(x, y - line.ascent, gf.width, line.height()));
1508
QTextCharFormat::VerticalAlignment valign = chf.verticalAlignment();
1509
if (valign == QTextCharFormat::AlignSubScript)
1510
itemBaseLine += (si.ascent + si.descent + 1) / 6;
1511
else if (valign == QTextCharFormat::AlignSuperScript)
1512
itemBaseLine -= (si.ascent + si.descent + 1) / 2;
1517
gf.fontEngine = f.d->engineForScript(si.analysis.script);
1520
gf.flags |= QTextItem::Underline;
1522
gf.flags |= QTextItem::Overline;
1524
gf.flags |= QTextItem::StrikeOut;
1525
Q_ASSERT(gf.fontEngine);
1527
if (eng->underlinePositions) {
1528
// can't have selections in this case
1529
drawMenuText(p, x, itemBaseLine, si, gf, eng, start, gs);
1531
p->drawTextItem(QPointF(x, itemBaseLine), gf);
1539
if (outlineRect.isValid()) {
1540
p->setPen(outlinePen);
1541
p->drawRect(outlineRect);
1548
\fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
1555
Converts the cursor position \a cursorPos to the corresponding x position
1556
inside the line, taking account of the \a edge.
1558
If \a cursorPos is not a valid cursor position, the nearest valid
1559
cursor position will be used instead, and cpos will be modified to
1560
point to this valid cursor position.
1564
qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
1566
if (!eng->layoutData)
1569
const QScriptLine &line = eng->lines[i];
1572
x += alignLine(eng, line);
1574
if (!i && !eng->layoutData->items.size()) {
1579
int pos = *cursorPos;
1580
int itm = eng->findItem(pos);
1581
if (pos == line.from + (int)line.length) {
1582
// end of line ensure we have the last item on the line
1583
itm = eng->findItem(pos-1);
1586
const QScriptItem *si = &eng->layoutData->items[itm];
1587
if (!si->num_glyphs)
1589
pos -= si->position;
1591
QGlyphLayout *glyphs = eng->glyphs(si);
1592
unsigned short *logClusters = eng->logClusters(si);
1594
int l = eng->length(itm);
1600
int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
1601
if (edge == Trailing) {
1602
// trailing edge is leading edge of next cluster
1603
while (glyph_pos < si->num_glyphs && !glyphs[glyph_pos].attributes.clusterStart)
1607
bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
1609
int lineEnd = line.from + line.length;
1610
// don't draw trailing spaces or take them into the layout.
1611
if (!(eng->option.flags() & QTextOption::IncludeTrailingSpaces)) {
1612
const QCharAttributes *attributes = eng->attributes();
1613
while (lineEnd > line.from && attributes[lineEnd-1].whiteSpace)
1617
// add the items left of the cursor
1619
int firstItem = eng->findItem(line.from);
1620
int lastItem = eng->findItem(lineEnd - 1);
1621
int nItems = lastItem-firstItem+1;
1623
QVarLengthArray<int> visualOrder(nItems);
1624
QVarLengthArray<uchar> levels(nItems);
1625
for (int i = 0; i < nItems; ++i)
1626
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
1627
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
1629
for (int i = 0; i < nItems; ++i) {
1630
int item = visualOrder[i]+firstItem;
1633
QScriptItem &si = eng->layoutData->items[item];
1638
x = eng->nextTab(&si, x);
1640
} else if (si.isObject) {
1644
int start = qMax(line.from, si.position);
1645
int end = qMin(lineEnd, si.position + eng->length(item));
1647
logClusters = eng->logClusters(&si);
1649
int gs = logClusters[start-si.position];
1650
int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
1652
QGlyphLayout *glyphs = eng->glyphs(&si);
1655
x += glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1660
logClusters = eng->logClusters(si);
1661
glyphs = eng->glyphs(si);
1664
x = eng->nextTab(si, x);
1665
} else if (si->isObject) {
1670
int end = qMin(lineEnd, si->position + l) - si->position;
1671
int glyph_end = end == l ? si->num_glyphs : logClusters[end];
1672
for (int i = glyph_end - 1; i >= glyph_pos; i--)
1673
x += glyphs[i].advance.x() + qreal(glyphs[i].space_18d6)/qreal(64);
1675
int start = qMax(line.from - si->position, 0);
1676
int glyph_start = logClusters[start];
1677
for (int i = glyph_start; i < glyph_pos; i++)
1678
x += glyphs[i].advance.x() + qreal(glyphs[i].space_18d6)/qreal(64);
1682
*cursorPos = pos + si->position;
1687
Converts the x-coordinate \a x, to the nearest matching cursor
1688
position, depending on the cursor position type, \a cpos.
1692
int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
1694
const QScriptLine &line = eng->lines[i];
1696
if (!eng->layoutData)
1699
int line_length = line.length;
1701
if (line_length > 0 && eng->layoutData->string.at(line.from + line_length - 1) == QChar::LineSeparator)
1704
// don't draw trailing spaces or take them into the layout.
1705
const QCharAttributes *a = eng->attributes() + line.from;
1706
while (line_length && a[line_length-1].whiteSpace)
1712
int firstItem = eng->findItem(line.from);
1713
int lastItem = eng->findItem(line.from + line_length - 1);
1714
int nItems = lastItem-firstItem+1;
1717
x -= alignLine(eng, line);
1718
// qDebug("xToCursor: x=%f, cpos=%d", x, cpos);
1720
QVarLengthArray<int> visualOrder(nItems);
1721
QVarLengthArray<unsigned char> levels(nItems);
1722
for (int i = 0; i < nItems; ++i)
1723
levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
1724
QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
1727
// left of first item
1728
int item = visualOrder[0]+firstItem;
1729
QScriptItem &si = eng->layoutData->items[item];
1732
int pos = si.position;
1733
if (si.analysis.bidiLevel % 2)
1734
pos += eng->length(item);
1735
pos = qMax(line.from, pos);
1736
pos = qMin(line.from + line_length, pos);
1738
} else if (x < line.textWidth
1739
|| (line.justified && x < line.width)) {
1740
// has to be in one of the runs
1743
for (int i = 0; i < nItems; ++i) {
1744
int item = visualOrder[i]+firstItem;
1745
QScriptItem &si = eng->layoutData->items[item];
1748
int item_length = eng->length(item);
1749
// qDebug(" item %d, visual %d x_remain=%f", i, item, x);
1751
int start = qMax(line.from - si.position, 0);
1752
int end = qMin(line.from + line_length - si.position, item_length);
1754
unsigned short *logClusters = eng->logClusters(&si);
1756
int gs = logClusters[start];
1757
int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
1758
QGlyphLayout *glyphs = eng->glyphs(&si);
1760
qreal item_width = 0;
1762
item_width = eng->nextTab(&si, pos) - pos;
1763
} else if (si.isObject) {
1764
item_width = si.width;
1768
item_width += glyphs[g].advance.x() + qreal(glyphs[g].space_18d6)/qreal(64);
1772
// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width);
1774
if (pos + item_width < x) {
1778
// qDebug(" inside run");
1779
if (si.isTab || si.isObject) {
1780
if (cpos == QTextLine::CursorOnCharacter)
1782
bool left_half = (x - pos) < item_width/2.;
1784
if (bool(si.analysis.bidiLevel % 2) ^ left_half)
1786
return si.position + 1;
1790
// has to be inside run
1791
if (cpos == QTextLine::CursorOnCharacter) {
1792
if (si.analysis.bidiLevel % 2) {
1794
int last_glyph = gs;
1796
if (glyphs[gs].attributes.clusterStart && pos < x) {
1797
glyph_pos = last_glyph;
1800
pos -= glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1806
if (glyphs[gs].attributes.clusterStart) {
1811
pos += glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1816
qreal dist = qreal(INT_MAX/256);
1817
if (si.analysis.bidiLevel % 2) {
1820
if (glyphs[gs].attributes.clusterStart && qAbs(x-pos) < dist) {
1824
pos -= glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1829
if (glyphs[gs].attributes.clusterStart && qAbs(x-pos) < dist) {
1833
pos += glyphs[gs].advance.x() + qreal(glyphs[gs].space_18d6)/qreal(64);
1837
if (qAbs(x-pos) < dist)
1838
return si.position + end;
1840
Q_ASSERT(glyph_pos != -1);
1842
for (j = 0; j < eng->length(item); ++j)
1843
if (logClusters[j] == glyph_pos)
1845
// qDebug("at pos %d (in run: %d)", si.position + j, j);
1846
return si.position + j;
1849
// right of last item
1850
int item = visualOrder[nItems-1]+firstItem;
1851
QScriptItem &si = eng->layoutData->items[item];
1854
int pos = si.position;
1855
if (!(si.analysis.bidiLevel % 2))
1856
pos += eng->length(item);
1857
pos = qMax(line.from, pos);
1858
pos = qMin(line.from + line_length, pos);