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