Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/text/qtextdocumentlayout.cpp
Line
Count
Source
1
// Copyright (C) 2016 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
#include "qtextdocumentlayout_p.h"
5
#include "qtextdocument_p.h"
6
#include "qtextimagehandler_p.h"
7
#include "qtexttable.h"
8
#include "qtextlist.h"
9
#include "qtextengine_p.h"
10
#if QT_CONFIG(cssparser)
11
#include "private/qcssutil_p.h"
12
#endif
13
#include "private/qguiapplication_p.h"
14
15
#include "qabstracttextdocumentlayout_p.h"
16
#include "qcssparser_p.h"
17
18
#include <qpainter.h>
19
#include <qmath.h>
20
#include <qrect.h>
21
#include <qpalette.h>
22
#include <qdebug.h>
23
#include <qvarlengtharray.h>
24
#include <limits.h>
25
#include <qbasictimer.h>
26
#include "private/qfunctions_p.h"
27
#include <qloggingcategory.h>
28
#include <QtCore/qpointer.h>
29
30
#include <algorithm>
31
32
QT_BEGIN_NAMESPACE
33
34
Q_STATIC_LOGGING_CATEGORY(lcDraw, "qt.text.drawing")
35
Q_STATIC_LOGGING_CATEGORY(lcHit, "qt.text.hittest")
36
Q_LOGGING_CATEGORY(lcLayout, "qt.text.layout")
37
Q_STATIC_LOGGING_CATEGORY(lcTable, "qt.text.layout.table")
38
39
// ################ should probably add frameFormatChange notification!
40
41
struct QTextLayoutStruct;
42
43
class QTextFrameData : public QTextFrameLayoutData
44
{
45
public:
46
    QTextFrameData();
47
48
    // relative to parent frame
49
    QFixedPoint position;
50
    QFixedSize size;
51
52
    // contents starts at (margin+border/margin+border)
53
    QFixed topMargin;
54
    QFixed bottomMargin;
55
    QFixed leftMargin;
56
    QFixed rightMargin;
57
    QFixed border;
58
    QFixed padding;
59
    // contents width includes padding (as we need to treat this on a per cell basis for tables)
60
    QFixed contentsWidth;
61
    QFixed contentsHeight;
62
    QFixed oldContentsWidth;
63
64
    // accumulated margins
65
    QFixed effectiveTopMargin;
66
    QFixed effectiveBottomMargin;
67
68
    QFixed minimumWidth;
69
    QFixed maximumWidth;
70
71
    QTextLayoutStruct *currentLayoutStruct;
72
73
    bool sizeDirty;
74
    bool layoutDirty;
75
76
    QList<QPointer<QTextFrame>> floats;
77
};
78
79
QTextFrameData::QTextFrameData()
80
17.4k
    : maximumWidth(QFIXED_MAX),
81
17.4k
      currentLayoutStruct(nullptr), sizeDirty(true), layoutDirty(true)
82
17.4k
{
83
17.4k
}
84
85
struct QTextLayoutStruct {
86
34.8k
    QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
87
34.8k
    {}
88
    QTextFrame *frame;
89
    QFixed x_left;
90
    QFixed x_right;
91
    QFixed frameY; // absolute y position of the current frame
92
    QFixed y; // always relative to the current frame
93
    QFixed contentsWidth;
94
    QFixed minimumWidth;
95
    QFixed maximumWidth;
96
    bool fullLayout;
97
    QList<QTextFrame *> pendingFloats;
98
    QFixed pageHeight;
99
    QFixed pageBottom;
100
    QFixed pageTopMargin;
101
    QFixed pageBottomMargin;
102
    QRectF updateRect;
103
    QRectF updateRectForFloats;
104
105
0
    inline void addUpdateRectForFloat(const QRectF &rect) {
106
0
        if (updateRectForFloats.isValid())
107
0
            updateRectForFloats |= rect;
108
0
        else
109
0
            updateRectForFloats = rect;
110
0
    }
111
112
    inline QFixed absoluteY() const
113
2.29M
    { return frameY + y; }
114
115
    inline QFixed contentHeight() const
116
91.8k
    { return pageHeight - pageBottomMargin - pageTopMargin; }
117
118
    inline int currentPage() const
119
17.4k
    { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
120
121
    inline void newPage()
122
91.8k
    { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = qMax(y, pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY); }
123
};
124
125
#ifndef QT_NO_CSSPARSER
126
// helper struct to collect edge data and priorize edges for border-collapse mode
127
struct EdgeData {
128
129
    enum EdgeClass {
130
        // don't change order, used for comparison
131
        ClassInvalid,     // queried (adjacent) cell does not exist
132
        ClassNone,        // no explicit border, no grid, no table border
133
        ClassGrid,        // 1px grid if drawGrid is true
134
        ClassTableBorder, // an outermost edge
135
        ClassExplicit     // set in cell's format
136
    };
137
138
    EdgeData(qreal width, const QTextTableCell &cell, QCss::Edge edge, EdgeClass edgeClass) :
139
0
        width(width), cell(cell), edge(edge), edgeClass(edgeClass) {}
140
    EdgeData() :
141
0
        width(0), edge(QCss::NumEdges), edgeClass(ClassInvalid) {}
142
143
    // used for priorization with qMax
144
0
    bool operator< (const EdgeData &other) const {
145
0
        if (width < other.width) return true;
146
0
        if (width > other.width) return false;
147
0
        if (edgeClass < other.edgeClass) return true;
148
0
        if (edgeClass > other.edgeClass) return false;
149
0
        if (edge == QCss::TopEdge && other.edge == QCss::BottomEdge) return true;
150
0
        if (edge == QCss::BottomEdge && other.edge == QCss::TopEdge) return false;
151
0
        if (edge == QCss::LeftEdge && other.edge == QCss::RightEdge) return true;
152
0
        return false;
153
0
    }
154
0
    bool operator> (const EdgeData &other) const {
155
0
        return other < *this;
156
0
    }
157
158
    qreal width;
159
    QTextTableCell cell;
160
    QCss::Edge edge;
161
    EdgeClass edgeClass;
162
};
163
164
// axisEdgeData is referenced by QTextTableData's inline methods, so predeclare
165
class QTextTableData;
166
static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell, QCss::Edge edge);
167
#endif
168
169
class QTextTableData : public QTextFrameData
170
{
171
public:
172
    QFixed cellSpacing, cellPadding;
173
    qreal deviceScale;
174
    QList<QFixed> minWidths;
175
    QList<QFixed> maxWidths;
176
    QList<QFixed> widths;
177
    QList<QFixed> heights;
178
    QList<QFixed> columnPositions;
179
    QList<QFixed> rowPositions;
180
181
    QList<QFixed> cellVerticalOffsets;
182
183
    // without borderCollapse, those equal QTextFrameData::border;
184
    // otherwise the widest outermost cell edge will be used
185
    QFixed effectiveLeftBorder;
186
    QFixed effectiveTopBorder;
187
    QFixed effectiveRightBorder;
188
    QFixed effectiveBottomBorder;
189
190
    QFixed headerHeight;
191
192
    QFixed borderCell; // 0 if borderCollapse is enabled, QTextFrameData::border otherwise
193
    bool borderCollapse;
194
    bool drawGrid;
195
196
    // maps from cell index (row + col * rowCount) to child frames belonging to
197
    // the specific cell
198
    QMultiHash<int, QTextFrame *> childFrameMap;
199
200
    inline QFixed cellWidth(int column, int colspan) const
201
0
    { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
202
0
             - columnPositions.at(column); }
203
204
    inline void calcRowPosition(int row)
205
0
    {
206
0
        if (row > 0)
207
0
            rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + borderCell + cellSpacing + borderCell;
208
0
    }
209
210
    QRectF cellRect(const QTextTableCell &cell) const;
211
212
    inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
213
0
    {
214
0
        QVariant v = format.property(property);
215
0
        if (v.isNull()) {
216
0
            return cellPadding;
217
0
        } else {
218
0
            Q_ASSERT(v.userType() == QMetaType::Double || v.userType() == QMetaType::Float);
219
0
            return QFixed::fromReal(v.toReal() * deviceScale);
220
0
        }
221
0
    }
222
223
#ifndef QT_NO_CSSPARSER
224
    inline QFixed cellBorderWidth(QTextTable *table, const QTextTableCell &cell, QCss::Edge edge) const
225
0
    {
226
0
        qreal rv = axisEdgeData(table, this, cell, edge).width;
227
0
        if (borderCollapse)
228
0
            rv /= 2; // each cell has to add half of the border's width to its own padding
229
0
        return QFixed::fromReal(rv * deviceScale);
230
0
    }
231
#endif
232
233
    inline QFixed topPadding(QTextTable *table, const QTextTableCell &cell) const
234
0
    {
235
#ifdef QT_NO_CSSPARSER
236
        Q_UNUSED(table);
237
#endif
238
0
        return paddingProperty(cell.format(), QTextFormat::TableCellTopPadding)
239
0
#ifndef QT_NO_CSSPARSER
240
0
                + cellBorderWidth(table, cell, QCss::TopEdge)
241
0
#endif
242
0
        ;
243
0
    }
244
245
    inline QFixed bottomPadding(QTextTable *table, const QTextTableCell &cell) const
246
0
    {
247
#ifdef QT_NO_CSSPARSER
248
        Q_UNUSED(table);
249
#endif
250
0
        return paddingProperty(cell.format(), QTextFormat::TableCellBottomPadding)
251
0
#ifndef QT_NO_CSSPARSER
252
0
                + cellBorderWidth(table, cell, QCss::BottomEdge)
253
0
#endif
254
0
                ;
255
0
    }
256
257
    inline QFixed leftPadding(QTextTable *table, const QTextTableCell &cell) const
258
0
    {
259
#ifdef QT_NO_CSSPARSER
260
        Q_UNUSED(table);
261
#endif
262
0
        return paddingProperty(cell.format(), QTextFormat::TableCellLeftPadding)
263
0
#ifndef QT_NO_CSSPARSER
264
0
                + cellBorderWidth(table, cell, QCss::LeftEdge)
265
0
#endif
266
0
        ;
267
0
    }
268
269
    inline QFixed rightPadding(QTextTable *table, const QTextTableCell &cell) const
270
0
    {
271
#ifdef QT_NO_CSSPARSER
272
        Q_UNUSED(table);
273
#endif
274
0
        return paddingProperty(cell.format(), QTextFormat::TableCellRightPadding)
275
0
#ifndef QT_NO_CSSPARSER
276
0
                + cellBorderWidth(table, cell, QCss::RightEdge)
277
0
#endif
278
0
        ;
279
0
    }
280
281
    inline QFixedPoint cellPosition(QTextTable *table, const QTextTableCell &cell) const
282
0
    {
283
0
        return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(table, cell), topPadding(table, cell));
284
0
    }
285
286
    void updateTableSize();
287
288
private:
289
    inline QFixedPoint cellPosition(int row, int col) const
290
0
    { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
291
};
292
293
static QTextFrameData *createData(QTextFrame *f)
294
17.4k
{
295
17.4k
    QTextFrameData *data;
296
17.4k
    if (qobject_cast<QTextTable *>(f))
297
0
        data = new QTextTableData;
298
17.4k
    else
299
17.4k
        data = new QTextFrameData;
300
17.4k
    f->setLayoutData(data);
301
17.4k
    return data;
302
17.4k
}
303
304
static inline QTextFrameData *data(QTextFrame *f)
305
3.31M
{
306
3.31M
    QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
307
3.31M
    if (!data)
308
17.4k
        data = createData(f);
309
3.31M
    return data;
310
3.31M
}
311
312
static bool isFrameFromInlineObject(QTextFrame *f)
313
34.8k
{
314
34.8k
    return f->firstPosition() > f->lastPosition();
315
34.8k
}
316
317
void QTextTableData::updateTableSize()
318
0
{
319
0
    const QFixed effectiveTopMargin = this->topMargin + effectiveTopBorder + padding;
320
0
    const QFixed effectiveBottomMargin = this->bottomMargin + effectiveBottomBorder + padding;
321
0
    const QFixed effectiveLeftMargin = this->leftMargin + effectiveLeftBorder + padding;
322
0
    const QFixed effectiveRightMargin = this->rightMargin + effectiveRightBorder + padding;
323
0
    size.height = contentsHeight == -1
324
0
                   ? rowPositions.constLast() + heights.constLast() + padding + border + cellSpacing + effectiveBottomMargin
325
0
                   : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
326
0
    size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
327
0
}
328
329
QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
330
0
{
331
0
    const int row = cell.row();
332
0
    const int rowSpan = cell.rowSpan();
333
0
    const int column = cell.column();
334
0
    const int colSpan = cell.columnSpan();
335
336
0
    return QRectF(columnPositions.at(column).toReal(),
337
0
                  rowPositions.at(row).toReal(),
338
0
                  (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
339
0
                  (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
340
0
}
341
342
static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
343
1.13M
{
344
1.13M
    return !nextIt.atEnd()
345
1.09M
           && qobject_cast<QTextTable *>(nextIt.currentFrame())
346
0
           && block.isValid()
347
0
           && block.length() == 1
348
0
           && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
349
0
           && !format.hasProperty(QTextFormat::BackgroundBrush)
350
0
           && nextIt.currentFrame()->firstPosition() == block.position() + 1
351
1.13M
           ;
352
1.13M
}
353
354
static inline bool isEmptyBlockBeforeTable(const QTextFrame::Iterator &it)
355
55.1k
{
356
55.1k
    QTextFrame::Iterator next = it; ++next;
357
55.1k
    if (it.currentFrame())
358
0
        return false;
359
55.1k
    QTextBlock block = it.currentBlock();
360
55.1k
    return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
361
55.1k
}
362
363
static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
364
1.08M
{
365
1.08M
    return qobject_cast<const QTextTable *>(previousFrame)
366
0
           && block.isValid()
367
0
           && block.length() == 1
368
0
           && previousFrame->lastPosition() == block.position() - 1
369
1.08M
           ;
370
1.08M
}
371
372
static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
373
818k
{
374
818k
    return qobject_cast<const QTextTable *>(previousFrame)
375
0
           && block.isValid()
376
0
           && block.length() > 1
377
0
           && block.text().at(0) == QChar::LineSeparator
378
0
           && previousFrame->lastPosition() == block.position() - 1
379
818k
           ;
380
818k
}
381
382
/*
383
384
Optimization strategies:
385
386
HTML layout:
387
388
* Distinguish between normal and special flow. For normal flow the condition:
389
  y1 > y2 holds for all blocks with b1.key() > b2.key().
390
* Special flow is: floats, table cells
391
392
* Normal flow within table cells. Tables (not cells) are part of the normal flow.
393
394
395
* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
396
* If height doesn't change, no need to do anything
397
398
Table cells:
399
400
* If minWidth of cell changes, recalculate table width, relayout if needed.
401
* What about maxWidth when doing auto layout?
402
403
Floats:
404
* need fixed or proportional width, otherwise don't float!
405
* On width/height change relayout surrounding paragraphs.
406
407
Document width change:
408
* full relayout needed
409
410
411
Float handling:
412
413
* Floats are specified by a special format object.
414
* currently only floating images are implemented.
415
416
*/
417
418
/*
419
420
   On the table layouting:
421
422
   +---[ table border ]-------------------------
423
   |      [ cell spacing ]
424
   |  +------[ cell border ]-----+  +--------
425
   |  |                          |  |
426
   |  |
427
   |  |
428
   |  |
429
   |
430
431
   rowPositions[i] and columnPositions[i] point at the cell content
432
   position. So for example the left border is drawn at
433
   x = columnPositions[i] - fd->border and similar for y.
434
435
*/
436
437
struct QCheckPoint
438
{
439
    QFixed y;
440
    QFixed frameY; // absolute y position of the current frame
441
    int positionInFrame;
442
    QFixed minimumWidth;
443
    QFixed maximumWidth;
444
    QFixed contentsWidth;
445
};
446
Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
447
448
static bool operator<(const QCheckPoint &checkPoint, QFixed y)
449
58.8k
{
450
58.8k
    return checkPoint.y < y;
451
58.8k
}
452
453
static bool operator<(const QCheckPoint &checkPoint, int pos)
454
35.0k
{
455
35.0k
    return checkPoint.positionInFrame < pos;
456
35.0k
}
457
458
static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, const QRectF &gradientRect = QRectF())
459
0
{
460
0
    p->save();
461
0
    if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
462
0
        if (!gradientRect.isNull()) {
463
0
            QTransform m;
464
0
            m.translate(gradientRect.left(), gradientRect.top());
465
0
            m.scale(gradientRect.width(), gradientRect.height());
466
0
            brush.setTransform(m);
467
0
            const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
468
0
        }
469
0
    } else {
470
0
        p->setBrushOrigin(origin);
471
0
    }
472
0
    p->fillRect(rect, brush);
473
0
    p->restore();
474
0
}
475
476
class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
477
{
478
    Q_DECLARE_PUBLIC(QTextDocumentLayout)
479
public:
480
    QTextDocumentLayoutPrivate();
481
482
    QTextOption::WrapMode wordWrapMode;
483
#ifdef LAYOUT_DEBUG
484
    mutable QString debug_indent;
485
#endif
486
487
    int fixedColumnWidth;
488
    int cursorWidth;
489
490
    QSizeF lastReportedSize;
491
    QRectF viewportRect;
492
    QRectF clipRect;
493
494
    mutable int currentLazyLayoutPosition;
495
    mutable int lazyLayoutStepSize;
496
    QBasicTimer layoutTimer;
497
    mutable QBasicTimer sizeChangedTimer;
498
    uint showLayoutProgress : 1;
499
    uint insideDocumentChange : 1;
500
501
    int lastPageCount;
502
    qreal idealWidth;
503
    bool contentHasAlignment;
504
505
    QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
506
507
    void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
508
                   QTextFrame *f) const;
509
    void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
510
                  QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
511
    void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
512
                   const QTextBlock &bl, bool inRootFrame) const;
513
    void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
514
                      const QTextBlock &bl, const QTextCharFormat *selectionFormat) const;
515
    void drawTableCellBorder(const QRectF &cellRect, QPainter *painter, QTextTable *table, QTextTableData *td, const QTextTableCell &cell) const;
516
    void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
517
                       QTextTable *table, QTextTableData *td, int r, int c,
518
                       QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
519
    void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
520
                    const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
521
    void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
522
523
    enum HitPoint {
524
        PointBefore,
525
        PointAfter,
526
        PointInside,
527
        PointExact
528
    };
529
    HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
530
    HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
531
                     int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
532
    HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
533
    HitPoint hitTest(const QTextBlock &bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
534
535
    QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
536
                                 int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
537
                                 bool withPageBreaks);
538
    void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
539
    QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
540
541
    void positionFloat(QTextFrame *frame, QTextLine *currentLine = nullptr);
542
543
    // calls the next one
544
    QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
545
    QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
546
547
    void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
548
                     QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
549
    void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
550
551
    void floatMargins(QFixed y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
552
    QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const;
553
554
    QList<QCheckPoint> checkPoints;
555
556
    QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
557
    QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
558
559
    void ensureLayouted(QFixed y) const;
560
    void ensureLayoutedByPosition(int position) const;
561
    inline void ensureLayoutFinished() const
562
0
    { ensureLayoutedByPosition(INT_MAX); }
563
    void layoutStep() const;
564
565
    QRectF frameBoundingRectInternal(QTextFrame *frame) const;
566
567
    qreal scaleToDevice(qreal value) const;
568
    QFixed scaleToDevice(QFixed value) const;
569
};
570
571
QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
572
17.4k
    : fixedColumnWidth(-1),
573
17.4k
      cursorWidth(1),
574
17.4k
      currentLazyLayoutPosition(-1),
575
17.4k
      lazyLayoutStepSize(1000),
576
17.4k
      lastPageCount(-1)
577
17.4k
{
578
17.4k
    showLayoutProgress = true;
579
17.4k
    insideDocumentChange = false;
580
17.4k
    idealWidth = 0;
581
17.4k
    contentHasAlignment = false;
582
17.4k
}
583
584
QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
585
17.4k
{
586
17.4k
    QTextFrame *rootFrame = document->rootFrame();
587
588
17.4k
    if (checkPoints.isEmpty()
589
17.4k
        || y < 0 || y > data(rootFrame)->size.height)
590
0
        return rootFrame->begin();
591
592
17.4k
    auto checkPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), y);
593
17.4k
    if (checkPoint == checkPoints.end())
594
0
        return rootFrame->begin();
595
596
17.4k
    if (checkPoint != checkPoints.begin())
597
0
        --checkPoint;
598
599
17.4k
    const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
600
17.4k
    return frameIteratorForTextPosition(position);
601
17.4k
}
602
603
QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
604
89.9k
{
605
89.9k
    QTextFrame *rootFrame = docPrivate->rootFrame();
606
607
89.9k
    const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
608
89.9k
    const int begin = map.findNode(rootFrame->firstPosition());
609
89.9k
    const int end = map.findNode(rootFrame->lastPosition()+1);
610
611
89.9k
    const int block = map.findNode(position);
612
89.9k
    const int blockPos = map.position(block);
613
614
89.9k
    QTextFrame::iterator it(rootFrame, block, begin, end);
615
616
89.9k
    QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
617
89.9k
    if (containingFrame != rootFrame) {
618
0
        while (containingFrame->parentFrame() != rootFrame) {
619
0
            containingFrame = containingFrame->parentFrame();
620
0
            Q_ASSERT(containingFrame);
621
0
        }
622
623
0
        it.cf = containingFrame;
624
0
        it.cb = 0;
625
0
    }
626
627
89.9k
    return it;
628
89.9k
}
629
630
QTextDocumentLayoutPrivate::HitPoint
631
QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
632
0
{
633
0
    QTextFrameData *fd = data(frame);
634
    // #########
635
0
    if (fd->layoutDirty)
636
0
        return PointAfter;
637
0
    Q_ASSERT(!fd->layoutDirty);
638
0
    Q_ASSERT(!fd->sizeDirty);
639
0
    const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
640
641
0
    QTextFrame *rootFrame = docPrivate->rootFrame();
642
643
0
    qCDebug(lcHit) << "checking frame" << frame->firstPosition() << "point=" << point.toPointF()
644
0
                   << "position" << fd->position.toPointF() << "size" << fd->size.toSizeF();
645
0
    if (frame != rootFrame) {
646
0
        if (relativePoint.y < 0 || relativePoint.x < 0) {
647
0
            *position = frame->firstPosition() - 1;
648
0
            qCDebug(lcHit) << "before pos=" << *position;
649
0
            return PointBefore;
650
0
        } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
651
0
            *position = frame->lastPosition() + 1;
652
0
            qCDebug(lcHit) << "after pos=" << *position;
653
0
            return PointAfter;
654
0
        }
655
0
    }
656
657
0
    if (isFrameFromInlineObject(frame)) {
658
0
        *position = frame->firstPosition() - 1;
659
0
        return PointExact;
660
0
    }
661
662
0
    if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
663
0
        const int rows = table->rows();
664
0
        const int columns = table->columns();
665
0
        QTextTableData *td = static_cast<QTextTableData *>(data(table));
666
667
0
        if (!td->childFrameMap.isEmpty()) {
668
0
            for (int r = 0; r < rows; ++r) {
669
0
                for (int c = 0; c < columns; ++c) {
670
0
                    QTextTableCell cell = table->cellAt(r, c);
671
0
                    if (cell.row() != r || cell.column() != c)
672
0
                        continue;
673
674
0
                    QRectF cellRect = td->cellRect(cell);
675
0
                    const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
676
0
                    const QFixedPoint pointInCell = relativePoint - cellPos;
677
678
0
                    const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
679
0
                    for (int i = 0; i < childFrames.size(); ++i) {
680
0
                        QTextFrame *child = childFrames.at(i);
681
0
                        if (isFrameFromInlineObject(child)
682
0
                            && child->frameFormat().position() != QTextFrameFormat::InFlow
683
0
                            && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
684
0
                        {
685
0
                            return PointExact;
686
0
                        }
687
0
                    }
688
0
                }
689
0
            }
690
0
        }
691
692
0
        return hitTest(table, relativePoint, position, l, accuracy);
693
0
    }
694
695
0
    const QList<QTextFrame *> childFrames = frame->childFrames();
696
0
    for (int i = 0; i < childFrames.size(); ++i) {
697
0
        QTextFrame *child = childFrames.at(i);
698
0
        if (isFrameFromInlineObject(child)
699
0
            && child->frameFormat().position() != QTextFrameFormat::InFlow
700
0
            && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
701
0
        {
702
0
            return PointExact;
703
0
        }
704
0
    }
705
706
0
    QTextFrame::Iterator it = frame->begin();
707
708
0
    if (frame == rootFrame) {
709
0
        it = frameIteratorForYPosition(relativePoint.y);
710
711
0
        Q_ASSERT(it.parentFrame() == frame);
712
0
    }
713
714
0
    if (it.currentFrame())
715
0
        *position = it.currentFrame()->firstPosition();
716
0
    else
717
0
        *position = it.currentBlock().position();
718
719
0
    return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
720
0
}
721
722
QTextDocumentLayoutPrivate::HitPoint
723
QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
724
                                    int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
725
0
{
726
0
    for (; !it.atEnd(); ++it) {
727
0
        QTextFrame *c = it.currentFrame();
728
0
        HitPoint hp;
729
0
        int pos = -1;
730
0
        if (c) {
731
0
            hp = hitTest(c, p, &pos, l, accuracy);
732
0
        } else {
733
0
            hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
734
0
        }
735
0
        if (hp >= PointInside) {
736
0
            if (isEmptyBlockBeforeTable(it))
737
0
                continue;
738
0
            hit = hp;
739
0
            *position = pos;
740
0
            break;
741
0
        }
742
0
        if (hp == PointBefore && pos < *position) {
743
0
            *position = pos;
744
0
            hit = hp;
745
0
        } else if (hp == PointAfter && pos > *position) {
746
0
            *position = pos;
747
0
            hit = hp;
748
0
        }
749
0
    }
750
751
0
    qCDebug(lcHit) << "inside=" << hit << " pos=" << *position;
752
0
    return hit;
753
0
}
754
755
QTextDocumentLayoutPrivate::HitPoint
756
QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
757
                                    int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
758
0
{
759
0
    QTextTableData *td = static_cast<QTextTableData *>(data(table));
760
761
0
    auto rowIt = std::lower_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
762
0
    if (rowIt == td->rowPositions.constEnd()) {
763
0
        rowIt = td->rowPositions.constEnd() - 1;
764
0
    } else if (rowIt != td->rowPositions.constBegin()) {
765
0
        --rowIt;
766
0
    }
767
768
0
    auto colIt = std::lower_bound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
769
0
    if (colIt == td->columnPositions.constEnd()) {
770
0
        colIt = td->columnPositions.constEnd() - 1;
771
0
    } else if (colIt != td->columnPositions.constBegin()) {
772
0
        --colIt;
773
0
    }
774
775
0
    QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
776
0
                                        colIt - td->columnPositions.constBegin());
777
0
    if (!cell.isValid())
778
0
        return PointBefore;
779
780
0
    *position = cell.firstPosition();
781
782
0
    HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(table, cell), position, l, accuracy);
783
784
0
    if (hp == PointExact)
785
0
        return hp;
786
0
    if (hp == PointAfter)
787
0
        *position = cell.lastPosition();
788
0
    return PointInside;
789
0
}
790
791
QTextDocumentLayoutPrivate::HitPoint
792
QTextDocumentLayoutPrivate::hitTest(const QTextBlock &bl, const QFixedPoint &point, int *position, QTextLayout **l,
793
                                    Qt::HitTestAccuracy accuracy) const
794
0
{
795
0
    QTextLayout *tl = bl.layout();
796
0
    QRectF textrect = tl->boundingRect();
797
0
    textrect.translate(tl->position());
798
0
    qCDebug(lcHit) << "    checking block" << bl.position() << "point=" << point.toPointF() << "    tlrect" << textrect;
799
0
    *position = bl.position();
800
0
    if (point.y.toReal() < textrect.top() - bl.blockFormat().topMargin()) {
801
0
        qCDebug(lcHit) << "    before pos=" << *position;
802
0
        return PointBefore;
803
0
    } else if (point.y.toReal() > textrect.bottom()) {
804
0
        *position += bl.length();
805
0
        qCDebug(lcHit) << "    after pos=" << *position;
806
0
        return PointAfter;
807
0
    }
808
809
0
    QPointF pos = point.toPointF() - tl->position();
810
811
    // ### rtl?
812
813
0
    HitPoint hit = PointInside;
814
0
    *l = tl;
815
0
    int off = 0;
816
0
    for (int i = 0; i < tl->lineCount(); ++i) {
817
0
        QTextLine line = tl->lineAt(i);
818
0
        const QRectF lr = line.naturalTextRect();
819
0
        if (lr.top() > pos.y()) {
820
0
            off = qMin(off, line.textStart());
821
0
        } else if (lr.bottom() <= pos.y()) {
822
0
            off = qMax(off, line.textStart() + line.textLength());
823
0
        } else {
824
0
            if (lr.left() <= pos.x() && lr.right() >= pos.x())
825
0
                hit = PointExact;
826
            // when trying to hit an anchor we want it to hit not only in the left
827
            // half
828
0
            if (accuracy == Qt::ExactHit)
829
0
                off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
830
0
            else
831
0
                off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
832
0
            break;
833
0
        }
834
0
    }
835
0
    *position += off;
836
837
0
    qCDebug(lcHit) << "    inside=" << hit << " pos=" << *position;
838
0
    return hit;
839
0
}
840
841
// ### could be moved to QTextBlock
842
QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
843
818k
{
844
818k
    qreal indent = blockFormat.indent();
845
846
818k
    QTextObject *object = document->objectForFormat(blockFormat);
847
818k
    if (object)
848
0
        indent += object->format().toListFormat().indent();
849
850
818k
    if (qIsNull(indent))
851
818k
        return 0;
852
853
0
    qreal scale = 1;
854
0
    if (paintDevice) {
855
0
        scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
856
0
    }
857
858
0
    return QFixed::fromReal(indent * scale * document->indentWidth());
859
818k
}
860
861
struct BorderPaginator
862
{
863
    BorderPaginator(QTextDocument *document, const QRectF &rect, qreal topMarginAfterPageBreak, qreal bottomMargin, qreal border) :
864
0
        pageHeight(document->pageSize().height()),
865
0
        topPage(pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0),
866
0
        bottomPage(pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0),
867
0
        rect(rect),
868
0
        topMarginAfterPageBreak(topMarginAfterPageBreak),
869
0
        bottomMargin(bottomMargin), border(border)
870
0
    {}
871
872
    QRectF clipRect(int page) const
873
0
    {
874
0
        QRectF clipped = rect.toRect();
875
876
0
        if (topPage != bottomPage) {
877
0
            clipped.setTop(qMax(clipped.top(), page * pageHeight + topMarginAfterPageBreak - border));
878
0
            clipped.setBottom(qMin(clipped.bottom(), (page + 1) * pageHeight - bottomMargin));
879
880
0
            if (clipped.bottom() <= clipped.top())
881
0
                return QRectF();
882
0
        }
883
884
0
        return clipped;
885
0
    }
886
887
    qreal pageHeight;
888
    int topPage;
889
    int bottomPage;
890
    QRectF rect;
891
    qreal topMarginAfterPageBreak;
892
    qreal bottomMargin;
893
    qreal border;
894
};
895
896
void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
897
                                            qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
898
0
{
899
0
    BorderPaginator paginator(document, rect, topMargin, bottomMargin, border);
900
901
0
#ifndef QT_NO_CSSPARSER
902
0
    QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
903
#else
904
    Q_UNUSED(style);
905
#endif //QT_NO_CSSPARSER
906
907
0
    bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
908
0
    painter->setRenderHint(QPainter::Antialiasing);
909
910
0
    for (int i = paginator.topPage; i <= paginator.bottomPage; ++i) {
911
0
        QRectF clipped = paginator.clipRect(i);
912
0
        if (!clipped.isValid())
913
0
            continue;
914
915
0
#ifndef QT_NO_CSSPARSER
916
0
        qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
917
0
        qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
918
0
        qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
919
0
        qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
920
#else
921
        painter->save();
922
        painter->setPen(Qt::NoPen);
923
        painter->setBrush(brush);
924
        painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
925
        painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
926
        painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
927
        painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
928
        painter->restore();
929
#endif //QT_NO_CSSPARSER
930
0
    }
931
0
    if (turn_off_antialiasing)
932
0
        painter->setRenderHint(QPainter::Antialiasing, false);
933
0
}
934
935
void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
936
17.4k
{
937
938
17.4k
    const QBrush bg = frame->frameFormat().background();
939
17.4k
    if (bg != Qt::NoBrush) {
940
0
        QRectF bgRect = rect;
941
0
        bgRect.adjust((fd->leftMargin + fd->border).toReal(),
942
0
                      (fd->topMargin + fd->border).toReal(),
943
0
                      - (fd->rightMargin + fd->border).toReal(),
944
0
                      - (fd->bottomMargin + fd->border).toReal());
945
946
0
        QRectF gradientRect; // invalid makes it default to bgRect
947
0
        QPointF origin = bgRect.topLeft();
948
0
        if (!frame->parentFrame()) {
949
0
            bgRect = clip;
950
0
            gradientRect.setWidth(painter->device()->width());
951
0
            gradientRect.setHeight(painter->device()->height());
952
0
        }
953
0
        fillBackground(painter, bgRect, bg, origin, gradientRect);
954
0
    }
955
17.4k
    if (fd->border != 0) {
956
0
        painter->save();
957
0
        painter->setBrush(Qt::lightGray);
958
0
        painter->setPen(Qt::NoPen);
959
960
0
        const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
961
0
        const qreal border = fd->border.toReal();
962
0
        const qreal topMargin = fd->topMargin.toReal();
963
0
        const qreal leftMargin = fd->leftMargin.toReal();
964
0
        const qreal bottomMargin = fd->bottomMargin.toReal();
965
0
        const qreal rightMargin = fd->rightMargin.toReal();
966
0
        const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
967
0
        const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
968
969
0
        drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
970
0
                   fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
971
0
                   border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
972
973
0
        painter->restore();
974
0
    }
975
17.4k
}
976
977
static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
978
                                           const QTextTableCell &cell,
979
                                           int r, int c,
980
                                           const int *selectedTableCells)
981
0
{
982
0
    for (int i = 0; i < cell_context.selections.size(); ++i) {
983
0
        int row_start = selectedTableCells[i * 4];
984
0
        int col_start = selectedTableCells[i * 4 + 1];
985
0
        int num_rows = selectedTableCells[i * 4 + 2];
986
0
        int num_cols = selectedTableCells[i * 4 + 3];
987
988
0
        if (row_start != -1) {
989
0
            if (r >= row_start && r < row_start + num_rows
990
0
                && c >= col_start && c < col_start + num_cols)
991
0
            {
992
0
                int firstPosition = cell.firstPosition();
993
0
                int lastPosition = cell.lastPosition();
994
995
                // make sure empty cells are still selected
996
0
                if (firstPosition == lastPosition)
997
0
                    ++lastPosition;
998
999
0
                cell_context.selections[i].cursor.setPosition(firstPosition);
1000
0
                cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
1001
0
            } else {
1002
0
                cell_context.selections[i].cursor.clearSelection();
1003
0
            }
1004
0
        }
1005
1006
        // FullWidthSelection is not useful for tables
1007
0
        cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
1008
0
    }
1009
0
}
1010
1011
static bool cellClipTest(QTextTable *table, QTextTableData *td,
1012
                         const QAbstractTextDocumentLayout::PaintContext &cell_context,
1013
                         const QTextTableCell &cell,
1014
                         QRectF cellRect)
1015
0
{
1016
#ifdef QT_NO_CSSPARSER
1017
    Q_UNUSED(table);
1018
    Q_UNUSED(cell);
1019
#endif
1020
1021
0
    if (!cell_context.clip.isValid())
1022
0
        return false;
1023
1024
0
    if (td->borderCollapse) {
1025
        // we need to account for the cell borders in the clipping test
1026
0
#ifndef QT_NO_CSSPARSER
1027
0
        cellRect.adjust(-axisEdgeData(table, td, cell, QCss::LeftEdge).width / 2,
1028
0
                        -axisEdgeData(table, td, cell, QCss::TopEdge).width / 2,
1029
0
                        axisEdgeData(table, td, cell, QCss::RightEdge).width / 2,
1030
0
                        axisEdgeData(table, td, cell, QCss::BottomEdge).width / 2);
1031
0
#endif
1032
0
    } else {
1033
0
        qreal border = td->border.toReal();
1034
0
        cellRect.adjust(-border, -border, border, border);
1035
0
    }
1036
1037
0
    if (!cellRect.intersects(cell_context.clip))
1038
0
        return true;
1039
1040
0
    return false;
1041
0
}
1042
1043
void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
1044
                                           const QAbstractTextDocumentLayout::PaintContext &context,
1045
                                           QTextFrame *frame) const
1046
17.4k
{
1047
17.4k
    QTextFrameData *fd = data(frame);
1048
    // #######
1049
17.4k
    if (fd->layoutDirty)
1050
0
        return;
1051
17.4k
    Q_ASSERT(!fd->sizeDirty);
1052
17.4k
    Q_ASSERT(!fd->layoutDirty);
1053
1054
    // floor the offset to avoid painting artefacts when drawing adjacent borders
1055
    // we later also round table cell heights and widths
1056
17.4k
    const QPointF off = QPointF(QPointF(offset + fd->position.toPointF()).toPoint());
1057
1058
17.4k
    if (context.clip.isValid()
1059
17.4k
        && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
1060
17.4k
            || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
1061
0
        return;
1062
1063
17.4k
     qCDebug(lcDraw) << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
1064
1065
    // if the cursor is /on/ a table border we may need to repaint it
1066
    // afterwards, as we usually draw the decoration first
1067
17.4k
    QTextBlock cursorBlockNeedingRepaint;
1068
17.4k
    QPointF offsetOfRepaintedCursorBlock = off;
1069
1070
17.4k
    QTextTable *table = qobject_cast<QTextTable *>(frame);
1071
17.4k
    const QRectF frameRect(off, fd->size.toSizeF());
1072
1073
17.4k
    if (table) {
1074
0
        const int rows = table->rows();
1075
0
        const int columns = table->columns();
1076
0
        QTextTableData *td = static_cast<QTextTableData *>(data(table));
1077
1078
0
        QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
1079
0
        for (int i = 0; i < context.selections.size(); ++i) {
1080
0
            const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
1081
0
            int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
1082
1083
0
            if (s.cursor.currentTable() == table)
1084
0
                s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
1085
1086
0
            selectedTableCells[i * 4] = row_start;
1087
0
            selectedTableCells[i * 4 + 1] = col_start;
1088
0
            selectedTableCells[i * 4 + 2] = num_rows;
1089
0
            selectedTableCells[i * 4 + 3] = num_cols;
1090
0
        }
1091
1092
0
        QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1093
0
        if (pageHeight <= 0)
1094
0
            pageHeight = QFIXED_MAX;
1095
1096
0
        QFixed absYPos = td->position.y;
1097
0
        QTextFrame *parentFrame = table->parentFrame();
1098
0
        while (parentFrame) {
1099
0
            absYPos += data(parentFrame)->position.y;
1100
0
            parentFrame = parentFrame->parentFrame();
1101
0
        }
1102
0
        const int tableStartPage = (absYPos / pageHeight).truncate();
1103
0
        const int tableEndPage = ((absYPos + td->size.height) / pageHeight).truncate();
1104
1105
        // for borderCollapse draw frame decoration by drawing the outermost
1106
        // cell edges with width = td->border
1107
0
        if (!td->borderCollapse)
1108
0
            drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1109
1110
        // draw the repeated table headers for table continuation after page breaks
1111
0
        const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1112
0
        int page = tableStartPage + 1;
1113
0
        while (page <= tableEndPage) {
1114
0
            const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
1115
0
            const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
1116
0
            for (int r = 0; r < headerRowCount; ++r) {
1117
0
                for (int c = 0; c < columns; ++c) {
1118
0
                    QTextTableCell cell = table->cellAt(r, c);
1119
0
                    QAbstractTextDocumentLayout::PaintContext cell_context = context;
1120
0
                    adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1121
0
                    QRectF cellRect = td->cellRect(cell);
1122
1123
0
                    cellRect.translate(off.x(), headerOffset);
1124
0
                    if (cellClipTest(table, td, cell_context, cell, cellRect))
1125
0
                        continue;
1126
1127
0
                    drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1128
0
                                  &offsetOfRepaintedCursorBlock);
1129
0
                }
1130
0
            }
1131
0
            ++page;
1132
0
        }
1133
1134
0
        int firstRow = 0;
1135
0
        int lastRow = rows;
1136
1137
0
        if (context.clip.isValid()) {
1138
0
            auto rowIt = std::lower_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
1139
0
            if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
1140
0
                --rowIt;
1141
0
                firstRow = rowIt - td->rowPositions.constBegin();
1142
0
            }
1143
1144
0
            rowIt = std::upper_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
1145
0
            if (rowIt != td->rowPositions.constEnd()) {
1146
0
                ++rowIt;
1147
0
                lastRow = rowIt - td->rowPositions.constBegin();
1148
0
            }
1149
0
        }
1150
1151
0
        for (int c = 0; c < columns; ++c) {
1152
0
            QTextTableCell cell = table->cellAt(firstRow, c);
1153
0
            firstRow = qMin(firstRow, cell.row());
1154
0
        }
1155
1156
0
        for (int r = firstRow; r < lastRow; ++r) {
1157
0
            for (int c = 0; c < columns; ++c) {
1158
0
                QTextTableCell cell = table->cellAt(r, c);
1159
0
                QAbstractTextDocumentLayout::PaintContext cell_context = context;
1160
0
                adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1161
0
                QRectF cellRect = td->cellRect(cell);
1162
1163
0
                cellRect.translate(off);
1164
0
                if (cellClipTest(table, td, cell_context, cell, cellRect))
1165
0
                    continue;
1166
1167
0
                drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1168
0
                              &offsetOfRepaintedCursorBlock);
1169
0
            }
1170
0
        }
1171
1172
17.4k
    } else {
1173
17.4k
        drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1174
1175
17.4k
        QTextFrame::Iterator it = frame->begin();
1176
1177
17.4k
        if (frame == docPrivate->rootFrame())
1178
17.4k
            it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
1179
1180
17.4k
        QList<QTextFrame *> floats;
1181
17.4k
        const int numFloats = fd->floats.size();
1182
17.4k
        floats.reserve(numFloats);
1183
17.4k
        for (int i = 0; i < numFloats; ++i)
1184
0
            floats.append(fd->floats.at(i));
1185
1186
17.4k
        drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
1187
17.4k
    }
1188
1189
17.4k
    if (cursorBlockNeedingRepaint.isValid()) {
1190
0
        const QPen oldPen = painter->pen();
1191
0
        painter->setPen(context.palette.color(QPalette::Text));
1192
0
        const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
1193
0
        cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
1194
0
                                                       cursorPos, cursorWidth);
1195
0
        painter->setPen(oldPen);
1196
0
    }
1197
1198
17.4k
    return;
1199
17.4k
}
1200
1201
#ifndef QT_NO_CSSPARSER
1202
1203
static inline QTextFormat::Property borderPropertyForEdge(QCss::Edge edge)
1204
0
{
1205
0
    switch (edge) {
1206
0
    case QCss::TopEdge:
1207
0
        return QTextFormat::TableCellTopBorder;
1208
0
    case QCss::BottomEdge:
1209
0
        return QTextFormat::TableCellBottomBorder;
1210
0
    case QCss::LeftEdge:
1211
0
        return QTextFormat::TableCellLeftBorder;
1212
0
    case QCss::RightEdge:
1213
0
        return QTextFormat::TableCellRightBorder;
1214
0
    default:
1215
0
        Q_UNREACHABLE_RETURN(QTextFormat::UserProperty);
1216
0
    }
1217
0
}
1218
1219
static inline QTextFormat::Property borderStylePropertyForEdge(QCss::Edge edge)
1220
0
{
1221
0
    switch (edge) {
1222
0
    case QCss::TopEdge:
1223
0
        return QTextFormat::TableCellTopBorderStyle;
1224
0
    case QCss::BottomEdge:
1225
0
        return QTextFormat::TableCellBottomBorderStyle;
1226
0
    case QCss::LeftEdge:
1227
0
        return QTextFormat::TableCellLeftBorderStyle;
1228
0
    case QCss::RightEdge:
1229
0
        return QTextFormat::TableCellRightBorderStyle;
1230
0
    default:
1231
0
        Q_UNREACHABLE_RETURN(QTextFormat::UserProperty);
1232
0
    }
1233
0
}
1234
1235
static inline QCss::Edge adjacentEdge(QCss::Edge edge)
1236
0
{
1237
0
    switch (edge) {
1238
0
    case QCss::TopEdge:
1239
0
        return QCss::BottomEdge;
1240
0
    case QCss::RightEdge:
1241
0
        return QCss::LeftEdge;
1242
0
    case QCss::BottomEdge:
1243
0
        return QCss::TopEdge;
1244
0
    case QCss::LeftEdge:
1245
0
        return QCss::RightEdge;
1246
0
    default:
1247
0
        Q_UNREACHABLE_RETURN(QCss::NumEdges);
1248
0
    }
1249
0
}
1250
1251
static inline bool isSameAxis(QCss::Edge e1, QCss::Edge e2)
1252
0
{
1253
0
    return e1 == e2 || e1 == adjacentEdge(e2);
1254
0
}
1255
1256
static inline bool isVerticalAxis(QCss::Edge e)
1257
0
{
1258
0
    return e % 2 > 0;
1259
0
}
1260
1261
static inline QTextTableCell adjacentCell(QTextTable *table, const QTextTableCell &cell,
1262
                                          QCss::Edge edge)
1263
0
{
1264
0
    int dc = 0;
1265
0
    int dr = 0;
1266
1267
0
    switch (edge) {
1268
0
    case QCss::LeftEdge:
1269
0
        dc = -1;
1270
0
        break;
1271
0
    case QCss::RightEdge:
1272
0
        dc = cell.columnSpan();
1273
0
        break;
1274
0
    case QCss::TopEdge:
1275
0
        dr = -1;
1276
0
        break;
1277
0
    case QCss::BottomEdge:
1278
0
        dr = cell.rowSpan();
1279
0
        break;
1280
0
    default:
1281
0
        Q_UNREACHABLE();
1282
0
        break;
1283
0
    }
1284
1285
    // get sibling cell
1286
0
    int col = cell.column() + dc;
1287
0
    int row = cell.row() + dr;
1288
1289
0
    if (col < 0 || row < 0 || col >= table->columns() || row >= table->rows())
1290
0
        return QTextTableCell();
1291
0
    else
1292
0
        return table->cellAt(cell.row() + dr, cell.column() + dc);
1293
0
}
1294
1295
// returns true if the specified edges of both cells
1296
// are "one the same line" aka axis.
1297
//
1298
// | C0
1299
// |-----|-----|----|-----  < "axis"
1300
// | C1  | C2  | C3 | C4
1301
//
1302
// cell    edge    competingCell competingEdge  result
1303
// C0      Left    C1            Left           true
1304
// C0      Left    C2            Left           false
1305
// C0      Bottom  C2            Top            true
1306
// C0      Bottom  C4            Left           INVALID
1307
static inline bool sharesAxis(const QTextTableCell &cell, QCss::Edge edge,
1308
                              const QTextTableCell &competingCell, QCss::Edge competingCellEdge)
1309
0
{
1310
0
    Q_ASSERT(isVerticalAxis(edge) == isVerticalAxis(competingCellEdge));
1311
1312
0
    switch (edge) {
1313
0
    case QCss::TopEdge:
1314
0
        return cell.row() ==
1315
0
                competingCell.row() + (competingCellEdge == QCss::BottomEdge ? competingCell.rowSpan() : 0);
1316
0
    case QCss::BottomEdge:
1317
0
        return cell.row() + cell.rowSpan() ==
1318
0
                competingCell.row() + (competingCellEdge == QCss::TopEdge ? 0 : competingCell.rowSpan());
1319
0
    case QCss::LeftEdge:
1320
0
        return cell.column() ==
1321
0
                competingCell.column() + (competingCellEdge == QCss::RightEdge ? competingCell.columnSpan() : 0);
1322
0
    case QCss::RightEdge:
1323
0
        return cell.column() + cell.columnSpan() ==
1324
0
                competingCell.column() + (competingCellEdge == QCss::LeftEdge ? 0 : competingCell.columnSpan());
1325
0
    default:
1326
0
        Q_UNREACHABLE_RETURN(false);
1327
0
    }
1328
0
}
1329
1330
// returns the applicable EdgeData for the given cell and edge.
1331
// this is either set explicitly by the cell's format, an activated grid
1332
// or the general table border width for outermost edges.
1333
static inline EdgeData cellEdgeData(QTextTable *table, const QTextTableData *td,
1334
                                    const QTextTableCell &cell, QCss::Edge edge)
1335
0
{
1336
0
    if (!cell.isValid()) {
1337
        // e.g. non-existing adjacent cell
1338
0
        return EdgeData();
1339
0
    }
1340
1341
0
    QTextTableCellFormat f = cell.format().toTableCellFormat();
1342
0
    if (f.hasProperty(borderStylePropertyForEdge(edge))) {
1343
        // border style is set
1344
0
        double width = 3; // default to 3 like browsers do
1345
0
        if (f.hasProperty(borderPropertyForEdge(edge)))
1346
0
            width = f.property(borderPropertyForEdge(edge)).toDouble();
1347
0
        return EdgeData(width, cell, edge, EdgeData::ClassExplicit);
1348
0
    } else if (td->drawGrid) {
1349
0
        const bool outermost =
1350
0
                (edge == QCss::LeftEdge && cell.column() == 0) ||
1351
0
                (edge == QCss::TopEdge && cell.row() == 0) ||
1352
0
                (edge == QCss::RightEdge && cell.column() + cell.columnSpan() >= table->columns()) ||
1353
0
                (edge == QCss::BottomEdge && cell.row() + cell.rowSpan() >= table->rows());
1354
1355
0
        if (outermost) {
1356
0
            qreal border = table->format().border();
1357
0
            if (border > 1.0) {
1358
                // table border
1359
0
                return EdgeData(border, cell, edge, EdgeData::ClassTableBorder);
1360
0
            }
1361
0
        }
1362
        // 1px clean grid
1363
0
        return EdgeData(1.0, cell, edge, EdgeData::ClassGrid);
1364
0
    }
1365
0
    else {
1366
0
        return EdgeData(0, cell, edge, EdgeData::ClassNone);
1367
0
    }
1368
0
}
1369
1370
// returns the EdgeData with the larger width of either the cell's edge its adjacent cell's edge
1371
static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td,
1372
                                    const QTextTableCell &cell, QCss::Edge edge)
1373
0
{
1374
0
    Q_ASSERT(cell.isValid());
1375
1376
0
    EdgeData result = cellEdgeData(table, td, cell, edge);
1377
0
    if (!td->borderCollapse)
1378
0
        return result;
1379
1380
0
    QTextTableCell ac = adjacentCell(table, cell, edge);
1381
0
    result = qMax(result, cellEdgeData(table, td, ac, adjacentEdge(edge)));
1382
1383
0
    bool mustCheckThirdCell = false;
1384
0
    if (ac.isValid()) {
1385
        /* if C0 and C3 don't share the left/top axis, we must
1386
         * also check C1.
1387
         *
1388
         * C0 and C4 don't share the left axis so we have
1389
         * to take the top edge of C1 (T1) into account
1390
         * because this might be wider than C0's bottom
1391
         * edge (B0). For the sake of simplicity we skip
1392
         * checking T2 and T3.
1393
         *
1394
         * | C0
1395
         * |-----|-----|----|-----
1396
         * | C1  | C2  | C3 | C4
1397
         *
1398
         * width(T4) = max(T4, B0, T1) (T2 and T3 won't be checked)
1399
         */
1400
0
        switch (edge) {
1401
0
        case QCss::TopEdge:
1402
0
        case QCss::BottomEdge:
1403
0
            mustCheckThirdCell = !sharesAxis(cell, QCss::LeftEdge, ac, QCss::LeftEdge);
1404
0
            break;
1405
0
        case QCss::LeftEdge:
1406
0
        case QCss::RightEdge:
1407
0
            mustCheckThirdCell = !sharesAxis(cell, QCss::TopEdge, ac, QCss::TopEdge);
1408
0
            break;
1409
0
        default:
1410
0
            Q_UNREACHABLE();
1411
0
            break;
1412
0
        }
1413
0
    }
1414
1415
0
    if (mustCheckThirdCell)
1416
0
        result = qMax(result, cellEdgeData(table, td, adjacentCell(table, ac, adjacentEdge(edge)), edge));
1417
1418
0
    return result;
1419
0
}
1420
1421
// checks an edge's joined competing edge according to priority rules and
1422
// adjusts maxCompetingEdgeData and maxOrthogonalEdgeData
1423
static inline void checkJoinedEdge(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
1424
                                   QCss::Edge competingEdge,
1425
                                   const EdgeData &edgeData,
1426
                                   bool couldHaveContinuation,
1427
                                   EdgeData *maxCompetingEdgeData,
1428
                                   EdgeData *maxOrthogonalEdgeData)
1429
0
{
1430
0
    EdgeData competingEdgeData = axisEdgeData(table, td, cell, competingEdge);
1431
1432
0
    if (competingEdgeData > edgeData) {
1433
0
        *maxCompetingEdgeData = competingEdgeData;
1434
0
    } else if (competingEdgeData.width == edgeData.width) {
1435
0
        if ((isSameAxis(edgeData.edge, competingEdge) && couldHaveContinuation)
1436
0
                || (!isVerticalAxis(edgeData.edge) && isVerticalAxis(competingEdge)) /* both widths are equal, vertical edge has priority */ ) {
1437
0
            *maxCompetingEdgeData = competingEdgeData;
1438
0
        }
1439
0
    }
1440
1441
0
    if (maxOrthogonalEdgeData && competingEdgeData.width > maxOrthogonalEdgeData->width)
1442
0
        *maxOrthogonalEdgeData = competingEdgeData;
1443
0
}
1444
1445
// the offset to make adjacent edges overlap in border collapse mode
1446
static inline qreal collapseOffset(const QTextDocumentLayoutPrivate *p, const EdgeData &w)
1447
0
{
1448
0
    return p->scaleToDevice(w.width) / 2.0;
1449
0
}
1450
1451
// returns the offset that must be applied to the edge's
1452
// anchor (start point or end point) to avoid overlapping edges.
1453
//
1454
// Example 1:
1455
//       2
1456
//       2
1457
// 11111144444444      4 = top edge of cell, 4 pixels width
1458
//       3             3 = right edge of cell, 3 pixels width
1459
//       3 cell 4
1460
//
1461
// cell 4's top border is the widest border and will be
1462
// drawn with horiz. offset = -3/2 whereas its left border
1463
// of width 3 will be drawn with vert. offset = +4/2.
1464
//
1465
// Example 2:
1466
//       2
1467
//       2
1468
// 11111143333333
1469
//       4
1470
//       4 cell 4
1471
//
1472
// cell 4's left border is the widest and will be drawn
1473
// with vert. offset = -3/2 whereas its top border
1474
// of of width 3 will be drawn with hor. offset = +4/2.
1475
//
1476
// couldHaveContinuation: true for "end" anchor of an edge:
1477
//      C
1478
// AAAAABBBBBB
1479
//      D
1480
// width(A) == width(B) we consider B to be a continuation of A, so that B wins
1481
// and will be painted. A would only be painted including the right anchor if
1482
// there was no edge B (due to a rowspan or the axis C-D being the table's right
1483
// border).
1484
//
1485
// ignoreEdgesAbove: true if an edge (left, right or top) for the first row
1486
// after a table page break should be painted. In this case the edges of the
1487
// row above must be ignored.
1488
static inline double prioritizedEdgeAnchorOffset(const QTextDocumentLayoutPrivate *p,
1489
                                                 QTextTable *table, const QTextTableData *td,
1490
                                                 const QTextTableCell &cell,
1491
                                                 const EdgeData &edgeData,
1492
                                                 QCss::Edge orthogonalEdge,
1493
                                                 bool couldHaveContinuation,
1494
                                                 bool ignoreEdgesAbove)
1495
0
{
1496
0
    EdgeData maxCompetingEdgeData;
1497
0
    EdgeData maxOrthogonalEdgeData;
1498
0
    QTextTableCell competingCell;
1499
1500
    // reference scenario for the inline comments:
1501
    // - edgeData being the top "T0" edge of C0
1502
    // - right anchor is '+', orthogonal edge is "R0"
1503
    //   B C3 R|L C2 B
1504
    //   ------+------
1505
    //   T C0 R|L C1 T
1506
1507
    // C0: T0/B3
1508
    // this is "edgeData"
1509
1510
    // C0: R0/L1
1511
0
    checkJoinedEdge(table, td, cell, orthogonalEdge, edgeData, false,
1512
0
                    &maxCompetingEdgeData, &maxOrthogonalEdgeData);
1513
1514
0
    if (td->borderCollapse) {
1515
        // C1: T1/B2
1516
0
        if (!isVerticalAxis(edgeData.edge) || !ignoreEdgesAbove) {
1517
0
            competingCell = adjacentCell(table, cell, orthogonalEdge);
1518
0
            if (competingCell.isValid()) {
1519
0
                checkJoinedEdge(table, td, competingCell, edgeData.edge, edgeData, couldHaveContinuation,
1520
0
                                &maxCompetingEdgeData, nullptr);
1521
0
            }
1522
0
        }
1523
1524
        // C3: R3/L2
1525
0
        if (edgeData.edge != QCss::TopEdge || !ignoreEdgesAbove) {
1526
0
            competingCell = adjacentCell(table, cell, edgeData.edge);
1527
0
            if (competingCell.isValid() && sharesAxis(cell, orthogonalEdge, competingCell, orthogonalEdge)) {
1528
0
                checkJoinedEdge(table, td, competingCell, orthogonalEdge, edgeData, false,
1529
0
                                &maxCompetingEdgeData, &maxOrthogonalEdgeData);
1530
0
            }
1531
0
        }
1532
0
    }
1533
1534
    // wider edge has priority
1535
0
    bool hasPriority = edgeData > maxCompetingEdgeData;
1536
1537
0
    if (td->borderCollapse) {
1538
0
        qreal offset = collapseOffset(p, maxOrthogonalEdgeData);
1539
0
        return hasPriority ? -offset : offset;
1540
0
    }
1541
0
    else
1542
0
        return hasPriority ? 0 : p->scaleToDevice(maxOrthogonalEdgeData.width);
1543
0
}
1544
1545
// draw one edge of the given cell
1546
//
1547
// these options are for pagination / pagebreak handling:
1548
//
1549
// forceHeaderRow: true for all rows directly below a (repeated) header row.
1550
//  if the table has headers the first row after a page break must check against
1551
//  the last table header's row, not its actual predecessor.
1552
//
1553
// adjustTopAnchor: false for rows that are a continuation of a row after a page break
1554
//   only evaluated for left/right edges
1555
//
1556
// adjustBottomAnchor: false for rows that will continue after a page break
1557
//   only evaluated for left/right edges
1558
//
1559
// ignoreEdgesAbove: true if a row starts on top of the page and the
1560
//   bottom edges of the prior row can therefore be ignored.
1561
static inline
1562
void drawCellBorder(const QTextDocumentLayoutPrivate *p, QPainter *painter,
1563
                    QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
1564
                    const QRectF &borderRect, QCss::Edge edge,
1565
                    int forceHeaderRow, bool adjustTopAnchor, bool adjustBottomAnchor,
1566
                    bool ignoreEdgesAbove)
1567
0
{
1568
0
    QPointF p1, p2;
1569
0
    qreal wh = 0;
1570
0
    qreal wv = 0;
1571
0
    EdgeData edgeData = axisEdgeData(table, td, cell, edge);
1572
1573
0
    if (edgeData.width == 0)
1574
0
        return;
1575
1576
0
    QTextTableCellFormat fmt = edgeData.cell.format().toTableCellFormat();
1577
0
    QTextFrameFormat::BorderStyle borderStyle = QTextFrameFormat::BorderStyle_None;
1578
0
    QBrush brush;
1579
1580
0
    if (edgeData.edgeClass != EdgeData::ClassExplicit && td->drawGrid) {
1581
0
        borderStyle = table->format().borderStyle();
1582
0
        brush = table->format().borderBrush();
1583
0
    }
1584
0
    else {
1585
0
        switch (edgeData.edge) {
1586
0
        case QCss::TopEdge:
1587
0
            brush = fmt.topBorderBrush();
1588
0
            borderStyle = fmt.topBorderStyle();
1589
0
            break;
1590
0
        case QCss::BottomEdge:
1591
0
            brush = fmt.bottomBorderBrush();
1592
0
            borderStyle = fmt.bottomBorderStyle();
1593
0
            break;
1594
0
        case QCss::LeftEdge:
1595
0
            brush = fmt.leftBorderBrush();
1596
0
            borderStyle = fmt.leftBorderStyle();
1597
0
            break;
1598
0
        case QCss::RightEdge:
1599
0
            brush = fmt.rightBorderBrush();
1600
0
            borderStyle = fmt.rightBorderStyle();
1601
0
            break;
1602
0
        default:
1603
0
            Q_UNREACHABLE();
1604
0
            break;
1605
0
        }
1606
0
    }
1607
1608
0
    if (borderStyle == QTextFrameFormat::BorderStyle_None)
1609
0
        return;
1610
1611
    // assume black if not explicit brush is set
1612
0
    if (brush.style() == Qt::NoBrush)
1613
0
        brush = Qt::black;
1614
1615
0
    QTextTableCell cellOrHeader = cell;
1616
0
    if (forceHeaderRow != -1)
1617
0
        cellOrHeader = table->cellAt(forceHeaderRow, cell.column());
1618
1619
    // adjust start and end anchors (e.g. left/right for top) according to priority rules
1620
0
    switch (edge) {
1621
0
    case QCss::TopEdge:
1622
0
        wv = p->scaleToDevice(edgeData.width);
1623
0
        p1 = borderRect.topLeft()
1624
0
                + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, ignoreEdgesAbove)), 0);
1625
0
        p2 = borderRect.topRight()
1626
0
                + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, ignoreEdgesAbove)), 0);
1627
0
        break;
1628
0
    case QCss::BottomEdge:
1629
0
        wv = p->scaleToDevice(edgeData.width);
1630
0
        p1 = borderRect.bottomLeft()
1631
0
                + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, false)), -wv);
1632
0
        p2 = borderRect.bottomRight()
1633
0
                + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, false)), -wv);
1634
0
        break;
1635
0
    case QCss::LeftEdge:
1636
0
        wh = p->scaleToDevice(edgeData.width);
1637
0
        p1 = borderRect.topLeft()
1638
0
                + QPointF(0, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
1639
0
                                                                                  forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
1640
0
                                                                                  false, ignoreEdgesAbove))
1641
0
                                             : 0);
1642
0
        p2 = borderRect.bottomLeft()
1643
0
                + QPointF(0, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
1644
0
                                                : 0);
1645
0
        break;
1646
0
    case QCss::RightEdge:
1647
0
        wh = p->scaleToDevice(edgeData.width);
1648
0
        p1 = borderRect.topRight()
1649
0
                + QPointF(-wh, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
1650
0
                                                                                    forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
1651
0
                                                                                    false, ignoreEdgesAbove))
1652
0
                                               : 0);
1653
0
        p2 = borderRect.bottomRight()
1654
0
                + QPointF(-wh, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
1655
0
                                                  : 0);
1656
0
        break;
1657
0
    default: break;
1658
0
    }
1659
1660
    // for borderCollapse move edge width/2 pixel out of the borderRect
1661
    // so that it shares space with the adjacent cell's edge.
1662
    // to avoid fractional offsets, qCeil/qFloor is used
1663
0
    if (td->borderCollapse) {
1664
0
        QPointF offset;
1665
0
        switch (edge) {
1666
0
        case QCss::TopEdge:
1667
0
            offset = QPointF(0, -qCeil(collapseOffset(p, edgeData)));
1668
0
            break;
1669
0
        case QCss::BottomEdge:
1670
0
            offset = QPointF(0, qFloor(collapseOffset(p, edgeData)));
1671
0
            break;
1672
0
        case QCss::LeftEdge:
1673
0
            offset = QPointF(-qCeil(collapseOffset(p, edgeData)), 0);
1674
0
            break;
1675
0
        case QCss::RightEdge:
1676
0
            offset = QPointF(qFloor(collapseOffset(p, edgeData)), 0);
1677
0
            break;
1678
0
        default: break;
1679
0
        }
1680
0
        p1 += offset;
1681
0
        p2 += offset;
1682
0
    }
1683
1684
0
    QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(borderStyle + 1);
1685
1686
// this reveals errors in the drawing logic
1687
#ifdef COLLAPSE_DEBUG
1688
    QColor c = brush.color();
1689
    c.setAlpha(150);
1690
    brush.setColor(c);
1691
#endif
1692
1693
0
    qDrawEdge(painter, p1.x(), p1.y(), p2.x() + wh, p2.y() + wv, 0, 0, edge, cssStyle, brush);
1694
0
}
1695
#endif
1696
1697
void QTextDocumentLayoutPrivate::drawTableCellBorder(const QRectF &cellRect, QPainter *painter,
1698
                                                     QTextTable *table, QTextTableData *td,
1699
                                                     const QTextTableCell &cell) const
1700
0
{
1701
0
#ifndef QT_NO_CSSPARSER
1702
0
    qreal topMarginAfterPageBreak = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1703
0
    qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1704
1705
0
    const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1706
0
    if (headerRowCount > 0 && cell.row() >= headerRowCount)
1707
0
        topMarginAfterPageBreak += td->headerHeight.toReal();
1708
1709
0
    BorderPaginator paginator(document, cellRect, topMarginAfterPageBreak, bottomMargin, 0);
1710
1711
0
    bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
1712
0
    painter->setRenderHint(QPainter::Antialiasing);
1713
1714
    // paint cell borders for every page the cell appears on
1715
0
    for (int page = paginator.topPage; page <= paginator.bottomPage; ++page) {
1716
0
        const QRectF clipped = paginator.clipRect(page);
1717
0
        if (!clipped.isValid())
1718
0
            continue;
1719
1720
0
        const qreal offset = cellRect.top() - td->rowPositions.at(cell.row()).toReal();
1721
0
        const int lastHeaderRow = table->format().headerRowCount() - 1;
1722
0
        const bool tableHasHeader = table->format().headerRowCount() > 0;
1723
0
        const bool isHeaderRow = cell.row() < table->format().headerRowCount();
1724
0
        const bool isFirstRow = cell.row() == lastHeaderRow + 1;
1725
0
        const bool isLastRow = cell.row() + cell.rowSpan() >= table->rows();
1726
0
        const bool previousRowOnPreviousPage = !isFirstRow
1727
0
                && !isHeaderRow
1728
0
                && BorderPaginator(document,
1729
0
                                   td->cellRect(adjacentCell(table, cell, QCss::TopEdge)).translated(0, offset),
1730
0
                                   topMarginAfterPageBreak,
1731
0
                                   bottomMargin,
1732
0
                                   0).bottomPage < page;
1733
0
        const bool nextRowOnNextPage = !isLastRow
1734
0
                && BorderPaginator(document,
1735
0
                                   td->cellRect(adjacentCell(table, cell, QCss::BottomEdge)).translated(0, offset),
1736
0
                                   topMarginAfterPageBreak,
1737
0
                                   bottomMargin,
1738
0
                                   0).topPage > page;
1739
0
        const bool rowStartsOnPage = page == paginator.topPage;
1740
0
        const bool rowEndsOnPage = page == paginator.bottomPage;
1741
0
        const bool rowStartsOnPageTop = !tableHasHeader
1742
0
                && rowStartsOnPage
1743
0
                && previousRowOnPreviousPage;
1744
0
        const bool rowStartsOnPageBelowHeader = tableHasHeader
1745
0
                && rowStartsOnPage
1746
0
                && previousRowOnPreviousPage;
1747
1748
0
        const bool suppressTopBorder = td->borderCollapse
1749
0
                ? !isHeaderRow && (!rowStartsOnPage || rowStartsOnPageBelowHeader)
1750
0
                : !rowStartsOnPage;
1751
0
        const bool suppressBottomBorder = td->borderCollapse
1752
0
                ? !isHeaderRow && (!rowEndsOnPage || nextRowOnNextPage)
1753
0
                : !rowEndsOnPage;
1754
0
        const bool doNotAdjustTopAnchor = td->borderCollapse
1755
0
                ? !tableHasHeader && !rowStartsOnPage
1756
0
                : !rowStartsOnPage;
1757
0
        const bool doNotAdjustBottomAnchor = suppressBottomBorder;
1758
1759
0
        if (!suppressTopBorder) {
1760
0
            drawCellBorder(this, painter, table, td, cell, clipped, QCss::TopEdge,
1761
0
                           -1, true, true, rowStartsOnPageTop);
1762
0
        }
1763
1764
0
        drawCellBorder(this, painter, table, td, cell, clipped, QCss::LeftEdge,
1765
0
                       suppressTopBorder ? lastHeaderRow : -1,
1766
0
                       !doNotAdjustTopAnchor,
1767
0
                       !doNotAdjustBottomAnchor,
1768
0
                       rowStartsOnPageTop);
1769
0
        drawCellBorder(this, painter, table, td, cell, clipped, QCss::RightEdge,
1770
0
                       suppressTopBorder ? lastHeaderRow : -1,
1771
0
                       !doNotAdjustTopAnchor,
1772
0
                       !doNotAdjustBottomAnchor,
1773
0
                       rowStartsOnPageTop);
1774
1775
0
        if (!suppressBottomBorder) {
1776
0
            drawCellBorder(this, painter, table, td, cell, clipped, QCss::BottomEdge,
1777
0
                           -1, true, true, false);
1778
0
        }
1779
0
    }
1780
1781
0
    if (turn_off_antialiasing)
1782
0
        painter->setRenderHint(QPainter::Antialiasing, false);
1783
#else
1784
    Q_UNUSED(cell);
1785
    Q_UNUSED(cellRect);
1786
    Q_UNUSED(painter);
1787
    Q_UNUSED(table);
1788
    Q_UNUSED(td);
1789
    Q_UNUSED(cell);
1790
#endif
1791
0
}
1792
1793
void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
1794
                                               QTextTable *table, QTextTableData *td, int r, int c,
1795
                                               QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
1796
0
{
1797
0
    QTextTableCell cell = table->cellAt(r, c);
1798
0
    int rspan = cell.rowSpan();
1799
0
    int cspan = cell.columnSpan();
1800
0
    if (rspan != 1) {
1801
0
        int cr = cell.row();
1802
0
        if (cr != r)
1803
0
            return;
1804
0
    }
1805
0
    if (cspan != 1) {
1806
0
        int cc = cell.column();
1807
0
        if (cc != c)
1808
0
            return;
1809
0
    }
1810
1811
0
    const QFixed leftPadding = td->leftPadding(table, cell);
1812
0
    const QFixed topPadding = td->topPadding(table, cell);
1813
1814
0
    qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1815
0
    qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1816
1817
0
    const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1818
0
    if (r >= headerRowCount)
1819
0
        topMargin += td->headerHeight.toReal();
1820
1821
    // If cell border configured, don't draw default border for cells. It will be taken care later by
1822
    // drawTableCellBorder().
1823
0
    bool cellBorderConfigured = (cell.format().hasProperty(QTextFormat::TableCellLeftBorder) ||
1824
0
                                 cell.format().hasProperty(QTextFormat::TableCellTopBorder) ||
1825
0
                                 cell.format().hasProperty(QTextFormat::TableCellRightBorder) ||
1826
0
                                 cell.format().hasProperty(QTextFormat::TableCellBottomBorder));
1827
1828
0
    if (!td->borderCollapse && td->border != 0 && !cellBorderConfigured) {
1829
0
        const QBrush oldBrush = painter->brush();
1830
0
        const QPen oldPen = painter->pen();
1831
1832
        // If border is configured for the table (and not explicitly for the cell), then
1833
        // always draw 1px border around the cell
1834
0
        const qreal border = 1;
1835
1836
0
        QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
1837
1838
        // invert the border style for cells
1839
0
        QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
1840
0
        switch (cellBorder) {
1841
0
        case QTextFrameFormat::BorderStyle_Inset:
1842
0
            cellBorder = QTextFrameFormat::BorderStyle_Outset;
1843
0
            break;
1844
0
        case QTextFrameFormat::BorderStyle_Outset:
1845
0
            cellBorder = QTextFrameFormat::BorderStyle_Inset;
1846
0
            break;
1847
0
        case QTextFrameFormat::BorderStyle_Groove:
1848
0
            cellBorder = QTextFrameFormat::BorderStyle_Ridge;
1849
0
            break;
1850
0
        case QTextFrameFormat::BorderStyle_Ridge:
1851
0
            cellBorder = QTextFrameFormat::BorderStyle_Groove;
1852
0
            break;
1853
0
        default:
1854
0
            break;
1855
0
        }
1856
1857
0
        drawBorder(painter, borderRect, topMargin, bottomMargin,
1858
0
                   border, table->format().borderBrush(), cellBorder);
1859
1860
0
        painter->setBrush(oldBrush);
1861
0
        painter->setPen(oldPen);
1862
0
    }
1863
1864
0
    const QBrush bg = cell.format().background();
1865
0
    const QPointF brushOrigin = painter->brushOriginF();
1866
0
    if (bg.style() != Qt::NoBrush) {
1867
0
        const qreal pageHeight = document->pageSize().height();
1868
0
        const int topPage = pageHeight > 0 ? static_cast<int>(cellRect.top() / pageHeight) : 0;
1869
0
        const int bottomPage = pageHeight > 0 ? static_cast<int>((cellRect.bottom()) / pageHeight) : 0;
1870
1871
0
        if (topPage == bottomPage)
1872
0
            fillBackground(painter, cellRect, bg, cellRect.topLeft());
1873
0
        else {
1874
0
            for (int i = topPage; i <= bottomPage; ++i) {
1875
0
                QRectF clipped = cellRect.toRect();
1876
1877
0
                if (topPage != bottomPage) {
1878
0
                    const qreal top = qMax(i * pageHeight + topMargin, cell_context.clip.top());
1879
0
                    const qreal bottom = qMin((i + 1) * pageHeight - bottomMargin, cell_context.clip.bottom());
1880
1881
0
                    clipped.setTop(qMax(clipped.top(), top));
1882
0
                    clipped.setBottom(qMin(clipped.bottom(), bottom));
1883
1884
0
                    if (clipped.bottom() <= clipped.top())
1885
0
                        continue;
1886
1887
0
                    fillBackground(painter, clipped, bg, cellRect.topLeft());
1888
0
                }
1889
0
            }
1890
0
        }
1891
1892
0
        if (bg.style() > Qt::SolidPattern)
1893
0
            painter->setBrushOrigin(cellRect.topLeft());
1894
0
    }
1895
1896
    // paint over the background - otherwise we would have to adjust the background paint cellRect for the border values
1897
0
    if (cellBorderConfigured || (td->borderCollapse && td->border != 0))
1898
0
        drawTableCellBorder(cellRect, painter, table, td, cell);
1899
1900
0
    const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
1901
1902
0
    const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
1903
0
                                    cellRect.top() + (topPadding + verticalOffset).toReal());
1904
1905
0
    QTextBlock repaintBlock;
1906
0
    drawFlow(cellPos, painter, cell_context, cell.begin(),
1907
0
             td->childFrameMap.values(r + c * table->rows()),
1908
0
             &repaintBlock);
1909
0
    if (repaintBlock.isValid()) {
1910
0
        *cursorBlockNeedingRepaint = repaintBlock;
1911
0
        *cursorBlockOffset = cellPos;
1912
0
    }
1913
1914
0
    if (bg.style() > Qt::SolidPattern)
1915
0
        painter->setBrushOrigin(brushOrigin);
1916
0
}
1917
1918
void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
1919
                                          QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
1920
17.4k
{
1921
17.4k
    Q_Q(const QTextDocumentLayout);
1922
17.4k
    const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == nullptr);
1923
1924
17.4k
    auto lastVisibleCheckPoint = checkPoints.end();
1925
17.4k
    if (inRootFrame && context.clip.isValid()) {
1926
17.4k
        lastVisibleCheckPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
1927
17.4k
    }
1928
1929
17.4k
    QTextBlock previousBlock;
1930
17.4k
    QTextFrame *previousFrame = nullptr;
1931
1932
282k
    for (; !it.atEnd(); ++it) {
1933
265k
        QTextFrame *c = it.currentFrame();
1934
1935
265k
        if (inRootFrame && !checkPoints.isEmpty()) {
1936
265k
            int currentPosInDoc;
1937
265k
            if (c)
1938
0
                currentPosInDoc = c->firstPosition();
1939
265k
            else
1940
265k
                currentPosInDoc = it.currentBlock().position();
1941
1942
            // if we're past what is already laid out then we're better off
1943
            // not trying to draw things that may not be positioned correctly yet
1944
265k
            if (currentPosInDoc >= checkPoints.constLast().positionInFrame)
1945
540
                break;
1946
1947
265k
            if (lastVisibleCheckPoint != checkPoints.end()
1948
247k
                && context.clip.isValid()
1949
247k
                && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
1950
265k
               )
1951
374
                break;
1952
265k
        }
1953
1954
265k
        if (c)
1955
0
            drawFrame(offset, painter, context, c);
1956
265k
        else {
1957
265k
            QAbstractTextDocumentLayout::PaintContext pc = context;
1958
265k
            if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
1959
0
                pc.selections.clear();
1960
265k
            drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
1961
265k
        }
1962
1963
        // when entering a table and the previous block is empty
1964
        // then layoutFlow 'hides' the block that just causes a
1965
        // new line by positioning it /on/ the table border. as we
1966
        // draw that block before the table itself the decoration
1967
        // 'overpaints' the cursor and we need to paint it afterwards
1968
        // again
1969
265k
        if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
1970
0
            && previousBlock.contains(context.cursorPosition)
1971
265k
           ) {
1972
0
            *cursorBlockNeedingRepaint = previousBlock;
1973
0
        }
1974
1975
265k
        previousBlock = it.currentBlock();
1976
265k
        previousFrame = c;
1977
265k
    }
1978
1979
17.4k
    for (int i = 0; i < floats.size(); ++i) {
1980
0
        QTextFrame *frame = floats.at(i);
1981
0
        if (!isFrameFromInlineObject(frame)
1982
0
            || frame->frameFormat().position() == QTextFrameFormat::InFlow)
1983
0
            continue;
1984
1985
0
        const int pos = frame->firstPosition() - 1;
1986
0
        QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
1987
0
        QTextObjectInterface *handler = q->handlerForObject(format.objectType());
1988
0
        if (handler) {
1989
0
            QRectF rect = frameBoundingRectInternal(frame);
1990
0
            handler->drawObject(painter, rect, document, pos, format);
1991
0
        }
1992
0
    }
1993
17.4k
}
1994
1995
void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
1996
                                           const QAbstractTextDocumentLayout::PaintContext &context,
1997
                                           const QTextBlock &bl, bool inRootFrame) const
1998
265k
{
1999
265k
    const QTextLayout *tl = bl.layout();
2000
265k
    QRectF r = tl->boundingRect();
2001
265k
    r.translate(offset + tl->position());
2002
265k
    if (!bl.isVisible() || (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom())))
2003
209k
        return;
2004
265k
    qCDebug(lcDraw) << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
2005
2006
55.1k
    QTextBlockFormat blockFormat = bl.blockFormat();
2007
2008
55.1k
    QBrush bg = blockFormat.background();
2009
55.1k
    if (bg != Qt::NoBrush) {
2010
0
        QRectF rect = r;
2011
2012
        // extend the background rectangle if we're in the root frame with NoWrap,
2013
        // as the rect of the text block will then be only the width of the text
2014
        // instead of the full page width
2015
0
        if (inRootFrame && document->pageSize().width() <= 0) {
2016
0
            const QTextFrameData *fd = data(document->rootFrame());
2017
0
            rect.setRight((fd->size.width - fd->rightMargin).toReal());
2018
0
        }
2019
2020
        // in the case of <hr>, the background-color CSS style fills only the rule's thickness instead of the whole line
2021
0
        if (!blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth))
2022
0
            fillBackground(painter, rect, bg, r.topLeft());
2023
0
    }
2024
2025
55.1k
    QList<QTextLayout::FormatRange> selections;
2026
55.1k
    int blpos = bl.position();
2027
55.1k
    int bllen = bl.length();
2028
55.1k
    const QTextCharFormat *selFormat = nullptr;
2029
55.1k
    for (int i = 0; i < context.selections.size(); ++i) {
2030
0
        const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
2031
0
        const int selStart = range.cursor.selectionStart() - blpos;
2032
0
        const int selEnd = range.cursor.selectionEnd() - blpos;
2033
0
        if (selStart < bllen && selEnd > 0
2034
0
             && selEnd > selStart) {
2035
0
            QTextLayout::FormatRange o;
2036
0
            o.start = selStart;
2037
0
            o.length = selEnd - selStart;
2038
0
            o.format = range.format;
2039
0
            selections.append(o);
2040
0
        } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
2041
0
                   && bl.contains(range.cursor.position())) {
2042
            // for full width selections we don't require an actual selection, just
2043
            // a position to specify the line. that's more convenience in usage.
2044
0
            QTextLayout::FormatRange o;
2045
0
            QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
2046
0
            o.start = l.textStart();
2047
0
            o.length = l.textLength();
2048
0
            if (o.start + o.length == bllen - 1)
2049
0
                ++o.length; // include newline
2050
0
            o.format = range.format;
2051
0
            selections.append(o);
2052
0
       }
2053
0
        if (selStart < 0 && selEnd >= 1)
2054
0
            selFormat = &range.format;
2055
0
    }
2056
2057
55.1k
    QTextObject *object = document->objectForFormat(bl.blockFormat());
2058
55.1k
    if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
2059
0
        drawListItem(offset, painter, context, bl, selFormat);
2060
2061
55.1k
    QPen oldPen = painter->pen();
2062
55.1k
    painter->setPen(context.palette.color(QPalette::Text));
2063
2064
55.1k
    tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
2065
2066
    // if the block is empty and it precedes a table, do not draw the cursor.
2067
    // the cursor is drawn later after the table has been drawn so no need
2068
    // to draw it here.
2069
55.1k
    if (!isEmptyBlockBeforeTable(frameIteratorForTextPosition(blpos))
2070
55.1k
        && ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
2071
55.1k
            || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty()))) {
2072
0
        int cpos = context.cursorPosition;
2073
0
        if (cpos < -1)
2074
0
            cpos = tl->preeditAreaPosition() - (cpos + 2);
2075
0
        else
2076
0
            cpos -= blpos;
2077
0
        tl->drawCursor(painter, offset, cpos, cursorWidth);
2078
0
    }
2079
2080
55.1k
    if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2081
0
        const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
2082
0
        const auto color = blockFormat.hasProperty(QTextFormat::BackgroundBrush)
2083
0
                         ? qvariant_cast<QBrush>(blockFormat.property(QTextFormat::BackgroundBrush)).color()
2084
0
                         : context.palette.color(QPalette::Inactive, QPalette::WindowText);
2085
0
        painter->setPen(color);
2086
0
        qreal y = r.bottom();
2087
0
        if (bl.length() == 1)
2088
0
            y = r.top() + r.height() / 2;
2089
2090
0
        const qreal middleX = r.left() + r.width() / 2;
2091
0
        painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
2092
0
    }
2093
2094
55.1k
    painter->setPen(oldPen);
2095
55.1k
}
2096
2097
2098
void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
2099
                                              const QAbstractTextDocumentLayout::PaintContext &context,
2100
                                              const QTextBlock &bl, const QTextCharFormat *selectionFormat) const
2101
0
{
2102
0
    Q_Q(const QTextDocumentLayout);
2103
0
    const QTextBlockFormat blockFormat = bl.blockFormat();
2104
0
    const QTextCharFormat charFormat = bl.charFormat();
2105
0
    QFont font(charFormat.font());
2106
0
    if (q->paintDevice())
2107
0
        font = QFont(font, q->paintDevice());
2108
2109
0
    const QFontMetrics fontMetrics(font);
2110
0
    QTextObject * const object = document->objectForFormat(blockFormat);
2111
0
    const QTextListFormat lf = object->format().toListFormat();
2112
0
    int style = lf.style();
2113
0
    QString itemText;
2114
0
    QSizeF size;
2115
2116
0
    if (blockFormat.hasProperty(QTextFormat::ListStyle))
2117
0
        style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
2118
2119
0
    QTextLayout *layout = bl.layout();
2120
0
    if (layout->lineCount() == 0)
2121
0
        return;
2122
0
    QTextLine firstLine = layout->lineAt(0);
2123
0
    Q_ASSERT(firstLine.isValid());
2124
0
    QPointF pos = (offset + layout->position()).toPoint();
2125
0
    Qt::LayoutDirection dir = bl.textDirection();
2126
0
    {
2127
0
        QRectF textRect = firstLine.naturalTextRect();
2128
0
        pos += textRect.topLeft().toPoint();
2129
0
        if (dir == Qt::RightToLeft)
2130
0
            pos.rx() += textRect.width();
2131
0
    }
2132
2133
0
    switch (style) {
2134
0
    case QTextListFormat::ListDecimal:
2135
0
    case QTextListFormat::ListLowerAlpha:
2136
0
    case QTextListFormat::ListUpperAlpha:
2137
0
    case QTextListFormat::ListLowerRoman:
2138
0
    case QTextListFormat::ListUpperRoman:
2139
0
        itemText = static_cast<QTextList *>(object)->itemText(bl);
2140
0
        size.setWidth(fontMetrics.horizontalAdvance(itemText));
2141
0
        size.setHeight(fontMetrics.height());
2142
0
        break;
2143
2144
0
    case QTextListFormat::ListSquare:
2145
0
    case QTextListFormat::ListCircle:
2146
0
    case QTextListFormat::ListDisc:
2147
0
        size.setWidth(fontMetrics.lineSpacing() / 3);
2148
0
        size.setHeight(size.width());
2149
0
        break;
2150
2151
0
    case QTextListFormat::ListStyleUndefined:
2152
0
        return;
2153
0
    default: return;
2154
0
    }
2155
2156
0
    QRectF r(pos, size);
2157
2158
0
    qreal xoff = fontMetrics.horizontalAdvance(u' ');
2159
0
    if (dir == Qt::LeftToRight)
2160
0
        xoff = -xoff - size.width();
2161
0
    r.translate( xoff, (fontMetrics.height() / 2) - (size.height() / 2));
2162
2163
0
    painter->save();
2164
2165
0
    painter->setRenderHint(QPainter::Antialiasing);
2166
2167
0
    const bool marker = bl.blockFormat().marker() != QTextBlockFormat::MarkerType::NoMarker;
2168
0
    if (selectionFormat) {
2169
0
        painter->setPen(QPen(selectionFormat->foreground(), 0));
2170
0
        if (!marker)
2171
0
            painter->fillRect(r, selectionFormat->background());
2172
0
    } else {
2173
0
        QBrush fg = charFormat.foreground();
2174
0
        if (fg == Qt::NoBrush)
2175
0
            fg = context.palette.text();
2176
0
        painter->setPen(QPen(fg, 0));
2177
0
    }
2178
2179
0
    QBrush brush = context.palette.brush(QPalette::Text);
2180
2181
0
    if (marker) {
2182
0
        int adj = fontMetrics.lineSpacing() / 6;
2183
0
        r.adjust(-adj, 0, -adj, 0);
2184
0
        const QRectF outer = r.adjusted(-adj, -adj, adj, adj);
2185
0
        if (selectionFormat)
2186
0
            painter->fillRect(outer, selectionFormat->background());
2187
0
        if (bl.blockFormat().marker() == QTextBlockFormat::MarkerType::Checked) {
2188
            // ### Qt7: render with QStyle / PE_IndicatorCheckBox. We don't currently
2189
            // have access to that here, because it would be a widget dependency.
2190
0
            painter->setPen(QPen(painter->pen().color(), 2));
2191
0
            painter->drawLine(r.topLeft(), r.bottomRight());
2192
0
            painter->drawLine(r.topRight(), r.bottomLeft());
2193
0
            painter->setPen(QPen(painter->pen().color(), 0));
2194
0
        }
2195
0
        painter->drawRect(outer);
2196
0
    }
2197
2198
0
    switch (style) {
2199
0
    case QTextListFormat::ListDecimal:
2200
0
    case QTextListFormat::ListLowerAlpha:
2201
0
    case QTextListFormat::ListUpperAlpha:
2202
0
    case QTextListFormat::ListLowerRoman:
2203
0
    case QTextListFormat::ListUpperRoman: {
2204
0
        QTextLayout layout(itemText, font, q->paintDevice());
2205
0
        layout.setCacheEnabled(true);
2206
0
        QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
2207
0
        option.setTextDirection(dir);
2208
0
        layout.setTextOption(option);
2209
0
        layout.beginLayout();
2210
0
        QTextLine line = layout.createLine();
2211
0
        if (line.isValid())
2212
0
            line.setLeadingIncluded(true);
2213
0
        layout.endLayout();
2214
0
        layout.draw(painter, QPointF(r.left(), pos.y()));
2215
0
        break;
2216
0
    }
2217
0
    case QTextListFormat::ListSquare:
2218
0
        if (!marker)
2219
0
            painter->fillRect(r, painter->pen().brush());
2220
0
        break;
2221
0
    case QTextListFormat::ListCircle:
2222
0
        if (!marker)
2223
0
            painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
2224
0
        break;
2225
0
    case QTextListFormat::ListDisc:
2226
0
        if (!marker) {
2227
0
            painter->setBrush(painter->pen().brush());
2228
0
            painter->setPen(Qt::NoPen);
2229
0
            painter->drawEllipse(r);
2230
0
        }
2231
0
        break;
2232
0
    case QTextListFormat::ListStyleUndefined:
2233
0
        break;
2234
0
    default:
2235
0
        break;
2236
0
    }
2237
2238
0
    painter->restore();
2239
0
}
2240
2241
static QFixed flowPosition(const QTextFrame::iterator &it)
2242
0
{
2243
0
    if (it.atEnd())
2244
0
        return 0;
2245
2246
0
    if (it.currentFrame()) {
2247
0
        return data(it.currentFrame())->position.y;
2248
0
    } else {
2249
0
        QTextBlock block = it.currentBlock();
2250
0
        QTextLayout *layout = block.layout();
2251
0
        if (layout->lineCount() == 0)
2252
0
            return QFixed::fromReal(layout->position().y());
2253
0
        else
2254
0
            return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
2255
0
    }
2256
0
}
2257
2258
static QFixed firstChildPos(const QTextFrame *f)
2259
0
{
2260
0
    return flowPosition(f->begin());
2261
0
}
2262
2263
QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
2264
                                                        int layoutFrom, int layoutTo, QTextTableData *td,
2265
                                                        QFixed absoluteTableY, bool withPageBreaks)
2266
0
{
2267
0
    qCDebug(lcTable) << "layoutCell";
2268
0
    QTextLayoutStruct layoutStruct;
2269
0
    layoutStruct.frame = t;
2270
0
    layoutStruct.minimumWidth = 0;
2271
0
    layoutStruct.maximumWidth = QFIXED_MAX;
2272
0
    layoutStruct.y = 0;
2273
2274
0
    const QFixed topPadding = td->topPadding(t, cell);
2275
0
    if (withPageBreaks) {
2276
0
        layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
2277
0
    }
2278
0
    layoutStruct.x_left = 0;
2279
0
    layoutStruct.x_right = width;
2280
    // we get called with different widths all the time (for example for figuring
2281
    // out the min/max widths), so we always have to do the full layout ;(
2282
    // also when for example in a table layoutFrom/layoutTo affect only one cell,
2283
    // making that one cell grow the available width of the other cells may change
2284
    // (shrink) and therefore when layoutCell gets called for them they have to
2285
    // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
2286
    // this line:
2287
2288
0
    layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
2289
0
    if (layoutStruct.pageHeight < 0 || !withPageBreaks)
2290
0
        layoutStruct.pageHeight = QFIXED_MAX;
2291
0
    const int currentPage = layoutStruct.currentPage();
2292
2293
0
    layoutStruct.pageTopMargin = td->effectiveTopMargin
2294
0
            + td->cellSpacing
2295
0
            + td->border
2296
0
            + td->paddingProperty(cell.format(), QTextFormat::TableCellTopPadding); // top cell-border is not repeated
2297
2298
0
#ifndef QT_NO_CSSPARSER
2299
0
    const int headerRowCount = t->format().headerRowCount();
2300
0
    if (td->borderCollapse && headerRowCount > 0) {
2301
        // consider the header row's bottom edge width
2302
0
        qreal headerRowBottomBorderWidth = axisEdgeData(t, td, t->cellAt(headerRowCount - 1, cell.column()), QCss::BottomEdge).width;
2303
0
        layoutStruct.pageTopMargin += QFixed::fromReal(scaleToDevice(headerRowBottomBorderWidth) / 2);
2304
0
    }
2305
0
#endif
2306
2307
0
    layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->effectiveBottomBorder + td->bottomPadding(t, cell);
2308
0
    layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
2309
2310
0
    layoutStruct.fullLayout = true;
2311
2312
0
    QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
2313
0
    layoutStruct.y = qMax(layoutStruct.y, pageTop);
2314
2315
0
    const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
2316
0
    for (int i = 0; i < childFrames.size(); ++i) {
2317
0
        QTextFrame *frame = childFrames.at(i);
2318
0
        QTextFrameData *cd = data(frame);
2319
0
        cd->sizeDirty = true;
2320
0
    }
2321
2322
0
    layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
2323
2324
0
    QFixed floatMinWidth;
2325
2326
    // floats that are located inside the text (like inline images) aren't taken into account by
2327
    // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
2328
    // do that here. For example with <td><img align="right" src="..." />blah</td>
2329
    // when the image happens to be higher than the text
2330
0
    for (int i = 0; i < childFrames.size(); ++i) {
2331
0
        QTextFrame *frame = childFrames.at(i);
2332
0
        QTextFrameData *cd = data(frame);
2333
2334
0
        if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
2335
0
            layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
2336
2337
0
        floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
2338
0
    }
2339
2340
    // constraint the maximum/minimumWidth by the minimum width of the fixed size floats,
2341
    // to keep them visible
2342
0
    layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
2343
0
    layoutStruct.minimumWidth = qMax(layoutStruct.minimumWidth, floatMinWidth);
2344
2345
    // as floats in cells get added to the table's float list but must not affect
2346
    // floats in other cells we must clear the list here.
2347
0
    data(t)->floats.clear();
2348
2349
//    qDebug("layoutCell done");
2350
2351
0
    return layoutStruct;
2352
0
}
2353
2354
#ifndef QT_NO_CSSPARSER
2355
static inline void findWidestOutermostBorder(QTextTable *table, QTextTableData *td,
2356
                                             const QTextTableCell &cell, QCss::Edge edge,
2357
                                             qreal *outerBorders)
2358
0
{
2359
0
    EdgeData w = cellEdgeData(table, td, cell, edge);
2360
0
    if (w.width > outerBorders[edge])
2361
0
        outerBorders[edge] = w.width;
2362
0
}
2363
#endif
2364
2365
QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
2366
0
{
2367
0
    qCDebug(lcTable) << "layoutTable from" << layoutFrom << "to" << layoutTo << "parentY" << parentY;
2368
0
    QTextTableData *td = static_cast<QTextTableData *>(data(table));
2369
0
    Q_ASSERT(td->sizeDirty);
2370
0
    const int rows = table->rows();
2371
0
    const int columns = table->columns();
2372
2373
0
    const QTextTableFormat fmt = table->format();
2374
2375
0
    td->childFrameMap.clear();
2376
0
    {
2377
0
        const QList<QTextFrame *> children = table->childFrames();
2378
0
        for (int i = 0; i < children.size(); ++i) {
2379
0
            QTextFrame *frame = children.at(i);
2380
0
            QTextTableCell cell = table->cellAt(frame->firstPosition());
2381
0
            td->childFrameMap.insert(cell.row() + cell.column() * rows, frame);
2382
0
        }
2383
0
    }
2384
2385
0
    QList<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
2386
0
    if (columnWidthConstraints.size() != columns)
2387
0
        columnWidthConstraints.resize(columns);
2388
0
    Q_ASSERT(columnWidthConstraints.size() == columns);
2389
2390
    // borderCollapse will disable drawing the html4 style table cell borders
2391
    // and draw a 1px grid instead. This also sets a fixed cellspacing
2392
    // of 1px if border > 0 (for the grid) and ignore any explicitly set
2393
    // cellspacing.
2394
0
    td->borderCollapse = fmt.borderCollapse();
2395
0
    td->borderCell = td->borderCollapse ? 0 : td->border;
2396
0
    const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(td->borderCollapse ? 0 : fmt.cellSpacing())).round();
2397
2398
0
    td->drawGrid = (td->borderCollapse && fmt.border() >= 1);
2399
2400
0
    td->effectiveTopBorder = td->effectiveBottomBorder = td->effectiveLeftBorder = td->effectiveRightBorder = td->border;
2401
2402
0
#ifndef QT_NO_CSSPARSER
2403
0
    if (td->borderCollapse) {
2404
        // find the widest borders of the outermost cells
2405
0
        qreal outerBorders[QCss::NumEdges];
2406
0
        for (int i = 0; i < QCss::NumEdges; ++i)
2407
0
            outerBorders[i] = 0;
2408
2409
0
        for (int r = 0; r < rows; ++r) {
2410
0
            if (r == 0) {
2411
0
                for (int c = 0; c < columns; ++c)
2412
0
                    findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::TopEdge, outerBorders);
2413
0
            }
2414
0
            if (r == rows - 1) {
2415
0
                for (int c = 0; c < columns; ++c)
2416
0
                    findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::BottomEdge, outerBorders);
2417
0
            }
2418
0
            findWidestOutermostBorder(table, td, table->cellAt(r, 0), QCss::LeftEdge, outerBorders);
2419
0
            findWidestOutermostBorder(table, td, table->cellAt(r, columns - 1), QCss::RightEdge, outerBorders);
2420
0
        }
2421
0
        td->effectiveTopBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::TopEdge] / 2)).round();
2422
0
        td->effectiveBottomBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::BottomEdge] / 2)).round();
2423
0
        td->effectiveLeftBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::LeftEdge] / 2)).round();
2424
0
        td->effectiveRightBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::RightEdge] / 2)).round();
2425
0
    }
2426
0
#endif
2427
2428
0
    td->deviceScale = scaleToDevice(qreal(1));
2429
0
    td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
2430
0
    const QFixed leftMargin = td->leftMargin + td->padding + td->effectiveLeftBorder;
2431
0
    const QFixed rightMargin = td->rightMargin + td->padding + td->effectiveRightBorder;
2432
0
    const QFixed topMargin = td->topMargin + td->padding + td->effectiveTopBorder;
2433
2434
0
    const QFixed absoluteTableY = parentY + td->position.y;
2435
2436
0
    const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
2437
2438
0
recalc_minmax_widths:
2439
2440
0
    QFixed remainingWidth = td->contentsWidth;
2441
    // two (vertical) borders per cell per column
2442
0
    remainingWidth -= columns * 2 * td->borderCell;
2443
    // inter-cell spacing
2444
0
    remainingWidth -= (columns - 1) * cellSpacing;
2445
    // cell spacing at the left and right hand side
2446
0
    remainingWidth -= 2 * cellSpacing;
2447
2448
0
    if (td->borderCollapse) {
2449
0
        remainingWidth -= td->effectiveLeftBorder;
2450
0
        remainingWidth -= td->effectiveRightBorder;
2451
0
    }
2452
2453
    // remember the width used to distribute to percentaged columns
2454
0
    const QFixed initialTotalWidth = remainingWidth;
2455
2456
0
    td->widths.resize(columns);
2457
0
    td->widths.fill(0);
2458
2459
0
    td->minWidths.resize(columns);
2460
    // start with a minimum width of 0. totally empty
2461
    // cells of default created tables are invisible otherwise
2462
    // and therefore hardly editable
2463
0
    td->minWidths.fill(1);
2464
2465
0
    td->maxWidths.resize(columns);
2466
0
    td->maxWidths.fill(QFIXED_MAX);
2467
2468
    // calculate minimum and maximum sizes of the columns
2469
0
    for (int i = 0; i < columns; ++i) {
2470
0
        for (int row = 0; row < rows; ++row) {
2471
0
            const QTextTableCell cell = table->cellAt(row, i);
2472
0
            const int cspan = cell.columnSpan();
2473
2474
0
            if (cspan > 1 && i != cell.column())
2475
0
                continue;
2476
2477
0
            const QFixed leftPadding = td->leftPadding(table, cell);
2478
0
            const QFixed rightPadding = td->rightPadding(table, cell);
2479
0
            const QFixed widthPadding = leftPadding + rightPadding;
2480
2481
            // to figure out the min and the max width lay out the cell at
2482
            // maximum width. otherwise the maxwidth calculation sometimes
2483
            // returns wrong values
2484
0
            QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
2485
0
                                                        layoutTo, td, absoluteTableY,
2486
0
                                                        /*withPageBreaks =*/false);
2487
2488
            // distribute the minimum width over all columns the cell spans
2489
0
            QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
2490
0
            for (int n = 0; n < cspan; ++n) {
2491
0
                const int col = i + n;
2492
0
                QFixed w = widthToDistribute / (cspan - n);
2493
                // ceil to avoid going below minWidth when rounding all column widths later
2494
0
                td->minWidths[col] = qMax(td->minWidths.at(col), w).ceil();
2495
0
                widthToDistribute -= td->minWidths.at(col);
2496
0
                if (widthToDistribute <= 0)
2497
0
                    break;
2498
0
            }
2499
2500
0
            QFixed maxW = td->maxWidths.at(i);
2501
0
            if (layoutStruct.maximumWidth != QFIXED_MAX) {
2502
0
                if (maxW == QFIXED_MAX)
2503
0
                    maxW = layoutStruct.maximumWidth + widthPadding;
2504
0
                else
2505
0
                    maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
2506
0
            }
2507
0
            if (maxW == QFIXED_MAX)
2508
0
                continue;
2509
2510
            // for variable columns the maxWidth will later be considered as the
2511
            // column width (column width = content width). We must avoid that the
2512
            // pixel-alignment rounding step floors this value and thus the text
2513
            // rendering later erroneously wraps the content.
2514
0
            maxW = maxW.ceil();
2515
2516
0
            widthToDistribute = maxW;
2517
0
            for (int n = 0; n < cspan; ++n) {
2518
0
                const int col = i + n;
2519
0
                QFixed w = widthToDistribute / (cspan - n);
2520
0
                if (td->maxWidths[col] != QFIXED_MAX)
2521
0
                    w = qMax(td->maxWidths[col], w);
2522
0
                td->maxWidths[col] = qMax(td->minWidths.at(col), w);
2523
0
                widthToDistribute -= td->maxWidths.at(col);
2524
0
                if (widthToDistribute <= 0)
2525
0
                    break;
2526
0
            }
2527
0
        }
2528
0
    }
2529
2530
    // set fixed values, figure out total percentages used and number of
2531
    // variable length cells. Also assign the minimum width for variable columns.
2532
0
    QFixed totalPercentage;
2533
0
    int variableCols = 0;
2534
0
    QFixed totalMinWidth = 0;
2535
0
    for (int i = 0; i < columns; ++i) {
2536
0
        const QTextLength &length = columnWidthConstraints.at(i);
2537
0
        if (length.type() == QTextLength::FixedLength) {
2538
0
            td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
2539
0
            remainingWidth -= td->widths.at(i);
2540
0
            qCDebug(lcTable) << "column" << i << "has width constraint" << td->minWidths.at(i) << "px, remaining width now" << remainingWidth;
2541
0
        } else if (length.type() == QTextLength::PercentageLength) {
2542
0
            totalPercentage += QFixed::fromReal(length.rawValue());
2543
0
        } else if (length.type() == QTextLength::VariableLength) {
2544
0
            variableCols++;
2545
2546
0
            td->widths[i] = td->minWidths.at(i);
2547
0
            remainingWidth -= td->minWidths.at(i);
2548
0
            qCDebug(lcTable) << "column" << i << "has variable width, min" << td->minWidths.at(i) << "remaining width now" << remainingWidth;
2549
0
        }
2550
0
        totalMinWidth += td->minWidths.at(i);
2551
0
    }
2552
2553
    // set percentage values
2554
0
    {
2555
0
        const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
2556
0
        QFixed remainingMinWidths = totalMinWidth;
2557
0
        for (int i = 0; i < columns; ++i) {
2558
0
            remainingMinWidths -= td->minWidths.at(i);
2559
0
            if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
2560
0
                const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
2561
2562
0
                const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
2563
0
                QFixed maxWidth = remainingWidth - remainingMinWidths;
2564
0
                if (percentWidth >= td->minWidths.at(i) && maxWidth > td->minWidths.at(i)) {
2565
0
                    td->widths[i] = qBound(td->minWidths.at(i), percentWidth, maxWidth);
2566
0
                } else {
2567
0
                    td->widths[i] = td->minWidths.at(i);
2568
0
                }
2569
0
                qCDebug(lcTable) << "column" << i << "has width constraint" << columnWidthConstraints.at(i).rawValue()
2570
0
                                 << "%, allocated width" << td->widths[i] << "remaining width now" << remainingWidth;
2571
0
                remainingWidth -= td->widths.at(i);
2572
0
            }
2573
0
        }
2574
0
    }
2575
2576
    // for variable columns distribute the remaining space
2577
0
    if (variableCols > 0 && remainingWidth > 0) {
2578
0
        QVarLengthArray<int> columnsWithProperMaxSize;
2579
0
        for (int i = 0; i < columns; ++i)
2580
0
            if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
2581
0
                && td->maxWidths.at(i) != QFIXED_MAX)
2582
0
                columnsWithProperMaxSize.append(i);
2583
2584
0
        QFixed lastRemainingWidth = remainingWidth;
2585
0
        while (remainingWidth > 0) {
2586
0
            for (int k = 0; k < columnsWithProperMaxSize.size(); ++k) {
2587
0
                const int col = columnsWithProperMaxSize[k];
2588
0
                const int colsLeft = columnsWithProperMaxSize.size() - k;
2589
0
                const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
2590
0
                td->widths[col] += w;
2591
0
                remainingWidth -= w;
2592
0
            }
2593
0
            if (remainingWidth == lastRemainingWidth)
2594
0
                break;
2595
0
            lastRemainingWidth = remainingWidth;
2596
0
        }
2597
2598
0
        if (remainingWidth > 0
2599
            // don't unnecessarily grow variable length sized tables
2600
0
            && fmt.width().type() != QTextLength::VariableLength) {
2601
0
            const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
2602
0
            for (int col = 0; col < columns; ++col) {
2603
0
                if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
2604
0
                    td->widths[col] += widthPerAnySizedCol;
2605
0
            }
2606
0
        }
2607
0
    }
2608
2609
    // in order to get a correct border rendering we must ensure that the distance between
2610
    // two cells is exactly 2 * td->cellBorder pixel. we do this by rounding the calculated width
2611
    // values here.
2612
    // to minimize the total rounding error we propagate the rounding error for each width
2613
    // to its successor.
2614
0
    QFixed error = 0;
2615
0
    for (int i = 0; i < columns; ++i) {
2616
0
        QFixed orig = td->widths[i];
2617
0
        td->widths[i] = (td->widths[i] - error).round();
2618
0
        error = td->widths[i] - orig;
2619
0
    }
2620
2621
0
    td->columnPositions.resize(columns);
2622
0
    td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
2623
2624
0
    for (int i = 1; i < columns; ++i)
2625
0
        td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->borderCell + cellSpacing;
2626
2627
    // - margin to compensate the + margin in columnPositions[0]
2628
0
    const QFixed contentsWidth = td->columnPositions.constLast() + td->widths.constLast() + td->padding + td->border + cellSpacing - leftMargin;
2629
2630
    // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
2631
    // mode
2632
0
    if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
2633
0
        && contentsWidth > td->contentsWidth) {
2634
0
        docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
2635
        // go back to the top of the function
2636
0
        goto recalc_minmax_widths;
2637
0
    }
2638
2639
0
    td->contentsWidth = contentsWidth;
2640
2641
0
    docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
2642
2643
0
    td->heights.resize(rows);
2644
0
    td->heights.fill(0);
2645
2646
0
    td->rowPositions.resize(rows);
2647
0
    td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
2648
2649
0
    bool haveRowSpannedCells = false;
2650
2651
    // need to keep track of cell heights for vertical alignment
2652
0
    QList<QFixed> cellHeights;
2653
0
    cellHeights.reserve(rows * columns);
2654
2655
0
    QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
2656
0
    if (pageHeight <= 0)
2657
0
        pageHeight = QFIXED_MAX;
2658
2659
0
    QList<QFixed> heightToDistribute;
2660
0
    heightToDistribute.resize(columns);
2661
2662
0
    td->headerHeight = 0;
2663
0
    const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
2664
0
    const QFixed originalTopMargin = td->effectiveTopMargin;
2665
0
    bool hasDroppedTable = false;
2666
2667
    // now that we have the column widths we can lay out all cells with the right width.
2668
    // spanning cells are only allowed to grow the last row spanned by the cell.
2669
    //
2670
    // ### this could be made faster by iterating over the cells array of QTextTable
2671
0
    for (int r = 0; r < rows; ++r) {
2672
0
        td->calcRowPosition(r);
2673
2674
0
        const int tableStartPage = (absoluteTableY / pageHeight).truncate();
2675
0
        const int currentPage = ((td->rowPositions.at(r) + absoluteTableY) / pageHeight).truncate();
2676
0
        const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
2677
0
        const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
2678
0
        const QFixed nextPageTop = pageTop + pageHeight;
2679
2680
0
        if (td->rowPositions.at(r) > pageBottom)
2681
0
            td->rowPositions[r] = nextPageTop;
2682
0
        else if (td->rowPositions.at(r) < pageTop)
2683
0
            td->rowPositions[r] = pageTop;
2684
2685
0
        bool dropRowToNextPage = true;
2686
0
        int cellCountBeforeRow = cellHeights.size();
2687
2688
        // if we drop the row to the next page we need to subtract the drop
2689
        // distance from any row spanning cells
2690
0
        QFixed dropDistance = 0;
2691
2692
0
relayout:
2693
0
        const int rowStartPage = ((td->rowPositions.at(r) + absoluteTableY) / pageHeight).truncate();
2694
        // if any of the header rows or the first non-header row start on the next page
2695
        // then the entire header should be dropped
2696
0
        if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
2697
0
            td->rowPositions[0] = nextPageTop;
2698
0
            cellHeights.clear();
2699
0
            td->effectiveTopMargin = originalTopMargin;
2700
0
            hasDroppedTable = true;
2701
0
            r = -1;
2702
0
            continue;
2703
0
        }
2704
2705
0
        int rowCellCount = 0;
2706
0
        for (int c = 0; c < columns; ++c) {
2707
0
            QTextTableCell cell = table->cellAt(r, c);
2708
0
            const int rspan = cell.rowSpan();
2709
0
            const int cspan = cell.columnSpan();
2710
2711
0
            if (cspan > 1 && cell.column() != c)
2712
0
                continue;
2713
2714
0
            if (rspan > 1) {
2715
0
                haveRowSpannedCells = true;
2716
2717
0
                const int cellRow = cell.row();
2718
0
                if (cellRow != r) {
2719
                    // the last row gets all the remaining space
2720
0
                    if (cellRow + rspan - 1 == r)
2721
0
                        td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance).round();
2722
0
                    continue;
2723
0
                }
2724
0
            }
2725
2726
0
            const QFixed topPadding = td->topPadding(table, cell);
2727
0
            const QFixed bottomPadding = td->bottomPadding(table, cell);
2728
0
            const QFixed leftPadding = td->leftPadding(table, cell);
2729
0
            const QFixed rightPadding = td->rightPadding(table, cell);
2730
0
            const QFixed widthPadding = leftPadding + rightPadding;
2731
2732
0
            ++rowCellCount;
2733
2734
0
            const QFixed width = td->cellWidth(c, cspan) - widthPadding;
2735
0
            QTextLayoutStruct layoutStruct = layoutCell(table, cell, width,
2736
0
                                                       layoutFrom, layoutTo,
2737
0
                                                       td, absoluteTableY,
2738
0
                                                       /*withPageBreaks =*/true);
2739
2740
0
            const QFixed height = (layoutStruct.y + bottomPadding + topPadding).round();
2741
2742
0
            if (rspan > 1)
2743
0
                heightToDistribute[c] = height + dropDistance;
2744
0
            else
2745
0
                td->heights[r] = qMax(td->heights.at(r), height);
2746
2747
0
            cellHeights.append(layoutStruct.y);
2748
2749
0
            QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
2750
0
            if (childPos < pageBottom)
2751
0
                dropRowToNextPage = false;
2752
0
        }
2753
2754
0
        if (rowCellCount > 0 && dropRowToNextPage) {
2755
0
            dropDistance = nextPageTop - td->rowPositions.at(r);
2756
0
            td->rowPositions[r] = nextPageTop;
2757
0
            td->heights[r] = 0;
2758
0
            dropRowToNextPage = false;
2759
0
            cellHeights.resize(cellCountBeforeRow);
2760
0
            if (r > headerRowCount)
2761
0
                td->heights[r - 1] = pageBottom - td->rowPositions.at(r - 1);
2762
0
            goto relayout;
2763
0
        }
2764
2765
0
        if (haveRowSpannedCells) {
2766
0
            const QFixed effectiveHeight = td->heights.at(r) + td->borderCell + cellSpacing + td->borderCell;
2767
0
            for (int c = 0; c < columns; ++c)
2768
0
                heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
2769
0
        }
2770
2771
0
        if (r == headerRowCount - 1) {
2772
0
            td->headerHeight = td->rowPositions.at(r) + td->heights.at(r) - td->rowPositions.at(0) + td->cellSpacing + 2 * td->borderCell;
2773
0
            td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
2774
0
            td->effectiveTopMargin += td->headerHeight;
2775
0
        }
2776
0
    }
2777
2778
0
    td->effectiveTopMargin = originalTopMargin;
2779
2780
    // now that all cells have been properly laid out, we can compute the
2781
    // vertical offsets for vertical alignment
2782
0
    td->cellVerticalOffsets.resize(rows * columns);
2783
0
    int cellIndex = 0;
2784
0
    for (int r = 0; r < rows; ++r) {
2785
0
        for (int c = 0; c < columns; ++c) {
2786
0
            QTextTableCell cell = table->cellAt(r, c);
2787
0
            if (cell.row() != r || cell.column() != c)
2788
0
                continue;
2789
2790
0
            const int rowSpan = cell.rowSpan();
2791
0
            const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
2792
2793
0
            const QTextCharFormat cellFormat = cell.format();
2794
0
            const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(table, cell) + td->bottomPadding(table, cell);
2795
2796
0
            QFixed offset = 0;
2797
0
            switch (cellFormat.verticalAlignment()) {
2798
0
            case QTextCharFormat::AlignMiddle:
2799
0
                offset = (availableHeight - cellHeight) / 2;
2800
0
                break;
2801
0
            case QTextCharFormat::AlignBottom:
2802
0
                offset = availableHeight - cellHeight;
2803
0
                break;
2804
0
            default:
2805
0
                break;
2806
0
            };
2807
2808
0
            for (int rd = 0; rd < cell.rowSpan(); ++rd) {
2809
0
                for (int cd = 0; cd < cell.columnSpan(); ++cd) {
2810
0
                    const int index = (c + cd) + (r + rd) * columns;
2811
0
                    td->cellVerticalOffsets[index] = offset;
2812
0
                }
2813
0
            }
2814
0
        }
2815
0
    }
2816
2817
0
    td->minimumWidth = td->columnPositions.at(0);
2818
0
    for (int i = 0; i < columns; ++i) {
2819
0
        td->minimumWidth += td->minWidths.at(i) + 2 * td->borderCell + cellSpacing;
2820
0
    }
2821
0
    td->minimumWidth += rightMargin - td->border;
2822
2823
0
    td->maximumWidth = td->columnPositions.at(0);
2824
0
    for (int i = 0; i < columns; ++i) {
2825
0
        if (td->maxWidths.at(i) != QFIXED_MAX)
2826
0
            td->maximumWidth += td->maxWidths.at(i) + 2 * td->borderCell + cellSpacing;
2827
0
        qCDebug(lcTable) << "column" << i << "has final width" << td->widths.at(i).toReal()
2828
0
                         << "min" << td->minWidths.at(i).toReal() << "max" << td->maxWidths.at(i).toReal();
2829
0
    }
2830
0
    td->maximumWidth += rightMargin - td->border;
2831
2832
0
    td->updateTableSize();
2833
0
    td->sizeDirty = false;
2834
0
    return QRectF(); // invalid rect -> update everything
2835
0
}
2836
2837
void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
2838
0
{
2839
0
    QTextFrameData *fd = data(frame);
2840
2841
0
    QTextFrame *parent = frame->parentFrame();
2842
0
    Q_ASSERT(parent);
2843
0
    QTextFrameData *pd = data(parent);
2844
0
    Q_ASSERT(pd && pd->currentLayoutStruct);
2845
2846
0
    QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct;
2847
2848
0
    if (!pd->floats.contains(frame))
2849
0
        pd->floats.append(frame);
2850
0
    fd->layoutDirty = true;
2851
0
    Q_ASSERT(!fd->sizeDirty);
2852
2853
//     qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
2854
0
    QFixed y = layoutStruct->y;
2855
0
    if (currentLine) {
2856
0
        QFixed left, right;
2857
0
        floatMargins(y, layoutStruct, &left, &right);
2858
//         qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
2859
0
        if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
2860
0
            layoutStruct->pendingFloats.append(frame);
2861
//             qDebug("    adding to pending list");
2862
0
            return;
2863
0
        }
2864
0
    }
2865
2866
0
    bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom);
2867
0
    if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) {
2868
0
        layoutStruct->newPage();
2869
0
        y = layoutStruct->y;
2870
2871
0
        frameSpansIntoNextPage = false;
2872
0
    }
2873
2874
0
    y = findY(y, layoutStruct, fd->size.width);
2875
2876
0
    QFixed left, right;
2877
0
    floatMargins(y, layoutStruct, &left, &right);
2878
2879
0
    if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
2880
0
        fd->position.x = left;
2881
0
        fd->position.y = y;
2882
0
    } else {
2883
0
        fd->position.x = right - fd->size.width;
2884
0
        fd->position.y = y;
2885
0
    }
2886
2887
0
    layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
2888
0
    layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
2889
2890
//     qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
2891
0
    fd->layoutDirty = false;
2892
2893
    // If the frame is a table, then positioning it will affect the size if it covers more than
2894
    // one page, because of page breaks and repeating the header.
2895
0
    if (qobject_cast<QTextTable *>(frame) != nullptr)
2896
0
        fd->sizeDirty = frameSpansIntoNextPage;
2897
0
}
2898
2899
QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
2900
34.8k
{
2901
34.8k
    qCDebug(lcLayout, "layoutFrame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2902
34.8k
    Q_ASSERT(data(f)->sizeDirty);
2903
2904
34.8k
    QTextFrameFormat fformat = f->frameFormat();
2905
2906
34.8k
    QTextFrame *parent = f->parentFrame();
2907
34.8k
    const QTextFrameData *pd = parent ? data(parent) : nullptr;
2908
2909
34.8k
    const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
2910
34.8k
    QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
2911
34.8k
    if (fformat.width().type() == QTextLength::FixedLength)
2912
0
        width = scaleToDevice(width);
2913
2914
34.8k
    const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
2915
34.8k
    const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
2916
34.8k
                            ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
2917
34.8k
                            : -1;
2918
2919
34.8k
    return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
2920
34.8k
}
2921
2922
QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
2923
34.8k
{
2924
34.8k
    qCDebug(lcLayout, "layoutFrame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2925
34.8k
    Q_ASSERT(data(f)->sizeDirty);
2926
2927
34.8k
    QTextFrameData *fd = data(f);
2928
34.8k
    QFixed newContentsWidth;
2929
2930
34.8k
    bool fullLayout = false;
2931
34.8k
    {
2932
34.8k
        QTextFrameFormat fformat = f->frameFormat();
2933
        // set sizes of this frame from the format
2934
34.8k
        QFixed tm = QFixed::fromReal(scaleToDevice(fformat.topMargin())).round();
2935
34.8k
        if (tm != fd->topMargin) {
2936
0
            fd->topMargin = tm;
2937
0
            fullLayout = true;
2938
0
        }
2939
34.8k
        QFixed bm = QFixed::fromReal(scaleToDevice(fformat.bottomMargin())).round();
2940
34.8k
        if (bm != fd->bottomMargin) {
2941
0
            fd->bottomMargin = bm;
2942
0
            fullLayout = true;
2943
0
        }
2944
34.8k
        fd->leftMargin = QFixed::fromReal(scaleToDevice(fformat.leftMargin())).round();
2945
34.8k
        fd->rightMargin = QFixed::fromReal(scaleToDevice(fformat.rightMargin())).round();
2946
34.8k
        QFixed b = QFixed::fromReal(scaleToDevice(fformat.border())).round();
2947
34.8k
        if (b != fd->border) {
2948
0
            fd->border = b;
2949
0
            fullLayout = true;
2950
0
        }
2951
34.8k
        QFixed p = QFixed::fromReal(scaleToDevice(fformat.padding())).round();
2952
34.8k
        if (p != fd->padding) {
2953
0
            fd->padding = p;
2954
0
            fullLayout = true;
2955
0
        }
2956
2957
34.8k
        QTextFrame *parent = f->parentFrame();
2958
34.8k
        const QTextFrameData *pd = parent ? data(parent) : nullptr;
2959
2960
        // accumulate top and bottom margins
2961
34.8k
        if (parent) {
2962
0
            fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
2963
0
            fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
2964
2965
0
            if (qobject_cast<QTextTable *>(parent)) {
2966
0
                const QTextTableData *td = static_cast<const QTextTableData *>(pd);
2967
0
                fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
2968
0
                fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
2969
0
            }
2970
34.8k
        } else {
2971
34.8k
            fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
2972
34.8k
            fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
2973
34.8k
        }
2974
2975
34.8k
        newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
2976
34.8k
                           - fd->leftMargin - fd->rightMargin;
2977
2978
34.8k
        if (frameHeight != -1) {
2979
0
            fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
2980
0
                                 - fd->topMargin - fd->bottomMargin;
2981
34.8k
        } else {
2982
34.8k
            fd->contentsHeight = frameHeight;
2983
34.8k
        }
2984
34.8k
    }
2985
2986
34.8k
    if (isFrameFromInlineObject(f)) {
2987
        // never reached, handled in resizeInlineObject/positionFloat instead
2988
0
        return QRectF();
2989
0
    }
2990
2991
34.8k
    if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2992
0
        fd->contentsWidth = newContentsWidth;
2993
0
        return layoutTable(table, layoutFrom, layoutTo, parentY);
2994
0
    }
2995
2996
    // set fd->contentsWidth temporarily, so that layoutFrame for the children
2997
    // picks the right width. We'll initialize it properly at the end of this
2998
    // function.
2999
34.8k
    fd->contentsWidth = newContentsWidth;
3000
3001
34.8k
    QTextLayoutStruct layoutStruct;
3002
34.8k
    layoutStruct.frame = f;
3003
34.8k
    layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
3004
34.8k
    layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
3005
34.8k
    layoutStruct.y = fd->topMargin + fd->border + fd->padding;
3006
34.8k
    layoutStruct.frameY = parentY + fd->position.y;
3007
34.8k
    layoutStruct.contentsWidth = 0;
3008
34.8k
    layoutStruct.minimumWidth = 0;
3009
34.8k
    layoutStruct.maximumWidth = QFIXED_MAX;
3010
34.8k
    layoutStruct.fullLayout = fullLayout || (fd->oldContentsWidth != newContentsWidth);
3011
34.8k
    layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
3012
34.8k
    qCDebug(lcLayout) << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
3013
0
                      << "fullLayout" << layoutStruct.fullLayout;
3014
34.8k
    fd->oldContentsWidth = newContentsWidth;
3015
3016
34.8k
    layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
3017
34.8k
    if (layoutStruct.pageHeight < 0)
3018
0
        layoutStruct.pageHeight = QFIXED_MAX;
3019
3020
34.8k
    const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
3021
34.8k
    layoutStruct.pageTopMargin = fd->effectiveTopMargin;
3022
34.8k
    layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
3023
34.8k
    layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
3024
3025
34.8k
    if (!f->parentFrame())
3026
34.8k
        idealWidth = 0; // reset
3027
3028
34.8k
    QTextFrame::Iterator it = f->begin();
3029
34.8k
    layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
3030
3031
34.8k
    QFixed maxChildFrameWidth = 0;
3032
34.8k
    QList<QTextFrame *> children = f->childFrames();
3033
34.8k
    for (int i = 0; i < children.size(); ++i) {
3034
0
        QTextFrame *c = children.at(i);
3035
0
        QTextFrameData *cd = data(c);
3036
0
        maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
3037
0
    }
3038
3039
34.8k
    const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
3040
34.8k
    if (!f->parentFrame()) {
3041
34.8k
        idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
3042
34.8k
        idealWidth += marginWidth.toReal();
3043
34.8k
    }
3044
3045
34.8k
    QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
3046
34.8k
    fd->contentsWidth = actualWidth;
3047
34.8k
    if (newContentsWidth <= 0) { // nowrap layout?
3048
0
        fd->contentsWidth = newContentsWidth;
3049
0
    }
3050
3051
34.8k
    fd->minimumWidth = layoutStruct.minimumWidth;
3052
34.8k
    fd->maximumWidth = layoutStruct.maximumWidth;
3053
3054
34.8k
    fd->size.height = fd->contentsHeight == -1
3055
34.8k
                 ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
3056
34.8k
                 : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
3057
34.8k
    fd->size.width = actualWidth + marginWidth;
3058
34.8k
    fd->sizeDirty = false;
3059
34.8k
    if (layoutStruct.updateRectForFloats.isValid())
3060
0
        layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
3061
34.8k
    return layoutStruct.updateRect;
3062
34.8k
}
3063
3064
void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct,
3065
                                            int layoutFrom, int layoutTo, QFixed width)
3066
34.8k
{
3067
34.8k
    qCDebug(lcLayout) << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
3068
34.8k
    QTextFrameData *fd = data(layoutStruct->frame);
3069
3070
34.8k
    fd->currentLayoutStruct = layoutStruct;
3071
3072
34.8k
    QTextFrame::Iterator previousIt;
3073
3074
34.8k
    const bool inRootFrame = (it.parentFrame() == document->rootFrame());
3075
34.8k
    if (inRootFrame) {
3076
34.8k
        bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
3077
3078
34.8k
        if (!redoCheckPoints) {
3079
17.4k
            auto checkPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), layoutFrom);
3080
17.4k
            if (checkPoint != checkPoints.end()) {
3081
17.4k
                if (checkPoint != checkPoints.begin())
3082
0
                    --checkPoint;
3083
3084
17.4k
                layoutStruct->y = checkPoint->y;
3085
17.4k
                layoutStruct->frameY = checkPoint->frameY;
3086
17.4k
                layoutStruct->minimumWidth = checkPoint->minimumWidth;
3087
17.4k
                layoutStruct->maximumWidth = checkPoint->maximumWidth;
3088
17.4k
                layoutStruct->contentsWidth = checkPoint->contentsWidth;
3089
3090
17.4k
                if (layoutStruct->pageHeight > 0) {
3091
17.4k
                    int page = layoutStruct->currentPage();
3092
17.4k
                    layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
3093
17.4k
                }
3094
3095
17.4k
                it = frameIteratorForTextPosition(checkPoint->positionInFrame);
3096
17.4k
                checkPoints.resize(checkPoint - checkPoints.begin() + 1);
3097
3098
17.4k
                if (checkPoint != checkPoints.begin()) {
3099
0
                    previousIt = it;
3100
0
                    --previousIt;
3101
0
                }
3102
17.4k
            } else {
3103
0
                redoCheckPoints = true;
3104
0
            }
3105
17.4k
        }
3106
3107
34.8k
        if (redoCheckPoints) {
3108
17.4k
            checkPoints.clear();
3109
17.4k
            QCheckPoint cp;
3110
17.4k
            cp.y = layoutStruct->y;
3111
17.4k
            cp.frameY = layoutStruct->frameY;
3112
17.4k
            cp.positionInFrame = 0;
3113
17.4k
            cp.minimumWidth = layoutStruct->minimumWidth;
3114
17.4k
            cp.maximumWidth = layoutStruct->maximumWidth;
3115
17.4k
            cp.contentsWidth = layoutStruct->contentsWidth;
3116
17.4k
            checkPoints.append(cp);
3117
17.4k
        }
3118
34.8k
    }
3119
3120
34.8k
    QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
3121
3122
34.8k
    QFixed maximumBlockWidth = 0;
3123
853k
    while (!it.atEnd() && layoutStruct->absoluteY() < QFIXED_MAX) {
3124
819k
        QTextFrame *c = it.currentFrame();
3125
3126
819k
        int docPos;
3127
819k
        if (it.currentFrame())
3128
0
            docPos = it.currentFrame()->firstPosition();
3129
819k
        else
3130
819k
            docPos = it.currentBlock().position();
3131
3132
819k
        if (inRootFrame) {
3133
819k
            if (qAbs(layoutStruct->y - checkPoints.constLast().y) > 2000) {
3134
2.91k
                QFixed left, right;
3135
2.91k
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3136
2.91k
                if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
3137
2.91k
                    QCheckPoint p;
3138
2.91k
                    p.y = layoutStruct->y;
3139
2.91k
                    p.frameY = layoutStruct->frameY;
3140
2.91k
                    p.positionInFrame = docPos;
3141
2.91k
                    p.minimumWidth = layoutStruct->minimumWidth;
3142
2.91k
                    p.maximumWidth = layoutStruct->maximumWidth;
3143
2.91k
                    p.contentsWidth = layoutStruct->contentsWidth;
3144
2.91k
                    checkPoints.append(p);
3145
3146
2.91k
                    if (currentLazyLayoutPosition != -1
3147
2.67k
                        && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
3148
771
                        break;
3149
3150
2.91k
                }
3151
2.91k
            }
3152
819k
        }
3153
3154
818k
        if (c) {
3155
            // position child frame
3156
0
            QTextFrameData *cd = data(c);
3157
3158
0
            QTextFrameFormat fformat = c->frameFormat();
3159
3160
0
            if (fformat.position() == QTextFrameFormat::InFlow) {
3161
0
                if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
3162
0
                    layoutStruct->newPage();
3163
3164
0
                QFixed left, right;
3165
0
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3166
0
                left = qMax(left, layoutStruct->x_left);
3167
0
                right = qMin(right, layoutStruct->x_right);
3168
3169
0
                if (right - left < cd->size.width) {
3170
0
                    layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
3171
0
                    floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3172
0
                }
3173
3174
0
                QFixedPoint pos(left, layoutStruct->y);
3175
3176
0
                Qt::Alignment align = Qt::AlignLeft;
3177
3178
0
                QTextTable *table = qobject_cast<QTextTable *>(c);
3179
3180
0
                if (table)
3181
0
                    align = table->format().alignment() & Qt::AlignHorizontal_Mask;
3182
3183
                // detect whether we have any alignment in the document that disallows optimizations,
3184
                // such as not laying out the document again in a textedit with wrapping disabled.
3185
0
                if (inRootFrame && !(align & Qt::AlignLeft))
3186
0
                    contentHasAlignment = true;
3187
3188
0
                cd->position = pos;
3189
3190
0
                if (document->pageSize().height() > 0.0f)
3191
0
                    cd->sizeDirty = true;
3192
3193
0
                if (cd->sizeDirty) {
3194
0
                    if (width != 0)
3195
0
                        layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
3196
0
                    else
3197
0
                        layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
3198
3199
0
                    QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
3200
0
                    absoluteChildPos += layoutStruct->frameY;
3201
3202
                    // drop entire frame to next page if first child of frame is on next page
3203
0
                    if (absoluteChildPos > layoutStruct->pageBottom) {
3204
0
                        layoutStruct->newPage();
3205
0
                        pos.y = layoutStruct->y;
3206
3207
0
                        cd->position = pos;
3208
0
                        cd->sizeDirty = true;
3209
3210
0
                        if (width != 0)
3211
0
                            layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
3212
0
                        else
3213
0
                            layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
3214
0
                    }
3215
0
                }
3216
3217
                // align only if there is space for alignment
3218
0
                if (right - left > cd->size.width) {
3219
0
                    if (align & Qt::AlignRight)
3220
0
                        pos.x += layoutStruct->x_right - cd->size.width;
3221
0
                    else if (align & Qt::AlignHCenter)
3222
0
                        pos.x += (layoutStruct->x_right - cd->size.width) / 2;
3223
0
                }
3224
3225
0
                cd->position = pos;
3226
3227
0
                layoutStruct->y += cd->size.height;
3228
0
                const int page = layoutStruct->currentPage();
3229
0
                layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
3230
3231
0
                cd->layoutDirty = false;
3232
3233
0
                if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
3234
0
                    layoutStruct->newPage();
3235
0
            } else {
3236
0
                QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
3237
0
                QRectF updateRect;
3238
3239
0
                if (cd->sizeDirty)
3240
0
                    updateRect = layoutFrame(c, layoutFrom, layoutTo);
3241
3242
0
                positionFloat(c);
3243
3244
                // If the size was made dirty when the position was set, layout again
3245
0
                if (cd->sizeDirty)
3246
0
                    updateRect = layoutFrame(c, layoutFrom, layoutTo);
3247
3248
0
                QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
3249
3250
0
                if (frameRect == oldFrameRect && updateRect.isValid())
3251
0
                    updateRect.translate(cd->position.toPointF());
3252
0
                else
3253
0
                    updateRect = frameRect;
3254
3255
0
                layoutStruct->addUpdateRectForFloat(updateRect);
3256
0
                if (oldFrameRect.isValid())
3257
0
                    layoutStruct->addUpdateRectForFloat(oldFrameRect);
3258
0
            }
3259
3260
0
            layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
3261
0
            layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
3262
3263
0
            previousIt = it;
3264
0
            ++it;
3265
818k
        } else {
3266
818k
            QTextFrame::Iterator lastIt;
3267
818k
            if (!previousIt.atEnd() && previousIt != it)
3268
783k
                lastIt = previousIt;
3269
818k
            previousIt = it;
3270
818k
            QTextBlock block = it.currentBlock();
3271
818k
            ++it;
3272
3273
818k
            const QTextBlockFormat blockFormat = block.blockFormat();
3274
3275
818k
            if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
3276
0
                layoutStruct->newPage();
3277
3278
818k
            const QFixed origY = layoutStruct->y;
3279
818k
            const QFixed origPageBottom = layoutStruct->pageBottom;
3280
818k
            const QFixed origMaximumWidth = layoutStruct->maximumWidth;
3281
818k
            layoutStruct->maximumWidth = 0;
3282
3283
818k
            const QTextBlockFormat *previousBlockFormatPtr = nullptr;
3284
818k
            if (lastIt.currentBlock().isValid())
3285
783k
                previousBlockFormatPtr = &previousBlockFormat;
3286
3287
            // layout and position child block
3288
818k
            layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
3289
3290
            // detect whether we have any alignment in the document that disallows optimizations,
3291
            // such as not laying out the document again in a textedit with wrapping disabled.
3292
818k
            if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
3293
0
                contentHasAlignment = true;
3294
3295
            // if the block right before a table is empty 'hide' it by
3296
            // positioning it into the table border
3297
818k
            if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
3298
0
                const QTextBlock lastBlock = lastIt.currentBlock();
3299
0
                const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
3300
0
                layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
3301
0
                layoutStruct->pageBottom = origPageBottom;
3302
818k
            } else {
3303
                // if the block right after a table is empty then 'hide' it, too
3304
818k
                if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
3305
0
                    QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
3306
0
                    QTextLayout *layout = block.layout();
3307
3308
0
                    QPointF pos((td->position.x + td->size.width).toReal(),
3309
0
                                (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
3310
3311
0
                    layout->setPosition(pos);
3312
0
                    layoutStruct->y = origY;
3313
0
                    layoutStruct->pageBottom = origPageBottom;
3314
0
                }
3315
3316
                // if the block right after a table starts with a line separator, shift it up by one line
3317
818k
                if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
3318
0
                    QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
3319
0
                    QTextLayout *layout = block.layout();
3320
3321
0
                    QFixed height = layout->lineCount() > 0 ? QFixed::fromReal(layout->lineAt(0).height()) : QFixed();
3322
3323
0
                    if (layoutStruct->pageBottom == origPageBottom) {
3324
0
                        layoutStruct->y -= height;
3325
0
                        layout->setPosition(layout->position() - QPointF(0, height.toReal()));
3326
0
                    } else {
3327
                        // relayout block to correctly handle page breaks
3328
0
                        layoutStruct->y = origY - height;
3329
0
                        layoutStruct->pageBottom = origPageBottom;
3330
0
                        layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
3331
0
                    }
3332
3333
0
                    if (layout->lineCount() > 0) {
3334
0
                        QPointF linePos((td->position.x + td->size.width).toReal(),
3335
0
                                        (td->position.y + td->size.height - height).toReal());
3336
3337
0
                        layout->lineAt(0).setPosition(linePos - layout->position());
3338
0
                    }
3339
0
                }
3340
3341
818k
                if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
3342
0
                    layoutStruct->newPage();
3343
818k
            }
3344
3345
818k
            maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
3346
818k
            layoutStruct->maximumWidth = origMaximumWidth;
3347
818k
            previousBlockFormat = blockFormat;
3348
818k
        }
3349
818k
    }
3350
34.8k
    if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
3351
34.5k
        layoutStruct->maximumWidth = maximumBlockWidth;
3352
308
    else
3353
308
        layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
3354
3355
    // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
3356
    // we don't need to do it for tables though because floats in tables are per table
3357
    // and not per cell and layoutCell already takes care of doing the same as we do here
3358
34.8k
    if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
3359
34.8k
        QList<QTextFrame *> children = layoutStruct->frame->childFrames();
3360
34.8k
        for (int i = 0; i < children.size(); ++i) {
3361
0
            QTextFrameData *fd = data(children.at(i));
3362
0
            if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
3363
0
                layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
3364
0
        }
3365
34.8k
    }
3366
3367
34.8k
    if (inRootFrame) {
3368
        // we assume that any float is aligned in a way that disallows the optimizations that rely
3369
        // on unaligned content.
3370
34.8k
        if (!fd->floats.isEmpty())
3371
0
            contentHasAlignment = true;
3372
3373
34.8k
        if (it.atEnd() || layoutStruct->absoluteY() >= QFIXED_MAX) {
3374
            //qDebug("layout done!");
3375
34.0k
            currentLazyLayoutPosition = -1;
3376
34.0k
            QCheckPoint cp;
3377
34.0k
            cp.y = layoutStruct->y;
3378
34.0k
            cp.positionInFrame = docPrivate->length();
3379
34.0k
            cp.minimumWidth = layoutStruct->minimumWidth;
3380
34.0k
            cp.maximumWidth = layoutStruct->maximumWidth;
3381
34.0k
            cp.contentsWidth = layoutStruct->contentsWidth;
3382
34.0k
            checkPoints.append(cp);
3383
34.0k
            checkPoints.reserve(checkPoints.size());
3384
34.0k
        } else {
3385
771
            currentLazyLayoutPosition = checkPoints.constLast().positionInFrame;
3386
            // #######
3387
            //checkPoints.last().positionInFrame = QTextDocumentPrivate::get(q->document())->length();
3388
771
        }
3389
34.8k
    }
3390
3391
3392
34.8k
    fd->currentLayoutStruct = nullptr;
3393
34.8k
}
3394
3395
static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling,
3396
                                       QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight, QFixed *lineBottom)
3397
1.36M
{
3398
1.36M
    const qreal height = line.height();
3399
1.36M
    const int lineHeightType = blockFormat.lineHeightType();
3400
1.36M
    qreal rawHeight = qCeil(line.ascent() + line.descent() + line.leading());
3401
1.36M
    *lineHeight = QFixed::fromReal(blockFormat.lineHeight(rawHeight, scaling));
3402
1.36M
    *lineBottom = QFixed::fromReal(blockFormat.lineHeight(height, scaling));
3403
3404
1.36M
    if (lineHeightType == QTextBlockFormat::FixedHeight || lineHeightType == QTextBlockFormat::MinimumHeight) {
3405
0
        *lineBreakHeight = *lineBottom;
3406
0
        if (lineHeightType == QTextBlockFormat::FixedHeight)
3407
0
            *lineAdjustment = QFixed::fromReal(line.ascent() + qMax(line.leading(), qreal(0.0))) - ((*lineHeight * 4) / 5);
3408
0
        else
3409
0
            *lineAdjustment = QFixed::fromReal(height) - *lineHeight;
3410
0
    }
3411
1.36M
    else {
3412
1.36M
        *lineBreakHeight = QFixed::fromReal(height);
3413
1.36M
        *lineAdjustment = 0;
3414
1.36M
    }
3415
1.36M
}
3416
3417
void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
3418
                                             QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
3419
818k
{
3420
818k
    Q_Q(QTextDocumentLayout);
3421
818k
    if (!bl.isVisible())
3422
0
        return;
3423
3424
818k
    QTextLayout *tl = bl.layout();
3425
818k
    const int blockLength = bl.length();
3426
3427
818k
    qCDebug(lcLayout) << "layoutBlock from=" << layoutFrom << "to=" << layoutTo
3428
0
                      << "; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')';
3429
3430
818k
    if (previousBlockFormat) {
3431
783k
        qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
3432
783k
        if (margin > 0 && q->paintDevice()) {
3433
0
            margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
3434
0
        }
3435
783k
        layoutStruct->y += QFixed::fromReal(margin);
3436
783k
    }
3437
3438
    //QTextFrameData *fd = data(layoutStruct->frame);
3439
3440
818k
    Qt::LayoutDirection dir = bl.textDirection();
3441
3442
818k
    QFixed extraMargin;
3443
818k
    if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
3444
0
        QFontMetricsF fm(bl.charFormat().font());
3445
0
        extraMargin = QFixed::fromReal(fm.horizontalAdvance(u'\x21B5'));
3446
0
    }
3447
3448
818k
    const QFixed indent = this->blockIndent(blockFormat);
3449
818k
    const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
3450
818k
    const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
3451
3452
818k
    const QPointF oldPosition = tl->position();
3453
818k
    tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
3454
3455
818k
    if (layoutStruct->fullLayout
3456
424k
        || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
3457
        // force relayout if we cross a page boundary
3458
818k
        || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
3459
3460
818k
        qCDebug(lcLayout) << "do layout";
3461
818k
        QTextOption option = docPrivate->defaultTextOption;
3462
818k
        option.setTextDirection(dir);
3463
818k
        option.setTabs( blockFormat.tabPositions() );
3464
3465
818k
        Qt::Alignment align = docPrivate->defaultTextOption.alignment();
3466
818k
        if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
3467
0
            align = blockFormat.alignment();
3468
818k
        option.setAlignment(QGuiApplicationPrivate::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
3469
3470
818k
        if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
3471
0
            option.setWrapMode(QTextOption::ManualWrap);
3472
0
        }
3473
3474
818k
        tl->setTextOption(option);
3475
3476
818k
        const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
3477
3478
//         qDebug() << "    layouting block at" << bl.position();
3479
818k
        const QFixed cy = layoutStruct->y;
3480
818k
        const QFixed l = layoutStruct->x_left  + totalLeftMargin;
3481
818k
        const QFixed r = layoutStruct->x_right - totalRightMargin;
3482
818k
        QFixed bottom;
3483
3484
818k
        tl->beginLayout();
3485
818k
        bool firstLine = true;
3486
2.18M
        while (1) {
3487
2.18M
            QTextLine line = tl->createLine();
3488
2.18M
            if (!line.isValid())
3489
818k
                break;
3490
1.36M
            line.setLeadingIncluded(true);
3491
3492
1.36M
            QFixed left, right;
3493
1.36M
            floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3494
1.36M
            left = qMax(left, l);
3495
1.36M
            right = qMin(right, r);
3496
1.36M
            QFixed text_indent;
3497
1.36M
            if (firstLine) {
3498
818k
                text_indent = QFixed::fromReal(blockFormat.textIndent());
3499
818k
                if (dir == Qt::LeftToRight)
3500
818k
                    left += text_indent;
3501
0
                else
3502
0
                    right -= text_indent;
3503
818k
                firstLine = false;
3504
818k
            }
3505
//         qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
3506
3507
1.36M
            if (fixedColumnWidth != -1)
3508
0
                line.setNumColumns(fixedColumnWidth, (right - left).toReal());
3509
1.36M
            else
3510
1.36M
                line.setLineWidth((right - left).toReal());
3511
3512
//        qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
3513
1.36M
            floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3514
1.36M
            left = qMax(left, l);
3515
1.36M
            right = qMin(right, r);
3516
1.36M
            if (dir == Qt::LeftToRight)
3517
1.36M
                left += text_indent;
3518
0
            else
3519
0
                right -= text_indent;
3520
3521
1.36M
            if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
3522
                // float has been added in the meantime, redo
3523
80.3k
                layoutStruct->pendingFloats.clear();
3524
3525
80.3k
                line.setLineWidth((right-left).toReal());
3526
80.3k
                if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
3527
80.3k
                    if (haveWordOrAnyWrapMode) {
3528
80.3k
                        option.setWrapMode(QTextOption::WrapAnywhere);
3529
80.3k
                        tl->setTextOption(option);
3530
80.3k
                    }
3531
3532
80.3k
                    layoutStruct->pendingFloats.clear();
3533
                    // lines min width more than what we have
3534
80.3k
                    layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
3535
80.3k
                    floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3536
80.3k
                    left = qMax(left, l);
3537
80.3k
                    right = qMin(right, r);
3538
80.3k
                    if (dir == Qt::LeftToRight)
3539
80.3k
                        left += text_indent;
3540
0
                    else
3541
0
                        right -= text_indent;
3542
80.3k
                    line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
3543
3544
80.3k
                    if (haveWordOrAnyWrapMode) {
3545
80.3k
                        option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
3546
80.3k
                        tl->setTextOption(option);
3547
80.3k
                    }
3548
80.3k
                }
3549
3550
80.3k
            }
3551
3552
1.36M
            QFixed lineBreakHeight, lineHeight, lineAdjustment, lineBottom;
3553
1.36M
            qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
3554
1.36M
                            qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
3555
1.36M
            getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight, &lineBottom);
3556
3557
1.45M
            while (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom &&
3558
91.8k
                layoutStruct->contentHeight() >= lineBreakHeight) {
3559
91.8k
                if (layoutStruct->pageHeight == QFIXED_MAX) {
3560
0
                    layoutStruct->y = QFIXED_MAX - layoutStruct->frameY;
3561
0
                    break;
3562
0
                }
3563
3564
91.8k
                layoutStruct->newPage();
3565
3566
91.8k
                floatMargins(layoutStruct->y, layoutStruct, &left, &right);
3567
91.8k
                left = qMax(left, l);
3568
91.8k
                right = qMin(right, r);
3569
91.8k
                if (dir == Qt::LeftToRight)
3570
91.8k
                    left += text_indent;
3571
0
                else
3572
0
                    right -= text_indent;
3573
91.8k
            }
3574
3575
1.36M
            line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy - lineAdjustment).toReal()));
3576
1.36M
            bottom = layoutStruct->y + lineBottom;
3577
1.36M
            layoutStruct->y += lineHeight;
3578
1.36M
            layoutStruct->contentsWidth
3579
1.36M
                = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
3580
3581
            // position floats
3582
1.36M
            for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
3583
0
                QTextFrame *f = layoutStruct->pendingFloats.at(i);
3584
0
                positionFloat(f);
3585
0
            }
3586
1.36M
            layoutStruct->pendingFloats.clear();
3587
1.36M
        }
3588
818k
        layoutStruct->y = qMax(layoutStruct->y, bottom);
3589
818k
        tl->endLayout();
3590
818k
    } else {
3591
0
        const int cnt = tl->lineCount();
3592
0
        QFixed bottom;
3593
0
        for (int i = 0; i < cnt; ++i) {
3594
0
            qCDebug(lcLayout) << "going to move text line" << i;
3595
0
            QTextLine line = tl->lineAt(i);
3596
0
            layoutStruct->contentsWidth
3597
0
                = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
3598
3599
0
            QFixed lineBreakHeight, lineHeight, lineAdjustment, lineBottom;
3600
0
            qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
3601
0
                            qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
3602
0
            getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight, &lineBottom);
3603
3604
0
            if (layoutStruct->pageHeight != QFIXED_MAX) {
3605
0
                if (layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom)
3606
0
                    layoutStruct->newPage();
3607
0
                line.setPosition(QPointF(line.position().x(), (layoutStruct->y - lineAdjustment).toReal() - tl->position().y()));
3608
0
            }
3609
0
            bottom = layoutStruct->y + lineBottom;
3610
0
            layoutStruct->y += lineHeight;
3611
0
        }
3612
0
        layoutStruct->y = qMax(layoutStruct->y, bottom);
3613
0
        if (layoutStruct->updateRect.isValid()
3614
0
            && blockLength > 1) {
3615
0
            if (layoutFrom >= blockPosition + blockLength) {
3616
                // if our height didn't change and the change in the document is
3617
                // in one of the later paragraphs, then we don't need to repaint
3618
                // this one
3619
0
                layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
3620
0
            } else if (layoutTo < blockPosition) {
3621
0
                if (oldPosition == tl->position())
3622
                    // if the change in the document happened earlier in the document
3623
                    // and our position did /not/ change because none of the earlier paragraphs
3624
                    // or frames changed their height, then we don't need to repaint
3625
                    // this one
3626
0
                    layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
3627
0
                else
3628
0
                    layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
3629
0
            }
3630
0
        }
3631
0
    }
3632
3633
    // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
3634
818k
    const QFixed margins = totalLeftMargin + totalRightMargin;
3635
818k
    layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
3636
3637
818k
    const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
3638
3639
818k
    if (maxW > 0) {
3640
213k
        if (layoutStruct->maximumWidth == QFIXED_MAX)
3641
0
            layoutStruct->maximumWidth = maxW;
3642
213k
        else
3643
213k
            layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
3644
213k
    }
3645
818k
}
3646
3647
void QTextDocumentLayoutPrivate::floatMargins(QFixed y, const QTextLayoutStruct *layoutStruct,
3648
                                              QFixed *left, QFixed *right) const
3649
2.98M
{
3650
//     qDebug() << "floatMargins y=" << y;
3651
2.98M
    *left = layoutStruct->x_left;
3652
2.98M
    *right = layoutStruct->x_right;
3653
2.98M
    QTextFrameData *lfd = data(layoutStruct->frame);
3654
2.98M
    for (int i = 0; i < lfd->floats.size(); ++i) {
3655
0
        QTextFrameData *fd = data(lfd->floats.at(i));
3656
0
        if (!fd->layoutDirty) {
3657
0
            if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
3658
//                 qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
3659
0
                if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
3660
0
                    *left = qMax(*left, fd->position.x + fd->size.width);
3661
0
                else
3662
0
                    *right = qMin(*right, fd->position.x);
3663
0
            }
3664
0
        }
3665
0
    }
3666
//     qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
3667
2.98M
}
3668
3669
QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const
3670
80.3k
{
3671
80.3k
    QFixed right, left;
3672
80.3k
    requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
3673
3674
//     qDebug() << "findY:" << yFrom;
3675
80.3k
    while (1) {
3676
80.3k
        floatMargins(yFrom, layoutStruct, &left, &right);
3677
//         qDebug() << "    yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
3678
80.3k
        if (right-left >= requiredWidth)
3679
80.3k
            break;
3680
3681
        // move float down until we find enough space
3682
0
        QFixed newY = QFIXED_MAX;
3683
0
        QTextFrameData *lfd = data(layoutStruct->frame);
3684
0
        for (int i = 0; i < lfd->floats.size(); ++i) {
3685
0
            QTextFrameData *fd = data(lfd->floats.at(i));
3686
0
            if (!fd->layoutDirty) {
3687
0
                if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
3688
0
                    newY = qMin(newY, fd->position.y + fd->size.height);
3689
0
            }
3690
0
        }
3691
0
        if (newY == QFIXED_MAX)
3692
0
            break;
3693
0
        yFrom = newY;
3694
0
    }
3695
80.3k
    return yFrom;
3696
80.3k
}
3697
3698
QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
3699
17.4k
    : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
3700
17.4k
{
3701
17.4k
    registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
3702
17.4k
}
3703
3704
3705
void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
3706
17.4k
{
3707
17.4k
    Q_D(QTextDocumentLayout);
3708
17.4k
    QTextFrame *frame = d->document->rootFrame();
3709
17.4k
    QTextFrameData *fd = data(frame);
3710
3711
17.4k
    if (fd->sizeDirty)
3712
0
        return;
3713
3714
17.4k
    if (context.clip.isValid()) {
3715
17.4k
        d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
3716
17.4k
    } else {
3717
0
        d->ensureLayoutFinished();
3718
0
    }
3719
3720
17.4k
    QFixed width = fd->size.width;
3721
17.4k
    if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
3722
        // we're in NoWrap mode, meaning the frame should expand to the viewport
3723
        // so that backgrounds are drawn correctly
3724
0
        fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
3725
0
    }
3726
3727
    // Make sure we conform to the root frames bounds when drawing.
3728
17.4k
    d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
3729
17.4k
    d->drawFrame(QPointF(), painter, context, frame);
3730
17.4k
    fd->size.width = width;
3731
17.4k
}
3732
3733
void QTextDocumentLayout::setViewport(const QRectF &viewport)
3734
0
{
3735
0
    Q_D(QTextDocumentLayout);
3736
0
    d->viewportRect = viewport;
3737
0
}
3738
3739
static void markFrames(QTextFrame *current, int from, int oldLength, int length)
3740
34.8k
{
3741
34.8k
    int end = qMax(oldLength, length) + from;
3742
3743
34.8k
    if (current->firstPosition() >= end || current->lastPosition() < from)
3744
0
        return;
3745
3746
34.8k
    QTextFrameData *fd = data(current);
3747
    // float got removed in editing operation
3748
34.8k
    fd->floats.removeAll(nullptr);
3749
3750
34.8k
    fd->layoutDirty = true;
3751
34.8k
    fd->sizeDirty = true;
3752
3753
//     qDebug("    marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
3754
34.8k
    QList<QTextFrame *> children = current->childFrames();
3755
34.8k
    for (int i = 0; i < children.size(); ++i)
3756
0
        markFrames(children.at(i), from, oldLength, length);
3757
34.8k
}
3758
3759
void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
3760
34.8k
{
3761
34.8k
    Q_D(QTextDocumentLayout);
3762
3763
34.8k
    QTextBlock blockIt = document()->findBlock(from);
3764
34.8k
    QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
3765
34.8k
    if (endIt.isValid())
3766
34.8k
        endIt = endIt.next();
3767
883k
     for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
3768
848k
         blockIt.clearLayout();
3769
3770
34.8k
    if (!d->docPrivate->canLayout())
3771
0
        return;
3772
3773
34.8k
    QRectF updateRect;
3774
3775
34.8k
    d->lazyLayoutStepSize = 1000;
3776
34.8k
    d->sizeChangedTimer.stop();
3777
34.8k
    d->insideDocumentChange = true;
3778
3779
34.8k
    const int documentLength = d->docPrivate->length();
3780
34.8k
    const bool fullLayout = (oldLength == 0 && length == documentLength);
3781
34.8k
    const bool smallChange = documentLength > 0
3782
34.8k
                             && (qMax(length, oldLength) * 100 / documentLength) < 5;
3783
3784
    // don't show incremental layout progress (avoid scroll bar flicker)
3785
    // if we see only a small change in the document and we're either starting
3786
    // a layout run or we're already in progress for that and we haven't seen
3787
    // any bigger change previously (showLayoutProgress already false)
3788
34.8k
    if (smallChange
3789
0
        && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
3790
0
        d->showLayoutProgress = false;
3791
34.8k
    else
3792
34.8k
        d->showLayoutProgress = true;
3793
3794
34.8k
    if (fullLayout) {
3795
17.4k
        d->contentHasAlignment = false;
3796
17.4k
        d->currentLazyLayoutPosition = 0;
3797
17.4k
        d->checkPoints.clear();
3798
17.4k
        d->layoutStep();
3799
17.4k
    } else {
3800
17.4k
        d->ensureLayoutedByPosition(from);
3801
17.4k
        updateRect = doLayout(from, oldLength, length);
3802
17.4k
    }
3803
3804
34.8k
    if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
3805
771
        d->layoutTimer.start(10, this);
3806
3807
34.8k
    d->insideDocumentChange = false;
3808
3809
34.8k
    if (d->showLayoutProgress) {
3810
34.8k
        const QSizeF newSize = dynamicDocumentSize();
3811
34.8k
        if (newSize != d->lastReportedSize) {
3812
18.1k
            d->lastReportedSize = newSize;
3813
18.1k
            emit documentSizeChanged(newSize);
3814
18.1k
        }
3815
34.8k
    }
3816
3817
34.8k
    if (!updateRect.isValid()) {
3818
        // don't use the frame size, it might have shrunken
3819
17.4k
        updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
3820
17.4k
    }
3821
3822
34.8k
    emit update(updateRect);
3823
34.8k
}
3824
3825
QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
3826
34.8k
{
3827
34.8k
    Q_D(QTextDocumentLayout);
3828
3829
//     qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
3830
3831
    // mark all frames between f_start and f_end as dirty
3832
34.8k
    markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
3833
3834
34.8k
    QRectF updateRect;
3835
3836
34.8k
    QTextFrame *root = d->docPrivate->rootFrame();
3837
34.8k
    if (data(root)->sizeDirty)
3838
34.8k
        updateRect = d->layoutFrame(root, from, from + length);
3839
34.8k
    data(root)->layoutDirty = false;
3840
3841
34.8k
    if (d->currentLazyLayoutPosition == -1)
3842
34.0k
        layoutFinished();
3843
771
    else if (d->showLayoutProgress)
3844
771
        d->sizeChangedTimer.start(0, this);
3845
3846
34.8k
    return updateRect;
3847
34.8k
}
3848
3849
int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
3850
0
{
3851
0
    Q_D(const QTextDocumentLayout);
3852
0
    d->ensureLayouted(QFixed::fromReal(point.y()));
3853
0
    QTextFrame *f = d->docPrivate->rootFrame();
3854
0
    int position = 0;
3855
0
    QTextLayout *l = nullptr;
3856
0
    QFixedPoint pointf;
3857
0
    pointf.x = QFixed::fromReal(point.x());
3858
0
    pointf.y = QFixed::fromReal(point.y());
3859
0
    QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
3860
0
    if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
3861
0
        return -1;
3862
3863
    // ensure we stay within document bounds
3864
0
    int lastPos = f->lastPosition();
3865
0
    if (l && !l->preeditAreaText().isEmpty())
3866
0
        lastPos += l->preeditAreaText().size();
3867
0
    if (position > lastPos)
3868
0
        position = lastPos;
3869
0
    else if (position < 0)
3870
0
        position = 0;
3871
3872
0
    return position;
3873
0
}
3874
3875
void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
3876
18.3k
{
3877
18.3k
    Q_D(QTextDocumentLayout);
3878
18.3k
    QTextCharFormat f = format.toCharFormat();
3879
18.3k
    Q_ASSERT(f.isValid());
3880
18.3k
    QTextObjectHandler handler = d->handlers.value(f.objectType());
3881
18.3k
    if (!handler.component)
3882
18.3k
        return;
3883
3884
0
    QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
3885
3886
0
    QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
3887
0
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3888
0
    if (frame) {
3889
0
        pos = frame->frameFormat().position();
3890
0
        QTextFrameData *fd = data(frame);
3891
0
        fd->sizeDirty = false;
3892
0
        fd->size = QFixedSize::fromSizeF(intrinsic);
3893
0
        fd->minimumWidth = fd->maximumWidth = fd->size.width;
3894
0
    }
3895
3896
0
    QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
3897
0
    item.setWidth(inlineSize.width());
3898
3899
0
    switch (f.verticalAlignment()) {
3900
0
    case QTextCharFormat::AlignMiddle: {
3901
0
        QFontMetrics m(f.font());
3902
0
        qreal halfX = m.xHeight()/2.;
3903
0
        item.setAscent((inlineSize.height() + halfX) / 2.);
3904
0
        item.setDescent((inlineSize.height() - halfX) / 2.);
3905
0
        break;
3906
0
    }
3907
0
    case QTextCharFormat::AlignBaseline: {
3908
0
        QFontMetrics m(f.font());
3909
0
        qreal descent = m.descent();
3910
0
        item.setDescent(descent);
3911
0
        item.setAscent(inlineSize.height() - descent);
3912
0
        break;
3913
0
    }
3914
0
    default:
3915
0
        item.setDescent(0);
3916
0
        item.setAscent(inlineSize.height());
3917
0
        break;
3918
0
    }
3919
0
}
3920
3921
void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
3922
9.06k
{
3923
9.06k
    Q_D(QTextDocumentLayout);
3924
9.06k
    Q_UNUSED(posInDocument);
3925
9.06k
    if (item.width() != 0)
3926
        // inline
3927
9.06k
        return;
3928
3929
0
    QTextCharFormat f = format.toCharFormat();
3930
0
    Q_ASSERT(f.isValid());
3931
0
    QTextObjectHandler handler = d->handlers.value(f.objectType());
3932
0
    if (!handler.component)
3933
0
        return;
3934
3935
0
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3936
0
    if (!frame)
3937
0
        return;
3938
3939
0
    QTextBlock b = d->document->findBlock(frame->firstPosition());
3940
0
    QTextLine line;
3941
0
    if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
3942
0
        line = b.layout()->lineAt(b.layout()->lineCount()-1);
3943
//     qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
3944
//         frame->firstPosition() << frame->lastPosition();
3945
0
    d->positionFloat(frame, line.isValid() ? &line : nullptr);
3946
0
}
3947
3948
void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
3949
                                           int posInDocument, const QTextFormat &format)
3950
2.33k
{
3951
2.33k
    Q_D(QTextDocumentLayout);
3952
2.33k
    QTextCharFormat f = format.toCharFormat();
3953
2.33k
    Q_ASSERT(f.isValid());
3954
2.33k
    QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3955
2.33k
    if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
3956
0
        return; // don't draw floating frames from inline objects here but in drawFlow instead
3957
3958
//    qDebug() << "drawObject at" << r;
3959
2.33k
    QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
3960
2.33k
}
3961
3962
int QTextDocumentLayout::dynamicPageCount() const
3963
0
{
3964
0
    Q_D(const QTextDocumentLayout);
3965
0
    const QSizeF pgSize = d->document->pageSize();
3966
0
    if (pgSize.height() < 0)
3967
0
        return 1;
3968
0
    return qCeil(dynamicDocumentSize().height() / pgSize.height());
3969
0
}
3970
3971
QSizeF QTextDocumentLayout::dynamicDocumentSize() const
3972
35.5k
{
3973
35.5k
    Q_D(const QTextDocumentLayout);
3974
35.5k
    return data(d->docPrivate->rootFrame())->size.toSizeF();
3975
35.5k
}
3976
3977
int QTextDocumentLayout::pageCount() const
3978
0
{
3979
0
    Q_D(const QTextDocumentLayout);
3980
0
    d->ensureLayoutFinished();
3981
0
    return dynamicPageCount();
3982
0
}
3983
3984
QSizeF QTextDocumentLayout::documentSize() const
3985
0
{
3986
0
    Q_D(const QTextDocumentLayout);
3987
0
    d->ensureLayoutFinished();
3988
0
    return dynamicDocumentSize();
3989
0
}
3990
3991
void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
3992
17.4k
{
3993
17.4k
    Q_Q(const QTextDocumentLayout);
3994
17.4k
    if (currentLazyLayoutPosition == -1)
3995
16.6k
        return;
3996
771
    const QSizeF oldSize = q->dynamicDocumentSize();
3997
771
    Q_UNUSED(oldSize);
3998
3999
771
    if (checkPoints.isEmpty())
4000
0
        layoutStep();
4001
4002
771
    while (currentLazyLayoutPosition != -1
4003
771
           && checkPoints.last().y < y)
4004
0
        layoutStep();
4005
771
}
4006
4007
void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
4008
34.8k
{
4009
34.8k
    if (currentLazyLayoutPosition == -1)
4010
16.6k
        return;
4011
18.1k
    if (position < currentLazyLayoutPosition)
4012
771
        return;
4013
34.8k
    while (currentLazyLayoutPosition != -1
4014
18.1k
           && currentLazyLayoutPosition < position) {
4015
17.4k
        const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
4016
17.4k
    }
4017
17.4k
}
4018
4019
void QTextDocumentLayoutPrivate::layoutStep() const
4020
17.4k
{
4021
17.4k
    ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
4022
17.4k
    lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
4023
17.4k
}
4024
4025
void QTextDocumentLayout::setCursorWidth(int width)
4026
0
{
4027
0
    Q_D(QTextDocumentLayout);
4028
0
    d->cursorWidth = width;
4029
0
}
4030
4031
int QTextDocumentLayout::cursorWidth() const
4032
0
{
4033
0
    Q_D(const QTextDocumentLayout);
4034
0
    return d->cursorWidth;
4035
0
}
4036
4037
void QTextDocumentLayout::setFixedColumnWidth(int width)
4038
0
{
4039
0
    Q_D(QTextDocumentLayout);
4040
0
    d->fixedColumnWidth = width;
4041
0
}
4042
4043
QRectF QTextDocumentLayout::tableCellBoundingRect(QTextTable *table, const QTextTableCell &cell) const
4044
0
{
4045
0
    if (!cell.isValid())
4046
0
        return QRectF();
4047
4048
0
    QTextTableData *td = static_cast<QTextTableData *>(data(table));
4049
4050
0
    QRectF tableRect = tableBoundingRect(table);
4051
0
    QRectF cellRect = td->cellRect(cell);
4052
4053
0
    return cellRect.translated(tableRect.topLeft());
4054
0
}
4055
4056
QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
4057
0
{
4058
0
    Q_D(const QTextDocumentLayout);
4059
0
    if (!d->docPrivate->canLayout())
4060
0
        return QRectF();
4061
0
    d->ensureLayoutFinished();
4062
4063
0
    QPointF pos;
4064
0
    const int framePos = table->firstPosition();
4065
0
    QTextFrame *f = table;
4066
0
    while (f) {
4067
0
        QTextFrameData *fd = data(f);
4068
0
        pos += fd->position.toPointF();
4069
4070
0
        if (f != table) {
4071
0
            if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
4072
0
                QTextTableCell cell = table->cellAt(framePos);
4073
0
                if (cell.isValid())
4074
0
                    pos += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
4075
0
            }
4076
0
        }
4077
4078
0
        f = f->parentFrame();
4079
0
    }
4080
0
    return QRectF(pos, data(table)->size.toSizeF());
4081
0
}
4082
4083
QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
4084
0
{
4085
0
    Q_D(const QTextDocumentLayout);
4086
0
    if (!d->docPrivate->canLayout())
4087
0
        return QRectF();
4088
0
    d->ensureLayoutFinished();
4089
0
    return d->frameBoundingRectInternal(frame);
4090
0
}
4091
4092
QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
4093
0
{
4094
0
    QPointF pos;
4095
0
    const int framePos = frame->firstPosition();
4096
0
    QTextFrame *f = frame;
4097
0
    while (f) {
4098
0
        QTextFrameData *fd = data(f);
4099
0
        pos += fd->position.toPointF();
4100
4101
0
        if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
4102
0
            QTextTableCell cell = table->cellAt(framePos);
4103
0
            if (cell.isValid())
4104
0
                pos += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
4105
0
        }
4106
4107
0
        f = f->parentFrame();
4108
0
    }
4109
0
    return QRectF(pos, data(frame)->size.toSizeF());
4110
0
}
4111
4112
QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
4113
0
{
4114
0
    Q_D(const QTextDocumentLayout);
4115
0
    if (!d->docPrivate->canLayout() || !block.isValid() || !block.isVisible())
4116
0
        return QRectF();
4117
0
    d->ensureLayoutedByPosition(block.position() + block.length());
4118
0
    QTextFrame *frame = d->document->frameAt(block.position());
4119
0
    QPointF offset;
4120
0
    const int blockPos = block.position();
4121
4122
0
    while (frame) {
4123
0
        QTextFrameData *fd = data(frame);
4124
0
        offset += fd->position.toPointF();
4125
4126
0
        if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
4127
0
            QTextTableCell cell = table->cellAt(blockPos);
4128
0
            if (cell.isValid())
4129
0
                offset += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
4130
0
        }
4131
4132
0
        frame = frame->parentFrame();
4133
0
    }
4134
4135
0
    const QTextLayout *layout = block.layout();
4136
0
    QRectF rect = layout->boundingRect();
4137
0
    rect.moveTopLeft(layout->position() + offset);
4138
0
    return rect;
4139
0
}
4140
4141
int QTextDocumentLayout::layoutStatus() const
4142
0
{
4143
0
    Q_D(const QTextDocumentLayout);
4144
0
    int pos = d->currentLazyLayoutPosition;
4145
0
    if (pos == -1)
4146
0
        return 100;
4147
0
    return pos * 100 / QTextDocumentPrivate::get(d->document)->length();
4148
0
}
4149
4150
void QTextDocumentLayout::timerEvent(QTimerEvent *e)
4151
0
{
4152
0
    Q_D(QTextDocumentLayout);
4153
0
    if (e->timerId() == d->layoutTimer.timerId()) {
4154
0
        if (d->currentLazyLayoutPosition != -1)
4155
0
            d->layoutStep();
4156
0
    } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
4157
0
        d->lastReportedSize = dynamicDocumentSize();
4158
0
        emit documentSizeChanged(d->lastReportedSize);
4159
0
        d->sizeChangedTimer.stop();
4160
4161
0
        if (d->currentLazyLayoutPosition == -1) {
4162
0
            const int newCount = dynamicPageCount();
4163
0
            if (newCount != d->lastPageCount) {
4164
0
                d->lastPageCount = newCount;
4165
0
                emit pageCountChanged(newCount);
4166
0
            }
4167
0
        }
4168
0
    } else {
4169
0
        QAbstractTextDocumentLayout::timerEvent(e);
4170
0
    }
4171
0
}
4172
4173
void QTextDocumentLayout::layoutFinished()
4174
34.0k
{
4175
34.0k
    Q_D(QTextDocumentLayout);
4176
34.0k
    d->layoutTimer.stop();
4177
34.0k
    if (!d->insideDocumentChange)
4178
0
        d->sizeChangedTimer.start(0, this);
4179
    // reset
4180
34.0k
    d->showLayoutProgress = true;
4181
34.0k
}
4182
4183
void QTextDocumentLayout::ensureLayouted(qreal y)
4184
0
{
4185
0
    d_func()->ensureLayouted(QFixed::fromReal(y));
4186
0
}
4187
4188
qreal QTextDocumentLayout::idealWidth() const
4189
0
{
4190
0
    Q_D(const QTextDocumentLayout);
4191
0
    d->ensureLayoutFinished();
4192
0
    return d->idealWidth;
4193
0
}
4194
4195
bool QTextDocumentLayout::contentHasAlignment() const
4196
0
{
4197
0
    Q_D(const QTextDocumentLayout);
4198
0
    return d->contentHasAlignment;
4199
0
}
4200
4201
qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
4202
208k
{
4203
208k
    if (!paintDevice)
4204
208k
        return value;
4205
0
    return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi());
4206
208k
}
4207
4208
QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
4209
0
{
4210
0
    if (!paintDevice)
4211
0
        return value;
4212
0
    return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi());
4213
0
}
4214
4215
QT_END_NAMESPACE
4216
4217
#include "moc_qtextdocumentlayout_p.cpp"