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

source code of qtbase/src/gui/text/qstatictext.cpp