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

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