Coverage Report

Created: 2026-08-11 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/painting/qtextureglyphcache.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
// Qt-Security score:significant reason:default
4
5
#include <qmath.h>
6
7
#include "qtextureglyphcache_p.h"
8
#include "private/qfontengine_p.h"
9
#include "private/qnumeric_p.h"
10
11
#include <QtGui/qpainterpath.h>
12
13
QT_BEGIN_NAMESPACE
14
15
// #define CACHE_DEBUG
16
17
// out-of-line to avoid vtable duplication, breaking e.g. RTTI
18
QTextureGlyphCache::~QTextureGlyphCache()
19
0
{
20
0
}
21
22
int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const
23
0
{
24
    // Test 12 different subpixel positions since it factors into 3*4 so it gives
25
    // the coverage we need.
26
27
0
    const int NumSubpixelPositions = 12;
28
29
0
    QImage images[NumSubpixelPositions];
30
0
    int numImages = 0;
31
0
    for (int i = 0; i < NumSubpixelPositions; ++i) {
32
0
        QImage img = textureMapForGlyph(glyph, QFixedPoint(QFixed::fromReal(i / 12.0), 0));
33
34
0
        if (numImages == 0) {
35
0
            QPainterPath path;
36
0
            QFixedPoint point;
37
0
            m_current_fontengine->addGlyphsToPath(&glyph, &point, 1, &path, QTextItem::RenderFlags());
38
39
            // Glyph is space, return 0 to indicate that we need to keep trying
40
0
            if (path.isEmpty())
41
0
                break;
42
43
0
            images[numImages++] = std::move(img);
44
0
        } else {
45
0
            bool found = false;
46
0
            for (int j = 0; j < numImages; ++j) {
47
0
                if (images[j] == img) {
48
0
                    found = true;
49
0
                    break;
50
0
                }
51
0
            }
52
0
            if (!found)
53
0
                images[numImages++] = std::move(img);
54
0
        }
55
0
    }
56
57
0
    return numImages;
58
0
}
59
60
bool QTextureGlyphCache::populate(QFontEngine *fontEngine,
61
                                  qsizetype numGlyphs,
62
                                  const glyph_t *glyphs,
63
                                  const QFixedPoint *positions,
64
                                  QPainter::RenderHints renderHints,
65
                                  bool includeGlyphCacheScale)
66
0
{
67
#ifdef CACHE_DEBUG
68
    printf("Populating with %lld glyphs\n", static_cast<long long>(numGlyphs));
69
    qDebug() << " -> current transformation: " << m_transform;
70
#endif
71
72
0
    m_current_fontengine = fontEngine;
73
0
    const int padding = glyphPadding();
74
0
    const int paddingDoubled = padding * 2;
75
76
0
    bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions();
77
0
    bool verticalSubPixelPositions = fontEngine->supportsVerticalSubPixelPositions()
78
0
            && (renderHints & QPainter::VerticalSubpixelPositioning) != 0;
79
0
    if (fontEngine->m_subPixelPositionCount == 0) {
80
0
        if (!supportsSubPixelPositions) {
81
0
            fontEngine->m_subPixelPositionCount = 1;
82
0
        } else {
83
0
            qsizetype i = 0;
84
0
            while (fontEngine->m_subPixelPositionCount == 0 && i < numGlyphs)
85
0
                fontEngine->m_subPixelPositionCount = calculateSubPixelPositionCount(glyphs[i++]);
86
0
        }
87
0
    }
88
89
0
    if (m_cx == 0 && m_cy == 0) {
90
0
        m_cx = padding;
91
0
        m_cy = padding;
92
0
    }
93
94
0
    qreal glyphCacheScaleX = transform().m11();
95
0
    qreal glyphCacheScaleY = transform().m22();
96
97
0
    QHash<GlyphAndSubPixelPosition, Coord> listItemCoordinates;
98
0
    int rowHeight = 0;
99
100
    // check each glyph for its metrics and get the required rowHeight.
101
0
    for (qsizetype i = 0; i < numGlyphs; ++i) {
102
0
        const glyph_t glyph = glyphs[i];
103
104
0
        QFixedPoint subPixelPosition;
105
0
        if (supportsSubPixelPositions) {
106
0
            QFixedPoint pos = positions != nullptr ? positions[i] : QFixedPoint();
107
0
            if (includeGlyphCacheScale) {
108
0
                pos = QFixedPoint(QFixed::fromReal(pos.x.toReal() * glyphCacheScaleX),
109
0
                                  QFixed::fromReal(pos.y.toReal() * glyphCacheScaleY));
110
0
            }
111
0
            subPixelPosition = fontEngine->subPixelPositionFor(pos);
112
0
            if (!verticalSubPixelPositions)
113
0
                subPixelPosition.y = 0;
114
0
        }
115
116
0
        if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition)))
117
0
            continue;
118
0
        if (listItemCoordinates.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition)))
119
0
            continue;
120
121
0
        glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, m_transform, m_format);
122
123
#ifdef CACHE_DEBUG
124
        printf("(%4x): w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n",
125
               glyph,
126
               metrics.width.toReal(),
127
               metrics.height.toReal(),
128
               metrics.xoff.toReal(),
129
               metrics.yoff.toReal(),
130
               metrics.x.toReal(),
131
               metrics.y.toReal());
132
#endif
133
0
        GlyphAndSubPixelPosition key(glyph, subPixelPosition);
134
0
        int glyph_width = metrics.width.ceil().toInt();
135
0
        int glyph_height = metrics.height.ceil().toInt();
136
0
        if (glyph_height == 0 || glyph_width == 0) {
137
            // Avoid multiple calls to boundingBox() for non-printable characters
138
0
            Coord c = { 0, 0, 0, 0, 0, 0 };
139
0
            coords.insert(key, c);
140
0
            continue;
141
0
        }
142
        // align to 8-bit boundary
143
0
        if (m_format == QFontEngine::Format_Mono)
144
0
            glyph_width = (glyph_width+7)&~7;
145
146
0
        Coord c = { 0, 0, // will be filled in later
147
0
                    glyph_width,
148
0
                    glyph_height, // texture coords
149
0
                    metrics.x.truncate(),
150
0
                    -metrics.y.truncate() }; // baseline for horizontal scripts
151
152
0
        listItemCoordinates.insert(key, c);
153
0
        rowHeight = qMax(rowHeight, glyph_height);
154
0
    }
155
0
    if (listItemCoordinates.isEmpty())
156
0
        return true;
157
158
0
    rowHeight += paddingDoubled;
159
160
0
    if (m_w == 0) {
161
0
        if (fontEngine->maxCharWidth() <= QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH)
162
0
            m_w = QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH;
163
0
        else
164
0
            m_w = qNextPowerOfTwo(qCeil(fontEngine->maxCharWidth()) - 1);
165
0
    }
166
167
    // now actually use the coords and paint the wanted glyps into cache.
168
0
    QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = listItemCoordinates.begin();
169
0
    int requiredWidth = m_w;
170
0
    while (iter != listItemCoordinates.end()) {
171
0
        Coord c = iter.value();
172
173
0
        m_currentRowHeight = qMax(m_currentRowHeight, c.h);
174
175
0
        if (m_cx + c.w + padding > requiredWidth) {
176
0
            int new_width = requiredWidth*2;
177
0
            while (new_width < m_cx + c.w + padding)
178
0
                new_width *= 2;
179
0
            if (new_width <= maxTextureWidth()) {
180
0
                requiredWidth = new_width;
181
0
            } else {
182
                // no room on the current line, start new glyph strip
183
0
                m_cx = padding;
184
0
                m_cy += m_currentRowHeight + paddingDoubled;
185
0
                m_currentRowHeight = c.h; // New row
186
0
            }
187
0
        }
188
189
0
        if (maxTextureHeight() > 0 && m_cy + c.h + padding > maxTextureHeight()) {
190
            // We can't make a cache of the required size, so we bail out
191
0
            return false;
192
0
        }
193
194
0
        c.x = m_cx;
195
0
        c.y = m_cy;
196
197
0
        coords.insert(iter.key(), c);
198
0
        m_pendingGlyphs.insert(iter.key(), c);
199
200
0
        m_cx += c.w + paddingDoubled;
201
0
        ++iter;
202
0
    }
203
0
    return true;
204
205
0
}
206
207
void QTextureGlyphCache::fillInPendingGlyphs()
208
0
{
209
0
    if (!hasPendingGlyphs())
210
0
        return;
211
212
0
    int requiredHeight = m_h;
213
0
    int requiredWidth = m_w; // Use a minimum size to avoid a lot of initial reallocations
214
0
    const int padding = glyphPadding();
215
0
    {
216
0
        QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin();
217
0
        while (iter != m_pendingGlyphs.end()) {
218
0
            Coord c = iter.value();
219
0
            requiredHeight = qMax(requiredHeight, c.y + c.h + padding);
220
0
            requiredWidth = qMax(requiredWidth, c.x + c.w + padding);
221
0
            ++iter;
222
0
        }
223
0
    }
224
225
0
    if (isNull() || requiredHeight > m_h || requiredWidth > m_w) {
226
0
        if (isNull())
227
0
            createCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1));
228
0
        else
229
0
            resizeCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1));
230
0
    }
231
232
0
    beginFillTexture();
233
0
    {
234
0
        QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin();
235
0
        while (iter != m_pendingGlyphs.end()) {
236
0
            GlyphAndSubPixelPosition key = iter.key();
237
0
            fillTexture(iter.value(), key.glyph, key.subPixelPosition);
238
239
0
            ++iter;
240
0
        }
241
0
    }
242
0
    endFillTexture();
243
244
0
    m_pendingGlyphs.clear();
245
0
}
246
247
QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const
248
0
{
249
0
    switch (m_format) {
250
0
    case QFontEngine::Format_A32:
251
0
        return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, m_transform);
252
0
    case QFontEngine::Format_ARGB:
253
0
        return m_current_fontengine->bitmapForGlyph(g, subPixelPosition, m_transform, color());
254
0
    default:
255
0
        return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, m_transform);
256
0
    }
257
0
}
258
259
/************************************************************************
260
 * QImageTextureGlyphCache
261
 */
262
263
// out-of-line to avoid vtable duplication, breaking e.g. RTTI
264
QImageTextureGlyphCache::~QImageTextureGlyphCache()
265
0
{
266
0
}
267
268
void QImageTextureGlyphCache::resizeTextureData(int width, int height)
269
0
{
270
0
    m_image = m_image.copy(0, 0, width, height);
271
    // Regions not part of the copy are initialized to 0, and that is just what
272
    // we need.
273
0
}
274
275
void QImageTextureGlyphCache::createTextureData(int width, int height)
276
0
{
277
0
    switch (m_format) {
278
0
    case QFontEngine::Format_Mono:
279
0
        m_image = QImage(width, height, QImage::Format_Mono);
280
0
        break;
281
0
    case QFontEngine::Format_A8:
282
0
        m_image = QImage(width, height, QImage::Format_Alpha8);
283
0
        break;
284
0
    case QFontEngine::Format_A32:
285
0
        m_image = QImage(width, height, QImage::Format_RGB32);
286
0
        break;
287
0
    case QFontEngine::Format_ARGB:
288
0
        m_image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
289
0
        break;
290
0
    default:
291
0
        Q_UNREACHABLE();
292
0
    }
293
294
    // Regions not touched by the glyphs must be initialized to 0. (such
295
    // locations may in fact be sampled with styled (shifted) text materials)
296
    // When resizing, the QImage copy() does this implicitly but the initial
297
    // contents must be zeroed out explicitly here.
298
0
    m_image.fill(0);
299
0
}
300
301
void QImageTextureGlyphCache::fillTexture(const Coord &c,
302
                                          glyph_t g,
303
                                          const QFixedPoint &subPixelPosition)
304
0
{
305
0
    QImage mask = textureMapForGlyph(g, subPixelPosition);
306
0
    if (mask.isNull())
307
0
        return;
308
309
#ifdef CACHE_DEBUG
310
    printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height());
311
    if (mask.width() > c.w || mask.height() > c.h) {
312
        printf("   ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n", mask.width(), mask.height(), c.w,c.h);
313
        return;
314
    }
315
#endif
316
0
    if (m_format == QFontEngine::Format_A32
317
0
        || m_format == QFontEngine::Format_ARGB) {
318
0
        QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()),
319
0
                   qMin(mask.width(), c.w), qMin(mask.height(), c.h), m_image.bytesPerLine(),
320
0
                   m_image.format());
321
0
        QPainter p(&ref);
322
0
        p.setCompositionMode(QPainter::CompositionMode_Source);
323
0
        p.fillRect(0, 0, c.w, c.h, QColor(0,0,0,0)); // TODO optimize this
324
0
        p.drawImage(0, 0, mask);
325
0
        p.end();
326
0
    } else if (m_format == QFontEngine::Format_Mono) {
327
0
        if (mask.depth() > 1) {
328
            // TODO optimize this
329
0
            mask.convertTo(QImage::Format_Alpha8);
330
0
            mask.reinterpretAsFormat(QImage::Format_Grayscale8);
331
0
            mask.invertPixels();
332
0
            mask.convertTo(QImage::Format_Mono, Qt::ThresholdDither);
333
0
        }
334
335
0
        int mw = qMin(mask.width(), c.w);
336
0
        int mh = qMin(mask.height(), c.h);
337
0
        uchar *d = m_image.bits();
338
0
        qsizetype dbpl = m_image.bytesPerLine();
339
340
0
        for (int y = 0; y < c.h; ++y) {
341
0
            uchar *dest = d + (c.y + y) *dbpl + c.x/8;
342
343
0
            if (y < mh) {
344
0
                const uchar *src = mask.constScanLine(y);
345
0
                for (int x = 0; x < c.w/8; ++x) {
346
0
                    if (x < (mw+7)/8)
347
0
                        dest[x] = src[x];
348
0
                    else
349
0
                        dest[x] = 0;
350
0
                }
351
0
            } else {
352
0
                for (int x = 0; x < c.w/8; ++x)
353
0
                    dest[x] = 0;
354
0
            }
355
0
        }
356
0
    } else { // A8
357
0
        int mw = qMin(mask.width(), c.w);
358
0
        int mh = qMin(mask.height(), c.h);
359
0
        uchar *d = m_image.bits();
360
0
        qsizetype dbpl = m_image.bytesPerLine();
361
362
0
        if (mask.depth() == 1) {
363
0
            for (int y = 0; y < c.h; ++y) {
364
0
                uchar *dest = d + (c.y + y) *dbpl + c.x;
365
0
                if (y < mh) {
366
0
                    const uchar *src = mask.constScanLine(y);
367
0
                    for (int x = 0; x < c.w; ++x) {
368
0
                        if (x < mw)
369
0
                            dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0;
370
0
                    }
371
0
                }
372
0
            }
373
0
        } else if (mask.depth() == 8) {
374
0
            for (int y = 0; y < c.h; ++y) {
375
0
                uchar *dest = d + (c.y + y) *dbpl + c.x;
376
0
                if (y < mh) {
377
0
                    const uchar *src = mask.constScanLine(y);
378
0
                    for (int x = 0; x < c.w; ++x) {
379
0
                        if (x < mw)
380
0
                            dest[x] = src[x];
381
0
                    }
382
0
                }
383
0
            }
384
0
        }
385
0
    }
386
387
#ifdef CACHE_DEBUG
388
//     QPainter p(&m_image);
389
//     p.drawLine(
390
    int margin = m_current_fontengine ? m_current_fontengine->glyphMargin(m_format) : 0;
391
    QPoint base(c.x + margin, c.y + margin + c.baseLineY-1);
392
    if (m_image.rect().contains(base))
393
        m_image.setPixel(base, 255);
394
    m_image.save(QString::fromLatin1("cache-%1.png").arg(qint64(this)));
395
#endif
396
0
}
397
398
QT_END_NAMESPACE