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

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