1 /****************************************************************************
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Copyright (C) 2016 Ivailo Monev
6 ** This file is part of the test suite of the Katie Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser
12 ** General Public License version 2.1 as published by the Free Software
13 ** Foundation and appearing in the file LICENSE.LGPL included in the
14 ** packaging of this file. Please review the following information to
15 ** ensure the GNU Lesser General Public License version 2.1 requirements
16 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
20 ****************************************************************************/
22 #include "qstatictext.h"
23 #include "qstatictext_p.h"
24 #include "qtextengine_p.h"
25 #include "qfontengine_p.h"
26 #include "qfontmetrics.h"
27 #include "qabstracttextdocumentlayout.h"
28 #include "qapplication.h"
29 #include "qx11info_x11.h"
35 \brief The QStaticText class enables optimized drawing of text when the text and its layout
43 QStaticText provides a way to cache layout data for a block of text so that it can be drawn
44 more efficiently than by using QPainter::drawText() in which the layout information is
45 recalculated with every call.
47 The class primarily provides an optimization for cases where the text, its font and the
48 transformations on the painter are static over several paint events. If the text or its layout
49 is changed for every iteration, QPainter::drawText() is the more efficient alternative, since
50 the static text's layout would have to be recalculated to take the new state into consideration.
52 Translating the painter will not cause the layout of the text to be recalculated, but will cause
53 a very small performance impact on drawStaticText(). Altering any other parts of the painter's
54 transformation or the painter's font will cause the layout of the static text to be
55 recalculated. This should be avoided as often as possible to maximize the performance
56 benefit of using QStaticText.
58 In addition, only affine transformations are supported by drawStaticText(). Calling
59 drawStaticText() on a projected painter will perform slightly worse than using the regular
60 drawText() call, so this should be avoided.
63 class MyWidget: public QWidget
66 MyWidget(QWidget *parent = nullptr) : QWidget(parent), m_staticText("This is static text")
69 void paintEvent(QPaintEvent *)
71 QPainter painter(this);
72 painter.drawStaticText(0, 0, m_staticText);
76 QStaticText m_staticText;
80 The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific
81 point with no boundaries, and also when QPainter::drawText() is called with a bounding
84 If a bounding rectangle is not required, create a QStaticText object without setting a preferred
85 text width. The text will then occupy a single line.
87 If you set a text width on the QStaticText object, this will bound the text. The text will
88 be formatted so that no line exceeds the given width. The text width set for QStaticText will
89 not automatically be used for clipping. To achieve clipping in addition to line breaks, use
90 QPainter::setClipRect(). The position of the text is decided by the argument passed to
91 QPainter::drawStaticText() and can change from call to call with a minimal impact on
94 For extra convenience, it is possible to apply formatting to the text using the HTML subset
95 supported by QTextDocument. QStaticText will attempt to guess the format of the input text using
96 Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force
97 QStaticText to display its contents as either plain text or rich text, use the function
98 QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText.
100 QStaticText can only represent text, so only HTML tags which alter the layout or appearance of
101 the text will be respected. Adding an image to the input HTML, for instance, will cause the
102 image to be included as part of the layout, affecting the positions of the text glyphs, but it
103 will not be displayed. The result will be an empty area the size of the image in the output.
104 Similarly, using tables will cause the text to be laid out in table format, but the borders
107 If it's the first time the static text is drawn, or if the static text, or the painter's font
108 has been altered since the last time it was drawn, the text's layout has to be
109 recalculated. On some paint engines, changing the matrix of the painter will also cause the
110 layout to be recalculated. In particular, this will happen for any paint engine. Recalculating
111 the layout will impose an overhead on the QPainter::drawStaticText() call where it occurs. To
112 avoid this overhead in the paint event, you can call prepare() ahead of time to ensure that
113 the layout is calculated.
115 \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument
119 Constructs an empty QStaticText
121 QStaticText::QStaticText()
122 : data(new QStaticTextPrivate)
127 Constructs a QStaticText object with the given \a text.
129 QStaticText::QStaticText(const QString &text)
130 : data(new QStaticTextPrivate)
137 Constructs a QStaticText object which is a copy of \a other.
139 QStaticText::QStaticText(const QStaticText &other)
145 Destroys the QStaticText.
147 QStaticText::~QStaticText()
149 Q_ASSERT(!data || data->ref >= 1);
155 void QStaticText::detach()
162 Prepares the QStaticText object for being painted with the given \a matrix and the given \a font
163 to avoid overhead when the actual drawStaticText() call is made.
165 When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part
166 of the QStaticText object has changed since the last time it was drawn. It will also be
167 recalculated if the painter's font is not the same as when the QStaticText was last drawn, or,
168 on any other paint engine, if the painter's matrix has been altered since the static text was
171 To avoid the overhead of creating the layout the first time you draw the QStaticText after
172 making changes, you can use the prepare() function and pass in the \a matrix and \a font you
173 expect to use when drawing the text.
175 \sa QPainter::setFont(), QPainter::setMatrix()
177 void QStaticText::prepare(const QTransform &matrix, const QFont &font)
179 data->matrix = matrix;
186 Assigns \a other to this QStaticText.
188 QStaticText &QStaticText::operator=(const QStaticText &other)
195 Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths
198 bool QStaticText::operator==(const QStaticText &other) const
200 return (data == other.data
201 || (data->text == other.data->text
202 && data->font == other.data->font
203 && data->textWidth == other.data->textWidth));
207 Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes
210 bool QStaticText::operator!=(const QStaticText &other) const
212 return !(*this == other);
216 Sets the text of the QStaticText to \a text.
218 \note This function will cause the layout of the text to require recalculation.
222 void QStaticText::setText(const QString &text)
230 Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
231 Qt::AutoText (the default), the format of the text will try to be determined using the
232 function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
233 displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
234 that alter the font of the text, its color, or its layout are supported by QStaticText.
236 \note This function will cause the layout of the text to require recalculation.
238 \sa textFormat(), setText(), text()
240 void QStaticText::setTextFormat(Qt::TextFormat textFormat)
243 data->textFormat = textFormat;
248 Returns the text format of the QStaticText.
250 \sa setTextFormat(), setText(), text()
252 Qt::TextFormat QStaticText::textFormat() const
254 return data->textFormat;
258 Returns the text of the QStaticText.
262 QString QStaticText::text() const
268 Sets the text option structure that controls the layout process to the given \a textOption.
272 void QStaticText::setTextOption(const QTextOption &textOption)
275 data->textOption = textOption;
280 Returns the current text option used to control the layout process.
282 QTextOption QStaticText::textOption() const
284 return data->textOption;
288 Sets the preferred width for this QStaticText. If the text is wider than the specified width,
289 it will be broken into multiple lines and grow vertically. If the text cannot be split into
290 multiple lines, it will be larger than the specified \a textWidth.
292 Setting the preferred text width to a negative number will cause the text to be unbounded.
294 Use size() to get the actual size of the text.
296 \note This function will cause the layout of the text to require recalculation.
298 \sa textWidth(), size()
300 void QStaticText::setTextWidth(qreal textWidth)
303 data->textWidth = textWidth;
308 Returns the preferred width for this QStaticText.
312 qreal QStaticText::textWidth() const
314 return data->textWidth;
318 Returns the size of the bounding rect for this QStaticText.
322 QSizeF QStaticText::size() const
324 if (data->needsRelayout)
326 return data->actualSize;
329 QStaticTextPrivate::QStaticTextPrivate()
330 : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0),
331 needsRelayout(true), textFormat(Qt::AutoText),
332 untransformedCoordinates(false)
336 QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
337 : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
338 items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption),
339 needsRelayout(true), textFormat(other.textFormat),
340 untransformedCoordinates(other.untransformedCoordinates)
344 QStaticTextPrivate::~QStaticTextPrivate()
348 delete[] positionPool;
352 QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
354 return q->data.data();
359 class DrawTextItemRecorder: public QPaintEngine
362 DrawTextItemRecorder(bool untransformedCoordinates)
363 : m_dirtyPen(false), m_untransformedCoordinates(untransformedCoordinates),
364 m_currentColor(Qt::black)
368 virtual void updateState(const QPaintEngineState &newState)
370 if (newState.state() & QPaintEngine::DirtyPen
371 && newState.pen().color() != m_currentColor) {
373 m_currentColor = newState.pen().color();
377 virtual void drawTextItem(const QPointF &position, const QTextItem &textItem)
379 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
381 QStaticTextItem currentItem;
382 currentItem.setFontEngine(ti.fontEngine);
383 currentItem.font = ti.font();
384 currentItem.charOffset = m_chars.size();
385 currentItem.numChars = ti.num_chars;
386 currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
387 currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
389 currentItem.color = m_currentColor;
391 QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
392 matrix.translate(position.x(), position.y());
394 QVarLengthArray<glyph_t> glyphs;
395 QVarLengthArray<QFixedPoint> positions;
396 ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
398 int size = glyphs.size();
399 Q_ASSERT(size == positions.size());
400 currentItem.numGlyphs = size;
402 m_glyphs.resize(m_glyphs.size() + size);
403 m_positions.resize(m_glyphs.size());
404 m_chars.resize(m_chars.size() + ti.num_chars);
406 glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
407 memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
409 QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
410 memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
412 QChar *charsDestination = m_chars.data() + currentItem.charOffset;
413 memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars);
415 m_items.append(currentItem);
418 virtual void drawPolygon(const QPointF *, int , PolygonDrawMode )
420 /* intentionally empty */
423 virtual bool begin(QPaintDevice *) { return true; }
424 virtual bool end() { return true; }
425 virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {}
426 virtual Type type() const
431 QVector<QStaticTextItem> items() const
436 QVector<QFixedPoint> positions() const
441 QVector<glyph_t> glyphs() const
446 QVector<QChar> chars() const
452 QVector<QStaticTextItem> m_items;
453 QVector<QFixedPoint> m_positions;
454 QVector<glyph_t> m_glyphs;
455 QVector<QChar> m_chars;
458 bool m_untransformedCoordinates;
459 QColor m_currentColor;
462 class DrawTextItemDevice: public QPaintDevice
465 DrawTextItemDevice(bool untransformedCoordinates)
467 m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates);
470 ~DrawTextItemDevice()
472 delete m_paintEngine;
475 int metric(PaintDeviceMetric m) const
486 case PdmPhysicalDpiX:
487 val = QX11Info::appDpiX();
490 case PdmPhysicalDpiY:
491 val = QX11Info::appDpiY();
501 qWarning("DrawTextItemDevice::metric: Invalid metric command");
506 virtual QPaintEngine *paintEngine() const
508 return m_paintEngine;
511 QVector<glyph_t> glyphs() const
513 return m_paintEngine->glyphs();
516 QVector<QFixedPoint> positions() const
518 return m_paintEngine->positions();
521 QVector<QStaticTextItem> items() const
523 return m_paintEngine->items();
526 QVector<QChar> chars() const
528 return m_paintEngine->chars();
532 DrawTextItemRecorder *m_paintEngine;
536 void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p)
538 bool preferRichText = textFormat == Qt::RichText
539 || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
541 if (!preferRichText) {
542 QTextLayout textLayout;
543 textLayout.setText(text);
544 textLayout.setFont(font);
545 textLayout.setTextOption(textOption);
547 qreal leading = QFontMetricsF(font).leading();
548 qreal height = -leading;
550 textLayout.beginLayout();
552 QTextLine line = textLayout.createLine();
556 if (textWidth >= 0.0)
557 line.setLineWidth(textWidth);
559 line.setPosition(QPointF(0.0, height));
560 height += line.height();
562 textLayout.endLayout();
564 actualSize = textLayout.boundingRect().size();
565 textLayout.draw(p, topLeftPosition);
567 QTextDocument document;
568 #ifndef QT_NO_CSSPARSER
569 const QColor color = p->pen().color();
570 document.setDefaultStyleSheet(QString::fromLatin1("body { color: %1 }")
573 document.setDefaultFont(font);
574 document.setDocumentMargin(0.0);
575 #ifndef QT_NO_TEXTHTMLPARSER
576 document.setHtml(text);
578 document.setPlainText(text);
580 if (textWidth >= 0.0)
581 document.setTextWidth(textWidth);
583 document.adjustSize();
584 document.setDefaultTextOption(textOption);
587 p->translate(topLeftPosition);
588 QAbstractTextDocumentLayout::PaintContext ctx;
589 ctx.palette.setColor(QPalette::Text, p->pen().color());
590 document.documentLayout()->draw(p, ctx);
593 if (textWidth >= 0.0)
594 document.adjustSize(); // Find optimal size
596 actualSize = document.size();
600 void QStaticTextPrivate::init()
604 delete[] positionPool;
607 position = QPointF(0, 0);
609 DrawTextItemDevice device(untransformedCoordinates);
611 QPainter painter(&device);
612 painter.setFont(font);
613 painter.setTransform(matrix);
615 paintText(QPointF(0, 0), &painter);
618 QVector<QStaticTextItem> deviceItems = device.items();
619 QVector<QFixedPoint> positions = device.positions();
620 QVector<glyph_t> glyphs = device.glyphs();
621 QVector<QChar> chars = device.chars();
623 itemCount = deviceItems.size();
624 items = new QStaticTextItem[itemCount];
626 glyphPool = new glyph_t[glyphs.size()];
627 memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
629 positionPool = new QFixedPoint[positions.size()];
630 memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
632 Q_ASSERT(chars.size() >= 1);
633 charPool = new QChar[chars.size()];
634 memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar));
636 for (int i=0; i<itemCount; ++i) {
637 items[i] = deviceItems.at(i);
639 items[i].glyphs = glyphPool + items[i].glyphOffset;
640 items[i].glyphPositions = positionPool + items[i].positionOffset;
641 items[i].chars = charPool + items[i].charOffset;
644 needsRelayout = false;
647 QStaticTextItem::~QStaticTextItem()
650 m_fontEngine->ref.deref();
654 void QStaticTextItem::setFontEngine(QFontEngine *fe)
658 m_fontEngine->ref.ref();