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