1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qstatictext.h"
43 #include "qstatictext_p.h"
44 #include <private/qtextengine_p.h>
45 #include <private/qfontengine_p.h>
46 #include <qabstracttextdocumentlayout.h>
52 \brief The QStaticText class enables optimized drawing of text when the text and its layout
61 QStaticText provides a way to cache layout data for a block of text so that it can be drawn
62 more efficiently than by using QPainter::drawText() in which the layout information is
63 recalculated with every call.
65 The class primarily provides an optimization for cases where the text, its font and the
66 transformations on the painter are static over several paint events. If the text or its layout
67 is changed for every iteration, QPainter::drawText() is the more efficient alternative, since
68 the static text's layout would have to be recalculated to take the new state into consideration.
70 Translating the painter will not cause the layout of the text to be recalculated, but will cause
71 a very small performance impact on drawStaticText(). Altering any other parts of the painter's
72 transformation or the painter's font will cause the layout of the static text to be
73 recalculated. This should be avoided as often as possible to maximize the performance
74 benefit of using QStaticText.
76 In addition, only affine transformations are supported by drawStaticText(). Calling
77 drawStaticText() on a projected painter will perform slightly worse than using the regular
78 drawText() call, so this should be avoided.
81 class MyWidget: public QWidget
84 MyWidget(QWidget *parent = 0) : QWidget(parent), m_staticText("This is static text")
87 void paintEvent(QPaintEvent *)
89 QPainter painter(this);
90 painter.drawStaticText(0, 0, m_staticText);
94 QStaticText m_staticText;
98 The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific
99 point with no boundaries, and also when QPainter::drawText() is called with a bounding
102 If a bounding rectangle is not required, create a QStaticText object without setting a preferred
103 text width. The text will then occupy a single line.
105 If you set a text width on the QStaticText object, this will bound the text. The text will
106 be formatted so that no line exceeds the given width. The text width set for QStaticText will
107 not automatically be used for clipping. To achieve clipping in addition to line breaks, use
108 QPainter::setClipRect(). The position of the text is decided by the argument passed to
109 QPainter::drawStaticText() and can change from call to call with a minimal impact on
112 For extra convenience, it is possible to apply formatting to the text using the HTML subset
113 supported by QTextDocument. QStaticText will attempt to guess the format of the input text using
114 Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force
115 QStaticText to display its contents as either plain text or rich text, use the function
116 QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText.
118 QStaticText can only represent text, so only HTML tags which alter the layout or appearance of
119 the text will be respected. Adding an image to the input HTML, for instance, will cause the
120 image to be included as part of the layout, affecting the positions of the text glyphs, but it
121 will not be displayed. The result will be an empty area the size of the image in the output.
122 Similarly, using tables will cause the text to be laid out in table format, but the borders
125 If it's the first time the static text is drawn, or if the static text, or the painter's font
126 has been altered since the last time it was drawn, the text's layout has to be
127 recalculated. On some paint engines, changing the matrix of the painter will also cause the
128 layout to be recalculated. In particular, this will happen for any engine except for the
129 OpenGL2 paint engine. Recalculating the layout will impose an overhead on the
130 QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you
131 can call prepare() ahead of time to ensure that the layout is calculated.
133 \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument
137 \enum QStaticText::PerformanceHint
139 This enum the different performance hints that can be set on the QStaticText. These hints
140 can be used to indicate that the QStaticText should use additional caches, if possible,
141 to improve performance at the expense of memory. In particular, setting the performance hint
142 AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics
143 system or when drawing to a QOpenGLWidget.
145 \value ModerateCaching Do basic caching for high performance at a low memory cost.
146 \value AggressiveCaching Use additional caching when available. This may improve performance
147 at a higher memory cost.
151 Constructs an empty QStaticText
153 QStaticText::QStaticText()
154 : data(new QStaticTextPrivate)
159 Constructs a QStaticText object with the given \a text.
161 QStaticText::QStaticText(const QString &text)
162 : data(new QStaticTextPrivate)
169 Constructs a QStaticText object which is a copy of \a other.
171 QStaticText::QStaticText(const QStaticText &other)
177 Destroys the QStaticText.
179 QStaticText::~QStaticText()
181 Q_ASSERT(!data || data->ref.load() >= 1);
187 void QStaticText::detach()
189 if (data->ref.load() != 1)
194 Prepares the QStaticText object for being painted with the given \a matrix and the given \a font
195 to avoid overhead when the actual drawStaticText() call is made.
197 When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part
198 of the QStaticText object has changed since the last time it was drawn. It will also be
199 recalculated if the painter's font is not the same as when the QStaticText was last drawn, or,
200 on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered
201 since the static text was last drawn.
203 To avoid the overhead of creating the layout the first time you draw the QStaticText after
204 making changes, you can use the prepare() function and pass in the \a matrix and \a font you
205 expect to use when drawing the text.
207 \sa QPainter::setFont(), QPainter::setMatrix()
209 void QStaticText::prepare(const QTransform &matrix, const QFont &font)
211 data->matrix = matrix;
218 Assigns \a other to this QStaticText.
220 QStaticText &QStaticText::operator=(const QStaticText &other)
227 Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths
230 bool QStaticText::operator==(const QStaticText &other) const
232 return (data == other.data
233 || (data->text == other.data->text
234 && data->font == other.data->font
235 && data->textWidth == other.data->textWidth));
239 Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes
242 bool QStaticText::operator!=(const QStaticText &other) const
244 return !(*this == other);
248 Sets the text of the QStaticText to \a text.
250 \note This function will cause the layout of the text to require recalculation.
254 void QStaticText::setText(const QString &text)
262 Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
263 Qt::AutoText (the default), the format of the text will try to be determined using the
264 function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
265 displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
266 that alter the font of the text, its color, or its layout are supported by QStaticText.
268 \note This function will cause the layout of the text to require recalculation.
270 \sa textFormat(), setText(), text()
272 void QStaticText::setTextFormat(Qt::TextFormat textFormat)
275 data->textFormat = textFormat;
280 Returns the text format of the QStaticText.
282 \sa setTextFormat(), setText(), text()
284 Qt::TextFormat QStaticText::textFormat() const
286 return Qt::TextFormat(data->textFormat);
290 Returns the text of the QStaticText.
294 QString QStaticText::text() const
300 Sets the performance hint of the QStaticText according to the \a
301 performanceHint provided. The \a performanceHint is used to
302 customize how much caching is done internally to improve
305 The default is QStaticText::ModerateCaching.
307 \note This function will cause the layout of the text to require recalculation.
309 \sa performanceHint()
311 void QStaticText::setPerformanceHint(PerformanceHint performanceHint)
313 if ((performanceHint == ModerateCaching && !data->useBackendOptimizations)
314 || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) {
318 data->useBackendOptimizations = (performanceHint == AggressiveCaching);
323 Returns which performance hint is set for the QStaticText.
325 \sa setPerformanceHint()
327 QStaticText::PerformanceHint QStaticText::performanceHint() const
329 return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching;
333 Sets the text option structure that controls the layout process to the given \a textOption.
337 void QStaticText::setTextOption(const QTextOption &textOption)
340 data->textOption = textOption;
345 Returns the current text option used to control the layout process.
347 QTextOption QStaticText::textOption() const
349 return data->textOption;
353 Sets the preferred width for this QStaticText. If the text is wider than the specified width,
354 it will be broken into multiple lines and grow vertically. If the text cannot be split into
355 multiple lines, it will be larger than the specified \a textWidth.
357 Setting the preferred text width to a negative number will cause the text to be unbounded.
359 Use size() to get the actual size of the text.
361 \note This function will cause the layout of the text to require recalculation.
363 \sa textWidth(), size()
365 void QStaticText::setTextWidth(qreal textWidth)
368 data->textWidth = textWidth;
373 Returns the preferred width for this QStaticText.
377 qreal QStaticText::textWidth() const
379 return data->textWidth;
383 Returns the size of the bounding rect for this QStaticText.
387 QSizeF QStaticText::size() const
389 if (data->needsRelayout)
391 return data->actualSize;
394 QStaticTextPrivate::QStaticTextPrivate()
395 : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0),
396 needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText),
397 untransformedCoordinates(false)
401 QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
402 : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
403 items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption),
404 needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations),
405 textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates)
409 QStaticTextPrivate::~QStaticTextPrivate()
413 delete[] positionPool;
417 QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
419 return q->data.data();
424 class DrawTextItemRecorder: public QPaintEngine
427 DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
428 : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
429 m_untransformedCoordinates(untransformedCoordinates), m_currentColor(Qt::black)
433 virtual void updateState(const QPaintEngineState &newState)
435 if (newState.state() & QPaintEngine::DirtyPen
436 && newState.pen().color() != m_currentColor) {
438 m_currentColor = newState.pen().color();
442 virtual void drawTextItem(const QPointF &position, const QTextItem &textItem)
444 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
446 QStaticTextItem currentItem;
447 currentItem.setFontEngine(ti.fontEngine);
448 currentItem.font = ti.font();
449 currentItem.charOffset = m_chars.size();
450 currentItem.numChars = ti.num_chars;
451 currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
452 currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
453 currentItem.useBackendOptimizations = m_useBackendOptimizations;
455 currentItem.color = m_currentColor;
457 QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
458 matrix.translate(position.x(), position.y());
460 QVarLengthArray<glyph_t> glyphs;
461 QVarLengthArray<QFixedPoint> positions;
462 ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
464 int size = glyphs.size();
465 Q_ASSERT(size == positions.size());
466 currentItem.numGlyphs = size;
468 m_glyphs.resize(m_glyphs.size() + size);
469 m_positions.resize(m_glyphs.size());
470 m_chars.resize(m_chars.size() + ti.num_chars);
472 glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
473 memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
475 QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
476 memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
478 QChar *charsDestination = m_chars.data() + currentItem.charOffset;
479 memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars);
481 m_items.append(currentItem);
484 virtual void drawPolygon(const QPointF *, int , PolygonDrawMode )
486 /* intentionally empty */
489 virtual bool begin(QPaintDevice *) { return true; }
490 virtual bool end() { return true; }
491 virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {}
492 virtual Type type() const
497 QVector<QStaticTextItem> items() const
502 QVector<QFixedPoint> positions() const
507 QVector<glyph_t> glyphs() const
512 QVector<QChar> chars() const
518 QVector<QStaticTextItem> m_items;
519 QVector<QFixedPoint> m_positions;
520 QVector<glyph_t> m_glyphs;
521 QVector<QChar> m_chars;
524 bool m_useBackendOptimizations;
525 bool m_untransformedCoordinates;
526 QColor m_currentColor;
529 class DrawTextItemDevice: public QPaintDevice
532 DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
534 m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
535 useBackendOptimizations);
538 ~DrawTextItemDevice()
540 delete m_paintEngine;
543 int metric(PaintDeviceMetric m) const
554 case PdmPhysicalDpiX:
555 val = qt_defaultDpiX();
558 case PdmPhysicalDpiY:
559 val = qt_defaultDpiY();
569 qWarning("DrawTextItemDevice::metric: Invalid metric command");
574 virtual QPaintEngine *paintEngine() const
576 return m_paintEngine;
579 QVector<glyph_t> glyphs() const
581 return m_paintEngine->glyphs();
584 QVector<QFixedPoint> positions() const
586 return m_paintEngine->positions();
589 QVector<QStaticTextItem> items() const
591 return m_paintEngine->items();
594 QVector<QChar> chars() const
596 return m_paintEngine->chars();
600 DrawTextItemRecorder *m_paintEngine;
604 void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p)
606 bool preferRichText = textFormat == Qt::RichText
607 || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
609 if (!preferRichText) {
610 QTextLayout textLayout;
611 textLayout.setText(text);
612 textLayout.setFont(font);
613 textLayout.setTextOption(textOption);
614 textLayout.setCacheEnabled(true);
616 qreal leading = QFontMetricsF(font).leading();
617 qreal height = -leading;
619 textLayout.beginLayout();
621 QTextLine line = textLayout.createLine();
625 if (textWidth >= 0.0)
626 line.setLineWidth(textWidth);
628 line.setPosition(QPointF(0.0, height));
629 height += line.height();
631 textLayout.endLayout();
633 actualSize = textLayout.boundingRect().size();
634 textLayout.draw(p, topLeftPosition);
636 QTextDocument document;
637 #ifndef QT_NO_CSSPARSER
638 QColor color = p->pen().color();
639 document.setDefaultStyleSheet(QString::fromLatin1("body { color: #%1%2%3 }")
640 .arg(QString::number(color.red(), 16), 2, QLatin1Char('0'))
641 .arg(QString::number(color.green(), 16), 2, QLatin1Char('0'))
642 .arg(QString::number(color.blue(), 16), 2, QLatin1Char('0')));
644 document.setDefaultFont(font);
645 document.setDocumentMargin(0.0);
646 #ifndef QT_NO_TEXTHTMLPARSER
647 document.setHtml(text);
649 document.setPlainText(text);
651 if (textWidth >= 0.0)
652 document.setTextWidth(textWidth);
654 document.adjustSize();
655 document.setDefaultTextOption(textOption);
658 p->translate(topLeftPosition);
659 QAbstractTextDocumentLayout::PaintContext ctx;
660 ctx.palette.setColor(QPalette::Text, p->pen().color());
661 document.documentLayout()->draw(p, ctx);
664 if (textWidth >= 0.0)
665 document.adjustSize(); // Find optimal size
667 actualSize = document.size();
671 void QStaticTextPrivate::init()
675 delete[] positionPool;
678 position = QPointF(0, 0);
680 DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations);
682 QPainter painter(&device);
683 painter.setFont(font);
684 painter.setTransform(matrix);
686 paintText(QPointF(0, 0), &painter);
689 QVector<QStaticTextItem> deviceItems = device.items();
690 QVector<QFixedPoint> positions = device.positions();
691 QVector<glyph_t> glyphs = device.glyphs();
692 QVector<QChar> chars = device.chars();
694 itemCount = deviceItems.size();
695 items = new QStaticTextItem[itemCount];
697 glyphPool = new glyph_t[glyphs.size()];
698 memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
700 positionPool = new QFixedPoint[positions.size()];
701 memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
703 charPool = new QChar[chars.size()];
704 memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar));
706 for (int i=0; i<itemCount; ++i) {
707 items[i] = deviceItems.at(i);
709 items[i].glyphs = glyphPool + items[i].glyphOffset;
710 items[i].glyphPositions = positionPool + items[i].positionOffset;
711 items[i].chars = charPool + items[i].charOffset;
714 needsRelayout = false;
717 QStaticTextItem::~QStaticTextItem()
719 if (m_userData != 0 && !m_userData->ref.deref())
721 m_fontEngine->ref.deref();
724 void QStaticTextItem::setFontEngine(QFontEngine *fe)
726 if (m_fontEngine != 0)
727 m_fontEngine->ref.deref();
729 if (m_fontEngine != 0)
730 m_fontEngine->ref.ref();