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

source code of qtsvg/src/plugins/iconengines/svgiconengine/qsvgiconengine.cpp