Coverage Report

Created: 2026-02-10 07:39

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