Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtsvg/src/plugins/iconengines/svgiconengine/qsvgiconengine.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
#include "qsvgiconengine.h"
4
5
#ifndef QT_NO_SVGRENDERER
6
7
#include "qpainter.h"
8
#include "qpixmap.h"
9
#include "qsvgrenderer.h"
10
#include "qpixmapcache.h"
11
#include "qfileinfo.h"
12
#if QT_CONFIG(mimetype)
13
#include <qmimedatabase.h>
14
#include <qmimetype.h>
15
#endif
16
#include <QAtomicInt>
17
#include "qdebug.h"
18
#include <private/qguiapplication_p.h>
19
#include <private/qhexstring_p.h>
20
21
QT_BEGIN_NAMESPACE
22
23
class QSvgIconEnginePrivate : public QSharedData
24
{
25
public:
26
    QSvgIconEnginePrivate()
27
0
    {
28
0
        stepSerialNum();
29
0
    }
30
31
    static int hashKey(QIcon::Mode mode, QIcon::State state)
32
0
    {
33
0
        return ((mode << 4) | state);
34
0
    }
35
36
    QString pmcKey(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) const
37
0
    {
38
0
        return QLatin1String("$qt_svgicon_")
39
0
                % HexString<int>(serialNum)
40
0
                % HexString<qint8>(mode)
41
0
                % HexString<qint8>(state)
42
0
                % HexString<int>(size.width())
43
0
                % HexString<int>(size.height())
44
0
                % HexString<qint16>(qRound(scale * 1000));
45
0
    }
46
47
    void stepSerialNum()
48
0
    {
49
0
        serialNum = lastSerialNum.fetchAndAddRelaxed(1);
50
0
    }
51
52
    bool tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
53
    QIcon::Mode loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
54
55
    QHash<int, QString> svgFiles;
56
    QHash<int, QByteArray> svgBuffers;
57
    QMultiHash<int, QPixmap> addedPixmaps;
58
    int serialNum = 0;
59
    static QAtomicInt lastSerialNum;
60
};
61
62
QAtomicInt QSvgIconEnginePrivate::lastSerialNum;
63
64
QSvgIconEngine::QSvgIconEngine()
65
0
    : d(new QSvgIconEnginePrivate)
66
0
{
67
0
}
68
69
QSvgIconEngine::QSvgIconEngine(const QSvgIconEngine &other)
70
0
    : QIconEngine(other), d(new QSvgIconEnginePrivate)
71
0
{
72
0
    d->svgFiles = other.d->svgFiles;
73
0
    d->svgBuffers = other.d->svgBuffers;
74
0
    d->addedPixmaps = other.d->addedPixmaps;
75
0
}
76
77
78
QSvgIconEngine::~QSvgIconEngine()
79
0
{
80
0
}
81
82
83
QSize QSvgIconEngine::actualSize(const QSize &size, QIcon::Mode mode,
84
                                 QIcon::State state)
85
0
{
86
0
    if (!d->addedPixmaps.isEmpty()) {
87
0
        const auto key = d->hashKey(mode, state);
88
0
        auto it = d->addedPixmaps.constFind(key);
89
0
        while (it != d->addedPixmaps.end() && it.key() == key) {
90
0
            const auto &pm = it.value();
91
0
            if (!pm.isNull() && pm.size() == size)
92
0
                return size;
93
0
            ++it;
94
0
        }
95
0
    }
96
97
0
    QPixmap pm = pixmap(size, mode, state);
98
0
    if (pm.isNull())
99
0
        return QSize();
100
0
    return pm.size();
101
0
}
102
103
static inline QByteArray maybeUncompress(const QByteArray &ba)
104
0
{
105
0
#ifndef QT_NO_COMPRESS
106
0
    return qUncompress(ba);
107
#else
108
    return ba;
109
#endif
110
0
}
111
112
bool QSvgIconEnginePrivate::tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
113
0
{
114
0
    const auto key = hashKey(mode, state);
115
0
    QByteArray buf = svgBuffers.value(key);
116
0
    if (!buf.isEmpty()) {
117
0
        if (renderer->load(maybeUncompress(buf)))
118
0
            return true;
119
0
        svgBuffers.remove(key);
120
0
    }
121
0
    QString svgFile = svgFiles.value(key);
122
0
    if (!svgFile.isEmpty()) {
123
0
        if (renderer->load(svgFile))
124
0
            return true;
125
0
    }
126
0
    return false;
127
0
}
128
129
QIcon::Mode QSvgIconEnginePrivate::loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
130
0
{
131
0
    if (tryLoad(renderer, mode, state))
132
0
        return mode;
133
134
0
    const QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On;
135
0
    if (mode == QIcon::Disabled || mode == QIcon::Selected) {
136
0
        const QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled;
137
0
        if (tryLoad(renderer, QIcon::Normal, state))
138
0
            return QIcon::Normal;
139
0
        if (tryLoad(renderer, QIcon::Active, state))
140
0
            return QIcon::Active;
141
0
        if (tryLoad(renderer, mode, oppositeState))
142
0
            return mode;
143
0
        if (tryLoad(renderer, QIcon::Normal, oppositeState))
144
0
            return QIcon::Normal;
145
0
        if (tryLoad(renderer, QIcon::Active, oppositeState))
146
0
            return QIcon::Active;
147
0
        if (tryLoad(renderer, oppositeMode, state))
148
0
            return oppositeMode;
149
0
        if (tryLoad(renderer, oppositeMode, oppositeState))
150
0
            return oppositeMode;
151
0
    } else {
152
0
        const QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
153
0
        if (tryLoad(renderer, oppositeMode, state))
154
0
            return oppositeMode;
155
0
        if (tryLoad(renderer, mode, oppositeState))
156
0
            return mode;
157
0
        if (tryLoad(renderer, oppositeMode, oppositeState))
158
0
            return oppositeMode;
159
0
        if (tryLoad(renderer, QIcon::Disabled, state))
160
0
            return QIcon::Disabled;
161
0
        if (tryLoad(renderer, QIcon::Selected, state))
162
0
            return QIcon::Selected;
163
0
        if (tryLoad(renderer, QIcon::Disabled, oppositeState))
164
0
            return QIcon::Disabled;
165
0
        if (tryLoad(renderer, QIcon::Selected, oppositeState))
166
0
            return QIcon::Selected;
167
0
    }
168
0
    return QIcon::Normal;
169
0
}
170
171
QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
172
                               QIcon::State state)
173
0
{
174
0
    return scaledPixmap(size, mode, state, 1.0);
175
0
}
176
177
QPixmap QSvgIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state,
178
                                     qreal scale)
179
0
{
180
0
    QPixmap pm;
181
182
0
    QString pmckey(d->pmcKey(size, mode, state, scale));
183
0
    if (QPixmapCache::find(pmckey, &pm))
184
0
        return pm;
185
186
0
    if (!d->addedPixmaps.isEmpty()) {
187
0
        const auto realSize = size * scale;
188
0
        const auto key = d->hashKey(mode, state);
189
0
        auto it = d->addedPixmaps.constFind(key);
190
0
        while (it != d->addedPixmaps.end() && it.key() == key) {
191
0
            const auto &pm = it.value();
192
0
            if (!pm.isNull()) {
193
                // we don't care about dpr here - don't use QSvgIconEngine when
194
                // there are a lot of raster images are to handle.
195
0
                if (pm.size() == realSize)
196
0
                    return pm;
197
0
            }
198
0
            ++it;
199
0
        }
200
0
    }
201
202
0
    QSvgRenderer renderer;
203
0
    const QIcon::Mode loadmode = d->loadDataForModeAndState(&renderer, mode, state);
204
0
    if (!renderer.isValid())
205
0
        return pm;
206
207
0
    QSize actualSize = renderer.defaultSize();
208
0
    if (!actualSize.isNull())
209
0
        actualSize.scale(size * scale, Qt::KeepAspectRatio);
210
211
0
    if (actualSize.isEmpty())
212
0
        return pm;
213
214
0
    pm = QPixmap(actualSize);
215
0
    pm.fill(Qt::transparent);
216
0
    QPainter p(&pm);
217
0
    renderer.render(&p);
218
0
    p.end();
219
0
    if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
220
0
        if (loadmode != mode && mode != QIcon::Normal) {
221
0
            const QPixmap generated = QGuiApplicationPrivate::instance()->applyQIconStyleHelper(mode, pm);
222
0
            if (!generated.isNull())
223
0
                pm = generated;
224
0
        }
225
0
    }
226
227
0
    if (!pm.isNull()) {
228
0
        pm.setDevicePixelRatio(scale);
229
0
        QPixmapCache::insert(pmckey, pm);
230
0
    }
231
232
0
    return pm;
233
0
}
234
235
236
void QSvgIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode,
237
                               QIcon::State state)
238
0
{
239
0
    d->stepSerialNum();
240
0
    d->addedPixmaps.insert(d->hashKey(mode, state), pixmap);
241
0
}
242
243
enum FileType { OtherFile, SvgFile, CompressedSvgFile };
244
245
static FileType fileType(const QFileInfo &fi)
246
0
{
247
0
    const QString &suffix = fi.completeSuffix();
248
0
    if (suffix.endsWith(QLatin1String("svg"), Qt::CaseInsensitive))
249
0
        return SvgFile;
250
0
    if (suffix.endsWith(QLatin1String("svgz"), Qt::CaseInsensitive)
251
0
        || suffix.endsWith(QLatin1String("svg.gz"), Qt::CaseInsensitive)) {
252
0
        return CompressedSvgFile;
253
0
    }
254
0
#if QT_CONFIG(mimetype)
255
0
    const QString &mimeTypeName = QMimeDatabase().mimeTypeForFile(fi).name();
256
0
    if (mimeTypeName == QLatin1String("image/svg+xml"))
257
0
        return SvgFile;
258
0
    if (mimeTypeName == QLatin1String("image/svg+xml-compressed"))
259
0
        return CompressedSvgFile;
260
0
#endif
261
0
    return OtherFile;
262
0
}
263
264
void QSvgIconEngine::addFile(const QString &fileName, const QSize &,
265
                             QIcon::Mode mode, QIcon::State state)
266
0
{
267
0
    if (!fileName.isEmpty()) {
268
0
         const QFileInfo fi(fileName);
269
0
         const QString abs = fi.absoluteFilePath();
270
0
         const FileType type = fileType(fi);
271
0
#ifndef QT_NO_COMPRESS
272
0
         if (type == SvgFile || type == CompressedSvgFile) {
273
#else
274
         if (type == SvgFile) {
275
#endif
276
0
             QSvgRenderer renderer(abs);
277
0
             if (renderer.isValid()) {
278
0
                 d->stepSerialNum();
279
0
                 d->svgFiles.insert(d->hashKey(mode, state), abs);
280
0
             }
281
0
         } else if (type == OtherFile) {
282
0
             QPixmap pm(abs);
283
0
             if (!pm.isNull())
284
0
                 addPixmap(pm, mode, state);
285
0
         }
286
0
    }
287
0
}
288
289
void QSvgIconEngine::paint(QPainter *painter, const QRect &rect,
290
                           QIcon::Mode mode, QIcon::State state)
291
0
{
292
0
    QSize pixmapSize = rect.size();
293
0
    if (painter->device())
294
0
        pixmapSize *= painter->device()->devicePixelRatio();
295
0
    painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
296
0
}
297
298
bool QSvgIconEngine::isNull()
299
0
{
300
0
    return d->svgFiles.isEmpty() && d->addedPixmaps.isEmpty() && d->svgBuffers.isEmpty();
301
0
}
302
303
QString QSvgIconEngine::key() const
304
0
{
305
0
    return QLatin1String("svg");
306
0
}
307
308
QIconEngine *QSvgIconEngine::clone() const
309
0
{
310
0
    return new QSvgIconEngine(*this);
311
0
}
312
313
314
bool QSvgIconEngine::read(QDataStream &in)
315
0
{
316
0
    d = new QSvgIconEnginePrivate;
317
318
0
    if (in.version() >= QDataStream::Qt_4_4) {
319
0
        int isCompressed;
320
0
        QHash<int, QString> fileNames;  // For memoryoptimization later
321
0
        in >> fileNames >> isCompressed >> d->svgBuffers;
322
0
#ifndef QT_NO_COMPRESS
323
0
        if (!isCompressed) {
324
0
            for (auto &svgBuf : d->svgBuffers)
325
0
                svgBuf = qCompress(svgBuf);
326
0
        }
327
#else
328
        if (isCompressed) {
329
            qWarning("QSvgIconEngine: Can not decompress SVG data");
330
            d->svgBuffers.clear();
331
        }
332
#endif
333
0
        int hasAddedPixmaps;
334
0
        in >> hasAddedPixmaps;
335
0
        if (hasAddedPixmaps) {
336
0
            in >> d->addedPixmaps;
337
0
        }
338
0
    }
339
0
    else {
340
0
        QPixmap pixmap;
341
0
        QByteArray data;
342
0
        uint mode;
343
0
        uint state;
344
0
        int num_entries;
345
346
0
        in >> data;
347
0
        if (!data.isEmpty()) {
348
0
#ifndef QT_NO_COMPRESS
349
0
            data = qUncompress(data);
350
0
#endif
351
0
            if (!data.isEmpty())
352
0
                d->svgBuffers.insert(d->hashKey(QIcon::Normal, QIcon::Off), data);
353
0
        }
354
0
        in >> num_entries;
355
0
        for (int i=0; i<num_entries; ++i) {
356
0
            if (in.atEnd())
357
0
                return false;
358
0
            in >> pixmap;
359
0
            in >> mode;
360
0
            in >> state;
361
            // The pm list written by 4.3 is buggy and/or useless, so ignore.
362
            //addPixmap(pixmap, QIcon::Mode(mode), QIcon::State(state));
363
0
        }
364
0
    }
365
366
0
    return true;
367
0
}
368
369
370
bool QSvgIconEngine::write(QDataStream &out) const
371
0
{
372
0
    if (out.version() >= QDataStream::Qt_4_4) {
373
0
        int isCompressed = 0;
374
0
#ifndef QT_NO_COMPRESS
375
0
        isCompressed = 1;
376
0
#endif
377
0
        QHash<int, QByteArray> svgBuffers = d->svgBuffers;
378
0
        for (const auto &it : d->svgFiles.asKeyValueRange()) {
379
0
            QByteArray buf;
380
0
            QFile f(it.second);
381
0
            if (f.open(QIODevice::ReadOnly))
382
0
                buf = f.readAll();
383
0
#ifndef QT_NO_COMPRESS
384
0
            buf = qCompress(buf);
385
0
#endif
386
0
            svgBuffers.insert(it.first, buf);
387
0
        }
388
0
        out << d->svgFiles << isCompressed << svgBuffers;
389
0
        if (d->addedPixmaps.isEmpty())
390
0
            out << 0;
391
0
        else
392
0
            out << 1 << d->addedPixmaps;
393
0
    }
394
0
    else {
395
0
        const auto key = d->hashKey(QIcon::Normal, QIcon::Off);
396
0
        QByteArray buf = d->svgBuffers.value(key);
397
0
        if (buf.isEmpty()) {
398
0
            QString svgFile = d->svgFiles.value(key);
399
0
            if (!svgFile.isEmpty()) {
400
0
                QFile f(svgFile);
401
0
                if (f.open(QIODevice::ReadOnly))
402
0
                    buf = f.readAll();
403
0
            }
404
0
        }
405
0
#ifndef QT_NO_COMPRESS
406
0
        buf = qCompress(buf);
407
0
#endif
408
0
        out << buf;
409
        // 4.3 has buggy handling of added pixmaps, so don't write any
410
0
        out << (int)0;
411
0
    }
412
0
    return true;
413
0
}
414
415
QT_END_NAMESPACE
416
417
#endif // QT_NO_SVGRENDERER