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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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