/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $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 "private/qguiapplication_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 <qbasictimer.h>
#include "private/qfunctions_p.h"
#include <qloggingcategory.h>

#include <algorithm>

QT_BEGIN_NAMESPACE

Q_LOGGING_CATEGORY(lcDraw, "qt.text.drawing")
Q_LOGGING_CATEGORY(lcHit, "qt.text.hittest")
Q_LOGGING_CATEGORY(lcLayout, "qt.text.layout")
Q_LOGGING_CATEGORY(lcTable, "qt.text.layout.table")

// ################ 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;

    QVector<QPointer<QTextFrame> > floats;
};

QTextFrameData::QTextFrameData()
    : maximumWidth(QFIXED_MAX),
      currentLayoutStruct(nullptr), 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 QFixed contentHeight() const
    { return pageHeight - pageBottomMargin - pageTopMargin; }

    inline int currentPage() const
    { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }

    inline void newPage()
    { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = qMax(y, pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY); }
};

#ifndef QT_NO_CSSPARSER
// helper struct to collect edge data and priorize edges for border-collapse mode
struct EdgeData {

    enum EdgeClass {
        // don't change order, used for comparison
        ClassInvalid,     // queried (adjacent) cell does not exist
        ClassNone,        // no explicit border, no grid, no table border
        ClassGrid,        // 1px grid if drawGrid is true
        ClassTableBorder, // an outermost edge
        ClassExplicit     // set in cell's format
    };

    EdgeData(qreal width, const QTextTableCell &cell, QCss::Edge edge, EdgeClass edgeClass) :
        width(width), cell(cell), edge(edge), edgeClass(edgeClass) {}
    EdgeData() :
        width(0), edge(QCss::NumEdges), edgeClass(ClassInvalid) {}

    // used for priorization with qMax
    bool operator< (const EdgeData &other) const {
        if (width < other.width) return true;
        if (width > other.width) return false;
        if (edgeClass < other.edgeClass) return true;
        if (edgeClass > other.edgeClass) return false;
        if (edge == QCss::TopEdge && other.edge == QCss::BottomEdge) return true;
        if (edge == QCss::BottomEdge && other.edge == QCss::TopEdge) return false;
        if (edge == QCss::LeftEdge && other.edge == QCss::RightEdge) return true;
        return false;
    }
    bool operator> (const EdgeData &other) const {
        return other < *this;
    }

    qreal width;
    QTextTableCell cell;
    QCss::Edge edge;
    EdgeClass edgeClass;
};

// axisEdgeData is referenced by QTextTableData's inline methods, so predeclare
class QTextTableData;
static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell, QCss::Edge edge);
#endif

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;

    // without borderCollapse, those equal QTextFrameData::border;
    // otherwise the widest outermost cell edge will be used
    QFixed effectiveLeftBorder;
    QFixed effectiveTopBorder;
    QFixed effectiveRightBorder;
    QFixed effectiveBottomBorder;

    QFixed headerHeight;

    QFixed borderCell; // 0 if borderCollapse is enabled, QTextFrameData::border otherwise
    bool borderCollapse;
    bool drawGrid;

    // 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) + borderCell + cellSpacing + borderCell;
    }

    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() == QMetaType::Double || v.userType() == QMetaType::Float);
            return QFixed::fromReal(v.toReal() * deviceScale);
        }
    }

#ifndef QT_NO_CSSPARSER
    inline QFixed cellBorderWidth(QTextTable *table, const QTextTableCell &cell, QCss::Edge edge) const
    {
        qreal rv = axisEdgeData(table, this, cell, edge).width;
        if (borderCollapse)
            rv /= 2; // each cell has to add half of the border's width to its own padding
        return QFixed::fromReal(rv * deviceScale);
    }
#endif

    inline QFixed topPadding(QTextTable *table, const QTextTableCell &cell) const
    {
#ifdef QT_NO_CSSPARSER
        Q_UNUSED(table);
#endif
        return paddingProperty(cell.format(), QTextFormat::TableCellTopPadding)
#ifndef QT_NO_CSSPARSER
                + cellBorderWidth(table, cell, QCss::TopEdge)
#endif
        ;
    }

    inline QFixed bottomPadding(QTextTable *table, const QTextTableCell &cell) const
    {
#ifdef QT_NO_CSSPARSER
        Q_UNUSED(table);
#endif
        return paddingProperty(cell.format(), QTextFormat::TableCellBottomPadding)
#ifndef QT_NO_CSSPARSER
                + cellBorderWidth(table, cell, QCss::BottomEdge)
#endif
                ;
    }

    inline QFixed leftPadding(QTextTable *table, const QTextTableCell &cell) const
    {
#ifdef QT_NO_CSSPARSER
        Q_UNUSED(table);
#endif
        return paddingProperty(cell.format(), QTextFormat::TableCellLeftPadding)
#ifndef QT_NO_CSSPARSER
                + cellBorderWidth(table, cell, QCss::LeftEdge)
#endif
        ;
    }

    inline QFixed rightPadding(QTextTable *table, const QTextTableCell &cell) const
    {
#ifdef QT_NO_CSSPARSER
        Q_UNUSED(table);
#endif
        return paddingProperty(cell.format(), QTextFormat::TableCellRightPadding)
#ifndef QT_NO_CSSPARSER
                + cellBorderWidth(table, cell, QCss::RightEdge)
#endif
        ;
    }

    inline QFixedPoint cellPosition(QTextTable *table, const QTextTableCell &cell) const
    {
        return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(table, cell), topPadding(table, cell));
    }

    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 + effectiveTopBorder + padding;
    const QFixed effectiveBottomMargin = this->bottomMargin + effectiveBottomBorder + padding;
    const QFixed effectiveLeftMargin = this->leftMargin + effectiveLeftBorder + padding;
    const QFixed effectiveRightMargin = this->rightMargin + effectiveRightBorder + padding;
    size.height = contentsHeight == -1
                   ? rowPositions.constLast() + heights.constLast() + 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(const 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);

static bool operator<(const QCheckPoint &checkPoint, QFixed y)
{
    return checkPoint.y < y;
}

static bool operator<(const QCheckPoint &checkPoint, int pos)
{
    return checkPoint.positionInFrame < pos;
}

static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, const 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,
                   const QTextBlock &bl, bool inRootFrame) const;
    void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
                      const QTextBlock &bl, const QTextCharFormat *selectionFormat) const;
    void drawTableCellBorder(const QRectF &cellRect, QPainter *painter, QTextTable *table, QTextTableData *td, const QTextTableCell &cell) 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(const 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 = nullptr);

    // 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 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 = std::lower_bound(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();

    qCDebug(lcHit) << "checking frame" << frame->firstPosition() << "point=" << point.toPointF()
                   << "position" << fd->position.toPointF() << "size" << fd->size.toSizeF();
    if (frame != rootFrame) {
        if (relativePoint.y < 0 || relativePoint.x < 0) {
            *position = frame->firstPosition() - 1;
            qCDebug(lcHit) << "before pos=" << *position;
            return PointBefore;
        } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
            *position = frame->lastPosition() + 1;
            qCDebug(lcHit) << "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
{
    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;
        }
    }

    qCDebug(lcHit) << "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 = std::lower_bound(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 = std::lower_bound(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(table, cell), position, l, accuracy);

    if (hp == PointExact)
        return hp;
    if (hp == PointAfter)
        *position = cell.lastPosition();
    return PointInside;
}

QTextDocumentLayoutPrivate::HitPoint
QTextDocumentLayoutPrivate::hitTest(const 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());
    qCDebug(lcHit) << "    checking block" << bl.position() << "point=" << point.toPointF() << "    tlrect" << textrect;
    *position = bl.position();
    if (point.y.toReal() < textrect.top()) {
        qCDebug(lcHit) << "    before pos=" << *position;
        return PointBefore;
    } else if (point.y.toReal() > textrect.bottom()) {
        *position += bl.length();
        qCDebug(lcHit) << "    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;

    qCDebug(lcHit) << "    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());
}

struct BorderPaginator
{
    BorderPaginator(QTextDocument *document, const QRectF &rect, qreal topMarginAfterPageBreak, qreal bottomMargin, qreal border) :
        pageHeight(document->pageSize().height()),
        topPage(pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0),
        bottomPage(pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0),
        rect(rect),
        topMarginAfterPageBreak(topMarginAfterPageBreak),
        bottomMargin(bottomMargin), border(border)
    {}

    QRectF clipRect(int page) const
    {
        QRectF clipped = rect.toRect();

        if (topPage != bottomPage) {
            clipped.setTop(qMax(clipped.top(), page * pageHeight + topMarginAfterPageBreak - border));
            clipped.setBottom(qMin(clipped.bottom(), (page + 1) * pageHeight - bottomMargin));

            if (clipped.bottom() <= clipped.top())
                return QRectF();
        }

        return clipped;
    }

    qreal pageHeight;
    int topPage;
    int bottomPage;
    QRectF rect;
    qreal topMarginAfterPageBreak;
    qreal bottomMargin;
    qreal border;
};

void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
                                            qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
{
    BorderPaginator paginator(document, rect, topMargin, bottomMargin, border);

#ifndef QT_NO_CSSPARSER
    QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
#else
    Q_UNUSED(style);
#endif //QT_NO_CSSPARSER

    bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
    painter->setRenderHint(QPainter::Antialiasing);

    for (int i = paginator.topPage; i <= paginator.bottomPage; ++i) {
        QRectF clipped = paginator.clipRect(i);
        if (!clipped.isValid())
            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);
    }
}

static bool cellClipTest(QTextTable *table, QTextTableData *td,
                         const QAbstractTextDocumentLayout::PaintContext &cell_context,
                         const QTextTableCell &cell,
                         QRectF cellRect)
{
#ifdef QT_NO_CSSPARSER
    Q_UNUSED(table);
    Q_UNUSED(cell);
#endif

    if (!cell_context.clip.isValid())
        return false;

    if (td->borderCollapse) {
        // we need to account for the cell borders in the clipping test
#ifndef QT_NO_CSSPARSER
        cellRect.adjust(-axisEdgeData(table, td, cell, QCss::LeftEdge).width / 2,
                        -axisEdgeData(table, td, cell, QCss::TopEdge).width / 2,
                        axisEdgeData(table, td, cell, QCss::RightEdge).width / 2,
                        axisEdgeData(table, td, cell, QCss::BottomEdge).width / 2);
#endif
    } else {
        qreal border = td->border.toReal();
        cellRect.adjust(-border, -border, border, border);
    }

    if (!cellRect.intersects(cell_context.clip))
        return true;

    return false;
}

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);

    // floor the offset to avoid painting artefacts when drawing adjacent borders
    // we later also round table cell heights and widths
    const QPointF off = QPointF(QPointF(offset + fd->position.toPointF()).toPoint());

    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;

     qCDebug(lcDraw) << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;

    // 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;

        QFixed absYPos = td->position.y;
        QTextFrame *parentFrame = table->parentFrame();
        while (parentFrame) {
            absYPos += data(parentFrame)->position.y;
            parentFrame = parentFrame->parentFrame();
        }
        const int tableStartPage = (absYPos / pageHeight).truncate();
        const int tableEndPage = ((absYPos + td->size.height) / pageHeight).truncate();

        // for borderCollapse draw frame decoration by drawing the outermost
        // cell edges with width = td->border
        if (!td->borderCollapse)
            drawFrameDecoration(painter, frame, fd, context.clip, frameRect);

        // draw the repeated table headers for table continuation after page breaks
        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);
                    if (cellClipTest(table, td, cell_context, cell, cellRect))
                        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 = std::lower_bound(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 = std::upper_bound(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);
                if (cellClipTest(table, td, cell_context, cell, cellRect))
                    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;
        const int numFloats = fd->floats.count();
        floats.reserve(numFloats);
        for (int i = 0; i < numFloats; ++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);
    }

    return;
}

#ifndef QT_NO_CSSPARSER

static inline QTextFormat::Property borderPropertyForEdge(QCss::Edge edge)
{
    switch (edge) {
    case QCss::TopEdge:
        return QTextFormat::TableCellTopBorder;
    case QCss::BottomEdge:
        return QTextFormat::TableCellBottomBorder;
    case QCss::LeftEdge:
        return QTextFormat::TableCellLeftBorder;
    case QCss::RightEdge:
        return QTextFormat::TableCellRightBorder;
    default:
        Q_UNREACHABLE();
        return QTextFormat::UserProperty;
    }
}

static inline QTextFormat::Property borderStylePropertyForEdge(QCss::Edge edge)
{
    switch (edge) {
    case QCss::TopEdge:
        return QTextFormat::TableCellTopBorderStyle;
    case QCss::BottomEdge:
        return QTextFormat::TableCellBottomBorderStyle;
    case QCss::LeftEdge:
        return QTextFormat::TableCellLeftBorderStyle;
    case QCss::RightEdge:
        return QTextFormat::TableCellRightBorderStyle;
    default:
        Q_UNREACHABLE();
        return QTextFormat::UserProperty;
    }
}

static inline QCss::Edge adjacentEdge(QCss::Edge edge)
{
    switch (edge) {
    case QCss::TopEdge:
        return QCss::BottomEdge;
    case QCss::RightEdge:
        return QCss::LeftEdge;
    case QCss::BottomEdge:
        return QCss::TopEdge;
    case QCss::LeftEdge:
        return QCss::RightEdge;
    default:
        Q_UNREACHABLE();
        return QCss::NumEdges;
    }
}

static inline bool isSameAxis(QCss::Edge e1, QCss::Edge e2)
{
    return e1 == e2 || e1 == adjacentEdge(e2);
}

static inline bool isVerticalAxis(QCss::Edge e)
{
    return e % 2 > 0;
}

static inline QTextTableCell adjacentCell(QTextTable *table, const QTextTableCell &cell,
                                          QCss::Edge edge)
{
    int dc = 0;
    int dr = 0;

    switch (edge) {
    case QCss::LeftEdge:
        dc = -1;
        break;
    case QCss::RightEdge:
        dc = cell.columnSpan();
        break;
    case QCss::TopEdge:
        dr = -1;
        break;
    case QCss::BottomEdge:
        dr = cell.rowSpan();
        break;
    default:
        Q_UNREACHABLE();
        break;
    }

    // get sibling cell
    int col = cell.column() + dc;
    int row = cell.row() + dr;

    if (col < 0 || row < 0 || col >= table->columns() || row >= table->rows())
        return QTextTableCell();
    else
        return table->cellAt(cell.row() + dr, cell.column() + dc);
}

// returns true if the specified edges of both cells
// are "one the same line" aka axis.
//
// | C0
// |-----|-----|----|-----  < "axis"
// | C1  | C2  | C3 | C4
//
// cell    edge    competingCell competingEdge  result
// C0      Left    C1            Left           true
// C0      Left    C2            Left           false
// C0      Bottom  C2            Top            true
// C0      Bottom  C4            Left           INVALID
static inline bool sharesAxis(const QTextTableCell &cell, QCss::Edge edge,
                              const QTextTableCell &competingCell, QCss::Edge competingCellEdge)
{
    Q_ASSERT(isVerticalAxis(edge) == isVerticalAxis(competingCellEdge));

    switch (edge) {
    case QCss::TopEdge:
        return cell.row() ==
                competingCell.row() + (competingCellEdge == QCss::BottomEdge ? competingCell.rowSpan() : 0);
    case QCss::BottomEdge:
        return cell.row() + cell.rowSpan() ==
                competingCell.row() + (competingCellEdge == QCss::TopEdge ? 0 : competingCell.rowSpan());
    case QCss::LeftEdge:
        return cell.column() ==
                competingCell.column() + (competingCellEdge == QCss::RightEdge ? competingCell.columnSpan() : 0);
    case QCss::RightEdge:
        return cell.column() + cell.columnSpan() ==
                competingCell.column() + (competingCellEdge == QCss::LeftEdge ? 0 : competingCell.columnSpan());
    default:
        Q_UNREACHABLE();
        return false;
    }
}

// returns the applicable EdgeData for the given cell and edge.
// this is either set explicitly by the cell's format, an activated grid
// or the general table border width for outermost edges.
static inline EdgeData cellEdgeData(QTextTable *table, const QTextTableData *td,
                                    const QTextTableCell &cell, QCss::Edge edge)
{
    if (!cell.isValid()) {
        // e.g. non-existing adjacent cell
        return EdgeData();
    }

    QTextTableCellFormat f = cell.format().toTableCellFormat();
    if (f.hasProperty(borderStylePropertyForEdge(edge))) {
        // border style is set
        double width = 3; // default to 3 like browsers do
        if (f.hasProperty(borderPropertyForEdge(edge)))
            width = f.property(borderPropertyForEdge(edge)).toDouble();
        return EdgeData(width, cell, edge, EdgeData::ClassExplicit);
    } else if (td->drawGrid) {
        const bool outermost =
                (edge == QCss::LeftEdge && cell.column() == 0) ||
                (edge == QCss::TopEdge && cell.row() == 0) ||
                (edge == QCss::RightEdge && cell.column() + cell.columnSpan() >= table->columns()) ||
                (edge == QCss::BottomEdge && cell.row() + cell.rowSpan() >= table->rows());

        if (outermost) {
            qreal border = table->format().border();
            if (border > 1.0) {
                // table border
                return EdgeData(border, cell, edge, EdgeData::ClassTableBorder);
            }
        }
        // 1px clean grid
        return EdgeData(1.0, cell, edge, EdgeData::ClassGrid);
    }
    else {
        return EdgeData(0, cell, edge, EdgeData::ClassNone);
    }
}

// returns the EdgeData with the larger width of either the cell's edge its adjacent cell's edge
static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td,
                                    const QTextTableCell &cell, QCss::Edge edge)
{
    Q_ASSERT(cell.isValid());

    EdgeData result = cellEdgeData(table, td, cell, edge);
    if (!td->borderCollapse)
        return result;

    QTextTableCell ac = adjacentCell(table, cell, edge);
    result = qMax(result, cellEdgeData(table, td, ac, adjacentEdge(edge)));

    bool mustCheckThirdCell = false;
    if (ac.isValid()) {
        /* if C0 and C3 don't share the left/top axis, we must
         * also check C1.
         *
         * C0 and C4 don't share the left axis so we have
         * to take the top edge of C1 (T1) into account
         * because this might be wider than C0's bottom
         * edge (B0). For the sake of simplicity we skip
         * checking T2 and T3.
         *
         * | C0
         * |-----|-----|----|-----
         * | C1  | C2  | C3 | C4
         *
         * width(T4) = max(T4, B0, T1) (T2 and T3 won't be checked)
         */
        switch (edge) {
        case QCss::TopEdge:
        case QCss::BottomEdge:
            mustCheckThirdCell = !sharesAxis(cell, QCss::LeftEdge, ac, QCss::LeftEdge);
            break;
        case QCss::LeftEdge:
        case QCss::RightEdge:
            mustCheckThirdCell = !sharesAxis(cell, QCss::TopEdge, ac, QCss::TopEdge);
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }

    if (mustCheckThirdCell)
        result = qMax(result, cellEdgeData(table, td, adjacentCell(table, ac, adjacentEdge(edge)), edge));

    return result;
}

// checks an edge's joined competing edge according to priority rules and
// adjusts maxCompetingEdgeData and maxOrthogonalEdgeData
static inline void checkJoinedEdge(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
                                   QCss::Edge competingEdge,
                                   const EdgeData &edgeData,
                                   bool couldHaveContinuation,
                                   EdgeData *maxCompetingEdgeData,
                                   EdgeData *maxOrthogonalEdgeData)
{
    EdgeData competingEdgeData = axisEdgeData(table, td, cell, competingEdge);

    if (competingEdgeData > edgeData) {
        *maxCompetingEdgeData = competingEdgeData;
    } else if (competingEdgeData.width == edgeData.width) {
        if ((isSameAxis(edgeData.edge, competingEdge) && couldHaveContinuation)
                || (!isVerticalAxis(edgeData.edge) && isVerticalAxis(competingEdge)) /* both widths are equal, vertical edge has priority */ ) {
            *maxCompetingEdgeData = competingEdgeData;
        }
    }

    if (maxOrthogonalEdgeData && competingEdgeData.width > maxOrthogonalEdgeData->width)
        *maxOrthogonalEdgeData = competingEdgeData;
}

// the offset to make adjacent edges overlap in border collapse mode
static inline qreal collapseOffset(const QTextDocumentLayoutPrivate *p, const EdgeData &w)
{
    return p->scaleToDevice(w.width) / 2.0;
}

// returns the offset that must be applied to the edge's
// anchor (start point or end point) to avoid overlapping edges.
//
// Example 1:
//       2
//       2
// 11111144444444      4 = top edge of cell, 4 pixels width
//       3             3 = right edge of cell, 3 pixels width
//       3 cell 4
//
// cell 4's top border is the widest border and will be
// drawn with horiz. offset = -3/2 whereas its left border
// of width 3 will be drawn with vert. offset = +4/2.
//
// Example 2:
//       2
//       2
// 11111143333333
//       4
//       4 cell 4
//
// cell 4's left border is the widest and will be drawn
// with vert. offset = -3/2 whereas its top border
// of of width 3 will be drawn with hor. offset = +4/2.
//
// couldHaveContinuation: true for "end" anchor of an edge:
//      C
// AAAAABBBBBB
//      D
// width(A) == width(B) we consider B to be a continuation of A, so that B wins
// and will be painted. A would only be painted including the right anchor if
// there was no edge B (due to a rowspan or the axis C-D being the table's right
// border).
//
// ignoreEdgesAbove: true if an egde (left, right or top) for the first row
// after a table page break should be painted. In this case the edges of the
// row above must be ignored.
static inline double prioritizedEdgeAnchorOffset(const QTextDocumentLayoutPrivate *p,
                                                 QTextTable *table, const QTextTableData *td,
                                                 const QTextTableCell &cell,
                                                 const EdgeData &edgeData,
                                                 QCss::Edge orthogonalEdge,
                                                 bool couldHaveContinuation,
                                                 bool ignoreEdgesAbove)
{
    EdgeData maxCompetingEdgeData;
    EdgeData maxOrthogonalEdgeData;
    QTextTableCell competingCell;

    // reference scenario for the inline comments:
    // - edgeData being the top "T0" edge of C0
    // - right anchor is '+', orthogonal edge is "R0"
    //   B C3 R|L C2 B
    //   ------+------
    //   T C0 R|L C1 T

    // C0: T0/B3
    // this is "edgeData"

    // C0: R0/L1
    checkJoinedEdge(table, td, cell, orthogonalEdge, edgeData, false,
                    &maxCompetingEdgeData, &maxOrthogonalEdgeData);

    if (td->borderCollapse) {
        // C1: T1/B2
        if (!isVerticalAxis(edgeData.edge) || !ignoreEdgesAbove) {
            competingCell = adjacentCell(table, cell, orthogonalEdge);
            if (competingCell.isValid()) {
                checkJoinedEdge(table, td, competingCell, edgeData.edge, edgeData, couldHaveContinuation,
                                &maxCompetingEdgeData, nullptr);
            }
        }

        // C3: R3/L2
        if (edgeData.edge != QCss::TopEdge || !ignoreEdgesAbove) {
            competingCell = adjacentCell(table, cell, edgeData.edge);
            if (competingCell.isValid() && sharesAxis(cell, orthogonalEdge, competingCell, orthogonalEdge)) {
                checkJoinedEdge(table, td, competingCell, orthogonalEdge, edgeData, false,
                                &maxCompetingEdgeData, &maxOrthogonalEdgeData);
            }
        }
    }

    // wider edge has priority
    bool hasPriority = edgeData > maxCompetingEdgeData;

    if (td->borderCollapse) {
        qreal offset = collapseOffset(p, maxOrthogonalEdgeData);
        return hasPriority ? -offset : offset;
    }
    else
        return hasPriority ? 0 : p->scaleToDevice(maxOrthogonalEdgeData.width);
}

// draw one edge of the given cell
//
// these options are for pagination / pagebreak handling:
//
// forceHeaderRow: true for all rows directly below a (repeated) header row.
//  if the table has headers the first row after a page break must check against
//  the last table header's row, not its actual predecessor.
//
// adjustTopAnchor: false for rows that are a continuation of a row after a page break
//   only evaluated for left/right edges
//
// adjustBottomAnchor: false for rows that will continue after a page break
//   only evaluated for left/right edges
//
// ignoreEdgesAbove: true if a row starts on top of the page and the
//   bottom edges of the prior row can therefore be ignored.
static inline
void drawCellBorder(const QTextDocumentLayoutPrivate *p, QPainter *painter,
                    QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
                    const QRectF &borderRect, QCss::Edge edge,
                    int forceHeaderRow, bool adjustTopAnchor, bool adjustBottomAnchor,
                    bool ignoreEdgesAbove)
{
    QPointF p1, p2;
    qreal wh = 0;
    qreal wv = 0;
    EdgeData edgeData = axisEdgeData(table, td, cell, edge);

    if (edgeData.width == 0)
        return;

    QTextTableCellFormat fmt = edgeData.cell.format().toTableCellFormat();
    QTextFrameFormat::BorderStyle borderStyle = QTextFrameFormat::BorderStyle_None;
    QBrush brush;

    if (edgeData.edgeClass != EdgeData::ClassExplicit && td->drawGrid) {
        borderStyle = QTextFrameFormat::BorderStyle_Solid;
        brush = table->format().borderBrush();
    }
    else {
        switch (edgeData.edge) {
        case QCss::TopEdge:
            brush = fmt.topBorderBrush();
            borderStyle = fmt.topBorderStyle();
            break;
        case QCss::BottomEdge:
            brush = fmt.bottomBorderBrush();
            borderStyle = fmt.bottomBorderStyle();
            break;
        case QCss::LeftEdge:
            brush = fmt.leftBorderBrush();
            borderStyle = fmt.leftBorderStyle();
            break;
        case QCss::RightEdge:
            brush = fmt.rightBorderBrush();
            borderStyle = fmt.rightBorderStyle();
            break;
        default:
            Q_UNREACHABLE();
            break;
        }
    }

    if (borderStyle == QTextFrameFormat::BorderStyle_None)
        return;

    // assume black if not explicit brush is set
    if (brush.style() == Qt::NoBrush)
        brush = Qt::black;

    QTextTableCell cellOrHeader = cell;
    if (forceHeaderRow != -1)
        cellOrHeader = table->cellAt(forceHeaderRow, cell.column());

    // adjust start and end anchors (e.g. left/right for top) according to priority rules
    switch (edge) {
    case QCss::TopEdge:
        wv = p->scaleToDevice(edgeData.width);
        p1 = borderRect.topLeft()
                + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, ignoreEdgesAbove)), 0);
        p2 = borderRect.topRight()
                + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, ignoreEdgesAbove)), 0);
        break;
    case QCss::BottomEdge:
        wv = p->scaleToDevice(edgeData.width);
        p1 = borderRect.bottomLeft()
                + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, false)), -wv);
        p2 = borderRect.bottomRight()
                + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, false)), -wv);
        break;
    case QCss::LeftEdge:
        wh = p->scaleToDevice(edgeData.width);
        p1 = borderRect.topLeft()
                + QPointF(0, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
                                                                                  forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
                                                                                  false, ignoreEdgesAbove))
                                             : 0);
        p2 = borderRect.bottomLeft()
                + QPointF(0, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
                                                : 0);
        break;
    case QCss::RightEdge:
        wh = p->scaleToDevice(edgeData.width);
        p1 = borderRect.topRight()
                + QPointF(-wh, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
                                                                                    forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
                                                                                    false, ignoreEdgesAbove))
                                               : 0);
        p2 = borderRect.bottomRight()
                + QPointF(-wh, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
                                                  : 0);
        break;
    default: break;
    }

    // for borderCollapse move edge width/2 pixel out of the borderRect
    // so that it shares space with the adjacent cell's edge.
    // to avoid fractional offsets, qCeil/qFloor is used
    if (td->borderCollapse) {
        QPointF offset;
        switch (edge) {
        case QCss::TopEdge:
            offset = QPointF(0, -qCeil(collapseOffset(p, edgeData)));
            break;
        case QCss::BottomEdge:
            offset = QPointF(0, qFloor(collapseOffset(p, edgeData)));
            break;
        case QCss::LeftEdge:
            offset = QPointF(-qCeil(collapseOffset(p, edgeData)), 0);
            break;
        case QCss::RightEdge:
            offset = QPointF(qFloor(collapseOffset(p, edgeData)), 0);
            break;
        default: break;
        }
        p1 += offset;
        p2 += offset;
    }

    QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(borderStyle + 1);

// this reveals errors in the drawing logic
#ifdef COLLAPSE_DEBUG
    QColor c = brush.color();
    c.setAlpha(150);
    brush.setColor(c);
#endif

    qDrawEdge(painter, p1.x(), p1.y(), p2.x() + wh, p2.y() + wv, 0, 0, edge, cssStyle, brush);
}
#endif

void QTextDocumentLayoutPrivate::drawTableCellBorder(const QRectF &cellRect, QPainter *painter,
                                                     QTextTable *table, QTextTableData *td,
                                                     const QTextTableCell &cell) const
{
#ifndef QT_NO_CSSPARSER
    qreal topMarginAfterPageBreak = (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 (headerRowCount > 0 && cell.row() >= headerRowCount)
        topMarginAfterPageBreak += td->headerHeight.toReal();

    BorderPaginator paginator(document, cellRect, topMarginAfterPageBreak, bottomMargin, 0);

    bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
    painter->setRenderHint(QPainter::Antialiasing);

    // paint cell borders for every page the cell appears on
    for (int page = paginator.topPage; page <= paginator.bottomPage; ++page) {
        const QRectF clipped = paginator.clipRect(page);
        if (!clipped.isValid())
            continue;

        const qreal offset = cellRect.top() - td->rowPositions.at(cell.row()).toReal();
        const int lastHeaderRow = table->format().headerRowCount() - 1;
        const bool tableHasHeader = table->format().headerRowCount() > 0;
        const bool isHeaderRow = cell.row() < table->format().headerRowCount();
        const bool isFirstRow = cell.row() == lastHeaderRow + 1;
        const bool isLastRow = cell.row() + cell.rowSpan() >= table->rows();
        const bool previousRowOnPreviousPage = !isFirstRow
                && !isHeaderRow
                && BorderPaginator(document,
                                   td->cellRect(adjacentCell(table, cell, QCss::TopEdge)).translated(0, offset),
                                   topMarginAfterPageBreak,
                                   bottomMargin,
                                   0).bottomPage < page;
        const bool nextRowOnNextPage = !isLastRow
                && BorderPaginator(document,
                                   td->cellRect(adjacentCell(table, cell, QCss::BottomEdge)).translated(0, offset),
                                   topMarginAfterPageBreak,
                                   bottomMargin,
                                   0).topPage > page;
        const bool rowStartsOnPage = page == paginator.topPage;
        const bool rowEndsOnPage = page == paginator.bottomPage;
        const bool rowStartsOnPageTop = !tableHasHeader
                && rowStartsOnPage
                && previousRowOnPreviousPage;
        const bool rowStartsOnPageBelowHeader = tableHasHeader
                && rowStartsOnPage
                && previousRowOnPreviousPage;

        const bool suppressTopBorder = td->borderCollapse
                ? !isHeaderRow && (!rowStartsOnPage || rowStartsOnPageBelowHeader)
                : !rowStartsOnPage;
        const bool suppressBottomBorder = td->borderCollapse
                ? !isHeaderRow && (!rowEndsOnPage || nextRowOnNextPage)
                : !rowEndsOnPage;
        const bool doNotAdjustTopAnchor = td->borderCollapse
                ? !tableHasHeader && !rowStartsOnPage
                : !rowStartsOnPage;
        const bool doNotAdjustBottomAnchor = suppressBottomBorder;

        if (!suppressTopBorder) {
            drawCellBorder(this, painter, table, td, cell, clipped, QCss::TopEdge,
                           -1, true, true, rowStartsOnPageTop);
        }

        drawCellBorder(this, painter, table, td, cell, clipped, QCss::LeftEdge,
                       suppressTopBorder ? lastHeaderRow : -1,
                       !doNotAdjustTopAnchor,
                       !doNotAdjustBottomAnchor,
                       rowStartsOnPageTop);
        drawCellBorder(this, painter, table, td, cell, clipped, QCss::RightEdge,
                       suppressTopBorder ? lastHeaderRow : -1,
                       !doNotAdjustTopAnchor,
                       !doNotAdjustBottomAnchor,
                       rowStartsOnPageTop);

        if (!suppressBottomBorder) {
            drawCellBorder(this, painter, table, td, cell, clipped, QCss::BottomEdge,
                           -1, true, true, false);
        }
    }

    if (turn_off_antialiasing)
        painter->setRenderHint(QPainter::Antialiasing, false);
#else
    Q_UNUSED(cell);
    Q_UNUSED(cellRect);
    Q_UNUSED(painter);
    Q_UNUSED(table);
    Q_UNUSED(td);
    Q_UNUSED(cell);
#endif
}

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;
    }

    const QFixed leftPadding = td->leftPadding(table, cell);
    const QFixed topPadding = td->topPadding(table, cell);

    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();

    if (!td->borderCollapse && 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;
        }

        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) {
        const qreal pageHeight = document->pageSize().height();
        const int topPage = pageHeight > 0 ? static_cast<int>(cellRect.top() / pageHeight) : 0;
        const int bottomPage = pageHeight > 0 ? static_cast<int>((cellRect.bottom()) / pageHeight) : 0;

        if (topPage == bottomPage)
            fillBackground(painter, cellRect, bg, cellRect.topLeft());
        else {
            for (int i = topPage; i <= bottomPage; ++i) {
                QRectF clipped = cellRect.toRect();

                if (topPage != bottomPage) {
                    const qreal top = qMax(i * pageHeight + topMargin, cell_context.clip.top());
                    const qreal bottom = qMin((i + 1) * pageHeight - bottomMargin, cell_context.clip.bottom());

                    clipped.setTop(qMax(clipped.top(), top));
                    clipped.setBottom(qMin(clipped.bottom(), bottom));

                    if (clipped.bottom() <= clipped.top())
                        continue;

                    fillBackground(painter, clipped, bg, cellRect.topLeft());
                }
            }
        }

        if (bg.style() > Qt::SolidPattern)
            painter->setBrushOrigin(cellRect.topLeft());
    }

    // paint over the background - otherwise we would have to adjust the background paint cellRect for the border values
    drawTableCellBorder(cellRect, painter, table, td, cell);

    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() == nullptr);

    QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
    if (inRootFrame && context.clip.isValid()) {
        lastVisibleCheckPoint = std::lower_bound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
    }

    QTextBlock previousBlock;
    QTextFrame *previousFrame = nullptr;

    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.constLast().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,
                                           const QTextBlock &bl, bool inRootFrame) const
{
    const QTextLayout *tl = bl.layout();
    QRectF r = tl->boundingRect();
    r.translate(offset + tl->position());
    if (!bl.isVisible() || (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom())))
        return;
    qCDebug(lcDraw) << "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 = nullptr;
    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 the block is empty and it precedes a table, do not draw the cursor.
    // the cursor is drawn later after the table has been drawn so no need
    // to draw it here.
    if (!isEmptyBlockBeforeTable(frameIteratorForTextPosition(blpos))
        && ((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,
                                              const QTextBlock &bl, const QTextCharFormat *selectionFormat) const
{
    Q_Q(const QTextDocumentLayout);
    const QTextBlockFormat blockFormat = bl.blockFormat();
    const QTextCharFormat charFormat = 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.horizontalAdvance(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.horizontalAdvance(QLatin1Char(' '));
    if (dir == Qt::LeftToRight)
        xoff = -xoff - size.width();
    r.translate( xoff, (fontMetrics.height() / 2) - (size.height() / 2));

    painter->save();

    painter->setRenderHint(QPainter::Antialiasing);

    const bool marker = bl.blockFormat().marker() != QTextBlockFormat::MarkerType::NoMarker;
    if (selectionFormat) {
        painter->setPen(QPen(selectionFormat->foreground(), 0));
        if (!marker)
            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);

    if (marker) {
        int adj = fontMetrics.lineSpacing() / 6;
        r.adjust(-adj, 0, -adj, 0);
        const QRectF outer = r.adjusted(-adj, -adj, adj, adj);
        if (selectionFormat)
            painter->fillRect(outer, selectionFormat->background());
        if (bl.blockFormat().marker() == QTextBlockFormat::MarkerType::Checked) {
            // ### Qt7: render with QStyle / PE_IndicatorCheckBox. We don't currently
            // have access to that here, because it would be a widget dependency.
            painter->setPen(QPen(painter->pen().color(), 2));
            painter->drawLine(r.topLeft(), r.bottomRight());
            painter->drawLine(r.topRight(), r.bottomLeft());
            painter->setPen(QPen(painter->pen().color(), 0));
        }
        painter->drawRect(outer);
    }

    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:
        if (!marker)
            painter->fillRect(r, brush);
        break;
    case QTextListFormat::ListCircle:
        if (!marker) {
            painter->setPen(QPen(brush, 0));
            painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
        }
        break;
    case QTextListFormat::ListDisc:
        if (!marker) {
            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)
{
    qCDebug(lcTable) << "layoutCell";
    QTextLayoutStruct layoutStruct;
    layoutStruct.frame = t;
    layoutStruct.minimumWidth = 0;
    layoutStruct.maximumWidth = QFIXED_MAX;
    layoutStruct.y = 0;

    const QFixed topPadding = td->topPadding(t, cell);
    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
            + td->paddingProperty(cell.format(), QTextFormat::TableCellTopPadding); // top cell-border is not repeated

#ifndef QT_NO_CSSPARSER
    const int headerRowCount = t->format().headerRowCount();
    if (td->borderCollapse && headerRowCount > 0) {
        // consider the header row's bottom edge width
        qreal headerRowBottomBorderWidth = axisEdgeData(t, td, t->cellAt(headerRowCount - 1, cell.column()), QCss::BottomEdge).width;
        layoutStruct.pageTopMargin += QFixed::fromReal(scaleToDevice(headerRowBottomBorderWidth) / 2);
    }
#endif

    layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->effectiveBottomBorder + td->bottomPadding(t, cell);
    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 maximum/minimumWidth by the minimum width of the fixed size floats,
    // to keep them visible
    layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
    layoutStruct.minimumWidth = qMax(layoutStruct.minimumWidth, 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;
}

#ifndef QT_NO_CSSPARSER
static inline void findWidestOutermostBorder(QTextTable *table, QTextTableData *td,
                                             const QTextTableCell &cell, QCss::Edge edge,
                                             qreal *outerBorders)
{
    EdgeData w = cellEdgeData(table, td, cell, edge);
    if (w.width > outerBorders[edge])
        outerBorders[edge] = w.width;
}
#endif

QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
{
    qCDebug(lcTable) << "layoutTable from" << layoutFrom << "to" << layoutTo << "parentY" << parentY;
    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.insert(cell.row() + cell.column() * rows, frame);
        }
    }

    QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
    if (columnWidthConstraints.size() != columns)
        columnWidthConstraints.resize(columns);
    Q_ASSERT(columnWidthConstraints.count() == columns);

    // borderCollapse will disable drawing the html4 style table cell borders
    // and draw a 1px grid instead. This also sets a fixed cellspacing
    // of 1px if border > 0 (for the grid) and ignore any explicitly set
    // cellspacing.
    td->borderCollapse = fmt.borderCollapse();
    td->borderCell = td->borderCollapse ? 0 : td->border;
    const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(td->borderCollapse ? 0 : fmt.cellSpacing())).round();

    td->drawGrid = (td->borderCollapse && fmt.border() >= 1);

    td->effectiveTopBorder = td->effectiveBottomBorder = td->effectiveLeftBorder = td->effectiveRightBorder = td->border;

#ifndef QT_NO_CSSPARSER
    if (td->borderCollapse) {
        // find the widest borders of the outermost cells
        qreal outerBorders[QCss::NumEdges];
        for (int i = 0; i < QCss::NumEdges; ++i)
            outerBorders[i] = 0;

        for (int r = 0; r < rows; ++r) {
            if (r == 0) {
                for (int c = 0; c < columns; ++c)
                    findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::TopEdge, outerBorders);
            }
            if (r == rows - 1) {
                for (int c = 0; c < columns; ++c)
                    findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::BottomEdge, outerBorders);
            }
            findWidestOutermostBorder(table, td, table->cellAt(r, 0), QCss::LeftEdge, outerBorders);
            findWidestOutermostBorder(table, td, table->cellAt(r, columns - 1), QCss::RightEdge, outerBorders);
        }
        td->effectiveTopBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::TopEdge] / 2)).round();
        td->effectiveBottomBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::BottomEdge] / 2)).round();
        td->effectiveLeftBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::LeftEdge] / 2)).round();
        td->effectiveRightBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::RightEdge] / 2)).round();
    }
#endif

    td->deviceScale = scaleToDevice(qreal(1));
    td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
    const QFixed leftMargin = td->leftMargin + td->padding + td->effectiveLeftBorder;
    const QFixed rightMargin = td->rightMargin + td->padding + td->effectiveRightBorder;
    const QFixed topMargin = td->topMargin + td->padding + td->effectiveTopBorder;

    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->borderCell;
    // inter-cell spacing
    remainingWidth -= (columns - 1) * cellSpacing;
    // cell spacing at the left and right hand side
    remainingWidth -= 2 * cellSpacing;

    if (td->borderCollapse) {
        remainingWidth -= td->effectiveLeftBorder;
        remainingWidth -= td->effectiveRightBorder;
    }

    // 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 QFixed leftPadding = td->leftPadding(table, cell);
            const QFixed rightPadding = td->rightPadding(table, cell);
            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);
                // ceil to avoid going below minWidth when rounding all column widths later
                td->minWidths[col] = qMax(td->minWidths.at(col), w).ceil();
                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;

            // for variable columns the maxWidth will later be considered as the
            // column width (column width = content width). We must avoid that the
            // pixel-alignment rounding step floors this value and thus the text
            // rendering later erroneously wraps the content.
            maxW = maxW.ceil();

            widthToDistribute = maxW;
            for (int n = 0; n < cspan; ++n) {
                const int col = i + n;
                QFixed w = widthToDistribute / (cspan - n);
                if (td->maxWidths[col] != QFIXED_MAX)
                    w = qMax(td->maxWidths[col], w);
                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);
            qCDebug(lcTable) << "column" << i << "has width constraint" << td->minWidths.at(i) << "px, remaining width now" << remainingWidth;
        } 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);
            qCDebug(lcTable) << "column" << i << "has variable width, min" << td->minWidths.at(i) << "remaining width now" << remainingWidth;
        }
        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);
                }
                qCDebug(lcTable) << "column" << i << "has width constraint" << columnWidthConstraints.at(i).rawValue()
                                 << "%, allocated width" << td->widths[i] << "remaining width now" << remainingWidth;
                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;
            }
        }
    }

    // in order to get a correct border rendering we must ensure that the distance between
    // two cells is exactly 2 * td->cellBorder pixel. we do this by rounding the calculated width
    // values here.
    // to minimize the total rounding error we propagate the rounding error for each width
    // to its successor.
    QFixed error = 0;
    for (int i = 0; i < columns; ++i) {
        QFixed orig = td->widths[i];
        td->widths[i] = (td->widths[i] - error).round();
        error = td->widths[i] - orig;
    }

    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->borderCell + cellSpacing;

    // - margin to compensate the + margin in columnPositions[0]
    const QFixed contentsWidth = td->columnPositions.constLast() + td->widths.constLast() + 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.at(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.at(r) > pageBottom)
            td->rowPositions[r] = nextPageTop;
        else if (td->rowPositions.at(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.at(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).round();
                    continue;
                }
            }

            const QFixed topPadding = td->topPadding(table, cell);
            const QFixed bottomPadding = td->bottomPadding(table, cell);
            const QFixed leftPadding = td->leftPadding(table, cell);
            const QFixed rightPadding = td->rightPadding(table, cell);
            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).round();

            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.at(r);
            td->rowPositions[r] = nextPageTop;
            td->heights[r] = 0;
            dropRowToNextPage = false;
            cellHeights.resize(cellCountBeforeRow);
            if (r > headerRowCount)
                td->heights[r - 1] = pageBottom - td->rowPositions.at(r - 1);
            goto relayout;
        }

        if (haveRowSpannedCells) {
            const QFixed effectiveHeight = td->heights.at(r) + td->borderCell + cellSpacing + td->borderCell;
            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.at(r) + td->heights.at(r) - td->rowPositions.at(0) + td->cellSpacing + 2 * td->borderCell;
            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(table, cell) + td->bottomPadding(table, cell);

            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->borderCell + 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->borderCell + cellSpacing;
        qCDebug(lcTable) << "column" << i << "has final width" << td->widths.at(i).toReal()
                         << "min" << td->minWidths.at(i).toReal() << "max" << td->maxWidths.at(i).toReal();
    }
    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)
{
    qCDebug(lcLayout, "layoutFrame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
    Q_ASSERT(data(f)->sizeDirty);

    QTextFrameFormat fformat = f->frameFormat();

    QTextFrame *parent = f->parentFrame();
    const QTextFrameData *pd = parent ? data(parent) : nullptr;

    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)
{
    qCDebug(lcLayout, "layoutFrame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
    Q_ASSERT(data(f)->sizeDirty);

    QTextFrameData *fd = data(f);
    QFixed newContentsWidth;

    bool fullLayout = false;
    {
        QTextFrameFormat fformat = f->frameFormat();
        // set sizes of this frame from the format
        QFixed tm = QFixed::fromReal(scaleToDevice(fformat.topMargin())).round();
        if (tm != fd->topMargin) {
            fd->topMargin = tm;
            fullLayout = true;
        }
        QFixed bm = QFixed::fromReal(scaleToDevice(fformat.bottomMargin())).round();
        if (bm != fd->bottomMargin) {
            fd->bottomMargin = bm;
            fullLayout = true;
        }
        fd->leftMargin = QFixed::fromReal(scaleToDevice(fformat.leftMargin())).round();
        fd->rightMargin = QFixed::fromReal(scaleToDevice(fformat.rightMargin())).round();
        QFixed b = QFixed::fromReal(scaleToDevice(fformat.border())).round();
        if (b != fd->border) {
            fd->border = b;
            fullLayout = true;
        }
        QFixed p = QFixed::fromReal(scaleToDevice(fformat.padding())).round();
        if (p != fd->padding) {
            fd->padding = p;
            fullLayout = true;
        }

        QTextFrame *parent = f->parentFrame();
        const QTextFrameData *pd = parent ? data(parent) : nullptr;

        // 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 = fullLayout || (fd->oldContentsWidth != newContentsWidth);
    layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
    qCDebug(lcLayout) << "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)
{
    qCDebug(lcLayout) << "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 = std::lower_bound(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.constLast().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() && previousIt != it)
                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 = nullptr;
            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 = layout->lineCount() > 0 ? QFixed::fromReal(layout->lineAt(0).height()) : QFixed();

                    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);
                    }

                    if (layout->lineCount() > 0) {
                        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.constLast().positionInFrame;
            // #######
            //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
        }
    }


    fd->currentLayoutStruct = nullptr;
}

static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling,
                                       QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight, QFixed *lineBottom)
{
    qreal rawHeight = qCeil(line.ascent() + line.descent() + line.leading());
    *lineHeight = QFixed::fromReal(blockFormat.lineHeight(rawHeight, scaling));
    *lineBottom = QFixed::fromReal(blockFormat.lineHeight(line.height(), scaling));

    if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight || blockFormat.lineHeightType() == QTextBlockFormat::MinimumHeight) {
        *lineBreakHeight = *lineBottom;
        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);
    if (!bl.isVisible())
        return;

    QTextLayout *tl = bl.layout();
    const int blockLength = bl.length();

    qCDebug(lcLayout) << "layoutBlock from=" << layoutFrom << "to=" << layoutTo
                      << "; 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.horizontalAdvance(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)) {

        qCDebug(lcLayout) << "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(QGuiApplicationPrivate::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;
        QFixed bottom;

        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, lineBottom;
            qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
                            qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
            getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight, &lineBottom);

            while (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom &&
                layoutStruct->contentHeight() >= lineBreakHeight) {
                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()));
            bottom = layoutStruct->y + lineBottom;
            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();
        }
        layoutStruct->y = qMax(layoutStruct->y, bottom);
        tl->endLayout();
    } else {
        const int cnt = tl->lineCount();
        QFixed bottom;
        for (int i = 0; i < cnt; ++i) {
            qCDebug(lcLayout) << "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, lineBottom;
            qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ?
                            qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1;
            getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight, &lineBottom);

            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()));
            }
            bottom = layoutStruct->y + lineBottom;
            layoutStruct->y += lineHeight;
        }
        layoutStruct->y = qMax(layoutStruct->y, bottom);
        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);
    // float got removed in editing operation
    QTextFrame *null = nullptr; // work-around for (at least) MSVC 2012 emitting
                                // warning C4100 for its own header <algorithm>
                                // when passing nullptr directly to std::remove
    fd->floats.erase(std::remove(fd->floats.begin(), fd->floats.end(), null),
                     fd->floats.end());

    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 = nullptr;
    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) {
        QFontMetrics m(f.font());
        qreal halfX = m.xHeight()/2.;
        item.setAscent((inlineSize.height() + halfX) / 2.);
        item.setDescent((inlineSize.height() - halfX) / 2.);
    } else {
        item.setDescent(0);
        item.setAscent(inlineSize.height());
    }
}

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 : nullptr);
}

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();
    Q_UNUSED(oldSize);

    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::tableCellBoundingRect(QTextTable *table, const QTextTableCell &cell) const
{
    if (!cell.isValid())
        return QRectF();

    QTextTableData *td = static_cast<QTextTableData *>(data(table));

    QRectF tableRect = tableBoundingRect(table);
    QRectF cellRect = td->cellRect(cell);

    return cellRect.translated(tableRect.topLeft());
}

QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
{
    Q_D(const QTextDocumentLayout);
    if (d->docPrivate->pageSize.isNull())
        return QRectF();
    d->ensureLayoutFinished();

    QPointF pos;
    const int framePos = table->firstPosition();
    QTextFrame *f = table;
    while (f) {
        QTextFrameData *fd = data(f);
        pos += fd->position.toPointF();

        if (f != table) {
            if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
                QTextTableCell cell = table->cellAt(framePos);
                if (cell.isValid())
                    pos += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
            }
        }

        f = f->parentFrame();
    }
    return QRectF(pos, data(table)->size.toSizeF());
}

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(table, 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() || !block.isVisible())
        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(table, 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"
