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

source code of qtbase/src/gui/text/qtextdocumentlayout.cpp