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

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

trunk: Merged in qt 4.6.1 sources.

File size: 79.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
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 [email protected].
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.textWidth + leadingSpaceWidth(eng, line));
93 else if (align & Qt::AlignHCenter)
94 x = (line.width - line.textWidth)/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 const 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 p->fillRect(QRectF(x, y, qreal(width), (base + descent + 1).toReal()), p->pen().brush());
1354 if (toggleAntialiasing)
1355 p->setRenderHint(QPainter::Antialiasing, false);
1356 if (d->layoutData->hasBidi) {
1357 const int arrow_extent = 4;
1358 int sign = rightToLeft ? -1 : 1;
1359 p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
1360 p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
1361 }
1362 return;
1363}
1364
1365/*!
1366 \class QTextLine
1367 \reentrant
1368
1369 \brief The QTextLine class represents a line of text inside a QTextLayout.
1370
1371 \ingroup richtext-processing
1372
1373 A text line is usually created by QTextLayout::createLine().
1374
1375 After being created, the line can be filled using the setLineWidth()
1376 or setNumColumns() functions. A line has a number of attributes including the
1377 rectangle it occupies, rect(), its coordinates, x() and y(), its
1378 textLength(), width() and naturalTextWidth(), and its ascent() and decent()
1379 relative to the text. The position of the cursor in terms of the
1380 line is available from cursorToX() and its inverse from
1381 xToCursor(). A line can be moved with setPosition().
1382*/
1383
1384/*!
1385 \enum QTextLine::Edge
1386
1387 \value Leading
1388 \value Trailing
1389*/
1390
1391/*!
1392 \enum QTextLine::CursorPosition
1393
1394 \value CursorBetweenCharacters
1395 \value CursorOnCharacter
1396*/
1397
1398/*!
1399 \fn QTextLine::QTextLine(int line, QTextEngine *e)
1400 \internal
1401
1402 Constructs a new text line using the line at position \a line in
1403 the text engine \a e.
1404*/
1405
1406/*!
1407 \fn QTextLine::QTextLine()
1408
1409 Creates an invalid line.
1410*/
1411
1412/*!
1413 \fn bool QTextLine::isValid() const
1414
1415 Returns true if this text line is valid; otherwise returns false.
1416*/
1417
1418/*!
1419 \fn int QTextLine::lineNumber() const
1420
1421 Returns the position of the line in the text engine.
1422*/
1423
1424
1425/*!
1426 Returns the line's bounding rectangle.
1427
1428 \sa x() y() textLength() width()
1429*/
1430QRectF QTextLine::rect() const
1431{
1432 const QScriptLine& sl = eng->lines[i];
1433 return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
1434}
1435
1436/*!
1437 Returns the rectangle covered by the line.
1438*/
1439QRectF QTextLine::naturalTextRect() const
1440{
1441 const QScriptLine& sl = eng->lines[i];
1442 QFixed x = sl.x + alignLine(eng, sl);
1443
1444 QFixed width = sl.textWidth;
1445 if (sl.justified)
1446 width = sl.width;
1447
1448 return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
1449}
1450
1451/*!
1452 Returns the line's x position.
1453
1454 \sa rect() y() textLength() width()
1455*/
1456qreal QTextLine::x() const
1457{
1458 return eng->lines[i].x.toReal();
1459}
1460
1461/*!
1462 Returns the line's y position.
1463
1464 \sa x() rect() textLength() width()
1465*/
1466qreal QTextLine::y() const
1467{
1468 return eng->lines[i].y.toReal();
1469}
1470
1471/*!
1472 Returns the line's width as specified by the layout() function.
1473
1474 \sa naturalTextWidth() x() y() textLength() rect()
1475*/
1476qreal QTextLine::width() const
1477{
1478 return eng->lines[i].width.toReal();
1479}
1480
1481
1482/*!
1483 Returns the line's ascent.
1484
1485 \sa descent() height()
1486*/
1487qreal QTextLine::ascent() const
1488{
1489 return eng->lines[i].ascent.toReal();
1490}
1491
1492/*!
1493 Returns the line's descent.
1494
1495 \sa ascent() height()
1496*/
1497qreal QTextLine::descent() const
1498{
1499 return eng->lines[i].descent.toReal();
1500}
1501
1502/*!
1503 Returns the line's height. This is equal to ascent() + descent() + 1
1504 if leading is not included. If leading is included, this equals to
1505 ascent() + descent() + leading() + 1.
1506
1507 \sa ascent() descent() leading() setLeadingIncluded()
1508*/
1509qreal QTextLine::height() const
1510{
1511 return eng->lines[i].height().toReal();
1512}
1513
1514/*!
1515 \since 4.6
1516
1517 Returns the line's leading.
1518
1519 \sa ascent() descent() height()
1520*/
1521qreal QTextLine::leading() const
1522{
1523 return eng->lines[i].leading.toReal();
1524}
1525
1526/*! \since 4.6
1527
1528 Includes positive leading into the line's height if \a included is true;
1529 otherwise does not include leading.
1530
1531 By default, leading is not included.
1532
1533 Note that negative leading is ignored, it must be handled
1534 in the code using the text lines by letting the lines overlap.
1535
1536 \sa leadingIncluded()
1537
1538*/
1539void QTextLine::setLeadingIncluded(bool included)
1540{
1541 eng->lines[i].leadingIncluded= included;
1542
1543}
1544
1545/*! \since 4.6
1546
1547 Returns true if positive leading is included into the line's height; otherwise returns false.
1548
1549 By default, leading is not included.
1550
1551 \sa setLeadingIncluded()
1552*/
1553bool QTextLine::leadingIncluded() const
1554{
1555 return eng->lines[i].leadingIncluded;
1556}
1557
1558
1559/*!
1560 Returns the width of the line that is occupied by text. This is
1561 always \<= to width(), and is the minimum width that could be used
1562 by layout() without changing the line break position.
1563*/
1564qreal QTextLine::naturalTextWidth() const
1565{
1566 return eng->lines[i].textWidth.toReal();
1567}
1568
1569/*!
1570 Lays out the line with the given \a width. The line is filled from
1571 its starting position with as many characters as will fit into
1572 the line. In case the text cannot be split at the end of the line,
1573 it will be filled with additional characters to the next whitespace
1574 or end of the text.
1575*/
1576void QTextLine::setLineWidth(qreal width)
1577{
1578 QScriptLine &line = eng->lines[i];
1579 if (!eng->layoutData) {
1580 qWarning("QTextLine: Can't set a line width while not layouting.");
1581 return;
1582 }
1583
1584 if (width > QFIXED_MAX)
1585 width = QFIXED_MAX;
1586
1587 line.width = QFixed::fromReal(width);
1588 if (line.length
1589 && line.textWidth <= line.width
1590 && line.from + line.length == eng->layoutData->string.length())
1591 // no need to do anything if the line is already layouted and the last one. This optimisation helps
1592 // when using things in a single line layout.
1593 return;
1594 line.length = 0;
1595 line.textWidth = 0;
1596
1597 layout_helper(INT_MAX);
1598}
1599
1600/*!
1601 Lays out the line. The line is filled from its starting position
1602 with as many characters as are specified by \a numColumns. In case
1603 the text cannot be split until \a numColumns characters, the line
1604 will be filled with as many characters to the next whitespace or
1605 end of the text.
1606*/
1607void QTextLine::setNumColumns(int numColumns)
1608{
1609 QScriptLine &line = eng->lines[i];
1610 line.width = QFIXED_MAX;
1611 line.length = 0;
1612 line.textWidth = 0;
1613 layout_helper(numColumns);
1614}
1615
1616/*!
1617 Lays out the line. The line is filled from its starting position
1618 with as many characters as are specified by \a numColumns. In case
1619 the text cannot be split until \a numColumns characters, the line
1620 will be filled with as many characters to the next whitespace or
1621 end of the text. The provided \a alignmentWidth is used as reference
1622 width for alignment.
1623*/
1624void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
1625{
1626 QScriptLine &line = eng->lines[i];
1627 line.width = QFixed::fromReal(alignmentWidth);
1628 line.length = 0;
1629 line.textWidth = 0;
1630 layout_helper(numColumns);
1631}
1632
1633#if 0
1634#define LB_DEBUG qDebug
1635#else
1636#define LB_DEBUG if (0) qDebug
1637#endif
1638
1639namespace {
1640
1641 struct LineBreakHelper
1642 {
1643 LineBreakHelper() : glyphCount(0), maxGlyphs(0), manualWrap(false) {}
1644
1645 QScriptLine tmpData;
1646 QScriptLine spaceData;
1647
1648 int glyphCount;
1649 int maxGlyphs;
1650
1651 QFixed minw;
1652 QFixed softHyphenWidth;
1653 QFixed rightBearing;
1654
1655 bool manualWrap;
1656
1657 bool checkFullOtherwiseExtend(QScriptLine &line);
1658 };
1659
1660inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
1661{
1662 LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
1663
1664 QFixed newWidth = line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth + rightBearing;
1665 if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
1666 return true;
1667
1668 minw = qMax(minw, tmpData.textWidth);
1669 line += tmpData;
1670 line.textWidth += spaceData.textWidth;
1671
1672 line.length += spaceData.length;
1673 tmpData.textWidth = 0;
1674 tmpData.length = 0;
1675 spaceData.textWidth = 0;
1676 spaceData.length = 0;
1677
1678 return false;
1679}
1680
1681} // anonymous namespace
1682
1683
1684static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
1685 const QScriptItem &current, const unsigned short *logClusters,
1686 const QGlyphLayout &glyphs)
1687{
1688 int glyphPosition = logClusters[pos];
1689 do { // got to the first next cluster
1690 ++pos;
1691 ++line.length;
1692 } while (pos < end && logClusters[pos] == glyphPosition);
1693 do { // calculate the textWidth for the rest of the current cluster.
1694 line.textWidth += glyphs.advances_x[glyphPosition] * !glyphs.attributes[glyphPosition].dontPrint;
1695 ++glyphPosition;
1696 } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
1697
1698 Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
1699
1700 ++glyphCount;
1701}
1702
1703
1704// fill QScriptLine
1705void QTextLine::layout_helper(int maxGlyphs)
1706{
1707 QScriptLine &line = eng->lines[i];
1708 line.length = 0;
1709 line.textWidth = 0;
1710 line.hasTrailingSpaces = false;
1711
1712 if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
1713 line.setDefaultHeight(eng);
1714 return;
1715 }
1716
1717 Q_ASSERT(line.from < eng->layoutData->string.length());
1718
1719 LineBreakHelper lbh;
1720
1721 lbh.maxGlyphs = maxGlyphs;
1722
1723 QTextOption::WrapMode wrapMode = eng->option.wrapMode();
1724 bool breakany = (wrapMode == QTextOption::WrapAnywhere);
1725 lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
1726
1727 // #### binary search!
1728 int item = -1;
1729 int newItem;
1730 for (newItem = eng->layoutData->items.size()-1; newItem > 0; --newItem) {
1731 if (eng->layoutData->items[newItem].position <= line.from)
1732 break;
1733 }
1734
1735 LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
1736
1737 Qt::Alignment alignment = eng->option.alignment();
1738
1739 const HB_CharAttributes *attributes = eng->attributes();
1740 int pos = line.from;
1741 int end = 0;
1742 QGlyphLayout glyphs;
1743 const unsigned short *logClusters = eng->layoutData->logClustersPtr;
1744
1745 while (newItem < eng->layoutData->items.size()) {
1746 lbh.rightBearing = 0;
1747 lbh.softHyphenWidth = 0;
1748 if (newItem != item) {
1749 item = newItem;
1750 const QScriptItem &current = eng->layoutData->items[item];
1751 if (!current.num_glyphs) {
1752 eng->shape(item);
1753 attributes = eng->attributes();
1754 logClusters = eng->layoutData->logClustersPtr;
1755 }
1756 pos = qMax(line.from, current.position);
1757 end = current.position + eng->length(item);
1758 glyphs = eng->shapedGlyphs(&current);
1759 }
1760 const QScriptItem &current = eng->layoutData->items[item];
1761
1762 lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent,
1763 current.leading + current.ascent) - qMax(lbh.tmpData.ascent,
1764 current.ascent);
1765 lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
1766 lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
1767
1768 if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
1769 if (lbh.checkFullOtherwiseExtend(line))
1770 goto found;
1771
1772 QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
1773 QFixed tabWidth = eng->calculateTabWidth(item, x);
1774
1775 lbh.spaceData.textWidth += tabWidth;
1776 lbh.spaceData.length++;
1777 newItem = item + 1;
1778
1779 QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
1780 lbh.glyphCount += qRound(tabWidth / averageCharWidth);
1781
1782 if (lbh.checkFullOtherwiseExtend(line))
1783 goto found;
1784 } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
1785 // if the line consists only of the line separator make sure
1786 // we have a sane height
1787 if (!line.length && !lbh.tmpData.length)
1788 line.setDefaultHeight(eng);
1789 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
1790 addNextCluster(pos, end, lbh.tmpData, lbh.glyphCount,
1791 current, logClusters, glyphs);
1792 } else {
1793 lbh.tmpData.length++;
1794 }
1795 line += lbh.tmpData;
1796 goto found;
1797 } else if (current.analysis.flags == QScriptAnalysis::Object) {
1798 lbh.tmpData.length++;
1799
1800 QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
1801 if (eng->block.docHandle())
1802 eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
1803
1804 lbh.tmpData.textWidth += current.width;
1805
1806 newItem = item + 1;
1807 ++lbh.glyphCount;
1808 if (lbh.checkFullOtherwiseExtend(line))
1809 goto found;
1810 } else if (attributes[pos].whiteSpace) {
1811 while (pos < end && attributes[pos].whiteSpace)
1812 addNextCluster(pos, end, lbh.spaceData, lbh.glyphCount,
1813 current, logClusters, glyphs);
1814
1815 if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
1816 lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
1817 goto found;
1818 }
1819 } else {
1820 bool sb_or_ws = false;
1821 do {
1822 addNextCluster(pos, end, lbh.tmpData, lbh.glyphCount,
1823 current, logClusters, glyphs);
1824
1825 if (attributes[pos].whiteSpace || attributes[pos-1].lineBreakType != HB_NoBreak) {
1826 sb_or_ws = true;
1827 break;
1828 } else if (breakany && attributes[pos].charStop) {
1829 break;
1830 }
1831 } while (pos < end);
1832 lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
1833
1834 if (pos && attributes[pos - 1].lineBreakType == HB_SoftHyphen) {
1835 // if we are splitting up a word because of
1836 // a soft hyphen then we ...
1837 //
1838 // a) have to take the width of the soft hyphen into
1839 // account to see if the first syllable(s) /and/
1840 // the soft hyphen fit into the line
1841 //
1842 // b) if we are so short of available width that the
1843 // soft hyphen is the first breakable position, then
1844 // we don't want to show it. However we initially
1845 // have to take the width for it into accoun so that
1846 // the text document layout sees the overflow and
1847 // switch to break-anywhere mode, in which we
1848 // want the soft-hyphen to slip into the next line
1849 // and thus become invisible again.
1850 //
1851 if (line.length)
1852 lbh.softHyphenWidth = glyphs.advances_x[logClusters[pos - 1]];
1853 else if (breakany)
1854 lbh.tmpData.textWidth += glyphs.advances_x[logClusters[pos - 1]];
1855 }
1856
1857 // The actual width of the text needs to take the right bearing into account. The
1858 // right bearing is left-ward, which means that if the rightmost pixel is to the right
1859 // of the advance of the glyph, the bearing will be negative. We flip the sign
1860 // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
1861 if (pos) {
1862 QFontEngine *fontEngine = eng->fontEngine(current);
1863 glyph_t glyph = glyphs.glyphs[logClusters[pos - 1]];
1864 glyph_metrics_t gi = fontEngine->boundingBox(glyph);
1865 if (gi.isValid())
1866 lbh.rightBearing = qMax(QFixed(), -(gi.xoff - gi.x - gi.width));
1867 }
1868
1869 if ((sb_or_ws|breakany) && lbh.checkFullOtherwiseExtend(line)) {
1870 if (!breakany) {
1871 line.textWidth += lbh.softHyphenWidth;
1872 }
1873
1874 line.textWidth += lbh.rightBearing;
1875
1876 goto found;
1877 }
1878 }
1879 if (pos == end)
1880 newItem = item + 1;
1881 }
1882 LB_DEBUG("reached end of line");
1883 lbh.checkFullOtherwiseExtend(line);
1884 line.textWidth += lbh.rightBearing;
1885
1886found:
1887 if (line.length == 0) {
1888 LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
1889 lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
1890 lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
1891 line += lbh.tmpData;
1892 }
1893
1894 LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
1895 line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
1896 LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
1897
1898 if (lbh.manualWrap) {
1899 eng->minWidth = qMax(eng->minWidth, line.textWidth);
1900 eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
1901 } else {
1902 eng->minWidth = qMax(eng->minWidth, lbh.minw);
1903 eng->maxWidth += line.textWidth;
1904 }
1905
1906 if (line.textWidth > 0 && item < eng->layoutData->items.size())
1907 eng->maxWidth += lbh.spaceData.textWidth;
1908 if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
1909 line.textWidth += lbh.spaceData.textWidth;
1910 line.length += lbh.spaceData.length;
1911 if (lbh.spaceData.length)
1912 line.hasTrailingSpaces = true;
1913
1914 line.justified = false;
1915 line.gridfitted = false;
1916
1917 if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
1918 if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
1919 || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
1920
1921 eng->option.setWrapMode(QTextOption::WrapAnywhere);
1922 line.length = 0;
1923 line.textWidth = 0;
1924 layout_helper(lbh.maxGlyphs);
1925 eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1926 }
1927 }
1928}
1929
1930/*!
1931 Moves the line to position \a pos.
1932*/
1933void QTextLine::setPosition(const QPointF &pos)
1934{
1935 eng->lines[i].x = QFixed::fromReal(pos.x());
1936 eng->lines[i].y = QFixed::fromReal(pos.y());
1937}
1938
1939/*!
1940 Returns the line's position relative to the text layout's position.
1941*/
1942QPointF QTextLine::position() const
1943{
1944 return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
1945}
1946
1947// ### DOC: I have no idea what this means/does.
1948// You create a text layout with a string of text. Once you laid
1949// it out, it contains a number of QTextLines. from() returns the position
1950// inside the text string where this line starts. If you e.g. has a
1951// text of "This is a string", laid out into two lines (the second
1952// starting at the word 'a'), layout.lineAt(0).from() == 0 and
1953// layout.lineAt(1).from() == 8.
1954/*!
1955 Returns the start of the line from the beginning of the string
1956 passed to the QTextLayout.
1957*/
1958int QTextLine::textStart() const
1959{
1960 return eng->lines[i].from;
1961}
1962
1963/*!
1964 Returns the length of the text in the line.
1965
1966 \sa naturalTextWidth()
1967*/
1968int QTextLine::textLength() const
1969{
1970 if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
1971 && eng->block.isValid() && i == eng->lines.count()-1) {
1972 return eng->lines[i].length - 1;
1973 }
1974 return eng->lines[i].length;
1975}
1976
1977static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
1978 int start, int glyph_start)
1979{
1980 int ge = glyph_start + gf.glyphs.numGlyphs;
1981 int gs = glyph_start;
1982 int end = start + gf.num_chars;
1983 unsigned short *logClusters = eng->logClusters(&si);
1984 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
1985 QFixed orig_width = gf.width;
1986
1987 int *ul = eng->underlinePositions;
1988 if (ul)
1989 while (*ul != -1 && *ul < start)
1990 ++ul;
1991 bool rtl = si.analysis.bidiLevel % 2;
1992 if (rtl)
1993 x += si.width;
1994
1995 do {
1996 int gtmp = ge;
1997 int stmp = end;
1998 if (ul && *ul != -1 && *ul < end) {
1999 stmp = *ul;
2000 gtmp = logClusters[*ul-si.position];
2001 }
2002
2003 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2004 gf.num_chars = stmp - start;
2005 gf.chars = eng->layoutData->string.unicode() + start;
2006 QFixed w = 0;
2007 while (gs < gtmp) {
2008 w += glyphs.effectiveAdvance(gs);
2009 ++gs;
2010 }
2011 start = stmp;
2012 gf.width = w;
2013 if (rtl)
2014 x -= w;
2015 if (gf.num_chars)
2016 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2017 if (!rtl)
2018 x += w;
2019 if (ul && *ul != -1 && *ul < end) {
2020 // draw underline
2021 gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
2022 ++stmp;
2023 gf.glyphs = glyphs.mid(gs, gtmp - gs);
2024 gf.num_chars = stmp - start;
2025 gf.chars = eng->layoutData->string.unicode() + start;
2026 gf.logClusters = logClusters + start - si.position;
2027 w = 0;
2028 while (gs < gtmp) {
2029 w += glyphs.effectiveAdvance(gs);
2030 ++gs;
2031 }
2032 ++start;
2033 gf.width = w;
2034 gf.underlineStyle = QTextCharFormat::SingleUnderline;
2035 if (rtl)
2036 x -= w;
2037 p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
2038 if (!rtl)
2039 x += w;
2040 gf.underlineStyle = QTextCharFormat::NoUnderline;
2041 ++gf.chars;
2042 ++ul;
2043 }
2044 } while (gs < ge);
2045
2046 gf.width = orig_width;
2047}
2048
2049
2050static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
2051{
2052 QBrush c = chf.foreground();
2053 if (c.style() == Qt::NoBrush) {
2054 p->setPen(defaultPen);
2055 }
2056
2057 QBrush bg = chf.background();
2058 if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
2059 p->fillRect(r, bg);
2060 if (c.style() != Qt::NoBrush) {
2061 p->setPen(QPen(c, 0));
2062 }
2063
2064}
2065
2066/*!
2067 \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
2068
2069 Draws a line on the given \a painter at the specified \a position.
2070 The \a selection is reserved for internal use.
2071*/
2072void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
2073{
2074 const QScriptLine &line = eng->lines[i];
2075 QPen pen = p->pen();
2076
2077 bool noText = (selection && selection->format.property(SuppressText).toBool());
2078
2079 if (!line.length) {
2080 if (selection
2081 && selection->start <= line.from
2082 && selection->start + selection->length > line.from) {
2083
2084 const qreal lineHeight = line.height().toReal();
2085 QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
2086 lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
2087 setPenAndDrawBackground(p, QPen(), selection->format, r);
2088 p->setPen(pen);
2089 }
2090 return;
2091 }
2092
2093
2094 QTextLineItemIterator iterator(eng, i, pos, selection);
2095 QFixed lineBase = line.base();
2096
2097 const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase;
2098
2099 bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
2100 while (!iterator.atEnd()) {
2101 QScriptItem &si = iterator.next();
2102
2103 if (selection && selection->start >= 0 && iterator.isOutsideSelection())
2104 continue;
2105
2106 if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
2107 && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
2108 continue;
2109
2110 QFixed itemBaseLine = y;
2111 QFont f = eng->font(si);
2112 QTextCharFormat format;
2113
2114 if (eng->hasFormats() || selection) {
2115 if (!suppressColors)
2116 format = eng->format(&si);
2117 if (selection)
2118 format.merge(selection->format);
2119
2120 setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
2121 iterator.itemWidth.toReal(), line.height().toReal()));
2122
2123 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2124 if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
2125 QFontEngine *fe = f.d->engineForScript(si.analysis.script);
2126 QFixed height = fe->ascent() + fe->descent();
2127 if (valign == QTextCharFormat::AlignSubScript)
2128 itemBaseLine += height / 6;
2129 else if (valign == QTextCharFormat::AlignSuperScript)
2130 itemBaseLine -= height / 2;
2131 }
2132 }
2133
2134 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2135
2136 if (eng->hasFormats()) {
2137 p->save();
2138 if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
2139 QFixed itemY = y - si.ascent;
2140 if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
2141 itemY = y - lineBase;
2142 }
2143
2144 QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
2145
2146 eng->docLayout()->drawInlineObject(p, itemRect,
2147 QTextInlineObject(iterator.item, eng),
2148 si.position + eng->block.position(),
2149 format);
2150 if (selection) {
2151 QBrush bg = format.brushProperty(ObjectSelectionBrush);
2152 if (bg.style() != Qt::NoBrush) {
2153 QColor c = bg.color();
2154 c.setAlpha(128);
2155 p->fillRect(itemRect, c);
2156 }
2157 }
2158 } else { // si.isTab
2159 QFont f = eng->font(si);
2160 QTextItemInt gf(si, &f, format);
2161 gf.chars = 0;
2162 gf.num_chars = 0;
2163 gf.width = iterator.itemWidth;
2164 p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
2165 if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
2166 QChar visualTab(0x2192);
2167 int w = QFontMetrics(f).width(visualTab);
2168 qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
2169 if (x < 0)
2170 p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
2171 iterator.itemWidth.toReal(), line.height().toReal()),
2172 Qt::IntersectClip);
2173 else
2174 x /= 2; // Centered
2175 p->drawText(QPointF(iterator.x.toReal() + x,
2176 y.toReal()), visualTab);
2177 }
2178
2179 }
2180 p->restore();
2181 }
2182
2183 continue;
2184 }
2185
2186 unsigned short *logClusters = eng->logClusters(&si);
2187 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2188
2189 QTextItemInt gf(si, &f, format);
2190 gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
2191 gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
2192 gf.logClusters = logClusters + iterator.itemStart - si.position;
2193 gf.num_chars = iterator.itemEnd - iterator.itemStart;
2194 gf.width = iterator.itemWidth;
2195 gf.justified = line.justified;
2196
2197 Q_ASSERT(gf.fontEngine);
2198
2199 if (eng->underlinePositions) {
2200 // can't have selections in this case
2201 drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
2202 } else {
2203 QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
2204 if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
2205 QPainterPath path;
2206 path.setFillRule(Qt::WindingFill);
2207
2208 if (gf.glyphs.numGlyphs)
2209 gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
2210 if (gf.flags) {
2211 const QFontEngine *fe = gf.fontEngine;
2212 const qreal lw = fe->lineThickness().toReal();
2213 if (gf.flags & QTextItem::Underline) {
2214 qreal offs = fe->underlinePosition().toReal();
2215 path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
2216 }
2217 if (gf.flags & QTextItem::Overline) {
2218 qreal offs = fe->ascent().toReal() + 1;
2219 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2220 }
2221 if (gf.flags & QTextItem::StrikeOut) {
2222 qreal offs = fe->ascent().toReal() / 3;
2223 path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
2224 }
2225 }
2226
2227 p->save();
2228 p->setRenderHint(QPainter::Antialiasing);
2229 //Currently QPen with a Qt::NoPen style still returns a default
2230 //QBrush which != Qt::NoBrush so we need this specialcase to reset it
2231 if (p->pen().style() == Qt::NoPen)
2232 p->setBrush(Qt::NoBrush);
2233 else
2234 p->setBrush(p->pen().brush());
2235
2236 p->setPen(format.textOutline());
2237 p->drawPath(path);
2238 p->restore();
2239 } else {
2240 if (noText)
2241 gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
2242 p->drawTextItem(pos, gf);
2243 }
2244 }
2245 if (si.analysis.flags == QScriptAnalysis::Space
2246 && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
2247 QBrush c = format.foreground();
2248 if (c.style() != Qt::NoBrush)
2249 p->setPen(c.color());
2250 QChar visualSpace((ushort)0xb7);
2251 p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
2252 p->setPen(pen);
2253 }
2254 }
2255
2256
2257 if (eng->hasFormats())
2258 p->setPen(pen);
2259}
2260
2261/*!
2262 \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
2263
2264 \overload
2265*/
2266
2267
2268/*!
2269 Converts the cursor position \a cursorPos to the corresponding x position
2270 inside the line, taking account of the \a edge.
2271
2272 If \a cursorPos is not a valid cursor position, the nearest valid
2273 cursor position will be used instead, and cpos will be modified to
2274 point to this valid cursor position.
2275
2276 \sa xToCursor()
2277*/
2278qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
2279{
2280 if (!eng->layoutData)
2281 eng->itemize();
2282
2283 const QScriptLine &line = eng->lines[i];
2284
2285 QFixed x = line.x;
2286 x += alignLine(eng, line);
2287
2288 if (!i && !eng->layoutData->items.size()) {
2289 *cursorPos = 0;
2290 return x.toReal();
2291 }
2292
2293 int pos = *cursorPos;
2294 int itm;
2295 if (pos == line.from + (int)line.length) {
2296 // end of line ensure we have the last item on the line
2297 itm = eng->findItem(pos-1);
2298 }
2299 else
2300 itm = eng->findItem(pos);
2301 eng->shapeLine(line);
2302
2303 const QScriptItem *si = &eng->layoutData->items[itm];
2304 if (!si->num_glyphs)
2305 eng->shape(itm);
2306 pos -= si->position;
2307
2308 QGlyphLayout glyphs = eng->shapedGlyphs(si);
2309 unsigned short *logClusters = eng->logClusters(si);
2310 Q_ASSERT(logClusters);
2311
2312 int l = eng->length(itm);
2313 if (pos > l)
2314 pos = l;
2315 if (pos < 0)
2316 pos = 0;
2317
2318 int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
2319 if (edge == Trailing) {
2320 // trailing edge is leading edge of next cluster
2321 while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
2322 glyph_pos++;
2323 }
2324
2325 bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
2326
2327 int lineEnd = line.from + line.length;
2328
2329 // add the items left of the cursor
2330
2331 int firstItem = eng->findItem(line.from);
2332 int lastItem = eng->findItem(lineEnd - 1);
2333 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2334
2335 QVarLengthArray<int> visualOrder(nItems);
2336 QVarLengthArray<uchar> levels(nItems);
2337 for (int i = 0; i < nItems; ++i)
2338 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2339 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2340
2341 for (int i = 0; i < nItems; ++i) {
2342 int item = visualOrder[i]+firstItem;
2343 if (item == itm)
2344 break;
2345 QScriptItem &si = eng->layoutData->items[item];
2346 if (!si.num_glyphs)
2347 eng->shape(item);
2348
2349 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2350 x += si.width;
2351 continue;
2352 }
2353 int start = qMax(line.from, si.position);
2354 int end = qMin(lineEnd, si.position + eng->length(item));
2355
2356 logClusters = eng->logClusters(&si);
2357
2358 int gs = logClusters[start-si.position];
2359 int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
2360
2361 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2362
2363 while (gs <= ge) {
2364 x += glyphs.effectiveAdvance(gs);
2365 ++gs;
2366 }
2367 }
2368
2369 logClusters = eng->logClusters(si);
2370 glyphs = eng->shapedGlyphs(si);
2371 if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
2372 if(pos == l)
2373 x += si->width;
2374 } else {
2375 int offsetInCluster = 0;
2376 for (int i=pos-1; i >= 0; i--) {
2377 if (logClusters[i] == glyph_pos)
2378 offsetInCluster++;
2379 else
2380 break;
2381 }
2382
2383 if (reverse) {
2384 int end = qMin(lineEnd, si->position + l) - si->position;
2385 int glyph_end = end == l ? si->num_glyphs : logClusters[end];
2386 for (int i = glyph_end - 1; i >= glyph_pos; i--)
2387 x += glyphs.effectiveAdvance(i);
2388 } else {
2389 int start = qMax(line.from - si->position, 0);
2390 int glyph_start = logClusters[start];
2391 for (int i = glyph_start; i < glyph_pos; i++)
2392 x += glyphs.effectiveAdvance(i);
2393 }
2394 if (offsetInCluster > 0) { // in the case that the offset is inside a (multi-character) glyph, interpolate the position.
2395 int clusterLength = 0;
2396 for (int i=pos - offsetInCluster; i < line.length; i++) {
2397 if (logClusters[i] == glyph_pos)
2398 clusterLength++;
2399 else
2400 break;
2401 }
2402 if (clusterLength)
2403 x+= glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
2404 }
2405 }
2406
2407 *cursorPos = pos + si->position;
2408 return x.toReal();
2409}
2410
2411/*!
2412 \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
2413
2414 Converts the x-coordinate \a x, to the nearest matching cursor
2415 position, depending on the cursor position type, \a cpos.
2416
2417 \sa cursorToX()
2418*/
2419int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
2420{
2421 QFixed x = QFixed::fromReal(_x);
2422 const QScriptLine &line = eng->lines[i];
2423
2424 if (!eng->layoutData)
2425 eng->itemize();
2426
2427 int line_length = textLength();
2428
2429 if (!line_length)
2430 return line.from;
2431
2432 int firstItem = eng->findItem(line.from);
2433 int lastItem = eng->findItem(line.from + line_length - 1);
2434 int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
2435
2436 if (!nItems)
2437 return 0;
2438
2439 x -= line.x;
2440 x -= alignLine(eng, line);
2441// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
2442
2443 QVarLengthArray<int> visualOrder(nItems);
2444 QVarLengthArray<unsigned char> levels(nItems);
2445 for (int i = 0; i < nItems; ++i)
2446 levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
2447 QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
2448
2449 if (x <= 0) {
2450 // left of first item
2451 int item = visualOrder[0]+firstItem;
2452 QScriptItem &si = eng->layoutData->items[item];
2453 if (!si.num_glyphs)
2454 eng->shape(item);
2455 int pos = si.position;
2456 if (si.analysis.bidiLevel % 2)
2457 pos += eng->length(item);
2458 pos = qMax(line.from, pos);
2459 pos = qMin(line.from + line_length, pos);
2460 return pos;
2461 } else if (x < line.textWidth
2462 || (line.justified && x < line.width)) {
2463 // has to be in one of the runs
2464 QFixed pos;
2465
2466 eng->shapeLine(line);
2467 for (int i = 0; i < nItems; ++i) {
2468 int item = visualOrder[i]+firstItem;
2469 QScriptItem &si = eng->layoutData->items[item];
2470 int item_length = eng->length(item);
2471// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
2472
2473 int start = qMax(line.from - si.position, 0);
2474 int end = qMin(line.from + line_length - si.position, item_length);
2475
2476 unsigned short *logClusters = eng->logClusters(&si);
2477
2478 int gs = logClusters[start];
2479 int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
2480 QGlyphLayout glyphs = eng->shapedGlyphs(&si);
2481
2482 QFixed item_width = 0;
2483 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2484 item_width = si.width;
2485 } else {
2486 int g = gs;
2487 while (g <= ge) {
2488 item_width += glyphs.effectiveAdvance(g);
2489 ++g;
2490 }
2491 }
2492// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
2493
2494 if (pos + item_width < x) {
2495 pos += item_width;
2496 continue;
2497 }
2498// qDebug(" inside run");
2499 if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
2500 if (cpos == QTextLine::CursorOnCharacter)
2501 return si.position;
2502 bool left_half = (x - pos) < item_width/2;
2503
2504 if (bool(si.analysis.bidiLevel % 2) != left_half)
2505 return si.position;
2506 return si.position + 1;
2507 }
2508
2509 int glyph_pos = -1;
2510 // has to be inside run
2511 if (cpos == QTextLine::CursorOnCharacter) {
2512 if (si.analysis.bidiLevel % 2) {
2513 pos += item_width;
2514 glyph_pos = gs;
2515 while (gs <= ge) {
2516 if (glyphs.attributes[gs].clusterStart) {
2517 if (pos < x)
2518 break;
2519 glyph_pos = gs;
2520 break;
2521 }
2522 pos -= glyphs.effectiveAdvance(gs);
2523 ++gs;
2524 }
2525 } else {
2526 glyph_pos = gs;
2527 while (gs <= ge) {
2528 if (glyphs.attributes[gs].clusterStart) {
2529 if (pos > x)
2530 break;
2531 glyph_pos = gs;
2532 }
2533 pos += glyphs.effectiveAdvance(gs);
2534 ++gs;
2535 }
2536 }
2537 } else {
2538 QFixed dist = INT_MAX/256;
2539 if (si.analysis.bidiLevel % 2) {
2540 pos += item_width;
2541 while (gs <= ge) {
2542 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2543 glyph_pos = gs;
2544 dist = qAbs(x-pos);
2545 }
2546 pos -= glyphs.effectiveAdvance(gs);
2547 ++gs;
2548 }
2549 } else {
2550 while (gs <= ge) {
2551 if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
2552 glyph_pos = gs;
2553 dist = qAbs(x-pos);
2554 }
2555 pos += glyphs.effectiveAdvance(gs);
2556 ++gs;
2557 }
2558 }
2559 if (qAbs(x-pos) < dist)
2560 return si.position + end;
2561 }
2562 Q_ASSERT(glyph_pos != -1);
2563 int j;
2564 for (j = 0; j < eng->length(item); ++j)
2565 if (logClusters[j] == glyph_pos)
2566 break;
2567// qDebug("at pos %d (in run: %d)", si.position + j, j);
2568 return si.position + j;
2569 }
2570 }
2571 // right of last item
2572// qDebug() << "right of last";
2573 int item = visualOrder[nItems-1]+firstItem;
2574 QScriptItem &si = eng->layoutData->items[item];
2575 if (!si.num_glyphs)
2576 eng->shape(item);
2577 int pos = si.position;
2578 if (!(si.analysis.bidiLevel % 2))
2579 pos += eng->length(item);
2580 pos = qMax(line.from, pos);
2581
2582 int maxPos = line.from + line_length;
2583
2584 // except for the last line we assume that the
2585 // character between lines is a space and we want
2586 // to position the cursor to the left of that
2587 // character.
2588 // ###### breaks with japanese for example
2589 if (this->i < eng->lines.count() - 1)
2590 --maxPos;
2591
2592 pos = qMin(pos, maxPos);
2593 return pos;
2594}
2595
2596QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.