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