source: trunk/src/gui/text/qtextlayout.cpp@ 1009

Last change on this file since 1009 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 83.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
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.
23**
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.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtextlayout.h"
43#include "qtextengine_p.h"
44
45#include <qfont.h>
46#include <qapplication.h>
47#include <qpainter.h>
48#include <qvarlengtharray.h>
49#include <qtextformat.h>
50#include <qabstracttextdocumentlayout.h>
51#include "qtextdocument_p.h"
52#include "qtextformat_p.h"
53#include "qstyleoption.h"
54#include "qpainterpath.h"
55#include <limits.h>
56
57#include <qdebug.h>
58
59#include "qfontengine_p.h"
60
61QT_BEGIN_NAMESPACE
62
63#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
64#define SuppressText 0x5012
65#define SuppressBackground 0x513
66
67static inline QFixed leadingSpaceWidth(QTextEngine *eng, const QScriptLine &line)
68{
69 if (!line.hasTrailingSpaces
70 || (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
71 || !(eng->option.alignment() & Qt::AlignRight)
72 || !eng->isRightToLeft())
73 return QFixed();
74
75 int pos = line.length;
76 const HB_CharAttributes *attributes = eng->attributes();
77 if (!attributes)
78 return QFixed();
79 while (pos > 0 && attributes[line.from + pos - 1].whiteSpace)
80 --pos;
81 return eng->width(line.from + pos, line.length - pos);
82}
83
84static QFixed alignLine(QTextEngine *eng, const QScriptLine &line)
85{
86 QFixed x = 0;
87 eng->justify(line);
88 // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
89 if (!line.justified && line.width != QFIXED_MAX) {
90 int align = eng->option.alignment();
91 if (align & Qt::AlignJustify && eng->isRightToLeft())
92 align = Qt::AlignRight;
93 if (align & Qt::AlignRight)
94 x = line.width - (line.textAdvance + leadingSpaceWidth(eng, line));
95 else if (align & Qt::AlignHCenter)
96 x = (line.width - line.textAdvance)/2;
97 }
98 return x;
99}
100
101/*!
102 \class QTextLayout::FormatRange
103 \reentrant
104
105 \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
106 for a specified area in the text layout's content.
107
108 \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw()
109*/
110
111/*!
112 \variable QTextLayout::FormatRange::start
113 Specifies the beginning of the format range within the text layout's text.
114*/
115
116/*!
117 \variable QTextLayout::FormatRange::length
118 Specifies the numer of characters the format range spans.
119*/
120
121/*!
122 \variable QTextLayout::FormatRange::format
123 Specifies the format to apply.
124*/
125
126/*!
127 \class QTextInlineObject
128 \reentrant
129
130 \brief The QTextInlineObject class represents an inline object in
131 a QTextLayout.
132
133 \ingroup richtext-processing
134
135 This class is only used if the text layout is used to lay out
136 parts of a QTextDocument.
137
138 The inline object has various attributes that can be set, for
139 example using, setWidth(), setAscent(), and setDescent(). The
140 rectangle it occupies is given by rect(), and its direction by
141 isRightToLeft(). Its position in the text layout is given by at(),
142 and its format is given by format().
143*/
144
145/*!
146 \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
147
148 Creates a new inline object for the item at position \a i in the
149 text engine \a e.
150*/
151
152/*!
153 \fn QTextInlineObject::QTextInlineObject()
154
155 \internal
156*/
157
158/*!
159 \fn bool QTextInlineObject::isValid() const
160
161 Returns true if this inline object is valid; otherwise returns
162 false.
163*/
164
165/*!
166 Returns the inline object's rectangle.
167
168 \sa ascent() descent() width()
169*/
170QRectF QTextInlineObject::rect() const
171{
172 QScriptItem& si = eng->layoutData->items[itm];
173 return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
174}
175
176/*!
177 Returns the inline object's width.
178
179 \sa ascent() descent() rect()
180*/
181qreal QTextInlineObject::width() const
182{
183 return eng->layoutData->items[itm].width.toReal();
184}
185
186/*!
187 Returns the inline object's ascent.
188
189 \sa descent() width() rect()
190*/
191qreal QTextInlineObject::ascent() const
192{
193 return eng->layoutData->items[itm].ascent.toReal();
194}
195
196/*!
197 Returns the inline object's descent.
198
199 \sa ascent() width() rect()
200*/
201qreal QTextInlineObject::descent() const
202{
203 return eng->layoutData->items[itm].descent.toReal();
204}
205
206/*!
207 Returns the inline object's total height. This is equal to
208 ascent() + descent() + 1.
209
210 \sa ascent() descent() width() rect()
211*/
212qreal QTextInlineObject::height() const
213{
214 return eng->layoutData->items[itm].height().toReal();
215}
216
217
218/*!
219 Sets the inline object's width to \a w.
220
221 \sa width() ascent() descent() rect()
222*/
223void QTextInlineObject::setWidth(qreal w)
224{
225 eng->layoutData->items[itm].width = QFixed::fromReal(w);
226}
227
228/*!
229 Sets the inline object's ascent to \a a.
230
231 \sa ascent() setDescent() width() rect()
232*/
233void QTextInlineObject::setAscent(qreal a)
234{
235 eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
236}
237
238/*!
239 Sets the inline object's decent to \a d.
240
241 \sa descent() setAscent() width() rect()
242*/
243void QTextInlineObject::setDescent(qreal d)
244{
245 eng->layoutData->items[itm].descent = QFixed::fromReal(d);
246}
247
248/*!
249 The position of the inline object within the text layout.
250*/
251int QTextInlineObject::textPosition() const
252{
253 return eng->layoutData->items[itm].position;
254}
255
256/*!
257 Returns an integer describing the format of the inline object
258 within the text layout.
259*/
260int QTextInlineObject::formatIndex() const
261{
262 return eng->formatIndex(&eng->layoutData->items[itm]);
263}
264
265/*!
266 Returns format of the inline object within the text layout.
267*/
268QTextFormat QTextInlineObject::format() const
269{
270 if (!eng->block.docHandle())
271 return QTextFormat();
272 return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
273}
274
275/*!
276 Returns if the object should be laid out right-to-left or left-to-right.
277*/
278Qt::LayoutDirection QTextInlineObject::textDirection() const
279{
280 return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
281}
282
283/*!
284 \class QTextLayout
285 \reentrant
286
287 \brief The QTextLayout class is used to lay out and render text.
288
289 \ingroup richtext-processing
290
291 It offers many features expected from a modern text layout
292 engine, including Unicode compliant rendering, line breaking and
293 handling of cursor positioning. It can also produce and render
294 device independent layout, something that is important for WYSIWYG
295 applications.
296
297 The class has a rather low level API and unless you intend to
298 implement your own text rendering for some specialized widget, you
299 probably won't need to use it directly.
300
301 QTextLayout can be used with both plain and rich text.
302
303 QTextLayout can be used to create a sequence of QTextLine
304 instances with given widths and can position them independently
305 on the screen. Once the layout is done, these lines can be drawn
306 on a paint device.
307
308 The text to be laid out can be provided in the constructor or set with
309 setText().
310
311 The layout can be seen as a sequence of QTextLine objects; use createLine()
312 to create a QTextLine instance, and lineAt() or lineForTextPosition() to retrieve
313 created lines.
314
315 Here is a code snippet that demonstrates the layout phase:
316 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 0
317
318 The text can then be rendered by calling the layout's draw() function:
319 \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 1
320
321 For a given position in the text you can find a valid cursor position with
322 isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition().
323
324 The QTextLayout itself can be positioned with setPosition(); it has a
325 boundingRect(), and a minimumWidth() and a maximumWidth().
326
327 \sa QStaticText
328
329*/
330
331/*!
332 \enum QTextLayout::CursorMode
333
334 \value SkipCharacters
335 \value SkipWords
336*/
337
338/*!
339 \fn QTextEngine *QTextLayout::engine() const
340 \internal
341
342 Returns the text engine used to render the text layout.
343*/
344
345/*!
346 Constructs an empty text layout.
347
348 \sa setText()
349*/
350QTextLayout::QTextLayout()
351{ d = new QTextEngine(); }
352
353/*!
354 Constructs a text layout to lay out the given \a text.
355*/
356QTextLayout::QTextLayout(const QString& text)
357{
358 d = new QTextEngine();
359 d->text = text;
360}
361
362/*!
363 Constructs a text layout to lay out the given \a text with the specified
364 \a font.
365
366 All the metric and layout calculations will be done in terms of
367 the paint device, \a paintdevice. If \a paintdevice is 0 the
368 calculations will be done in screen metrics.
369*/
370QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
371{
372 QFont f(font);
373 if (paintdevice)
374 f = QFont(font, paintdevice);
375 d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f.d.data());
376}
377
378/*!
379 \internal
380 Constructs a text layout to lay out the given \a block.
381*/
382QTextLayout::QTextLayout(const QTextBlock &block)
383{
384 d = new QTextEngine();
385 d->block = block;
386}
387
388/*!
389 Destructs the layout.
390*/
391QTextLayout::~QTextLayout()
392{
393 if (!d->stackEngine)
394 delete d;
395}
396
397/*!
398 Sets the layout's font to the given \a font. The layout is
399 invalidated and must be laid out again.
400
401 \sa text()
402*/
403void QTextLayout::setFont(const QFont &font)
404{
405 d->fnt = font;
406}
407
408/*!
409 Returns the current font that is used for the layout, or a default
410 font if none is set.
411*/
412QFont QTextLayout::font() const
413{
414 return d->font();
415}
416
417/*!
418 Sets the layout's text to the given \a string. The layout is
419 invalidated and must be laid out again.
420
421 Notice that when using this QTextLayout as part of a QTextDocument this
422 method will have no effect.
423
424 \sa text()
425*/
426void QTextLayout::setText(const QString& string)
427{
428 d->invalidate();
429 d->clearLineData();
430 d->text = string;
431}
432
433/*!
434 Returns the layout's text.
435
436 \sa setText()
437*/
438QString QTextLayout::text() const
439{
440 return d->text;
441}
442
443/*!
444 Sets the text option structure that controls the layout process to the
445 given \a option.
446
447 \sa textOption() QTextOption
448*/
449void QTextLayout::setTextOption(const QTextOption &option)
450{
451 d->option = option;
452}
453
454/*!
455 Returns the current text option used to control the layout process.
456
457 \sa setTextOption() QTextOption
458*/
459QTextOption QTextLayout::textOption() const
460{
461 return d->option;
462}
463
464/*!
465 Sets the \a position and \a text of the area in the layout that is
466 processed before editing occurs.
467*/
468void QTextLayout::setPreeditArea(int position, const QString &text)
469{
470 if (text.isEmpty()) {
471 if (!d->specialData)
472 return;
473 if (d->specialData->addFormats.isEmpty()) {
474 delete d->specialData;
475 d->specialData = 0;
476 } else {
477 d->specialData->preeditText = QString();
478 d->specialData->preeditPosition = -1;
479 }
480 } else {
481 if (!d->specialData)
482 d->specialData = new QTextEngine::SpecialData;
483 d->specialData->preeditPosition = position;
484 d->specialData->preeditText = text;
485 }
486 d->invalidate();
487 d->clearLineData();
488 if (d->block.docHandle())
489 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
490}
491
492/*!
493 Returns the position of the area in the text layout that will be
494 processed before editing occurs.
495*/
496int QTextLayout::preeditAreaPosition() const
497{
498 return d->specialData ? d->specialData->preeditPosition : -1;
499}
500
501/*!
502 Returns the text that is inserted in the layout before editing occurs.
503*/
504QString QTextLayout::preeditAreaText() const
505{
506 return d->specialData ? d->specialData->preeditText : QString();
507}
508
509
510/*!
511 Sets the additional formats supported by the text layout to \a
512 formatList.
513
514 \sa additionalFormats(), clearAdditionalFormats()
515*/
516void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
517{
518 if (formatList.isEmpty()) {
519 if (!d->specialData)
520 return;
521 if (d->specialData->preeditText.isEmpty()) {
522 delete d->specialData;
523 d->specialData = 0;
524 } else {
525 d->specialData->addFormats = formatList;
526 d->specialData->addFormatIndices.clear();
527 }
528 } else {
529 if (!d->specialData) {
530 d->specialData = new QTextEngine::SpecialData;
531 d->specialData->preeditPosition = -1;
532 }
533 d->specialData->addFormats = formatList;
534 d->indexAdditionalFormats();
535 }
536 if (d->block.docHandle())
537 d->block.docHandle()->documentChange(d->block.position(), d->block.length());
538}
539
540/*!
541 Returns the list of additional formats supported by the text layout.
542
543 \sa setAdditionalFormats(), clearAdditionalFormats()
544*/
545QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
546{
547 QList<FormatRange> formats;
548 if (!d->specialData)
549 return formats;
550
551 formats = d->specialData->addFormats;
552
553 if (d->specialData->addFormatIndices.isEmpty())
554 return formats;
555
556 const QTextFormatCollection *collection = d->formats();
557
558 for (int i = 0; i < d->specialData->addFormatIndices.count(); ++i)
559 formats[i].format = collection->charFormat(d->specialData->addFormatIndices.at(i));
560
561 return formats;
562}
563
564/*!
565 Clears the list of additional formats supported by the text layout.
566
567 \sa additionalFormats(), setAdditionalFormats()
568*/
569void QTextLayout::clearAdditionalFormats()
570{
571 setAdditionalFormats(QList<FormatRange>());
572}
573
574/*!
575 Enables caching of the complete layout information if \a enable is
576 true; otherwise disables layout caching. Usually
577 QTextLayout throws most of the layouting information away after a
578 call to endLayout() to reduce memory consumption. If you however
579 want to draw the laid out text directly afterwards enabling caching
580 might speed up drawing significantly.
581
582 \sa cacheEnabled()
583*/
584void QTextLayout::setCacheEnabled(bool enable)
585{
586 d->cacheGlyphs = enable;
587}
588
589/*!
590 Returns true if the complete layout information is cached; otherwise
591 returns false.
592
593 \sa setCacheEnabled()
594*/
595bool QTextLayout::cacheEnabled() const
596{
597 return d->cacheGlyphs;
598}
599
600/*!
601 Begins the layout process.
602*/
603void QTextLayout::beginLayout()
604{
605#ifndef QT_NO_DEBUG
606 if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) {
607 qWarning("QTextLayout::beginLayout: Called while already doing layout");
608 return;
609 }
610#endif
611 d->invalidate();
612 d->clearLineData();
613 d->itemize();
614 d->layoutData->layoutState = QTextEngine::InLayout;
615}
616
617/*!
618 Ends the layout process.
619*/
620void QTextLayout::endLayout()
621{
622#ifndef QT_NO_DEBUG
623 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
624 qWarning("QTextLayout::endLayout: Called without beginLayout()");
625 return;
626 }
627#endif
628 int l = d->lines.size();
629 if (l && d->lines.at(l-1).length < 0) {
630 QTextLine(l-1, d).setNumColumns(INT_MAX);
631 }
632 d->layoutData->layoutState = QTextEngine::LayoutEmpty;
633 if (!d->cacheGlyphs)
634 d->freeMemory();
635}
636
637/*! \since 4.4
638
639Clears the line information in the layout. After having called
640this function, lineCount() returns 0.
641 */
642void QTextLayout::clearLayout()
643{
644 d->clearLineData();
645}
646
647
648/*!
649 Returns the next valid cursor position after \a oldPos that
650 respects the given cursor \a mode.
651
652 \sa isValidCursorPosition() previousCursorPosition()
653*/
654int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
655{
656// qDebug("looking for next cursor pos for %d", oldPos);
657 const HB_CharAttributes *attributes = d->attributes();
658 if (!attributes)
659 return 0;
660 int len = d->block.isValid() ?
661 (d->block.length() - 1)
662 : d->layoutData->string.length();
663
664 if (oldPos >= len)
665 return oldPos;
666 if (mode == SkipCharacters) {
667 oldPos++;
668 while (oldPos < len && !attributes[oldPos].charStop)
669 oldPos++;
670 } else {
671 if (oldPos < len && d->atWordSeparator(oldPos)) {
672 oldPos++;
673 while (oldPos < len && d->atWordSeparator(oldPos))
674 oldPos++;
675 } else {
676 while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
677 oldPos++;
678 }
679 while (oldPos < len && d->atSpace(oldPos))
680 oldPos++;
681 }
682// qDebug(" -> %d", oldPos);
683 return oldPos;
684}
685
686/*!
687 Returns the first valid cursor position before \a oldPos that
688 respects the given cursor \a mode.
689
690 \sa isValidCursorPosition() nextCursorPosition()
691*/
692int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
693{
694// qDebug("looking for previous cursor pos for %d", oldPos);
695 const HB_CharAttributes *attributes = d->attributes();
696 if (!attributes || oldPos <= 0)
697 return 0;
698 if (mode == SkipCharacters) {
699 oldPos--;
700 while (oldPos && !attributes[oldPos].charStop)
701 oldPos--;
702 } else {
703 while (oldPos && d->atSpace(oldPos-1))
704 oldPos--;
705
706 if (oldPos && d->atWordSeparator(oldPos-1)) {
707 oldPos--;
708 while (oldPos && d->atWordSeparator(oldPos-1))
709 oldPos--;
710 } else {
711 while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
712 oldPos--;
713 }
714 }
715// qDebug(" -> %d", oldPos);
716 return oldPos;
717}
718
719/*!
720 Returns true if position \a pos is a valid cursor position.
721
722 In a Unicode context some positions in the text are not valid
723 cursor positions, because the position is inside a Unicode
724 surrogate or a grapheme cluster.
725
726 A grapheme cluster is a sequence of two or more Unicode characters
727 that form one indivisible entity on the screen. For example the
728 latin character `\Auml' can be represented in Unicode by two
729 characters, `A' (0x41), and the combining diaresis (0x308). A text
730 cursor can only validly be positioned before or after these two
731 characters, never between them since that wouldn't make sense. In
732 indic languages every syllable forms a grapheme cluster.
733*/
734bool QTextLayout::isValidCursorPosition(int pos) const
735{
736 const HB_CharAttributes *attributes = d->attributes();
737 if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
738 return false;
739 return attributes[pos].charStop;
740}
741
742
743/*!
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.
746
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.
752
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).
758*/
759QTextLine QTextLayout::createLine()
760{
761#ifndef QT_NO_DEBUG
762 if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) {
763 qWarning("QTextLayout::createLine: Called without layouting");
764 return QTextLine();
765 }
766#endif
767 if (d->layoutData->layoutState == QTextEngine::LayoutFailed)
768 return QTextLine();
769
770 int l = d->lines.size();
771 if (l && d->lines.at(l-1).length < 0) {
772 QTextLine(l-1, d).setNumColumns(INT_MAX);
773 }
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)
778 return QTextLine();
779 }
780
781 QScriptLine line;
782 line.from = from;
783 line.length = -1;
784 line.justified = false;
785 line.gridfitted = false;
786
787 d->lines.append(line);
788 return QTextLine(l, d);
789}
790
791/*!
792 Returns the number of lines in this text layout.
793
794 \sa lineAt()
795*/
796int QTextLayout::lineCount() const
797{
798 return d->lines.size();
799}
800
801/*!
802 Returns the \a{i}-th line of text in this text layout.
803
804 \sa lineCount() lineForTextPosition()
805*/
806QTextLine QTextLayout::lineAt(int i) const
807{
808 return QTextLine(i, d);
809}
810
811/*!
812 Returns the line that contains the cursor position specified by \a pos.
813
814 \sa isValidCursorPosition() lineAt()
815*/
816QTextLine QTextLayout::lineForTextPosition(int pos) const
817{
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);
822 }
823 if (!d->layoutData)
824 d->itemize();
825 if (pos == d->layoutData->string.length() && d->lines.size())
826 return QTextLine(d->lines.size()-1, d);
827 return QTextLine();
828}
829
830/*!
831 \since 4.2
832
833 The global position of the layout. This is independent of the
834 bounding rectangle and of the layout process.
835
836 \sa setPosition()
837*/
838QPointF QTextLayout::position() const
839{
840 return d->position;
841}
842
843/*!
844 Moves the text layout to point \a p.
845
846 \sa position()
847*/
848void QTextLayout::setPosition(const QPointF &p)
849{
850 d->position = p;
851}
852
853/*!
854 The smallest rectangle that contains all the lines in the layout.
855*/
856QRectF QTextLayout::boundingRect() const
857{
858 if (d->lines.isEmpty())
859 return QRectF();
860
861 QFixed xmax, ymax;
862 QFixed xmin = d->lines.at(0).x;
863 QFixed ymin = d->lines.at(0).y;
864
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());
873 }
874 return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
875}
876
877/*!
878 The minimum width the layout needs. This is the width of the
879 layout's smallest non-breakable substring.
880
881 \warning This function only returns a valid value after the layout
882 has been done.
883
884 \sa maximumWidth()
885*/
886qreal QTextLayout::minimumWidth() const
887{
888 return d->minWidth.toReal();
889}
890
891/*!
892 The maximum width the layout could expand to; this is essentially
893 the width of the entire text.
894
895 \warning This function only returns a valid value after the layout
896 has been done.
897
898 \sa minimumWidth()
899*/
900qreal QTextLayout::maximumWidth() const
901{
902 return d->maxWidth.toReal();
903}
904
905/*!
906 \internal
907*/
908void QTextLayout::setFlags(int flags)
909{
910 if (flags & Qt::TextJustificationForced) {
911 d->option.setAlignment(Qt::AlignJustify);
912 d->forceJustification = true;
913 }
914
915 if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
916 d->ignoreBidi = true;
917 d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
918 }
919}
920
921struct QTextLineItemIterator
922{
923 QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
924 const QTextLayout::FormatRange *_selection = 0);
925
926 inline bool atEnd() const { return logicalItem >= nItems - 1; }
927 QScriptItem &next();
928
929 bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
930 inline bool isOutsideSelection() const {
931 QFixed tmp1, tmp2;
932 return !getSelectionBounds(&tmp1, &tmp2);
933 }
934
935 QTextEngine *eng;
936
937 QFixed x;
938 QFixed pos_x;
939 const QScriptLine &line;
940 QScriptItem *si;
941
942 int lineEnd;
943 int firstItem;
944 int lastItem;
945 int nItems;
946 int logicalItem;
947 int item;
948 int itemLength;
949
950 int glyphsStart;
951 int glyphsEnd;
952 int itemStart;
953 int itemEnd;
954
955 QFixed itemWidth;
956
957 QVarLengthArray<int> visualOrder;
958 QVarLengthArray<uchar> levels;
959
960 const QTextLayout::FormatRange *selection;
961};
962
963QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int lineNum, const QPointF &pos,
964 const QTextLayout::FormatRange *_selection)
965 : eng(_eng),
966 line(eng->lines[lineNum]),
967 si(0),
968 lineEnd(line.from + line.length),
969 firstItem(eng->findItem(line.from)),
970 lastItem(eng->findItem(lineEnd - 1)),
971 nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
972 logicalItem(-1),
973 item(-1),
974 visualOrder(nItems),
975 levels(nItems),
976 selection(_selection)
977{
978 pos_x = x = QFixed::fromReal(pos.x());
979
980 x += line.x;
981
982 x += alignLine(eng, line);
983
984 for (int i = 0; i < nItems; ++i)
985 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
986 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
987
988 eng->shapeLine(line);
989}
990
991QScriptItem &QTextLineItemIterator::next()
992{
993 x += itemWidth;
994
995 ++logicalItem;
996 item = visualOrder[logicalItem] + firstItem;
997 itemLength = eng->length(item);
998 si = &eng->layoutData->items[item];
999 if (!si->num_glyphs)
1000 eng->shape(item);
1001
1002 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
1003 itemWidth = si->width;
1004 return *si;
1005 }
1006
1007 unsigned short *logClusters = eng->logClusters(si);
1008 QGlyphLayout glyphs = eng->shapedGlyphs(si);
1009
1010 itemStart = qMax(line.from, si->position);
1011 glyphsStart = logClusters[itemStart - si->position];
1012 if (lineEnd < si->position + itemLength) {
1013 itemEnd = lineEnd;
1014 glyphsEnd = logClusters[itemEnd-si->position];
1015 } else {
1016 itemEnd = si->position + itemLength;
1017 glyphsEnd = si->num_glyphs;
1018 }
1019 // show soft-hyphen at line-break
1020 if (si->position + itemLength >= lineEnd
1021 && eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
1022 glyphs.attributes[glyphsEnd - 1].dontPrint = false;
1023
1024 itemWidth = 0;
1025 for (int g = glyphsStart; g < glyphsEnd; ++g)
1026 itemWidth += glyphs.effectiveAdvance(g);
1027
1028 return *si;
1029}
1030
1031bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
1032{
1033 *selectionX = *selectionWidth = 0;
1034
1035 if (!selection)
1036 return false;
1037
1038 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
1039 if (si->position >= selection->start + selection->length
1040 || si->position + itemLength <= selection->start)
1041 return false;
1042
1043 *selectionX = x;
1044 *selectionWidth = itemWidth;
1045 } else {
1046 unsigned short *logClusters = eng->logClusters(si);
1047 QGlyphLayout glyphs = eng->shapedGlyphs(si);
1048
1049 int from = qMax(itemStart, selection->start) - si->position;
1050 int to = qMin(itemEnd, selection->start + selection->length) - si->position;
1051 if (from >= to)
1052 return false;
1053
1054 int start_glyph = logClusters[from];
1055 int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
1056 QFixed soff;
1057 QFixed swidth;
1058 if (si->analysis.bidiLevel %2) {
1059 for (int g = glyphsEnd - 1; g >= end_glyph; --g)
1060 soff += glyphs.effectiveAdvance(g);
1061 for (int g = end_glyph - 1; g >= start_glyph; --g)
1062 swidth += glyphs.effectiveAdvance(g);
1063 } else {
1064 for (int g = glyphsStart; g < start_glyph; ++g)
1065 soff += glyphs.effectiveAdvance(g);
1066 for (int g = start_glyph; g < end_glyph; ++g)
1067 swidth += glyphs.effectiveAdvance(g);
1068 }
1069
1070 *selectionX = x + soff;
1071 *selectionWidth = swidth;
1072 }
1073 return true;
1074}
1075
1076static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
1077 QPainterPath *region, QRectF boundingRect)
1078{
1079 const QScriptLine &line = eng->lines[lineNumber];
1080
1081 QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
1082
1083
1084
1085 const qreal selectionY = pos.y() + line.y.toReal();
1086 const qreal lineHeight = line.height().toReal();
1087
1088 QFixed lastSelectionX = iterator.x;
1089 QFixed lastSelectionWidth;
1090
1091 while (!iterator.atEnd()) {
1092 iterator.next();
1093
1094 QFixed selectionX, selectionWidth;
1095 if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
1096 if (selectionX == lastSelectionX + lastSelectionWidth) {
1097 lastSelectionWidth += selectionWidth;
1098 continue;
1099 }
1100
1101 if (lastSelectionWidth > 0)
1102 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
1103
1104 lastSelectionX = selectionX;
1105 lastSelectionWidth = selectionWidth;
1106 }
1107 }
1108 if (lastSelectionWidth > 0)
1109 region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
1110}
1111
1112static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
1113{
1114 return clip.isValid() ? (rect & clip) : rect;
1115}
1116
1117/*!
1118 Draws the whole layout on the painter \a p at the position specified by
1119 \a pos.
1120 The rendered layout includes the given \a selections and is clipped within
1121 the rectangle specified by \a clip.
1122*/
1123void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
1124{
1125 if (d->lines.isEmpty())
1126 return;
1127
1128 if (!d->layoutData)
1129 d->itemize();
1130
1131 QPointF position = pos + d->position;
1132
1133 QFixed clipy = (INT_MIN/256);
1134 QFixed clipe = (INT_MAX/256);
1135 if (clip.isValid()) {
1136 clipy = QFixed::fromReal(clip.y() - position.y());
1137 clipe = clipy + QFixed::fromReal(clip.height());
1138 }
1139
1140 int firstLine = 0;
1141 int lastLine = d->lines.size();
1142 for (int i = 0; i < d->lines.size(); ++i) {
1143 QTextLine l(i, d);
1144 const QScriptLine &sl = d->lines[i];
1145
1146 if (sl.y > clipe) {
1147 lastLine = i;
1148 break;
1149 }
1150 if ((sl.y + sl.height()) < clipy) {
1151 firstLine = i;
1152 continue;
1153 }
1154 }
1155
1156 QPainterPath excludedRegion;
1157 QPainterPath textDoneRegion;
1158 for (int i = 0; i < selections.size(); ++i) {
1159 FormatRange selection = selections.at(i);
1160 const QBrush bg = selection.format.background();
1161
1162 QPainterPath region;
1163 region.setFillRule(Qt::WindingFill);
1164
1165 for (int line = firstLine; line < lastLine; ++line) {
1166 const QScriptLine &sl = d->lines[line];
1167 QTextLine tl(line, d);
1168
1169 QRectF lineRect(tl.naturalTextRect());
1170 lineRect.translate(position);
1171
1172 bool isLastLineInBlock = (line == d->lines.size()-1);
1173 int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
1174
1175
1176 if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
1177 continue; // no actual intersection
1178
1179 const bool selectionStartInLine = sl.from <= selection.start;
1180 const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
1181
1182 if (sl.length && (selectionStartInLine || selectionEndInLine)) {
1183 addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
1184 } else {
1185 region.addRect(clipIfValid(lineRect, clip));
1186 }
1187
1188 if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
1189 QRectF fullLineRect(tl.rect());
1190 fullLineRect.translate(position);
1191 fullLineRect.setRight(QFIXED_MAX);
1192 if (!selectionEndInLine)
1193 region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
1194 if (!selectionStartInLine)
1195 region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
1196 } else if (!selectionEndInLine
1197 && isLastLineInBlock
1198 &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
1199 region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
1200 lineRect.height()/4, lineRect.height()), clip));
1201 }
1202
1203 }
1204 {
1205 const QPen oldPen = p->pen();
1206 const QBrush oldBrush = p->brush();
1207
1208 p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
1209 p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
1210 p->drawPath(region);
1211
1212 p->setPen(oldPen);
1213 p->setBrush(oldBrush);
1214 }
1215
1216
1217
1218 bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
1219 bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
1220
1221 if (hasBackground) {
1222 selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
1223 // don't just clear the property, set an empty brush that overrides a potential
1224 // background brush specified in the text
1225 selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
1226 selection.format.clearProperty(QTextFormat::OutlinePen);
1227 }
1228
1229 selection.format.setProperty(SuppressText, !hasText);
1230
1231 if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
1232 continue;
1233
1234 p->save();
1235 p->setClipPath(region, Qt::IntersectClip);
1236
1237 for (int line = firstLine; line < lastLine; ++line) {
1238 QTextLine l(line, d);
1239 l.draw(p, position, &selection);
1240 }
1241 p->restore();
1242
1243 if (hasText) {
1244 textDoneRegion += region;
1245 } else {
1246 if (hasBackground)
1247 textDoneRegion -= region;
1248 }
1249
1250 excludedRegion += region;
1251 }
1252
1253 QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
1254 if (!needsTextButNoBackground.isEmpty()){
1255 p->save();
1256 p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
1257 FormatRange selection;
1258 selection.start = 0;
1259 selection.length = INT_MAX;
1260 selection.format.setProperty(SuppressBackground, true);
1261 for (int line = firstLine; line < lastLine; ++line) {
1262 QTextLine l(line, d);
1263 l.draw(p, position, &selection);
1264 }
1265 p->restore();
1266 }
1267
1268 if (!excludedRegion.isEmpty()) {
1269 p->save();
1270 QPainterPath path;
1271 QRectF br = boundingRect().translated(position);
1272 br.setRight(QFIXED_MAX);
1273 if (!clip.isNull())
1274 br = br.intersected(clip);
1275 path.addRect(br);
1276 path -= excludedRegion;
1277 p->setClipPath(path, Qt::IntersectClip);
1278 }
1279
1280 for (int i = firstLine; i < lastLine; ++i) {
1281 QTextLine l(i, d);
1282 l.draw(p, position);
1283 }
1284 if (!excludedRegion.isEmpty())
1285 p->restore();
1286
1287
1288 if (!d->cacheGlyphs)
1289 d->freeMemory();
1290}
1291
1292/*!
1293 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
1294 \overload
1295
1296 Draws a text cursor with the current pen at the given \a position using the
1297 \a painter specified.
1298 The corresponding position within the text is specified by \a cursorPosition.
1299*/
1300void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
1301{
1302 drawCursor(p, pos, cursorPosition, 1);
1303}
1304
1305/*!
1306 \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
1307
1308 Draws a text cursor with the current pen and the specified \a width at the given \a position using the
1309 \a painter specified.
1310 The corresponding position within the text is specified by \a cursorPosition.
1311*/
1312void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
1313{
1314 if (d->lines.isEmpty())
1315 return;
1316
1317 if (!d->layoutData)
1318 d->itemize();
1319
1320 QPointF position = pos + d->position;
1321 QFixed pos_x = QFixed::fromReal(position.x());
1322 QFixed pos_y = QFixed::fromReal(position.y());
1323
1324 cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
1325 int line = 0;
1326 if (cursorPosition == d->layoutData->string.length()) {
1327 line = d->lines.size() - 1;
1328 } else {
1329 // ### binary search
1330 for (line = 0; line < d->lines.size(); line++) {
1331 const QScriptLine &sl = d->lines[line];
1332 if (sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
1333 break;
1334 }
1335 }
1336
1337 if (line >= d->lines.size())
1338 return;
1339
1340 QTextLine l(line, d);
1341 const QScriptLine &sl = d->lines[line];
1342
1343 qreal x = position.x() + l.cursorToX(cursorPosition);
1344
1345 int itm = d->findItem(cursorPosition - 1);
1346 QFixed base = sl.base();
1347 QFixed descent = sl.descent;
1348 bool rightToLeft = d->isRightToLeft();
1349 if (itm >= 0) {
1350 const QScriptItem &si = d->layoutData->items.at(itm);
1351 if (si.ascent > 0)
1352 base = si.ascent;
1353 if (si.descent > 0)
1354 descent = si.descent;
1355 rightToLeft = si.analysis.bidiLevel % 2;
1356 }
1357 qreal y = position.y() + (sl.y + sl.base() - base).toReal();
1358 bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
1359 && (p->transform().type() > QTransform::TxTranslate);
1360 if (toggleAntialiasing)
1361 p->setRenderHint(QPainter::Antialiasing);
1362#if defined(QT_MAC_USE_COCOA)
1363 // Always draw the cursor aligned to pixel boundary.
1364 x = qRound(x);
1365#endif
1366 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1367 if (toggleAntialiasing)
1368 p->setRenderHint(QPainter::Antialiasing, false);
1369 if (d->layoutData->hasBidi) {
1370 const int arrow_extent = 4;
1371 int sign = rightToLeft ? -1 : 1;
1372 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1373 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1374 }
1375 return;
1376}
1377
1378/*!
1379 \class QTextLine
1380 \reentrant
1381
1382 \brief The QTextLine class represents a line of text inside a QTextLayout.
1383
1384 \ingroup richtext-processing
1385
1386 A text line is usually created by QTextLayout::createLine().
1387
1388 After being created, the line can be filled using the setLineWidth()
1389 or setNumColumns() functions. A line has a number of attributes including the
1390 rectangle it occupies, rect(), its coordinates, x() and y(), its
1391 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1392 relative to the text. The position of the cursor in terms of the
1393 line is available from cursorToX() and its inverse from
1394 xToCursor(). A line can be moved with setPosition().
1395*/
1396
1397/*!
1398 \enum QTextLine::Edge
1399
1400 \value Leading
1401 \value Trailing
1402*/
1403
1404/*!
1405 \enum QTextLine::CursorPosition
1406
1407 \value CursorBetweenCharacters
1408 \value CursorOnCharacter
1409*/
1410
1411/*!
1412 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1413 \internal
1414
1415 Constructs a new text line using the line at position \a line in
1416 the text engine \a e.
1417*/
1418
1419/*!
1420 \fn QTextLine::QTextLine()
1421
1422 Creates an invalid line.
1423*/
1424
1425/*!
1426 \fn bool QTextLine::isValid() const
1427
1428 Returns true if this text line is valid; otherwise returns false.
1429*/
1430
1431/*!
1432 \fn int QTextLine::lineNumber() const
1433
1434 Returns the position of the line in the text engine.
1435*/
1436
1437
1438/*!
1439 Returns the line's bounding rectangle.
1440
1441 \sa x() y() textLength() width()
1442*/
1443QRectF QTextLine::rect() const
1444{
1445 const QScriptLine& sl = eng->lines[i];
1446 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1447}
1448
1449/*!
1450 Returns the rectangle covered by the line.
1451*/
1452QRectF QTextLine::naturalTextRect() const
1453{
1454 const QScriptLine& sl = eng->lines[i];
1455 QFixed x = sl.x + alignLine(eng, sl);
1456
1457 QFixed width = sl.textWidth;
1458 if (sl.justified)
1459 width = sl.width;
1460
1461 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1462}
1463
1464/*!
1465 Returns the line's x position.
1466
1467 \sa rect() y() textLength() width()
1468*/
1469qreal QTextLine::x() const
1470{
1471 return eng->lines[i].x.toReal();
1472}
1473
1474/*!
1475 Returns the line's y position.
1476
1477 \sa x() rect() textLength() width()
1478*/
1479qreal QTextLine::y() const
1480{
1481 return eng->lines[i].y.toReal();
1482}
1483
1484/*!
1485 Returns the line's width as specified by the layout() function.
1486
1487 \sa naturalTextWidth() x() y() textLength() rect()
1488*/
1489qreal QTextLine::width() const
1490{
1491 return eng->lines[i].width.toReal();
1492}
1493
1494
1495/*!
1496 Returns the line's ascent.
1497
1498 \sa descent() height()
1499*/
1500qreal QTextLine::ascent() const
1501{
1502 return eng->lines[i].ascent.toReal();
1503}
1504
1505/*!
1506 Returns the line's descent.
1507
1508 \sa ascent() height()
1509*/
1510qreal QTextLine::descent() const
1511{
1512 return eng->lines[i].descent.toReal();
1513}
1514
1515/*!
1516 Returns the line's height. This is equal to ascent() + descent() + 1
1517 if leading is not included. If leading is included, this equals to
1518 ascent() + descent() + leading() + 1.
1519
1520 \sa ascent() descent() leading() setLeadingIncluded()
1521*/
1522qreal QTextLine::height() const
1523{
1524 return eng->lines[i].height().toReal();
1525}
1526
1527/*!
1528 \since 4.6
1529
1530 Returns the line's leading.
1531
1532 \sa ascent() descent() height()
1533*/
1534qreal QTextLine::leading() const
1535{
1536 return eng->lines[i].leading.toReal();
1537}
1538
1539/*! \since 4.6
1540
1541 Includes positive leading into the line's height if \a included is true;
1542 otherwise does not include leading.
1543
1544 By default, leading is not included.
1545
1546 Note that negative leading is ignored, it must be handled
1547 in the code using the text lines by letting the lines overlap.
1548
1549 \sa leadingIncluded()
1550
1551*/
1552void QTextLine::setLeadingIncluded(bool included)
1553{
1554 eng->lines[i].leadingIncluded= included;
1555
1556}
1557
1558/*! \since 4.6
1559
1560 Returns true if positive leading is included into the line's height; otherwise returns false.
1561
1562 By default, leading is not included.
1563
1564 \sa setLeadingIncluded()
1565*/
1566bool QTextLine::leadingIncluded() const
1567{
1568 return eng->lines[i].leadingIncluded;
1569}
1570
1571
1572/*!
1573 Returns the width of the line that is occupied by text. This is
1574 always \<= to width(), and is the minimum width that could be used
1575 by layout() without changing the line break position.
1576*/
1577qreal QTextLine::naturalTextWidth() const
1578{
1579 return eng->lines[i].textWidth.toReal();
1580}
1581
1582/*! \since 4.7
1583 Returns the horizontal advance of the text. The advance of the text
1584 is the distance from its position to the next position at which
1585 text would naturally be drawn.
1586
1587 By adding the advance to the position of the text line and using this
1588 as the position of a second text line, you will be able to position
1589 the two lines side-by-side without gaps in-between.
1590*/
1591qreal QTextLine::horizontalAdvance() const
1592{
1593 return eng->lines[i].textAdvance.toReal();
1594}
1595
1596/*!
1597 Lays out the line with the given \a width. The line is filled from
1598 its starting position with as many characters as will fit into
1599 the line. In case the text cannot be split at the end of the line,
1600 it will be filled with additional characters to the next whitespace
1601 or end of the text.
1602*/
1603void QTextLine::setLineWidth(qreal width)
1604{
1605 QScriptLine &line = eng->lines[i];
1606 if (!eng->layoutData) {
1607 qWarning("QTextLine: Can't set a line width while not layouting.");
1608 return;
1609 }
1610
1611 if (width > QFIXED_MAX)
1612 width = QFIXED_MAX;
1613
1614 line.width = QFixed::fromReal(width);
1615 if (line.length
1616 && line.textWidth <= line.width
1617 && line.from + line.length == eng->layoutData->string.length())
1618 // no need to do anything if the line is already layouted and the last one. This optimization helps
1619 // when using things in a single line layout.
1620 return;
1621 line.length = 0;
1622 line.textWidth = 0;
1623
1624 layout_helper(INT_MAX);
1625}
1626
1627/*!
1628 Lays out the line. The line is filled from its starting position
1629 with as many characters as are specified by \a numColumns. In case
1630 the text cannot be split until \a numColumns characters, the line
1631 will be filled with as many characters to the next whitespace or
1632 end of the text.
1633*/
1634void QTextLine::setNumColumns(int numColumns)
1635{
1636 QScriptLine &line = eng->lines[i];
1637 line.width = QFIXED_MAX;
1638 line.length = 0;
1639 line.textWidth = 0;
1640 layout_helper(numColumns);
1641}
1642
1643/*!
1644 Lays out the line. The line is filled from its starting position
1645 with as many characters as are specified by \a numColumns. In case
1646 the text cannot be split until \a numColumns characters, the line
1647 will be filled with as many characters to the next whitespace or
1648 end of the text. The provided \a alignmentWidth is used as reference
1649 width for alignment.
1650*/
1651void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1652{
1653 QScriptLine &line = eng->lines[i];
1654 line.width = QFixed::fromReal(alignmentWidth);
1655 line.length = 0;
1656 line.textWidth = 0;
1657 layout_helper(numColumns);
1658}
1659
1660#if 0
1661#define LB_DEBUG qDebug
1662#else
1663#define LB_DEBUG if (0) qDebug
1664#endif
1665
1666namespace {
1667
1668 struct LineBreakHelper
1669 {
1670 LineBreakHelper()
1671 : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0),
1672 manualWrap(false), whiteSpaceOrObject(true)
1673 {
1674 }
1675
1676
1677 QScriptLine tmpData;
1678 QScriptLine spaceData;
1679
1680 QGlyphLayout glyphs;
1681
1682 int glyphCount;
1683 int maxGlyphs;
1684 int currentPosition;
1685
1686 QFixed minw;
1687 QFixed softHyphenWidth;
1688 QFixed rightBearing;
1689 QFixed minimumRightBearing;
1690
1691 QFontEngine *fontEngine;
1692 const unsigned short *logClusters;
1693
1694 bool manualWrap;
1695 bool whiteSpaceOrObject;
1696
1697 bool checkFullOtherwiseExtend(QScriptLine &line);
1698
1699 QFixed calculateNewWidth(const QScriptLine &line) const {
1700 return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth
1701 - qMin(rightBearing, QFixed());
1702 }
1703
1704 inline glyph_t currentGlyph() const
1705 {
1706 Q_ASSERT(currentPosition > 0);
1707 Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
1708
1709 return glyphs.glyphs[logClusters[currentPosition - 1]];
1710 }
1711
1712 inline void adjustRightBearing(glyph_t glyph)
1713 {
1714 qreal rb;
1715 fontEngine->getGlyphBearings(glyph, 0, &rb);
1716 rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
1717 }
1718
1719 inline void adjustRightBearing()
1720 {
1721 if (currentPosition <= 0)
1722 return;
1723 adjustRightBearing(currentGlyph());
1724 }
1725
1726 inline void resetRightBearing()
1727 {
1728 rightBearing = QFixed(1); // Any positive number is defined as invalid since only
1729 // negative right bearings are interesting to us.
1730 }
1731 };
1732
1733inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1734{
1735 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1736
1737 QFixed newWidth = calculateNewWidth(line);
1738 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1739 return true;
1740
1741 minw = qMax(minw, tmpData.textWidth);
1742 line += tmpData;
1743 line.textWidth += spaceData.textWidth;
1744
1745 line.length += spaceData.length;
1746 tmpData.textWidth = 0;
1747 tmpData.length = 0;
1748 spaceData.textWidth = 0;
1749 spaceData.length = 0;
1750
1751 return false;
1752}
1753
1754} // anonymous namespace
1755
1756
1757static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1758 const QScriptItem &current, const unsigned short *logClusters,
1759 const QGlyphLayout &glyphs)
1760{
1761 int glyphPosition = logClusters[pos];
1762 do { // got to the first next cluster
1763 ++pos;
1764 ++line.length;
1765 } while (pos < end && logClusters[pos] == glyphPosition);
1766 do { // calculate the textWidth for the rest of the current cluster.
1767 line.textWidth += glyphs.advances_x[glyphPosition] * !glyphs.attributes[glyphPosition].dontPrint;
1768 ++glyphPosition;
1769 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1770
1771 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1772
1773 ++glyphCount;
1774}
1775
1776
1777// fill QScriptLine
1778void QTextLine::layout_helper(int maxGlyphs)
1779{
1780 QScriptLine &line = eng->lines[i];
1781 line.length = 0;
1782 line.textWidth = 0;
1783 line.hasTrailingSpaces = false;
1784
1785 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1786 line.setDefaultHeight(eng);
1787 return;
1788 }
1789
1790 Q_ASSERT(line.from < eng->layoutData->string.length());
1791
1792 LineBreakHelper lbh;
1793
1794 lbh.maxGlyphs = maxGlyphs;
1795
1796 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1797 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1798 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1799
1800 int item = -1;
1801 int newItem = -1;
1802 int left = 0;
1803 int right = eng->layoutData->items.size()-1;
1804 while(left <= right) {
1805 int middle = ((right-left)/2)+left;
1806 if (line.from > eng->layoutData->items[middle].position)
1807 left = middle+1;
1808 else if(line.from < eng->layoutData->items[middle].position)
1809 right = middle-1;
1810 else {
1811 newItem = middle;
1812 break;
1813 }
1814 }
1815 if (newItem == -1)
1816 newItem = right;
1817
1818 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1819
1820 Qt::Alignment alignment = eng->option.alignment();
1821
1822 const HB_CharAttributes *attributes = eng->attributes();
1823 if (!attributes)
1824 return;
1825 lbh.currentPosition = line.from;
1826 int end = 0;
1827 lbh.logClusters = eng->layoutData->logClustersPtr;
1828
1829 while (newItem < eng->layoutData->items.size()) {
1830 lbh.resetRightBearing();
1831 lbh.softHyphenWidth = 0;
1832 if (newItem != item) {
1833 item = newItem;
1834 const QScriptItem &current = eng->layoutData->items[item];
1835 if (!current.num_glyphs) {
1836 eng->shape(item);
1837 attributes = eng->attributes();
1838 if (!attributes)
1839 return;
1840 lbh.logClusters = eng->layoutData->logClustersPtr;
1841 }
1842 lbh.currentPosition = qMax(line.from, current.position);
1843 end = current.position + eng->length(item);
1844 lbh.glyphs = eng->shapedGlyphs(&current);
1845 }
1846 const QScriptItem &current = eng->layoutData->items[item];
1847 QFontEngine *fontEngine = eng->fontEngine(current);
1848 if (lbh.fontEngine != fontEngine) {
1849 lbh.fontEngine = fontEngine;
1850 lbh.minimumRightBearing = qMin(QFixed(),
1851 QFixed::fromReal(fontEngine->minRightBearing()));
1852 }
1853
1854 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1855 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1856 current.ascent);
1857 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1858 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1859
1860 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1861 lbh.whiteSpaceOrObject = true;
1862 if (lbh.checkFullOtherwiseExtend(line))
1863 goto found;
1864
1865 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1866 QFixed tabWidth = eng->calculateTabWidth(item, x);
1867
1868 lbh.spaceData.textWidth += tabWidth;
1869 lbh.spaceData.length++;
1870 newItem = item + 1;
1871
1872 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1873 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1874
1875 if (lbh.checkFullOtherwiseExtend(line))
1876 goto found;
1877 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1878 lbh.whiteSpaceOrObject = true;
1879 // if the line consists only of the line separator make sure
1880 // we have a sane height
1881 if (!line.length && !lbh.tmpData.length)
1882 line.setDefaultHeight(eng);
1883 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1884 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1885 current, lbh.logClusters, lbh.glyphs);
1886 } else {
1887 lbh.tmpData.length++;
1888 }
1889 line += lbh.tmpData;
1890 goto found;
1891 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1892 lbh.whiteSpaceOrObject = true;
1893 lbh.tmpData.length++;
1894
1895 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1896 if (eng->block.docHandle())
1897 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1898
1899 lbh.tmpData.textWidth += current.width;
1900
1901 newItem = item + 1;
1902 ++lbh.glyphCount;
1903 if (lbh.checkFullOtherwiseExtend(line))
1904 goto found;
1905 } else if (attributes[lbh.currentPosition].whiteSpace) {
1906 lbh.whiteSpaceOrObject = true;
1907 while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
1908 addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
1909 current, lbh.logClusters, lbh.glyphs);
1910
1911 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1912 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1913 goto found;
1914 }
1915 } else {
1916 lbh.whiteSpaceOrObject = false;
1917 bool sb_or_ws = false;
1918 glyph_t previousGlyph = 0;
1919 if (lbh.currentPosition > 0 && lbh.logClusters[lbh.currentPosition - 1] <lbh.glyphs.numGlyphs)
1920 previousGlyph = lbh.currentGlyph(); // needed to calculate right bearing later
1921 do {
1922 addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
1923 current, lbh.logClusters, lbh.glyphs);
1924
1925 if (attributes[lbh.currentPosition].whiteSpace || attributes[lbh.currentPosition-1].lineBreakType != HB_NoBreak) {
1926 sb_or_ws = true;
1927 break;
1928 } else if (breakany && attributes[lbh.currentPosition].charStop) {
1929 break;
1930 }
1931 } while (lbh.currentPosition < end);
1932 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1933
1934 if (lbh.currentPosition && attributes[lbh.currentPosition - 1].lineBreakType == HB_SoftHyphen) {
1935 // if we are splitting up a word because of
1936 // a soft hyphen then we ...
1937 //
1938 // a) have to take the width of the soft hyphen into
1939 // account to see if the first syllable(s) /and/
1940 // the soft hyphen fit into the line
1941 //
1942 // b) if we are so short of available width that the
1943 // soft hyphen is the first breakable position, then
1944 // we don't want to show it. However we initially
1945 // have to take the width for it into accoun so that
1946 // the text document layout sees the overflow and
1947 // switch to break-anywhere mode, in which we
1948 // want the soft-hyphen to slip into the next line
1949 // and thus become invisible again.
1950 //
1951 if (line.length)
1952 lbh.softHyphenWidth = lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1953 else if (breakany)
1954 lbh.tmpData.textWidth += lbh.glyphs.advances_x[lbh.logClusters[lbh.currentPosition - 1]];
1955 }
1956
1957 // The actual width of the text needs to take the right bearing into account. The
1958 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1959 // of the advance of the glyph, the bearing will be negative. We flip the sign
1960 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1961 // We ignore the right bearing if the minimum negative bearing is too little to
1962 // expand the text beyond the edge.
1963 if (sb_or_ws|breakany) {
1964 QFixed rightBearing = lbh.rightBearing; // store previous right bearing
1965#if !defined(Q_WS_MAC)
1966 if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width)
1967#endif
1968 lbh.adjustRightBearing();
1969 if (lbh.checkFullOtherwiseExtend(line)) {
1970 // we are too wide, fix right bearing
1971 if (rightBearing <= 0)
1972 lbh.rightBearing = rightBearing; // take from cache
1973 else if (previousGlyph > 0)
1974 lbh.adjustRightBearing(previousGlyph);
1975 if (!breakany) {
1976 line.textWidth += lbh.softHyphenWidth;
1977 }
1978
1979 goto found;
1980 }
1981 }
1982 }
1983 if (lbh.currentPosition == end)
1984 newItem = item + 1;
1985 }
1986 LB_DEBUG("reached end of line");
1987 lbh.checkFullOtherwiseExtend(line);
1988found:
1989 if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted
1990 lbh.adjustRightBearing();
1991 line.textAdvance = line.textWidth;
1992 line.textWidth -= qMin(QFixed(), lbh.rightBearing);
1993
1994 if (line.length == 0) {
1995 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1996 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1997 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1998 line += lbh.tmpData;
1999 }
2000
2001 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
2002 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
2003 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
2004
2005 if (lbh.manualWrap) {
2006 eng->minWidth = qMax(eng->minWidth, line.textWidth);
2007 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
2008 } else {
2009 eng->minWidth = qMax(eng->minWidth, lbh.minw);
2010 eng->maxWidth += line.textWidth;
2011 }
2012
2013 if (line.textWidth > 0 && item < eng->layoutData->items.size())
2014 eng->maxWidth += lbh.spaceData.textWidth;
2015 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
2016 line.textWidth += lbh.spaceData.textWidth;
2017 line.length += lbh.spaceData.length;
2018 if (lbh.spaceData.length)
2019 line.hasTrailingSpaces = true;
2020
2021 line.justified = false;
2022 line.gridfitted = false;
2023
2024 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
2025 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
2026 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
2027
2028 eng->option.setWrapMode(QTextOption::WrapAnywhere);
2029 line.length = 0;
2030 line.textWidth = 0;
2031 layout_helper(lbh.maxGlyphs);
2032 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2033 }
2034 }
2035}
2036
2037/*!
2038 Moves the line to position \a pos.
2039*/
2040void QTextLine::setPosition(const QPointF &pos)
2041{
2042 eng->lines[i].x = QFixed::fromReal(pos.x());
2043 eng->lines[i].y = QFixed::fromReal(pos.y());
2044}
2045
2046/*!
2047 Returns the line's position relative to the text layout's position.
2048*/
2049QPointF QTextLine::position() const
2050{
2051 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
2052}
2053
2054// ### DOC: I have no idea what this means/does.
2055// You create a text layout with a string of text. Once you laid
2056// it out, it contains a number of QTextLines. from() returns the position
2057// inside the text string where this line starts. If you e.g. has a
2058// text of "This is a string", laid out into two lines (the second
2059// starting at the word 'a'), layout.lineAt(0).from() == 0 and
2060// layout.lineAt(1).from() == 8.
2061/*!
2062 Returns the start of the line from the beginning of the string
2063 passed to the QTextLayout.
2064*/
2065int QTextLine::textStart() const
2066{
2067 return eng->lines[i].from;
2068}
2069
2070/*!
2071 Returns the length of the text in the line.
2072
2073 \sa naturalTextWidth()
2074*/
2075int QTextLine::textLength() const
2076{
2077 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
2078 && eng->block.isValid() && i == eng->lines.count()-1) {
2079 return eng->lines[i].length - 1;
2080 }
2081 return eng->lines[i].length;
2082}
2083
2084static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
2085 int start, int glyph_start)
2086{
2087 int ge = glyph_start + gf.glyphs.numGlyphs;
2088 int gs = glyph_start;
2089 int end = start + gf.num_chars;
2090 unsigned short *logClusters = eng->logClusters(&si);
2091 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2092 QFixed orig_width = gf.width;
2093
2094 int *ul = eng->underlinePositions;
2095 if (ul)
2096 while (*ul != -1 && *ul < start)
2097 ++ul;
2098 bool rtl = si.analysis.bidiLevel % 2;
2099 if (rtl)
2100 x += si.width;
2101
2102 do {
2103 int gtmp = ge;
2104 int stmp = end;
2105 if (ul && *ul != -1 && *ul < end) {
2106 stmp = *ul;
2107 gtmp = logClusters[*ul-si.position];
2108 }
2109
2110 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2111 gf.num_chars = stmp - start;
2112 gf.chars = eng->layoutData->string.unicode() + start;
2113 QFixed w = 0;
2114 while (gs < gtmp) {
2115 w += glyphs.effectiveAdvance(gs);
2116 ++gs;
2117 }
2118 start = stmp;
2119 gf.width = w;
2120 if (rtl)
2121 x -= w;
2122 if (gf.num_chars)
2123 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2124 if (!rtl)
2125 x += w;
2126 if (ul && *ul != -1 && *ul < end) {
2127 // draw underline
2128 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2129 ++stmp;
2130 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2131 gf.num_chars = stmp - start;
2132 gf.chars = eng->layoutData->string.unicode() + start;
2133 gf.logClusters = logClusters + start - si.position;
2134 w = 0;
2135 while (gs < gtmp) {
2136 w += glyphs.effectiveAdvance(gs);
2137 ++gs;
2138 }
2139 ++start;
2140 gf.width = w;
2141 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2142 if (rtl)
2143 x -= w;
2144 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2145 if (!rtl)
2146 x += w;
2147 gf.underlineStyle = QTextCharFormat::NoUnderline;
2148 ++gf.chars;
2149 ++ul;
2150 }
2151 } while (gs < ge);
2152
2153 gf.width = orig_width;
2154}
2155
2156
2157static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2158{
2159 QBrush c = chf.foreground();
2160 if (c.style() == Qt::NoBrush) {
2161 p->setPen(defaultPen);
2162 }
2163
2164 QBrush bg = chf.background();
2165 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2166 p->fillRect(r, bg);
2167 if (c.style() != Qt::NoBrush) {
2168 p->setPen(QPen(c, 0));
2169 }
2170
2171}
2172
2173/*!
2174 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2175
2176 Draws a line on the given \a painter at the specified \a position.
2177 The \a selection is reserved for internal use.
2178*/
2179void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2180{
2181 const QScriptLine &line = eng->lines[i];
2182 QPen pen = p->pen();
2183
2184 bool noText = (selection && selection->format.property(SuppressText).toBool());
2185
2186 if (!line.length) {
2187 if (selection
2188 && selection->start <= line.from
2189 && selection->start + selection->length > line.from) {
2190
2191 const qreal lineHeight = line.height().toReal();
2192 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2193 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2194 setPenAndDrawBackground(p, QPen(), selection->format, r);
2195 p->setPen(pen);
2196 }
2197 return;
2198 }
2199
2200
2201 QTextLineItemIterator iterator(eng, i, pos, selection);
2202 QFixed lineBase = line.base();
2203
2204 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2205
2206 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2207 while (!iterator.atEnd()) {
2208 QScriptItem &si = iterator.next();
2209
2210 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2211 continue;
2212
2213 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2214 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2215 continue;
2216
2217 QFixed itemBaseLine = y;
2218 QFont f = eng->font(si);
2219 QTextCharFormat format;
2220
2221 if (eng->hasFormats() || selection) {
2222 if (!suppressColors)
2223 format = eng->format(&si);
2224 if (selection)
2225 format.merge(selection->format);
2226
2227 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2228 iterator.itemWidth.toReal(), line.height().toReal()));
2229
2230 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2231 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2232 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2233 QFixed height = fe->ascent() + fe->descent();
2234 if (valign == QTextCharFormat::AlignSubScript)
2235 itemBaseLine += height / 6;
2236 else if (valign == QTextCharFormat::AlignSuperScript)
2237 itemBaseLine -= height / 2;
2238 }
2239 }
2240
2241 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2242
2243 if (eng->hasFormats()) {
2244 p->save();
2245 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2246 QFixed itemY = y - si.ascent;
2247 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2248 itemY = y - lineBase;
2249 }
2250
2251 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2252
2253 eng->docLayout()->drawInlineObject(p, itemRect,
2254 QTextInlineObject(iterator.item, eng),
2255 si.position + eng->block.position(),
2256 format);
2257 if (selection) {
2258 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2259 if (bg.style() != Qt::NoBrush) {
2260 QColor c = bg.color();
2261 c.setAlpha(128);
2262 p->fillRect(itemRect, c);
2263 }
2264 }
2265 } else { // si.isTab
2266 QFont f = eng->font(si);
2267 QTextItemInt gf(si, &f, format);
2268 gf.chars = 0;
2269 gf.num_chars = 0;
2270 gf.width = iterator.itemWidth;
2271 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2272 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2273 QChar visualTab(0x2192);
2274 int w = QFontMetrics(f).width(visualTab);
2275 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2276 if (x < 0)
2277 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2278 iterator.itemWidth.toReal(), line.height().toReal()),
2279 Qt::IntersectClip);
2280 else
2281 x /= 2; // Centered
2282 p->drawText(QPointF(iterator.x.toReal() + x,
2283 y.toReal()), visualTab);
2284 }
2285
2286 }
2287 p->restore();
2288 }
2289
2290 continue;
2291 }
2292
2293 unsigned short *logClusters = eng->logClusters(&si);
2294 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2295
2296 QTextItemInt gf(si, &f, format);
2297 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2298 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2299 gf.logClusters = logClusters + iterator.itemStart - si.position;
2300 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2301 gf.width = iterator.itemWidth;
2302 gf.justified = line.justified;
2303
2304 Q_ASSERT(gf.fontEngine);
2305
2306 if (eng->underlinePositions) {
2307 // can't have selections in this case
2308 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2309 } else {
2310 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2311 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2312 QPainterPath path;
2313 path.setFillRule(Qt::WindingFill);
2314
2315 if (gf.glyphs.numGlyphs)
2316 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2317 if (gf.flags) {
2318 const QFontEngine *fe = gf.fontEngine;
2319 const qreal lw = fe->lineThickness().toReal();
2320 if (gf.flags & QTextItem::Underline) {
2321 qreal offs = fe->underlinePosition().toReal();
2322 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2323 }
2324 if (gf.flags & QTextItem::Overline) {
2325 qreal offs = fe->ascent().toReal() + 1;
2326 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2327 }
2328 if (gf.flags & QTextItem::StrikeOut) {
2329 qreal offs = fe->ascent().toReal() / 3;
2330 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2331 }
2332 }
2333
2334 p->save();
2335 p->setRenderHint(QPainter::Antialiasing);
2336 //Currently QPen with a Qt::NoPen style still returns a default
2337 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2338 if (p->pen().style() == Qt::NoPen)
2339 p->setBrush(Qt::NoBrush);
2340 else
2341 p->setBrush(p->pen().brush());
2342
2343 p->setPen(format.textOutline());
2344 p->drawPath(path);
2345 p->restore();
2346 } else {
2347 if (noText)
2348 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2349 p->drawTextItem(pos, gf);
2350 }
2351 }
2352 if (si.analysis.flags == QScriptAnalysis::Space
2353 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2354 QBrush c = format.foreground();
2355 if (c.style() != Qt::NoBrush)
2356 p->setPen(c.color());
2357 QChar visualSpace((ushort)0xb7);
2358 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2359 p->setPen(pen);
2360 }
2361 }
2362
2363
2364 if (eng->hasFormats())
2365 p->setPen(pen);
2366}
2367
2368/*!
2369 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2370
2371 \overload
2372*/
2373
2374
2375/*!
2376 Converts the cursor position \a cursorPos to the corresponding x position
2377 inside the line, taking account of the \a edge.
2378
2379 If \a cursorPos is not a valid cursor position, the nearest valid
2380 cursor position will be used instead, and cpos will be modified to
2381 point to this valid cursor position.
2382
2383 \sa xToCursor()
2384*/
2385qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2386{
2387 if (!eng->layoutData)
2388 eng->itemize();
2389
2390 const QScriptLine &line = eng->lines[i];
2391
2392 QFixed x = line.x;
2393 x += alignLine(eng, line);
2394
2395 if (!i && !eng->layoutData->items.size()) {
2396 *cursorPos = 0;
2397 return x.toReal();
2398 }
2399
2400 int pos = *cursorPos;
2401 int itm;
2402 if (pos == line.from + (int)line.length) {
2403 // end of line ensure we have the last item on the line
2404 itm = eng->findItem(pos-1);
2405 }
2406 else
2407 itm = eng->findItem(pos);
2408 eng->shapeLine(line);
2409
2410 const QScriptItem *si = &eng->layoutData->items[itm];
2411 if (!si->num_glyphs)
2412 eng->shape(itm);
2413 pos -= si->position;
2414
2415 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2416 unsigned short *logClusters = eng->logClusters(si);
2417 Q_ASSERT(logClusters);
2418
2419 int l = eng->length(itm);
2420 if (pos > l)
2421 pos = l;
2422 if (pos < 0)
2423 pos = 0;
2424
2425 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2426 if (edge == Trailing) {
2427 // trailing edge is leading edge of next cluster
2428 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2429 glyph_pos++;
2430 }
2431
2432 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2433
2434 int lineEnd = line.from + line.length;
2435
2436 // add the items left of the cursor
2437
2438 int firstItem = eng->findItem(line.from);
2439 int lastItem = eng->findItem(lineEnd - 1);
2440 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2441
2442 QVarLengthArray<int> visualOrder(nItems);
2443 QVarLengthArray<uchar> levels(nItems);
2444 for (int i = 0; i < nItems; ++i)
2445 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2446 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2447
2448 for (int i = 0; i < nItems; ++i) {
2449 int item = visualOrder[i]+firstItem;
2450 if (item == itm)
2451 break;
2452 QScriptItem &si = eng->layoutData->items[item];
2453 if (!si.num_glyphs)
2454 eng->shape(item);
2455
2456 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2457 x += si.width;
2458 continue;
2459 }
2460 int start = qMax(line.from, si.position);
2461 int end = qMin(lineEnd, si.position + eng->length(item));
2462
2463 logClusters = eng->logClusters(&si);
2464
2465 int gs = logClusters[start-si.position];
2466 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2467
2468 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2469
2470 while (gs <= ge) {
2471 x += glyphs.effectiveAdvance(gs);
2472 ++gs;
2473 }
2474 }
2475
2476 logClusters = eng->logClusters(si);
2477 glyphs = eng->shapedGlyphs(si);
2478 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2479 if(pos == l)
2480 x += si->width;
2481 } else {
2482 int offsetInCluster = 0;
2483 for (int i=pos-1; i >= 0; i--) {
2484 if (logClusters[i] == glyph_pos)
2485 offsetInCluster++;
2486 else
2487 break;
2488 }
2489
2490 if (reverse) {
2491 int end = qMin(lineEnd, si->position + l) - si->position;
2492 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2493 for (int i = glyph_end - 1; i >= glyph_pos; i--)
2494 x += glyphs.effectiveAdvance(i);
2495 } else {
2496 int start = qMax(line.from - si->position, 0);
2497 int glyph_start = logClusters[start];
2498 for (int i = glyph_start; i < glyph_pos; i++)
2499 x += glyphs.effectiveAdvance(i);
2500 }
2501 if (offsetInCluster > 0) { // in the case that the offset is inside a (multi-character) glyph, interpolate the position.
2502 int clusterLength = 0;
2503 for (int i=pos - offsetInCluster; i < line.length; i++) {
2504 if (logClusters[i] == glyph_pos)
2505 clusterLength++;
2506 else
2507 break;
2508 }
2509 if (clusterLength)
2510 x+= glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
2511 }
2512 }
2513
2514 *cursorPos = pos + si->position;
2515 return x.toReal();
2516}
2517
2518/*!
2519 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2520
2521 Converts the x-coordinate \a x, to the nearest matching cursor
2522 position, depending on the cursor position type, \a cpos.
2523
2524 \sa cursorToX()
2525*/
2526int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2527{
2528 QFixed x = QFixed::fromReal(_x);
2529 const QScriptLine &line = eng->lines[i];
2530
2531 if (!eng->layoutData)
2532 eng->itemize();
2533
2534 int line_length = textLength();
2535
2536 if (!line_length)
2537 return line.from;
2538
2539 int firstItem = eng->findItem(line.from);
2540 int lastItem = eng->findItem(line.from + line_length - 1);
2541 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2542
2543 if (!nItems)
2544 return 0;
2545
2546 x -= line.x;
2547 x -= alignLine(eng, line);
2548// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2549
2550 QVarLengthArray<int> visualOrder(nItems);
2551 QVarLengthArray<unsigned char> levels(nItems);
2552 for (int i = 0; i < nItems; ++i)
2553 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2554 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2555
2556 if (x <= 0) {
2557 // left of first item
2558 int item = visualOrder[0]+firstItem;
2559 QScriptItem &si = eng->layoutData->items[item];
2560 if (!si.num_glyphs)
2561 eng->shape(item);
2562 int pos = si.position;
2563 if (si.analysis.bidiLevel % 2)
2564 pos += eng->length(item);
2565 pos = qMax(line.from, pos);
2566 pos = qMin(line.from + line_length, pos);
2567 return pos;
2568 } else if (x < line.textWidth
2569 || (line.justified && x < line.width)) {
2570 // has to be in one of the runs
2571 QFixed pos;
2572
2573 eng->shapeLine(line);
2574 for (int i = 0; i < nItems; ++i) {
2575 int item = visualOrder[i]+firstItem;
2576 QScriptItem &si = eng->layoutData->items[item];
2577 int item_length = eng->length(item);
2578// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2579
2580 int start = qMax(line.from - si.position, 0);
2581 int end = qMin(line.from + line_length - si.position, item_length);
2582
2583 unsigned short *logClusters = eng->logClusters(&si);
2584
2585 int gs = logClusters[start];
2586 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2587 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2588
2589 QFixed item_width = 0;
2590 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2591 item_width = si.width;
2592 } else {
2593 int g = gs;
2594 while (g <= ge) {
2595 item_width += glyphs.effectiveAdvance(g);
2596 ++g;
2597 }
2598 }
2599// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2600
2601 if (pos + item_width < x) {
2602 pos += item_width;
2603 continue;
2604 }
2605// qDebug(" inside run");
2606 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2607 if (cpos == QTextLine::CursorOnCharacter)
2608 return si.position;
2609 bool left_half = (x - pos) < item_width/2;
2610
2611 if (bool(si.analysis.bidiLevel % 2) != left_half)
2612 return si.position;
2613 return si.position + 1;
2614 }
2615
2616 int glyph_pos = -1;
2617 // has to be inside run
2618 if (cpos == QTextLine::CursorOnCharacter) {
2619 if (si.analysis.bidiLevel % 2) {
2620 pos += item_width;
2621 glyph_pos = gs;
2622 while (gs <= ge) {
2623 if (glyphs.attributes[gs].clusterStart) {
2624 if (pos < x)
2625 break;
2626 glyph_pos = gs;
2627 break;
2628 }
2629 pos -= glyphs.effectiveAdvance(gs);
2630 ++gs;
2631 }
2632 } else {
2633 glyph_pos = gs;
2634 while (gs <= ge) {
2635 if (glyphs.attributes[gs].clusterStart) {
2636 if (pos > x)
2637 break;
2638 glyph_pos = gs;
2639 }
2640 pos += glyphs.effectiveAdvance(gs);
2641 ++gs;
2642 }
2643 }
2644 } else {
2645 QFixed dist = INT_MAX/256;
2646 if (si.analysis.bidiLevel % 2) {
2647 pos += item_width;
2648 while (gs <= ge) {
2649 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2650 glyph_pos = gs;
2651 dist = qAbs(x-pos);
2652 }
2653 pos -= glyphs.effectiveAdvance(gs);
2654 ++gs;
2655 }
2656 } else {
2657 while (gs <= ge) {
2658 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2659 glyph_pos = gs;
2660 dist = qAbs(x-pos);
2661 }
2662 pos += glyphs.effectiveAdvance(gs);
2663 ++gs;
2664 }
2665 }
2666 if (qAbs(x-pos) < dist)
2667 return si.position + end;
2668 }
2669 Q_ASSERT(glyph_pos != -1);
2670 int j;
2671 for (j = 0; j < eng->length(item); ++j)
2672 if (logClusters[j] == glyph_pos)
2673 break;
2674// qDebug("at pos %d (in run: %d)", si.position + j, j);
2675 return si.position + j;
2676 }
2677 }
2678 // right of last item
2679// qDebug() << "right of last";
2680 int item = visualOrder[nItems-1]+firstItem;
2681 QScriptItem &si = eng->layoutData->items[item];
2682 if (!si.num_glyphs)
2683 eng->shape(item);
2684 int pos = si.position;
2685 if (!(si.analysis.bidiLevel % 2))
2686 pos += eng->length(item);
2687 pos = qMax(line.from, pos);
2688
2689 int maxPos = line.from + line_length;
2690
2691 // except for the last line we assume that the
2692 // character between lines is a space and we want
2693 // to position the cursor to the left of that
2694 // character.
2695 // ###### breaks with japanese for example
2696 if (this->i < eng->lines.count() - 1)
2697 --maxPos;
2698
2699 pos = qMin(pos, maxPos);
2700 return pos;
2701}
2702
2703QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.