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