Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qsvgiconengine.h"
++
++ #ifndef QT_NO_SVGRENDERER
++
++ #include "qpainter.h"
++ #include "qpixmap.h"
++ #include "qsvgrenderer.h"
++ #include "qpixmapcache.h"
++ #include "qfileinfo.h"
++ #if QT_CONFIG(mimetype)
++ #include <qmimedatabase.h>
++ #include <qmimetype.h>
++ #endif
++ #include <QAtomicInt>
++ #include "qdebug.h"
++ #include <private/qguiapplication_p.h>
++ #include <private/qhexstring_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ class QSvgIconEnginePrivate : public QSharedData
++ {
++ public:
++ QSvgIconEnginePrivate()
++ {
++ stepSerialNum();
++ }
++
++ static int hashKey(QIcon::Mode mode, QIcon::State state)
++ {
++ return ((mode << 4) | state);
++ }
++
++ QString pmcKey(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) const
++ {
++ return QLatin1String("$qt_svgicon_")
++ % HexString<int>(serialNum)
++ % HexString<qint8>(mode)
++ % HexString<qint8>(state)
++ % HexString<int>(size.width())
++ % HexString<int>(size.height())
++ % HexString<qint16>(qRound(scale * 1000));
++ }
++
++ void stepSerialNum()
++ {
++ serialNum = lastSerialNum.fetchAndAddRelaxed(1);
++ }
++
++ bool tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
++ QIcon::Mode loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
++
++ QHash<int, QString> svgFiles;
++ QHash<int, QByteArray> svgBuffers;
++ QMultiHash<int, QPixmap> addedPixmaps;
++ int serialNum{0};
++ static QAtomicInt lastSerialNum;
++ };
++
++ QAtomicInt QSvgIconEnginePrivate::lastSerialNum;
++
++ QSvgIconEngine::QSvgIconEngine()
++ : d(new QSvgIconEnginePrivate)
++ {
++ }
++
++ QSvgIconEngine::QSvgIconEngine(const QSvgIconEngine &other)
++ : QIconEngine(other), d(new QSvgIconEnginePrivate)
++ {
++ d->svgFiles = other.d->svgFiles;
++ d->svgBuffers = other.d->svgBuffers;
++ d->addedPixmaps = other.d->addedPixmaps;
++ }
++
++
++ QSvgIconEngine::~QSvgIconEngine()
++ {
++ }
++
++
++ QSize QSvgIconEngine::actualSize(const QSize &size, QIcon::Mode mode,
++ QIcon::State state)
++ {
++ if (!d->addedPixmaps.isEmpty()) {
++ const auto key = d->hashKey(mode, state);
++ auto it = d->addedPixmaps.constFind(key);
++ while (it != d->addedPixmaps.cend() && it.key() == key) {
++ const auto &pm = it.value();
++ if (!pm.isNull() && pm.size() == size)
++ return size;
++ ++it;
++ }
++ }
++
++ QPixmap pm = pixmap(size, mode, state);
++ if (pm.isNull())
++ return QSize();
++ return pm.size();
++ }
++
++ static inline QByteArray maybeUncompress(const QByteArray &ba)
++ {
++ #ifndef QT_NO_COMPRESS
++ return qUncompress(ba);
++ #else
++ return ba;
++ #endif
++ }
++
++ bool QSvgIconEnginePrivate::tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
++ {
++ const auto key = hashKey(mode, state);
++ QByteArray buf = svgBuffers.value(key);
++ if (!buf.isEmpty()) {
++ if (renderer->load(maybeUncompress(buf)))
++ return true;
++ svgBuffers.remove(key);
++ }
++ QString svgFile = svgFiles.value(key);
++ if (!svgFile.isEmpty()) {
++ if (renderer->load(svgFile))
++ return true;
++ }
++ return false;
++ }
++
++ QIcon::Mode QSvgIconEnginePrivate::loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
++ {
++ if (tryLoad(renderer, mode, state))
++ return mode;
++
++ const QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On;
++ if (mode == QIcon::Disabled || mode == QIcon::Selected) {
++ const QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled;
++ if (tryLoad(renderer, QIcon::Normal, state))
++ return QIcon::Normal;
++ if (tryLoad(renderer, QIcon::Active, state))
++ return QIcon::Active;
++ if (tryLoad(renderer, mode, oppositeState))
++ return mode;
++ if (tryLoad(renderer, QIcon::Normal, oppositeState))
++ return QIcon::Normal;
++ if (tryLoad(renderer, QIcon::Active, oppositeState))
++ return QIcon::Active;
++ if (tryLoad(renderer, oppositeMode, state))
++ return oppositeMode;
++ if (tryLoad(renderer, oppositeMode, oppositeState))
++ return oppositeMode;
++ } else {
++ const QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
++ if (tryLoad(renderer, oppositeMode, state))
++ return oppositeMode;
++ if (tryLoad(renderer, mode, oppositeState))
++ return mode;
++ if (tryLoad(renderer, oppositeMode, oppositeState))
++ return oppositeMode;
++ if (tryLoad(renderer, QIcon::Disabled, state))
++ return QIcon::Disabled;
++ if (tryLoad(renderer, QIcon::Selected, state))
++ return QIcon::Selected;
++ if (tryLoad(renderer, QIcon::Disabled, oppositeState))
++ return QIcon::Disabled;
++ if (tryLoad(renderer, QIcon::Selected, oppositeState))
++ return QIcon::Selected;
++ }
++ return QIcon::Normal;
++ }
++
++ QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
++ QIcon::State state)
++ {
++ return scaledPixmap(size, mode, state, 1.0);
++ }
++
++ QPixmap QSvgIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state,
++ qreal scale)
++ {
++ QPixmap pm;
++
++ QString pmckey(d->pmcKey(size, mode, state, scale));
++ if (QPixmapCache::find(pmckey, &pm))
++ return pm;
++
++ if (!d->addedPixmaps.isEmpty()) {
++ const auto realSize = size * scale;
++ const auto key = d->hashKey(mode, state);
++ auto it = d->addedPixmaps.constFind(key);
++ while (it != d->addedPixmaps.cend() && it.key() == key) {
++ const auto &pm = it.value();
++ if (!pm.isNull()) {
++ // we don't care about dpr here - don't use QSvgIconEngine when
++ // there are a lot of raster images are to handle.
++ if (pm.size() == realSize)
++ return pm;
++ }
++ ++it;
++ }
++ }
++
++ QSvgRenderer renderer;
++ const QIcon::Mode loadmode = d->loadDataForModeAndState(&renderer, mode, state);
++ if (!renderer.isValid())
++ return pm;
++
++ QSize actualSize = renderer.defaultSize();
++ if (!actualSize.isNull())
++ actualSize.scale(size * scale, Qt::KeepAspectRatio);
++
++ if (actualSize.isEmpty())
++ return pm;
++
++ pm = QPixmap(actualSize);
++ pm.fill(Qt::transparent);
++ QPainter p(&pm);
++ renderer.render(&p);
++ p.end();
++ if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
++ if (loadmode != mode && mode != QIcon::Normal) {
++ const QPixmap generated = QGuiApplicationPrivate::instance()->applyQIconStyleHelper(mode, pm);
++ if (!generated.isNull())
++ pm = generated;
++ }
++ }
++
++ if (!pm.isNull()) {
++ pm.setDevicePixelRatio(scale);
++ QPixmapCache::insert(pmckey, pm);
++ }
++
++ return pm;
++ }
++
++
++ void QSvgIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode,
++ QIcon::State state)
++ {
++ d->stepSerialNum();
++ d->addedPixmaps.insert(d->hashKey(mode, state), pixmap);
++ }
++
++ enum FileType { OtherFile, SvgFile, CompressedSvgFile };
++
++ static FileType fileType(const QFileInfo &fi)
++ {
++ const QString &suffix = fi.completeSuffix();
++ if (suffix.endsWith(QLatin1String("svg"), Qt::CaseInsensitive))
++ return SvgFile;
++ if (suffix.endsWith(QLatin1String("svgz"), Qt::CaseInsensitive)
++ || suffix.endsWith(QLatin1String("svg.gz"), Qt::CaseInsensitive)) {
++ return CompressedSvgFile;
++ }
++ #if QT_CONFIG(mimetype)
++ const QString &mimeTypeName = QMimeDatabase().mimeTypeForFile(fi).name();
++ if (mimeTypeName == QLatin1String("image/svg+xml"))
++ return SvgFile;
++ if (mimeTypeName == QLatin1String("image/svg+xml-compressed"))
++ return CompressedSvgFile;
++ #endif
++ return OtherFile;
++ }
++
++ void QSvgIconEngine::addFile(const QString &fileName, const QSize &,
++ QIcon::Mode mode, QIcon::State state)
++ {
++ if (!fileName.isEmpty()) {
++ const QFileInfo fi(fileName);
++ const QString abs = fi.absoluteFilePath();
++ const FileType type = fileType(fi);
++ #ifndef QT_NO_COMPRESS
++ if (type == SvgFile || type == CompressedSvgFile) {
++ #else
++ if (type == SvgFile) {
++ #endif
++ QSvgRenderer renderer(abs);
++ if (renderer.isValid()) {
++ d->stepSerialNum();
++ d->svgFiles.insert(d->hashKey(mode, state), abs);
++ }
++ } else if (type == OtherFile) {
++ QPixmap pm(abs);
++ if (!pm.isNull())
++ addPixmap(pm, mode, state);
++ }
++ }
++ }
++
++ void QSvgIconEngine::paint(QPainter *painter, const QRect &rect,
++ QIcon::Mode mode, QIcon::State state)
++ {
++ QSize pixmapSize = rect.size();
++ if (painter->device())
++ pixmapSize *= painter->device()->devicePixelRatio();
++ painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
++ }
++
++ bool QSvgIconEngine::isNull()
++ {
++ return d->svgFiles.isEmpty() && d->addedPixmaps.isEmpty() && d->svgBuffers.isEmpty();
++ }
++
++ QString QSvgIconEngine::key() const
++ {
++ return QLatin1String("svg");
++ }
++
++ QSvgIconEngine *QSvgIconEngine::clone() const
++ {
++ return new QSvgIconEngine(*this);
++ }
++
++
++ bool QSvgIconEngine::read(QDataStream &in)
++ {
++ d = new QSvgIconEnginePrivate;
++
++ if (in.version() >= QDataStream::Qt_4_4) {
++ int isCompressed;
++ QHash<int, QString> fileNames; // For memoryoptimization later
++ in >> fileNames >> isCompressed >> d->svgBuffers;
++ #ifndef QT_NO_COMPRESS
++ if (!isCompressed) {
++ for (auto &svgBuf : d->svgBuffers)
++ svgBuf = qCompress(svgBuf);
++ }
++ #else
++ if (isCompressed) {
++ qWarning("QSvgIconEngine: Can not decompress SVG data");
++ d->svgBuffers.clear();
++ }
++ #endif
++ int hasAddedPixmaps;
++ in >> hasAddedPixmaps;
++ if (hasAddedPixmaps) {
++ in >> d->addedPixmaps;
++ }
++ }
++ else {
++ QPixmap pixmap;
++ QByteArray data;
++ uint mode;
++ uint state;
++ int num_entries;
++
++ in >> data;
++ if (!data.isEmpty()) {
++ #ifndef QT_NO_COMPRESS
++ data = qUncompress(data);
++ #endif
++ if (!data.isEmpty())
++ d->svgBuffers.insert(d->hashKey(QIcon::Normal, QIcon::Off), data);
++ }
++ in >> num_entries;
++ for (int i=0; i<num_entries; ++i) {
++ if (in.atEnd())
++ return false;
++ in >> pixmap;
++ in >> mode;
++ in >> state;
++ // The pm list written by 4.3 is buggy and/or useless, so ignore.
++ //addPixmap(pixmap, QIcon::Mode(mode), QIcon::State(state));
++ }
++ }
++
++ return true;
++ }
++
++
++ bool QSvgIconEngine::write(QDataStream &out) const
++ {
++ if (out.version() >= QDataStream::Qt_4_4) {
++ int isCompressed = 0;
++ #ifndef QT_NO_COMPRESS
++ isCompressed = 1;
++ #endif
++ QHash<int, QByteArray> svgBuffers = d->svgBuffers;
++ for (const auto &it : d->svgFiles.asKeyValueRange()) {
++ QByteArray buf;
++ QFile f(it.second);
++ if (f.open(QIODevice::ReadOnly))
++ buf = f.readAll();
++ #ifndef QT_NO_COMPRESS
++ buf = qCompress(buf);
++ #endif
++ svgBuffers.insert(it.first, buf);
++ }
++ out << d->svgFiles << isCompressed << svgBuffers;
++ if (d->addedPixmaps.isEmpty())
++ out << 0;
++ else
++ out << 1 << d->addedPixmaps;
++ }
++ else {
++ const auto key = d->hashKey(QIcon::Normal, QIcon::Off);
++ QByteArray buf = d->svgBuffers.value(key);
++ if (buf.isEmpty()) {
++ QString svgFile = d->svgFiles.value(key);
++ if (!svgFile.isEmpty()) {
++ QFile f(svgFile);
++ if (f.open(QIODevice::ReadOnly))
++ buf = f.readAll();
++ }
++ }
++ #ifndef QT_NO_COMPRESS
++ buf = qCompress(buf);
++ #endif
++ out << buf;
++ // 4.3 has buggy handling of added pixmaps, so don't write any
++ out << (int)0;
++ }
++ return true;
++ }
++
++ QT_END_NAMESPACE
++
++ #endif // QT_NO_SVGRENDERER