QTextDocument: add setLayoutEnabled()

This allows to set up everything first - without paying for the layout
calculation at every step - and only then trigger the layout once.

Results:
 0.065 msecs to create a QGraphicsTextItem with some text (layouted)
 0.036 msecs to set everything up in a QGraphicsTextItem with 0 width

Change-Id: I138bd1d58941d029bc0a36d2730216778f1fbd97
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
David Faure 2022-05-25 22:07:36 +02:00
parent 2996ca031e
commit 09c7457f4a
6 changed files with 85 additions and 4 deletions

View File

@ -634,6 +634,40 @@ bool QTextDocument::useDesignMetrics() const
return d->defaultTextOption.useDesignMetrics();
}
/*!
\property QTextDocument::layoutEnabled
\since 6.4
\brief whether QTextDocument should recalculate the layout after every change
If this property is set to true, any change to the document triggers a layout,
which makes everything work as expected but takes time.
Temporarily disabling the layout can save time when making multiple changes
(not just text content, but also default font, default text option....)
so that the document is only laid out once at the end. This can be useful when
the text width or page size isn't yet known, for instance.
By default, this property is \c true.
\sa setTextWidth
*/
void QTextDocument::setLayoutEnabled(bool b)
{
Q_D(QTextDocument);
if (d->layoutEnabled == b)
return;
d->layoutEnabled = b;
if (b && d->lout)
d->lout->documentChanged(0, 0, d->length());
}
bool QTextDocument::isLayoutEnabled() const
{
Q_D(const QTextDocument);
return d->layoutEnabled;
}
/*!
\since 4.2

View File

@ -62,6 +62,7 @@ class Q_GUI_EXPORT QTextDocument : public QObject
Q_PROPERTY(QSizeF pageSize READ pageSize WRITE setPageSize)
Q_PROPERTY(QFont defaultFont READ defaultFont WRITE setDefaultFont)
Q_PROPERTY(bool useDesignMetrics READ useDesignMetrics WRITE setUseDesignMetrics)
Q_PROPERTY(bool layoutEnabled READ isLayoutEnabled WRITE setLayoutEnabled)
Q_PROPERTY(QSizeF size READ size)
Q_PROPERTY(qreal textWidth READ textWidth WRITE setTextWidth)
Q_PROPERTY(int blockCount READ blockCount)
@ -218,6 +219,9 @@ public:
void setUseDesignMetrics(bool b);
bool useDesignMetrics() const;
void setLayoutEnabled(bool b);
bool isLayoutEnabled() const;
void drawContents(QPainter *painter, const QRectF &rect = QRectF());
void setTextWidth(qreal width);

View File

@ -291,6 +291,8 @@ public:
return get(object->document());
}
bool canLayout() const { return layoutEnabled && !pageSize.isNull(); }
private:
QTextDocumentPrivate(const QTextDocumentPrivate& m);
QTextDocumentPrivate& operator= (const QTextDocumentPrivate& m);
@ -337,6 +339,7 @@ private:
public:
bool inContentsChange;
bool layoutEnabled = true;
QTextOption defaultTextOption;
Qt::CursorMoveStyle defaultCursorMoveStyle;
#ifndef QT_NO_CSSPARSER

View File

@ -3754,7 +3754,7 @@ void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
blockIt.clearLayout();
if (d->docPrivate->pageSize.isNull())
if (!d->docPrivate->canLayout())
return;
QRectF updateRect;
@ -4032,7 +4032,7 @@ QRectF QTextDocumentLayout::tableCellBoundingRect(QTextTable *table, const QText
QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
{
Q_D(const QTextDocumentLayout);
if (d->docPrivate->pageSize.isNull())
if (!d->docPrivate->canLayout())
return QRectF();
d->ensureLayoutFinished();
@ -4059,7 +4059,7 @@ QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
{
Q_D(const QTextDocumentLayout);
if (d->docPrivate->pageSize.isNull())
if (!d->docPrivate->canLayout())
return QRectF();
d->ensureLayoutFinished();
return d->frameBoundingRectInternal(frame);
@ -4088,7 +4088,7 @@ QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame)
QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{
Q_D(const QTextDocumentLayout);
if (d->docPrivate->pageSize.isNull() || !block.isValid() || !block.isVisible())
if (!d->docPrivate->canLayout() || !block.isValid() || !block.isVisible())
return QRectF();
d->ensureLayoutedByPosition(block.position() + block.length());
QTextFrame *frame = d->document->frameAt(block.position());

View File

@ -180,6 +180,8 @@ private slots:
void insertHtmlWithComments_data();
void insertHtmlWithComments();
void delayedLayout();
private:
void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
void buildRegExpData();
@ -3931,5 +3933,24 @@ void tst_QTextDocument::insertHtmlWithComments()
QCOMPARE(blockContent, expectedBlocks);
}
void tst_QTextDocument::delayedLayout()
{
QTextDocument doc;
doc.setHtml("<html>Foobar</html>");
QCOMPARE(doc.blockCount(), 1);
doc.setLayoutEnabled(false);
// Force creation of a layout
QVERIFY(doc.documentLayout());
QTextBlock block = doc.begin();
QTextLayout *layout = block.layout();
QCOMPARE(layout->lineCount(), 0); // layout didn't happen yet
doc.setLayoutEnabled(true);
QCOMPARE(layout->lineCount(), 1); // layout happened
}
QTEST_MAIN(tst_QTextDocument)
#include "tst_qtextdocument.moc"

View File

@ -5,6 +5,7 @@
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QTextDocument>
class tst_QGraphicsItem : public QObject
{
@ -33,6 +34,7 @@ private slots:
void shear();
void translate();
void createTextItem();
void createTextItemNoLayouting();
};
tst_QGraphicsItem::tst_QGraphicsItem()
@ -216,5 +218,22 @@ void tst_QGraphicsItem::createTextItem()
}
}
void tst_QGraphicsItem::createTextItemNoLayouting()
{
// Ensure QFontDatabase loaded the font beforehand
QFontInfo(qApp->font()).family();
const QString text = "This is some text";
QBENCHMARK {
QGraphicsTextItem item;
item.document()->setLayoutEnabled(false);
// Prepare everything
item.setPlainText(text);
QTextOption option = item.document()->defaultTextOption();
option.setAlignment(Qt::AlignHCenter);
item.document()->setDefaultTextOption(option);
// And (in a real app) enable layouting here
}
}
QTEST_MAIN(tst_QGraphicsItem)
#include "tst_qgraphicsitem.moc"