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