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

Last change on this file since 796 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

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