1
/****************************************************************************
2
**
3
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the QtGui module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include "qtextdocumentlayout_p.h"
43
#include "qtextdocument_p.h"
44
#include "qtextimagehandler_p.h"
45
#include "qtexttable.h"
46
#include "qtextlist.h"
47
#include "qtextengine_p.h"
48
#include "private/qcssutil_p.h"
49
50
#include "qabstracttextdocumentlayout_p.h"
51
#include "qcssparser_p.h"
52
53
#include <qpainter.h>
54
#include <qmath.h>
55
#include <qrect.h>
56
#include <qpalette.h>
57
#include <qdebug.h>
58
#include <qvarlengtharray.h>
59
#include <limits.h>
60
#include <qstyle.h>
61
#include <qbasictimer.h>
62
#include "private/qfunctions_p.h"
63
64
// #define LAYOUT_DEBUG
65
66
#ifdef LAYOUT_DEBUG
67
#define LDEBUG qDebug()
68
#define INC_INDENT debug_indent += "  "
69
#define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2)
70
#else
71
#define LDEBUG if(0) qDebug()
72
#define INC_INDENT do {} while(0)
73
#define DEC_INDENT do {} while(0)
74
#endif
75
76
QT_BEGIN_NAMESPACE
77
78
extern int qt_defaultDpi();
79
80
// ################ should probably add frameFormatChange notification!
81
82
struct QLayoutStruct;
83
84
class QTextFrameData : public QTextFrameLayoutData
85
{
86
public:
87
    QTextFrameData();
88
89
    // relative to parent frame
90
    QFixedPoint position;
91
    QFixedSize size;
92
93
    // contents starts at (margin+border/margin+border)
94
    QFixed topMargin;
95
    QFixed bottomMargin;
96
    QFixed leftMargin;
97
    QFixed rightMargin;
98
    QFixed border;
99
    QFixed padding;
100
    // contents width includes padding (as we need to treat this on a per cell basis for tables)
101
    QFixed contentsWidth;
102
    QFixed contentsHeight;
103
    QFixed oldContentsWidth;
104
105
    // accumulated margins
106
    QFixed effectiveTopMargin;
107
    QFixed effectiveBottomMargin;
108
109
    QFixed minimumWidth;
110
    QFixed maximumWidth;
111
112
    QLayoutStruct *currentLayoutStruct;
113
114
    bool sizeDirty;
115
    bool layoutDirty;
116
117
    QList<QPointer<QTextFrame> > floats;
118
};
119
120
QTextFrameData::QTextFrameData()
121
    : maximumWidth(QFIXED_MAX),
122
      currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
123
{
124
}
125
126
struct QLayoutStruct {
127
    QLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
128
    {}
129
    QTextFrame *frame;
130
    QFixed x_left;
131
    QFixed x_right;
132
    QFixed frameY; // absolute y position of the current frame
133
    QFixed y; // always relative to the current frame
134
    QFixed contentsWidth;
135
    QFixed minimumWidth;
136
    QFixed maximumWidth;
137
    bool fullLayout;
138
    QList<QTextFrame *> pendingFloats;
139
    QFixed pageHeight;
140
    QFixed pageBottom;
141
    QFixed pageTopMargin;
142
    QFixed pageBottomMargin;
143
    QRectF updateRect;
144
    QRectF updateRectForFloats;
145
146
    inline void addUpdateRectForFloat(const QRectF &rect) {
147
        if (updateRectForFloats.isValid())
148
            updateRectForFloats |= rect;
149
        else
150
            updateRectForFloats = rect;
151
    }
152
153
    inline QFixed absoluteY() const
154
    { return frameY + y; }
155
156
    inline int currentPage() const
157
    { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
158
159
    inline void newPage()
160
    { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; }
161
};
162
163
class QTextTableData : public QTextFrameData
164
{
165
public:
166
    QFixed cellSpacing, cellPadding;
167
    qreal deviceScale;
168
    QVector<QFixed> minWidths;
169
    QVector<QFixed> maxWidths;
170
    QVector<QFixed> widths;
171
    QVector<QFixed> heights;
172
    QVector<QFixed> columnPositions;
173
    QVector<QFixed> rowPositions;
174
175
    QVector<QFixed> cellVerticalOffsets;
176
177
    QFixed headerHeight;
178
179
    // maps from cell index (row + col * rowCount) to child frames belonging to
180
    // the specific cell
181
    QMultiHash<int, QTextFrame *> childFrameMap;
182
183
    inline QFixed cellWidth(int column, int colspan) const
184
    { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
185
             - columnPositions.at(column); }
186
187
    inline void calcRowPosition(int row)
188
    {
189
        if (row > 0)
190
            rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
191
    }
192
193
    QRectF cellRect(const QTextTableCell &cell) const;
194
195
    inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
196
    {
197
        QVariant v = format.property(property);
198
        if (v.isNull()) {
199
            return cellPadding;
200
        } else {
201
            Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float);
202
            return QFixed::fromReal(v.toReal() * deviceScale);
203
        }
204
    }
205
206
    inline QFixed topPadding(const QTextFormat &format) const
207
    {
208
        return paddingProperty(format, QTextFormat::TableCellTopPadding);
209
    }
210
211
    inline QFixed bottomPadding(const QTextFormat &format) const
212
    {
213
        return paddingProperty(format, QTextFormat::TableCellBottomPadding);
214
    }
215
216
    inline QFixed leftPadding(const QTextFormat &format) const
217
    {
218
        return paddingProperty(format, QTextFormat::TableCellLeftPadding);
219
    }
220
221
    inline QFixed rightPadding(const QTextFormat &format) const
222
    {
223
        return paddingProperty(format, QTextFormat::TableCellRightPadding);
224
    }
225
226
    inline QFixedPoint cellPosition(const QTextTableCell &cell) const
227
    {
228
        const QTextFormat fmt = cell.format();
229
        return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
230
    }
231
232
    void updateTableSize();
233
234
private:
235
    inline QFixedPoint cellPosition(int row, int col) const
236
    { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
237
};
238
239
static QTextFrameData *createData(QTextFrame *f)
240
{
241
    QTextFrameData *data;
242
    if (qobject_cast<QTextTable *>(f))
243
        data = new QTextTableData;
244
    else
245
        data = new QTextFrameData;
246
    f->setLayoutData(data);
247
    return data;
248
}
249
250
static inline QTextFrameData *data(QTextFrame *f)
251
{
252
    QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
253
    if (!data)
254
        data = createData(f);
255
    return data;
256
}
257
258
static bool isFrameFromInlineObject(QTextFrame *f)
259
{
260
    return f->firstPosition() > f->lastPosition();
261
}
262
263
void QTextTableData::updateTableSize()
264
{
265
    const QFixed effectiveTopMargin = this->topMargin + border + padding;
266
    const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
267
    const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
268
    const QFixed effectiveRightMargin = this->rightMargin + border + padding;
269
    size.height = contentsHeight == -1
270
                   ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin
271
                   : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
272
    size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
273
}
274
275
QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
276
{
277
    const int row = cell.row();
278
    const int rowSpan = cell.rowSpan();
279
    const int column = cell.column();
280
    const int colSpan = cell.columnSpan();
281
282
    return QRectF(columnPositions.at(column).toReal(),
283
                  rowPositions.at(row).toReal(),
284
                  (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
285
                  (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
286
}
287
288
static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
289
{
290
    return !nextIt.atEnd()
291
           && qobject_cast<QTextTable *>(nextIt.currentFrame())
292
           && block.isValid()
293
           && block.length() == 1
294
           && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
295
           && !format.hasProperty(QTextFormat::BackgroundBrush)
296
           && nextIt.currentFrame()->firstPosition() == block.position() + 1
297
           ;
298
}
299
300
static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it)
301
{
302
    QTextFrame::Iterator next = it; ++next;
303
    if (it.currentFrame())
304
        return false;
305
    QTextBlock block = it.currentBlock();
306
    return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
307
}
308
309
static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
310
{
311
    return qobject_cast<const QTextTable *>(previousFrame)
312
           && block.isValid()
313
           && block.length() == 1
314
           && previousFrame->lastPosition() == block.position() - 1
315
           ;
316
}
317
318
static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
319
{
320
    return qobject_cast<const QTextTable *>(previousFrame)
321
           && block.isValid()
322
           && block.length() > 1
323
           && block.text().at(0) == QChar::LineSeparator
324
           && previousFrame->lastPosition() == block.position() - 1
325
           ;
326
}
327
328
/*
329
330
Optimisation strategies:
331
332
HTML layout:
333
334
* Distinguish between normal and special flow. For normal flow the condition:
335
  y1 > y2 holds for all blocks with b1.key() > b2.key().
336
* Special flow is: floats, table cells
337
338
* Normal flow within table cells. Tables (not cells) are part of the normal flow.
339
340
341
* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
342
* If height doesn't change, no need to do anything
343
344
Table cells:
345
346
* If minWidth of cell changes, recalculate table width, relayout if needed.
347
* What about maxWidth when doing auto layout?
348
349
Floats:
350
* need fixed or proportional width, otherwise don't float!
351
* On width/height change relayout surrounding paragraphs.
352
353
Document width change:
354
* full relayout needed
355
356
357
Float handling:
358
359
* Floats are specified by a special format object.
360
* currently only floating images are implemented.
361
362
*/
363
364
/*
365
366
   On the table layouting:
367
368
   +---[ table border ]-------------------------
369
   |      [ cell spacing ]
370
   |  +------[ cell border ]-----+  +--------
371
   |  |                          |  |
372
   |  |
373
   |  |
374
   |  |
375
   |
376
377
   rowPositions[i] and columnPositions[i] point at the cell content
378
   position. So for example the left border is drawn at
379
   x = columnPositions[i] - fd->border and similar for y.
380
381
*/
382
383
struct QCheckPoint
384
{
385
    QFixed y;
386
    QFixed frameY; // absolute y position of the current frame
387
    int positionInFrame;
388
    QFixed minimumWidth;
389
    QFixed maximumWidth;
390
    QFixed contentsWidth;
391
};
392
Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
393
394
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y)
395
{
396
    return checkPoint.y < y;
397
}
398
399
Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos)
400
{
401
    return checkPoint.positionInFrame < pos;
402
}
403
404
static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF())
405
{
406
    p->save();
407
    if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
408
        if (!gradientRect.isNull()) {
409
            QTransform m;
410
            m.translate(gradientRect.left(), gradientRect.top());
411
            m.scale(gradientRect.width(), gradientRect.height());
412
            brush.setTransform(m);
413
            const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
414
        }
415
    } else {
416
        p->setBrushOrigin(origin);
417
    }
418
    p->fillRect(rect, brush);
419
    p->restore();
420
}
421
422
class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
423
{
424
    Q_DECLARE_PUBLIC(QTextDocumentLayout)
425
public:
426
    QTextDocumentLayoutPrivate();
427
428
    QTextOption::WrapMode wordWrapMode;
429
#ifdef LAYOUT_DEBUG
430
    mutable QString debug_indent;
431
#endif
432
433
    int fixedColumnWidth;
434
    int cursorWidth;
435
436
    QSizeF lastReportedSize;
437
    QRectF viewportRect;
438
    QRectF clipRect;
439
440
    mutable int currentLazyLayoutPosition;
441
    mutable int lazyLayoutStepSize;
442
    QBasicTimer layoutTimer;
443
    mutable QBasicTimer sizeChangedTimer;
444
    uint showLayoutProgress : 1;
445
    uint insideDocumentChange : 1;
446
447
    int lastPageCount;
448
    qreal idealWidth;
449
    bool contentHasAlignment;
450
451
    QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
452
453
    void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
454
                   QTextFrame *f) const;
455
    void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
456
                  QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
457
    void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
458
                   QTextBlock bl, bool inRootFrame) const;
459
    void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
460
                      QTextBlock bl, const QTextCharFormat *selectionFormat) const;
461
    void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
462
                       QTextTable *table, QTextTableData *td, int r, int c,
463
                       QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
464
    void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
465
                    const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
466
    void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
467
468
    enum HitPoint {
469
        PointBefore,
470
        PointAfter,
471
        PointInside,
472
        PointExact
473
    };
474
    HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
475
    HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
476
                     int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
477
    HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
478
    HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
479
480
    QLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
481
                            int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
482
                            bool withPageBreaks);
483
    void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
484
    QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
485
486
    void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0);
487
488
    // calls the next one
489
    QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
490
    QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
491
492
    void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
493
                     QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
494
    void layoutFlow(QTextFrame::Iterator it, QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
495
    void pageBreakInsideTable(QTextTable *table, QLayoutStruct *layoutStruct);
496
497
498
    void floatMargins(const QFixed &y, const QLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
499
    QFixed findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const;
500
501
    QVector<QCheckPoint> checkPoints;
502
503
    QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
504
    QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
505
506
    void ensureLayouted(QFixed y) const;
507
    void ensureLayoutedByPosition(int position) const;
508
    inline void ensureLayoutFinished() const
509
    { ensureLayoutedByPosition(INT_MAX); }
510
    void layoutStep() const;
511
512
    QRectF frameBoundingRectInternal(QTextFrame *frame) const;
513
514
    qreal scaleToDevice(qreal value) const;
515
    QFixed scaleToDevice(QFixed value) const;
516
};
517
518
QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
519
    : fixedColumnWidth(-1),
520
      cursorWidth(1),
521
      currentLazyLayoutPosition(-1),
522
      lazyLayoutStepSize(1000),
523
      lastPageCount(-1)
524
{
525
    showLayoutProgress = true;
526
    insideDocumentChange = false;
527
    idealWidth = 0;
528
    contentHasAlignment = false;
529
}
530
531
QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
532
{
533
    QTextFrame *rootFrame = document->rootFrame();
534
535
    if (checkPoints.isEmpty()
536
        || y < 0 || y > data(rootFrame)->size.height)
537
        return rootFrame->begin();
538
539
    QVector<QCheckPoint>::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y);
540
    if (checkPoint == checkPoints.end())
541
        return rootFrame->begin();
542
543
    if (checkPoint != checkPoints.begin())
544
        --checkPoint;
545
546
    const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
547
    return frameIteratorForTextPosition(position);
548
}
549
550
QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
551
{
552
    QTextFrame *rootFrame = docPrivate->rootFrame();
553
554
    const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
555
    const int begin = map.findNode(rootFrame->firstPosition());
556
    const int end = map.findNode(rootFrame->lastPosition()+1);
557
558
    const int block = map.findNode(position);
559
    const int blockPos = map.position(block);
560
561
    QTextFrame::iterator it(rootFrame, block, begin, end);
562
563
    QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
564
    if (containingFrame != rootFrame) {
565
        while (containingFrame->parentFrame() != rootFrame) {
566
            containingFrame = containingFrame->parentFrame();
567
            Q_ASSERT(containingFrame);
568
        }
569
570
        it.cf = containingFrame;
571
        it.cb = 0;
572
    }
573
574
    return it;
575
}
576
577
QTextDocumentLayoutPrivate::HitPoint
578
QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
579
{
580
    QTextFrameData *fd = data(frame);
581
    // #########
582
    if (fd->layoutDirty)
583
        return PointAfter;
584
    Q_ASSERT(!fd->layoutDirty);
585
    Q_ASSERT(!fd->sizeDirty);
586
    const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
587
588
    QTextFrame *rootFrame = docPrivate->rootFrame();
589
590
//     LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point
591
//            << "position" << fd->position << "size" << fd->size;
592
    if (frame != rootFrame) {
593
        if (relativePoint.y < 0 || relativePoint.x < 0) {
594
            *position = frame->firstPosition() - 1;
595
//             LDEBUG << "before pos=" << *position;
596
            return PointBefore;
597
        } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
598
            *position = frame->lastPosition() + 1;
599
//             LDEBUG << "after pos=" << *position;
600
            return PointAfter;
601
        }
602
    }
603
604
    if (isFrameFromInlineObject(frame)) {
605
        *position = frame->firstPosition() - 1;
606
        return PointExact;
607
    }
608
609
    if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
610
        const int rows = table->rows();
611
        const int columns = table->columns();
612
        QTextTableData *td = static_cast<QTextTableData *>(data(table));
613
614
        if (!td->childFrameMap.isEmpty()) {
615
            for (int r = 0; r < rows; ++r) {
616
                for (int c = 0; c < columns; ++c) {
617
                    QTextTableCell cell = table->cellAt(r, c);
618
                    if (cell.row() != r || cell.column() != c)
619
                        continue;
620
621
                    QRectF cellRect = td->cellRect(cell);
622
                    const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
623
                    const QFixedPoint pointInCell = relativePoint - cellPos;
624
625
                    const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
626
                    for (int i = 0; i < childFrames.size(); ++i) {
627
                        QTextFrame *child = childFrames.at(i);
628
                        if (isFrameFromInlineObject(child)
629
                            && child->frameFormat().position() != QTextFrameFormat::InFlow
630
                            && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
631
                        {
632
                            return PointExact;
633
                        }
634
                    }
635
                }
636
            }
637
        }
638
639
        return hitTest(table, relativePoint, position, l, accuracy);
640
    }
641
642
    const QList<QTextFrame *> childFrames = frame->childFrames();
643
    for (int i = 0; i < childFrames.size(); ++i) {
644
        QTextFrame *child = childFrames.at(i);
645
        if (isFrameFromInlineObject(child)
646
            && child->frameFormat().position() != QTextFrameFormat::InFlow
647
            && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
648
        {
649
            return PointExact;
650
        }
651
    }
652
653
    QTextFrame::Iterator it = frame->begin();
654
655
    if (frame == rootFrame) {
656
        it = frameIteratorForYPosition(relativePoint.y);
657
658
        Q_ASSERT(it.parentFrame() == frame);
659
    }
660
661
    if (it.currentFrame())
662
        *position = it.currentFrame()->firstPosition();
663
    else
664
        *position = it.currentBlock().position();
665
666
    return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
667
}
668
669
QTextDocumentLayoutPrivate::HitPoint
670
QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
671
                                    int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
672
{
673
    INC_INDENT;
674
675
    for (; !it.atEnd(); ++it) {
676
        QTextFrame *c = it.currentFrame();
677
        HitPoint hp;
678
        int pos = -1;
679
        if (c) {
680
            hp = hitTest(c, p, &pos, l, accuracy);
681
        } else {
682
            hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
683
        }
684
        if (hp >= PointInside) {
685
            if (isEmptyBlockBeforeTable(it))
686
                continue;
687
            hit = hp;
688
            *position = pos;
689
            break;
690
        }
691
        if (hp == PointBefore && pos < *position) {
692
            *position = pos;
693
            hit = hp;
694
        } else if (hp == PointAfter && pos > *position) {
695
            *position = pos;
696
            hit = hp;
697
        }
698
    }
699
700
    DEC_INDENT;
701
//     LDEBUG << "inside=" << hit << " pos=" << *position;
702
    return hit;
703
}
704
705
QTextDocumentLayoutPrivate::HitPoint
706
QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
707
                                    int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
708
{
709
    QTextTableData *td = static_cast<QTextTableData *>(data(table));
710
711
    QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
712
    if (rowIt == td->rowPositions.constEnd()) {
713
        rowIt = td->rowPositions.constEnd() - 1;
714
    } else if (rowIt != td->rowPositions.constBegin()) {
715
        --rowIt;
716
    }
717
718
    QVector<QFixed>::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
719
    if (colIt == td->columnPositions.constEnd()) {
720
        colIt = td->columnPositions.constEnd() - 1;
721
    } else if (colIt != td->columnPositions.constBegin()) {
722
        --colIt;
723
    }
724
725
    QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
726
                                        colIt - td->columnPositions.constBegin());
727
    if (!cell.isValid())
728
        return PointBefore;
729
730
    *position = cell.firstPosition();
731
732
    HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
733
734
    if (hp == PointExact)
735
        return hp;
736
    if (hp == PointAfter)
737
        *position = cell.lastPosition();
738
    return PointInside;
739
}
740
741
QTextDocumentLayoutPrivate::HitPoint
742
QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l,
743
                                    Qt::HitTestAccuracy accuracy) const
744
{
745
    QTextLayout *tl = bl.layout();
746
    QRectF textrect = tl->boundingRect();
747
    textrect.translate(tl->position());
748
//     LDEBUG << "    checking block" << bl.position() << "point=" << point
749
//            << "    tlrect" << textrect;
750
    *position = bl.position();
751
    if (point.y.toReal() < textrect.top()) {
752
//             LDEBUG << "    before pos=" << *position;
753
        return PointBefore;
754
    } else if (point.y.toReal() > textrect.bottom()) {
755
        *position += bl.length();
756
//             LDEBUG << "    after pos=" << *position;
757
        return PointAfter;
758
    }
759
760
    QPointF pos = point.toPointF() - tl->position();
761
762
    // ### rtl?
763
764
    HitPoint hit = PointInside;
765
    *l = tl;
766
    int off = 0;
767
    for (int i = 0; i < tl->lineCount(); ++i) {
768
        QTextLine line = tl->lineAt(i);
769
        const QRectF lr = line.naturalTextRect();
770
        if (lr.top() > pos.y()) {
771
            off = qMin(off, line.textStart());
772
        } else if (lr.bottom() <= pos.y()) {
773
            off = qMax(off, line.textStart() + line.textLength());
774
        } else {
775
            if (lr.left() <= pos.x() && lr.right() >= pos.x())
776
                hit = PointExact;
777
            // when trying to hit an anchor we want it to hit not only in the left
778
            // half
779
            if (accuracy == Qt::ExactHit)
780
                off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
781
            else
782
                off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
783
            break;
784
        }
785
    }
786
    *position += off;
787
788
//     LDEBUG << "    inside=" << hit << " pos=" << *position;
789
    return hit;
790
}
791
792
// ### could be moved to QTextBlock
793
QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
794
{
795
    qreal indent = blockFormat.indent();
796
797
    QTextObject *object = document->objectForFormat(blockFormat);
798
    if (object)
799
        indent += object->format().toListFormat().indent();
800
801
    if (qIsNull(indent))
802
        return 0;
803
804
    qreal scale = 1;
805
    if (paintDevice) {
806
        scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
807
    }
808
809
    return QFixed::fromReal(indent * scale * document->indentWidth());
810
}
811
812
void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
813
                                            qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
814
{
815
    const qreal pageHeight = document->pageSize().height();
816
    const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
817
    const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
818
819
#ifndef QT_NO_CSSPARSER
820
    QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
821
#endif //QT_NO_CSSPARSER
822
823
    bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
824
    painter->setRenderHint(QPainter::Antialiasing);
825
826
    for (int i = topPage; i <= bottomPage; ++i) {
827
        QRectF clipped = rect.toRect();
828
829
        if (topPage != bottomPage) {
830
            clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
831
            clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
832
833
            if (clipped.bottom() <= clipped.top())
834
                continue;
835
        }
836
#ifndef QT_NO_CSSPARSER
837
        qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
838
        qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
839
        qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
840
        qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
841
#else
842
        painter->save();
843
        painter->setPen(Qt::NoPen);
844
        painter->setBrush(brush);
845
        painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
846
        painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
847
        painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
848
        painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
849
        painter->restore();
850
#endif //QT_NO_CSSPARSER
851
    }
852
    if (turn_off_antialiasing)
853
        painter->setRenderHint(QPainter::Antialiasing, false);
854
}
855
856
void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
857
{
858
859
    const QBrush bg = frame->frameFormat().background();
860
    if (bg != Qt::NoBrush) {
861
        QRectF bgRect = rect;
862
        bgRect.adjust((fd->leftMargin + fd->border).toReal(),
863
                      (fd->topMargin + fd->border).toReal(),
864
                      - (fd->rightMargin + fd->border).toReal(),
865
                      - (fd->bottomMargin + fd->border).toReal());
866
867
        QRectF gradientRect; // invalid makes it default to bgRect
868
        QPointF origin = bgRect.topLeft();
869
        if (!frame->parentFrame()) {
870
            bgRect = clip;
871
            gradientRect.setWidth(painter->device()->width());
872
            gradientRect.setHeight(painter->device()->height());
873
        }
874
        fillBackground(painter, bgRect, bg, origin, gradientRect);
875
    }
876
    if (fd->border != 0) {
877
        painter->save();
878
        painter->setBrush(Qt::lightGray);
879
        painter->setPen(Qt::NoPen);
880
881
        const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
882
        const qreal border = fd->border.toReal();
883
        const qreal topMargin = fd->topMargin.toReal();
884
        const qreal leftMargin = fd->leftMargin.toReal();
885
        const qreal bottomMargin = fd->bottomMargin.toReal();
886
        const qreal rightMargin = fd->rightMargin.toReal();
887
        const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
888
        const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
889
890
        drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
891
                   fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
892
                   border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
893
894
        painter->restore();
895
    }
896
}
897
898
static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
899
                                           const QTextTableCell &cell,
900
                                           int r, int c,
901
                                           const int *selectedTableCells)
902
{
903
    for (int i = 0; i < cell_context.selections.size(); ++i) {
904
        int row_start = selectedTableCells[i * 4];
905
        int col_start = selectedTableCells[i * 4 + 1];
906
        int num_rows = selectedTableCells[i * 4 + 2];
907
        int num_cols = selectedTableCells[i * 4 + 3];
908
909
        if (row_start != -1) {
910
            if (r >= row_start && r < row_start + num_rows
911
                && c >= col_start && c < col_start + num_cols)
912
            {
913
                int firstPosition = cell.firstPosition();
914
                int lastPosition = cell.lastPosition();
915
916
                // make sure empty cells are still selected
917
                if (firstPosition == lastPosition)
918
                    ++lastPosition;
919
920
                cell_context.selections[i].cursor.setPosition(firstPosition);
921
                cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
922
            } else {
923
                cell_context.selections[i].cursor.clearSelection();
924
            }
925
        }
926
927
        // FullWidthSelection is not useful for tables
928
        cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
929
    }
930
}
931
932
void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
933
                                           const QAbstractTextDocumentLayout::PaintContext &context,
934
                                           QTextFrame *frame) const
935
{
936
    QTextFrameData *fd = data(frame);
937
    // #######
938
    if (fd->layoutDirty)
939
        return;
940
    Q_ASSERT(!fd->sizeDirty);
941
    Q_ASSERT(!fd->layoutDirty);
942
943
    const QPointF off = offset + fd->position.toPointF();
944
    if (context.clip.isValid()
945
        && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
946
            || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
947
        return;
948
949
//     LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
950
//     INC_INDENT;
951
952
    // if the cursor is /on/ a table border we may need to repaint it
953
    // afterwards, as we usually draw the decoration first
954
    QTextBlock cursorBlockNeedingRepaint;
955
    QPointF offsetOfRepaintedCursorBlock = off;
956
957
    QTextTable *table = qobject_cast<QTextTable *>(frame);
958
    const QRectF frameRect(off, fd->size.toSizeF());
959
960
    if (table) {
961
        const int rows = table->rows();
962
        const int columns = table->columns();
963
        QTextTableData *td = static_cast<QTextTableData *>(data(table));
964
965
        QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
966
        for (int i = 0; i < context.selections.size(); ++i) {
967
            const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
968
            int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
969
970
            if (s.cursor.currentTable() == table)
971
                s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
972
973
            selectedTableCells[i * 4] = row_start;
974
            selectedTableCells[i * 4 + 1] = col_start;
975
            selectedTableCells[i * 4 + 2] = num_rows;
976
            selectedTableCells[i * 4 + 3] = num_cols;
977
        }
978
979
        QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
980
        if (pageHeight <= 0)
981
            pageHeight = QFIXED_MAX;
982
983
        const int tableStartPage = (td->position.y / pageHeight).truncate();
984
        const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate();
985
986
        qreal border = td->border.toReal();
987
        drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
988
989
        // draw the table headers
990
        const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
991
        int page = tableStartPage + 1;
992
        while (page <= tableEndPage) {
993
            const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
994
            const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
995
            for (int r = 0; r < headerRowCount; ++r) {
996
                for (int c = 0; c < columns; ++c) {
997
                    QTextTableCell cell = table->cellAt(r, c);
998
                    QAbstractTextDocumentLayout::PaintContext cell_context = context;
999
                    adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1000
                    QRectF cellRect = td->cellRect(cell);
1001
1002
                    cellRect.translate(off.x(), headerOffset);
1003
                    // we need to account for the cell border in the clipping test
1004
                    int leftAdjust = qMin(qreal(0), 1 - border);
1005
                    if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1006
                        continue;
1007
1008
                    drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1009
                                  &offsetOfRepaintedCursorBlock);
1010
                }
1011
            }
1012
            ++page;
1013
        }
1014
1015
        int firstRow = 0;
1016
        int lastRow = rows;
1017
1018
        if (context.clip.isValid()) {
1019
            QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
1020
            if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
1021
                --rowIt;
1022
                firstRow = rowIt - td->rowPositions.constBegin();
1023
            }
1024
1025
            rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
1026
            if (rowIt != td->rowPositions.constEnd()) {
1027
                ++rowIt;
1028
                lastRow = rowIt - td->rowPositions.constBegin();
1029
            }
1030
        }
1031
1032
        for (int c = 0; c < columns; ++c) {
1033
            QTextTableCell cell = table->cellAt(firstRow, c);
1034
            firstRow = qMin(firstRow, cell.row());
1035
        }
1036
1037
        for (int r = firstRow; r < lastRow; ++r) {
1038
            for (int c = 0; c < columns; ++c) {
1039
                QTextTableCell cell = table->cellAt(r, c);
1040
                QAbstractTextDocumentLayout::PaintContext cell_context = context;
1041
                adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1042
                QRectF cellRect = td->cellRect(cell);
1043
1044
                cellRect.translate(off);
1045
                // we need to account for the cell border in the clipping test
1046
                int leftAdjust = qMin(qreal(0), 1 - border);
1047
                if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1048
                    continue;
1049
1050
                drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1051
                              &offsetOfRepaintedCursorBlock);
1052
            }
1053
        }
1054
1055
    } else {
1056
        drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1057
1058
        QTextFrame::Iterator it = frame->begin();
1059
1060
        if (frame == docPrivate->rootFrame())
1061
            it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
1062
1063
        QList<QTextFrame *> floats;
1064
        for (int i = 0; i < fd->floats.count(); ++i)
1065
            floats.append(fd->floats.at(i));
1066
1067
        drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
1068
    }
1069
1070
    if (cursorBlockNeedingRepaint.isValid()) {
1071
        const QPen oldPen = painter->pen();
1072
        painter->setPen(context.palette.color(QPalette::Text));
1073
        const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
1074
        cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
1075
                                                       cursorPos, cursorWidth);
1076
        painter->setPen(oldPen);
1077
    }
1078
1079
//     DEC_INDENT;
1080
1081
    return;
1082
}
1083
1084
void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
1085
                                               QTextTable *table, QTextTableData *td, int r, int c,
1086
                                               QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
1087
{
1088
    QTextTableCell cell = table->cellAt(r, c);
1089
    int rspan = cell.rowSpan();
1090
    int cspan = cell.columnSpan();
1091
    if (rspan != 1) {
1092
        int cr = cell.row();
1093
        if (cr != r)
1094
            return;
1095
    }
1096
    if (cspan != 1) {
1097
        int cc = cell.column();
1098
        if (cc != c)
1099
            return;
1100
    }
1101
1102
    QTextFormat fmt = cell.format();
1103
    const QFixed leftPadding = td->leftPadding(fmt);
1104
    const QFixed topPadding = td->topPadding(fmt);
1105
1106
    if (td->border != 0) {
1107
        const QBrush oldBrush = painter->brush();
1108
        const QPen oldPen = painter->pen();
1109
1110
        const qreal border = td->border.toReal();
1111
1112
        QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
1113
1114
        // invert the border style for cells
1115
        QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
1116
        switch (cellBorder) {
1117
        case QTextFrameFormat::BorderStyle_Inset:
1118
            cellBorder = QTextFrameFormat::BorderStyle_Outset;
1119
            break;
1120
        case QTextFrameFormat::BorderStyle_Outset:
1121
            cellBorder = QTextFrameFormat::BorderStyle_Inset;
1122
            break;
1123
        case QTextFrameFormat::BorderStyle_Groove:
1124
            cellBorder = QTextFrameFormat::BorderStyle_Ridge;
1125
            break;
1126
        case QTextFrameFormat::BorderStyle_Ridge:
1127
            cellBorder = QTextFrameFormat::BorderStyle_Groove;
1128
            break;
1129
        default:
1130
            break;
1131
        }
1132
1133
        qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1134
        qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1135
1136
        const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1137
        if (r >= headerRowCount)
1138
            topMargin += td->headerHeight.toReal();
1139
1140
        drawBorder(painter, borderRect, topMargin, bottomMargin,
1141
                   border, table->format().borderBrush(), cellBorder);
1142
1143
        painter->setBrush(oldBrush);
1144
        painter->setPen(oldPen);
1145
    }
1146
1147
    const QBrush bg = cell.format().background();
1148
    const QPointF brushOrigin = painter->brushOrigin();
1149
    if (bg.style() != Qt::NoBrush) {
1150
        fillBackground(painter, cellRect, bg, cellRect.topLeft());
1151
1152
        if (bg.style() > Qt::SolidPattern)
1153
            painter->setBrushOrigin(cellRect.topLeft());
1154
    }
1155
1156
    const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
1157
1158
    const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
1159
                                    cellRect.top() + (topPadding + verticalOffset).toReal());
1160
1161
    QTextBlock repaintBlock;
1162
    drawFlow(cellPos, painter, cell_context, cell.begin(),
1163
             td->childFrameMap.values(r + c * table->rows()),
1164
             &repaintBlock);
1165
    if (repaintBlock.isValid()) {
1166
        *cursorBlockNeedingRepaint = repaintBlock;
1167
        *cursorBlockOffset = cellPos;
1168
    }
1169
1170
    if (bg.style() > Qt::SolidPattern)
1171
        painter->setBrushOrigin(brushOrigin);
1172
}
1173
1174
void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
1175
                                          QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
1176
{
1177
    Q_Q(const QTextDocumentLayout);
1178
    const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
1179
1180
    QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
1181
    if (inRootFrame && context.clip.isValid()) {
1182
        lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
1183
    }
1184
1185
    QTextBlock previousBlock;
1186
    QTextFrame *previousFrame = 0;
1187
1188
    for (; !it.atEnd(); ++it) {
1189
        QTextFrame *c = it.currentFrame();
1190
1191
        if (inRootFrame && !checkPoints.isEmpty()) {
1192
            int currentPosInDoc;
1193
            if (c)
1194
                currentPosInDoc = c->firstPosition();
1195
            else
1196
                currentPosInDoc = it.currentBlock().position();
1197
1198
            // if we're past what is already laid out then we're better off
1199
            // not trying to draw things that may not be positioned correctly yet
1200
            if (currentPosInDoc >= checkPoints.last().positionInFrame)
1201
                break;
1202
1203
            if (lastVisibleCheckPoint != checkPoints.end()
1204
                && context.clip.isValid()
1205
                && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
1206
               )
1207
                break;
1208
        }
1209
1210
        if (c)
1211
            drawFrame(offset, painter, context, c);
1212
        else {
1213
            QAbstractTextDocumentLayout::PaintContext pc = context;
1214
            if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
1215
                pc.selections.clear();
1216
            drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
1217
        }
1218
1219
        // when entering a table and the previous block is empty
1220
        // then layoutFlow 'hides' the block that just causes a
1221
        // new line by positioning it /on/ the table border. as we
1222
        // draw that block before the table itself the decoration
1223
        // 'overpaints' the cursor and we need to paint it afterwards
1224
        // again
1225
        if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
1226
            && previousBlock.contains(context.cursorPosition)
1227
           ) {
1228
            *cursorBlockNeedingRepaint = previousBlock;
1229
        }
1230
1231
        previousBlock = it.currentBlock();
1232
        previousFrame = c;
1233
    }
1234
1235
    for (int i = 0; i < floats.count(); ++i) {
1236
        QTextFrame *frame = floats.at(i);
1237
        if (!isFrameFromInlineObject(frame)
1238
            || frame->frameFormat().position() == QTextFrameFormat::InFlow)
1239
            continue;
1240
1241
        const int pos = frame->firstPosition() - 1;
1242
        QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
1243
        QTextObjectInterface *handler = q->handlerForObject(format.objectType());
1244
        if (handler) {
1245
            QRectF rect = frameBoundingRectInternal(frame);
1246
            handler->drawObject(painter, rect, document, pos, format);
1247
        }
1248
    }
1249
}
1250
1251
void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
1252
                                           const QAbstractTextDocumentLayout::PaintContext &context,
1253
                                           QTextBlock bl, bool inRootFrame) const
1254
{
1255
    const QTextLayout *tl = bl.layout();
1256
    QRectF r = tl->boundingRect();
1257
    r.translate(offset + tl->position());
1258
    if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom()))
1259
        return;
1260
//      LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
1261
1262
    QTextBlockFormat blockFormat = bl.blockFormat();
1263
1264
    QBrush bg = blockFormat.background();
1265
    if (bg != Qt::NoBrush) {
1266
        QRectF rect = r;
1267
1268
        // extend the background rectangle if we're in the root frame with NoWrap,
1269
        // as the rect of the text block will then be only the width of the text
1270
        // instead of the full page width
1271
        if (inRootFrame && document->pageSize().width() <= 0) {
1272
            const QTextFrameData *fd = data(document->rootFrame());
1273
            rect.setRight((fd->size.width - fd->rightMargin).toReal());
1274
        }
1275
1276
        fillBackground(painter, rect, bg, r.topLeft());
1277
    }
1278
1279
    QVector<QTextLayout::FormatRange> selections;
1280
    int blpos = bl.position();
1281
    int bllen = bl.length();
1282
    const QTextCharFormat *selFormat = 0;
1283
    for (int i = 0; i < context.selections.size(); ++i) {
1284
        const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
1285
        const int selStart = range.cursor.selectionStart() - blpos;
1286
        const int selEnd = range.cursor.selectionEnd() - blpos;
1287
        if (selStart < bllen && selEnd > 0
1288
             && selEnd > selStart) {
1289
            QTextLayout::FormatRange o;
1290
            o.start = selStart;
1291
            o.length = selEnd - selStart;
1292
            o.format = range.format;
1293
            selections.append(o);
1294
        } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
1295
                   && bl.contains(range.cursor.position())) {
1296
            // for full width selections we don't require an actual selection, just
1297
            // a position to specify the line. that's more convenience in usage.
1298
            QTextLayout::FormatRange o;
1299
            QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
1300
            o.start = l.textStart();
1301
            o.length = l.textLength();
1302
            if (o.start + o.length == bllen - 1)
1303
                ++o.length; // include newline
1304
            o.format = range.format;
1305
            selections.append(o);
1306
       }
1307
        if (selStart < 0 && selEnd >= 1)
1308
            selFormat = &range.format;
1309
    }
1310
1311
    QTextObject *object = document->objectForFormat(bl.blockFormat());
1312
    if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
1313
        drawListItem(offset, painter, context, bl, selFormat);
1314
1315
    QPen oldPen = painter->pen();
1316
    painter->setPen(context.palette.color(QPalette::Text));
1317
1318
    tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
1319
1320
    if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
1321
        || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) {
1322
        int cpos = context.cursorPosition;
1323
        if (cpos < -1)
1324
            cpos = tl->preeditAreaPosition() - (cpos + 2);
1325
        else
1326
            cpos -= blpos;
1327
        tl->drawCursor(painter, offset, cpos, cursorWidth);
1328
    }
1329
1330
    if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1331
        const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
1332
        painter->setPen(context.palette.color(QPalette::Dark));
1333
        qreal y = r.bottom();
1334
        if (bl.length() == 1)
1335
            y = r.top() + r.height() / 2;
1336
1337
        const qreal middleX = r.left() + r.width() / 2;
1338
        painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
1339
    }
1340
1341
    painter->setPen(oldPen);
1342
}
1343
1344
1345
void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
1346
                                              const QAbstractTextDocumentLayout::PaintContext &context,
1347
                                              QTextBlock bl, const QTextCharFormat *selectionFormat) const
1348
{
1349
    Q_Q(const QTextDocumentLayout);
1350
    const QTextBlockFormat blockFormat = bl.blockFormat();
1351
    const QTextCharFormat charFormat = QTextCursor(bl).charFormat();
1352
    QFont font(charFormat.font());
1353
    if (q->paintDevice())
1354
        font = QFont(font, q->paintDevice());
1355
1356
    const QFontMetrics fontMetrics(font);
1357
    QTextObject * const object = document->objectForFormat(blockFormat);
1358
    const QTextListFormat lf = object->format().toListFormat();
1359
    int style = lf.style();
1360
    QString itemText;
1361
    QSizeF size;
1362
1363
    if (blockFormat.hasProperty(QTextFormat::ListStyle))
1364
        style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
1365
1366
    QTextLayout *layout = bl.layout();
1367
    if (layout->lineCount() == 0)
1368
        return;
1369
    QTextLine firstLine = layout->lineAt(0);
1370
    Q_ASSERT(firstLine.isValid());
1371
    QPointF pos = (offset + layout->position()).toPoint();
1372
    Qt::LayoutDirection dir = docPrivate->defaultTextOption.textDirection();
1373
    if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
1374
        dir = blockFormat.layoutDirection();
1375
    {
1376
        QRectF textRect = firstLine.naturalTextRect();
1377
        pos += textRect.topLeft().toPoint();
1378
        if (dir == Qt::RightToLeft)
1379
            pos.rx() += textRect.width();
1380
    }
1381
1382
    switch (style) {
1383
    case QTextListFormat::ListDecimal:
1384
    case QTextListFormat::ListLowerAlpha:
1385
    case QTextListFormat::ListUpperAlpha:
1386
    case QTextListFormat::ListLowerRoman:
1387
    case QTextListFormat::ListUpperRoman:
1388
        itemText = static_cast<QTextList *>(object)->itemText(bl);
1389
        size.setWidth(fontMetrics.width(itemText));
1390
        size.setHeight(fontMetrics.height());
1391
        break;
1392
1393
    case QTextListFormat::ListSquare:
1394
    case QTextListFormat::ListCircle:
1395
    case QTextListFormat::ListDisc:
1396
        size.setWidth(fontMetrics.lineSpacing() / 3);
1397
        size.setHeight(size.width());
1398
        break;
1399
1400
    case QTextListFormat::ListStyleUndefined:
1401
        return;
1402
    default: return;
1403
    }
1404
1405
    QRectF r(pos, size);
1406
1407
    qreal xoff = fontMetrics.width(QLatin1Char(' '));
1408
    if (dir == Qt::LeftToRight)
1409
        xoff = -xoff - size.width();
1410
    r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2));
1411
1412
    painter->save();
1413
1414
    painter->setRenderHint(QPainter::Antialiasing);
1415
1416
    if (selectionFormat) {
1417
        painter->setPen(QPen(selectionFormat->foreground(), 0));
1418
        painter->fillRect(r, selectionFormat->background());
1419
    } else {
1420
        QBrush fg = charFormat.foreground();
1421
        if (fg == Qt::NoBrush)
1422
            fg = context.palette.text();
1423
        painter->setPen(QPen(fg, 0));
1424
    }
1425
1426
    QBrush brush = context.palette.brush(QPalette::Text);
1427
1428
    switch (style) {
1429
    case QTextListFormat::ListDecimal:
1430
    case QTextListFormat::ListLowerAlpha:
1431
    case QTextListFormat::ListUpperAlpha:
1432
    case QTextListFormat::ListLowerRoman:
1433
    case QTextListFormat::ListUpperRoman: {
1434
        QTextLayout layout(itemText, font, q->paintDevice());
1435
        layout.setCacheEnabled(true);
1436
        QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
1437
        option.setTextDirection(dir);
1438
        layout.setTextOption(option);
1439
        layout.beginLayout();
1440
        QTextLine line = layout.createLine();
1441
        if (line.isValid())
1442
            line.setLeadingIncluded(true);
1443
        layout.endLayout();
1444
        layout.draw(painter, QPointF(r.left(), pos.y()));
1445
        break;
1446
    }
1447
    case QTextListFormat::ListSquare:
1448
        painter->fillRect(r, brush);
1449
        break;
1450
    case QTextListFormat::ListCircle:
1451
        painter->setPen(QPen(brush, 0));
1452
        painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
1453
        break;
1454
    case QTextListFormat::ListDisc:
1455
        painter->setBrush(brush);
1456
        painter->setPen(Qt::NoPen);
1457
        painter->drawEllipse(r);
1458
        break;
1459
    case QTextListFormat::ListStyleUndefined:
1460
        break;
1461
    default:
1462
        break;
1463
    }
1464
1465
    painter->restore();
1466
}
1467
1468
static QFixed flowPosition(const QTextFrame::iterator it)
1469
{
1470
    if (it.atEnd())
1471
        return 0;
1472
1473
    if (it.currentFrame()) {
1474
        return data(it.currentFrame())->position.y;
1475
    } else {
1476
        QTextBlock block = it.currentBlock();
1477
        QTextLayout *layout = block.layout();
1478
        if (layout->lineCount() == 0)
1479
            return QFixed::fromReal(layout->position().y());
1480
        else
1481
            return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
1482
    }
1483
}
1484
1485
static QFixed firstChildPos(const QTextFrame *f)
1486
{
1487
    return flowPosition(f->begin());
1488
}
1489
1490
QLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
1491
                                                    int layoutFrom, int layoutTo, QTextTableData *td,
1492
                                                    QFixed absoluteTableY, bool withPageBreaks)
1493
{
1494
    LDEBUG << "layoutCell";
1495
    QLayoutStruct layoutStruct;
1496
    layoutStruct.frame = t;
1497
    layoutStruct.minimumWidth = 0;
1498
    layoutStruct.maximumWidth = QFIXED_MAX;
1499
    layoutStruct.y = 0;
1500
1501
    const QTextFormat fmt = cell.format();
1502
    const QFixed topPadding = td->topPadding(fmt);
1503
    if (withPageBreaks) {
1504
        layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
1505
    }
1506
    layoutStruct.x_left = 0;
1507
    layoutStruct.x_right = width;
1508
    // we get called with different widths all the time (for example for figuring
1509
    // out the min/max widths), so we always have to do the full layout ;(
1510
    // also when for example in a table layoutFrom/layoutTo affect only one cell,
1511
    // making that one cell grow the available width of the other cells may change
1512
    // (shrink) and therefore when layoutCell gets called for them they have to
1513
    // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
1514
    // this line:
1515
1516
    layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
1517
    if (layoutStruct.pageHeight < 0 || !withPageBreaks)
1518
        layoutStruct.pageHeight = QFIXED_MAX;
1519
    const int currentPage = layoutStruct.currentPage();
1520
    layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
1521
    layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
1522
    layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
1523
1524
    layoutStruct.fullLayout = true;
1525
1526
    QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
1527
    layoutStruct.y = qMax(layoutStruct.y, pageTop);
1528
1529
    const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
1530
    for (int i = 0; i < childFrames.size(); ++i) {
1531
        QTextFrame *frame = childFrames.at(i);
1532
        QTextFrameData *cd = data(frame);
1533
        cd->sizeDirty = true;
1534
    }
1535
1536
    layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
1537
1538
    QFixed floatMinWidth;
1539
1540
    // floats that are located inside the text (like inline images) aren't taken into account by
1541
    // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
1542
    // do that here. For example with <td><img align="right" src="..." />blah</td>
1543
    // when the image happens to be higher than the text
1544
    for (int i = 0; i < childFrames.size(); ++i) {
1545
        QTextFrame *frame = childFrames.at(i);
1546
        QTextFrameData *cd = data(frame);
1547
1548
        if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
1549
            layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
1550
1551
        floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
1552
    }
1553
1554
    // constraint the maximumWidth by the minimum width of the fixed size floats, to
1555
    // keep them visible
1556
    layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
1557
1558
    // as floats in cells get added to the table's float list but must not affect
1559
    // floats in other cells we must clear the list here.
1560
    data(t)->floats.clear();
1561
1562
//    qDebug() << "layoutCell done";
1563
1564
    return layoutStruct;
1565
}
1566
1567
QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
1568
{
1569
    LDEBUG << "layoutTable";
1570
    QTextTableData *td = static_cast<QTextTableData *>(data(table));
1571
    Q_ASSERT(td->sizeDirty);
1572
    const int rows = table->rows();
1573
    const int columns = table->columns();
1574
1575
    const QTextTableFormat fmt = table->format();
1576
1577
    td->childFrameMap.clear();
1578
    {
1579
        const QList<QTextFrame *> children = table->childFrames();
1580
        for (int i = 0; i < children.count(); ++i) {
1581
            QTextFrame *frame = children.at(i);
1582
            QTextTableCell cell = table->cellAt(frame->firstPosition());
1583
            td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
1584
        }
1585
    }
1586
1587
    QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
1588
    if (columnWidthConstraints.size() != columns)
1589
        columnWidthConstraints.resize(columns);
1590
    Q_ASSERT(columnWidthConstraints.count() == columns);
1591
1592
    const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
1593
    td->deviceScale = scaleToDevice(qreal(1));
1594
    td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
1595
    const QFixed leftMargin = td->leftMargin + td->border + td->padding;
1596
    const QFixed rightMargin = td->rightMargin + td->border + td->padding;
1597
    const QFixed topMargin = td->topMargin + td->border + td->padding;
1598
1599
    const QFixed absoluteTableY = parentY + td->position.y;
1600
1601
    const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
1602
1603
recalc_minmax_widths:
1604
1605
    QFixed remainingWidth = td->contentsWidth;
1606
    // two (vertical) borders per cell per column
1607
    remainingWidth -= columns * 2 * td->border;
1608
    // inter-cell spacing
1609
    remainingWidth -= (columns - 1) * cellSpacing;
1610
    // cell spacing at the left and right hand side
1611
    remainingWidth -= 2 * cellSpacing;
1612
    // remember the width used to distribute to percentaged columns
1613
    const QFixed initialTotalWidth = remainingWidth;
1614
1615
    td->widths.resize(columns);
1616
    td->widths.fill(0);
1617
1618
    td->minWidths.resize(columns);
1619
    // start with a minimum width of 0. totally empty
1620
    // cells of default created tables are invisible otherwise
1621
    // and therefore hardly editable
1622
    td->minWidths.fill(1);
1623
1624
    td->maxWidths.resize(columns);
1625
    td->maxWidths.fill(QFIXED_MAX);
1626
1627
    // calculate minimum and maximum sizes of the columns
1628
    for (int i = 0; i < columns; ++i) {
1629
        for (int row = 0; row < rows; ++row) {
1630
            const QTextTableCell cell = table->cellAt(row, i);
1631
            const int cspan = cell.columnSpan();
1632
1633
            if (cspan > 1 && i != cell.column())
1634
                continue;
1635
1636
            const QTextFormat fmt = cell.format();
1637
            const QFixed leftPadding = td->leftPadding(fmt);
1638
            const QFixed rightPadding = td->rightPadding(fmt);
1639
            const QFixed widthPadding = leftPadding + rightPadding;
1640
1641
            // to figure out the min and the max width lay out the cell at
1642
            // maximum width. otherwise the maxwidth calculation sometimes
1643
            // returns wrong values
1644
            QLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
1645
                                                    layoutTo, td, absoluteTableY,
1646
                                                    /*withPageBreaks =*/false);
1647
1648
            // distribute the minimum width over all columns the cell spans
1649
            QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
1650
            for (int n = 0; n < cspan; ++n) {
1651
                const int col = i + n;
1652
                QFixed w = widthToDistribute / (cspan - n);
1653
                td->minWidths[col] = qMax(td->minWidths.at(col), w);
1654
                widthToDistribute -= td->minWidths.at(col);
1655
                if (widthToDistribute <= 0)
1656
                    break;
1657
            }
1658
1659
            QFixed maxW = td->maxWidths.at(i);
1660
            if (layoutStruct.maximumWidth != QFIXED_MAX) {
1661
                if (maxW == QFIXED_MAX)
1662
                    maxW = layoutStruct.maximumWidth + widthPadding;
1663
                else
1664
                    maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
1665
            }
1666
            if (maxW == QFIXED_MAX)
1667
                continue;
1668
1669
            widthToDistribute = maxW;
1670
            for (int n = 0; n < cspan; ++n) {
1671
                const int col = i + n;
1672
                QFixed w = widthToDistribute / (cspan - n);
1673
                td->maxWidths[col] = qMax(td->minWidths.at(col), w);
1674
                widthToDistribute -= td->maxWidths.at(col);
1675
                if (widthToDistribute <= 0)
1676
                    break;
1677
            }
1678
        }
1679
    }
1680
1681
    // set fixed values, figure out total percentages used and number of
1682
    // variable length cells. Also assign the minimum width for variable columns.
1683
    QFixed totalPercentage;
1684
    int variableCols = 0;
1685
    QFixed totalMinWidth = 0;
1686
    for (int i = 0; i < columns; ++i) {
1687
        const QTextLength &length = columnWidthConstraints.at(i);
1688
        if (length.type() == QTextLength::FixedLength) {
1689
            td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
1690
            remainingWidth -= td->widths.at(i);
1691
        } else if (length.type() == QTextLength::PercentageLength) {
1692
            totalPercentage += QFixed::fromReal(length.rawValue());
1693
        } else if (length.type() == QTextLength::VariableLength) {
1694
            variableCols++;
1695
1696
            td->widths[i] = td->minWidths.at(i);
1697
            remainingWidth -= td->minWidths.at(i);
1698
        }
1699
        totalMinWidth += td->minWidths.at(i);
1700
    }
1701
1702
    // set percentage values
1703
    {
1704
        const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
1705
        QFixed remainingMinWidths = totalMinWidth;
1706
        for (int i = 0; i < columns; ++i) {
1707
            remainingMinWidths -= td->minWidths.at(i);
1708
            if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
1709
                const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
1710
1711
                const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
1712
                if (percentWidth >= td->minWidths.at(i)) {
1713
                    td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
1714
                } else {
1715
                    td->widths[i] = td->minWidths.at(i);
1716
                }
1717
                remainingWidth -= td->widths.at(i);
1718
            }
1719
        }
1720
    }
1721
1722
    // for variable columns distribute the remaining space
1723
    if (variableCols > 0 && remainingWidth > 0) {
1724
        QVarLengthArray<int> columnsWithProperMaxSize;
1725
        for (int i = 0; i < columns; ++i)
1726
            if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
1727
                && td->maxWidths.at(i) != QFIXED_MAX)
1728
                columnsWithProperMaxSize.append(i);
1729
1730
        QFixed lastRemainingWidth = remainingWidth;
1731
        while (remainingWidth > 0) {
1732
            for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
1733
                const int col = columnsWithProperMaxSize[k];
1734
                const int colsLeft = columnsWithProperMaxSize.count() - k;
1735
                const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
1736
                td->widths[col] += w;
1737
                remainingWidth -= w;
1738
            }
1739
            if (remainingWidth == lastRemainingWidth)
1740
                break;
1741
            lastRemainingWidth = remainingWidth;
1742
        }
1743
1744
        if (remainingWidth > 0
1745
            // don't unnecessarily grow variable length sized tables
1746
            && fmt.width().type() != QTextLength::VariableLength) {
1747
            const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
1748
            for (int col = 0; col < columns; ++col) {
1749
                if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
1750
                    td->widths[col] += widthPerAnySizedCol;
1751
            }
1752
        }
1753
    }
1754
1755
    td->columnPositions.resize(columns);
1756
    td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
1757
1758
    for (int i = 1; i < columns; ++i)
1759
        td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
1760
1761
    // - margin to compensate the + margin in columnPositions[0]
1762
    const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin;
1763
1764
    // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
1765
    // mode
1766
    if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
1767
        && contentsWidth > td->contentsWidth) {
1768
        docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
1769
        // go back to the top of the function
1770
        goto recalc_minmax_widths;
1771
    }
1772
1773
    td->contentsWidth = contentsWidth;
1774
1775
    docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
1776
1777
    td->heights.resize(rows);
1778
    td->heights.fill(0);
1779
1780
    td->rowPositions.resize(rows);
1781
    td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
1782
1783
    bool haveRowSpannedCells = false;
1784
1785
    // need to keep track of cell heights for vertical alignment
1786
    QVector<QFixed> cellHeights;
1787
    cellHeights.reserve(rows * columns);
1788
1789
    QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1790
    if (pageHeight <= 0)
1791
        pageHeight = QFIXED_MAX;
1792
1793
    QVector<QFixed> heightToDistribute;
1794
    heightToDistribute.resize(columns);
1795
1796
    td->headerHeight = 0;
1797
    const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1798
    const QFixed originalTopMargin = td->effectiveTopMargin;
1799
    bool hasDroppedTable = false;
1800
1801
    // now that we have the column widths we can lay out all cells with the right width.
1802
    // spanning cells are only allowed to grow the last row spanned by the cell.
1803
    //
1804
    // ### this could be made faster by iterating over the cells array of QTextTable
1805
    for (int r = 0; r < rows; ++r) {
1806
        td->calcRowPosition(r);
1807
1808
        const int tableStartPage = (absoluteTableY / pageHeight).truncate();
1809
        const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1810
        const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
1811
        const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
1812
        const QFixed nextPageTop = pageTop + pageHeight;
1813
1814
        if (td->rowPositions[r] > pageBottom)
1815
            td->rowPositions[r] = nextPageTop;
1816
        else if (td->rowPositions[r] < pageTop)
1817
            td->rowPositions[r] = pageTop;
1818
1819
        bool dropRowToNextPage = true;
1820
        int cellCountBeforeRow = cellHeights.size();
1821
1822
        // if we drop the row to the next page we need to subtract the drop
1823
        // distance from any row spanning cells
1824
        QFixed dropDistance = 0;
1825
1826
relayout:
1827
        const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1828
        // if any of the header rows or the first non-header row start on the next page
1829
        // then the entire header should be dropped
1830
        if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
1831
            td->rowPositions[0] = nextPageTop;
1832
            cellHeights.clear();
1833
            td->effectiveTopMargin = originalTopMargin;
1834
            hasDroppedTable = true;
1835
            r = -1;
1836
            continue;
1837
        }
1838
1839
        int rowCellCount = 0;
1840
        for (int c = 0; c < columns; ++c) {
1841
            QTextTableCell cell = table->cellAt(r, c);
1842
            const int rspan = cell.rowSpan();
1843
            const int cspan = cell.columnSpan();
1844
1845
            if (cspan > 1 && cell.column() != c)
1846
                continue;
1847
1848
            if (rspan > 1) {
1849
                haveRowSpannedCells = true;
1850
1851
                const int cellRow = cell.row();
1852
                if (cellRow != r) {
1853
                    // the last row gets all the remaining space
1854
                    if (cellRow + rspan - 1 == r)
1855
                        td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
1856
                    continue;
1857
                }
1858
            }
1859
1860
            const QTextFormat fmt = cell.format();
1861
1862
            const QFixed topPadding = td->topPadding(fmt);
1863
            const QFixed bottomPadding = td->bottomPadding(fmt);
1864
            const QFixed leftPadding = td->leftPadding(fmt);
1865
            const QFixed rightPadding = td->rightPadding(fmt);
1866
            const QFixed widthPadding = leftPadding + rightPadding;
1867
1868
            ++rowCellCount;
1869
1870
            const QFixed width = td->cellWidth(c, cspan) - widthPadding;
1871
            QLayoutStruct layoutStruct = layoutCell(table, cell, width,
1872
                                                    layoutFrom, layoutTo,
1873
                                                    td, absoluteTableY,
1874
                                                    /*withPageBreaks =*/true);
1875
1876
            const QFixed height = layoutStruct.y + bottomPadding + topPadding;
1877
1878
            if (rspan > 1)
1879
                heightToDistribute[c] = height + dropDistance;
1880
            else
1881
                td->heights[r] = qMax(td->heights.at(r), height);
1882
1883
            cellHeights.append(layoutStruct.y);
1884
1885
            QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
1886
            if (childPos < pageBottom)
1887
                dropRowToNextPage = false;
1888
        }
1889
1890
        if (rowCellCount > 0 && dropRowToNextPage) {
1891
            dropDistance = nextPageTop - td->rowPositions[r];
1892
            td->rowPositions[r] = nextPageTop;
1893
            td->heights[r] = 0;
1894
            dropRowToNextPage = false;
1895
            cellHeights.resize(cellCountBeforeRow);
1896
            if (r > headerRowCount)
1897
                td->heights[r-1] = pageBottom - td->rowPositions[r-1];
1898
            goto relayout;
1899
        }
1900
1901
        if (haveRowSpannedCells) {
1902
            const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
1903
            for (int c = 0; c < columns; ++c)
1904
                heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
1905
        }
1906
1907
        if (r == headerRowCount - 1) {
1908
            td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border;
1909
            td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
1910
            td->effectiveTopMargin += td->headerHeight;
1911
        }
1912
    }
1913
1914
    td->effectiveTopMargin = originalTopMargin;
1915
1916
    // now that all cells have been properly laid out, we can compute the
1917
    // vertical offsets for vertical alignment
1918
    td->cellVerticalOffsets.resize(rows * columns);
1919
    int cellIndex = 0;
1920
    for (int r = 0; r < rows; ++r) {
1921
        for (int c = 0; c < columns; ++c) {
1922
            QTextTableCell cell = table->cellAt(r, c);
1923
            if (cell.row() != r || cell.column() != c)
1924
                continue;
1925
1926
            const int rowSpan = cell.rowSpan();
1927
            const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
1928
1929
            const QTextCharFormat cellFormat = cell.format();
1930
            const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
1931
1932
            QFixed offset = 0;
1933
            switch (cellFormat.verticalAlignment()) {
1934
            case QTextCharFormat::AlignMiddle:
1935
                offset = (availableHeight - cellHeight) / 2;
1936
                break;
1937
            case QTextCharFormat::AlignBottom:
1938
                offset = availableHeight - cellHeight;
1939
                break;
1940
            default:
1941
                break;
1942
            };
1943
1944
            for (int rd = 0; rd < cell.rowSpan(); ++rd) {
1945
                for (int cd = 0; cd < cell.columnSpan(); ++cd) {
1946
                    const int index = (c + cd) + (r + rd) * columns;
1947
                    td->cellVerticalOffsets[index] = offset;
1948
                }
1949
            }
1950
        }
1951
    }
1952
1953
    td->minimumWidth = td->columnPositions.at(0);
1954
    for (int i = 0; i < columns; ++i) {
1955
        td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
1956
    }
1957
    td->minimumWidth += rightMargin - td->border;
1958
1959
    td->maximumWidth = td->columnPositions.at(0);
1960
    for (int i = 0; i < columns; ++i)
1961
        if (td->maxWidths.at(i) != QFIXED_MAX)
1962
            td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
1963
    td->maximumWidth += rightMargin - td->border;
1964
1965
    td->updateTableSize();
1966
    td->sizeDirty = false;
1967
    return QRectF(); // invalid rect -> update everything
1968
}
1969
1970
void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
1971
{
1972
    QTextFrameData *fd = data(frame);
1973
1974
    QTextFrame *parent = frame->parentFrame();
1975
    Q_ASSERT(parent);
1976
    QTextFrameData *pd = data(parent);
1977
    Q_ASSERT(pd && pd->currentLayoutStruct);
1978
1979
    QLayoutStruct *layoutStruct = pd->currentLayoutStruct;
1980
1981
    if (!pd->floats.contains(frame))
1982
        pd->floats.append(frame);
1983
    fd->layoutDirty = true;
1984
    Q_ASSERT(!fd->sizeDirty);
1985
1986
//     qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
1987
    QFixed y = layoutStruct->y;
1988
    if (currentLine) {
1989
        QFixed left, right;
1990
        floatMargins(y, layoutStruct, &left, &right);
1991
//         qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
1992
        if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
1993
            layoutStruct->pendingFloats.append(frame);
1994
//             qDebug() << "    adding to pending list";
1995
            return;
1996
        }
1997
    }
1998
1999
    bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom);
2000
    if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) {
2001
        layoutStruct->newPage();
2002
        y = layoutStruct->y;
2003
2004
        frameSpansIntoNextPage = false;
2005
    }
2006
2007
    y = findY(y, layoutStruct, fd->size.width);
2008
2009
    QFixed left, right;
2010
    floatMargins(y, layoutStruct, &left, &right);
2011
2012
    if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
2013
        fd->position.x = left;
2014
        fd->position.y = y;
2015
    } else {
2016
        fd->position.x = right - fd->size.width;
2017
        fd->position.y = y;
2018
    }
2019
2020
    layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
2021
    layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
2022
2023
//     qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
2024
    fd->layoutDirty = false;
2025
2026
    // If the frame is a table, then positioning it will affect the size if it covers more than
2027
    // one page, because of page breaks and repeating the header.
2028
    if (qobject_cast<QTextTable *>(frame) != 0)
2029
        fd->sizeDirty = frameSpansIntoNextPage;
2030
}
2031
2032
QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
2033
{
2034
    LDEBUG << "layoutFrame (pre)";
2035
    Q_ASSERT(data(f)->sizeDirty);
2036
//     qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2037
2038
    QTextFrameFormat fformat = f->frameFormat();
2039
2040
    QTextFrame *parent = f->parentFrame();
2041
    const QTextFrameData *pd = parent ? data(parent) : 0;
2042
2043
    const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
2044
    QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
2045
    if (fformat.width().type() == QTextLength::FixedLength)
2046
        width = scaleToDevice(width);
2047
2048
    const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
2049
    const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
2050
                            ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
2051
                            : -1;
2052
2053
    return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
2054
}
2055
2056
QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
2057
{
2058
    LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo;
2059
    Q_ASSERT(data(f)->sizeDirty);
2060
//     qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2061
2062
    QTextFrameData *fd = data(f);
2063
    QFixed newContentsWidth;
2064
2065
    {
2066
        QTextFrameFormat fformat = f->frameFormat();
2067
        // set sizes of this frame from the format
2068
        fd->topMargin = QFixed::fromReal(fformat.topMargin());
2069
        fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin());
2070
        fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
2071
        fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
2072
        fd->border = QFixed::fromReal(fformat.border());
2073
        fd->padding = QFixed::fromReal(fformat.padding());
2074
2075
        QTextFrame *parent = f->parentFrame();
2076
        const QTextFrameData *pd = parent ? data(parent) : 0;
2077
2078
        // accumulate top and bottom margins
2079
        if (parent) {
2080
            fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
2081
            fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
2082
2083
            if (qobject_cast<QTextTable *>(parent)) {
2084
                const QTextTableData *td = static_cast<const QTextTableData *>(pd);
2085
                fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
2086
                fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
2087
            }
2088
        } else {
2089
            fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
2090
            fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
2091
        }
2092
2093
        newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
2094
                           - fd->leftMargin - fd->rightMargin;
2095
2096
        if (frameHeight != -1) {
2097
            fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
2098
                                 - fd->topMargin - fd->bottomMargin;
2099
        } else {
2100
            fd->contentsHeight = frameHeight;
2101
        }
2102
    }
2103
2104
    if (isFrameFromInlineObject(f)) {
2105
        // never reached, handled in resizeInlineObject/positionFloat instead
2106
        return QRectF();
2107
    }
2108
2109
    if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2110
        fd->contentsWidth = newContentsWidth;
2111
        return layoutTable(table, layoutFrom, layoutTo, parentY);
2112
    }
2113
2114
    // set fd->contentsWidth temporarily, so that layoutFrame for the children
2115
    // picks the right width. We'll initialize it properly at the end of this
2116
    // function.
2117
    fd->contentsWidth = newContentsWidth;
2118
2119
    QLayoutStruct layoutStruct;
2120
    layoutStruct.frame = f;
2121
    layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
2122
    layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
2123
    layoutStruct.y = fd->topMargin + fd->border + fd->padding;
2124
    layoutStruct.frameY = parentY + fd->position.y;
2125
    layoutStruct.contentsWidth = 0;
2126
    layoutStruct.minimumWidth = 0;
2127
    layoutStruct.maximumWidth = QFIXED_MAX;
2128
    layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth;
2129
    layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2130
    LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
2131
           << "fullLayout" << layoutStruct.fullLayout;
2132
    fd->oldContentsWidth = newContentsWidth;
2133
2134
    layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
2135
    if (layoutStruct.pageHeight < 0)
2136
        layoutStruct.pageHeight = QFIXED_MAX;
2137
2138
    const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
2139
    layoutStruct.pageTopMargin = fd->effectiveTopMargin;
2140
    layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
2141
    layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
2142
2143
    if (!f->parentFrame())
2144
        idealWidth = 0; // reset
2145
2146
    QTextFrame::Iterator it = f->begin();
2147
    layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
2148
2149
    QFixed maxChildFrameWidth = 0;
2150
    QList<QTextFrame *> children = f->childFrames();
2151
    for (int i = 0; i < children.size(); ++i) {
2152
        QTextFrame *c = children.at(i);
2153
        QTextFrameData *cd = data(c);
2154
        maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
2155
    }
2156
2157
    const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
2158
    if (!f->parentFrame()) {
2159
        idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
2160
        idealWidth += marginWidth.toReal();
2161
    }
2162
2163
    QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
2164
    fd->contentsWidth = actualWidth;
2165
    if (newContentsWidth <= 0) { // nowrap layout?
2166
        fd->contentsWidth = newContentsWidth;
2167
    }
2168
2169
    fd->minimumWidth = layoutStruct.minimumWidth;
2170
    fd->maximumWidth = layoutStruct.maximumWidth;
2171
2172
    fd->size.height = fd->contentsHeight == -1
2173
                 ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
2174
                 : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
2175
    fd->size.width = actualWidth + marginWidth;
2176
    fd->sizeDirty = false;
2177
    if (layoutStruct.updateRectForFloats.isValid())
2178
        layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
2179
    return layoutStruct.updateRect;
2180
}
2181
2182
void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QLayoutStruct *layoutStruct,
2183
                                            int layoutFrom, int layoutTo, QFixed width)
2184
{
2185
    LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
2186
    QTextFrameData *fd = data(layoutStruct->frame);
2187
2188
    fd->currentLayoutStruct = layoutStruct;
2189
2190
    QTextFrame::Iterator previousIt;
2191
2192
    const bool inRootFrame = (it.parentFrame() == document->rootFrame());
2193
    if (inRootFrame) {
2194
        bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
2195
2196
        if (!redoCheckPoints) {
2197
            QVector<QCheckPoint>::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom);
2198
            if (checkPoint != checkPoints.end()) {
2199
                if (checkPoint != checkPoints.begin())
2200
                    --checkPoint;
2201
2202
                layoutStruct->y = checkPoint->y;
2203
                layoutStruct->frameY = checkPoint->frameY;
2204
                layoutStruct->minimumWidth = checkPoint->minimumWidth;
2205
                layoutStruct->maximumWidth = checkPoint->maximumWidth;
2206
                layoutStruct->contentsWidth = checkPoint->contentsWidth;
2207
2208
                if (layoutStruct->pageHeight > 0) {
2209
                    int page = layoutStruct->currentPage();
2210
                    layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2211
                }
2212
2213
                it = frameIteratorForTextPosition(checkPoint->positionInFrame);
2214
                checkPoints.resize(checkPoint - checkPoints.begin() + 1);
2215
2216
                if (checkPoint != checkPoints.begin()) {
2217
                    previousIt = it;
2218
                    --previousIt;
2219
                }
2220
            } else {
2221
                redoCheckPoints = true;
2222
            }
2223
        }
2224
2225
        if (redoCheckPoints) {
2226
            checkPoints.clear();
2227
            QCheckPoint cp;
2228
            cp.y = layoutStruct->y;
2229
            cp.frameY = layoutStruct->frameY;
2230
            cp.positionInFrame = 0;
2231
            cp.minimumWidth = layoutStruct->minimumWidth;
2232
            cp.maximumWidth = layoutStruct->maximumWidth;
2233
            cp.contentsWidth = layoutStruct->contentsWidth;
2234
            checkPoints.append(cp);
2235
        }
2236
    }
2237
2238
    QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
2239
2240
    QFixed maximumBlockWidth = 0;
2241
    while (!it.atEnd()) {
2242
        QTextFrame *c = it.currentFrame();
2243
2244
        int docPos;
2245
        if (it.currentFrame())
2246
            docPos = it.currentFrame()->firstPosition();
2247
        else
2248
            docPos = it.currentBlock().position();
2249
2250
        if (inRootFrame) {
2251
            if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) {
2252
                QFixed left, right;
2253
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2254
                if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
2255
                    QCheckPoint p;
2256
                    p.y = layoutStruct->y;
2257
                    p.frameY = layoutStruct->frameY;
2258
                    p.positionInFrame = docPos;
2259
                    p.minimumWidth = layoutStruct->minimumWidth;
2260
                    p.maximumWidth = layoutStruct->maximumWidth;
2261
                    p.contentsWidth = layoutStruct->contentsWidth;
2262
                    checkPoints.append(p);
2263
2264
                    if (currentLazyLayoutPosition != -1
2265
                        && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
2266
                        break;
2267
2268
                }
2269
            }
2270
        }
2271
2272
        if (c) {
2273
            // position child frame
2274
            QTextFrameData *cd = data(c);
2275
2276
            QTextFrameFormat fformat = c->frameFormat();
2277
2278
            if (fformat.position() == QTextFrameFormat::InFlow) {
2279
                if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2280
                    layoutStruct->newPage();
2281
2282
                QFixed left, right;
2283
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2284
                left = qMax(left, layoutStruct->x_left);
2285
                right = qMin(right, layoutStruct->x_right);
2286
2287
                if (right - left < cd->size.width) {
2288
                    layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
2289
                    floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2290
                }
2291
2292
                QFixedPoint pos(left, layoutStruct->y);
2293
2294
                Qt::Alignment align = Qt::AlignLeft;
2295
2296
                QTextTable *table = qobject_cast<QTextTable *>(c);
2297
2298
                if (table)
2299
                    align = table->format().alignment() & Qt::AlignHorizontal_Mask;
2300
2301
                // detect whether we have any alignment in the document that disallows optimizations,
2302
                // such as not laying out the document again in a textedit with wrapping disabled.
2303
                if (inRootFrame && !(align & Qt::AlignLeft))
2304
                    contentHasAlignment = true;
2305
2306
                cd->position = pos;
2307
2308
                if (document->pageSize().height() > 0.0f)
2309
                    cd->sizeDirty = true;
2310
2311
                if (cd->sizeDirty) {
2312
                    if (width != 0)
2313
                        layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2314
                    else
2315
                        layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2316
2317
                    QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
2318
                    absoluteChildPos += layoutStruct->frameY;
2319
2320
                    // drop entire frame to next page if first child of frame is on next page
2321
                    if (absoluteChildPos > layoutStruct->pageBottom) {
2322
                        layoutStruct->newPage();
2323
                        pos.y = layoutStruct->y;
2324
2325
                        cd->position = pos;
2326
                        cd->sizeDirty = true;
2327
2328
                        if (width != 0)
2329
                            layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2330
                        else
2331
                            layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2332
                    }
2333
                }
2334
2335
                // align only if there is space for alignment
2336
                if (right - left > cd->size.width) {
2337
                    if (align & Qt::AlignRight)
2338
                        pos.x += layoutStruct->x_right - cd->size.width;
2339
                    else if (align & Qt::AlignHCenter)
2340
                        pos.x += (layoutStruct->x_right - cd->size.width) / 2;
2341
                }
2342
2343
                cd->position = pos;
2344
2345
                layoutStruct->y += cd->size.height;
2346
                const int page = layoutStruct->currentPage();
2347
                layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2348
2349
                cd->layoutDirty = false;
2350
2351
                if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2352
                    layoutStruct->newPage();
2353
            } else {
2354
                QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
2355
                QRectF updateRect;
2356
2357
                if (cd->sizeDirty)
2358
                    updateRect = layoutFrame(c, layoutFrom, layoutTo);
2359
2360
                positionFloat(c);
2361
2362
                // If the size was made dirty when the position was set, layout again
2363
                if (cd->sizeDirty)
2364
                    updateRect = layoutFrame(c, layoutFrom, layoutTo);
2365
2366
                QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
2367
2368
                if (frameRect == oldFrameRect && updateRect.isValid())
2369
                    updateRect.translate(cd->position.toPointF());
2370
                else
2371
                    updateRect = frameRect;
2372
2373
                layoutStruct->addUpdateRectForFloat(updateRect);
2374
                if (oldFrameRect.isValid())
2375
                    layoutStruct->addUpdateRectForFloat(oldFrameRect);
2376
            }
2377
2378
            layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
2379
            layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
2380
2381
            previousIt = it;
2382
            ++it;
2383
        } else {
2384
            QTextFrame::Iterator lastIt;
2385
            if (!previousIt.atEnd())
2386
                lastIt = previousIt;
2387
            previousIt = it;
2388
            QTextBlock block = it.currentBlock();
2389
            ++it;
2390
2391
            const QTextBlockFormat blockFormat = block.blockFormat();
2392
2393
            if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2394
                layoutStruct->newPage();
2395
2396
            const QFixed origY = layoutStruct->y;
2397
            const QFixed origPageBottom = layoutStruct->pageBottom;
2398
            const QFixed origMaximumWidth = layoutStruct->maximumWidth;
2399
            layoutStruct->maximumWidth = 0;
2400
2401
            const QTextBlockFormat *previousBlockFormatPtr = 0;
2402
            if (lastIt.currentBlock().isValid())
2403
                previousBlockFormatPtr = &previousBlockFormat;
2404
2405
            // layout and position child block
2406
            layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2407
2408
            // detect whether we have any alignment in the document that disallows optimizations,
2409
            // such as not laying out the document again in a textedit with wrapping disabled.
2410
            if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
2411
                contentHasAlignment = true;
2412
2413
            // if the block right before a table is empty 'hide' it by
2414
            // positioning it into the table border
2415
            if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
2416
                const QTextBlock lastBlock = lastIt.currentBlock();
2417
                const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
2418
                layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
2419
                layoutStruct->pageBottom = origPageBottom;
2420
            } else {
2421
                // if the block right after a table is empty then 'hide' it, too
2422
                if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
2423
                    QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2424
                    QTextLayout *layout = block.layout();
2425
2426
                    QPointF pos((td->position.x + td->size.width).toReal(),
2427
                                (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
2428
2429
                    layout->setPosition(pos);
2430
                    layoutStruct->y = origY;
2431
                    layoutStruct->pageBottom = origPageBottom;
2432
                }
2433
2434
                // if the block right after a table starts with a line separator, shift it up by one line
2435
                if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
2436
                    QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2437
                    QTextLayout *layout = block.layout();
2438
2439
                    QFixed height = QFixed::fromReal(layout->lineAt(0).height());
2440
2441
                    if (layoutStruct->pageBottom == origPageBottom) {
2442
                        layoutStruct->y -= height;
2443
                        layout->setPosition(layout->position() - QPointF(0, height.toReal()));
2444
                    } else {
2445
                        // relayout block to correctly handle page breaks
2446
                        layoutStruct->y = origY - height;
2447
                        layoutStruct->pageBottom = origPageBottom;
2448
                        layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2449
                    }
2450
2451
                    QPointF linePos((td->position.x + td->size.width).toReal(),
2452
                                    (td->position.y + td->size.height - height).toReal());
2453
2454
                    layout->lineAt(0).setPosition(linePos - layout->position());
2455
                }
2456
2457
                if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2458
                    layoutStruct->newPage();
2459
            }
2460
2461
            maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
2462
            layoutStruct->maximumWidth = origMaximumWidth;
2463
            previousBlockFormat = blockFormat;
2464
        }
2465
    }
2466
    if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
2467
        layoutStruct->maximumWidth = maximumBlockWidth;
2468
    else
2469
        layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
2470
2471
    // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
2472
    // we don't need to do it for tables though because floats in tables are per table
2473
    // and not per cell and layoutCell already takes care of doing the same as we do here
2474
    if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
2475
        QList<QTextFrame *> children = layoutStruct->frame->childFrames();
2476
        for (int i = 0; i < children.count(); ++i) {
2477
            QTextFrameData *fd = data(children.at(i));
2478
            if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
2479
                layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
2480
        }
2481
    }
2482
2483
    if (inRootFrame) {
2484
        // we assume that any float is aligned in a way that disallows the optimizations that rely
2485
        // on unaligned content.
2486
        if (!fd->floats.isEmpty())
2487
            contentHasAlignment = true;
2488
2489
        if (it.atEnd()) {
2490
            //qDebug() << "layout done!";
2491
            currentLazyLayoutPosition = -1;
2492
            QCheckPoint cp;
2493
            cp.y = layoutStruct->y;
2494
            cp.positionInFrame = docPrivate->length();
2495
            cp.minimumWidth = layoutStruct->minimumWidth;
2496
            cp.maximumWidth = layoutStruct->maximumWidth;
2497
            cp.contentsWidth = layoutStruct->contentsWidth;
2498
            checkPoints.append(cp);
2499
            checkPoints.reserve(checkPoints.size());
2500
        } else {
2501
            currentLazyLayoutPosition = checkPoints.last().positionInFrame;
2502
            // #######
2503
            //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
2504
        }
2505
    }
2506
2507
2508
    fd->currentLayoutStruct = 0;
2509
}
2510
2511
void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
2512
                                             QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
2513
{
2514
    Q_Q(QTextDocumentLayout);
2515
2516
    QTextLayout *tl = bl.layout();
2517
    const int blockLength = bl.length();
2518
2519
    LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo;
2520
2521
//    qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')';
2522
2523
    if (previousBlockFormat) {
2524
        qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
2525
        if (margin > 0 && q->paintDevice()) {
2526
            margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
2527
        }
2528
        layoutStruct->y += QFixed::fromReal(margin);
2529
    }
2530
2531
    //QTextFrameData *fd = data(layoutStruct->frame);
2532
2533
    Qt::LayoutDirection dir = docPrivate->defaultTextOption.textDirection();
2534
    if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
2535
        dir = blockFormat.layoutDirection();
2536
2537
    QFixed extraMargin;
2538
    if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
2539
        QFontMetricsF fm(bl.charFormat().font());
2540
        extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5))));
2541
    }
2542
2543
    const QFixed indent = this->blockIndent(blockFormat);
2544
    const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
2545
    const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
2546
2547
    const QPointF oldPosition = tl->position();
2548
    tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
2549
2550
    if (layoutStruct->fullLayout
2551
        || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
2552
        // force relayout if we cross a page boundary
2553
        || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
2554
2555
        LDEBUG << " do layout";
2556
        QTextOption option = docPrivate->defaultTextOption;
2557
        option.setTextDirection(dir);
2558
        option.setTabs( blockFormat.tabPositions() );
2559
2560
        Qt::Alignment align = docPrivate->defaultTextOption.alignment();
2561
        if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
2562
            align = blockFormat.alignment();
2563
        option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
2564
2565
        if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
2566
            option.setWrapMode(QTextOption::ManualWrap);
2567
        }
2568
2569
        tl->setTextOption(option);
2570
2571
        const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
2572
2573
//         qDebug() << "    layouting block at" << bl.position();
2574
        const QFixed cy = layoutStruct->y;
2575
        const QFixed l = layoutStruct->x_left  + totalLeftMargin;
2576
        const QFixed r = layoutStruct->x_right - totalRightMargin;
2577
2578
        tl->beginLayout();
2579
        bool firstLine = true;
2580
        while (1) {
2581
            QTextLine line = tl->createLine();
2582
            if (!line.isValid())
2583
                break;
2584
            line.setLeadingIncluded(true);
2585
2586
            QFixed left, right;
2587
            floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2588
            left = qMax(left, l);
2589
            right = qMin(right, r);
2590
            QFixed text_indent;
2591
            if (firstLine) {
2592
                text_indent = QFixed::fromReal(blockFormat.textIndent());
2593
                if (dir == Qt::LeftToRight)
2594
                    left += text_indent;
2595
                else
2596
                    right -= text_indent;
2597
                firstLine = false;
2598
            }
2599
//         qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
2600
2601
            if (fixedColumnWidth != -1)
2602
                line.setNumColumns(fixedColumnWidth, (right - left).toReal());
2603
            else
2604
                line.setLineWidth((right - left).toReal());
2605
2606
//        qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
2607
            floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2608
            left = qMax(left, l);
2609
            right = qMin(right, r);
2610
            if (dir == Qt::LeftToRight)
2611
                left += text_indent;
2612
            else
2613
                right -= text_indent;
2614
2615
            if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2616
                // float has been added in the meantime, redo
2617
                layoutStruct->pendingFloats.clear();
2618
2619
                line.setLineWidth((right-left).toReal());
2620
                if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2621
                    if (haveWordOrAnyWrapMode) {
2622
                        option.setWrapMode(QTextOption::WrapAnywhere);
2623
                        tl->setTextOption(option);
2624
                    }
2625
2626
                    layoutStruct->pendingFloats.clear();
2627
                    // lines min width more than what we have
2628
                    layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
2629
                    floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2630
                    left = qMax(left, l);
2631
                    right = qMin(right, r);
2632
                    if (dir == Qt::LeftToRight)
2633
                        left += text_indent;
2634
                    else
2635
                        right -= text_indent;
2636
                    line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
2637
2638
                    if (haveWordOrAnyWrapMode) {
2639
                        option.setWrapMode(QTextOption::WordWrap);
2640
                        tl->setTextOption(option);
2641
                    }
2642
                }
2643
2644
            }
2645
2646
            QFixed lineHeight = QFixed::fromReal(line.height());
2647
            if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom) {
2648
                layoutStruct->newPage();
2649
2650
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2651
                left = qMax(left, l);
2652
                right = qMin(right, r);
2653
                if (dir == Qt::LeftToRight)
2654
                    left += text_indent;
2655
                else
2656
                    right -= text_indent;
2657
            }
2658
2659
            line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy).toReal()));
2660
            layoutStruct->y += lineHeight;
2661
            layoutStruct->contentsWidth
2662
                = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
2663
2664
            // position floats
2665
            for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
2666
                QTextFrame *f = layoutStruct->pendingFloats.at(i);
2667
                positionFloat(f);
2668
            }
2669
            layoutStruct->pendingFloats.clear();
2670
        }
2671
        tl->endLayout();
2672
    } else {
2673
        const int cnt = tl->lineCount();
2674
        for (int i = 0; i < cnt; ++i) {
2675
            LDEBUG << "going to move text line" << i;
2676
            QTextLine line = tl->lineAt(i);
2677
            layoutStruct->contentsWidth
2678
                = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
2679
            const QFixed lineHeight = QFixed::fromReal(line.height());
2680
            if (layoutStruct->pageHeight != QFIXED_MAX) {
2681
                if (layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom)
2682
                    layoutStruct->newPage();
2683
                line.setPosition(QPointF(line.position().x(), layoutStruct->y.toReal() - tl->position().y()));
2684
            }
2685
            layoutStruct->y += lineHeight;
2686
        }
2687
        if (layoutStruct->updateRect.isValid()
2688
            && blockLength > 1) {
2689
            if (layoutFrom >= blockPosition + blockLength) {
2690
                // if our height didn't change and the change in the document is
2691
                // in one of the later paragraphs, then we don't need to repaint
2692
                // this one
2693
                layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
2694
            } else if (layoutTo < blockPosition) {
2695
                if (oldPosition == tl->position())
2696
                    // if the change in the document happened earlier in the document
2697
                    // and our position did /not/ change because none of the earlier paragraphs
2698
                    // or frames changed their height, then we don't need to repaint
2699
                    // this one
2700
                    layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
2701
                else
2702
                    layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
2703
            }
2704
        }
2705
    }
2706
2707
    // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
2708
    const QFixed margins = totalLeftMargin + totalRightMargin;
2709
    layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
2710
2711
    const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
2712
2713
    if (maxW > 0) {
2714
        if (layoutStruct->maximumWidth == QFIXED_MAX)
2715
            layoutStruct->maximumWidth = maxW;
2716
        else
2717
            layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
2718
    }
2719
}
2720
2721
void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QLayoutStruct *layoutStruct,
2722
                                              QFixed *left, QFixed *right) const
2723
{
2724
//     qDebug() << "floatMargins y=" << y;
2725
    *left = layoutStruct->x_left;
2726
    *right = layoutStruct->x_right;
2727
    QTextFrameData *lfd = data(layoutStruct->frame);
2728
    for (int i = 0; i < lfd->floats.size(); ++i) {
2729
        QTextFrameData *fd = data(lfd->floats.at(i));
2730
        if (!fd->layoutDirty) {
2731
            if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
2732
//                 qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
2733
                if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
2734
                    *left = qMax(*left, fd->position.x + fd->size.width);
2735
                else
2736
                    *right = qMin(*right, fd->position.x);
2737
            }
2738
        }
2739
    }
2740
//     qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
2741
}
2742
2743
QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const
2744
{
2745
    QFixed right, left;
2746
    requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
2747
2748
//     qDebug() << "findY:" << yFrom;
2749
    while (1) {
2750
        floatMargins(yFrom, layoutStruct, &left, &right);
2751
//         qDebug() << "    yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
2752
        if (right-left >= requiredWidth)
2753
            break;
2754
2755
        // move float down until we find enough space
2756
        QFixed newY = QFIXED_MAX;
2757
        QTextFrameData *lfd = data(layoutStruct->frame);
2758
        for (int i = 0; i < lfd->floats.size(); ++i) {
2759
            QTextFrameData *fd = data(lfd->floats.at(i));
2760
            if (!fd->layoutDirty) {
2761
                if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
2762
                    newY = qMin(newY, fd->position.y + fd->size.height);
2763
            }
2764
        }
2765
        if (newY == QFIXED_MAX)
2766
            break;
2767
        yFrom = newY;
2768
    }
2769
    return yFrom;
2770
}
2771
2772
QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
2773
    : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
2774
{
2775
    registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
2776
}
2777
2778
2779
void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
2780
{
2781
    Q_D(QTextDocumentLayout);
2782
    QTextFrame *frame = d->document->rootFrame();
2783
    QTextFrameData *fd = data(frame);
2784
2785
    if(fd->sizeDirty)
2786
        return;
2787
2788
    if (context.clip.isValid()) {
2789
        d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
2790
    } else {
2791
        d->ensureLayoutFinished();
2792
    }
2793
2794
    QFixed width = fd->size.width;
2795
    if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
2796
        // we're in NoWrap mode, meaning the frame should expand to the viewport
2797
        // so that backgrounds are drawn correctly
2798
        fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
2799
    }
2800
2801
    // Make sure we conform to the root frames bounds when drawing.
2802
    d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
2803
    d->drawFrame(QPointF(), painter, context, frame);
2804
    fd->size.width = width;
2805
}
2806
2807
void QTextDocumentLayout::setViewport(const QRectF &viewport)
2808
{
2809
    Q_D(QTextDocumentLayout);
2810
    d->viewportRect = viewport;
2811
}
2812
2813
static void markFrames(QTextFrame *current, int from, int oldLength, int length)
2814
{
2815
    int end = qMax(oldLength, length) + from;
2816
2817
    if (current->firstPosition() >= end || current->lastPosition() < from)
2818
        return;
2819
2820
    QTextFrameData *fd = data(current);
2821
    for (int i = 0; i < fd->floats.size(); ++i) {
2822
        QTextFrame *f = fd->floats[i];
2823
        if (!f) {
2824
            // float got removed in editing operation
2825
            fd->floats.removeAt(i);
2826
            --i;
2827
        }
2828
    }
2829
2830
    fd->layoutDirty = true;
2831
    fd->sizeDirty = true;
2832
2833
//     qDebug("    marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
2834
    QList<QTextFrame *> children = current->childFrames();
2835
    for (int i = 0; i < children.size(); ++i)
2836
        markFrames(children.at(i), from, oldLength, length);
2837
}
2838
2839
void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
2840
{
2841
    Q_D(QTextDocumentLayout);
2842
2843
    QTextBlock blockIt = document()->findBlock(from);
2844
    QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
2845
    if (endIt.isValid())
2846
        endIt = endIt.next();
2847
     for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
2848
         blockIt.clearLayout();
2849
2850
    if (d->docPrivate->pageSize.isNull())
2851
        return;
2852
2853
    QRectF updateRect;
2854
2855
    d->lazyLayoutStepSize = 1000;
2856
    d->sizeChangedTimer.stop();
2857
    d->insideDocumentChange = true;
2858
2859
    const int documentLength = d->docPrivate->length();
2860
    const bool fullLayout = (oldLength == 0 && length == documentLength);
2861
    const bool smallChange = documentLength > 0
2862
                             && (qMax(length, oldLength) * 100 / documentLength) < 5;
2863
2864
    // don't show incremental layout progress (avoid scroll bar flicker)
2865
    // if we see only a small change in the document and we're either starting
2866
    // a layout run or we're already in progress for that and we haven't seen
2867
    // any bigger change previously (showLayoutProgress already false)
2868
    if (smallChange
2869
        && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
2870
        d->showLayoutProgress = false;
2871
    else
2872
        d->showLayoutProgress = true;
2873
2874
    if (fullLayout) {
2875
        d->contentHasAlignment = false;
2876
        d->currentLazyLayoutPosition = 0;
2877
        d->checkPoints.clear();
2878
        d->layoutStep();
2879
    } else {
2880
        d->ensureLayoutedByPosition(from);
2881
        updateRect = doLayout(from, oldLength, length);
2882
    }
2883
2884
    if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
2885
        d->layoutTimer.start(10, this);
2886
2887
    d->insideDocumentChange = false;
2888
2889
    if (d->showLayoutProgress) {
2890
        const QSizeF newSize = dynamicDocumentSize();
2891
        if (newSize != d->lastReportedSize) {
2892
            d->lastReportedSize = newSize;
2893
            emit documentSizeChanged(newSize);
2894
        }
2895
    }
2896
2897
    if (!updateRect.isValid()) {
2898
        // don't use the frame size, it might have shrunken
2899
        updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2900
    }
2901
2902
    emit update(updateRect);
2903
}
2904
2905
QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
2906
{
2907
    Q_D(QTextDocumentLayout);
2908
2909
//     qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
2910
2911
    // mark all frames between f_start and f_end as dirty
2912
    markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
2913
2914
    QRectF updateRect;
2915
2916
    QTextFrame *root = d->docPrivate->rootFrame();
2917
    if(data(root)->sizeDirty)
2918
        updateRect = d->layoutFrame(root, from, from + length);
2919
    data(root)->layoutDirty = false;
2920
2921
    if (d->currentLazyLayoutPosition == -1)
2922
        layoutFinished();
2923
    else if (d->showLayoutProgress)
2924
        d->sizeChangedTimer.start(0, this);
2925
2926
    return updateRect;
2927
}
2928
2929
int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
2930
{
2931
    Q_D(const QTextDocumentLayout);
2932
    d->ensureLayouted(QFixed::fromReal(point.y()));
2933
    QTextFrame *f = d->docPrivate->rootFrame();
2934
    int position = 0;
2935
    QTextLayout *l = 0;
2936
    QFixedPoint pointf;
2937
    pointf.x = QFixed::fromReal(point.x());
2938
    pointf.y = QFixed::fromReal(point.y());
2939
    QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
2940
    if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
2941
        return -1;
2942
2943
    // ensure we stay within document bounds
2944
    int lastPos = f->lastPosition();
2945
    if (l && !l->preeditAreaText().isEmpty())
2946
        lastPos += l->preeditAreaText().length();
2947
    if (position > lastPos)
2948
        position = lastPos;
2949
    else if (position < 0)
2950
        position = 0;
2951
2952
    return position;
2953
}
2954
2955
void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2956
{
2957
    Q_D(QTextDocumentLayout);
2958
    QTextCharFormat f = format.toCharFormat();
2959
    Q_ASSERT(f.isValid());
2960
    QTextObjectHandler handler = d->handlers.value(f.objectType());
2961
    if (!handler.component)
2962
        return;
2963
2964
    QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
2965
2966
    QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
2967
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2968
    if (frame) {
2969
        pos = frame->frameFormat().position();
2970
        QTextFrameData *fd = data(frame);
2971
        fd->sizeDirty = false;
2972
        fd->size = QFixedSize::fromSizeF(intrinsic);
2973
        fd->minimumWidth = fd->maximumWidth = fd->size.width;
2974
    }
2975
2976
    QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
2977
    item.setWidth(inlineSize.width());
2978
    if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) {
2979
        item.setDescent(inlineSize.height() / 2);
2980
        item.setAscent(inlineSize.height() / 2 - 1);
2981
    } else {
2982
        item.setDescent(0);
2983
        item.setAscent(inlineSize.height() - 1);
2984
    }
2985
}
2986
2987
void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2988
{
2989
    Q_D(QTextDocumentLayout);
2990
    Q_UNUSED(posInDocument);
2991
    if (item.width() != 0)
2992
        // inline
2993
        return;
2994
2995
    QTextCharFormat f = format.toCharFormat();
2996
    Q_ASSERT(f.isValid());
2997
    QTextObjectHandler handler = d->handlers.value(f.objectType());
2998
    if (!handler.component)
2999
        return;
3000
3001
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3002
    if (!frame)
3003
        return;
3004
3005
    QTextBlock b = d->document->findBlock(frame->firstPosition());
3006
    QTextLine line;
3007
    if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
3008
        line = b.layout()->lineAt(b.layout()->lineCount()-1);
3009
//     qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
3010
//         frame->firstPosition() << frame->lastPosition();
3011
    d->positionFloat(frame, line.isValid() ? &line : 0);
3012
}
3013
3014
void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
3015
                                           int posInDocument, const QTextFormat &format)
3016
{
3017
    Q_D(QTextDocumentLayout);
3018
    QTextCharFormat f = format.toCharFormat();
3019
    Q_ASSERT(f.isValid());
3020
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3021
    if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
3022
        return; // don't draw floating frames from inline objects here but in drawFlow instead
3023
3024
//    qDebug() << "drawObject at" << r;
3025
    QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
3026
}
3027
3028
int QTextDocumentLayout::dynamicPageCount() const
3029
{
3030
    Q_D(const QTextDocumentLayout);
3031
    const QSizeF pgSize = d->document->pageSize();
3032
    if (pgSize.height() < 0)
3033
        return 1;
3034
    return qCeil(dynamicDocumentSize().height() / pgSize.height());
3035
}
3036
3037
QSizeF QTextDocumentLayout::dynamicDocumentSize() const
3038
{
3039
    Q_D(const QTextDocumentLayout);
3040
    return data(d->docPrivate->rootFrame())->size.toSizeF();
3041
}
3042
3043
int QTextDocumentLayout::pageCount() const
3044
{
3045
    Q_D(const QTextDocumentLayout);
3046
    d->ensureLayoutFinished();
3047
    return dynamicPageCount();
3048
}
3049
3050
QSizeF QTextDocumentLayout::documentSize() const
3051
{
3052
    Q_D(const QTextDocumentLayout);
3053
    d->ensureLayoutFinished();
3054
    return dynamicDocumentSize();
3055
}
3056
3057
void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
3058
{
3059
    Q_Q(const QTextDocumentLayout);
3060
    if (currentLazyLayoutPosition == -1)
3061
        return;
3062
    const QSizeF oldSize = q->dynamicDocumentSize();
3063
3064
    if (checkPoints.isEmpty())
3065
        layoutStep();
3066
3067
    while (currentLazyLayoutPosition != -1
3068
           && checkPoints.last().y < y)
3069
        layoutStep();
3070
}
3071
3072
void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
3073
{
3074
    if (currentLazyLayoutPosition == -1)
3075
        return;
3076
    if (position < currentLazyLayoutPosition)
3077
        return;
3078
    while (currentLazyLayoutPosition != -1
3079
           && currentLazyLayoutPosition < position) {
3080
        const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
3081
    }
3082
}
3083
3084
void QTextDocumentLayoutPrivate::layoutStep() const
3085
{
3086
    ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
3087
    lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
3088
}
3089
3090
void QTextDocumentLayout::setCursorWidth(int width)
3091
{
3092
    Q_D(QTextDocumentLayout);
3093
    d->cursorWidth = width;
3094
}
3095
3096
int QTextDocumentLayout::cursorWidth() const
3097
{
3098
    Q_D(const QTextDocumentLayout);
3099
    return d->cursorWidth;
3100
}
3101
3102
void QTextDocumentLayout::setFixedColumnWidth(int width)
3103
{
3104
    Q_D(QTextDocumentLayout);
3105
    d->fixedColumnWidth = width;
3106
}
3107
3108
QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
3109
{
3110
    Q_D(const QTextDocumentLayout);
3111
    if (d->docPrivate->pageSize.isNull())
3112
        return QRectF();
3113
    d->ensureLayoutFinished();
3114
    return d->frameBoundingRectInternal(frame);
3115
}
3116
3117
QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
3118
{
3119
    QPointF pos;
3120
    const int framePos = frame->firstPosition();
3121
    QTextFrame *f = frame;
3122
    while (f) {
3123
        QTextFrameData *fd = data(f);
3124
        pos += fd->position.toPointF();
3125
3126
        if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3127
            QTextTableCell cell = table->cellAt(framePos);
3128
            if (cell.isValid())
3129
                pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3130
        }
3131
3132
        f = f->parentFrame();
3133
    }
3134
    return QRectF(pos, data(frame)->size.toSizeF());
3135
}
3136
3137
QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
3138
{
3139
    Q_D(const QTextDocumentLayout);
3140
    if (d->docPrivate->pageSize.isNull())
3141
        return QRectF();
3142
    d->ensureLayoutedByPosition(block.position() + block.length());
3143
    QTextFrame *frame = d->document->frameAt(block.position());
3144
    QPointF offset;
3145
    const int blockPos = block.position();
3146
3147
    while (frame) {
3148
        QTextFrameData *fd = data(frame);
3149
        offset += fd->position.toPointF();
3150
3151
        if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
3152
            QTextTableCell cell = table->cellAt(blockPos);
3153
            if (cell.isValid())
3154
                offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3155
        }
3156
3157
        frame = frame->parentFrame();
3158
    }
3159
3160
    const QTextLayout *layout = block.layout();
3161
    QRectF rect = layout->boundingRect();
3162
    rect.moveTopLeft(layout->position() + offset);
3163
    return rect;
3164
}
3165
3166
int QTextDocumentLayout::layoutStatus() const
3167
{
3168
    Q_D(const QTextDocumentLayout);
3169
    int pos = d->currentLazyLayoutPosition;
3170
    if (pos == -1)
3171
        return 100;
3172
    return pos * 100 / d->document->docHandle()->length();
3173
}
3174
3175
void QTextDocumentLayout::timerEvent(QTimerEvent *e)
3176
{
3177
    Q_D(QTextDocumentLayout);
3178
    if (e->timerId() == d->layoutTimer.timerId()) {
3179
        if (d->currentLazyLayoutPosition != -1)
3180
            d->layoutStep();
3181
    } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
3182
        d->lastReportedSize = dynamicDocumentSize();
3183
        emit documentSizeChanged(d->lastReportedSize);
3184
        d->sizeChangedTimer.stop();
3185
3186
        if (d->currentLazyLayoutPosition == -1) {
3187
            const int newCount = dynamicPageCount();
3188
            if (newCount != d->lastPageCount) {
3189
                d->lastPageCount = newCount;
3190
                emit pageCountChanged(newCount);
3191
            }
3192
        }
3193
    } else {
3194
        QAbstractTextDocumentLayout::timerEvent(e);
3195
    }
3196
}
3197
3198
void QTextDocumentLayout::layoutFinished()
3199
{
3200
    Q_D(QTextDocumentLayout);
3201
    d->layoutTimer.stop();
3202
    if (!d->insideDocumentChange)
3203
        d->sizeChangedTimer.start(0, this);
3204
    // reset
3205
    d->showLayoutProgress = true;
3206
}
3207
3208
void QTextDocumentLayout::ensureLayouted(qreal y)
3209
{
3210
    d_func()->ensureLayouted(QFixed::fromReal(y));
3211
}
3212
3213
qreal QTextDocumentLayout::idealWidth() const
3214
{
3215
    Q_D(const QTextDocumentLayout);
3216
    d->ensureLayoutFinished();
3217
    return d->idealWidth;
3218
}
3219
3220
bool QTextDocumentLayout::contentHasAlignment() const
3221
{
3222
    Q_D(const QTextDocumentLayout);
3223
    return d->contentHasAlignment;
3224
}
3225
3226
qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
3227
{
3228
    if (!paintDevice)
3229
        return value;
3230
    return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi());
3231
}
3232
3233
QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
3234
{
3235
    if (!paintDevice)
3236
        return value;
3237
    return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi());
3238
}
3239
3240
QT_END_NAMESPACE
3241
3242
#include "moc_qtextdocumentlayout_p.cpp"