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

Last change on this file since 371 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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