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