Coverage Report

Created: 2026-07-25 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/text/qstatictext.cpp
Line
Count
Source
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
11
QT_BEGIN_NAMESPACE
12
13
QT_IMPL_METATYPE_EXTERN(QStaticText)
14
15
QStaticTextUserData::~QStaticTextUserData()
16
0
{
17
0
}
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
*/
122
QStaticText::QStaticText()
123
0
    : data(new QStaticTextPrivate)
124
0
{
125
0
}
126
127
/*!
128
    Constructs a QStaticText object with the given \a text.
129
*/
130
QStaticText::QStaticText(const QString &text)
131
0
    : data(new QStaticTextPrivate)
132
0
{
133
0
    data->text = text;
134
0
    data->invalidate();
135
0
}
136
137
/*!
138
    Constructs a QStaticText object which is a copy of \a other.
139
*/
140
QStaticText::QStaticText(const QStaticText &other)
141
0
{
142
0
    data = other.data;
143
0
}
144
145
/*!
146
    Destroys the QStaticText.
147
*/
148
QStaticText::~QStaticText()
149
0
{
150
0
    Q_ASSERT(!data || data->ref.loadRelaxed() >= 1);
151
0
}
152
153
/*!
154
    \internal
155
*/
156
void QStaticText::detach()
157
0
{
158
0
    if (data->ref.loadRelaxed() != 1)
159
0
        data.detach();
160
0
}
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
*/
178
void QStaticText::prepare(const QTransform &matrix, const QFont &font)
179
0
{
180
0
    data->matrix = matrix;
181
0
    data->font = font;
182
0
    data->init();
183
0
}
184
185
186
/*!
187
    Assigns \a other to this QStaticText.
188
*/
189
QStaticText &QStaticText::operator=(const QStaticText &other)
190
0
{
191
0
    data = other.data;
192
0
    return *this;
193
0
}
194
195
/*!
196
    \fn void QStaticText::swap(QStaticText &other)
197
    \since 5.0
198
    \memberswap{static text instance}
199
*/
200
201
/*!
202
    Compares \a other to this QStaticText. Returns \c true if the texts, fonts and text widths
203
    are equal.
204
*/
205
bool QStaticText::operator==(const QStaticText &other) const
206
0
{
207
0
    return (data == other.data
208
0
            || (data->text == other.data->text
209
0
                && data->font == other.data->font
210
0
                && data->textWidth == other.data->textWidth));
211
0
}
212
213
/*!
214
    Compares \a other to this QStaticText. Returns \c true if the texts, fonts or maximum sizes
215
    are different.
216
*/
217
bool QStaticText::operator!=(const QStaticText &other) const
218
0
{
219
0
    return !(*this == other);
220
0
}
221
222
/*!
223
    Sets the text of the QStaticText to \a text.
224
225
    \note This function will cause the layout of the text to require recalculation.
226
227
    \sa text()
228
*/
229
void QStaticText::setText(const QString &text)
230
0
{
231
0
    detach();
232
0
    data->text = text;
233
0
    data->invalidate();
234
0
}
235
236
/*!
237
   Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
238
   Qt::AutoText (the default), the format of the text will try to be determined using the
239
   function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
240
   displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
241
   that alter the font of the text, its color, or its layout are supported by QStaticText.
242
243
   \note This function will cause the layout of the text to require recalculation.
244
245
   \sa textFormat(), setText(), text()
246
*/
247
void QStaticText::setTextFormat(Qt::TextFormat textFormat)
248
0
{
249
0
    detach();
250
0
    data->textFormat = textFormat;
251
0
    data->invalidate();
252
0
}
253
254
/*!
255
  Returns the text format of the QStaticText.
256
257
  \sa setTextFormat(), setText(), text()
258
*/
259
Qt::TextFormat QStaticText::textFormat() const
260
0
{
261
0
    return Qt::TextFormat(data->textFormat);
262
0
}
263
264
/*!
265
    Returns the text of the QStaticText.
266
267
    \sa setText()
268
*/
269
QString QStaticText::text() const
270
0
{
271
0
    return data->text;
272
0
}
273
274
/*!
275
  Sets the performance hint of the QStaticText according to the \a
276
  performanceHint provided. The \a performanceHint is used to
277
  customize how much caching is done internally to improve
278
  performance.
279
280
  The default is QStaticText::ModerateCaching.
281
282
  \note This function will cause the layout of the text to require recalculation.
283
284
  \sa performanceHint()
285
*/
286
void QStaticText::setPerformanceHint(PerformanceHint performanceHint)
287
0
{
288
0
    if ((performanceHint == ModerateCaching && !data->useBackendOptimizations)
289
0
        || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) {
290
0
        return;
291
0
    }
292
0
    detach();
293
0
    data->useBackendOptimizations = (performanceHint == AggressiveCaching);
294
0
    data->invalidate();
295
0
}
296
297
/*!
298
  Returns which performance hint is set for the QStaticText.
299
300
  \sa setPerformanceHint()
301
*/
302
QStaticText::PerformanceHint QStaticText::performanceHint() const
303
0
{
304
0
    return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching;
305
0
}
306
307
/*!
308
   Sets the text option structure that controls the layout process to the given \a textOption.
309
310
   \sa textOption()
311
*/
312
void QStaticText::setTextOption(const QTextOption &textOption)
313
0
{
314
0
    detach();
315
0
    data->textOption = textOption;
316
0
    data->invalidate();
317
0
}
318
319
/*!
320
    Returns the current text option used to control the layout process.
321
*/
322
QTextOption QStaticText::textOption() const
323
0
{
324
0
    return data->textOption;
325
0
}
326
327
/*!
328
    Sets the preferred width for this QStaticText. If the text is wider than the specified width,
329
    it will be broken into multiple lines and grow vertically. If the text cannot be split into
330
    multiple lines, it will be larger than the specified \a textWidth.
331
332
    Setting the preferred text width to a negative number will cause the text to be unbounded.
333
334
    Use size() to get the actual size of the text.
335
336
    \note This function will cause the layout of the text to require recalculation.
337
338
    \sa textWidth(), size()
339
*/
340
void QStaticText::setTextWidth(qreal textWidth)
341
0
{
342
0
    detach();
343
0
    data->textWidth = textWidth;
344
0
    data->invalidate();
345
0
}
346
347
/*!
348
    Returns the preferred width for this QStaticText.
349
350
    \sa setTextWidth()
351
*/
352
qreal QStaticText::textWidth() const
353
0
{
354
0
    return data->textWidth;
355
0
}
356
357
/*!
358
  Returns the size of the bounding rect for this QStaticText.
359
360
  \sa textWidth()
361
*/
362
QSizeF QStaticText::size() const
363
0
{
364
0
    if (data->needsRelayout)
365
0
        data->init();
366
0
    return data->actualSize;
367
0
}
368
369
QStaticTextPrivate::QStaticTextPrivate()
370
0
        : textWidth(-1.0), items(nullptr), itemCount(0), glyphPool(nullptr), positionPool(nullptr),
371
0
          needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText),
372
0
          untransformedCoordinates(false)
373
0
{
374
0
}
375
376
QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
377
0
    : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
378
0
      items(nullptr), itemCount(0), glyphPool(nullptr), positionPool(nullptr), textOption(other.textOption),
379
0
      needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations),
380
0
      textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates)
381
0
{
382
0
}
383
384
QStaticTextPrivate::~QStaticTextPrivate()
385
0
{
386
0
    delete[] items;
387
0
    delete[] glyphPool;
388
0
    delete[] positionPool;
389
0
}
390
391
QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
392
0
{
393
0
    return q->data.data();
394
0
}
395
396
namespace {
397
398
    class DrawTextItemRecorder: public QPaintEngine
399
    {
400
    public:
401
        DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
402
0
                : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
403
0
                  m_untransformedCoordinates(untransformedCoordinates), m_currentColor(0, 0, 0, 0)
404
0
        {
405
0
        }
406
407
        virtual void updateState(const QPaintEngineState &newState) override
408
0
        {
409
0
            if (newState.state() & QPaintEngine::DirtyPen
410
0
                && newState.pen().color() != m_currentColor) {
411
0
                m_dirtyPen = true;
412
0
                m_currentColor = newState.pen().color();
413
0
            }
414
0
        }
415
416
        virtual void drawTextItem(const QPointF &position, const QTextItem &textItem) override
417
0
        {
418
0
            const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
419
420
0
            QStaticTextItem currentItem;
421
0
            currentItem.setFontEngine(ti.fontEngine);
422
0
            currentItem.font = ti.font();
423
0
            currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
424
0
            currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
425
0
            currentItem.useBackendOptimizations = m_useBackendOptimizations;
426
0
            if (m_dirtyPen)
427
0
                currentItem.color = m_currentColor;
428
429
0
            QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
430
0
            matrix.translate(position.x(), position.y());
431
432
0
            QVarLengthArray<glyph_t> glyphs;
433
0
            QVarLengthArray<QFixedPoint> positions;
434
0
            ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
435
436
0
            int size = glyphs.size();
437
0
            Q_ASSERT(size == positions.size());
438
0
            currentItem.numGlyphs = size;
439
440
0
            m_glyphs.resize(m_glyphs.size() + size);
441
0
            m_positions.resize(m_glyphs.size());
442
443
0
            glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
444
0
            memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
445
446
0
            QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
447
0
            memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
448
449
0
            m_items.append(currentItem);
450
0
        }
451
452
        virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ) override
453
0
        {
454
            /* intentionally empty */
455
0
        }
456
457
0
        virtual bool begin(QPaintDevice *) override  { return true; }
458
0
        virtual bool end() override { return true; }
459
0
        virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) override {}
460
        virtual Type type() const override
461
0
        {
462
0
            return User;
463
0
        }
464
465
        QList<QStaticTextItem> items() const
466
0
        {
467
0
            return m_items;
468
0
        }
469
470
        QList<QFixedPoint> positions() const
471
0
        {
472
0
            return m_positions;
473
0
        }
474
475
        QList<glyph_t> glyphs() const
476
0
        {
477
0
            return m_glyphs;
478
0
        }
479
480
    private:
481
        QList<QStaticTextItem> m_items;
482
        QList<QFixedPoint> m_positions;
483
        QList<glyph_t> m_glyphs;
484
485
        bool m_dirtyPen;
486
        bool m_useBackendOptimizations;
487
        bool m_untransformedCoordinates;
488
        QColor m_currentColor;
489
    };
490
491
    class DrawTextItemDevice: public QPaintDevice
492
    {
493
    public:
494
        DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
495
0
        {
496
0
            m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
497
0
                                                     useBackendOptimizations);
498
0
        }
499
500
        ~DrawTextItemDevice()
501
0
        {
502
0
            delete m_paintEngine;
503
0
        }
504
505
        int metric(PaintDeviceMetric m) const override
506
0
        {
507
0
            int val;
508
0
            switch (m) {
509
0
            case PdmWidth:
510
0
            case PdmHeight:
511
0
            case PdmWidthMM:
512
0
            case PdmHeightMM:
513
0
                val = 0;
514
0
                break;
515
0
            case PdmDpiX:
516
0
            case PdmPhysicalDpiX:
517
0
                val = qt_defaultDpiX();
518
0
                break;
519
0
            case PdmDpiY:
520
0
            case PdmPhysicalDpiY:
521
0
                val = qt_defaultDpiY();
522
0
                break;
523
0
            case PdmNumColors:
524
0
                val = 16777216;
525
0
                break;
526
0
            case PdmDepth:
527
0
                val = 24;
528
0
                break;
529
0
            case PdmDevicePixelRatio:
530
0
                val = 1;
531
0
                break;
532
0
            case PdmDevicePixelRatioScaled:
533
0
                val = devicePixelRatioFScale();
534
0
                break;
535
0
            default:
536
0
                val = 0;
537
0
                qWarning("DrawTextItemDevice::metric: Invalid metric command");
538
0
            }
539
0
            return val;
540
0
        }
541
542
        virtual QPaintEngine *paintEngine() const override
543
0
        {
544
0
            return m_paintEngine;
545
0
        }
546
547
        QList<glyph_t> glyphs() const
548
0
        {
549
0
            return m_paintEngine->glyphs();
550
0
        }
551
552
        QList<QFixedPoint> positions() const
553
0
        {
554
0
            return m_paintEngine->positions();
555
0
        }
556
557
        QList<QStaticTextItem> items() const
558
0
        {
559
0
            return m_paintEngine->items();
560
0
        }
561
562
    private:
563
        DrawTextItemRecorder *m_paintEngine;
564
    };
565
}
566
567
void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p, const QColor &pen)
568
0
{
569
0
    bool preferRichText = textFormat == Qt::RichText
570
0
                          || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
571
572
0
    if (!preferRichText) {
573
0
        QTextLayout textLayout;
574
0
        textLayout.setText(text);
575
0
        textLayout.setFont(font);
576
0
        textLayout.setTextOption(textOption);
577
0
        textLayout.setCacheEnabled(true);
578
579
0
        qreal height = 0;
580
0
        textLayout.beginLayout();
581
0
        while (1) {
582
0
            QTextLine line = textLayout.createLine();
583
0
            if (!line.isValid())
584
0
                break;
585
0
            line.setLeadingIncluded(true);
586
587
0
            if (textWidth >= 0.0)
588
0
                line.setLineWidth(textWidth);
589
0
            else
590
0
                line.setLineWidth(QFIXED_MAX);
591
0
            line.setPosition(QPointF(0.0, height));
592
0
            height += line.height();
593
0
            if (line.leading() < 0)
594
0
                height += qCeil(line.leading());
595
0
        }
596
0
        textLayout.endLayout();
597
598
0
        actualSize = textLayout.boundingRect().size();
599
0
        p->setPen(pen);
600
0
        textLayout.draw(p, topLeftPosition);
601
0
    } else {
602
0
        QTextDocument document;
603
0
#ifndef QT_NO_CSSPARSER
604
0
        document.setDefaultStyleSheet(QString::fromLatin1("body { color: rgba(%1, %2, %3, %4%) }")
605
0
                                      .arg(QString::number(pen.red()))
606
0
                                      .arg(QString::number(pen.green()))
607
0
                                      .arg(QString::number(pen.blue()))
608
0
                                      .arg(QString::number(pen.alpha())));
609
0
#endif
610
0
        document.setDefaultFont(font);
611
0
        document.setDocumentMargin(0.0);
612
0
#ifndef QT_NO_TEXTHTMLPARSER
613
0
        document.setHtml(text);
614
#else
615
        document.setPlainText(text);
616
#endif
617
0
        if (textWidth >= 0.0)
618
0
            document.setTextWidth(textWidth);
619
0
        else
620
0
            document.adjustSize();
621
0
        document.setDefaultTextOption(textOption);
622
623
0
        p->save();
624
0
        p->translate(topLeftPosition);
625
0
        QAbstractTextDocumentLayout::PaintContext ctx;
626
0
        ctx.palette.setColor(QPalette::Text, pen);
627
0
        document.documentLayout()->draw(p, ctx);
628
0
        p->restore();
629
630
0
        actualSize = document.size();
631
0
    }
632
0
}
633
634
void QStaticTextPrivate::init()
635
0
{
636
0
    delete[] items;
637
0
    delete[] glyphPool;
638
0
    delete[] positionPool;
639
640
0
    position = QPointF(0, 0);
641
642
0
    DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations);
643
0
    {
644
0
        QPainter painter(&device);
645
0
        painter.setFont(font);
646
0
        painter.setTransform(matrix);
647
648
0
        paintText(QPointF(0, 0), &painter, QColor(0, 0, 0, 0));
649
0
    }
650
651
0
    QList<QStaticTextItem> deviceItems = device.items();
652
0
    QList<QFixedPoint> positions = device.positions();
653
0
    QList<glyph_t> glyphs = device.glyphs();
654
655
0
    itemCount = deviceItems.size();
656
0
    items = new QStaticTextItem[itemCount];
657
658
0
    glyphPool = new glyph_t[glyphs.size()];
659
0
    memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
660
661
0
    positionPool = new QFixedPoint[positions.size()];
662
0
    memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
663
664
0
    for (int i=0; i<itemCount; ++i) {
665
0
        items[i] = deviceItems.at(i);
666
667
0
        items[i].glyphs = glyphPool + items[i].glyphOffset;
668
0
        items[i].glyphPositions = positionPool + items[i].positionOffset;
669
0
    }
670
671
0
    needsRelayout = false;
672
0
}
673
674
QT_END_NAMESPACE