summaryrefslogtreecommitdiffstats
path: root/src/gui/text/qtextdocumentlayout.cpp
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/gui/text/qtextdocumentlayout.cpp
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/gui/text/qtextdocumentlayout.cpp')
-rw-r--r--src/gui/text/qtextdocumentlayout.cpp3263
1 files changed, 3263 insertions, 0 deletions
diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp
new file mode 100644
index 0000000000..ce157be254
--- /dev/null
+++ b/src/gui/text/qtextdocumentlayout.cpp
@@ -0,0 +1,3263 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextdocumentlayout_p.h"
+#include "qtextdocument_p.h"
+#include "qtextimagehandler_p.h"
+#include "qtexttable.h"
+#include "qtextlist.h"
+#include "qtextengine_p.h"
+#include "private/qcssutil_p.h"
+
+#include "qabstracttextdocumentlayout_p.h"
+#include "qcssparser_p.h"
+
+#include <qpainter.h>
+#include <qmath.h>
+#include <qrect.h>
+#include <qpalette.h>
+#include <qdebug.h>
+#include <qvarlengtharray.h>
+#include <limits.h>
+#include <qstyle.h>
+#include <qbasictimer.h>
+#include "private/qfunctions_p.h"
+
+// #define LAYOUT_DEBUG
+
+#ifdef LAYOUT_DEBUG
+#define LDEBUG qDebug()
+#define INC_INDENT debug_indent += " "
+#define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2)
+#else
+#define LDEBUG if(0) qDebug()
+#define INC_INDENT do {} while(0)
+#define DEC_INDENT do {} while(0)
+#endif
+
+QT_BEGIN_NAMESPACE
+
+// ################ should probably add frameFormatChange notification!
+
+struct QTextLayoutStruct;
+
+class QTextFrameData : public QTextFrameLayoutData
+{
+public:
+ QTextFrameData();
+
+ // relative to parent frame
+ QFixedPoint position;
+ QFixedSize size;
+
+ // contents starts at (margin+border/margin+border)
+ QFixed topMargin;
+ QFixed bottomMargin;
+ QFixed leftMargin;
+ QFixed rightMargin;
+ QFixed border;
+ QFixed padding;
+ // contents width includes padding (as we need to treat this on a per cell basis for tables)
+ QFixed contentsWidth;
+ QFixed contentsHeight;
+ QFixed oldContentsWidth;
+
+ // accumulated margins
+ QFixed effectiveTopMargin;
+ QFixed effectiveBottomMargin;
+
+ QFixed minimumWidth;
+ QFixed maximumWidth;
+
+ QTextLayoutStruct *currentLayoutStruct;
+
+ bool sizeDirty;
+ bool layoutDirty;
+
+ QList<QPointer<QTextFrame> > floats;
+};
+
+QTextFrameData::QTextFrameData()
+ : maximumWidth(QFIXED_MAX),
+ currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
+{
+}
+
+struct QTextLayoutStruct {
+ QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
+ {}
+ QTextFrame *frame;
+ QFixed x_left;
+ QFixed x_right;
+ QFixed frameY; // absolute y position of the current frame
+ QFixed y; // always relative to the current frame
+ QFixed contentsWidth;
+ QFixed minimumWidth;
+ QFixed maximumWidth;
+ bool fullLayout;
+ QList<QTextFrame *> pendingFloats;
+ QFixed pageHeight;
+ QFixed pageBottom;
+ QFixed pageTopMargin;
+ QFixed pageBottomMargin;
+ QRectF updateRect;
+ QRectF updateRectForFloats;
+
+ inline void addUpdateRectForFloat(const QRectF &rect) {
+ if (updateRectForFloats.isValid())
+ updateRectForFloats |= rect;
+ else
+ updateRectForFloats = rect;
+ }
+
+ inline QFixed absoluteY() const
+ { return frameY + y; }
+
+ inline int currentPage() const
+ { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
+
+ inline void newPage()
+ { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; }
+};
+
+class QTextTableData : public QTextFrameData
+{
+public:
+ QFixed cellSpacing, cellPadding;
+ qreal deviceScale;
+ QVector<QFixed> minWidths;
+ QVector<QFixed> maxWidths;
+ QVector<QFixed> widths;
+ QVector<QFixed> heights;
+ QVector<QFixed> columnPositions;
+ QVector<QFixed> rowPositions;
+
+ QVector<QFixed> cellVerticalOffsets;
+
+ QFixed headerHeight;
+
+ // maps from cell index (row + col * rowCount) to child frames belonging to
+ // the specific cell
+ QMultiHash<int, QTextFrame *> childFrameMap;
+
+ inline QFixed cellWidth(int column, int colspan) const
+ { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
+ - columnPositions.at(column); }
+
+ inline void calcRowPosition(int row)
+ {
+ if (row > 0)
+ rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
+ }
+
+ QRectF cellRect(const QTextTableCell &cell) const;
+
+ inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
+ {
+ QVariant v = format.property(property);
+ if (v.isNull()) {
+ return cellPadding;
+ } else {
+ Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float);
+ return QFixed::fromReal(v.toReal() * deviceScale);
+ }
+ }
+
+ inline QFixed topPadding(const QTextFormat &format) const
+ {
+ return paddingProperty(format, QTextFormat::TableCellTopPadding);
+ }
+
+ inline QFixed bottomPadding(const QTextFormat &format) const
+ {
+ return paddingProperty(format, QTextFormat::TableCellBottomPadding);
+ }
+
+ inline QFixed leftPadding(const QTextFormat &format) const
+ {
+ return paddingProperty(format, QTextFormat::TableCellLeftPadding);
+ }
+
+ inline QFixed rightPadding(const QTextFormat &format) const
+ {
+ return paddingProperty(format, QTextFormat::TableCellRightPadding);
+ }
+
+ inline QFixedPoint cellPosition(const QTextTableCell &cell) const
+ {
+ const QTextFormat fmt = cell.format();
+ return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
+ }
+
+ void updateTableSize();
+
+private:
+ inline QFixedPoint cellPosition(int row, int col) const
+ { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
+};
+
+static QTextFrameData *createData(QTextFrame *f)
+{
+ QTextFrameData *data;
+ if (qobject_cast<QTextTable *>(f))
+ data = new QTextTableData;
+ else
+ data = new QTextFrameData;
+ f->setLayoutData(data);
+ return data;
+}
+
+static inline QTextFrameData *data(QTextFrame *f)
+{
+ QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
+ if (!data)
+ data = createData(f);
+ return data;
+}
+
+static bool isFrameFromInlineObject(QTextFrame *f)
+{
+ return f->firstPosition() > f->lastPosition();
+}
+
+void QTextTableData::updateTableSize()
+{
+ const QFixed effectiveTopMargin = this->topMargin + border + padding;
+ const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
+ const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
+ const QFixed effectiveRightMargin = this->rightMargin + border + padding;
+ size.height = contentsHeight == -1
+ ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin
+ : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
+ size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
+}
+
+QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
+{
+ const int row = cell.row();
+ const int rowSpan = cell.rowSpan();
+ const int column = cell.column();
+ const int colSpan = cell.columnSpan();
+
+ return QRectF(columnPositions.at(column).toReal(),
+ rowPositions.at(row).toReal(),
+ (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
+ (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
+}
+
+static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
+{
+ return !nextIt.atEnd()
+ && qobject_cast<QTextTable *>(nextIt.currentFrame())
+ && block.isValid()
+ && block.length() == 1
+ && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
+ && !format.hasProperty(QTextFormat::BackgroundBrush)
+ && nextIt.currentFrame()->firstPosition() == block.position() + 1
+ ;
+}
+
+static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it)
+{
+ QTextFrame::Iterator next = it; ++next;
+ if (it.currentFrame())
+ return false;
+ QTextBlock block = it.currentBlock();
+ return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
+}
+
+static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
+{
+ return qobject_cast<const QTextTable *>(previousFrame)
+ && block.isValid()
+ && block.length() == 1
+ && previousFrame->lastPosition() == block.position() - 1
+ ;
+}
+
+static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
+{
+ return qobject_cast<const QTextTable *>(previousFrame)
+ && block.isValid()
+ && block.length() > 1
+ && block.text().at(0) == QChar::LineSeparator
+ && previousFrame->lastPosition() == block.position() - 1
+ ;
+}
+
+/*
+
+Optimization strategies:
+
+HTML layout:
+
+* Distinguish between normal and special flow. For normal flow the condition:
+ y1 > y2 holds for all blocks with b1.key() > b2.key().
+* Special flow is: floats, table cells
+
+* Normal flow within table cells. Tables (not cells) are part of the normal flow.
+
+
+* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
+* If height doesn't change, no need to do anything
+
+Table cells:
+
+* If minWidth of cell changes, recalculate table width, relayout if needed.
+* What about maxWidth when doing auto layout?
+
+Floats:
+* need fixed or proportional width, otherwise don't float!
+* On width/height change relayout surrounding paragraphs.
+
+Document width change:
+* full relayout needed
+
+
+Float handling:
+
+* Floats are specified by a special format object.
+* currently only floating images are implemented.
+
+*/
+
+/*
+
+ On the table layouting:
+
+ +---[ table border ]-------------------------
+ | [ cell spacing ]
+ | +------[ cell border ]-----+ +--------
+ | | | |
+ | |
+ | |
+ | |
+ |
+
+ rowPositions[i] and columnPositions[i] point at the cell content
+ position. So for example the left border is drawn at
+ x = columnPositions[i] - fd->border and similar for y.
+
+*/
+
+struct QCheckPoint
+{
+ QFixed y;
+ QFixed frameY; // absolute y position of the current frame
+ int positionInFrame;
+ QFixed minimumWidth;
+ QFixed maximumWidth;
+ QFixed contentsWidth;
+};
+Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y)
+{
+ return checkPoint.y < y;
+}
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos)
+{
+ return checkPoint.positionInFrame < pos;
+}
+
+static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF())
+{
+ p->save();
+ if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
+ if (!gradientRect.isNull()) {
+ QTransform m;
+ m.translate(gradientRect.left(), gradientRect.top());
+ m.scale(gradientRect.width(), gradientRect.height());
+ brush.setTransform(m);
+ const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
+ }
+ } else {
+ p->setBrushOrigin(origin);
+ }
+ p->fillRect(rect, brush);
+ p->restore();
+}
+
+class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
+{
+ Q_DECLARE_PUBLIC(QTextDocumentLayout)
+public:
+ QTextDocumentLayoutPrivate();
+
+ QTextOption::WrapMode wordWrapMode;
+#ifdef LAYOUT_DEBUG
+ mutable QString debug_indent;
+#endif
+
+ int fixedColumnWidth;
+ int cursorWidth;
+
+ QSizeF lastReportedSize;
+ QRectF viewportRect;
+ QRectF clipRect;
+
+ mutable int currentLazyLayoutPosition;
+ mutable int lazyLayoutStepSize;
+ QBasicTimer layoutTimer;
+ mutable QBasicTimer sizeChangedTimer;
+ uint showLayoutProgress : 1;
+ uint insideDocumentChange : 1;
+
+ int lastPageCount;
+ qreal idealWidth;
+ bool contentHasAlignment;
+
+ QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
+
+ void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextFrame *f) const;
+ void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
+ void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextBlock bl, bool inRootFrame) const;
+ void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextBlock bl, const QTextCharFormat *selectionFormat) const;
+ void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
+ QTextTable *table, QTextTableData *td, int r, int c,
+ QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
+ void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
+ const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
+ void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
+
+ enum HitPoint {
+ PointBefore,
+ PointAfter,
+ PointInside,
+ PointExact
+ };
+ HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
+ HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
+ int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
+ HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
+ HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
+
+ QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
+ int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
+ bool withPageBreaks);
+ void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
+ QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
+
+ void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0);
+
+ // calls the next one
+ QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
+ QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
+
+ void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
+ QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
+ void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
+ void pageBreakInsideTable(QTextTable *table, QTextLayoutStruct *layoutStruct);
+
+
+ void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
+ QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const;
+
+ QVector<QCheckPoint> checkPoints;
+
+ QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
+ QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
+
+ void ensureLayouted(QFixed y) const;
+ void ensureLayoutedByPosition(int position) const;
+ inline void ensureLayoutFinished() const
+ { ensureLayoutedByPosition(INT_MAX); }
+ void layoutStep() const;
+
+ QRectF frameBoundingRectInternal(QTextFrame *frame) const;
+
+ qreal scaleToDevice(qreal value) const;
+ QFixed scaleToDevice(QFixed value) const;
+};
+
+QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
+ : fixedColumnWidth(-1),
+ cursorWidth(1),
+ currentLazyLayoutPosition(-1),
+ lazyLayoutStepSize(1000),
+ lastPageCount(-1)
+{
+ showLayoutProgress = true;
+ insideDocumentChange = false;
+ idealWidth = 0;
+ contentHasAlignment = false;
+}
+
+QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
+{
+ QTextFrame *rootFrame = document->rootFrame();
+
+ if (checkPoints.isEmpty()
+ || y < 0 || y > data(rootFrame)->size.height)
+ return rootFrame->begin();
+
+ QVector<QCheckPoint>::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y);
+ if (checkPoint == checkPoints.end())
+ return rootFrame->begin();
+
+ if (checkPoint != checkPoints.begin())
+ --checkPoint;
+
+ const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
+ return frameIteratorForTextPosition(position);
+}
+
+QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
+{
+ QTextFrame *rootFrame = docPrivate->rootFrame();
+
+ const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
+ const int begin = map.findNode(rootFrame->firstPosition());
+ const int end = map.findNode(rootFrame->lastPosition()+1);
+
+ const int block = map.findNode(position);
+ const int blockPos = map.position(block);
+
+ QTextFrame::iterator it(rootFrame, block, begin, end);
+
+ QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
+ if (containingFrame != rootFrame) {
+ while (containingFrame->parentFrame() != rootFrame) {
+ containingFrame = containingFrame->parentFrame();
+ Q_ASSERT(containingFrame);
+ }
+
+ it.cf = containingFrame;
+ it.cb = 0;
+ }
+
+ return it;
+}
+
+QTextDocumentLayoutPrivate::HitPoint
+QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
+{
+ QTextFrameData *fd = data(frame);
+ // #########
+ if (fd->layoutDirty)
+ return PointAfter;
+ Q_ASSERT(!fd->layoutDirty);
+ Q_ASSERT(!fd->sizeDirty);
+ const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
+
+ QTextFrame *rootFrame = docPrivate->rootFrame();
+
+// LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point
+// << "position" << fd->position << "size" << fd->size;
+ if (frame != rootFrame) {
+ if (relativePoint.y < 0 || relativePoint.x < 0) {
+ *position = frame->firstPosition() - 1;
+// LDEBUG << "before pos=" << *position;
+ return PointBefore;
+ } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
+ *position = frame->lastPosition() + 1;
+// LDEBUG << "after pos=" << *position;
+ return PointAfter;
+ }
+ }
+
+ if (isFrameFromInlineObject(frame)) {
+ *position = frame->firstPosition() - 1;
+ return PointExact;
+ }
+
+ if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
+ const int rows = table->rows();
+ const int columns = table->columns();
+ QTextTableData *td = static_cast<QTextTableData *>(data(table));
+
+ if (!td->childFrameMap.isEmpty()) {
+ for (int r = 0; r < rows; ++r) {
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(r, c);
+ if (cell.row() != r || cell.column() != c)
+ continue;
+
+ QRectF cellRect = td->cellRect(cell);
+ const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
+ const QFixedPoint pointInCell = relativePoint - cellPos;
+
+ const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
+ for (int i = 0; i < childFrames.size(); ++i) {
+ QTextFrame *child = childFrames.at(i);
+ if (isFrameFromInlineObject(child)
+ && child->frameFormat().position() != QTextFrameFormat::InFlow
+ && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
+ {
+ return PointExact;
+ }
+ }
+ }
+ }
+ }
+
+ return hitTest(table, relativePoint, position, l, accuracy);
+ }
+
+ const QList<QTextFrame *> childFrames = frame->childFrames();
+ for (int i = 0; i < childFrames.size(); ++i) {
+ QTextFrame *child = childFrames.at(i);
+ if (isFrameFromInlineObject(child)
+ && child->frameFormat().position() != QTextFrameFormat::InFlow
+ && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
+ {
+ return PointExact;
+ }
+ }
+
+ QTextFrame::Iterator it = frame->begin();
+
+ if (frame == rootFrame) {
+ it = frameIteratorForYPosition(relativePoint.y);
+
+ Q_ASSERT(it.parentFrame() == frame);
+ }
+
+ if (it.currentFrame())
+ *position = it.currentFrame()->firstPosition();
+ else
+ *position = it.currentBlock().position();
+
+ return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
+}
+
+QTextDocumentLayoutPrivate::HitPoint
+QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
+ int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
+{
+ INC_INDENT;
+
+ for (; !it.atEnd(); ++it) {
+ QTextFrame *c = it.currentFrame();
+ HitPoint hp;
+ int pos = -1;
+ if (c) {
+ hp = hitTest(c, p, &pos, l, accuracy);
+ } else {
+ hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
+ }
+ if (hp >= PointInside) {
+ if (isEmptyBlockBeforeTable(it))
+ continue;
+ hit = hp;
+ *position = pos;
+ break;
+ }
+ if (hp == PointBefore && pos < *position) {
+ *position = pos;
+ hit = hp;
+ } else if (hp == PointAfter && pos > *position) {
+ *position = pos;
+ hit = hp;
+ }
+ }
+
+ DEC_INDENT;
+// LDEBUG << "inside=" << hit << " pos=" << *position;
+ return hit;
+}
+
+QTextDocumentLayoutPrivate::HitPoint
+QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
+ int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
+{
+ QTextTableData *td = static_cast<QTextTableData *>(data(table));
+
+ QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
+ if (rowIt == td->rowPositions.constEnd()) {
+ rowIt = td->rowPositions.constEnd() - 1;
+ } else if (rowIt != td->rowPositions.constBegin()) {
+ --rowIt;
+ }
+
+ QVector<QFixed>::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
+ if (colIt == td->columnPositions.constEnd()) {
+ colIt = td->columnPositions.constEnd() - 1;
+ } else if (colIt != td->columnPositions.constBegin()) {
+ --colIt;
+ }
+
+ QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
+ colIt - td->columnPositions.constBegin());
+ if (!cell.isValid())
+ return PointBefore;
+
+ *position = cell.firstPosition();
+
+ HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
+
+ if (hp == PointExact)
+ return hp;
+ if (hp == PointAfter)
+ *position = cell.lastPosition();
+ return PointInside;
+}
+
+QTextDocumentLayoutPrivate::HitPoint
+QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l,
+ Qt::HitTestAccuracy accuracy) const
+{
+ QTextLayout *tl = bl.layout();
+ QRectF textrect = tl->boundingRect();
+ textrect.translate(tl->position());
+// LDEBUG << " checking block" << bl.position() << "point=" << point
+// << " tlrect" << textrect;
+ *position = bl.position();
+ if (point.y.toReal() < textrect.top()) {
+// LDEBUG << " before pos=" << *position;
+ return PointBefore;
+ } else if (point.y.toReal() > textrect.bottom()) {
+ *position += bl.length();
+// LDEBUG << " after pos=" << *position;
+ return PointAfter;
+ }
+
+ QPointF pos = point.toPointF() - tl->position();
+
+ // ### rtl?
+
+ HitPoint hit = PointInside;
+ *l = tl;
+ int off = 0;
+ for (int i = 0; i < tl->lineCount(); ++i) {
+ QTextLine line = tl->lineAt(i);
+ const QRectF lr = line.naturalTextRect();
+ if (lr.top() > pos.y()) {
+ off = qMin(off, line.textStart());
+ } else if (lr.bottom() <= pos.y()) {
+ off = qMax(off, line.textStart() + line.textLength());
+ } else {
+ if (lr.left() <= pos.x() && lr.right() >= pos.x())
+ hit = PointExact;
+ // when trying to hit an anchor we want it to hit not only in the left
+ // half
+ if (accuracy == Qt::ExactHit)
+ off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
+ else
+ off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
+ break;
+ }
+ }
+ *position += off;
+
+// LDEBUG << " inside=" << hit << " pos=" << *position;
+ return hit;
+}
+
+// ### could be moved to QTextBlock
+QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
+{
+ qreal indent = blockFormat.indent();
+
+ QTextObject *object = document->objectForFormat(blockFormat);
+ if (object)
+ indent += object->format().toListFormat().indent();
+
+ if (qIsNull(indent))
+ return 0;
+
+ qreal scale = 1;
+ if (paintDevice) {
+ scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
+ }
+
+ return QFixed::fromReal(indent * scale * document->indentWidth());
+}
+
+void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
+ qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
+{
+ const qreal pageHeight = document->pageSize().height();
+ const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
+ const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
+
+#ifndef QT_NO_CSSPARSER
+ QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
+#endif //QT_NO_CSSPARSER
+
+ bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ for (int i = topPage; i <= bottomPage; ++i) {
+ QRectF clipped = rect.toRect();
+
+ if (topPage != bottomPage) {
+ clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
+ clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
+
+ if (clipped.bottom() <= clipped.top())
+ continue;
+ }
+#ifndef QT_NO_CSSPARSER
+ qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
+ qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
+ qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
+ qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
+#else
+ painter->save();
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(brush);
+ painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
+ painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
+ painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
+ painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
+ painter->restore();
+#endif //QT_NO_CSSPARSER
+ }
+ if (turn_off_antialiasing)
+ painter->setRenderHint(QPainter::Antialiasing, false);
+}
+
+void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
+{
+
+ const QBrush bg = frame->frameFormat().background();
+ if (bg != Qt::NoBrush) {
+ QRectF bgRect = rect;
+ bgRect.adjust((fd->leftMargin + fd->border).toReal(),
+ (fd->topMargin + fd->border).toReal(),
+ - (fd->rightMargin + fd->border).toReal(),
+ - (fd->bottomMargin + fd->border).toReal());
+
+ QRectF gradientRect; // invalid makes it default to bgRect
+ QPointF origin = bgRect.topLeft();
+ if (!frame->parentFrame()) {
+ bgRect = clip;
+ gradientRect.setWidth(painter->device()->width());
+ gradientRect.setHeight(painter->device()->height());
+ }
+ fillBackground(painter, bgRect, bg, origin, gradientRect);
+ }
+ if (fd->border != 0) {
+ painter->save();
+ painter->setBrush(Qt::lightGray);
+ painter->setPen(Qt::NoPen);
+
+ const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
+ const qreal border = fd->border.toReal();
+ const qreal topMargin = fd->topMargin.toReal();
+ const qreal leftMargin = fd->leftMargin.toReal();
+ const qreal bottomMargin = fd->bottomMargin.toReal();
+ const qreal rightMargin = fd->rightMargin.toReal();
+ const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
+ const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
+
+ drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
+ fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
+ border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
+
+ painter->restore();
+ }
+}
+
+static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
+ const QTextTableCell &cell,
+ int r, int c,
+ const int *selectedTableCells)
+{
+ for (int i = 0; i < cell_context.selections.size(); ++i) {
+ int row_start = selectedTableCells[i * 4];
+ int col_start = selectedTableCells[i * 4 + 1];
+ int num_rows = selectedTableCells[i * 4 + 2];
+ int num_cols = selectedTableCells[i * 4 + 3];
+
+ if (row_start != -1) {
+ if (r >= row_start && r < row_start + num_rows
+ && c >= col_start && c < col_start + num_cols)
+ {
+ int firstPosition = cell.firstPosition();
+ int lastPosition = cell.lastPosition();
+
+ // make sure empty cells are still selected
+ if (firstPosition == lastPosition)
+ ++lastPosition;
+
+ cell_context.selections[i].cursor.setPosition(firstPosition);
+ cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
+ } else {
+ cell_context.selections[i].cursor.clearSelection();
+ }
+ }
+
+ // FullWidthSelection is not useful for tables
+ cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
+ }
+}
+
+void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
+ const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextFrame *frame) const
+{
+ QTextFrameData *fd = data(frame);
+ // #######
+ if (fd->layoutDirty)
+ return;
+ Q_ASSERT(!fd->sizeDirty);
+ Q_ASSERT(!fd->layoutDirty);
+
+ const QPointF off = offset + fd->position.toPointF();
+ if (context.clip.isValid()
+ && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
+ || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
+ return;
+
+// LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
+// INC_INDENT;
+
+ // if the cursor is /on/ a table border we may need to repaint it
+ // afterwards, as we usually draw the decoration first
+ QTextBlock cursorBlockNeedingRepaint;
+ QPointF offsetOfRepaintedCursorBlock = off;
+
+ QTextTable *table = qobject_cast<QTextTable *>(frame);
+ const QRectF frameRect(off, fd->size.toSizeF());
+
+ if (table) {
+ const int rows = table->rows();
+ const int columns = table->columns();
+ QTextTableData *td = static_cast<QTextTableData *>(data(table));
+
+ QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
+ for (int i = 0; i < context.selections.size(); ++i) {
+ const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
+ int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
+
+ if (s.cursor.currentTable() == table)
+ s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
+
+ selectedTableCells[i * 4] = row_start;
+ selectedTableCells[i * 4 + 1] = col_start;
+ selectedTableCells[i * 4 + 2] = num_rows;
+ selectedTableCells[i * 4 + 3] = num_cols;
+ }
+
+ QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
+ if (pageHeight <= 0)
+ pageHeight = QFIXED_MAX;
+
+ const int tableStartPage = (td->position.y / pageHeight).truncate();
+ const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate();
+
+ qreal border = td->border.toReal();
+ drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
+
+ // draw the table headers
+ const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
+ int page = tableStartPage + 1;
+ while (page <= tableEndPage) {
+ const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
+ const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
+ for (int r = 0; r < headerRowCount; ++r) {
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(r, c);
+ QAbstractTextDocumentLayout::PaintContext cell_context = context;
+ adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
+ QRectF cellRect = td->cellRect(cell);
+
+ cellRect.translate(off.x(), headerOffset);
+ // we need to account for the cell border in the clipping test
+ int leftAdjust = qMin(qreal(0), 1 - border);
+ if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
+ continue;
+
+ drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
+ &offsetOfRepaintedCursorBlock);
+ }
+ }
+ ++page;
+ }
+
+ int firstRow = 0;
+ int lastRow = rows;
+
+ if (context.clip.isValid()) {
+ QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
+ if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
+ --rowIt;
+ firstRow = rowIt - td->rowPositions.constBegin();
+ }
+
+ rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
+ if (rowIt != td->rowPositions.constEnd()) {
+ ++rowIt;
+ lastRow = rowIt - td->rowPositions.constBegin();
+ }
+ }
+
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(firstRow, c);
+ firstRow = qMin(firstRow, cell.row());
+ }
+
+ for (int r = firstRow; r < lastRow; ++r) {
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(r, c);
+ QAbstractTextDocumentLayout::PaintContext cell_context = context;
+ adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
+ QRectF cellRect = td->cellRect(cell);
+
+ cellRect.translate(off);
+ // we need to account for the cell border in the clipping test
+ int leftAdjust = qMin(qreal(0), 1 - border);
+ if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
+ continue;
+
+ drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
+ &offsetOfRepaintedCursorBlock);
+ }
+ }
+
+ } else {
+ drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
+
+ QTextFrame::Iterator it = frame->begin();
+
+ if (frame == docPrivate->rootFrame())
+ it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
+
+ QList<QTextFrame *> floats;
+ for (int i = 0; i < fd->floats.count(); ++i)
+ floats.append(fd->floats.at(i));
+
+ drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
+ }
+
+ if (cursorBlockNeedingRepaint.isValid()) {
+ const QPen oldPen = painter->pen();
+ painter->setPen(context.palette.color(QPalette::Text));
+ const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
+ cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
+ cursorPos, cursorWidth);
+ painter->setPen(oldPen);
+ }
+
+// DEC_INDENT;
+
+ return;
+}
+
+void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
+ QTextTable *table, QTextTableData *td, int r, int c,
+ QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
+{
+ QTextTableCell cell = table->cellAt(r, c);
+ int rspan = cell.rowSpan();
+ int cspan = cell.columnSpan();
+ if (rspan != 1) {
+ int cr = cell.row();
+ if (cr != r)
+ return;
+ }
+ if (cspan != 1) {
+ int cc = cell.column();
+ if (cc != c)
+ return;
+ }
+
+ QTextFormat fmt = cell.format();
+ const QFixed leftPadding = td->leftPadding(fmt);
+ const QFixed topPadding = td->topPadding(fmt);
+
+ if (td->border != 0) {
+ const QBrush oldBrush = painter->brush();
+ const QPen oldPen = painter->pen();
+
+ const qreal border = td->border.toReal();
+
+ QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
+
+ // invert the border style for cells
+ QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
+ switch (cellBorder) {
+ case QTextFrameFormat::BorderStyle_Inset:
+ cellBorder = QTextFrameFormat::BorderStyle_Outset;
+ break;
+ case QTextFrameFormat::BorderStyle_Outset:
+ cellBorder = QTextFrameFormat::BorderStyle_Inset;
+ break;
+ case QTextFrameFormat::BorderStyle_Groove:
+ cellBorder = QTextFrameFormat::BorderStyle_Ridge;
+ break;
+ case QTextFrameFormat::BorderStyle_Ridge:
+ cellBorder = QTextFrameFormat::BorderStyle_Groove;
+ break;
+ default:
+ break;
+ }
+
+ qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
+ qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
+
+ const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
+ if (r >= headerRowCount)
+ topMargin += td->headerHeight.toReal();
+
+ drawBorder(painter, borderRect, topMargin, bottomMargin,
+ border, table->format().borderBrush(), cellBorder);
+
+ painter->setBrush(oldBrush);
+ painter->setPen(oldPen);
+ }
+
+ const QBrush bg = cell.format().background();
+ const QPointF brushOrigin = painter->brushOrigin();
+ if (bg.style() != Qt::NoBrush) {
+ fillBackground(painter, cellRect, bg, cellRect.topLeft());
+
+ if (bg.style() > Qt::SolidPattern)
+ painter->setBrushOrigin(cellRect.topLeft());
+ }
+
+ const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
+
+ const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
+ cellRect.top() + (topPadding + verticalOffset).toReal());
+
+ QTextBlock repaintBlock;
+ drawFlow(cellPos, painter, cell_context, cell.begin(),
+ td->childFrameMap.values(r + c * table->rows()),
+ &repaintBlock);
+ if (repaintBlock.isValid()) {
+ *cursorBlockNeedingRepaint = repaintBlock;
+ *cursorBlockOffset = cellPos;
+ }
+
+ if (bg.style() > Qt::SolidPattern)
+ painter->setBrushOrigin(brushOrigin);
+}
+
+void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
+{
+ Q_Q(const QTextDocumentLayout);
+ const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
+
+ QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
+ if (inRootFrame && context.clip.isValid()) {
+ lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
+ }
+
+ QTextBlock previousBlock;
+ QTextFrame *previousFrame = 0;
+
+ for (; !it.atEnd(); ++it) {
+ QTextFrame *c = it.currentFrame();
+
+ if (inRootFrame && !checkPoints.isEmpty()) {
+ int currentPosInDoc;
+ if (c)
+ currentPosInDoc = c->firstPosition();
+ else
+ currentPosInDoc = it.currentBlock().position();
+
+ // if we're past what is already laid out then we're better off
+ // not trying to draw things that may not be positioned correctly yet
+ if (currentPosInDoc >= checkPoints.last().positionInFrame)
+ break;
+
+ if (lastVisibleCheckPoint != checkPoints.end()
+ && context.clip.isValid()
+ && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
+ )
+ break;
+ }
+
+ if (c)
+ drawFrame(offset, painter, context, c);
+ else {
+ QAbstractTextDocumentLayout::PaintContext pc = context;
+ if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
+ pc.selections.clear();
+ drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
+ }
+
+ // when entering a table and the previous block is empty
+ // then layoutFlow 'hides' the block that just causes a
+ // new line by positioning it /on/ the table border. as we
+ // draw that block before the table itself the decoration
+ // 'overpaints' the cursor and we need to paint it afterwards
+ // again
+ if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
+ && previousBlock.contains(context.cursorPosition)
+ ) {
+ *cursorBlockNeedingRepaint = previousBlock;
+ }
+
+ previousBlock = it.currentBlock();
+ previousFrame = c;
+ }
+
+ for (int i = 0; i < floats.count(); ++i) {
+ QTextFrame *frame = floats.at(i);
+ if (!isFrameFromInlineObject(frame)
+ || frame->frameFormat().position() == QTextFrameFormat::InFlow)
+ continue;
+
+ const int pos = frame->firstPosition() - 1;
+ QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
+ QTextObjectInterface *handler = q->handlerForObject(format.objectType());
+ if (handler) {
+ QRectF rect = frameBoundingRectInternal(frame);
+ handler->drawObject(painter, rect, document, pos, format);
+ }
+ }
+}
+
+void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
+ const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextBlock bl, bool inRootFrame) const
+{
+ const QTextLayout *tl = bl.layout();
+ QRectF r = tl->boundingRect();
+ r.translate(offset + tl->position());
+ if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom()))
+ return;
+// LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
+
+ QTextBlockFormat blockFormat = bl.blockFormat();
+
+ QBrush bg = blockFormat.background();
+ if (bg != Qt::NoBrush) {
+ QRectF rect = r;
+
+ // extend the background rectangle if we're in the root frame with NoWrap,
+ // as the rect of the text block will then be only the width of the text
+ // instead of the full page width
+ if (inRootFrame && document->pageSize().width() <= 0) {
+ const QTextFrameData *fd = data(document->rootFrame());
+ rect.setRight((fd->size.width - fd->rightMargin).toReal());
+ }
+
+ fillBackground(painter, rect, bg, r.topLeft());
+ }
+
+ QVector<QTextLayout::FormatRange> selections;
+ int blpos = bl.position();
+ int bllen = bl.length();
+ const QTextCharFormat *selFormat = 0;
+ for (int i = 0; i < context.selections.size(); ++i) {
+ const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
+ const int selStart = range.cursor.selectionStart() - blpos;
+ const int selEnd = range.cursor.selectionEnd() - blpos;
+ if (selStart < bllen && selEnd > 0
+ && selEnd > selStart) {
+ QTextLayout::FormatRange o;
+ o.start = selStart;
+ o.length = selEnd - selStart;
+ o.format = range.format;
+ selections.append(o);
+ } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
+ && bl.contains(range.cursor.position())) {
+ // for full width selections we don't require an actual selection, just
+ // a position to specify the line. that's more convenience in usage.
+ QTextLayout::FormatRange o;
+ QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
+ o.start = l.textStart();
+ o.length = l.textLength();
+ if (o.start + o.length == bllen - 1)
+ ++o.length; // include newline
+ o.format = range.format;
+ selections.append(o);
+ }
+ if (selStart < 0 && selEnd >= 1)
+ selFormat = &range.format;
+ }
+
+ QTextObject *object = document->objectForFormat(bl.blockFormat());
+ if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
+ drawListItem(offset, painter, context, bl, selFormat);
+
+ QPen oldPen = painter->pen();
+ painter->setPen(context.palette.color(QPalette::Text));
+
+ tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
+
+ if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
+ || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) {
+ int cpos = context.cursorPosition;
+ if (cpos < -1)
+ cpos = tl->preeditAreaPosition() - (cpos + 2);
+ else
+ cpos -= blpos;
+ tl->drawCursor(painter, offset, cpos, cursorWidth);
+ }
+
+ if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
+ const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
+ painter->setPen(context.palette.color(QPalette::Dark));
+ qreal y = r.bottom();
+ if (bl.length() == 1)
+ y = r.top() + r.height() / 2;
+
+ const qreal middleX = r.left() + r.width() / 2;
+ painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
+ }
+
+ painter->setPen(oldPen);
+}
+
+
+void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
+ const QAbstractTextDocumentLayout::PaintContext &context,
+ QTextBlock bl, const QTextCharFormat *selectionFormat) const
+{
+ Q_Q(const QTextDocumentLayout);
+ const QTextBlockFormat blockFormat = bl.blockFormat();
+ const QTextCharFormat charFormat = QTextCursor(bl).charFormat();
+ QFont font(charFormat.font());
+ if (q->paintDevice())
+ font = QFont(font, q->paintDevice());
+
+ const QFontMetrics fontMetrics(font);
+ QTextObject * const object = document->objectForFormat(blockFormat);
+ const QTextListFormat lf = object->format().toListFormat();
+ int style = lf.style();
+ QString itemText;
+ QSizeF size;
+
+ if (blockFormat.hasProperty(QTextFormat::ListStyle))
+ style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
+
+ QTextLayout *layout = bl.layout();
+ if (layout->lineCount() == 0)
+ return;
+ QTextLine firstLine = layout->lineAt(0);
+ Q_ASSERT(firstLine.isValid());
+ QPointF pos = (offset + layout->position()).toPoint();
+ Qt::LayoutDirection dir = bl.textDirection();
+ {
+ QRectF textRect = firstLine.naturalTextRect();
+ pos += textRect.topLeft().toPoint();
+ if (dir == Qt::RightToLeft)
+ pos.rx() += textRect.width();
+ }
+
+ switch (style) {
+ case QTextListFormat::ListDecimal:
+ case QTextListFormat::ListLowerAlpha:
+ case QTextListFormat::ListUpperAlpha:
+ case QTextListFormat::ListLowerRoman:
+ case QTextListFormat::ListUpperRoman:
+ itemText = static_cast<QTextList *>(object)->itemText(bl);
+ size.setWidth(fontMetrics.width(itemText));
+ size.setHeight(fontMetrics.height());
+ break;
+
+ case QTextListFormat::ListSquare:
+ case QTextListFormat::ListCircle:
+ case QTextListFormat::ListDisc:
+ size.setWidth(fontMetrics.lineSpacing() / 3);
+ size.setHeight(size.width());
+ break;
+
+ case QTextListFormat::ListStyleUndefined:
+ return;
+ default: return;
+ }
+
+ QRectF r(pos, size);
+
+ qreal xoff = fontMetrics.width(QLatin1Char(' '));
+ if (dir == Qt::LeftToRight)
+ xoff = -xoff - size.width();
+ r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2));
+
+ painter->save();
+
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ if (selectionFormat) {
+ painter->setPen(QPen(selectionFormat->foreground(), 0));
+ painter->fillRect(r, selectionFormat->background());
+ } else {
+ QBrush fg = charFormat.foreground();
+ if (fg == Qt::NoBrush)
+ fg = context.palette.text();
+ painter->setPen(QPen(fg, 0));
+ }
+
+ QBrush brush = context.palette.brush(QPalette::Text);
+
+ switch (style) {
+ case QTextListFormat::ListDecimal:
+ case QTextListFormat::ListLowerAlpha:
+ case QTextListFormat::ListUpperAlpha:
+ case QTextListFormat::ListLowerRoman:
+ case QTextListFormat::ListUpperRoman: {
+ QTextLayout layout(itemText, font, q->paintDevice());
+ layout.setCacheEnabled(true);
+ QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
+ option.setTextDirection(dir);
+ layout.setTextOption(option);
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ if (line.isValid())
+ line.setLeadingIncluded(true);
+ layout.endLayout();
+ layout.draw(painter, QPointF(r.left(), pos.y()));
+ break;
+ }
+ case QTextListFormat::ListSquare:
+ painter->fillRect(r, brush);
+ break;
+ case QTextListFormat::ListCircle:
+ painter->setPen(QPen(brush, 0));
+ painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
+ break;
+ case QTextListFormat::ListDisc:
+ painter->setBrush(brush);
+ painter->setPen(Qt::NoPen);
+ painter->drawEllipse(r);
+ break;
+ case QTextListFormat::ListStyleUndefined:
+ break;
+ default:
+ break;
+ }
+
+ painter->restore();
+}
+
+static QFixed flowPosition(const QTextFrame::iterator it)
+{
+ if (it.atEnd())
+ return 0;
+
+ if (it.currentFrame()) {
+ return data(it.currentFrame())->position.y;
+ } else {
+ QTextBlock block = it.currentBlock();
+ QTextLayout *layout = block.layout();
+ if (layout->lineCount() == 0)
+ return QFixed::fromReal(layout->position().y());
+ else
+ return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
+ }
+}
+
+static QFixed firstChildPos(const QTextFrame *f)
+{
+ return flowPosition(f->begin());
+}
+
+QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
+ int layoutFrom, int layoutTo, QTextTableData *td,
+ QFixed absoluteTableY, bool withPageBreaks)
+{
+ LDEBUG << "layoutCell";
+ QTextLayoutStruct layoutStruct;
+ layoutStruct.frame = t;
+ layoutStruct.minimumWidth = 0;
+ layoutStruct.maximumWidth = QFIXED_MAX;
+ layoutStruct.y = 0;
+
+ const QTextFormat fmt = cell.format();
+ const QFixed topPadding = td->topPadding(fmt);
+ if (withPageBreaks) {
+ layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
+ }
+ layoutStruct.x_left = 0;
+ layoutStruct.x_right = width;
+ // we get called with different widths all the time (for example for figuring
+ // out the min/max widths), so we always have to do the full layout ;(
+ // also when for example in a table layoutFrom/layoutTo affect only one cell,
+ // making that one cell grow the available width of the other cells may change
+ // (shrink) and therefore when layoutCell gets called for them they have to
+ // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
+ // this line:
+
+ layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
+ if (layoutStruct.pageHeight < 0 || !withPageBreaks)
+ layoutStruct.pageHeight = QFIXED_MAX;
+ const int currentPage = layoutStruct.currentPage();
+ layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
+ layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
+ layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
+
+ layoutStruct.fullLayout = true;
+
+ QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
+ layoutStruct.y = qMax(layoutStruct.y, pageTop);
+
+ const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
+ for (int i = 0; i < childFrames.size(); ++i) {
+ QTextFrame *frame = childFrames.at(i);
+ QTextFrameData *cd = data(frame);
+ cd->sizeDirty = true;
+ }
+
+ layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
+
+ QFixed floatMinWidth;
+
+ // floats that are located inside the text (like inline images) aren't taken into account by
+ // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
+ // do that here. For example with <td><img align="right" src="..." />blah</td>
+ // when the image happens to be higher than the text
+ for (int i = 0; i < childFrames.size(); ++i) {
+ QTextFrame *frame = childFrames.at(i);
+ QTextFrameData *cd = data(frame);
+
+ if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
+ layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
+
+ floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
+ }
+
+ // constraint the maximumWidth by the minimum width of the fixed size floats, to
+ // keep them visible
+ layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
+
+ // as floats in cells get added to the table's float list but must not affect
+ // floats in other cells we must clear the list here.
+ data(t)->floats.clear();
+
+// qDebug() << "layoutCell done";
+
+ return layoutStruct;
+}
+
+QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
+{
+ LDEBUG << "layoutTable";
+ QTextTableData *td = static_cast<QTextTableData *>(data(table));
+ Q_ASSERT(td->sizeDirty);
+ const int rows = table->rows();
+ const int columns = table->columns();
+
+ const QTextTableFormat fmt = table->format();
+
+ td->childFrameMap.clear();
+ {
+ const QList<QTextFrame *> children = table->childFrames();
+ for (int i = 0; i < children.count(); ++i) {
+ QTextFrame *frame = children.at(i);
+ QTextTableCell cell = table->cellAt(frame->firstPosition());
+ td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
+ }
+ }
+
+ QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
+ if (columnWidthConstraints.size() != columns)
+ columnWidthConstraints.resize(columns);
+ Q_ASSERT(columnWidthConstraints.count() == columns);
+
+ const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
+ td->deviceScale = scaleToDevice(qreal(1));
+ td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
+ const QFixed leftMargin = td->leftMargin + td->border + td->padding;
+ const QFixed rightMargin = td->rightMargin + td->border + td->padding;
+ const QFixed topMargin = td->topMargin + td->border + td->padding;
+
+ const QFixed absoluteTableY = parentY + td->position.y;
+
+ const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
+
+recalc_minmax_widths:
+
+ QFixed remainingWidth = td->contentsWidth;
+ // two (vertical) borders per cell per column
+ remainingWidth -= columns * 2 * td->border;
+ // inter-cell spacing
+ remainingWidth -= (columns - 1) * cellSpacing;
+ // cell spacing at the left and right hand side
+ remainingWidth -= 2 * cellSpacing;
+ // remember the width used to distribute to percentaged columns
+ const QFixed initialTotalWidth = remainingWidth;
+
+ td->widths.resize(columns);
+ td->widths.fill(0);
+
+ td->minWidths.resize(columns);
+ // start with a minimum width of 0. totally empty
+ // cells of default created tables are invisible otherwise
+ // and therefore hardly editable
+ td->minWidths.fill(1);
+
+ td->maxWidths.resize(columns);
+ td->maxWidths.fill(QFIXED_MAX);
+
+ // calculate minimum and maximum sizes of the columns
+ for (int i = 0; i < columns; ++i) {
+ for (int row = 0; row < rows; ++row) {
+ const QTextTableCell cell = table->cellAt(row, i);
+ const int cspan = cell.columnSpan();
+
+ if (cspan > 1 && i != cell.column())
+ continue;
+
+ const QTextFormat fmt = cell.format();
+ const QFixed leftPadding = td->leftPadding(fmt);
+ const QFixed rightPadding = td->rightPadding(fmt);
+ const QFixed widthPadding = leftPadding + rightPadding;
+
+ // to figure out the min and the max width lay out the cell at
+ // maximum width. otherwise the maxwidth calculation sometimes
+ // returns wrong values
+ QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
+ layoutTo, td, absoluteTableY,
+ /*withPageBreaks =*/false);
+
+ // distribute the minimum width over all columns the cell spans
+ QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
+ for (int n = 0; n < cspan; ++n) {
+ const int col = i + n;
+ QFixed w = widthToDistribute / (cspan - n);
+ td->minWidths[col] = qMax(td->minWidths.at(col), w);
+ widthToDistribute -= td->minWidths.at(col);
+ if (widthToDistribute <= 0)
+ break;
+ }
+
+ QFixed maxW = td->maxWidths.at(i);
+ if (layoutStruct.maximumWidth != QFIXED_MAX) {
+ if (maxW == QFIXED_MAX)
+ maxW = layoutStruct.maximumWidth + widthPadding;
+ else
+ maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
+ }
+ if (maxW == QFIXED_MAX)
+ continue;
+
+ widthToDistribute = maxW;
+ for (int n = 0; n < cspan; ++n) {
+ const int col = i + n;
+ QFixed w = widthToDistribute / (cspan - n);
+ td->maxWidths[col] = qMax(td->minWidths.at(col), w);
+ widthToDistribute -= td->maxWidths.at(col);
+ if (widthToDistribute <= 0)
+ break;
+ }
+ }
+ }
+
+ // set fixed values, figure out total percentages used and number of
+ // variable length cells. Also assign the minimum width for variable columns.
+ QFixed totalPercentage;
+ int variableCols = 0;
+ QFixed totalMinWidth = 0;
+ for (int i = 0; i < columns; ++i) {
+ const QTextLength &length = columnWidthConstraints.at(i);
+ if (length.type() == QTextLength::FixedLength) {
+ td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
+ remainingWidth -= td->widths.at(i);
+ } else if (length.type() == QTextLength::PercentageLength) {
+ totalPercentage += QFixed::fromReal(length.rawValue());
+ } else if (length.type() == QTextLength::VariableLength) {
+ variableCols++;
+
+ td->widths[i] = td->minWidths.at(i);
+ remainingWidth -= td->minWidths.at(i);
+ }
+ totalMinWidth += td->minWidths.at(i);
+ }
+
+ // set percentage values
+ {
+ const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
+ QFixed remainingMinWidths = totalMinWidth;
+ for (int i = 0; i < columns; ++i) {
+ remainingMinWidths -= td->minWidths.at(i);
+ if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
+ const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
+
+ const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
+ if (percentWidth >= td->minWidths.at(i)) {
+ td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
+ } else {
+ td->widths[i] = td->minWidths.at(i);
+ }
+ remainingWidth -= td->widths.at(i);
+ }
+ }
+ }
+
+ // for variable columns distribute the remaining space
+ if (variableCols > 0 && remainingWidth > 0) {
+ QVarLengthArray<int> columnsWithProperMaxSize;
+ for (int i = 0; i < columns; ++i)
+ if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
+ && td->maxWidths.at(i) != QFIXED_MAX)
+ columnsWithProperMaxSize.append(i);
+
+ QFixed lastRemainingWidth = remainingWidth;
+ while (remainingWidth > 0) {
+ for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
+ const int col = columnsWithProperMaxSize[k];
+ const int colsLeft = columnsWithProperMaxSize.count() - k;
+ const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
+ td->widths[col] += w;
+ remainingWidth -= w;
+ }
+ if (remainingWidth == lastRemainingWidth)
+ break;
+ lastRemainingWidth = remainingWidth;
+ }
+
+ if (remainingWidth > 0
+ // don't unnecessarily grow variable length sized tables
+ && fmt.width().type() != QTextLength::VariableLength) {
+ const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
+ for (int col = 0; col < columns; ++col) {
+ if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
+ td->widths[col] += widthPerAnySizedCol;
+ }
+ }
+ }
+
+ td->columnPositions.resize(columns);
+ td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
+
+ for (int i = 1; i < columns; ++i)
+ td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
+
+ // - margin to compensate the + margin in columnPositions[0]
+ const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin;
+
+ // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
+ // mode
+ if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
+ && contentsWidth > td->contentsWidth) {
+ docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
+ // go back to the top of the function
+ goto recalc_minmax_widths;
+ }
+
+ td->contentsWidth = contentsWidth;
+
+ docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
+
+ td->heights.resize(rows);
+ td->heights.fill(0);
+
+ td->rowPositions.resize(rows);
+ td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
+
+ bool haveRowSpannedCells = false;
+
+ // need to keep track of cell heights for vertical alignment
+ QVector<QFixed> cellHeights;
+ cellHeights.reserve(rows * columns);
+
+ QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
+ if (pageHeight <= 0)
+ pageHeight = QFIXED_MAX;
+
+ QVector<QFixed> heightToDistribute;
+ heightToDistribute.resize(columns);
+
+ td->headerHeight = 0;
+ const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
+ const QFixed originalTopMargin = td->effectiveTopMargin;
+ bool hasDroppedTable = false;
+
+ // now that we have the column widths we can lay out all cells with the right width.
+ // spanning cells are only allowed to grow the last row spanned by the cell.
+ //
+ // ### this could be made faster by iterating over the cells array of QTextTable
+ for (int r = 0; r < rows; ++r) {
+ td->calcRowPosition(r);
+
+ const int tableStartPage = (absoluteTableY / pageHeight).truncate();
+ const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
+ const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
+ const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
+ const QFixed nextPageTop = pageTop + pageHeight;
+
+ if (td->rowPositions[r] > pageBottom)
+ td->rowPositions[r] = nextPageTop;
+ else if (td->rowPositions[r] < pageTop)
+ td->rowPositions[r] = pageTop;
+
+ bool dropRowToNextPage = true;
+ int cellCountBeforeRow = cellHeights.size();
+
+ // if we drop the row to the next page we need to subtract the drop
+ // distance from any row spanning cells
+ QFixed dropDistance = 0;
+
+relayout:
+ const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
+ // if any of the header rows or the first non-header row start on the next page
+ // then the entire header should be dropped
+ if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
+ td->rowPositions[0] = nextPageTop;
+ cellHeights.clear();
+ td->effectiveTopMargin = originalTopMargin;
+ hasDroppedTable = true;
+ r = -1;
+ continue;
+ }
+
+ int rowCellCount = 0;
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(r, c);
+ const int rspan = cell.rowSpan();
+ const int cspan = cell.columnSpan();
+
+ if (cspan > 1 && cell.column() != c)
+ continue;
+
+ if (rspan > 1) {
+ haveRowSpannedCells = true;
+
+ const int cellRow = cell.row();
+ if (cellRow != r) {
+ // the last row gets all the remaining space
+ if (cellRow + rspan - 1 == r)
+ td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
+ continue;
+ }
+ }
+
+ const QTextFormat fmt = cell.format();
+
+ const QFixed topPadding = td->topPadding(fmt);
+ const QFixed bottomPadding = td->bottomPadding(fmt);
+ const QFixed leftPadding = td->leftPadding(fmt);
+ const QFixed rightPadding = td->rightPadding(fmt);
+ const QFixed widthPadding = leftPadding + rightPadding;
+
+ ++rowCellCount;
+
+ const QFixed width = td->cellWidth(c, cspan) - widthPadding;
+ QTextLayoutStruct layoutStruct = layoutCell(table, cell, width,
+ layoutFrom, layoutTo,
+ td, absoluteTableY,
+ /*withPageBreaks =*/true);
+
+ const QFixed height = layoutStruct.y + bottomPadding + topPadding;
+
+ if (rspan > 1)
+ heightToDistribute[c] = height + dropDistance;
+ else
+ td->heights[r] = qMax(td->heights.at(r), height);
+
+ cellHeights.append(layoutStruct.y);
+
+ QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
+ if (childPos < pageBottom)
+ dropRowToNextPage = false;
+ }
+
+ if (rowCellCount > 0 && dropRowToNextPage) {
+ dropDistance = nextPageTop - td->rowPositions[r];
+ td->rowPositions[r] = nextPageTop;
+ td->heights[r] = 0;
+ dropRowToNextPage = false;
+ cellHeights.resize(cellCountBeforeRow);
+ if (r > headerRowCount)
+ td->heights[r-1] = pageBottom - td->rowPositions[r-1];
+ goto relayout;
+ }
+
+ if (haveRowSpannedCells) {
+ const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
+ for (int c = 0; c < columns; ++c)
+ heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
+ }
+
+ if (r == headerRowCount - 1) {
+ td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border;
+ td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
+ td->effectiveTopMargin += td->headerHeight;
+ }
+ }
+
+ td->effectiveTopMargin = originalTopMargin;
+
+ // now that all cells have been properly laid out, we can compute the
+ // vertical offsets for vertical alignment
+ td->cellVerticalOffsets.resize(rows * columns);
+ int cellIndex = 0;
+ for (int r = 0; r < rows; ++r) {
+ for (int c = 0; c < columns; ++c) {
+ QTextTableCell cell = table->cellAt(r, c);
+ if (cell.row() != r || cell.column() != c)
+ continue;
+
+ const int rowSpan = cell.rowSpan();
+ const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
+
+ const QTextCharFormat cellFormat = cell.format();
+ const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
+
+ QFixed offset = 0;
+ switch (cellFormat.verticalAlignment()) {
+ case QTextCharFormat::AlignMiddle:
+ offset = (availableHeight - cellHeight) / 2;
+ break;
+ case QTextCharFormat::AlignBottom:
+ offset = availableHeight - cellHeight;
+ break;
+ default:
+ break;
+ };
+
+ for (int rd = 0; rd < cell.rowSpan(); ++rd) {
+ for (int cd = 0; cd < cell.columnSpan(); ++cd) {
+ const int index = (c + cd) + (r + rd) * columns;
+ td->cellVerticalOffsets[index] = offset;
+ }
+ }
+ }
+ }
+
+ td->minimumWidth = td->columnPositions.at(0);
+ for (int i = 0; i < columns; ++i) {
+ td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
+ }
+ td->minimumWidth += rightMargin - td->border;
+
+ td->maximumWidth = td->columnPositions.at(0);
+ for (int i = 0; i < columns; ++i)
+ if (td->maxWidths.at(i) != QFIXED_MAX)
+ td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
+ td->maximumWidth += rightMargin - td->border;
+
+ td->updateTableSize();
+ td->sizeDirty = false;
+ return QRectF(); // invalid rect -> update everything
+}
+
+void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
+{
+ QTextFrameData *fd = data(frame);
+
+ QTextFrame *parent = frame->parentFrame();
+ Q_ASSERT(parent);
+ QTextFrameData *pd = data(parent);
+ Q_ASSERT(pd && pd->currentLayoutStruct);
+
+ QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct;
+
+ if (!pd->floats.contains(frame))
+ pd->floats.append(frame);
+ fd->layoutDirty = true;
+ Q_ASSERT(!fd->sizeDirty);
+
+// qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
+ QFixed y = layoutStruct->y;
+ if (currentLine) {
+ QFixed left, right;
+ floatMargins(y, layoutStruct, &left, &right);
+// qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
+ if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
+ layoutStruct->pendingFloats.append(frame);
+// qDebug() << " adding to pending list";
+ return;
+ }
+ }
+
+ bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom);
+ if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) {
+ layoutStruct->newPage();
+ y = layoutStruct->y;
+
+ frameSpansIntoNextPage = false;
+ }
+
+ y = findY(y, layoutStruct, fd->size.width);
+
+ QFixed left, right;
+ floatMargins(y, layoutStruct, &left, &right);
+
+ if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
+ fd->position.x = left;
+ fd->position.y = y;
+ } else {
+ fd->position.x = right - fd->size.width;
+ fd->position.y = y;
+ }
+
+ layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
+ layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
+
+// qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
+ fd->layoutDirty = false;
+
+ // If the frame is a table, then positioning it will affect the size if it covers more than
+ // one page, because of page breaks and repeating the header.
+ if (qobject_cast<QTextTable *>(frame) != 0)
+ fd->sizeDirty = frameSpansIntoNextPage;
+}
+
+QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
+{
+ LDEBUG << "layoutFrame (pre)";
+ Q_ASSERT(data(f)->sizeDirty);
+// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
+
+ QTextFrameFormat fformat = f->frameFormat();
+
+ QTextFrame *parent = f->parentFrame();
+ const QTextFrameData *pd = parent ? data(parent) : 0;
+
+ const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
+ QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
+ if (fformat.width().type() == QTextLength::FixedLength)
+ width = scaleToDevice(width);
+
+ const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
+ const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
+ ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
+ : -1;
+
+ return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
+}
+
+QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
+{
+ LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo;
+ Q_ASSERT(data(f)->sizeDirty);
+// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
+
+ QTextFrameData *fd = data(f);
+ QFixed newContentsWidth;
+
+ {
+ QTextFrameFormat fformat = f->frameFormat();
+ // set sizes of this frame from the format
+ fd->topMargin = QFixed::fromReal(fformat.topMargin());
+ fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin());
+ fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
+ fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
+ fd->border = QFixed::fromReal(fformat.border());
+ fd->padding = QFixed::fromReal(fformat.padding());
+
+ QTextFrame *parent = f->parentFrame();
+ const QTextFrameData *pd = parent ? data(parent) : 0;
+
+ // accumulate top and bottom margins
+ if (parent) {
+ fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
+ fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
+
+ if (qobject_cast<QTextTable *>(parent)) {
+ const QTextTableData *td = static_cast<const QTextTableData *>(pd);
+ fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
+ fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
+ }
+ } else {
+ fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
+ fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
+ }
+
+ newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
+ - fd->leftMargin - fd->rightMargin;
+
+ if (frameHeight != -1) {
+ fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
+ - fd->topMargin - fd->bottomMargin;
+ } else {
+ fd->contentsHeight = frameHeight;
+ }
+ }
+
+ if (isFrameFromInlineObject(f)) {
+ // never reached, handled in resizeInlineObject/positionFloat instead
+ return QRectF();
+ }
+
+ if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
+ fd->contentsWidth = newContentsWidth;
+ return layoutTable(table, layoutFrom, layoutTo, parentY);
+ }
+
+ // set fd->contentsWidth temporarily, so that layoutFrame for the children
+ // picks the right width. We'll initialize it properly at the end of this
+ // function.
+ fd->contentsWidth = newContentsWidth;
+
+ QTextLayoutStruct layoutStruct;
+ layoutStruct.frame = f;
+ layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
+ layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
+ layoutStruct.y = fd->topMargin + fd->border + fd->padding;
+ layoutStruct.frameY = parentY + fd->position.y;
+ layoutStruct.contentsWidth = 0;
+ layoutStruct.minimumWidth = 0;
+ layoutStruct.maximumWidth = QFIXED_MAX;
+ layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth;
+ layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
+ LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
+ << "fullLayout" << layoutStruct.fullLayout;
+ fd->oldContentsWidth = newContentsWidth;
+
+ layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
+ if (layoutStruct.pageHeight < 0)
+ layoutStruct.pageHeight = QFIXED_MAX;
+
+ const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
+ layoutStruct.pageTopMargin = fd->effectiveTopMargin;
+ layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
+ layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
+
+ if (!f->parentFrame())
+ idealWidth = 0; // reset
+
+ QTextFrame::Iterator it = f->begin();
+ layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
+
+ QFixed maxChildFrameWidth = 0;
+ QList<QTextFrame *> children = f->childFrames();
+ for (int i = 0; i < children.size(); ++i) {
+ QTextFrame *c = children.at(i);
+ QTextFrameData *cd = data(c);
+ maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
+ }
+
+ const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
+ if (!f->parentFrame()) {
+ idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
+ idealWidth += marginWidth.toReal();
+ }
+
+ QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
+ fd->contentsWidth = actualWidth;
+ if (newContentsWidth <= 0) { // nowrap layout?
+ fd->contentsWidth = newContentsWidth;
+ }
+
+ fd->minimumWidth = layoutStruct.minimumWidth;
+ fd->maximumWidth = layoutStruct.maximumWidth;
+
+ fd->size.height = fd->contentsHeight == -1
+ ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
+ : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
+ fd->size.width = actualWidth + marginWidth;
+ fd->sizeDirty = false;
+ if (layoutStruct.updateRectForFloats.isValid())
+ layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
+ return layoutStruct.updateRect;
+}
+
+void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct,
+ int layoutFrom, int layoutTo, QFixed width)
+{
+ LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
+ QTextFrameData *fd = data(layoutStruct->frame);
+
+ fd->currentLayoutStruct = layoutStruct;
+
+ QTextFrame::Iterator previousIt;
+
+ const bool inRootFrame = (it.parentFrame() == document->rootFrame());
+ if (inRootFrame) {
+ bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
+
+ if (!redoCheckPoints) {
+ QVector<QCheckPoint>::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom);
+ if (checkPoint != checkPoints.end()) {
+ if (checkPoint != checkPoints.begin())
+ --checkPoint;
+
+ layoutStruct->y = checkPoint->y;
+ layoutStruct->frameY = checkPoint->frameY;
+ layoutStruct->minimumWidth = checkPoint->minimumWidth;
+ layoutStruct->maximumWidth = checkPoint->maximumWidth;
+ layoutStruct->contentsWidth = checkPoint->contentsWidth;
+
+ if (layoutStruct->pageHeight > 0) {
+ int page = layoutStruct->currentPage();
+ layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
+ }
+
+ it = frameIteratorForTextPosition(checkPoint->positionInFrame);
+ checkPoints.resize(checkPoint - checkPoints.begin() + 1);
+
+ if (checkPoint != checkPoints.begin()) {
+ previousIt = it;
+ --previousIt;
+ }
+ } else {
+ redoCheckPoints = true;
+ }
+ }
+
+ if (redoCheckPoints) {
+ checkPoints.clear();
+ QCheckPoint cp;
+ cp.y = layoutStruct->y;
+ cp.frameY = layoutStruct->frameY;
+ cp.positionInFrame = 0;
+ cp.minimumWidth = layoutStruct->minimumWidth;
+ cp.maximumWidth = layoutStruct->maximumWidth;
+ cp.contentsWidth = layoutStruct->contentsWidth;
+ checkPoints.append(cp);
+ }
+ }
+
+ QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
+
+ QFixed maximumBlockWidth = 0;
+ while (!it.atEnd()) {
+ QTextFrame *c = it.currentFrame();
+
+ int docPos;
+ if (it.currentFrame())
+ docPos = it.currentFrame()->firstPosition();
+ else
+ docPos = it.currentBlock().position();
+
+ if (inRootFrame) {
+ if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) {
+ QFixed left, right;
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
+ QCheckPoint p;
+ p.y = layoutStruct->y;
+ p.frameY = layoutStruct->frameY;
+ p.positionInFrame = docPos;
+ p.minimumWidth = layoutStruct->minimumWidth;
+ p.maximumWidth = layoutStruct->maximumWidth;
+ p.contentsWidth = layoutStruct->contentsWidth;
+ checkPoints.append(p);
+
+ if (currentLazyLayoutPosition != -1
+ && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
+ break;
+
+ }
+ }
+ }
+
+ if (c) {
+ // position child frame
+ QTextFrameData *cd = data(c);
+
+ QTextFrameFormat fformat = c->frameFormat();
+
+ if (fformat.position() == QTextFrameFormat::InFlow) {
+ if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
+ layoutStruct->newPage();
+
+ QFixed left, right;
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ left = qMax(left, layoutStruct->x_left);
+ right = qMin(right, layoutStruct->x_right);
+
+ if (right - left < cd->size.width) {
+ layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ }
+
+ QFixedPoint pos(left, layoutStruct->y);
+
+ Qt::Alignment align = Qt::AlignLeft;
+
+ QTextTable *table = qobject_cast<QTextTable *>(c);
+
+ if (table)
+ align = table->format().alignment() & Qt::AlignHorizontal_Mask;
+
+ // detect whether we have any alignment in the document that disallows optimizations,
+ // such as not laying out the document again in a textedit with wrapping disabled.
+ if (inRootFrame && !(align & Qt::AlignLeft))
+ contentHasAlignment = true;
+
+ cd->position = pos;
+
+ if (document->pageSize().height() > 0.0f)
+ cd->sizeDirty = true;
+
+ if (cd->sizeDirty) {
+ if (width != 0)
+ layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
+ else
+ layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
+
+ QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
+ absoluteChildPos += layoutStruct->frameY;
+
+ // drop entire frame to next page if first child of frame is on next page
+ if (absoluteChildPos > layoutStruct->pageBottom) {
+ layoutStruct->newPage();
+ pos.y = layoutStruct->y;
+
+ cd->position = pos;
+ cd->sizeDirty = true;
+
+ if (width != 0)
+ layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
+ else
+ layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
+ }
+ }
+
+ // align only if there is space for alignment
+ if (right - left > cd->size.width) {
+ if (align & Qt::AlignRight)
+ pos.x += layoutStruct->x_right - cd->size.width;
+ else if (align & Qt::AlignHCenter)
+ pos.x += (layoutStruct->x_right - cd->size.width) / 2;
+ }
+
+ cd->position = pos;
+
+ layoutStruct->y += cd->size.height;
+ const int page = layoutStruct->currentPage();
+ layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
+
+ cd->layoutDirty = false;
+
+ if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
+ layoutStruct->newPage();
+ } else {
+ QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
+ QRectF updateRect;
+
+ if (cd->sizeDirty)
+ updateRect = layoutFrame(c, layoutFrom, layoutTo);
+
+ positionFloat(c);
+
+ // If the size was made dirty when the position was set, layout again
+ if (cd->sizeDirty)
+ updateRect = layoutFrame(c, layoutFrom, layoutTo);
+
+ QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
+
+ if (frameRect == oldFrameRect && updateRect.isValid())
+ updateRect.translate(cd->position.toPointF());
+ else
+ updateRect = frameRect;
+
+ layoutStruct->addUpdateRectForFloat(updateRect);
+ if (oldFrameRect.isValid())
+ layoutStruct->addUpdateRectForFloat(oldFrameRect);
+ }
+
+ layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
+ layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
+
+ previousIt = it;
+ ++it;
+ } else {
+ QTextFrame::Iterator lastIt;
+ if (!previousIt.atEnd())
+ lastIt = previousIt;
+ previousIt = it;
+ QTextBlock block = it.currentBlock();
+ ++it;
+
+ const QTextBlockFormat blockFormat = block.blockFormat();
+
+ if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
+ layoutStruct->newPage();
+
+ const QFixed origY = layoutStruct->y;
+ const QFixed origPageBottom = layoutStruct->pageBottom;
+ const QFixed origMaximumWidth = layoutStruct->maximumWidth;
+ layoutStruct->maximumWidth = 0;
+
+ const QTextBlockFormat *previousBlockFormatPtr = 0;
+ if (lastIt.currentBlock().isValid())
+ previousBlockFormatPtr = &previousBlockFormat;
+
+ // layout and position child block
+ layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
+
+ // detect whether we have any alignment in the document that disallows optimizations,
+ // such as not laying out the document again in a textedit with wrapping disabled.
+ if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
+ contentHasAlignment = true;
+
+ // if the block right before a table is empty 'hide' it by
+ // positioning it into the table border
+ if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
+ const QTextBlock lastBlock = lastIt.currentBlock();
+ const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
+ layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
+ layoutStruct->pageBottom = origPageBottom;
+ } else {
+ // if the block right after a table is empty then 'hide' it, too
+ if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
+ QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
+ QTextLayout *layout = block.layout();
+
+ QPointF pos((td->position.x + td->size.width).toReal(),
+ (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
+
+ layout->setPosition(pos);
+ layoutStruct->y = origY;
+ layoutStruct->pageBottom = origPageBottom;
+ }
+
+ // if the block right after a table starts with a line separator, shift it up by one line
+ if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
+ QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
+ QTextLayout *layout = block.layout();
+
+ QFixed height = QFixed::fromReal(layout->lineAt(0).height());
+
+ if (layoutStruct->pageBottom == origPageBottom) {
+ layoutStruct->y -= height;
+ layout->setPosition(layout->position() - QPointF(0, height.toReal()));
+ } else {
+ // relayout block to correctly handle page breaks
+ layoutStruct->y = origY - height;
+ layoutStruct->pageBottom = origPageBottom;
+ layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
+ }
+
+ QPointF linePos((td->position.x + td->size.width).toReal(),
+ (td->position.y + td->size.height - height).toReal());
+
+ layout->lineAt(0).setPosition(linePos - layout->position());
+ }
+
+ if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
+ layoutStruct->newPage();
+ }
+
+ maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
+ layoutStruct->maximumWidth = origMaximumWidth;
+ previousBlockFormat = blockFormat;
+ }
+ }
+ if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
+ layoutStruct->maximumWidth = maximumBlockWidth;
+ else
+ layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
+
+ // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
+ // we don't need to do it for tables though because floats in tables are per table
+ // and not per cell and layoutCell already takes care of doing the same as we do here
+ if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
+ QList<QTextFrame *> children = layoutStruct->frame->childFrames();
+ for (int i = 0; i < children.count(); ++i) {
+ QTextFrameData *fd = data(children.at(i));
+ if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
+ layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
+ }
+ }
+
+ if (inRootFrame) {
+ // we assume that any float is aligned in a way that disallows the optimizations that rely
+ // on unaligned content.
+ if (!fd->floats.isEmpty())
+ contentHasAlignment = true;
+
+ if (it.atEnd()) {
+ //qDebug() << "layout done!";
+ currentLazyLayoutPosition = -1;
+ QCheckPoint cp;
+ cp.y = layoutStruct->y;
+ cp.positionInFrame = docPrivate->length();
+ cp.minimumWidth = layoutStruct->minimumWidth;
+ cp.maximumWidth = layoutStruct->maximumWidth;
+ cp.contentsWidth = layoutStruct->contentsWidth;
+ checkPoints.append(cp);
+ checkPoints.reserve(checkPoints.size());
+ } else {
+ currentLazyLayoutPosition = checkPoints.last().positionInFrame;
+ // #######
+ //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
+ }
+ }
+
+
+ fd->currentLayoutStruct = 0;
+}
+
+static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling,
+ QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight)
+{
+ *lineHeight = QFixed::fromReal(blockFormat.lineHeight(line.height(), scaling));
+
+ if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight || blockFormat.lineHeightType() == QTextBlockFormat::MinimumHeight) {
+ *lineBreakHeight = *lineHeight;
+ if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight)
+ *lineAdjustment = QFixed::fromReal(line.ascent() + qMax(line.leading(), qreal(0.0))) - ((*lineHeight * 4) / 5);
+ else
+ *lineAdjustment = QFixed::fromReal(line.height()) - *lineHeight;
+ }
+ else {
+ *lineBreakHeight = QFixed::fromReal(line.height());
+ *lineAdjustment = 0;
+ }
+}
+
+void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
+ QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
+{
+ Q_Q(QTextDocumentLayout);
+
+ QTextLayout *tl = bl.layout();
+ const int blockLength = bl.length();
+
+ LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo;
+
+// qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')';
+
+ if (previousBlockFormat) {
+ qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
+ if (margin > 0 && q->paintDevice()) {
+ margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
+ }
+ layoutStruct->y += QFixed::fromReal(margin);
+ }
+
+ //QTextFrameData *fd = data(layoutStruct->frame);
+
+ Qt::LayoutDirection dir = bl.textDirection();
+
+ QFixed extraMargin;
+ if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
+ QFontMetricsF fm(bl.charFormat().font());
+ extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5))));
+ }
+
+ const QFixed indent = this->blockIndent(blockFormat);
+ const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
+ const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
+
+ const QPointF oldPosition = tl->position();
+ tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
+
+ if (layoutStruct->fullLayout
+ || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
+ // force relayout if we cross a page boundary
+ || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
+
+ LDEBUG << " do layout";
+ QTextOption option = docPrivate->defaultTextOption;
+ option.setTextDirection(dir);
+ option.setTabs( blockFormat.tabPositions() );
+
+ Qt::Alignment align = docPrivate->defaultTextOption.alignment();
+ if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
+ align = blockFormat.alignment();
+ option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
+
+ if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
+ option.setWrapMode(QTextOption::ManualWrap);
+ }
+
+ tl->setTextOption(option);
+
+ const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
+
+// qDebug() << " layouting block at" << bl.position();
+ const QFixed cy = layoutStruct->y;
+ const QFixed l = layoutStruct->x_left + totalLeftMargin;
+ const QFixed r = layoutStruct->x_right - totalRightMargin;
+
+ tl->beginLayout();
+ bool firstLine = true;
+ while (1) {
+ QTextLine line = tl->createLine();
+ if (!line.isValid())
+ break;
+ line.setLeadingIncluded(true);
+
+ QFixed left, right;
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ left = qMax(left, l);
+ right = qMin(right, r);
+ QFixed text_indent;
+ if (firstLine) {
+ text_indent = QFixed::fromReal(blockFormat.textIndent());
+ if (dir == Qt::LeftToRight)
+ left += text_indent;
+ else
+ right -= text_indent;
+ firstLine = false;
+ }
+// qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
+
+ if (fixedColumnWidth != -1)
+ line.setNumColumns(fixedColumnWidth, (right - left).toReal());
+ else
+ line.setLineWidth((right - left).toReal());
+
+// qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ left = qMax(left, l);
+ right = qMin(right, r);
+ if (dir == Qt::LeftToRight)
+ left += text_indent;
+ else
+ right -= text_indent;
+
+ if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
+ // float has been added in the meantime, redo
+ layoutStruct->pendingFloats.clear();
+
+ line.setLineWidth((right-left).toReal());
+ if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
+ if (haveWordOrAnyWrapMode) {
+ option.setWrapMode(QTextOption::WrapAnywhere);
+ tl->setTextOption(option);
+ }
+
+ layoutStruct->pendingFloats.clear();
+ // lines min width more than what we have
+ layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ left = qMax(left, l);
+ right = qMin(right, r);
+ if (dir == Qt::LeftToRight)
+ left += text_indent;
+ else
+ right -= text_indent;
+ line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
+
+ if (haveWordOrAnyWrapMode) {
+ option.setWrapMode(QTextOption::WordWrap);
+ tl->setTextOption(option);
+ }
+ }
+
+ }
+
+ QFixed lineBreakHeight, lineHeight, lineAdjustment;
+ qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
+ qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
+ getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight);
+
+ if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) {
+ layoutStruct->newPage();
+
+ floatMargins(layoutStruct->y, layoutStruct, &left, &right);
+ left = qMax(left, l);
+ right = qMin(right, r);
+ if (dir == Qt::LeftToRight)
+ left += text_indent;
+ else
+ right -= text_indent;
+ }
+
+ line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy - lineAdjustment).toReal()));
+ layoutStruct->y += lineHeight;
+ layoutStruct->contentsWidth
+ = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
+
+ // position floats
+ for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
+ QTextFrame *f = layoutStruct->pendingFloats.at(i);
+ positionFloat(f);
+ }
+ layoutStruct->pendingFloats.clear();
+ }
+ tl->endLayout();
+ } else {
+ const int cnt = tl->lineCount();
+ for (int i = 0; i < cnt; ++i) {
+ LDEBUG << "going to move text line" << i;
+ QTextLine line = tl->lineAt(i);
+ layoutStruct->contentsWidth
+ = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
+
+ QFixed lineBreakHeight, lineHeight, lineAdjustment;
+ qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
+ qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
+ getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight);
+
+ if (layoutStruct->pageHeight != QFIXED_MAX) {
+ if (layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom)
+ layoutStruct->newPage();
+ line.setPosition(QPointF(line.position().x(), (layoutStruct->y - lineAdjustment).toReal() - tl->position().y()));
+ }
+ layoutStruct->y += lineHeight;
+ }
+ if (layoutStruct->updateRect.isValid()
+ && blockLength > 1) {
+ if (layoutFrom >= blockPosition + blockLength) {
+ // if our height didn't change and the change in the document is
+ // in one of the later paragraphs, then we don't need to repaint
+ // this one
+ layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
+ } else if (layoutTo < blockPosition) {
+ if (oldPosition == tl->position())
+ // if the change in the document happened earlier in the document
+ // and our position did /not/ change because none of the earlier paragraphs
+ // or frames changed their height, then we don't need to repaint
+ // this one
+ layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
+ else
+ layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
+ }
+ }
+ }
+
+ // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
+ const QFixed margins = totalLeftMargin + totalRightMargin;
+ layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
+
+ const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
+
+ if (maxW > 0) {
+ if (layoutStruct->maximumWidth == QFIXED_MAX)
+ layoutStruct->maximumWidth = maxW;
+ else
+ layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
+ }
+}
+
+void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct,
+ QFixed *left, QFixed *right) const
+{
+// qDebug() << "floatMargins y=" << y;
+ *left = layoutStruct->x_left;
+ *right = layoutStruct->x_right;
+ QTextFrameData *lfd = data(layoutStruct->frame);
+ for (int i = 0; i < lfd->floats.size(); ++i) {
+ QTextFrameData *fd = data(lfd->floats.at(i));
+ if (!fd->layoutDirty) {
+ if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
+// qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
+ if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
+ *left = qMax(*left, fd->position.x + fd->size.width);
+ else
+ *right = qMin(*right, fd->position.x);
+ }
+ }
+ }
+// qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
+}
+
+QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const
+{
+ QFixed right, left;
+ requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
+
+// qDebug() << "findY:" << yFrom;
+ while (1) {
+ floatMargins(yFrom, layoutStruct, &left, &right);
+// qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
+ if (right-left >= requiredWidth)
+ break;
+
+ // move float down until we find enough space
+ QFixed newY = QFIXED_MAX;
+ QTextFrameData *lfd = data(layoutStruct->frame);
+ for (int i = 0; i < lfd->floats.size(); ++i) {
+ QTextFrameData *fd = data(lfd->floats.at(i));
+ if (!fd->layoutDirty) {
+ if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
+ newY = qMin(newY, fd->position.y + fd->size.height);
+ }
+ }
+ if (newY == QFIXED_MAX)
+ break;
+ yFrom = newY;
+ }
+ return yFrom;
+}
+
+QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
+ : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
+{
+ registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
+}
+
+
+void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
+{
+ Q_D(QTextDocumentLayout);
+ QTextFrame *frame = d->document->rootFrame();
+ QTextFrameData *fd = data(frame);
+
+ if(fd->sizeDirty)
+ return;
+
+ if (context.clip.isValid()) {
+ d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
+ } else {
+ d->ensureLayoutFinished();
+ }
+
+ QFixed width = fd->size.width;
+ if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
+ // we're in NoWrap mode, meaning the frame should expand to the viewport
+ // so that backgrounds are drawn correctly
+ fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
+ }
+
+ // Make sure we conform to the root frames bounds when drawing.
+ d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
+ d->drawFrame(QPointF(), painter, context, frame);
+ fd->size.width = width;
+}
+
+void QTextDocumentLayout::setViewport(const QRectF &viewport)
+{
+ Q_D(QTextDocumentLayout);
+ d->viewportRect = viewport;
+}
+
+static void markFrames(QTextFrame *current, int from, int oldLength, int length)
+{
+ int end = qMax(oldLength, length) + from;
+
+ if (current->firstPosition() >= end || current->lastPosition() < from)
+ return;
+
+ QTextFrameData *fd = data(current);
+ for (int i = 0; i < fd->floats.size(); ++i) {
+ QTextFrame *f = fd->floats[i];
+ if (!f) {
+ // float got removed in editing operation
+ fd->floats.removeAt(i);
+ --i;
+ }
+ }
+
+ fd->layoutDirty = true;
+ fd->sizeDirty = true;
+
+// qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
+ QList<QTextFrame *> children = current->childFrames();
+ for (int i = 0; i < children.size(); ++i)
+ markFrames(children.at(i), from, oldLength, length);
+}
+
+void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
+{
+ Q_D(QTextDocumentLayout);
+
+ QTextBlock blockIt = document()->findBlock(from);
+ QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
+ if (endIt.isValid())
+ endIt = endIt.next();
+ for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
+ blockIt.clearLayout();
+
+ if (d->docPrivate->pageSize.isNull())
+ return;
+
+ QRectF updateRect;
+
+ d->lazyLayoutStepSize = 1000;
+ d->sizeChangedTimer.stop();
+ d->insideDocumentChange = true;
+
+ const int documentLength = d->docPrivate->length();
+ const bool fullLayout = (oldLength == 0 && length == documentLength);
+ const bool smallChange = documentLength > 0
+ && (qMax(length, oldLength) * 100 / documentLength) < 5;
+
+ // don't show incremental layout progress (avoid scroll bar flicker)
+ // if we see only a small change in the document and we're either starting
+ // a layout run or we're already in progress for that and we haven't seen
+ // any bigger change previously (showLayoutProgress already false)
+ if (smallChange
+ && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
+ d->showLayoutProgress = false;
+ else
+ d->showLayoutProgress = true;
+
+ if (fullLayout) {
+ d->contentHasAlignment = false;
+ d->currentLazyLayoutPosition = 0;
+ d->checkPoints.clear();
+ d->layoutStep();
+ } else {
+ d->ensureLayoutedByPosition(from);
+ updateRect = doLayout(from, oldLength, length);
+ }
+
+ if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
+ d->layoutTimer.start(10, this);
+
+ d->insideDocumentChange = false;
+
+ if (d->showLayoutProgress) {
+ const QSizeF newSize = dynamicDocumentSize();
+ if (newSize != d->lastReportedSize) {
+ d->lastReportedSize = newSize;
+ emit documentSizeChanged(newSize);
+ }
+ }
+
+ if (!updateRect.isValid()) {
+ // don't use the frame size, it might have shrunken
+ updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
+ }
+
+ emit update(updateRect);
+}
+
+QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
+{
+ Q_D(QTextDocumentLayout);
+
+// qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
+
+ // mark all frames between f_start and f_end as dirty
+ markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
+
+ QRectF updateRect;
+
+ QTextFrame *root = d->docPrivate->rootFrame();
+ if(data(root)->sizeDirty)
+ updateRect = d->layoutFrame(root, from, from + length);
+ data(root)->layoutDirty = false;
+
+ if (d->currentLazyLayoutPosition == -1)
+ layoutFinished();
+ else if (d->showLayoutProgress)
+ d->sizeChangedTimer.start(0, this);
+
+ return updateRect;
+}
+
+int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
+{
+ Q_D(const QTextDocumentLayout);
+ d->ensureLayouted(QFixed::fromReal(point.y()));
+ QTextFrame *f = d->docPrivate->rootFrame();
+ int position = 0;
+ QTextLayout *l = 0;
+ QFixedPoint pointf;
+ pointf.x = QFixed::fromReal(point.x());
+ pointf.y = QFixed::fromReal(point.y());
+ QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
+ if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
+ return -1;
+
+ // ensure we stay within document bounds
+ int lastPos = f->lastPosition();
+ if (l && !l->preeditAreaText().isEmpty())
+ lastPos += l->preeditAreaText().length();
+ if (position > lastPos)
+ position = lastPos;
+ else if (position < 0)
+ position = 0;
+
+ return position;
+}
+
+void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
+{
+ Q_D(QTextDocumentLayout);
+ QTextCharFormat f = format.toCharFormat();
+ Q_ASSERT(f.isValid());
+ QTextObjectHandler handler = d->handlers.value(f.objectType());
+ if (!handler.component)
+ return;
+
+ QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
+
+ QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
+ QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
+ if (frame) {
+ pos = frame->frameFormat().position();
+ QTextFrameData *fd = data(frame);
+ fd->sizeDirty = false;
+ fd->size = QFixedSize::fromSizeF(intrinsic);
+ fd->minimumWidth = fd->maximumWidth = fd->size.width;
+ }
+
+ QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
+ item.setWidth(inlineSize.width());
+ if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) {
+ item.setDescent(inlineSize.height() / 2);
+ item.setAscent(inlineSize.height() / 2 - 1);
+ } else {
+ item.setDescent(0);
+ item.setAscent(inlineSize.height() - 1);
+ }
+}
+
+void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
+{
+ Q_D(QTextDocumentLayout);
+ Q_UNUSED(posInDocument);
+ if (item.width() != 0)
+ // inline
+ return;
+
+ QTextCharFormat f = format.toCharFormat();
+ Q_ASSERT(f.isValid());
+ QTextObjectHandler handler = d->handlers.value(f.objectType());
+ if (!handler.component)
+ return;
+
+ QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
+ if (!frame)
+ return;
+
+ QTextBlock b = d->document->findBlock(frame->firstPosition());
+ QTextLine line;
+ if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
+ line = b.layout()->lineAt(b.layout()->lineCount()-1);
+// qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
+// frame->firstPosition() << frame->lastPosition();
+ d->positionFloat(frame, line.isValid() ? &line : 0);
+}
+
+void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
+ int posInDocument, const QTextFormat &format)
+{
+ Q_D(QTextDocumentLayout);
+ QTextCharFormat f = format.toCharFormat();
+ Q_ASSERT(f.isValid());
+ QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
+ if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
+ return; // don't draw floating frames from inline objects here but in drawFlow instead
+
+// qDebug() << "drawObject at" << r;
+ QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
+}
+
+int QTextDocumentLayout::dynamicPageCount() const
+{
+ Q_D(const QTextDocumentLayout);
+ const QSizeF pgSize = d->document->pageSize();
+ if (pgSize.height() < 0)
+ return 1;
+ return qCeil(dynamicDocumentSize().height() / pgSize.height());
+}
+
+QSizeF QTextDocumentLayout::dynamicDocumentSize() const
+{
+ Q_D(const QTextDocumentLayout);
+ return data(d->docPrivate->rootFrame())->size.toSizeF();
+}
+
+int QTextDocumentLayout::pageCount() const
+{
+ Q_D(const QTextDocumentLayout);
+ d->ensureLayoutFinished();
+ return dynamicPageCount();
+}
+
+QSizeF QTextDocumentLayout::documentSize() const
+{
+ Q_D(const QTextDocumentLayout);
+ d->ensureLayoutFinished();
+ return dynamicDocumentSize();
+}
+
+void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
+{
+ Q_Q(const QTextDocumentLayout);
+ if (currentLazyLayoutPosition == -1)
+ return;
+ const QSizeF oldSize = q->dynamicDocumentSize();
+
+ if (checkPoints.isEmpty())
+ layoutStep();
+
+ while (currentLazyLayoutPosition != -1
+ && checkPoints.last().y < y)
+ layoutStep();
+}
+
+void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
+{
+ if (currentLazyLayoutPosition == -1)
+ return;
+ if (position < currentLazyLayoutPosition)
+ return;
+ while (currentLazyLayoutPosition != -1
+ && currentLazyLayoutPosition < position) {
+ const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
+ }
+}
+
+void QTextDocumentLayoutPrivate::layoutStep() const
+{
+ ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
+ lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
+}
+
+void QTextDocumentLayout::setCursorWidth(int width)
+{
+ Q_D(QTextDocumentLayout);
+ d->cursorWidth = width;
+}
+
+int QTextDocumentLayout::cursorWidth() const
+{
+ Q_D(const QTextDocumentLayout);
+ return d->cursorWidth;
+}
+
+void QTextDocumentLayout::setFixedColumnWidth(int width)
+{
+ Q_D(QTextDocumentLayout);
+ d->fixedColumnWidth = width;
+}
+
+QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
+{
+ Q_D(const QTextDocumentLayout);
+ if (d->docPrivate->pageSize.isNull())
+ return QRectF();
+ d->ensureLayoutFinished();
+ return d->frameBoundingRectInternal(frame);
+}
+
+QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
+{
+ QPointF pos;
+ const int framePos = frame->firstPosition();
+ QTextFrame *f = frame;
+ while (f) {
+ QTextFrameData *fd = data(f);
+ pos += fd->position.toPointF();
+
+ if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
+ QTextTableCell cell = table->cellAt(framePos);
+ if (cell.isValid())
+ pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
+ }
+
+ f = f->parentFrame();
+ }
+ return QRectF(pos, data(frame)->size.toSizeF());
+}
+
+QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
+{
+ Q_D(const QTextDocumentLayout);
+ if (d->docPrivate->pageSize.isNull() || !block.isValid())
+ return QRectF();
+ d->ensureLayoutedByPosition(block.position() + block.length());
+ QTextFrame *frame = d->document->frameAt(block.position());
+ QPointF offset;
+ const int blockPos = block.position();
+
+ while (frame) {
+ QTextFrameData *fd = data(frame);
+ offset += fd->position.toPointF();
+
+ if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
+ QTextTableCell cell = table->cellAt(blockPos);
+ if (cell.isValid())
+ offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
+ }
+
+ frame = frame->parentFrame();
+ }
+
+ const QTextLayout *layout = block.layout();
+ QRectF rect = layout->boundingRect();
+ rect.moveTopLeft(layout->position() + offset);
+ return rect;
+}
+
+int QTextDocumentLayout::layoutStatus() const
+{
+ Q_D(const QTextDocumentLayout);
+ int pos = d->currentLazyLayoutPosition;
+ if (pos == -1)
+ return 100;
+ return pos * 100 / d->document->docHandle()->length();
+}
+
+void QTextDocumentLayout::timerEvent(QTimerEvent *e)
+{
+ Q_D(QTextDocumentLayout);
+ if (e->timerId() == d->layoutTimer.timerId()) {
+ if (d->currentLazyLayoutPosition != -1)
+ d->layoutStep();
+ } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
+ d->lastReportedSize = dynamicDocumentSize();
+ emit documentSizeChanged(d->lastReportedSize);
+ d->sizeChangedTimer.stop();
+
+ if (d->currentLazyLayoutPosition == -1) {
+ const int newCount = dynamicPageCount();
+ if (newCount != d->lastPageCount) {
+ d->lastPageCount = newCount;
+ emit pageCountChanged(newCount);
+ }
+ }
+ } else {
+ QAbstractTextDocumentLayout::timerEvent(e);
+ }
+}
+
+void QTextDocumentLayout::layoutFinished()
+{
+ Q_D(QTextDocumentLayout);
+ d->layoutTimer.stop();
+ if (!d->insideDocumentChange)
+ d->sizeChangedTimer.start(0, this);
+ // reset
+ d->showLayoutProgress = true;
+}
+
+void QTextDocumentLayout::ensureLayouted(qreal y)
+{
+ d_func()->ensureLayouted(QFixed::fromReal(y));
+}
+
+qreal QTextDocumentLayout::idealWidth() const
+{
+ Q_D(const QTextDocumentLayout);
+ d->ensureLayoutFinished();
+ return d->idealWidth;
+}
+
+bool QTextDocumentLayout::contentHasAlignment() const
+{
+ Q_D(const QTextDocumentLayout);
+ return d->contentHasAlignment;
+}
+
+qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
+{
+ if (!paintDevice)
+ return value;
+ return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi());
+}
+
+QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
+{
+ if (!paintDevice)
+ return value;
+ return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi());
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qtextdocumentlayout_p.cpp"