1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qstatictext.h"
41#include "qstatictext_p.h"
42#include <qmath.h>
43#include <private/qtextengine_p.h>
44#include <private/qfontengine_p.h>
45#include <qabstracttextdocumentlayout.h>
46
47QT_BEGIN_NAMESPACE
48
49QStaticTextUserData::~QStaticTextUserData()
50{
51}
52
53/*!
54 \class QStaticText
55 \brief The QStaticText class enables optimized drawing of text when the text and its layout
56 is updated rarely.
57 \since 4.7
58 \inmodule QtGui
59
60 \ingroup multimedia
61 \ingroup text
62 \ingroup shared
63
64 QStaticText provides a way to cache layout data for a block of text so that it can be drawn
65 more efficiently than by using QPainter::drawText() in which the layout information is
66 recalculated with every call.
67
68 The class primarily provides an optimization for cases where the text, its font and the
69 transformations on the painter are static over several paint events. If the text or its layout
70 is changed for every iteration, QPainter::drawText() is the more efficient alternative, since
71 the static text's layout would have to be recalculated to take the new state into consideration.
72
73 Translating the painter will not cause the layout of the text to be recalculated, but will cause
74 a very small performance impact on drawStaticText(). Altering any other parts of the painter's
75 transformation or the painter's font will cause the layout of the static text to be
76 recalculated. This should be avoided as often as possible to maximize the performance
77 benefit of using QStaticText.
78
79 In addition, only affine transformations are supported by drawStaticText(). Calling
80 drawStaticText() on a projected painter will perform slightly worse than using the regular
81 drawText() call, so this should be avoided.
82
83 \code
84 class MyWidget: public QWidget
85 {
86 public:
87 MyWidget(QWidget *parent = nullptr) : QWidget(parent), m_staticText("This is static text")
88
89 protected:
90 void paintEvent(QPaintEvent *)
91 {
92 QPainter painter(this);
93 painter.drawStaticText(0, 0, m_staticText);
94 }
95
96 private:
97 QStaticText m_staticText;
98 };
99 \endcode
100
101 The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific
102 point with no boundaries, and also when QPainter::drawText() is called with a bounding
103 rectangle.
104
105 If a bounding rectangle is not required, create a QStaticText object without setting a preferred
106 text width. The text will then occupy a single line.
107
108 If you set a text width on the QStaticText object, this will bound the text. The text will
109 be formatted so that no line exceeds the given width. The text width set for QStaticText will
110 not automatically be used for clipping. To achieve clipping in addition to line breaks, use
111 QPainter::setClipRect(). The position of the text is decided by the argument passed to
112 QPainter::drawStaticText() and can change from call to call with a minimal impact on
113 performance.
114
115 For extra convenience, it is possible to apply formatting to the text using the HTML subset
116 supported by QTextDocument. QStaticText will attempt to guess the format of the input text using
117 Qt::mightBeRichText(), and interpret it as rich text if this function returns \c true. To force
118 QStaticText to display its contents as either plain text or rich text, use the function
119 QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText.
120
121 QStaticText can only represent text, so only HTML tags which alter the layout or appearance of
122 the text will be respected. Adding an image to the input HTML, for instance, will cause the
123 image to be included as part of the layout, affecting the positions of the text glyphs, but it
124 will not be displayed. The result will be an empty area the size of the image in the output.
125 Similarly, using tables will cause the text to be laid out in table format, but the borders
126 will not be drawn.
127
128 If it's the first time the static text is drawn, or if the static text, or the painter's font
129 has been altered since the last time it was drawn, the text's layout has to be
130 recalculated. On some paint engines, changing the matrix of the painter will also cause the
131 layout to be recalculated. In particular, this will happen for any engine except for the
132 OpenGL2 paint engine. Recalculating the layout will impose an overhead on the
133 QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you
134 can call prepare() ahead of time to ensure that the layout is calculated.
135
136 \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument
137*/
138
139/*!
140 \enum QStaticText::PerformanceHint
141
142 This enum the different performance hints that can be set on the QStaticText. These hints
143 can be used to indicate that the QStaticText should use additional caches, if possible,
144 to improve performance at the expense of memory. In particular, setting the performance hint
145 AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics
146 system or when drawing to a QOpenGLWidget.
147
148 \value ModerateCaching Do basic caching for high performance at a low memory cost.
149 \value AggressiveCaching Use additional caching when available. This may improve performance
150 at a higher memory cost.
151*/
152
153/*!
154 Constructs an empty QStaticText
155*/
156QStaticText::QStaticText()
157 : data(new QStaticTextPrivate)
158{
159}
160
161/*!
162 Constructs a QStaticText object with the given \a text.
163*/
164QStaticText::QStaticText(const QString &text)
165 : data(new QStaticTextPrivate)
166{
167 data->text = text;
168 data->invalidate();
169}
170
171/*!
172 Constructs a QStaticText object which is a copy of \a other.
173*/
174QStaticText::QStaticText(const QStaticText &other)
175{
176 data = other.data;
177}
178
179/*!
180 Destroys the QStaticText.
181*/
182QStaticText::~QStaticText()
183{
184 Q_ASSERT(!data || data->ref.loadRelaxed() >= 1);
185}
186
187/*!
188 \internal
189*/
190void QStaticText::detach()
191{
192 if (data->ref.loadRelaxed() != 1)
193 data.detach();
194}
195
196/*!
197 Prepares the QStaticText object for being painted with the given \a matrix and the given \a font
198 to avoid overhead when the actual drawStaticText() call is made.
199
200 When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part
201 of the QStaticText object has changed since the last time it was drawn. It will also be
202 recalculated if the painter's font is not the same as when the QStaticText was last drawn, or,
203 on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered
204 since the static text was last drawn.
205
206 To avoid the overhead of creating the layout the first time you draw the QStaticText after
207 making changes, you can use the prepare() function and pass in the \a matrix and \a font you
208 expect to use when drawing the text.
209
210 \sa QPainter::setFont(), QPainter::setWorldTransform()
211*/
212void QStaticText::prepare(const QTransform &matrix, const QFont &font)
213{
214 data->matrix = matrix;
215 data->font = font;
216 data->init();
217}
218
219
220/*!
221 Assigns \a other to this QStaticText.
222*/
223QStaticText &QStaticText::operator=(const QStaticText &other)
224{
225 data = other.data;
226 return *this;
227}
228
229/*!
230 \fn void QStaticText::swap(QStaticText &other)
231 \since 5.0
232
233 Swaps this static text instance with \a other. This function is
234 very fast and never fails.
235*/
236
237/*!
238 Compares \a other to this QStaticText. Returns \c true if the texts, fonts and text widths
239 are equal.
240*/
241bool QStaticText::operator==(const QStaticText &other) const
242{
243 return (data == other.data
244 || (data->text == other.data->text
245 && data->font == other.data->font
246 && data->textWidth == other.data->textWidth));
247}
248
249/*!
250 Compares \a other to this QStaticText. Returns \c true if the texts, fonts or maximum sizes
251 are different.
252*/
253bool QStaticText::operator!=(const QStaticText &other) const
254{
255 return !(*this == other);
256}
257
258/*!
259 Sets the text of the QStaticText to \a text.
260
261 \note This function will cause the layout of the text to require recalculation.
262
263 \sa text()
264*/
265void QStaticText::setText(const QString &text)
266{
267 detach();
268 data->text = text;
269 data->invalidate();
270}
271
272/*!
273 Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
274 Qt::AutoText (the default), the format of the text will try to be determined using the
275 function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
276 displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
277 that alter the font of the text, its color, or its layout are supported by QStaticText.
278
279 \note This function will cause the layout of the text to require recalculation.
280
281 \sa textFormat(), setText(), text()
282*/
283void QStaticText::setTextFormat(Qt::TextFormat textFormat)
284{
285 detach();
286 data->textFormat = textFormat;
287 data->invalidate();
288}
289
290/*!
291 Returns the text format of the QStaticText.
292
293 \sa setTextFormat(), setText(), text()
294*/
295Qt::TextFormat QStaticText::textFormat() const
296{
297 return Qt::TextFormat(data->textFormat);
298}
299
300/*!
301 Returns the text of the QStaticText.
302
303 \sa setText()
304*/
305QString QStaticText::text() const
306{
307 return data->text;
308}
309
310/*!
311 Sets the performance hint of the QStaticText according to the \a
312 performanceHint provided. The \a performanceHint is used to
313 customize how much caching is done internally to improve
314 performance.
315
316 The default is QStaticText::ModerateCaching.
317
318 \note This function will cause the layout of the text to require recalculation.
319
320 \sa performanceHint()
321*/
322void QStaticText::setPerformanceHint(PerformanceHint performanceHint)
323{
324 if ((performanceHint == ModerateCaching && !data->useBackendOptimizations)
325 || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) {
326 return;
327 }
328 detach();
329 data->useBackendOptimizations = (performanceHint == AggressiveCaching);
330 data->invalidate();
331}
332
333/*!
334 Returns which performance hint is set for the QStaticText.
335
336 \sa setPerformanceHint()
337*/
338QStaticText::PerformanceHint QStaticText::performanceHint() const
339{
340 return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching;
341}
342
343/*!
344 Sets the text option structure that controls the layout process to the given \a textOption.
345
346 \sa textOption()
347*/
348void QStaticText::setTextOption(const QTextOption &textOption)
349{
350 detach();
351 data->textOption = textOption;
352 data->invalidate();
353}
354
355/*!
356 Returns the current text option used to control the layout process.
357*/
358QTextOption QStaticText::textOption() const
359{
360 return data->textOption;
361}
362
363/*!
364 Sets the preferred width for this QStaticText. If the text is wider than the specified width,
365 it will be broken into multiple lines and grow vertically. If the text cannot be split into
366 multiple lines, it will be larger than the specified \a textWidth.
367
368 Setting the preferred text width to a negative number will cause the text to be unbounded.
369
370 Use size() to get the actual size of the text.
371
372 \note This function will cause the layout of the text to require recalculation.
373
374 \sa textWidth(), size()
375*/
376void QStaticText::setTextWidth(qreal textWidth)
377{
378 detach();
379 data->textWidth = textWidth;
380 data->invalidate();
381}
382
383/*!
384 Returns the preferred width for this QStaticText.
385
386 \sa setTextWidth()
387*/
388qreal QStaticText::textWidth() const
389{
390 return data->textWidth;
391}
392
393/*!
394 Returns the size of the bounding rect for this QStaticText.
395
396 \sa textWidth()
397*/
398QSizeF QStaticText::size() const
399{
400 if (data->needsRelayout)
401 data->init();
402 return data->actualSize;
403}
404
405QStaticTextPrivate::QStaticTextPrivate()
406 : textWidth(-1.0), items(nullptr), itemCount(0), glyphPool(nullptr), positionPool(nullptr),
407 needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText),
408 untransformedCoordinates(false)
409{
410}
411
412QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
413 : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
414 items(nullptr), itemCount(0), glyphPool(nullptr), positionPool(nullptr), textOption(other.textOption),
415 needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations),
416 textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates)
417{
418}
419
420QStaticTextPrivate::~QStaticTextPrivate()
421{
422 delete[] items;
423 delete[] glyphPool;
424 delete[] positionPool;
425}
426
427QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
428{
429 return q->data.data();
430}
431
432namespace {
433
434 class DrawTextItemRecorder: public QPaintEngine
435 {
436 public:
437 DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
438 : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
439 m_untransformedCoordinates(untransformedCoordinates), m_currentColor(0, 0, 0, 0)
440 {
441 }
442
443 virtual void updateState(const QPaintEngineState &newState) override
444 {
445 if (newState.state() & QPaintEngine::DirtyPen
446 && newState.pen().color() != m_currentColor) {
447 m_dirtyPen = true;
448 m_currentColor = newState.pen().color();
449 }
450 }
451
452 virtual void drawTextItem(const QPointF &position, const QTextItem &textItem) override
453 {
454 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
455
456 QStaticTextItem currentItem;
457 currentItem.setFontEngine(ti.fontEngine);
458 currentItem.font = ti.font();
459 currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
460 currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
461 currentItem.useBackendOptimizations = m_useBackendOptimizations;
462 if (m_dirtyPen)
463 currentItem.color = m_currentColor;
464
465 QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
466 matrix.translate(position.x(), position.y());
467
468 QVarLengthArray<glyph_t> glyphs;
469 QVarLengthArray<QFixedPoint> positions;
470 ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
471
472 int size = glyphs.size();
473 Q_ASSERT(size == positions.size());
474 currentItem.numGlyphs = size;
475
476 m_glyphs.resize(m_glyphs.size() + size);
477 m_positions.resize(m_glyphs.size());
478
479 glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
480 memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
481
482 QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
483 memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
484
485 m_items.append(currentItem);
486 }
487
488 virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ) override
489 {
490 /* intentionally empty */
491 }
492
493 virtual bool begin(QPaintDevice *) override { return true; }
494 virtual bool end() override { return true; }
495 virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) override {}
496 virtual Type type() const override
497 {
498 return User;
499 }
500
501 QList<QStaticTextItem> items() const
502 {
503 return m_items;
504 }
505
506 QList<QFixedPoint> positions() const
507 {
508 return m_positions;
509 }
510
511 QList<glyph_t> glyphs() const
512 {
513 return m_glyphs;
514 }
515
516 private:
517 QList<QStaticTextItem> m_items;
518 QList<QFixedPoint> m_positions;
519 QList<glyph_t> m_glyphs;
520
521 bool m_dirtyPen;
522 bool m_useBackendOptimizations;
523 bool m_untransformedCoordinates;
524 QColor m_currentColor;
525 };
526
527 class DrawTextItemDevice: public QPaintDevice
528 {
529 public:
530 DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
531 {
532 m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
533 useBackendOptimizations);
534 }
535
536 ~DrawTextItemDevice()
537 {
538 delete m_paintEngine;
539 }
540
541 int metric(PaintDeviceMetric m) const override
542 {
543 int val;
544 switch (m) {
545 case PdmWidth:
546 case PdmHeight:
547 case PdmWidthMM:
548 case PdmHeightMM:
549 val = 0;
550 break;
551 case PdmDpiX:
552 case PdmPhysicalDpiX:
553 val = qt_defaultDpiX();
554 break;
555 case PdmDpiY:
556 case PdmPhysicalDpiY:
557 val = qt_defaultDpiY();
558 break;
559 case PdmNumColors:
560 val = 16777216;
561 break;
562 case PdmDepth:
563 val = 24;
564 break;
565 case PdmDevicePixelRatio:
566 val = 1;
567 break;
568 case PdmDevicePixelRatioScaled:
569 val = devicePixelRatioFScale();
570 break;
571 default:
572 val = 0;
573 qWarning("DrawTextItemDevice::metric: Invalid metric command");
574 }
575 return val;
576 }
577
578 virtual QPaintEngine *paintEngine() const override
579 {
580 return m_paintEngine;
581 }
582
583 QList<glyph_t> glyphs() const
584 {
585 return m_paintEngine->glyphs();
586 }
587
588 QList<QFixedPoint> positions() const
589 {
590 return m_paintEngine->positions();
591 }
592
593 QList<QStaticTextItem> items() const
594 {
595 return m_paintEngine->items();
596 }
597
598 private:
599 DrawTextItemRecorder *m_paintEngine;
600 };
601}
602
603void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p, const QColor &pen)
604{
605 bool preferRichText = textFormat == Qt::RichText
606 || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
607
608 if (!preferRichText) {
609 QTextLayout textLayout;
610 textLayout.setText(text);
611 textLayout.setFont(font);
612 textLayout.setTextOption(textOption);
613 textLayout.setCacheEnabled(true);
614
615 qreal height = 0;
616 textLayout.beginLayout();
617 while (1) {
618 QTextLine line = textLayout.createLine();
619 if (!line.isValid())
620 break;
621 line.setLeadingIncluded(true);
622
623 if (textWidth >= 0.0)
624 line.setLineWidth(textWidth);
625 else
626 line.setLineWidth(QFIXED_MAX);
627 line.setPosition(QPointF(0.0, height));
628 height += line.height();
629 if (line.leading() < 0)
630 height += qCeil(line.leading());
631 }
632 textLayout.endLayout();
633
634 actualSize = textLayout.boundingRect().size();
635 p->setPen(pen);
636 textLayout.draw(p, topLeftPosition);
637 } else {
638 QTextDocument document;
639#ifndef QT_NO_CSSPARSER
640 document.setDefaultStyleSheet(QString::fromLatin1("body { color: rgba(%1, %2, %3, %4%) }")
641 .arg(QString::number(pen.red()))
642 .arg(QString::number(pen.green()))
643 .arg(QString::number(pen.blue()))
644 .arg(QString::number(pen.alpha())));
645#endif
646 document.setDefaultFont(font);
647 document.setDocumentMargin(0.0);
648#ifndef QT_NO_TEXTHTMLPARSER
649 document.setHtml(text);
650#else
651 document.setPlainText(text);
652#endif
653 if (textWidth >= 0.0)
654 document.setTextWidth(textWidth);
655 else
656 document.adjustSize();
657 document.setDefaultTextOption(textOption);
658
659 p->save();
660 p->translate(topLeftPosition);
661 QAbstractTextDocumentLayout::PaintContext ctx;
662 ctx.palette.setColor(QPalette::Text, pen);
663 document.documentLayout()->draw(p, ctx);
664 p->restore();
665
666 actualSize = document.size();
667 }
668}
669
670void QStaticTextPrivate::init()
671{
672 delete[] items;
673 delete[] glyphPool;
674 delete[] positionPool;
675
676 position = QPointF(0, 0);
677
678 DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations);
679 {
680 QPainter painter(&device);
681 painter.setFont(font);
682 painter.setTransform(matrix);
683
684 paintText(QPointF(0, 0), &painter, QColor(0, 0, 0, 0));
685 }
686
687 QList<QStaticTextItem> deviceItems = device.items();
688 QList<QFixedPoint> positions = device.positions();
689 QList<glyph_t> glyphs = device.glyphs();
690
691 itemCount = deviceItems.size();
692 items = new QStaticTextItem[itemCount];
693
694 glyphPool = new glyph_t[glyphs.size()];
695 memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
696
697 positionPool = new QFixedPoint[positions.size()];
698 memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
699
700 for (int i=0; i<itemCount; ++i) {
701 items[i] = deviceItems.at(i);
702
703 items[i].glyphs = glyphPool + items[i].glyphOffset;
704 items[i].glyphPositions = positionPool + items[i].positionOffset;
705 }
706
707 needsRelayout = false;
708}
709
710QT_END_NAMESPACE
711