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

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