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