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
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
22
24{
25public:
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(scale * 1000));
45 }
46
48 {
49 serialNum = lastSerialNum.fetchAndAddRelaxed(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
58 int serialNum = 0;
60};
61
63
68
71{
72 d->svgFiles = other.d->svgFiles;
73 d->svgBuffers = other.d->svgBuffers;
74 d->addedPixmaps = other.d->addedPixmaps;
75}
76
77
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(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(maybeUncompress(buf)))
118 return true;
119 svgBuffers.remove(key);
120 }
121 QString svgFile = svgFiles.value(key);
122 if (!svgFile.isEmpty()) {
123 if (renderer->load(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, QIcon::Normal, state))
138 return QIcon::Normal;
139 if (tryLoad(renderer, QIcon::Active, state))
140 return QIcon::Active;
141 if (tryLoad(renderer, mode, oppositeState))
142 return mode;
143 if (tryLoad(renderer, QIcon::Normal, oppositeState))
144 return QIcon::Normal;
145 if (tryLoad(renderer, QIcon::Active, oppositeState))
146 return QIcon::Active;
147 if (tryLoad(renderer, oppositeMode, state))
148 return oppositeMode;
149 if (tryLoad(renderer, oppositeMode, oppositeState))
150 return oppositeMode;
151 } else {
152 const QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
153 if (tryLoad(renderer, oppositeMode, state))
154 return oppositeMode;
155 if (tryLoad(renderer, mode, oppositeState))
156 return mode;
157 if (tryLoad(renderer, oppositeMode, oppositeState))
158 return oppositeMode;
159 if (tryLoad(renderer, QIcon::Disabled, state))
160 return QIcon::Disabled;
161 if (tryLoad(renderer, QIcon::Selected, state))
162 return QIcon::Selected;
163 if (tryLoad(renderer, QIcon::Disabled, oppositeState))
164 return QIcon::Disabled;
165 if (tryLoad(renderer, QIcon::Selected, 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, 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(pmckey, &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, mode, state);
204 if (!renderer.isValid())
205 return pm;
206
207 QSize actualSize = renderer.defaultSize();
208 if (!actualSize.isNull())
209 actualSize.scale(size * scale, Qt::KeepAspectRatio);
210
211 if (actualSize.isEmpty())
212 return pm;
213
214 pm = QPixmap(actualSize);
215 pm.fill(Qt::transparent);
216 QPainter p(&pm);
217 renderer.render(&p);
218 p.end();
219 if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
220 if (loadmode != mode && mode != QIcon::Normal) {
221 const QPixmap generated = QGuiApplicationPrivate::instance()->applyQIconStyleHelper(mode, pm);
222 if (!generated.isNull())
223 pm = generated;
224 }
225 }
226
227 if (!pm.isNull()) {
228 pm.setDevicePixelRatio(scale);
229 QPixmapCache::insert(pmckey, 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(d->hashKey(mode, state), pixmap);
241}
242
244
245static FileType fileType(const QFileInfo &fi)
246{
247 const QString &suffix = fi.completeSuffix();
248 if (suffix.endsWith(QLatin1String("svg"), Qt::CaseInsensitive))
249 return SvgFile;
250 if (suffix.endsWith(QLatin1String("svgz"), Qt::CaseInsensitive)
251 || suffix.endsWith(QLatin1String("svg.gz"), Qt::CaseInsensitive)) {
252 return CompressedSvgFile;
253 }
254#if QT_CONFIG(mimetype)
255 const QString &mimeTypeName = QMimeDatabase().mimeTypeForFile(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(d->hashKey(mode, state), abs);
280 }
281 } else if (type == OtherFile) {
282 QPixmap pm(abs);
283 if (!pm.isNull())
284 addPixmap(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(rect, pixmap(pixmapSize, mode, state));
296}
297
299{
300 return d->svgFiles.isEmpty() && d->addedPixmaps.isEmpty() && d->svgBuffers.isEmpty();
301}
302
304{
305 return QLatin1String("svg");
306}
307
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(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(d->hashKey(QIcon::Normal, QIcon::Off), 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(QIODevice::ReadOnly))
382 buf = f.readAll();
383#ifndef QT_NO_COMPRESS
384 buf = qCompress(buf);
385#endif
386 svgBuffers.insert(it.first, 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(QIcon::Normal, 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(QIODevice::ReadOnly))
402 buf = f.readAll();
403 }
404 }
405#ifndef QT_NO_COMPRESS
406 buf = qCompress(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
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)