1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtextdocumentlayout_p.h"
43 #include "qtextdocument_p.h"
44 #include "qtextimagehandler_p.h"
45 #include "qtexttable.h"
46 #include "qtextlist.h"
47 #include "qtextengine_p.h"
48 #include "private/qcssutil_p.h"
49 #include "private/qguiapplication_p.h"
51 #include "qabstracttextdocumentlayout_p.h"
52 #include "qcssparser_p.h"
59 #include <qvarlengtharray.h>
61 #include <qbasictimer.h>
62 #include "private/qfunctions_p.h"
64 // #define LAYOUT_DEBUG
67 #define LDEBUG qDebug()
68 #define INC_INDENT debug_indent += " "
69 #define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2)
71 #define LDEBUG if(0) qDebug()
72 #define INC_INDENT do {} while(0)
73 #define DEC_INDENT do {} while(0)
78 // ################ should probably add frameFormatChange notification!
80 struct QTextLayoutStruct;
82 class QTextFrameData : public QTextFrameLayoutData
87 // relative to parent frame
91 // contents starts at (margin+border/margin+border)
98 // contents width includes padding (as we need to treat this on a per cell basis for tables)
100 QFixed contentsHeight;
101 QFixed oldContentsWidth;
103 // accumulated margins
104 QFixed effectiveTopMargin;
105 QFixed effectiveBottomMargin;
110 QTextLayoutStruct *currentLayoutStruct;
115 QList<QPointer<QTextFrame> > floats;
118 QTextFrameData::QTextFrameData()
119 : maximumWidth(QFIXED_MAX),
120 currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
124 struct QTextLayoutStruct {
125 QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
130 QFixed frameY; // absolute y position of the current frame
131 QFixed y; // always relative to the current frame
132 QFixed contentsWidth;
136 QList<QTextFrame *> pendingFloats;
139 QFixed pageTopMargin;
140 QFixed pageBottomMargin;
142 QRectF updateRectForFloats;
144 inline void addUpdateRectForFloat(const QRectF &rect) {
145 if (updateRectForFloats.isValid())
146 updateRectForFloats |= rect;
148 updateRectForFloats = rect;
151 inline QFixed absoluteY() const
152 { return frameY + y; }
154 inline int currentPage() const
155 { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
157 inline void newPage()
158 { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; }
161 class QTextTableData : public QTextFrameData
164 QFixed cellSpacing, cellPadding;
166 QVector<QFixed> minWidths;
167 QVector<QFixed> maxWidths;
168 QVector<QFixed> widths;
169 QVector<QFixed> heights;
170 QVector<QFixed> columnPositions;
171 QVector<QFixed> rowPositions;
173 QVector<QFixed> cellVerticalOffsets;
177 // maps from cell index (row + col * rowCount) to child frames belonging to
179 QMultiHash<int, QTextFrame *> childFrameMap;
181 inline QFixed cellWidth(int column, int colspan) const
182 { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
183 - columnPositions.at(column); }
185 inline void calcRowPosition(int row)
188 rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
191 QRectF cellRect(const QTextTableCell &cell) const;
193 inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
195 QVariant v = format.property(property);
199 Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float);
200 return QFixed::fromReal(v.toReal() * deviceScale);
204 inline QFixed topPadding(const QTextFormat &format) const
206 return paddingProperty(format, QTextFormat::TableCellTopPadding);
209 inline QFixed bottomPadding(const QTextFormat &format) const
211 return paddingProperty(format, QTextFormat::TableCellBottomPadding);
214 inline QFixed leftPadding(const QTextFormat &format) const
216 return paddingProperty(format, QTextFormat::TableCellLeftPadding);
219 inline QFixed rightPadding(const QTextFormat &format) const
221 return paddingProperty(format, QTextFormat::TableCellRightPadding);
224 inline QFixedPoint cellPosition(const QTextTableCell &cell) const
226 const QTextFormat fmt = cell.format();
227 return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
230 void updateTableSize();
233 inline QFixedPoint cellPosition(int row, int col) const
234 { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
237 static QTextFrameData *createData(QTextFrame *f)
239 QTextFrameData *data;
240 if (qobject_cast<QTextTable *>(f))
241 data = new QTextTableData;
243 data = new QTextFrameData;
244 f->setLayoutData(data);
248 static inline QTextFrameData *data(QTextFrame *f)
250 QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
252 data = createData(f);
256 static bool isFrameFromInlineObject(QTextFrame *f)
258 return f->firstPosition() > f->lastPosition();
261 void QTextTableData::updateTableSize()
263 const QFixed effectiveTopMargin = this->topMargin + border + padding;
264 const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
265 const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
266 const QFixed effectiveRightMargin = this->rightMargin + border + padding;
267 size.height = contentsHeight == -1
268 ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin
269 : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
270 size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
273 QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
275 const int row = cell.row();
276 const int rowSpan = cell.rowSpan();
277 const int column = cell.column();
278 const int colSpan = cell.columnSpan();
280 return QRectF(columnPositions.at(column).toReal(),
281 rowPositions.at(row).toReal(),
282 (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
283 (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
286 static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
288 return !nextIt.atEnd()
289 && qobject_cast<QTextTable *>(nextIt.currentFrame())
291 && block.length() == 1
292 && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
293 && !format.hasProperty(QTextFormat::BackgroundBrush)
294 && nextIt.currentFrame()->firstPosition() == block.position() + 1
298 static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it)
300 QTextFrame::Iterator next = it; ++next;
301 if (it.currentFrame())
303 QTextBlock block = it.currentBlock();
304 return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
307 static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
309 return qobject_cast<const QTextTable *>(previousFrame)
311 && block.length() == 1
312 && previousFrame->lastPosition() == block.position() - 1
316 static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
318 return qobject_cast<const QTextTable *>(previousFrame)
320 && block.length() > 1
321 && block.text().at(0) == QChar::LineSeparator
322 && previousFrame->lastPosition() == block.position() - 1
328 Optimization strategies:
332 * Distinguish between normal and special flow. For normal flow the condition:
333 y1 > y2 holds for all blocks with b1.key() > b2.key().
334 * Special flow is: floats, table cells
336 * Normal flow within table cells. Tables (not cells) are part of the normal flow.
339 * If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
340 * If height doesn't change, no need to do anything
344 * If minWidth of cell changes, recalculate table width, relayout if needed.
345 * What about maxWidth when doing auto layout?
348 * need fixed or proportional width, otherwise don't float!
349 * On width/height change relayout surrounding paragraphs.
351 Document width change:
352 * full relayout needed
357 * Floats are specified by a special format object.
358 * currently only floating images are implemented.
364 On the table layouting:
366 +---[ table border ]-------------------------
368 | +------[ cell border ]-----+ +--------
375 rowPositions[i] and columnPositions[i] point at the cell content
376 position. So for example the left border is drawn at
377 x = columnPositions[i] - fd->border and similar for y.
384 QFixed frameY; // absolute y position of the current frame
388 QFixed contentsWidth;
390 Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
392 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y)
394 return checkPoint.y < y;
397 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos)
399 return checkPoint.positionInFrame < pos;
402 static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF())
405 if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
406 if (!gradientRect.isNull()) {
408 m.translate(gradientRect.left(), gradientRect.top());
409 m.scale(gradientRect.width(), gradientRect.height());
410 brush.setTransform(m);
411 const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
414 p->setBrushOrigin(origin);
416 p->fillRect(rect, brush);
420 class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
422 Q_DECLARE_PUBLIC(QTextDocumentLayout)
424 QTextDocumentLayoutPrivate();
426 QTextOption::WrapMode wordWrapMode;
428 mutable QString debug_indent;
431 int fixedColumnWidth;
434 QSizeF lastReportedSize;
438 mutable int currentLazyLayoutPosition;
439 mutable int lazyLayoutStepSize;
440 QBasicTimer layoutTimer;
441 mutable QBasicTimer sizeChangedTimer;
442 uint showLayoutProgress : 1;
443 uint insideDocumentChange : 1;
447 bool contentHasAlignment;
449 QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
451 void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
452 QTextFrame *f) const;
453 void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
454 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
455 void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
456 QTextBlock bl, bool inRootFrame) const;
457 void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
458 QTextBlock bl, const QTextCharFormat *selectionFormat) const;
459 void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
460 QTextTable *table, QTextTableData *td, int r, int c,
461 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
462 void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
463 const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
464 void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
472 HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
473 HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
474 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
475 HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
476 HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
478 QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
479 int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
480 bool withPageBreaks);
481 void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
482 QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
484 void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0);
486 // calls the next one
487 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
488 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
490 void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
491 QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
492 void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
493 void pageBreakInsideTable(QTextTable *table, QTextLayoutStruct *layoutStruct);
496 void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
497 QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const;
499 QVector<QCheckPoint> checkPoints;
501 QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
502 QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
504 void ensureLayouted(QFixed y) const;
505 void ensureLayoutedByPosition(int position) const;
506 inline void ensureLayoutFinished() const
507 { ensureLayoutedByPosition(INT_MAX); }
508 void layoutStep() const;
510 QRectF frameBoundingRectInternal(QTextFrame *frame) const;
512 qreal scaleToDevice(qreal value) const;
513 QFixed scaleToDevice(QFixed value) const;
516 QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
517 : fixedColumnWidth(-1),
519 currentLazyLayoutPosition(-1),
520 lazyLayoutStepSize(1000),
523 showLayoutProgress = true;
524 insideDocumentChange = false;
526 contentHasAlignment = false;
529 QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
531 QTextFrame *rootFrame = document->rootFrame();
533 if (checkPoints.isEmpty()
534 || y < 0 || y > data(rootFrame)->size.height)
535 return rootFrame->begin();
537 QVector<QCheckPoint>::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y);
538 if (checkPoint == checkPoints.end())
539 return rootFrame->begin();
541 if (checkPoint != checkPoints.begin())
544 const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
545 return frameIteratorForTextPosition(position);
548 QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
550 QTextFrame *rootFrame = docPrivate->rootFrame();
552 const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
553 const int begin = map.findNode(rootFrame->firstPosition());
554 const int end = map.findNode(rootFrame->lastPosition()+1);
556 const int block = map.findNode(position);
557 const int blockPos = map.position(block);
559 QTextFrame::iterator it(rootFrame, block, begin, end);
561 QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
562 if (containingFrame != rootFrame) {
563 while (containingFrame->parentFrame() != rootFrame) {
564 containingFrame = containingFrame->parentFrame();
565 Q_ASSERT(containingFrame);
568 it.cf = containingFrame;
575 QTextDocumentLayoutPrivate::HitPoint
576 QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
578 QTextFrameData *fd = data(frame);
582 Q_ASSERT(!fd->layoutDirty);
583 Q_ASSERT(!fd->sizeDirty);
584 const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
586 QTextFrame *rootFrame = docPrivate->rootFrame();
588 // LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point
589 // << "position" << fd->position << "size" << fd->size;
590 if (frame != rootFrame) {
591 if (relativePoint.y < 0 || relativePoint.x < 0) {
592 *position = frame->firstPosition() - 1;
593 // LDEBUG << "before pos=" << *position;
595 } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
596 *position = frame->lastPosition() + 1;
597 // LDEBUG << "after pos=" << *position;
602 if (isFrameFromInlineObject(frame)) {
603 *position = frame->firstPosition() - 1;
607 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
608 const int rows = table->rows();
609 const int columns = table->columns();
610 QTextTableData *td = static_cast<QTextTableData *>(data(table));
612 if (!td->childFrameMap.isEmpty()) {
613 for (int r = 0; r < rows; ++r) {
614 for (int c = 0; c < columns; ++c) {
615 QTextTableCell cell = table->cellAt(r, c);
616 if (cell.row() != r || cell.column() != c)
619 QRectF cellRect = td->cellRect(cell);
620 const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
621 const QFixedPoint pointInCell = relativePoint - cellPos;
623 const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
624 for (int i = 0; i < childFrames.size(); ++i) {
625 QTextFrame *child = childFrames.at(i);
626 if (isFrameFromInlineObject(child)
627 && child->frameFormat().position() != QTextFrameFormat::InFlow
628 && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
637 return hitTest(table, relativePoint, position, l, accuracy);
640 const QList<QTextFrame *> childFrames = frame->childFrames();
641 for (int i = 0; i < childFrames.size(); ++i) {
642 QTextFrame *child = childFrames.at(i);
643 if (isFrameFromInlineObject(child)
644 && child->frameFormat().position() != QTextFrameFormat::InFlow
645 && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
651 QTextFrame::Iterator it = frame->begin();
653 if (frame == rootFrame) {
654 it = frameIteratorForYPosition(relativePoint.y);
656 Q_ASSERT(it.parentFrame() == frame);
659 if (it.currentFrame())
660 *position = it.currentFrame()->firstPosition();
662 *position = it.currentBlock().position();
664 return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
667 QTextDocumentLayoutPrivate::HitPoint
668 QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
669 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
673 for (; !it.atEnd(); ++it) {
674 QTextFrame *c = it.currentFrame();
678 hp = hitTest(c, p, &pos, l, accuracy);
680 hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
682 if (hp >= PointInside) {
683 if (isEmptyBlockBeforeTable(it))
689 if (hp == PointBefore && pos < *position) {
692 } else if (hp == PointAfter && pos > *position) {
699 // LDEBUG << "inside=" << hit << " pos=" << *position;
703 QTextDocumentLayoutPrivate::HitPoint
704 QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
705 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
707 QTextTableData *td = static_cast<QTextTableData *>(data(table));
709 QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
710 if (rowIt == td->rowPositions.constEnd()) {
711 rowIt = td->rowPositions.constEnd() - 1;
712 } else if (rowIt != td->rowPositions.constBegin()) {
716 QVector<QFixed>::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
717 if (colIt == td->columnPositions.constEnd()) {
718 colIt = td->columnPositions.constEnd() - 1;
719 } else if (colIt != td->columnPositions.constBegin()) {
723 QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
724 colIt - td->columnPositions.constBegin());
728 *position = cell.firstPosition();
730 HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
732 if (hp == PointExact)
734 if (hp == PointAfter)
735 *position = cell.lastPosition();
739 QTextDocumentLayoutPrivate::HitPoint
740 QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l,
741 Qt::HitTestAccuracy accuracy) const
743 QTextLayout *tl = bl.layout();
744 QRectF textrect = tl->boundingRect();
745 textrect.translate(tl->position());
746 // LDEBUG << " checking block" << bl.position() << "point=" << point
747 // << " tlrect" << textrect;
748 *position = bl.position();
749 if (point.y.toReal() < textrect.top()) {
750 // LDEBUG << " before pos=" << *position;
752 } else if (point.y.toReal() > textrect.bottom()) {
753 *position += bl.length();
754 // LDEBUG << " after pos=" << *position;
758 QPointF pos = point.toPointF() - tl->position();
762 HitPoint hit = PointInside;
765 for (int i = 0; i < tl->lineCount(); ++i) {
766 QTextLine line = tl->lineAt(i);
767 const QRectF lr = line.naturalTextRect();
768 if (lr.top() > pos.y()) {
769 off = qMin(off, line.textStart());
770 } else if (lr.bottom() <= pos.y()) {
771 off = qMax(off, line.textStart() + line.textLength());
773 if (lr.left() <= pos.x() && lr.right() >= pos.x())
775 // when trying to hit an anchor we want it to hit not only in the left
777 if (accuracy == Qt::ExactHit)
778 off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
780 off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
786 // LDEBUG << " inside=" << hit << " pos=" << *position;
790 // ### could be moved to QTextBlock
791 QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
793 qreal indent = blockFormat.indent();
795 QTextObject *object = document->objectForFormat(blockFormat);
797 indent += object->format().toListFormat().indent();
804 scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
807 return QFixed::fromReal(indent * scale * document->indentWidth());
810 void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
811 qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
813 const qreal pageHeight = document->pageSize().height();
814 const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
815 const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
817 #ifndef QT_NO_CSSPARSER
818 QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
819 #endif //QT_NO_CSSPARSER
821 bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
822 painter->setRenderHint(QPainter::Antialiasing);
824 for (int i = topPage; i <= bottomPage; ++i) {
825 QRectF clipped = rect.toRect();
827 if (topPage != bottomPage) {
828 clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
829 clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
831 if (clipped.bottom() <= clipped.top())
834 #ifndef QT_NO_CSSPARSER
835 qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
836 qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
837 qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
838 qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
841 painter->setPen(Qt::NoPen);
842 painter->setBrush(brush);
843 painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
844 painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
845 painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
846 painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
848 #endif //QT_NO_CSSPARSER
850 if (turn_off_antialiasing)
851 painter->setRenderHint(QPainter::Antialiasing, false);
854 void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
857 const QBrush bg = frame->frameFormat().background();
858 if (bg != Qt::NoBrush) {
859 QRectF bgRect = rect;
860 bgRect.adjust((fd->leftMargin + fd->border).toReal(),
861 (fd->topMargin + fd->border).toReal(),
862 - (fd->rightMargin + fd->border).toReal(),
863 - (fd->bottomMargin + fd->border).toReal());
865 QRectF gradientRect; // invalid makes it default to bgRect
866 QPointF origin = bgRect.topLeft();
867 if (!frame->parentFrame()) {
869 gradientRect.setWidth(painter->device()->width());
870 gradientRect.setHeight(painter->device()->height());
872 fillBackground(painter, bgRect, bg, origin, gradientRect);
874 if (fd->border != 0) {
876 painter->setBrush(Qt::lightGray);
877 painter->setPen(Qt::NoPen);
879 const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
880 const qreal border = fd->border.toReal();
881 const qreal topMargin = fd->topMargin.toReal();
882 const qreal leftMargin = fd->leftMargin.toReal();
883 const qreal bottomMargin = fd->bottomMargin.toReal();
884 const qreal rightMargin = fd->rightMargin.toReal();
885 const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
886 const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
888 drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
889 fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
890 border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
896 static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
897 const QTextTableCell &cell,
899 const int *selectedTableCells)
901 for (int i = 0; i < cell_context.selections.size(); ++i) {
902 int row_start = selectedTableCells[i * 4];
903 int col_start = selectedTableCells[i * 4 + 1];
904 int num_rows = selectedTableCells[i * 4 + 2];
905 int num_cols = selectedTableCells[i * 4 + 3];
907 if (row_start != -1) {
908 if (r >= row_start && r < row_start + num_rows
909 && c >= col_start && c < col_start + num_cols)
911 int firstPosition = cell.firstPosition();
912 int lastPosition = cell.lastPosition();
914 // make sure empty cells are still selected
915 if (firstPosition == lastPosition)
918 cell_context.selections[i].cursor.setPosition(firstPosition);
919 cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
921 cell_context.selections[i].cursor.clearSelection();
925 // FullWidthSelection is not useful for tables
926 cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
930 void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
931 const QAbstractTextDocumentLayout::PaintContext &context,
932 QTextFrame *frame) const
934 QTextFrameData *fd = data(frame);
938 Q_ASSERT(!fd->sizeDirty);
939 Q_ASSERT(!fd->layoutDirty);
941 const QPointF off = offset + fd->position.toPointF();
942 if (context.clip.isValid()
943 && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
944 || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
947 // LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
950 // if the cursor is /on/ a table border we may need to repaint it
951 // afterwards, as we usually draw the decoration first
952 QTextBlock cursorBlockNeedingRepaint;
953 QPointF offsetOfRepaintedCursorBlock = off;
955 QTextTable *table = qobject_cast<QTextTable *>(frame);
956 const QRectF frameRect(off, fd->size.toSizeF());
959 const int rows = table->rows();
960 const int columns = table->columns();
961 QTextTableData *td = static_cast<QTextTableData *>(data(table));
963 QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
964 for (int i = 0; i < context.selections.size(); ++i) {
965 const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
966 int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
968 if (s.cursor.currentTable() == table)
969 s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
971 selectedTableCells[i * 4] = row_start;
972 selectedTableCells[i * 4 + 1] = col_start;
973 selectedTableCells[i * 4 + 2] = num_rows;
974 selectedTableCells[i * 4 + 3] = num_cols;
977 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
979 pageHeight = QFIXED_MAX;
981 const int tableStartPage = (td->position.y / pageHeight).truncate();
982 const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate();
984 qreal border = td->border.toReal();
985 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
987 // draw the table headers
988 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
989 int page = tableStartPage + 1;
990 while (page <= tableEndPage) {
991 const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
992 const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
993 for (int r = 0; r < headerRowCount; ++r) {
994 for (int c = 0; c < columns; ++c) {
995 QTextTableCell cell = table->cellAt(r, c);
996 QAbstractTextDocumentLayout::PaintContext cell_context = context;
997 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
998 QRectF cellRect = td->cellRect(cell);
1000 cellRect.translate(off.x(), headerOffset);
1001 // we need to account for the cell border in the clipping test
1002 int leftAdjust = qMin(qreal(0), 1 - border);
1003 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1006 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1007 &offsetOfRepaintedCursorBlock);
1016 if (context.clip.isValid()) {
1017 QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
1018 if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
1020 firstRow = rowIt - td->rowPositions.constBegin();
1023 rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
1024 if (rowIt != td->rowPositions.constEnd()) {
1026 lastRow = rowIt - td->rowPositions.constBegin();
1030 for (int c = 0; c < columns; ++c) {
1031 QTextTableCell cell = table->cellAt(firstRow, c);
1032 firstRow = qMin(firstRow, cell.row());
1035 for (int r = firstRow; r < lastRow; ++r) {
1036 for (int c = 0; c < columns; ++c) {
1037 QTextTableCell cell = table->cellAt(r, c);
1038 QAbstractTextDocumentLayout::PaintContext cell_context = context;
1039 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1040 QRectF cellRect = td->cellRect(cell);
1042 cellRect.translate(off);
1043 // we need to account for the cell border in the clipping test
1044 int leftAdjust = qMin(qreal(0), 1 - border);
1045 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1048 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1049 &offsetOfRepaintedCursorBlock);
1054 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1056 QTextFrame::Iterator it = frame->begin();
1058 if (frame == docPrivate->rootFrame())
1059 it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
1061 QList<QTextFrame *> floats;
1062 for (int i = 0; i < fd->floats.count(); ++i)
1063 floats.append(fd->floats.at(i));
1065 drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
1068 if (cursorBlockNeedingRepaint.isValid()) {
1069 const QPen oldPen = painter->pen();
1070 painter->setPen(context.palette.color(QPalette::Text));
1071 const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
1072 cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
1073 cursorPos, cursorWidth);
1074 painter->setPen(oldPen);
1082 void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
1083 QTextTable *table, QTextTableData *td, int r, int c,
1084 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
1086 QTextTableCell cell = table->cellAt(r, c);
1087 int rspan = cell.rowSpan();
1088 int cspan = cell.columnSpan();
1090 int cr = cell.row();
1095 int cc = cell.column();
1100 QTextFormat fmt = cell.format();
1101 const QFixed leftPadding = td->leftPadding(fmt);
1102 const QFixed topPadding = td->topPadding(fmt);
1104 if (td->border != 0) {
1105 const QBrush oldBrush = painter->brush();
1106 const QPen oldPen = painter->pen();
1108 const qreal border = td->border.toReal();
1110 QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
1112 // invert the border style for cells
1113 QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
1114 switch (cellBorder) {
1115 case QTextFrameFormat::BorderStyle_Inset:
1116 cellBorder = QTextFrameFormat::BorderStyle_Outset;
1118 case QTextFrameFormat::BorderStyle_Outset:
1119 cellBorder = QTextFrameFormat::BorderStyle_Inset;
1121 case QTextFrameFormat::BorderStyle_Groove:
1122 cellBorder = QTextFrameFormat::BorderStyle_Ridge;
1124 case QTextFrameFormat::BorderStyle_Ridge:
1125 cellBorder = QTextFrameFormat::BorderStyle_Groove;
1131 qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1132 qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1134 const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1135 if (r >= headerRowCount)
1136 topMargin += td->headerHeight.toReal();
1138 drawBorder(painter, borderRect, topMargin, bottomMargin,
1139 border, table->format().borderBrush(), cellBorder);
1141 painter->setBrush(oldBrush);
1142 painter->setPen(oldPen);
1145 const QBrush bg = cell.format().background();
1146 const QPointF brushOrigin = painter->brushOrigin();
1147 if (bg.style() != Qt::NoBrush) {
1148 fillBackground(painter, cellRect, bg, cellRect.topLeft());
1150 if (bg.style() > Qt::SolidPattern)
1151 painter->setBrushOrigin(cellRect.topLeft());
1154 const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
1156 const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
1157 cellRect.top() + (topPadding + verticalOffset).toReal());
1159 QTextBlock repaintBlock;
1160 drawFlow(cellPos, painter, cell_context, cell.begin(),
1161 td->childFrameMap.values(r + c * table->rows()),
1163 if (repaintBlock.isValid()) {
1164 *cursorBlockNeedingRepaint = repaintBlock;
1165 *cursorBlockOffset = cellPos;
1168 if (bg.style() > Qt::SolidPattern)
1169 painter->setBrushOrigin(brushOrigin);
1172 void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
1173 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
1175 Q_Q(const QTextDocumentLayout);
1176 const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
1178 QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
1179 if (inRootFrame && context.clip.isValid()) {
1180 lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
1183 QTextBlock previousBlock;
1184 QTextFrame *previousFrame = 0;
1186 for (; !it.atEnd(); ++it) {
1187 QTextFrame *c = it.currentFrame();
1189 if (inRootFrame && !checkPoints.isEmpty()) {
1190 int currentPosInDoc;
1192 currentPosInDoc = c->firstPosition();
1194 currentPosInDoc = it.currentBlock().position();
1196 // if we're past what is already laid out then we're better off
1197 // not trying to draw things that may not be positioned correctly yet
1198 if (currentPosInDoc >= checkPoints.last().positionInFrame)
1201 if (lastVisibleCheckPoint != checkPoints.end()
1202 && context.clip.isValid()
1203 && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
1209 drawFrame(offset, painter, context, c);
1211 QAbstractTextDocumentLayout::PaintContext pc = context;
1212 if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
1213 pc.selections.clear();
1214 drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
1217 // when entering a table and the previous block is empty
1218 // then layoutFlow 'hides' the block that just causes a
1219 // new line by positioning it /on/ the table border. as we
1220 // draw that block before the table itself the decoration
1221 // 'overpaints' the cursor and we need to paint it afterwards
1223 if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
1224 && previousBlock.contains(context.cursorPosition)
1226 *cursorBlockNeedingRepaint = previousBlock;
1229 previousBlock = it.currentBlock();
1233 for (int i = 0; i < floats.count(); ++i) {
1234 QTextFrame *frame = floats.at(i);
1235 if (!isFrameFromInlineObject(frame)
1236 || frame->frameFormat().position() == QTextFrameFormat::InFlow)
1239 const int pos = frame->firstPosition() - 1;
1240 QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
1241 QTextObjectInterface *handler = q->handlerForObject(format.objectType());
1243 QRectF rect = frameBoundingRectInternal(frame);
1244 handler->drawObject(painter, rect, document, pos, format);
1249 void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
1250 const QAbstractTextDocumentLayout::PaintContext &context,
1251 QTextBlock bl, bool inRootFrame) const
1253 const QTextLayout *tl = bl.layout();
1254 QRectF r = tl->boundingRect();
1255 r.translate(offset + tl->position());
1256 if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom()))
1258 // LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
1260 QTextBlockFormat blockFormat = bl.blockFormat();
1262 QBrush bg = blockFormat.background();
1263 if (bg != Qt::NoBrush) {
1266 // extend the background rectangle if we're in the root frame with NoWrap,
1267 // as the rect of the text block will then be only the width of the text
1268 // instead of the full page width
1269 if (inRootFrame && document->pageSize().width() <= 0) {
1270 const QTextFrameData *fd = data(document->rootFrame());
1271 rect.setRight((fd->size.width - fd->rightMargin).toReal());
1274 fillBackground(painter, rect, bg, r.topLeft());
1277 QVector<QTextLayout::FormatRange> selections;
1278 int blpos = bl.position();
1279 int bllen = bl.length();
1280 const QTextCharFormat *selFormat = 0;
1281 for (int i = 0; i < context.selections.size(); ++i) {
1282 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
1283 const int selStart = range.cursor.selectionStart() - blpos;
1284 const int selEnd = range.cursor.selectionEnd() - blpos;
1285 if (selStart < bllen && selEnd > 0
1286 && selEnd > selStart) {
1287 QTextLayout::FormatRange o;
1289 o.length = selEnd - selStart;
1290 o.format = range.format;
1291 selections.append(o);
1292 } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
1293 && bl.contains(range.cursor.position())) {
1294 // for full width selections we don't require an actual selection, just
1295 // a position to specify the line. that's more convenience in usage.
1296 QTextLayout::FormatRange o;
1297 QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
1298 o.start = l.textStart();
1299 o.length = l.textLength();
1300 if (o.start + o.length == bllen - 1)
1301 ++o.length; // include newline
1302 o.format = range.format;
1303 selections.append(o);
1305 if (selStart < 0 && selEnd >= 1)
1306 selFormat = &range.format;
1309 QTextObject *object = document->objectForFormat(bl.blockFormat());
1310 if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
1311 drawListItem(offset, painter, context, bl, selFormat);
1313 QPen oldPen = painter->pen();
1314 painter->setPen(context.palette.color(QPalette::Text));
1316 tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
1318 if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
1319 || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) {
1320 int cpos = context.cursorPosition;
1322 cpos = tl->preeditAreaPosition() - (cpos + 2);
1325 tl->drawCursor(painter, offset, cpos, cursorWidth);
1328 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1329 const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
1330 painter->setPen(context.palette.color(QPalette::Dark));
1331 qreal y = r.bottom();
1332 if (bl.length() == 1)
1333 y = r.top() + r.height() / 2;
1335 const qreal middleX = r.left() + r.width() / 2;
1336 painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
1339 painter->setPen(oldPen);
1343 void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
1344 const QAbstractTextDocumentLayout::PaintContext &context,
1345 QTextBlock bl, const QTextCharFormat *selectionFormat) const
1347 Q_Q(const QTextDocumentLayout);
1348 const QTextBlockFormat blockFormat = bl.blockFormat();
1349 const QTextCharFormat charFormat = QTextCursor(bl).charFormat();
1350 QFont font(charFormat.font());
1351 if (q->paintDevice())
1352 font = QFont(font, q->paintDevice());
1354 const QFontMetrics fontMetrics(font);
1355 QTextObject * const object = document->objectForFormat(blockFormat);
1356 const QTextListFormat lf = object->format().toListFormat();
1357 int style = lf.style();
1361 if (blockFormat.hasProperty(QTextFormat::ListStyle))
1362 style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
1364 QTextLayout *layout = bl.layout();
1365 if (layout->lineCount() == 0)
1367 QTextLine firstLine = layout->lineAt(0);
1368 Q_ASSERT(firstLine.isValid());
1369 QPointF pos = (offset + layout->position()).toPoint();
1370 Qt::LayoutDirection dir = bl.textDirection();
1372 QRectF textRect = firstLine.naturalTextRect();
1373 pos += textRect.topLeft().toPoint();
1374 if (dir == Qt::RightToLeft)
1375 pos.rx() += textRect.width();
1379 case QTextListFormat::ListDecimal:
1380 case QTextListFormat::ListLowerAlpha:
1381 case QTextListFormat::ListUpperAlpha:
1382 case QTextListFormat::ListLowerRoman:
1383 case QTextListFormat::ListUpperRoman:
1384 itemText = static_cast<QTextList *>(object)->itemText(bl);
1385 size.setWidth(fontMetrics.width(itemText));
1386 size.setHeight(fontMetrics.height());
1389 case QTextListFormat::ListSquare:
1390 case QTextListFormat::ListCircle:
1391 case QTextListFormat::ListDisc:
1392 size.setWidth(fontMetrics.lineSpacing() / 3);
1393 size.setHeight(size.width());
1396 case QTextListFormat::ListStyleUndefined:
1401 QRectF r(pos, size);
1403 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1404 if (dir == Qt::LeftToRight)
1405 xoff = -xoff - size.width();
1406 r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2));
1410 painter->setRenderHint(QPainter::Antialiasing);
1412 if (selectionFormat) {
1413 painter->setPen(QPen(selectionFormat->foreground(), 0));
1414 painter->fillRect(r, selectionFormat->background());
1416 QBrush fg = charFormat.foreground();
1417 if (fg == Qt::NoBrush)
1418 fg = context.palette.text();
1419 painter->setPen(QPen(fg, 0));
1422 QBrush brush = context.palette.brush(QPalette::Text);
1425 case QTextListFormat::ListDecimal:
1426 case QTextListFormat::ListLowerAlpha:
1427 case QTextListFormat::ListUpperAlpha:
1428 case QTextListFormat::ListLowerRoman:
1429 case QTextListFormat::ListUpperRoman: {
1430 QTextLayout layout(itemText, font, q->paintDevice());
1431 layout.setCacheEnabled(true);
1432 QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
1433 option.setTextDirection(dir);
1434 layout.setTextOption(option);
1435 layout.beginLayout();
1436 QTextLine line = layout.createLine();
1438 line.setLeadingIncluded(true);
1440 layout.draw(painter, QPointF(r.left(), pos.y()));
1443 case QTextListFormat::ListSquare:
1444 painter->fillRect(r, brush);
1446 case QTextListFormat::ListCircle:
1447 painter->setPen(QPen(brush, 0));
1448 painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
1450 case QTextListFormat::ListDisc:
1451 painter->setBrush(brush);
1452 painter->setPen(Qt::NoPen);
1453 painter->drawEllipse(r);
1455 case QTextListFormat::ListStyleUndefined:
1464 static QFixed flowPosition(const QTextFrame::iterator it)
1469 if (it.currentFrame()) {
1470 return data(it.currentFrame())->position.y;
1472 QTextBlock block = it.currentBlock();
1473 QTextLayout *layout = block.layout();
1474 if (layout->lineCount() == 0)
1475 return QFixed::fromReal(layout->position().y());
1477 return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
1481 static QFixed firstChildPos(const QTextFrame *f)
1483 return flowPosition(f->begin());
1486 QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
1487 int layoutFrom, int layoutTo, QTextTableData *td,
1488 QFixed absoluteTableY, bool withPageBreaks)
1490 LDEBUG << "layoutCell";
1491 QTextLayoutStruct layoutStruct;
1492 layoutStruct.frame = t;
1493 layoutStruct.minimumWidth = 0;
1494 layoutStruct.maximumWidth = QFIXED_MAX;
1497 const QTextFormat fmt = cell.format();
1498 const QFixed topPadding = td->topPadding(fmt);
1499 if (withPageBreaks) {
1500 layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
1502 layoutStruct.x_left = 0;
1503 layoutStruct.x_right = width;
1504 // we get called with different widths all the time (for example for figuring
1505 // out the min/max widths), so we always have to do the full layout ;(
1506 // also when for example in a table layoutFrom/layoutTo affect only one cell,
1507 // making that one cell grow the available width of the other cells may change
1508 // (shrink) and therefore when layoutCell gets called for them they have to
1509 // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
1512 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
1513 if (layoutStruct.pageHeight < 0 || !withPageBreaks)
1514 layoutStruct.pageHeight = QFIXED_MAX;
1515 const int currentPage = layoutStruct.currentPage();
1516 layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
1517 layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
1518 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
1520 layoutStruct.fullLayout = true;
1522 QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
1523 layoutStruct.y = qMax(layoutStruct.y, pageTop);
1525 const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
1526 for (int i = 0; i < childFrames.size(); ++i) {
1527 QTextFrame *frame = childFrames.at(i);
1528 QTextFrameData *cd = data(frame);
1529 cd->sizeDirty = true;
1532 layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
1534 QFixed floatMinWidth;
1536 // floats that are located inside the text (like inline images) aren't taken into account by
1537 // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
1538 // do that here. For example with <td><img align="right" src="..." />blah</td>
1539 // when the image happens to be higher than the text
1540 for (int i = 0; i < childFrames.size(); ++i) {
1541 QTextFrame *frame = childFrames.at(i);
1542 QTextFrameData *cd = data(frame);
1544 if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
1545 layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
1547 floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
1550 // constraint the maximumWidth by the minimum width of the fixed size floats, to
1551 // keep them visible
1552 layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
1554 // as floats in cells get added to the table's float list but must not affect
1555 // floats in other cells we must clear the list here.
1556 data(t)->floats.clear();
1558 // qDebug() << "layoutCell done";
1560 return layoutStruct;
1563 QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
1565 LDEBUG << "layoutTable";
1566 QTextTableData *td = static_cast<QTextTableData *>(data(table));
1567 Q_ASSERT(td->sizeDirty);
1568 const int rows = table->rows();
1569 const int columns = table->columns();
1571 const QTextTableFormat fmt = table->format();
1573 td->childFrameMap.clear();
1575 const QList<QTextFrame *> children = table->childFrames();
1576 for (int i = 0; i < children.count(); ++i) {
1577 QTextFrame *frame = children.at(i);
1578 QTextTableCell cell = table->cellAt(frame->firstPosition());
1579 td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
1583 QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
1584 if (columnWidthConstraints.size() != columns)
1585 columnWidthConstraints.resize(columns);
1586 Q_ASSERT(columnWidthConstraints.count() == columns);
1588 const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
1589 td->deviceScale = scaleToDevice(qreal(1));
1590 td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
1591 const QFixed leftMargin = td->leftMargin + td->border + td->padding;
1592 const QFixed rightMargin = td->rightMargin + td->border + td->padding;
1593 const QFixed topMargin = td->topMargin + td->border + td->padding;
1595 const QFixed absoluteTableY = parentY + td->position.y;
1597 const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
1599 recalc_minmax_widths:
1601 QFixed remainingWidth = td->contentsWidth;
1602 // two (vertical) borders per cell per column
1603 remainingWidth -= columns * 2 * td->border;
1604 // inter-cell spacing
1605 remainingWidth -= (columns - 1) * cellSpacing;
1606 // cell spacing at the left and right hand side
1607 remainingWidth -= 2 * cellSpacing;
1608 // remember the width used to distribute to percentaged columns
1609 const QFixed initialTotalWidth = remainingWidth;
1611 td->widths.resize(columns);
1614 td->minWidths.resize(columns);
1615 // start with a minimum width of 0. totally empty
1616 // cells of default created tables are invisible otherwise
1617 // and therefore hardly editable
1618 td->minWidths.fill(1);
1620 td->maxWidths.resize(columns);
1621 td->maxWidths.fill(QFIXED_MAX);
1623 // calculate minimum and maximum sizes of the columns
1624 for (int i = 0; i < columns; ++i) {
1625 for (int row = 0; row < rows; ++row) {
1626 const QTextTableCell cell = table->cellAt(row, i);
1627 const int cspan = cell.columnSpan();
1629 if (cspan > 1 && i != cell.column())
1632 const QTextFormat fmt = cell.format();
1633 const QFixed leftPadding = td->leftPadding(fmt);
1634 const QFixed rightPadding = td->rightPadding(fmt);
1635 const QFixed widthPadding = leftPadding + rightPadding;
1637 // to figure out the min and the max width lay out the cell at
1638 // maximum width. otherwise the maxwidth calculation sometimes
1639 // returns wrong values
1640 QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
1641 layoutTo, td, absoluteTableY,
1642 /*withPageBreaks =*/false);
1644 // distribute the minimum width over all columns the cell spans
1645 QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
1646 for (int n = 0; n < cspan; ++n) {
1647 const int col = i + n;
1648 QFixed w = widthToDistribute / (cspan - n);
1649 td->minWidths[col] = qMax(td->minWidths.at(col), w);
1650 widthToDistribute -= td->minWidths.at(col);
1651 if (widthToDistribute <= 0)
1655 QFixed maxW = td->maxWidths.at(i);
1656 if (layoutStruct.maximumWidth != QFIXED_MAX) {
1657 if (maxW == QFIXED_MAX)
1658 maxW = layoutStruct.maximumWidth + widthPadding;
1660 maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
1662 if (maxW == QFIXED_MAX)
1665 widthToDistribute = maxW;
1666 for (int n = 0; n < cspan; ++n) {
1667 const int col = i + n;
1668 QFixed w = widthToDistribute / (cspan - n);
1669 td->maxWidths[col] = qMax(td->minWidths.at(col), w);
1670 widthToDistribute -= td->maxWidths.at(col);
1671 if (widthToDistribute <= 0)
1677 // set fixed values, figure out total percentages used and number of
1678 // variable length cells. Also assign the minimum width for variable columns.
1679 QFixed totalPercentage;
1680 int variableCols = 0;
1681 QFixed totalMinWidth = 0;
1682 for (int i = 0; i < columns; ++i) {
1683 const QTextLength &length = columnWidthConstraints.at(i);
1684 if (length.type() == QTextLength::FixedLength) {
1685 td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
1686 remainingWidth -= td->widths.at(i);
1687 } else if (length.type() == QTextLength::PercentageLength) {
1688 totalPercentage += QFixed::fromReal(length.rawValue());
1689 } else if (length.type() == QTextLength::VariableLength) {
1692 td->widths[i] = td->minWidths.at(i);
1693 remainingWidth -= td->minWidths.at(i);
1695 totalMinWidth += td->minWidths.at(i);
1698 // set percentage values
1700 const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
1701 QFixed remainingMinWidths = totalMinWidth;
1702 for (int i = 0; i < columns; ++i) {
1703 remainingMinWidths -= td->minWidths.at(i);
1704 if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
1705 const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
1707 const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
1708 if (percentWidth >= td->minWidths.at(i)) {
1709 td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
1711 td->widths[i] = td->minWidths.at(i);
1713 remainingWidth -= td->widths.at(i);
1718 // for variable columns distribute the remaining space
1719 if (variableCols > 0 && remainingWidth > 0) {
1720 QVarLengthArray<int> columnsWithProperMaxSize;
1721 for (int i = 0; i < columns; ++i)
1722 if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
1723 && td->maxWidths.at(i) != QFIXED_MAX)
1724 columnsWithProperMaxSize.append(i);
1726 QFixed lastRemainingWidth = remainingWidth;
1727 while (remainingWidth > 0) {
1728 for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
1729 const int col = columnsWithProperMaxSize[k];
1730 const int colsLeft = columnsWithProperMaxSize.count() - k;
1731 const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
1732 td->widths[col] += w;
1733 remainingWidth -= w;
1735 if (remainingWidth == lastRemainingWidth)
1737 lastRemainingWidth = remainingWidth;
1740 if (remainingWidth > 0
1741 // don't unnecessarily grow variable length sized tables
1742 && fmt.width().type() != QTextLength::VariableLength) {
1743 const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
1744 for (int col = 0; col < columns; ++col) {
1745 if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
1746 td->widths[col] += widthPerAnySizedCol;
1751 td->columnPositions.resize(columns);
1752 td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
1754 for (int i = 1; i < columns; ++i)
1755 td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
1757 // - margin to compensate the + margin in columnPositions[0]
1758 const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin;
1760 // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
1762 if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
1763 && contentsWidth > td->contentsWidth) {
1764 docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
1765 // go back to the top of the function
1766 goto recalc_minmax_widths;
1769 td->contentsWidth = contentsWidth;
1771 docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
1773 td->heights.resize(rows);
1774 td->heights.fill(0);
1776 td->rowPositions.resize(rows);
1777 td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
1779 bool haveRowSpannedCells = false;
1781 // need to keep track of cell heights for vertical alignment
1782 QVector<QFixed> cellHeights;
1783 cellHeights.reserve(rows * columns);
1785 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1786 if (pageHeight <= 0)
1787 pageHeight = QFIXED_MAX;
1789 QVector<QFixed> heightToDistribute;
1790 heightToDistribute.resize(columns);
1792 td->headerHeight = 0;
1793 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1794 const QFixed originalTopMargin = td->effectiveTopMargin;
1795 bool hasDroppedTable = false;
1797 // now that we have the column widths we can lay out all cells with the right width.
1798 // spanning cells are only allowed to grow the last row spanned by the cell.
1800 // ### this could be made faster by iterating over the cells array of QTextTable
1801 for (int r = 0; r < rows; ++r) {
1802 td->calcRowPosition(r);
1804 const int tableStartPage = (absoluteTableY / pageHeight).truncate();
1805 const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1806 const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
1807 const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
1808 const QFixed nextPageTop = pageTop + pageHeight;
1810 if (td->rowPositions[r] > pageBottom)
1811 td->rowPositions[r] = nextPageTop;
1812 else if (td->rowPositions[r] < pageTop)
1813 td->rowPositions[r] = pageTop;
1815 bool dropRowToNextPage = true;
1816 int cellCountBeforeRow = cellHeights.size();
1818 // if we drop the row to the next page we need to subtract the drop
1819 // distance from any row spanning cells
1820 QFixed dropDistance = 0;
1823 const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1824 // if any of the header rows or the first non-header row start on the next page
1825 // then the entire header should be dropped
1826 if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
1827 td->rowPositions[0] = nextPageTop;
1828 cellHeights.clear();
1829 td->effectiveTopMargin = originalTopMargin;
1830 hasDroppedTable = true;
1835 int rowCellCount = 0;
1836 for (int c = 0; c < columns; ++c) {
1837 QTextTableCell cell = table->cellAt(r, c);
1838 const int rspan = cell.rowSpan();
1839 const int cspan = cell.columnSpan();
1841 if (cspan > 1 && cell.column() != c)
1845 haveRowSpannedCells = true;
1847 const int cellRow = cell.row();
1849 // the last row gets all the remaining space
1850 if (cellRow + rspan - 1 == r)
1851 td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
1856 const QTextFormat fmt = cell.format();
1858 const QFixed topPadding = td->topPadding(fmt);
1859 const QFixed bottomPadding = td->bottomPadding(fmt);
1860 const QFixed leftPadding = td->leftPadding(fmt);
1861 const QFixed rightPadding = td->rightPadding(fmt);
1862 const QFixed widthPadding = leftPadding + rightPadding;
1866 const QFixed width = td->cellWidth(c, cspan) - widthPadding;
1867 QTextLayoutStruct layoutStruct = layoutCell(table, cell, width,
1868 layoutFrom, layoutTo,
1870 /*withPageBreaks =*/true);
1872 const QFixed height = layoutStruct.y + bottomPadding + topPadding;
1875 heightToDistribute[c] = height + dropDistance;
1877 td->heights[r] = qMax(td->heights.at(r), height);
1879 cellHeights.append(layoutStruct.y);
1881 QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
1882 if (childPos < pageBottom)
1883 dropRowToNextPage = false;
1886 if (rowCellCount > 0 && dropRowToNextPage) {
1887 dropDistance = nextPageTop - td->rowPositions[r];
1888 td->rowPositions[r] = nextPageTop;
1890 dropRowToNextPage = false;
1891 cellHeights.resize(cellCountBeforeRow);
1892 if (r > headerRowCount)
1893 td->heights[r-1] = pageBottom - td->rowPositions[r-1];
1897 if (haveRowSpannedCells) {
1898 const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
1899 for (int c = 0; c < columns; ++c)
1900 heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
1903 if (r == headerRowCount - 1) {
1904 td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border;
1905 td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
1906 td->effectiveTopMargin += td->headerHeight;
1910 td->effectiveTopMargin = originalTopMargin;
1912 // now that all cells have been properly laid out, we can compute the
1913 // vertical offsets for vertical alignment
1914 td->cellVerticalOffsets.resize(rows * columns);
1916 for (int r = 0; r < rows; ++r) {
1917 for (int c = 0; c < columns; ++c) {
1918 QTextTableCell cell = table->cellAt(r, c);
1919 if (cell.row() != r || cell.column() != c)
1922 const int rowSpan = cell.rowSpan();
1923 const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
1925 const QTextCharFormat cellFormat = cell.format();
1926 const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
1929 switch (cellFormat.verticalAlignment()) {
1930 case QTextCharFormat::AlignMiddle:
1931 offset = (availableHeight - cellHeight) / 2;
1933 case QTextCharFormat::AlignBottom:
1934 offset = availableHeight - cellHeight;
1940 for (int rd = 0; rd < cell.rowSpan(); ++rd) {
1941 for (int cd = 0; cd < cell.columnSpan(); ++cd) {
1942 const int index = (c + cd) + (r + rd) * columns;
1943 td->cellVerticalOffsets[index] = offset;
1949 td->minimumWidth = td->columnPositions.at(0);
1950 for (int i = 0; i < columns; ++i) {
1951 td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
1953 td->minimumWidth += rightMargin - td->border;
1955 td->maximumWidth = td->columnPositions.at(0);
1956 for (int i = 0; i < columns; ++i)
1957 if (td->maxWidths.at(i) != QFIXED_MAX)
1958 td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
1959 td->maximumWidth += rightMargin - td->border;
1961 td->updateTableSize();
1962 td->sizeDirty = false;
1963 return QRectF(); // invalid rect -> update everything
1966 void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
1968 QTextFrameData *fd = data(frame);
1970 QTextFrame *parent = frame->parentFrame();
1972 QTextFrameData *pd = data(parent);
1973 Q_ASSERT(pd && pd->currentLayoutStruct);
1975 QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct;
1977 if (!pd->floats.contains(frame))
1978 pd->floats.append(frame);
1979 fd->layoutDirty = true;
1980 Q_ASSERT(!fd->sizeDirty);
1982 // qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
1983 QFixed y = layoutStruct->y;
1986 floatMargins(y, layoutStruct, &left, &right);
1987 // qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
1988 if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
1989 layoutStruct->pendingFloats.append(frame);
1990 // qDebug() << " adding to pending list";
1995 bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom);
1996 if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) {
1997 layoutStruct->newPage();
1998 y = layoutStruct->y;
2000 frameSpansIntoNextPage = false;
2003 y = findY(y, layoutStruct, fd->size.width);
2006 floatMargins(y, layoutStruct, &left, &right);
2008 if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
2009 fd->position.x = left;
2012 fd->position.x = right - fd->size.width;
2016 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
2017 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
2019 // qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
2020 fd->layoutDirty = false;
2022 // If the frame is a table, then positioning it will affect the size if it covers more than
2023 // one page, because of page breaks and repeating the header.
2024 if (qobject_cast<QTextTable *>(frame) != 0)
2025 fd->sizeDirty = frameSpansIntoNextPage;
2028 QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
2030 LDEBUG << "layoutFrame (pre)";
2031 Q_ASSERT(data(f)->sizeDirty);
2032 // qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2034 QTextFrameFormat fformat = f->frameFormat();
2036 QTextFrame *parent = f->parentFrame();
2037 const QTextFrameData *pd = parent ? data(parent) : 0;
2039 const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
2040 QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
2041 if (fformat.width().type() == QTextLength::FixedLength)
2042 width = scaleToDevice(width);
2044 const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
2045 const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
2046 ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
2049 return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
2052 QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
2054 LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo;
2055 Q_ASSERT(data(f)->sizeDirty);
2056 // qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2058 QTextFrameData *fd = data(f);
2059 QFixed newContentsWidth;
2062 QTextFrameFormat fformat = f->frameFormat();
2063 // set sizes of this frame from the format
2064 fd->topMargin = QFixed::fromReal(fformat.topMargin());
2065 fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin());
2066 fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
2067 fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
2068 fd->border = QFixed::fromReal(fformat.border());
2069 fd->padding = QFixed::fromReal(fformat.padding());
2071 QTextFrame *parent = f->parentFrame();
2072 const QTextFrameData *pd = parent ? data(parent) : 0;
2074 // accumulate top and bottom margins
2076 fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
2077 fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
2079 if (qobject_cast<QTextTable *>(parent)) {
2080 const QTextTableData *td = static_cast<const QTextTableData *>(pd);
2081 fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
2082 fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
2085 fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
2086 fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
2089 newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
2090 - fd->leftMargin - fd->rightMargin;
2092 if (frameHeight != -1) {
2093 fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
2094 - fd->topMargin - fd->bottomMargin;
2096 fd->contentsHeight = frameHeight;
2100 if (isFrameFromInlineObject(f)) {
2101 // never reached, handled in resizeInlineObject/positionFloat instead
2105 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2106 fd->contentsWidth = newContentsWidth;
2107 return layoutTable(table, layoutFrom, layoutTo, parentY);
2110 // set fd->contentsWidth temporarily, so that layoutFrame for the children
2111 // picks the right width. We'll initialize it properly at the end of this
2113 fd->contentsWidth = newContentsWidth;
2115 QTextLayoutStruct layoutStruct;
2116 layoutStruct.frame = f;
2117 layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
2118 layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
2119 layoutStruct.y = fd->topMargin + fd->border + fd->padding;
2120 layoutStruct.frameY = parentY + fd->position.y;
2121 layoutStruct.contentsWidth = 0;
2122 layoutStruct.minimumWidth = 0;
2123 layoutStruct.maximumWidth = QFIXED_MAX;
2124 layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth;
2125 layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2126 LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
2127 << "fullLayout" << layoutStruct.fullLayout;
2128 fd->oldContentsWidth = newContentsWidth;
2130 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
2131 if (layoutStruct.pageHeight < 0)
2132 layoutStruct.pageHeight = QFIXED_MAX;
2134 const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
2135 layoutStruct.pageTopMargin = fd->effectiveTopMargin;
2136 layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
2137 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
2139 if (!f->parentFrame())
2140 idealWidth = 0; // reset
2142 QTextFrame::Iterator it = f->begin();
2143 layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
2145 QFixed maxChildFrameWidth = 0;
2146 QList<QTextFrame *> children = f->childFrames();
2147 for (int i = 0; i < children.size(); ++i) {
2148 QTextFrame *c = children.at(i);
2149 QTextFrameData *cd = data(c);
2150 maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
2153 const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
2154 if (!f->parentFrame()) {
2155 idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
2156 idealWidth += marginWidth.toReal();
2159 QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
2160 fd->contentsWidth = actualWidth;
2161 if (newContentsWidth <= 0) { // nowrap layout?
2162 fd->contentsWidth = newContentsWidth;
2165 fd->minimumWidth = layoutStruct.minimumWidth;
2166 fd->maximumWidth = layoutStruct.maximumWidth;
2168 fd->size.height = fd->contentsHeight == -1
2169 ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
2170 : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
2171 fd->size.width = actualWidth + marginWidth;
2172 fd->sizeDirty = false;
2173 if (layoutStruct.updateRectForFloats.isValid())
2174 layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
2175 return layoutStruct.updateRect;
2178 void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct,
2179 int layoutFrom, int layoutTo, QFixed width)
2181 LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
2182 QTextFrameData *fd = data(layoutStruct->frame);
2184 fd->currentLayoutStruct = layoutStruct;
2186 QTextFrame::Iterator previousIt;
2188 const bool inRootFrame = (it.parentFrame() == document->rootFrame());
2190 bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
2192 if (!redoCheckPoints) {
2193 QVector<QCheckPoint>::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom);
2194 if (checkPoint != checkPoints.end()) {
2195 if (checkPoint != checkPoints.begin())
2198 layoutStruct->y = checkPoint->y;
2199 layoutStruct->frameY = checkPoint->frameY;
2200 layoutStruct->minimumWidth = checkPoint->minimumWidth;
2201 layoutStruct->maximumWidth = checkPoint->maximumWidth;
2202 layoutStruct->contentsWidth = checkPoint->contentsWidth;
2204 if (layoutStruct->pageHeight > 0) {
2205 int page = layoutStruct->currentPage();
2206 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2209 it = frameIteratorForTextPosition(checkPoint->positionInFrame);
2210 checkPoints.resize(checkPoint - checkPoints.begin() + 1);
2212 if (checkPoint != checkPoints.begin()) {
2217 redoCheckPoints = true;
2221 if (redoCheckPoints) {
2222 checkPoints.clear();
2224 cp.y = layoutStruct->y;
2225 cp.frameY = layoutStruct->frameY;
2226 cp.positionInFrame = 0;
2227 cp.minimumWidth = layoutStruct->minimumWidth;
2228 cp.maximumWidth = layoutStruct->maximumWidth;
2229 cp.contentsWidth = layoutStruct->contentsWidth;
2230 checkPoints.append(cp);
2234 QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
2236 QFixed maximumBlockWidth = 0;
2237 while (!it.atEnd()) {
2238 QTextFrame *c = it.currentFrame();
2241 if (it.currentFrame())
2242 docPos = it.currentFrame()->firstPosition();
2244 docPos = it.currentBlock().position();
2247 if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) {
2249 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2250 if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
2252 p.y = layoutStruct->y;
2253 p.frameY = layoutStruct->frameY;
2254 p.positionInFrame = docPos;
2255 p.minimumWidth = layoutStruct->minimumWidth;
2256 p.maximumWidth = layoutStruct->maximumWidth;
2257 p.contentsWidth = layoutStruct->contentsWidth;
2258 checkPoints.append(p);
2260 if (currentLazyLayoutPosition != -1
2261 && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
2269 // position child frame
2270 QTextFrameData *cd = data(c);
2272 QTextFrameFormat fformat = c->frameFormat();
2274 if (fformat.position() == QTextFrameFormat::InFlow) {
2275 if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2276 layoutStruct->newPage();
2279 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2280 left = qMax(left, layoutStruct->x_left);
2281 right = qMin(right, layoutStruct->x_right);
2283 if (right - left < cd->size.width) {
2284 layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
2285 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2288 QFixedPoint pos(left, layoutStruct->y);
2290 Qt::Alignment align = Qt::AlignLeft;
2292 QTextTable *table = qobject_cast<QTextTable *>(c);
2295 align = table->format().alignment() & Qt::AlignHorizontal_Mask;
2297 // detect whether we have any alignment in the document that disallows optimizations,
2298 // such as not laying out the document again in a textedit with wrapping disabled.
2299 if (inRootFrame && !(align & Qt::AlignLeft))
2300 contentHasAlignment = true;
2304 if (document->pageSize().height() > 0.0f)
2305 cd->sizeDirty = true;
2307 if (cd->sizeDirty) {
2309 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2311 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2313 QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
2314 absoluteChildPos += layoutStruct->frameY;
2316 // drop entire frame to next page if first child of frame is on next page
2317 if (absoluteChildPos > layoutStruct->pageBottom) {
2318 layoutStruct->newPage();
2319 pos.y = layoutStruct->y;
2322 cd->sizeDirty = true;
2325 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2327 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2331 // align only if there is space for alignment
2332 if (right - left > cd->size.width) {
2333 if (align & Qt::AlignRight)
2334 pos.x += layoutStruct->x_right - cd->size.width;
2335 else if (align & Qt::AlignHCenter)
2336 pos.x += (layoutStruct->x_right - cd->size.width) / 2;
2341 layoutStruct->y += cd->size.height;
2342 const int page = layoutStruct->currentPage();
2343 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2345 cd->layoutDirty = false;
2347 if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2348 layoutStruct->newPage();
2350 QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
2354 updateRect = layoutFrame(c, layoutFrom, layoutTo);
2358 // If the size was made dirty when the position was set, layout again
2360 updateRect = layoutFrame(c, layoutFrom, layoutTo);
2362 QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
2364 if (frameRect == oldFrameRect && updateRect.isValid())
2365 updateRect.translate(cd->position.toPointF());
2367 updateRect = frameRect;
2369 layoutStruct->addUpdateRectForFloat(updateRect);
2370 if (oldFrameRect.isValid())
2371 layoutStruct->addUpdateRectForFloat(oldFrameRect);
2374 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
2375 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
2380 QTextFrame::Iterator lastIt;
2381 if (!previousIt.atEnd())
2382 lastIt = previousIt;
2384 QTextBlock block = it.currentBlock();
2387 const QTextBlockFormat blockFormat = block.blockFormat();
2389 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2390 layoutStruct->newPage();
2392 const QFixed origY = layoutStruct->y;
2393 const QFixed origPageBottom = layoutStruct->pageBottom;
2394 const QFixed origMaximumWidth = layoutStruct->maximumWidth;
2395 layoutStruct->maximumWidth = 0;
2397 const QTextBlockFormat *previousBlockFormatPtr = 0;
2398 if (lastIt.currentBlock().isValid())
2399 previousBlockFormatPtr = &previousBlockFormat;
2401 // layout and position child block
2402 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2404 // detect whether we have any alignment in the document that disallows optimizations,
2405 // such as not laying out the document again in a textedit with wrapping disabled.
2406 if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
2407 contentHasAlignment = true;
2409 // if the block right before a table is empty 'hide' it by
2410 // positioning it into the table border
2411 if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
2412 const QTextBlock lastBlock = lastIt.currentBlock();
2413 const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
2414 layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
2415 layoutStruct->pageBottom = origPageBottom;
2417 // if the block right after a table is empty then 'hide' it, too
2418 if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
2419 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2420 QTextLayout *layout = block.layout();
2422 QPointF pos((td->position.x + td->size.width).toReal(),
2423 (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
2425 layout->setPosition(pos);
2426 layoutStruct->y = origY;
2427 layoutStruct->pageBottom = origPageBottom;
2430 // if the block right after a table starts with a line separator, shift it up by one line
2431 if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
2432 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2433 QTextLayout *layout = block.layout();
2435 QFixed height = QFixed::fromReal(layout->lineAt(0).height());
2437 if (layoutStruct->pageBottom == origPageBottom) {
2438 layoutStruct->y -= height;
2439 layout->setPosition(layout->position() - QPointF(0, height.toReal()));
2441 // relayout block to correctly handle page breaks
2442 layoutStruct->y = origY - height;
2443 layoutStruct->pageBottom = origPageBottom;
2444 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2447 QPointF linePos((td->position.x + td->size.width).toReal(),
2448 (td->position.y + td->size.height - height).toReal());
2450 layout->lineAt(0).setPosition(linePos - layout->position());
2453 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2454 layoutStruct->newPage();
2457 maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
2458 layoutStruct->maximumWidth = origMaximumWidth;
2459 previousBlockFormat = blockFormat;
2462 if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
2463 layoutStruct->maximumWidth = maximumBlockWidth;
2465 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
2467 // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
2468 // we don't need to do it for tables though because floats in tables are per table
2469 // and not per cell and layoutCell already takes care of doing the same as we do here
2470 if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
2471 QList<QTextFrame *> children = layoutStruct->frame->childFrames();
2472 for (int i = 0; i < children.count(); ++i) {
2473 QTextFrameData *fd = data(children.at(i));
2474 if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
2475 layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
2480 // we assume that any float is aligned in a way that disallows the optimizations that rely
2481 // on unaligned content.
2482 if (!fd->floats.isEmpty())
2483 contentHasAlignment = true;
2486 //qDebug() << "layout done!";
2487 currentLazyLayoutPosition = -1;
2489 cp.y = layoutStruct->y;
2490 cp.positionInFrame = docPrivate->length();
2491 cp.minimumWidth = layoutStruct->minimumWidth;
2492 cp.maximumWidth = layoutStruct->maximumWidth;
2493 cp.contentsWidth = layoutStruct->contentsWidth;
2494 checkPoints.append(cp);
2495 checkPoints.reserve(checkPoints.size());
2497 currentLazyLayoutPosition = checkPoints.last().positionInFrame;
2499 //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
2504 fd->currentLayoutStruct = 0;
2507 static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling,
2508 QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight)
2510 *lineHeight = QFixed::fromReal(blockFormat.lineHeight(line.height(), scaling));
2512 if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight || blockFormat.lineHeightType() == QTextBlockFormat::MinimumHeight) {
2513 *lineBreakHeight = *lineHeight;
2514 if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight)
2515 *lineAdjustment = QFixed::fromReal(line.ascent() + qMax(line.leading(), qreal(0.0))) - ((*lineHeight * 4) / 5);
2517 *lineAdjustment = QFixed::fromReal(line.height()) - *lineHeight;
2520 *lineBreakHeight = QFixed::fromReal(line.height());
2521 *lineAdjustment = 0;
2525 void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
2526 QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
2528 Q_Q(QTextDocumentLayout);
2530 QTextLayout *tl = bl.layout();
2531 const int blockLength = bl.length();
2533 LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo;
2535 // qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')';
2537 if (previousBlockFormat) {
2538 qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
2539 if (margin > 0 && q->paintDevice()) {
2540 margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
2542 layoutStruct->y += QFixed::fromReal(margin);
2545 //QTextFrameData *fd = data(layoutStruct->frame);
2547 Qt::LayoutDirection dir = bl.textDirection();
2550 if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
2551 QFontMetricsF fm(bl.charFormat().font());
2552 extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5))));
2555 const QFixed indent = this->blockIndent(blockFormat);
2556 const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
2557 const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
2559 const QPointF oldPosition = tl->position();
2560 tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
2562 if (layoutStruct->fullLayout
2563 || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
2564 // force relayout if we cross a page boundary
2565 || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
2567 LDEBUG << " do layout";
2568 QTextOption option = docPrivate->defaultTextOption;
2569 option.setTextDirection(dir);
2570 option.setTabs( blockFormat.tabPositions() );
2572 Qt::Alignment align = docPrivate->defaultTextOption.alignment();
2573 if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
2574 align = blockFormat.alignment();
2575 option.setAlignment(QGuiApplicationPrivate::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
2577 if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
2578 option.setWrapMode(QTextOption::ManualWrap);
2581 tl->setTextOption(option);
2583 const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
2585 // qDebug() << " layouting block at" << bl.position();
2586 const QFixed cy = layoutStruct->y;
2587 const QFixed l = layoutStruct->x_left + totalLeftMargin;
2588 const QFixed r = layoutStruct->x_right - totalRightMargin;
2591 bool firstLine = true;
2593 QTextLine line = tl->createLine();
2594 if (!line.isValid())
2596 line.setLeadingIncluded(true);
2599 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2600 left = qMax(left, l);
2601 right = qMin(right, r);
2604 text_indent = QFixed::fromReal(blockFormat.textIndent());
2605 if (dir == Qt::LeftToRight)
2606 left += text_indent;
2608 right -= text_indent;
2611 // qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
2613 if (fixedColumnWidth != -1)
2614 line.setNumColumns(fixedColumnWidth, (right - left).toReal());
2616 line.setLineWidth((right - left).toReal());
2618 // qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
2619 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2620 left = qMax(left, l);
2621 right = qMin(right, r);
2622 if (dir == Qt::LeftToRight)
2623 left += text_indent;
2625 right -= text_indent;
2627 if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2628 // float has been added in the meantime, redo
2629 layoutStruct->pendingFloats.clear();
2631 line.setLineWidth((right-left).toReal());
2632 if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2633 if (haveWordOrAnyWrapMode) {
2634 option.setWrapMode(QTextOption::WrapAnywhere);
2635 tl->setTextOption(option);
2638 layoutStruct->pendingFloats.clear();
2639 // lines min width more than what we have
2640 layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
2641 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2642 left = qMax(left, l);
2643 right = qMin(right, r);
2644 if (dir == Qt::LeftToRight)
2645 left += text_indent;
2647 right -= text_indent;
2648 line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
2650 if (haveWordOrAnyWrapMode) {
2651 option.setWrapMode(QTextOption::WordWrap);
2652 tl->setTextOption(option);
2658 QFixed lineBreakHeight, lineHeight, lineAdjustment;
2659 qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
2660 qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
2661 getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight);
2663 if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) {
2664 layoutStruct->newPage();
2666 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2667 left = qMax(left, l);
2668 right = qMin(right, r);
2669 if (dir == Qt::LeftToRight)
2670 left += text_indent;
2672 right -= text_indent;
2675 line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy - lineAdjustment).toReal()));
2676 layoutStruct->y += lineHeight;
2677 layoutStruct->contentsWidth
2678 = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
2681 for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
2682 QTextFrame *f = layoutStruct->pendingFloats.at(i);
2685 layoutStruct->pendingFloats.clear();
2689 const int cnt = tl->lineCount();
2690 for (int i = 0; i < cnt; ++i) {
2691 LDEBUG << "going to move text line" << i;
2692 QTextLine line = tl->lineAt(i);
2693 layoutStruct->contentsWidth
2694 = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
2696 QFixed lineBreakHeight, lineHeight, lineAdjustment;
2697 qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
2698 qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
2699 getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight);
2701 if (layoutStruct->pageHeight != QFIXED_MAX) {
2702 if (layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom)
2703 layoutStruct->newPage();
2704 line.setPosition(QPointF(line.position().x(), (layoutStruct->y - lineAdjustment).toReal() - tl->position().y()));
2706 layoutStruct->y += lineHeight;
2708 if (layoutStruct->updateRect.isValid()
2709 && blockLength > 1) {
2710 if (layoutFrom >= blockPosition + blockLength) {
2711 // if our height didn't change and the change in the document is
2712 // in one of the later paragraphs, then we don't need to repaint
2714 layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
2715 } else if (layoutTo < blockPosition) {
2716 if (oldPosition == tl->position())
2717 // if the change in the document happened earlier in the document
2718 // and our position did /not/ change because none of the earlier paragraphs
2719 // or frames changed their height, then we don't need to repaint
2721 layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
2723 layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
2728 // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
2729 const QFixed margins = totalLeftMargin + totalRightMargin;
2730 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
2732 const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
2735 if (layoutStruct->maximumWidth == QFIXED_MAX)
2736 layoutStruct->maximumWidth = maxW;
2738 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
2742 void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct,
2743 QFixed *left, QFixed *right) const
2745 // qDebug() << "floatMargins y=" << y;
2746 *left = layoutStruct->x_left;
2747 *right = layoutStruct->x_right;
2748 QTextFrameData *lfd = data(layoutStruct->frame);
2749 for (int i = 0; i < lfd->floats.size(); ++i) {
2750 QTextFrameData *fd = data(lfd->floats.at(i));
2751 if (!fd->layoutDirty) {
2752 if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
2753 // qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
2754 if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
2755 *left = qMax(*left, fd->position.x + fd->size.width);
2757 *right = qMin(*right, fd->position.x);
2761 // qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
2764 QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const
2767 requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
2769 // qDebug() << "findY:" << yFrom;
2771 floatMargins(yFrom, layoutStruct, &left, &right);
2772 // qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
2773 if (right-left >= requiredWidth)
2776 // move float down until we find enough space
2777 QFixed newY = QFIXED_MAX;
2778 QTextFrameData *lfd = data(layoutStruct->frame);
2779 for (int i = 0; i < lfd->floats.size(); ++i) {
2780 QTextFrameData *fd = data(lfd->floats.at(i));
2781 if (!fd->layoutDirty) {
2782 if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
2783 newY = qMin(newY, fd->position.y + fd->size.height);
2786 if (newY == QFIXED_MAX)
2793 QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
2794 : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
2796 registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
2800 void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
2802 Q_D(QTextDocumentLayout);
2803 QTextFrame *frame = d->document->rootFrame();
2804 QTextFrameData *fd = data(frame);
2809 if (context.clip.isValid()) {
2810 d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
2812 d->ensureLayoutFinished();
2815 QFixed width = fd->size.width;
2816 if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
2817 // we're in NoWrap mode, meaning the frame should expand to the viewport
2818 // so that backgrounds are drawn correctly
2819 fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
2822 // Make sure we conform to the root frames bounds when drawing.
2823 d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
2824 d->drawFrame(QPointF(), painter, context, frame);
2825 fd->size.width = width;
2828 void QTextDocumentLayout::setViewport(const QRectF &viewport)
2830 Q_D(QTextDocumentLayout);
2831 d->viewportRect = viewport;
2834 static void markFrames(QTextFrame *current, int from, int oldLength, int length)
2836 int end = qMax(oldLength, length) + from;
2838 if (current->firstPosition() >= end || current->lastPosition() < from)
2841 QTextFrameData *fd = data(current);
2842 for (int i = 0; i < fd->floats.size(); ++i) {
2843 QTextFrame *f = fd->floats[i];
2845 // float got removed in editing operation
2846 fd->floats.removeAt(i);
2851 fd->layoutDirty = true;
2852 fd->sizeDirty = true;
2854 // qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
2855 QList<QTextFrame *> children = current->childFrames();
2856 for (int i = 0; i < children.size(); ++i)
2857 markFrames(children.at(i), from, oldLength, length);
2860 void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
2862 Q_D(QTextDocumentLayout);
2864 QTextBlock blockIt = document()->findBlock(from);
2865 QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
2866 if (endIt.isValid())
2867 endIt = endIt.next();
2868 for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
2869 blockIt.clearLayout();
2871 if (d->docPrivate->pageSize.isNull())
2876 d->lazyLayoutStepSize = 1000;
2877 d->sizeChangedTimer.stop();
2878 d->insideDocumentChange = true;
2880 const int documentLength = d->docPrivate->length();
2881 const bool fullLayout = (oldLength == 0 && length == documentLength);
2882 const bool smallChange = documentLength > 0
2883 && (qMax(length, oldLength) * 100 / documentLength) < 5;
2885 // don't show incremental layout progress (avoid scroll bar flicker)
2886 // if we see only a small change in the document and we're either starting
2887 // a layout run or we're already in progress for that and we haven't seen
2888 // any bigger change previously (showLayoutProgress already false)
2890 && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
2891 d->showLayoutProgress = false;
2893 d->showLayoutProgress = true;
2896 d->contentHasAlignment = false;
2897 d->currentLazyLayoutPosition = 0;
2898 d->checkPoints.clear();
2901 d->ensureLayoutedByPosition(from);
2902 updateRect = doLayout(from, oldLength, length);
2905 if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
2906 d->layoutTimer.start(10, this);
2908 d->insideDocumentChange = false;
2910 if (d->showLayoutProgress) {
2911 const QSizeF newSize = dynamicDocumentSize();
2912 if (newSize != d->lastReportedSize) {
2913 d->lastReportedSize = newSize;
2914 emit documentSizeChanged(newSize);
2918 if (!updateRect.isValid()) {
2919 // don't use the frame size, it might have shrunken
2920 updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2923 emit update(updateRect);
2926 QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
2928 Q_D(QTextDocumentLayout);
2930 // qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
2932 // mark all frames between f_start and f_end as dirty
2933 markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
2937 QTextFrame *root = d->docPrivate->rootFrame();
2938 if(data(root)->sizeDirty)
2939 updateRect = d->layoutFrame(root, from, from + length);
2940 data(root)->layoutDirty = false;
2942 if (d->currentLazyLayoutPosition == -1)
2944 else if (d->showLayoutProgress)
2945 d->sizeChangedTimer.start(0, this);
2950 int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
2952 Q_D(const QTextDocumentLayout);
2953 d->ensureLayouted(QFixed::fromReal(point.y()));
2954 QTextFrame *f = d->docPrivate->rootFrame();
2958 pointf.x = QFixed::fromReal(point.x());
2959 pointf.y = QFixed::fromReal(point.y());
2960 QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
2961 if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
2964 // ensure we stay within document bounds
2965 int lastPos = f->lastPosition();
2966 if (l && !l->preeditAreaText().isEmpty())
2967 lastPos += l->preeditAreaText().length();
2968 if (position > lastPos)
2970 else if (position < 0)
2976 void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2978 Q_D(QTextDocumentLayout);
2979 QTextCharFormat f = format.toCharFormat();
2980 Q_ASSERT(f.isValid());
2981 QTextObjectHandler handler = d->handlers.value(f.objectType());
2982 if (!handler.component)
2985 QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
2987 QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
2988 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2990 pos = frame->frameFormat().position();
2991 QTextFrameData *fd = data(frame);
2992 fd->sizeDirty = false;
2993 fd->size = QFixedSize::fromSizeF(intrinsic);
2994 fd->minimumWidth = fd->maximumWidth = fd->size.width;
2997 QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
2998 item.setWidth(inlineSize.width());
3000 QFontMetrics m(f.font());
3001 switch (f.verticalAlignment())
3003 case QTextCharFormat::AlignMiddle:
3004 item.setDescent(inlineSize.height() / 2);
3005 item.setAscent(inlineSize.height() / 2 - 1);
3007 case QTextCharFormat::AlignBaseline:
3008 item.setDescent(m.descent());
3009 item.setAscent(inlineSize.height() - m.descent() - 1);
3013 item.setAscent(inlineSize.height() - 1);
3017 void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
3019 Q_D(QTextDocumentLayout);
3020 Q_UNUSED(posInDocument);
3021 if (item.width() != 0)
3025 QTextCharFormat f = format.toCharFormat();
3026 Q_ASSERT(f.isValid());
3027 QTextObjectHandler handler = d->handlers.value(f.objectType());
3028 if (!handler.component)
3031 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3035 QTextBlock b = d->document->findBlock(frame->firstPosition());
3037 if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
3038 line = b.layout()->lineAt(b.layout()->lineCount()-1);
3039 // qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
3040 // frame->firstPosition() << frame->lastPosition();
3041 d->positionFloat(frame, line.isValid() ? &line : 0);
3044 void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
3045 int posInDocument, const QTextFormat &format)
3047 Q_D(QTextDocumentLayout);
3048 QTextCharFormat f = format.toCharFormat();
3049 Q_ASSERT(f.isValid());
3050 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3051 if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
3052 return; // don't draw floating frames from inline objects here but in drawFlow instead
3054 // qDebug() << "drawObject at" << r;
3055 QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
3058 int QTextDocumentLayout::dynamicPageCount() const
3060 Q_D(const QTextDocumentLayout);
3061 const QSizeF pgSize = d->document->pageSize();
3062 if (pgSize.height() < 0)
3064 return qCeil(dynamicDocumentSize().height() / pgSize.height());
3067 QSizeF QTextDocumentLayout::dynamicDocumentSize() const
3069 Q_D(const QTextDocumentLayout);
3070 return data(d->docPrivate->rootFrame())->size.toSizeF();
3073 int QTextDocumentLayout::pageCount() const
3075 Q_D(const QTextDocumentLayout);
3076 d->ensureLayoutFinished();
3077 return dynamicPageCount();
3080 QSizeF QTextDocumentLayout::documentSize() const
3082 Q_D(const QTextDocumentLayout);
3083 d->ensureLayoutFinished();
3084 return dynamicDocumentSize();
3087 void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
3089 Q_Q(const QTextDocumentLayout);
3090 if (currentLazyLayoutPosition == -1)
3092 const QSizeF oldSize = q->dynamicDocumentSize();
3095 if (checkPoints.isEmpty())
3098 while (currentLazyLayoutPosition != -1
3099 && checkPoints.last().y < y)
3103 void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
3105 if (currentLazyLayoutPosition == -1)
3107 if (position < currentLazyLayoutPosition)
3109 while (currentLazyLayoutPosition != -1
3110 && currentLazyLayoutPosition < position) {
3111 const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
3115 void QTextDocumentLayoutPrivate::layoutStep() const
3117 ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
3118 lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
3121 void QTextDocumentLayout::setCursorWidth(int width)
3123 Q_D(QTextDocumentLayout);
3124 d->cursorWidth = width;
3127 int QTextDocumentLayout::cursorWidth() const
3129 Q_D(const QTextDocumentLayout);
3130 return d->cursorWidth;
3133 void QTextDocumentLayout::setFixedColumnWidth(int width)
3135 Q_D(QTextDocumentLayout);
3136 d->fixedColumnWidth = width;
3139 QRectF QTextDocumentLayout::tableCellBoundingRect(QTextTable *table, const QTextTableCell &cell) const
3141 if (!cell.isValid())
3144 QTextTableData *td = static_cast<QTextTableData *>(data(table));
3146 QRectF tableRect = tableBoundingRect(table);
3147 QRectF cellRect = td->cellRect(cell);
3149 return cellRect.translated(tableRect.topLeft());
3152 QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
3154 Q_D(const QTextDocumentLayout);
3155 if (d->docPrivate->pageSize.isNull())
3157 d->ensureLayoutFinished();
3160 const int framePos = table->firstPosition();
3161 QTextFrame *f = table;
3163 QTextFrameData *fd = data(f);
3164 pos += fd->position.toPointF();
3167 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3168 QTextTableCell cell = table->cellAt(framePos);
3170 pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3174 f = f->parentFrame();
3176 return QRectF(pos, data(table)->size.toSizeF());
3179 QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
3181 Q_D(const QTextDocumentLayout);
3182 if (d->docPrivate->pageSize.isNull())
3184 d->ensureLayoutFinished();
3185 return d->frameBoundingRectInternal(frame);
3188 QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
3191 const int framePos = frame->firstPosition();
3192 QTextFrame *f = frame;
3194 QTextFrameData *fd = data(f);
3195 pos += fd->position.toPointF();
3197 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3198 QTextTableCell cell = table->cellAt(framePos);
3200 pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3203 f = f->parentFrame();
3205 return QRectF(pos, data(frame)->size.toSizeF());
3208 QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
3210 Q_D(const QTextDocumentLayout);
3211 if (d->docPrivate->pageSize.isNull() || !block.isValid())
3213 d->ensureLayoutedByPosition(block.position() + block.length());
3214 QTextFrame *frame = d->document->frameAt(block.position());
3216 const int blockPos = block.position();
3219 QTextFrameData *fd = data(frame);
3220 offset += fd->position.toPointF();
3222 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
3223 QTextTableCell cell = table->cellAt(blockPos);
3225 offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3228 frame = frame->parentFrame();
3231 const QTextLayout *layout = block.layout();
3232 QRectF rect = layout->boundingRect();
3233 rect.moveTopLeft(layout->position() + offset);
3237 int QTextDocumentLayout::layoutStatus() const
3239 Q_D(const QTextDocumentLayout);
3240 int pos = d->currentLazyLayoutPosition;
3243 return pos * 100 / d->document->docHandle()->length();
3246 void QTextDocumentLayout::timerEvent(QTimerEvent *e)
3248 Q_D(QTextDocumentLayout);
3249 if (e->timerId() == d->layoutTimer.timerId()) {
3250 if (d->currentLazyLayoutPosition != -1)
3252 } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
3253 d->lastReportedSize = dynamicDocumentSize();
3254 emit documentSizeChanged(d->lastReportedSize);
3255 d->sizeChangedTimer.stop();
3257 if (d->currentLazyLayoutPosition == -1) {
3258 const int newCount = dynamicPageCount();
3259 if (newCount != d->lastPageCount) {
3260 d->lastPageCount = newCount;
3261 emit pageCountChanged(newCount);
3265 QAbstractTextDocumentLayout::timerEvent(e);
3269 void QTextDocumentLayout::layoutFinished()
3271 Q_D(QTextDocumentLayout);
3272 d->layoutTimer.stop();
3273 if (!d->insideDocumentChange)
3274 d->sizeChangedTimer.start(0, this);
3276 d->showLayoutProgress = true;
3279 void QTextDocumentLayout::ensureLayouted(qreal y)
3281 d_func()->ensureLayouted(QFixed::fromReal(y));
3284 qreal QTextDocumentLayout::idealWidth() const
3286 Q_D(const QTextDocumentLayout);
3287 d->ensureLayoutFinished();
3288 return d->idealWidth;
3291 bool QTextDocumentLayout::contentHasAlignment() const
3293 Q_D(const QTextDocumentLayout);
3294 return d->contentHasAlignment;
3297 qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
3301 return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi());
3304 QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
3308 return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi());
3313 #include "moc_qtextdocumentlayout_p.cpp"