1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtextdocumentlayout_p.h"
41#include "qtextdocument_p.h"
42#include "qtextimagehandler_p.h"
43#include "qtexttable.h"
44#include "qtextlist.h"
45#include "qtextengine_p.h"
46#include "private/qcssutil_p.h"
47#include "private/qguiapplication_p.h"
48
49#include "qabstracttextdocumentlayout_p.h"
50#include "qcssparser_p.h"
51
52#include <qpainter.h>
53#include <qmath.h>
54#include <qrect.h>
55#include <qpalette.h>
56#include <qdebug.h>
57#include <qvarlengtharray.h>
58#include <limits.h>
59#include <qbasictimer.h>
60#include "private/qfunctions_p.h"
61#include <qloggingcategory.h>
62
63#include <algorithm>
64
65QT_BEGIN_NAMESPACE
66
67Q_LOGGING_CATEGORY(lcDraw, "qt.text.drawing")
68Q_LOGGING_CATEGORY(lcHit, "qt.text.hittest")
69Q_LOGGING_CATEGORY(lcLayout, "qt.text.layout")
70Q_LOGGING_CATEGORY(lcTable, "qt.text.layout.table")
71
72// ################ should probably add frameFormatChange notification!
73
74struct QTextLayoutStruct;
75
76class QTextFrameData : public QTextFrameLayoutData
77{
78public:
79 QTextFrameData();
80
81 // relative to parent frame
82 QFixedPoint position;
83 QFixedSize size;
84
85 // contents starts at (margin+border/margin+border)
86 QFixed topMargin;
87 QFixed bottomMargin;
88 QFixed leftMargin;
89 QFixed rightMargin;
90 QFixed border;
91 QFixed padding;
92 // contents width includes padding (as we need to treat this on a per cell basis for tables)
93 QFixed contentsWidth;
94 QFixed contentsHeight;
95 QFixed oldContentsWidth;
96
97 // accumulated margins
98 QFixed effectiveTopMargin;
99 QFixed effectiveBottomMargin;
100
101 QFixed minimumWidth;
102 QFixed maximumWidth;
103
104 QTextLayoutStruct *currentLayoutStruct;
105
106 bool sizeDirty;
107 bool layoutDirty;
108
109 QVector<QPointer<QTextFrame> > floats;
110};
111
112QTextFrameData::QTextFrameData()
113 : maximumWidth(QFIXED_MAX),
114 currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
115{
116}
117
118struct QTextLayoutStruct {
119 QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
120 {}
121 QTextFrame *frame;
122 QFixed x_left;
123 QFixed x_right;
124 QFixed frameY; // absolute y position of the current frame
125 QFixed y; // always relative to the current frame
126 QFixed contentsWidth;
127 QFixed minimumWidth;
128 QFixed maximumWidth;
129 bool fullLayout;
130 QList<QTextFrame *> pendingFloats;
131 QFixed pageHeight;
132 QFixed pageBottom;
133 QFixed pageTopMargin;
134 QFixed pageBottomMargin;
135 QRectF updateRect;
136 QRectF updateRectForFloats;
137
138 inline void addUpdateRectForFloat(const QRectF &rect) {
139 if (updateRectForFloats.isValid())
140 updateRectForFloats |= rect;
141 else
142 updateRectForFloats = rect;
143 }
144
145 inline QFixed absoluteY() const
146 { return frameY + y; }
147
148 inline QFixed contentHeight() const
149 { return pageHeight - pageBottomMargin - pageTopMargin; }
150
151 inline int currentPage() const
152 { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
153
154 inline void newPage()
155 { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = qMax(y, pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY); }
156};
157
158class QTextTableData : public QTextFrameData
159{
160public:
161 QFixed cellSpacing, cellPadding;
162 qreal deviceScale;
163 QVector<QFixed> minWidths;
164 QVector<QFixed> maxWidths;
165 QVector<QFixed> widths;
166 QVector<QFixed> heights;
167 QVector<QFixed> columnPositions;
168 QVector<QFixed> rowPositions;
169
170 QVector<QFixed> cellVerticalOffsets;
171
172 QFixed headerHeight;
173
174 // maps from cell index (row + col * rowCount) to child frames belonging to
175 // the specific cell
176 QMultiHash<int, QTextFrame *> childFrameMap;
177
178 inline QFixed cellWidth(int column, int colspan) const
179 { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
180 - columnPositions.at(column); }
181
182 inline void calcRowPosition(int row)
183 {
184 if (row > 0)
185 rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
186 }
187
188 QRectF cellRect(const QTextTableCell &cell) const;
189
190 inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
191 {
192 QVariant v = format.property(property);
193 if (v.isNull()) {
194 return cellPadding;
195 } else {
196 Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float);
197 return QFixed::fromReal(v.toReal() * deviceScale);
198 }
199 }
200
201 inline QFixed topPadding(const QTextFormat &format) const
202 {
203 return paddingProperty(format, QTextFormat::TableCellTopPadding);
204 }
205
206 inline QFixed bottomPadding(const QTextFormat &format) const
207 {
208 return paddingProperty(format, QTextFormat::TableCellBottomPadding);
209 }
210
211 inline QFixed leftPadding(const QTextFormat &format) const
212 {
213 return paddingProperty(format, QTextFormat::TableCellLeftPadding);
214 }
215
216 inline QFixed rightPadding(const QTextFormat &format) const
217 {
218 return paddingProperty(format, QTextFormat::TableCellRightPadding);
219 }
220
221 inline QFixedPoint cellPosition(const QTextTableCell &cell) const
222 {
223 const QTextFormat fmt = cell.format();
224 return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
225 }
226
227 void updateTableSize();
228
229private:
230 inline QFixedPoint cellPosition(int row, int col) const
231 { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
232};
233
234static QTextFrameData *createData(QTextFrame *f)
235{
236 QTextFrameData *data;
237 if (qobject_cast<QTextTable *>(f))
238 data = new QTextTableData;
239 else
240 data = new QTextFrameData;
241 f->setLayoutData(data);
242 return data;
243}
244
245static inline QTextFrameData *data(QTextFrame *f)
246{
247 QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
248 if (!data)
249 data = createData(f);
250 return data;
251}
252
253static bool isFrameFromInlineObject(QTextFrame *f)
254{
255 return f->firstPosition() > f->lastPosition();
256}
257
258void QTextTableData::updateTableSize()
259{
260 const QFixed effectiveTopMargin = this->topMargin + border + padding;
261 const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
262 const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
263 const QFixed effectiveRightMargin = this->rightMargin + border + padding;
264 size.height = contentsHeight == -1
265 ? rowPositions.constLast() + heights.constLast() + padding + border + cellSpacing + effectiveBottomMargin
266 : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
267 size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
268}
269
270QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
271{
272 const int row = cell.row();
273 const int rowSpan = cell.rowSpan();
274 const int column = cell.column();
275 const int colSpan = cell.columnSpan();
276
277 return QRectF(columnPositions.at(column).toReal(),
278 rowPositions.at(row).toReal(),
279 (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
280 (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
281}
282
283static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
284{
285 return !nextIt.atEnd()
286 && qobject_cast<QTextTable *>(nextIt.currentFrame())
287 && block.isValid()
288 && block.length() == 1
289 && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
290 && !format.hasProperty(QTextFormat::BackgroundBrush)
291 && nextIt.currentFrame()->firstPosition() == block.position() + 1
292 ;
293}
294
295static inline bool isEmptyBlockBeforeTable(const QTextFrame::Iterator &it)
296{
297 QTextFrame::Iterator next = it; ++next;
298 if (it.currentFrame())
299 return false;
300 QTextBlock block = it.currentBlock();
301 return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
302}
303
304static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
305{
306 return qobject_cast<const QTextTable *>(previousFrame)
307 && block.isValid()
308 && block.length() == 1
309 && previousFrame->lastPosition() == block.position() - 1
310 ;
311}
312
313static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
314{
315 return qobject_cast<const QTextTable *>(previousFrame)
316 && block.isValid()
317 && block.length() > 1
318 && block.text().at(0) == QChar::LineSeparator
319 && previousFrame->lastPosition() == block.position() - 1
320 ;
321}
322
323/*
324
325Optimization strategies:
326
327HTML layout:
328
329* Distinguish between normal and special flow. For normal flow the condition:
330 y1 > y2 holds for all blocks with b1.key() > b2.key().
331* Special flow is: floats, table cells
332
333* Normal flow within table cells. Tables (not cells) are part of the normal flow.
334
335
336* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
337* If height doesn't change, no need to do anything
338
339Table cells:
340
341* If minWidth of cell changes, recalculate table width, relayout if needed.
342* What about maxWidth when doing auto layout?
343
344Floats:
345* need fixed or proportional width, otherwise don't float!
346* On width/height change relayout surrounding paragraphs.
347
348Document width change:
349* full relayout needed
350
351
352Float handling:
353
354* Floats are specified by a special format object.
355* currently only floating images are implemented.
356
357*/
358
359/*
360
361 On the table layouting:
362
363 +---[ table border ]-------------------------
364 | [ cell spacing ]
365 | +------[ cell border ]-----+ +--------
366 | | | |
367 | |
368 | |
369 | |
370 |
371
372 rowPositions[i] and columnPositions[i] point at the cell content
373 position. So for example the left border is drawn at
374 x = columnPositions[i] - fd->border and similar for y.
375
376*/
377
378struct QCheckPoint
379{
380 QFixed y;
381 QFixed frameY; // absolute y position of the current frame
382 int positionInFrame;
383 QFixed minimumWidth;
384 QFixed maximumWidth;
385 QFixed contentsWidth;
386};
387Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
388
389static bool operator<(const QCheckPoint &checkPoint, QFixed y)
390{
391 return checkPoint.y < y;
392}
393
394static bool operator<(const QCheckPoint &checkPoint, int pos)
395{
396 return checkPoint.positionInFrame < pos;
397}
398
399static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, const QRectF &gradientRect = QRectF())
400{
401 p->save();
402 if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
403 if (!gradientRect.isNull()) {
404 QTransform m;
405 m.translate(gradientRect.left(), gradientRect.top());
406 m.scale(gradientRect.width(), gradientRect.height());
407 brush.setTransform(m);
408 const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
409 }
410 } else {
411 p->setBrushOrigin(origin);
412 }
413 p->fillRect(rect, brush);
414 p->restore();
415}
416
417class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
418{
419 Q_DECLARE_PUBLIC(QTextDocumentLayout)
420public:
421 QTextDocumentLayoutPrivate();
422
423 QTextOption::WrapMode wordWrapMode;
424#ifdef LAYOUT_DEBUG
425 mutable QString debug_indent;
426#endif
427
428 int fixedColumnWidth;
429 int cursorWidth;
430
431 QSizeF lastReportedSize;
432 QRectF viewportRect;
433 QRectF clipRect;
434
435 mutable int currentLazyLayoutPosition;
436 mutable int lazyLayoutStepSize;
437 QBasicTimer layoutTimer;
438 mutable QBasicTimer sizeChangedTimer;
439 uint showLayoutProgress : 1;
440 uint insideDocumentChange : 1;
441
442 int lastPageCount;
443 qreal idealWidth;
444 bool contentHasAlignment;
445
446 QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
447
448 void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
449 QTextFrame *f) const;
450 void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
451 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
452 void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
453 const QTextBlock &bl, bool inRootFrame) const;
454 void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
455 const QTextBlock &bl, const QTextCharFormat *selectionFormat) const;
456 void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
457 QTextTable *table, QTextTableData *td, int r, int c,
458 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
459 void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
460 const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
461 void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
462
463 enum HitPoint {
464 PointBefore,
465 PointAfter,
466 PointInside,
467 PointExact
468 };
469 HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
470 HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
471 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
472 HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
473 HitPoint hitTest(const QTextBlock &bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
474
475 QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
476 int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
477 bool withPageBreaks);
478 void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
479 QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
480
481 void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0);
482
483 // calls the next one
484 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
485 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
486
487 void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
488 QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
489 void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
490
491 void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
492 QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const;
493
494 QVector<QCheckPoint> checkPoints;
495
496 QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
497 QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
498
499 void ensureLayouted(QFixed y) const;
500 void ensureLayoutedByPosition(int position) const;
501 inline void ensureLayoutFinished() const
502 { ensureLayoutedByPosition(INT_MAX); }
503 void layoutStep() const;
504
505 QRectF frameBoundingRectInternal(QTextFrame *frame) const;
506
507 qreal scaleToDevice(qreal value) const;
508 QFixed scaleToDevice(QFixed value) const;
509};
510
511QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
512 : fixedColumnWidth(-1),
513 cursorWidth(1),
514 currentLazyLayoutPosition(-1),
515 lazyLayoutStepSize(1000),
516 lastPageCount(-1)
517{
518 showLayoutProgress = true;
519 insideDocumentChange = false;
520 idealWidth = 0;
521 contentHasAlignment = false;
522}
523
524QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
525{
526 QTextFrame *rootFrame = document->rootFrame();
527
528 if (checkPoints.isEmpty()
529 || y < 0 || y > data(rootFrame)->size.height)
530 return rootFrame->begin();
531
532 QVector<QCheckPoint>::ConstIterator checkPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), y);
533 if (checkPoint == checkPoints.end())
534 return rootFrame->begin();
535
536 if (checkPoint != checkPoints.begin())
537 --checkPoint;
538
539 const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
540 return frameIteratorForTextPosition(position);
541}
542
543QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
544{
545 QTextFrame *rootFrame = docPrivate->rootFrame();
546
547 const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
548 const int begin = map.findNode(rootFrame->firstPosition());
549 const int end = map.findNode(rootFrame->lastPosition()+1);
550
551 const int block = map.findNode(position);
552 const int blockPos = map.position(block);
553
554 QTextFrame::iterator it(rootFrame, block, begin, end);
555
556 QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
557 if (containingFrame != rootFrame) {
558 while (containingFrame->parentFrame() != rootFrame) {
559 containingFrame = containingFrame->parentFrame();
560 Q_ASSERT(containingFrame);
561 }
562
563 it.cf = containingFrame;
564 it.cb = 0;
565 }
566
567 return it;
568}
569
570QTextDocumentLayoutPrivate::HitPoint
571QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
572{
573 QTextFrameData *fd = data(frame);
574 // #########
575 if (fd->layoutDirty)
576 return PointAfter;
577 Q_ASSERT(!fd->layoutDirty);
578 Q_ASSERT(!fd->sizeDirty);
579 const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
580
581 QTextFrame *rootFrame = docPrivate->rootFrame();
582
583 qCDebug(lcHit) << "checking frame" << frame->firstPosition() << "point=" << point.toPointF()
584 << "position" << fd->position.toPointF() << "size" << fd->size.toSizeF();
585 if (frame != rootFrame) {
586 if (relativePoint.y < 0 || relativePoint.x < 0) {
587 *position = frame->firstPosition() - 1;
588 qCDebug(lcHit) << "before pos=" << *position;
589 return PointBefore;
590 } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
591 *position = frame->lastPosition() + 1;
592 qCDebug(lcHit) << "after pos=" << *position;
593 return PointAfter;
594 }
595 }
596
597 if (isFrameFromInlineObject(frame)) {
598 *position = frame->firstPosition() - 1;
599 return PointExact;
600 }
601
602 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
603 const int rows = table->rows();
604 const int columns = table->columns();
605 QTextTableData *td = static_cast<QTextTableData *>(data(table));
606
607 if (!td->childFrameMap.isEmpty()) {
608 for (int r = 0; r < rows; ++r) {
609 for (int c = 0; c < columns; ++c) {
610 QTextTableCell cell = table->cellAt(r, c);
611 if (cell.row() != r || cell.column() != c)
612 continue;
613
614 QRectF cellRect = td->cellRect(cell);
615 const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
616 const QFixedPoint pointInCell = relativePoint - cellPos;
617
618 const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
619 for (int i = 0; i < childFrames.size(); ++i) {
620 QTextFrame *child = childFrames.at(i);
621 if (isFrameFromInlineObject(child)
622 && child->frameFormat().position() != QTextFrameFormat::InFlow
623 && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
624 {
625 return PointExact;
626 }
627 }
628 }
629 }
630 }
631
632 return hitTest(table, relativePoint, position, l, accuracy);
633 }
634
635 const QList<QTextFrame *> childFrames = frame->childFrames();
636 for (int i = 0; i < childFrames.size(); ++i) {
637 QTextFrame *child = childFrames.at(i);
638 if (isFrameFromInlineObject(child)
639 && child->frameFormat().position() != QTextFrameFormat::InFlow
640 && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
641 {
642 return PointExact;
643 }
644 }
645
646 QTextFrame::Iterator it = frame->begin();
647
648 if (frame == rootFrame) {
649 it = frameIteratorForYPosition(relativePoint.y);
650
651 Q_ASSERT(it.parentFrame() == frame);
652 }
653
654 if (it.currentFrame())
655 *position = it.currentFrame()->firstPosition();
656 else
657 *position = it.currentBlock().position();
658
659 return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
660}
661
662QTextDocumentLayoutPrivate::HitPoint
663QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
664 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
665{
666 for (; !it.atEnd(); ++it) {
667 QTextFrame *c = it.currentFrame();
668 HitPoint hp;
669 int pos = -1;
670 if (c) {
671 hp = hitTest(c, p, &pos, l, accuracy);
672 } else {
673 hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
674 }
675 if (hp >= PointInside) {
676 if (isEmptyBlockBeforeTable(it))
677 continue;
678 hit = hp;
679 *position = pos;
680 break;
681 }
682 if (hp == PointBefore && pos < *position) {
683 *position = pos;
684 hit = hp;
685 } else if (hp == PointAfter && pos > *position) {
686 *position = pos;
687 hit = hp;
688 }
689 }
690
691 qCDebug(lcHit) << "inside=" << hit << " pos=" << *position;
692 return hit;
693}
694
695QTextDocumentLayoutPrivate::HitPoint
696QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
697 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
698{
699 QTextTableData *td = static_cast<QTextTableData *>(data(table));
700
701 QVector<QFixed>::ConstIterator rowIt = std::lower_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
702 if (rowIt == td->rowPositions.constEnd()) {
703 rowIt = td->rowPositions.constEnd() - 1;
704 } else if (rowIt != td->rowPositions.constBegin()) {
705 --rowIt;
706 }
707
708 QVector<QFixed>::ConstIterator colIt = std::lower_bound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
709 if (colIt == td->columnPositions.constEnd()) {
710 colIt = td->columnPositions.constEnd() - 1;
711 } else if (colIt != td->columnPositions.constBegin()) {
712 --colIt;
713 }
714
715 QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
716 colIt - td->columnPositions.constBegin());
717 if (!cell.isValid())
718 return PointBefore;
719
720 *position = cell.firstPosition();
721
722 HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
723
724 if (hp == PointExact)
725 return hp;
726 if (hp == PointAfter)
727 *position = cell.lastPosition();
728 return PointInside;
729}
730
731QTextDocumentLayoutPrivate::HitPoint
732QTextDocumentLayoutPrivate::hitTest(const QTextBlock &bl, const QFixedPoint &point, int *position, QTextLayout **l,
733 Qt::HitTestAccuracy accuracy) const
734{
735 QTextLayout *tl = bl.layout();
736 QRectF textrect = tl->boundingRect();
737 textrect.translate(tl->position());
738 qCDebug(lcHit) << " checking block" << bl.position() << "point=" << point.toPointF() << " tlrect" << textrect;
739 *position = bl.position();
740 if (point.y.toReal() < textrect.top()) {
741 qCDebug(lcHit) << " before pos=" << *position;
742 return PointBefore;
743 } else if (point.y.toReal() > textrect.bottom()) {
744 *position += bl.length();
745 qCDebug(lcHit) << " after pos=" << *position;
746 return PointAfter;
747 }
748
749 QPointF pos = point.toPointF() - tl->position();
750
751 // ### rtl?
752
753 HitPoint hit = PointInside;
754 *l = tl;
755 int off = 0;
756 for (int i = 0; i < tl->lineCount(); ++i) {
757 QTextLine line = tl->lineAt(i);
758 const QRectF lr = line.naturalTextRect();
759 if (lr.top() > pos.y()) {
760 off = qMin(off, line.textStart());
761 } else if (lr.bottom() <= pos.y()) {
762 off = qMax(off, line.textStart() + line.textLength());
763 } else {
764 if (lr.left() <= pos.x() && lr.right() >= pos.x())
765 hit = PointExact;
766 // when trying to hit an anchor we want it to hit not only in the left
767 // half
768 if (accuracy == Qt::ExactHit)
769 off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
770 else
771 off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
772 break;
773 }
774 }
775 *position += off;
776
777 qCDebug(lcHit) << " inside=" << hit << " pos=" << *position;
778 return hit;
779}
780
781// ### could be moved to QTextBlock
782QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
783{
784 qreal indent = blockFormat.indent();
785
786 QTextObject *object = document->objectForFormat(blockFormat);
787 if (object)
788 indent += object->format().toListFormat().indent();
789
790 if (qIsNull(indent))
791 return 0;
792
793 qreal scale = 1;
794 if (paintDevice) {
795 scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
796 }
797
798 return QFixed::fromReal(indent * scale * document->indentWidth());
799}
800
801void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
802 qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
803{
804 const qreal pageHeight = document->pageSize().height();
805 const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
806 const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
807
808#ifndef QT_NO_CSSPARSER
809 QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
810#else
811 Q_UNUSED(style);
812#endif //QT_NO_CSSPARSER
813
814 bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
815 painter->setRenderHint(QPainter::Antialiasing);
816
817 for (int i = topPage; i <= bottomPage; ++i) {
818 QRectF clipped = rect.toRect();
819
820 if (topPage != bottomPage) {
821 clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
822 clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
823
824 if (clipped.bottom() <= clipped.top())
825 continue;
826 }
827#ifndef QT_NO_CSSPARSER
828 qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
829 qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
830 qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
831 qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
832#else
833 painter->save();
834 painter->setPen(Qt::NoPen);
835 painter->setBrush(brush);
836 painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
837 painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
838 painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
839 painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
840 painter->restore();
841#endif //QT_NO_CSSPARSER
842 }
843 if (turn_off_antialiasing)
844 painter->setRenderHint(QPainter::Antialiasing, false);
845}
846
847void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
848{
849
850 const QBrush bg = frame->frameFormat().background();
851 if (bg != Qt::NoBrush) {
852 QRectF bgRect = rect;
853 bgRect.adjust((fd->leftMargin + fd->border).toReal(),
854 (fd->topMargin + fd->border).toReal(),
855 - (fd->rightMargin + fd->border).toReal(),
856 - (fd->bottomMargin + fd->border).toReal());
857
858 QRectF gradientRect; // invalid makes it default to bgRect
859 QPointF origin = bgRect.topLeft();
860 if (!frame->parentFrame()) {
861 bgRect = clip;
862 gradientRect.setWidth(painter->device()->width());
863 gradientRect.setHeight(painter->device()->height());
864 }
865 fillBackground(painter, bgRect, bg, origin, gradientRect);
866 }
867 if (fd->border != 0) {
868 painter->save();
869 painter->setBrush(Qt::lightGray);
870 painter->setPen(Qt::NoPen);
871
872 const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
873 const qreal border = fd->border.toReal();
874 const qreal topMargin = fd->topMargin.toReal();
875 const qreal leftMargin = fd->leftMargin.toReal();
876 const qreal bottomMargin = fd->bottomMargin.toReal();
877 const qreal rightMargin = fd->rightMargin.toReal();
878 const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
879 const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
880
881 drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
882 fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
883 border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
884
885 painter->restore();
886 }
887}
888
889static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
890 const QTextTableCell &cell,
891 int r, int c,
892 const int *selectedTableCells)
893{
894 for (int i = 0; i < cell_context.selections.size(); ++i) {
895 int row_start = selectedTableCells[i * 4];
896 int col_start = selectedTableCells[i * 4 + 1];
897 int num_rows = selectedTableCells[i * 4 + 2];
898 int num_cols = selectedTableCells[i * 4 + 3];
899
900 if (row_start != -1) {
901 if (r >= row_start && r < row_start + num_rows
902 && c >= col_start && c < col_start + num_cols)
903 {
904 int firstPosition = cell.firstPosition();
905 int lastPosition = cell.lastPosition();
906
907 // make sure empty cells are still selected
908 if (firstPosition == lastPosition)
909 ++lastPosition;
910
911 cell_context.selections[i].cursor.setPosition(firstPosition);
912 cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
913 } else {
914 cell_context.selections[i].cursor.clearSelection();
915 }
916 }
917
918 // FullWidthSelection is not useful for tables
919 cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
920 }
921}
922
923void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
924 const QAbstractTextDocumentLayout::PaintContext &context,
925 QTextFrame *frame) const
926{
927 QTextFrameData *fd = data(frame);
928 // #######
929 if (fd->layoutDirty)
930 return;
931 Q_ASSERT(!fd->sizeDirty);
932 Q_ASSERT(!fd->layoutDirty);
933
934 // floor the offset to avoid painting artefacts when drawing adjacent borders
935 // we later also round table cell heights and widths
936 const QPointF off = QPointF(QPointF(offset + fd->position.toPointF()).toPoint());
937
938 if (context.clip.isValid()
939 && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
940 || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
941 return;
942
943 qCDebug(lcDraw) << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
944
945 // if the cursor is /on/ a table border we may need to repaint it
946 // afterwards, as we usually draw the decoration first
947 QTextBlock cursorBlockNeedingRepaint;
948 QPointF offsetOfRepaintedCursorBlock = off;
949
950 QTextTable *table = qobject_cast<QTextTable *>(frame);
951 const QRectF frameRect(off, fd->size.toSizeF());
952
953 if (table) {
954 const int rows = table->rows();
955 const int columns = table->columns();
956 QTextTableData *td = static_cast<QTextTableData *>(data(table));
957
958 QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
959 for (int i = 0; i < context.selections.size(); ++i) {
960 const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
961 int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
962
963 if (s.cursor.currentTable() == table)
964 s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
965
966 selectedTableCells[i * 4] = row_start;
967 selectedTableCells[i * 4 + 1] = col_start;
968 selectedTableCells[i * 4 + 2] = num_rows;
969 selectedTableCells[i * 4 + 3] = num_cols;
970 }
971
972 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
973 if (pageHeight <= 0)
974 pageHeight = QFIXED_MAX;
975
976 QFixed absYPos = td->position.y;
977 QTextFrame *parentFrame = table->parentFrame();
978 while (parentFrame) {
979 absYPos += data(parentFrame)->position.y;
980 parentFrame = parentFrame->parentFrame();
981 }
982 const int tableStartPage = (absYPos / pageHeight).truncate();
983 const int tableEndPage = ((absYPos + td->size.height) / pageHeight).truncate();
984
985 qreal border = td->border.toReal();
986 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
987
988 // draw the table headers
989 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
990 int page = tableStartPage + 1;
991 while (page <= tableEndPage) {
992 const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
993 const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
994 for (int r = 0; r < headerRowCount; ++r) {
995 for (int c = 0; c < columns; ++c) {
996 QTextTableCell cell = table->cellAt(r, c);
997 QAbstractTextDocumentLayout::PaintContext cell_context = context;
998 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
999 QRectF cellRect = td->cellRect(cell);
1000
1001 cellRect.translate(off.x(), headerOffset);
1002 // we need to account for the cell border in the clipping test
1003 int leftAdjust = qMin(qreal(0), 1 - border);
1004 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1005 continue;
1006
1007 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1008 &offsetOfRepaintedCursorBlock);
1009 }
1010 }
1011 ++page;
1012 }
1013
1014 int firstRow = 0;
1015 int lastRow = rows;
1016
1017 if (context.clip.isValid()) {
1018 QVector<QFixed>::ConstIterator rowIt = std::lower_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
1019 if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
1020 --rowIt;
1021 firstRow = rowIt - td->rowPositions.constBegin();
1022 }
1023
1024 rowIt = std::upper_bound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
1025 if (rowIt != td->rowPositions.constEnd()) {
1026 ++rowIt;
1027 lastRow = rowIt - td->rowPositions.constBegin();
1028 }
1029 }
1030
1031 for (int c = 0; c < columns; ++c) {
1032 QTextTableCell cell = table->cellAt(firstRow, c);
1033 firstRow = qMin(firstRow, cell.row());
1034 }
1035
1036 for (int r = firstRow; r < lastRow; ++r) {
1037 for (int c = 0; c < columns; ++c) {
1038 QTextTableCell cell = table->cellAt(r, c);
1039 QAbstractTextDocumentLayout::PaintContext cell_context = context;
1040 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1041 QRectF cellRect = td->cellRect(cell);
1042
1043 cellRect.translate(off);
1044 // we need to account for the cell border in the clipping test
1045 int leftAdjust = qMin(qreal(0), 1 - border);
1046 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1047 continue;
1048
1049 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1050 &offsetOfRepaintedCursorBlock);
1051 }
1052 }
1053
1054 } else {
1055 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1056
1057 QTextFrame::Iterator it = frame->begin();
1058
1059 if (frame == docPrivate->rootFrame())
1060 it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
1061
1062 QList<QTextFrame *> floats;
1063 const int numFloats = fd->floats.count();
1064 floats.reserve(numFloats);
1065 for (int i = 0; i < numFloats; ++i)
1066 floats.append(fd->floats.at(i));
1067
1068 drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
1069 }
1070
1071 if (cursorBlockNeedingRepaint.isValid()) {
1072 const QPen oldPen = painter->pen();
1073 painter->setPen(context.palette.color(QPalette::Text));
1074 const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
1075 cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
1076 cursorPos, cursorWidth);
1077 painter->setPen(oldPen);
1078 }
1079
1080 return;
1081}
1082
1083void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
1084 QTextTable *table, QTextTableData *td, int r, int c,
1085 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
1086{
1087 QTextTableCell cell = table->cellAt(r, c);
1088 int rspan = cell.rowSpan();
1089 int cspan = cell.columnSpan();
1090 if (rspan != 1) {
1091 int cr = cell.row();
1092 if (cr != r)
1093 return;
1094 }
1095 if (cspan != 1) {
1096 int cc = cell.column();
1097 if (cc != c)
1098 return;
1099 }
1100
1101 QTextFormat fmt = cell.format();
1102 const QFixed leftPadding = td->leftPadding(fmt);
1103 const QFixed topPadding = td->topPadding(fmt);
1104
1105 qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1106 qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1107
1108 const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1109 if (r >= headerRowCount)
1110 topMargin += td->headerHeight.toReal();
1111
1112 if (td->border != 0) {
1113 const QBrush oldBrush = painter->brush();
1114 const QPen oldPen = painter->pen();
1115
1116 const qreal border = td->border.toReal();
1117
1118 QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
1119
1120 // invert the border style for cells
1121 QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
1122 switch (cellBorder) {
1123 case QTextFrameFormat::BorderStyle_Inset:
1124 cellBorder = QTextFrameFormat::BorderStyle_Outset;
1125 break;
1126 case QTextFrameFormat::BorderStyle_Outset:
1127 cellBorder = QTextFrameFormat::BorderStyle_Inset;
1128 break;
1129 case QTextFrameFormat::BorderStyle_Groove:
1130 cellBorder = QTextFrameFormat::BorderStyle_Ridge;
1131 break;
1132 case QTextFrameFormat::BorderStyle_Ridge:
1133 cellBorder = QTextFrameFormat::BorderStyle_Groove;
1134 break;
1135 default:
1136 break;
1137 }
1138
1139 drawBorder(painter, borderRect, topMargin, bottomMargin,
1140 border, table->format().borderBrush(), cellBorder);
1141
1142 painter->setBrush(oldBrush);
1143 painter->setPen(oldPen);
1144 }
1145
1146 const QBrush bg = cell.format().background();
1147 const QPointF brushOrigin = painter->brushOrigin();
1148 if (bg.style() != Qt::NoBrush) {
1149 const qreal pageHeight = document->pageSize().height();
1150 const int topPage = pageHeight > 0 ? static_cast<int>(cellRect.top() / pageHeight) : 0;
1151 const int bottomPage = pageHeight > 0 ? static_cast<int>((cellRect.bottom()) / pageHeight) : 0;
1152
1153 if (topPage == bottomPage)
1154 fillBackground(painter, cellRect, bg, cellRect.topLeft());
1155 else {
1156 for (int i = topPage; i <= bottomPage; ++i) {
1157 QRectF clipped = cellRect.toRect();
1158
1159 if (topPage != bottomPage) {
1160 const qreal top = qMax(i * pageHeight + topMargin, cell_context.clip.top());
1161 const qreal bottom = qMin((i + 1) * pageHeight - bottomMargin, cell_context.clip.bottom());
1162
1163 clipped.setTop(qMax(clipped.top(), top));
1164 clipped.setBottom(qMin(clipped.bottom(), bottom));
1165
1166 if (clipped.bottom() <= clipped.top())
1167 continue;
1168
1169 fillBackground(painter, clipped, bg, cellRect.topLeft());
1170 }
1171 }
1172 }
1173
1174 if (bg.style() > Qt::SolidPattern)
1175 painter->setBrushOrigin(cellRect.topLeft());
1176 }
1177
1178 const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
1179
1180 const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
1181 cellRect.top() + (topPadding + verticalOffset).toReal());
1182
1183 QTextBlock repaintBlock;
1184 drawFlow(cellPos, painter, cell_context, cell.begin(),
1185 td->childFrameMap.values(r + c * table->rows()),
1186 &repaintBlock);
1187 if (repaintBlock.isValid()) {
1188 *cursorBlockNeedingRepaint = repaintBlock;
1189 *cursorBlockOffset = cellPos;
1190 }
1191
1192 if (bg.style() > Qt::SolidPattern)
1193 painter->setBrushOrigin(brushOrigin);
1194}
1195
1196void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
1197 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
1198{
1199 Q_Q(const QTextDocumentLayout);
1200 const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
1201
1202 QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
1203 if (inRootFrame && context.clip.isValid()) {
1204 lastVisibleCheckPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
1205 }
1206
1207 QTextBlock previousBlock;
1208 QTextFrame *previousFrame = 0;
1209
1210 for (; !it.atEnd(); ++it) {
1211 QTextFrame *c = it.currentFrame();
1212
1213 if (inRootFrame && !checkPoints.isEmpty()) {
1214 int currentPosInDoc;
1215 if (c)
1216 currentPosInDoc = c->firstPosition();
1217 else
1218 currentPosInDoc = it.currentBlock().position();
1219
1220 // if we're past what is already laid out then we're better off
1221 // not trying to draw things that may not be positioned correctly yet
1222 if (currentPosInDoc >= checkPoints.constLast().positionInFrame)
1223 break;
1224
1225 if (lastVisibleCheckPoint != checkPoints.end()
1226 && context.clip.isValid()
1227 && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
1228 )
1229 break;
1230 }
1231
1232 if (c)
1233 drawFrame(offset, painter, context, c);
1234 else {
1235 QAbstractTextDocumentLayout::PaintContext pc = context;
1236 if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
1237 pc.selections.clear();
1238 drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
1239 }
1240
1241 // when entering a table and the previous block is empty
1242 // then layoutFlow 'hides' the block that just causes a
1243 // new line by positioning it /on/ the table border. as we
1244 // draw that block before the table itself the decoration
1245 // 'overpaints' the cursor and we need to paint it afterwards
1246 // again
1247 if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
1248 && previousBlock.contains(context.cursorPosition)
1249 ) {
1250 *cursorBlockNeedingRepaint = previousBlock;
1251 }
1252
1253 previousBlock = it.currentBlock();
1254 previousFrame = c;
1255 }
1256
1257 for (int i = 0; i < floats.count(); ++i) {
1258 QTextFrame *frame = floats.at(i);
1259 if (!isFrameFromInlineObject(frame)
1260 || frame->frameFormat().position() == QTextFrameFormat::InFlow)
1261 continue;
1262
1263 const int pos = frame->firstPosition() - 1;
1264 QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
1265 QTextObjectInterface *handler = q->handlerForObject(format.objectType());
1266 if (handler) {
1267 QRectF rect = frameBoundingRectInternal(frame);
1268 handler->drawObject(painter, rect, document, pos, format);
1269 }
1270 }
1271}
1272
1273void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
1274 const QAbstractTextDocumentLayout::PaintContext &context,
1275 const QTextBlock &bl, bool inRootFrame) const
1276{
1277 const QTextLayout *tl = bl.layout();
1278 QRectF r = tl->boundingRect();
1279 r.translate(offset + tl->position());
1280 if (!bl.isVisible() || (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom())))
1281 return;
1282 qCDebug(lcDraw) << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
1283
1284 QTextBlockFormat blockFormat = bl.blockFormat();
1285
1286 QBrush bg = blockFormat.background();
1287 if (bg != Qt::NoBrush) {
1288 QRectF rect = r;
1289
1290 // extend the background rectangle if we're in the root frame with NoWrap,
1291 // as the rect of the text block will then be only the width of the text
1292 // instead of the full page width
1293 if (inRootFrame && document->pageSize().width() <= 0) {
1294 const QTextFrameData *fd = data(document->rootFrame());
1295 rect.setRight((fd->size.width - fd->rightMargin).toReal());
1296 }
1297
1298 fillBackground(painter, rect, bg, r.topLeft());
1299 }
1300
1301 QVector<QTextLayout::FormatRange> selections;
1302 int blpos = bl.position();
1303 int bllen = bl.length();
1304 const QTextCharFormat *selFormat = 0;
1305 for (int i = 0; i < context.selections.size(); ++i) {
1306 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
1307 const int selStart = range.cursor.selectionStart() - blpos;
1308 const int selEnd = range.cursor.selectionEnd() - blpos;
1309 if (selStart < bllen && selEnd > 0
1310 && selEnd > selStart) {
1311 QTextLayout::FormatRange o;
1312 o.start = selStart;
1313 o.length = selEnd - selStart;
1314 o.format = range.format;
1315 selections.append(o);
1316 } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
1317 && bl.contains(range.cursor.position())) {
1318 // for full width selections we don't require an actual selection, just
1319 // a position to specify the line. that's more convenience in usage.
1320 QTextLayout::FormatRange o;
1321 QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
1322 o.start = l.textStart();
1323 o.length = l.textLength();
1324 if (o.start + o.length == bllen - 1)
1325 ++o.length; // include newline
1326 o.format = range.format;
1327 selections.append(o);
1328 }
1329 if (selStart < 0 && selEnd >= 1)
1330 selFormat = &range.format;
1331 }
1332
1333 QTextObject *object = document->objectForFormat(bl.blockFormat());
1334 if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
1335 drawListItem(offset, painter, context, bl, selFormat);
1336
1337 QPen oldPen = painter->pen();
1338 painter->setPen(context.palette.color(QPalette::Text));
1339
1340 tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
1341
1342 if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
1343 || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) {
1344 int cpos = context.cursorPosition;
1345 if (cpos < -1)
1346 cpos = tl->preeditAreaPosition() - (cpos + 2);
1347 else
1348 cpos -= blpos;
1349 tl->drawCursor(painter, offset, cpos, cursorWidth);
1350 }
1351
1352 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1353 const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
1354 painter->setPen(context.palette.color(QPalette::Dark));
1355 qreal y = r.bottom();
1356 if (bl.length() == 1)
1357 y = r.top() + r.height() / 2;
1358
1359 const qreal middleX = r.left() + r.width() / 2;
1360 painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
1361 }
1362
1363 painter->setPen(oldPen);
1364}
1365
1366
1367void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
1368 const QAbstractTextDocumentLayout::PaintContext &context,
1369 const QTextBlock &bl, const QTextCharFormat *selectionFormat) const
1370{
1371 Q_Q(const QTextDocumentLayout);
1372 const QTextBlockFormat blockFormat = bl.blockFormat();
1373 const QTextCharFormat charFormat = QTextCursor(bl).charFormat();
1374 QFont font(charFormat.font());
1375 if (q->paintDevice())
1376 font = QFont(font, q->paintDevice());
1377
1378 const QFontMetrics fontMetrics(font);
1379 QTextObject * const object = document->objectForFormat(blockFormat);
1380 const QTextListFormat lf = object->format().toListFormat();
1381 int style = lf.style();
1382 QString itemText;
1383 QSizeF size;
1384
1385 if (blockFormat.hasProperty(QTextFormat::ListStyle))
1386 style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
1387
1388 QTextLayout *layout = bl.layout();
1389 if (layout->lineCount() == 0)
1390 return;
1391 QTextLine firstLine = layout->lineAt(0);
1392 Q_ASSERT(firstLine.isValid());
1393 QPointF pos = (offset + layout->position()).toPoint();
1394 Qt::LayoutDirection dir = bl.textDirection();
1395 {
1396 QRectF textRect = firstLine.naturalTextRect();
1397 pos += textRect.topLeft().toPoint();
1398 if (dir == Qt::RightToLeft)
1399 pos.rx() += textRect.width();
1400 }
1401
1402 switch (style) {
1403 case QTextListFormat::ListDecimal:
1404 case QTextListFormat::ListLowerAlpha:
1405 case QTextListFormat::ListUpperAlpha:
1406 case QTextListFormat::ListLowerRoman:
1407 case QTextListFormat::ListUpperRoman:
1408 itemText = static_cast<QTextList *>(object)->itemText(bl);
1409 size.setWidth(fontMetrics.horizontalAdvance(itemText));
1410 size.setHeight(fontMetrics.height());
1411 break;
1412
1413 case QTextListFormat::ListSquare:
1414 case QTextListFormat::ListCircle:
1415 case QTextListFormat::ListDisc:
1416 size.setWidth(fontMetrics.lineSpacing() / 3);
1417 size.setHeight(size.width());
1418 break;
1419
1420 case QTextListFormat::ListStyleUndefined:
1421 return;
1422 default: return;
1423 }
1424
1425 QRectF r(pos, size);
1426
1427 qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' '));
1428 if (dir == Qt::LeftToRight)
1429 xoff = -xoff - size.width();
1430 r.translate( xoff, (fontMetrics.height() / 2) - (size.height() / 2));
1431
1432 painter->save();
1433
1434 painter->setRenderHint(QPainter::Antialiasing);
1435
1436 if (selectionFormat) {
1437 painter->setPen(QPen(selectionFormat->foreground(), 0));
1438 painter->fillRect(r, selectionFormat->background());
1439 } else {
1440 QBrush fg = charFormat.foreground();
1441 if (fg == Qt::NoBrush)
1442 fg = context.palette.text();
1443 painter->setPen(QPen(fg, 0));
1444 }
1445
1446 QBrush brush = context.palette.brush(QPalette::Text);
1447
1448 bool marker = bl.blockFormat().marker() != QTextBlockFormat::NoMarker;
1449 if (marker) {
1450 int adj = fontMetrics.lineSpacing() / 6;
1451 r.adjust(-adj, 0, -adj, 0);
1452 if (bl.blockFormat().marker() == QTextBlockFormat::Checked) {
1453 // ### Qt6: render with QStyle / PE_IndicatorCheckBox. We don't currently
1454 // have access to that here, because it would be a widget dependency.
1455 painter->setPen(QPen(painter->pen().color(), 2));
1456 painter->drawLine(r.topLeft(), r.bottomRight());
1457 painter->drawLine(r.topRight(), r.bottomLeft());
1458 painter->setPen(QPen(painter->pen().color(), 0));
1459 }
1460 painter->drawRect(r.adjusted(-adj, -adj, adj, adj));
1461 }
1462
1463 switch (style) {
1464 case QTextListFormat::ListDecimal:
1465 case QTextListFormat::ListLowerAlpha:
1466 case QTextListFormat::ListUpperAlpha:
1467 case QTextListFormat::ListLowerRoman:
1468 case QTextListFormat::ListUpperRoman: {
1469 QTextLayout layout(itemText, font, q->paintDevice());
1470 layout.setCacheEnabled(true);
1471 QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
1472 option.setTextDirection(dir);
1473 layout.setTextOption(option);
1474 layout.beginLayout();
1475 QTextLine line = layout.createLine();
1476 if (line.isValid())
1477 line.setLeadingIncluded(true);
1478 layout.endLayout();
1479 layout.draw(painter, QPointF(r.left(), pos.y()));
1480 break;
1481 }
1482 case QTextListFormat::ListSquare:
1483 if (!marker)
1484 painter->fillRect(r, brush);
1485 break;
1486 case QTextListFormat::ListCircle:
1487 if (!marker) {
1488 painter->setPen(QPen(brush, 0));
1489 painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
1490 }
1491 break;
1492 case QTextListFormat::ListDisc:
1493 if (!marker) {
1494 painter->setBrush(brush);
1495 painter->setPen(Qt::NoPen);
1496 painter->drawEllipse(r);
1497 }
1498 break;
1499 case QTextListFormat::ListStyleUndefined:
1500 break;
1501 default:
1502 break;
1503 }
1504
1505 painter->restore();
1506}
1507
1508static QFixed flowPosition(const QTextFrame::iterator &it)
1509{
1510 if (it.atEnd())
1511 return 0;
1512
1513 if (it.currentFrame()) {
1514 return data(it.currentFrame())->position.y;
1515 } else {
1516 QTextBlock block = it.currentBlock();
1517 QTextLayout *layout = block.layout();
1518 if (layout->lineCount() == 0)
1519 return QFixed::fromReal(layout->position().y());
1520 else
1521 return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
1522 }
1523}
1524
1525static QFixed firstChildPos(const QTextFrame *f)
1526{
1527 return flowPosition(f->begin());
1528}
1529
1530QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
1531 int layoutFrom, int layoutTo, QTextTableData *td,
1532 QFixed absoluteTableY, bool withPageBreaks)
1533{
1534 qCDebug(lcTable) << "layoutCell";
1535 QTextLayoutStruct layoutStruct;
1536 layoutStruct.frame = t;
1537 layoutStruct.minimumWidth = 0;
1538 layoutStruct.maximumWidth = QFIXED_MAX;
1539 layoutStruct.y = 0;
1540
1541 const QTextFormat fmt = cell.format();
1542 const QFixed topPadding = td->topPadding(fmt);
1543 if (withPageBreaks) {
1544 layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
1545 }
1546 layoutStruct.x_left = 0;
1547 layoutStruct.x_right = width;
1548 // we get called with different widths all the time (for example for figuring
1549 // out the min/max widths), so we always have to do the full layout ;(
1550 // also when for example in a table layoutFrom/layoutTo affect only one cell,
1551 // making that one cell grow the available width of the other cells may change
1552 // (shrink) and therefore when layoutCell gets called for them they have to
1553 // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
1554 // this line:
1555
1556 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
1557 if (layoutStruct.pageHeight < 0 || !withPageBreaks)
1558 layoutStruct.pageHeight = QFIXED_MAX;
1559 const int currentPage = layoutStruct.currentPage();
1560 layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
1561 layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
1562 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
1563
1564 layoutStruct.fullLayout = true;
1565
1566 QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
1567 layoutStruct.y = qMax(layoutStruct.y, pageTop);
1568
1569 const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
1570 for (int i = 0; i < childFrames.size(); ++i) {
1571 QTextFrame *frame = childFrames.at(i);
1572 QTextFrameData *cd = data(frame);
1573 cd->sizeDirty = true;
1574 }
1575
1576 layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
1577
1578 QFixed floatMinWidth;
1579
1580 // floats that are located inside the text (like inline images) aren't taken into account by
1581 // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
1582 // do that here. For example with <td><img align="right" src="..." />blah</td>
1583 // when the image happens to be higher than the text
1584 for (int i = 0; i < childFrames.size(); ++i) {
1585 QTextFrame *frame = childFrames.at(i);
1586 QTextFrameData *cd = data(frame);
1587
1588 if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
1589 layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
1590
1591 floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
1592 }
1593
1594 // constraint the maximumWidth by the minimum width of the fixed size floats, to
1595 // keep them visible
1596 layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
1597
1598 // as floats in cells get added to the table's float list but must not affect
1599 // floats in other cells we must clear the list here.
1600 data(t)->floats.clear();
1601
1602// qDebug("layoutCell done");
1603
1604 return layoutStruct;
1605}
1606
1607QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
1608{
1609 qCDebug(lcTable) << "layoutTable from" << layoutFrom << "to" << layoutTo << "parentY" << parentY;
1610 QTextTableData *td = static_cast<QTextTableData *>(data(table));
1611 Q_ASSERT(td->sizeDirty);
1612 const int rows = table->rows();
1613 const int columns = table->columns();
1614
1615 const QTextTableFormat fmt = table->format();
1616
1617 td->childFrameMap.clear();
1618 {
1619 const QList<QTextFrame *> children = table->childFrames();
1620 for (int i = 0; i < children.count(); ++i) {
1621 QTextFrame *frame = children.at(i);
1622 QTextTableCell cell = table->cellAt(frame->firstPosition());
1623 td->childFrameMap.insert(cell.row() + cell.column() * rows, frame);
1624 }
1625 }
1626
1627 QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
1628 if (columnWidthConstraints.size() != columns)
1629 columnWidthConstraints.resize(columns);
1630 Q_ASSERT(columnWidthConstraints.count() == columns);
1631
1632 const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
1633 td->deviceScale = scaleToDevice(qreal(1));
1634 td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
1635 const QFixed leftMargin = td->leftMargin + td->border + td->padding;
1636 const QFixed rightMargin = td->rightMargin + td->border + td->padding;
1637 const QFixed topMargin = td->topMargin + td->border + td->padding;
1638
1639 const QFixed absoluteTableY = parentY + td->position.y;
1640
1641 const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
1642
1643recalc_minmax_widths:
1644
1645 QFixed remainingWidth = td->contentsWidth;
1646 // two (vertical) borders per cell per column
1647 remainingWidth -= columns * 2 * td->border;
1648 // inter-cell spacing
1649 remainingWidth -= (columns - 1) * cellSpacing;
1650 // cell spacing at the left and right hand side
1651 remainingWidth -= 2 * cellSpacing;
1652 // remember the width used to distribute to percentaged columns
1653 const QFixed initialTotalWidth = remainingWidth;
1654
1655 td->widths.resize(columns);
1656 td->widths.fill(0);
1657
1658 td->minWidths.resize(columns);
1659 // start with a minimum width of 0. totally empty
1660 // cells of default created tables are invisible otherwise
1661 // and therefore hardly editable
1662 td->minWidths.fill(1);
1663
1664 td->maxWidths.resize(columns);
1665 td->maxWidths.fill(QFIXED_MAX);
1666
1667 // calculate minimum and maximum sizes of the columns
1668 for (int i = 0; i < columns; ++i) {
1669 for (int row = 0; row < rows; ++row) {
1670 const QTextTableCell cell = table->cellAt(row, i);
1671 const int cspan = cell.columnSpan();
1672
1673 if (cspan > 1 && i != cell.column())
1674 continue;
1675
1676 const QTextFormat fmt = cell.format();
1677 const QFixed leftPadding = td->leftPadding(fmt);
1678 const QFixed rightPadding = td->rightPadding(fmt);
1679 const QFixed widthPadding = leftPadding + rightPadding;
1680
1681 // to figure out the min and the max width lay out the cell at
1682 // maximum width. otherwise the maxwidth calculation sometimes
1683 // returns wrong values
1684 QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
1685 layoutTo, td, absoluteTableY,
1686 /*withPageBreaks =*/false);
1687
1688 // distribute the minimum width over all columns the cell spans
1689 QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
1690 for (int n = 0; n < cspan; ++n) {
1691 const int col = i + n;
1692 QFixed w = widthToDistribute / (cspan - n);
1693 // ceil to avoid going below minWidth when rounding all column widths later
1694 td->minWidths[col] = qMax(td->minWidths.at(col), w).ceil();
1695 widthToDistribute -= td->minWidths.at(col);
1696 if (widthToDistribute <= 0)
1697 break;
1698 }
1699
1700 QFixed maxW = td->maxWidths.at(i);
1701 if (layoutStruct.maximumWidth != QFIXED_MAX) {
1702 if (maxW == QFIXED_MAX)
1703 maxW = layoutStruct.maximumWidth + widthPadding;
1704 else
1705 maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
1706 }
1707 if (maxW == QFIXED_MAX)
1708 continue;
1709
1710 widthToDistribute = maxW;
1711 for (int n = 0; n < cspan; ++n) {
1712 const int col = i + n;
1713 QFixed w = widthToDistribute / (cspan - n);
1714 td->maxWidths[col] = qMax(td->minWidths.at(col), w);
1715 widthToDistribute -= td->maxWidths.at(col);
1716 if (widthToDistribute <= 0)
1717 break;
1718 }
1719 }
1720 }
1721
1722 // set fixed values, figure out total percentages used and number of
1723 // variable length cells. Also assign the minimum width for variable columns.
1724 QFixed totalPercentage;
1725 int variableCols = 0;
1726 QFixed totalMinWidth = 0;
1727 for (int i = 0; i < columns; ++i) {
1728 const QTextLength &length = columnWidthConstraints.at(i);
1729 if (length.type() == QTextLength::FixedLength) {
1730 td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
1731 remainingWidth -= td->widths.at(i);
1732 qCDebug(lcTable) << "column" << i << "has width constraint" << td->minWidths.at(i) << "px, remaining width now" << remainingWidth;
1733 } else if (length.type() == QTextLength::PercentageLength) {
1734 totalPercentage += QFixed::fromReal(length.rawValue());
1735 } else if (length.type() == QTextLength::VariableLength) {
1736 variableCols++;
1737
1738 td->widths[i] = td->minWidths.at(i);
1739 remainingWidth -= td->minWidths.at(i);
1740 qCDebug(lcTable) << "column" << i << "has variable width, min" << td->minWidths.at(i) << "remaining width now" << remainingWidth;
1741 }
1742 totalMinWidth += td->minWidths.at(i);
1743 }
1744
1745 // set percentage values
1746 {
1747 const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
1748 QFixed remainingMinWidths = totalMinWidth;
1749 for (int i = 0; i < columns; ++i) {
1750 remainingMinWidths -= td->minWidths.at(i);
1751 if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
1752 const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
1753
1754 const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
1755 if (percentWidth >= td->minWidths.at(i)) {
1756 td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
1757 } else {
1758 td->widths[i] = td->minWidths.at(i);
1759 }
1760 qCDebug(lcTable) << "column" << i << "has width constraint" << columnWidthConstraints.at(i).rawValue()
1761 << "%, allocated width" << td->widths[i] << "remaining width now" << remainingWidth;
1762 remainingWidth -= td->widths.at(i);
1763 }
1764 }
1765 }
1766
1767 // for variable columns distribute the remaining space
1768 if (variableCols > 0 && remainingWidth > 0) {
1769 QVarLengthArray<int> columnsWithProperMaxSize;
1770 for (int i = 0; i < columns; ++i)
1771 if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
1772 && td->maxWidths.at(i) != QFIXED_MAX)
1773 columnsWithProperMaxSize.append(i);
1774
1775 QFixed lastRemainingWidth = remainingWidth;
1776 while (remainingWidth > 0) {
1777 for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
1778 const int col = columnsWithProperMaxSize[k];
1779 const int colsLeft = columnsWithProperMaxSize.count() - k;
1780 const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
1781 td->widths[col] += w;
1782 remainingWidth -= w;
1783 }
1784 if (remainingWidth == lastRemainingWidth)
1785 break;
1786 lastRemainingWidth = remainingWidth;
1787 }
1788
1789 if (remainingWidth > 0
1790 // don't unnecessarily grow variable length sized tables
1791 && fmt.width().type() != QTextLength::VariableLength) {
1792 const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
1793 for (int col = 0; col < columns; ++col) {
1794 if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
1795 td->widths[col] += widthPerAnySizedCol;
1796 }
1797 }
1798 }
1799
1800 // in order to get a correct border rendering we must ensure that the distance between
1801 // two cells is exactly 2 * td->border pixel. we do this by rounding the calculated width
1802 // values here.
1803 // to minimize the total rounding error we propagate the rounding error for each width
1804 // to its successor.
1805 QFixed error = 0;
1806 for (int i = 0; i < columns; ++i) {
1807 QFixed orig = td->widths[i];
1808 td->widths[i] = (td->widths[i] - error).round();
1809 error = td->widths[i] - orig;
1810 }
1811
1812 td->columnPositions.resize(columns);
1813 td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
1814
1815 for (int i = 1; i < columns; ++i)
1816 td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
1817
1818 // - margin to compensate the + margin in columnPositions[0]
1819 const QFixed contentsWidth = td->columnPositions.constLast() + td->widths.constLast() + td->padding + td->border + cellSpacing - leftMargin;
1820
1821 // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
1822 // mode
1823 if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
1824 && contentsWidth > td->contentsWidth) {
1825 docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
1826 // go back to the top of the function
1827 goto recalc_minmax_widths;
1828 }
1829
1830 td->contentsWidth = contentsWidth;
1831
1832 docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
1833
1834 td->heights.resize(rows);
1835 td->heights.fill(0);
1836
1837 td->rowPositions.resize(rows);
1838 td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
1839
1840 bool haveRowSpannedCells = false;
1841
1842 // need to keep track of cell heights for vertical alignment
1843 QVector<QFixed> cellHeights;
1844 cellHeights.reserve(rows * columns);
1845
1846 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1847 if (pageHeight <= 0)
1848 pageHeight = QFIXED_MAX;
1849
1850 QVector<QFixed> heightToDistribute;
1851 heightToDistribute.resize(columns);
1852
1853 td->headerHeight = 0;
1854 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1855 const QFixed originalTopMargin = td->effectiveTopMargin;
1856 bool hasDroppedTable = false;
1857
1858 // now that we have the column widths we can lay out all cells with the right width.
1859 // spanning cells are only allowed to grow the last row spanned by the cell.
1860 //
1861 // ### this could be made faster by iterating over the cells array of QTextTable
1862 for (int r = 0; r < rows; ++r) {
1863 td->calcRowPosition(r);
1864
1865 const int tableStartPage = (absoluteTableY / pageHeight).truncate();
1866 const int currentPage = ((td->rowPositions.at(r) + absoluteTableY) / pageHeight).truncate();
1867 const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
1868 const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
1869 const QFixed nextPageTop = pageTop + pageHeight;
1870
1871 if (td->rowPositions.at(r) > pageBottom)
1872 td->rowPositions[r] = nextPageTop;
1873 else if (td->rowPositions.at(r) < pageTop)
1874 td->rowPositions[r] = pageTop;
1875
1876 bool dropRowToNextPage = true;
1877 int cellCountBeforeRow = cellHeights.size();
1878
1879 // if we drop the row to the next page we need to subtract the drop
1880 // distance from any row spanning cells
1881 QFixed dropDistance = 0;
1882
1883relayout:
1884 const int rowStartPage = ((td->rowPositions.at(r) + absoluteTableY) / pageHeight).truncate();
1885 // if any of the header rows or the first non-header row start on the next page
1886 // then the entire header should be dropped
1887 if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
1888 td->rowPositions[0] = nextPageTop;
1889 cellHeights.clear();
1890 td->effectiveTopMargin = originalTopMargin;
1891 hasDroppedTable = true;
1892 r = -1;
1893 continue;
1894 }
1895
1896 int rowCellCount = 0;
1897 for (int c = 0; c < columns; ++c) {
1898 QTextTableCell cell = table->cellAt(r, c);
1899 const int rspan = cell.rowSpan();
1900 const int cspan = cell.columnSpan();
1901
1902 if (cspan > 1 && cell.column() != c)
1903 continue;
1904
1905 if (rspan > 1) {
1906 haveRowSpannedCells = true;
1907
1908 const int cellRow = cell.row();
1909 if (cellRow != r) {
1910 // the last row gets all the remaining space
1911 if (cellRow + rspan - 1 == r)
1912 td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance).round();
1913 continue;
1914 }
1915 }
1916
1917 const QTextFormat fmt = cell.format();
1918
1919 const QFixed topPadding = td->topPadding(fmt);
1920 const QFixed bottomPadding = td->bottomPadding(fmt);
1921 const QFixed leftPadding = td->leftPadding(fmt);
1922 const QFixed rightPadding = td->rightPadding(fmt);
1923 const QFixed widthPadding = leftPadding + rightPadding;
1924
1925 ++rowCellCount;
1926
1927 const QFixed width = td->cellWidth(c, cspan) - widthPadding;
1928 QTextLayoutStruct layoutStruct = layoutCell(table, cell, width,
1929 layoutFrom, layoutTo,
1930 td, absoluteTableY,
1931 /*withPageBreaks =*/true);
1932
1933 const QFixed height = (layoutStruct.y + bottomPadding + topPadding).round();
1934
1935 if (rspan > 1)
1936 heightToDistribute[c] = height + dropDistance;
1937 else
1938 td->heights[r] = qMax(td->heights.at(r), height);
1939
1940 cellHeights.append(layoutStruct.y);
1941
1942 QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
1943 if (childPos < pageBottom)
1944 dropRowToNextPage = false;
1945 }
1946
1947 if (rowCellCount > 0 && dropRowToNextPage) {
1948 dropDistance = nextPageTop - td->rowPositions.at(r);
1949 td->rowPositions[r] = nextPageTop;
1950 td->heights[r] = 0;
1951 dropRowToNextPage = false;
1952 cellHeights.resize(cellCountBeforeRow);
1953 if (r > headerRowCount)
1954 td->heights[r - 1] = pageBottom - td->rowPositions.at(r - 1);
1955 goto relayout;
1956 }
1957
1958 if (haveRowSpannedCells) {
1959 const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
1960 for (int c = 0; c < columns; ++c)
1961 heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
1962 }
1963
1964 if (r == headerRowCount - 1) {
1965 td->headerHeight = td->rowPositions.at(r) + td->heights.at(r) - td->rowPositions.at(0) + td->cellSpacing + 2 * td->border;
1966 td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
1967 td->effectiveTopMargin += td->headerHeight;
1968 }
1969 }
1970
1971 td->effectiveTopMargin = originalTopMargin;
1972
1973 // now that all cells have been properly laid out, we can compute the
1974 // vertical offsets for vertical alignment
1975 td->cellVerticalOffsets.resize(rows * columns);
1976 int cellIndex = 0;
1977 for (int r = 0; r < rows; ++r) {
1978 for (int c = 0; c < columns; ++c) {
1979 QTextTableCell cell = table->cellAt(r, c);
1980 if (cell.row() != r || cell.column() != c)
1981 continue;
1982
1983 const int rowSpan = cell.rowSpan();
1984 const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
1985
1986 const QTextCharFormat cellFormat = cell.format();
1987 const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
1988
1989 QFixed offset = 0;
1990 switch (cellFormat.verticalAlignment()) {
1991 case QTextCharFormat::AlignMiddle:
1992 offset = (availableHeight - cellHeight) / 2;
1993 break;
1994 case QTextCharFormat::AlignBottom:
1995 offset = availableHeight - cellHeight;
1996 break;
1997 default:
1998 break;
1999 };
2000
2001 for (int rd = 0; rd < cell.rowSpan(); ++rd) {
2002