File indexing completed on 2025-01-26 04:04:56

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2011 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "SvgLoadingContext.h"
0008 
0009 #include <QStack>
0010 #include <QFileInfo>
0011 #include <QDir>
0012 
0013 #include <KoColorSpaceRegistry.h>
0014 #include <KoColorSpaceEngine.h>
0015 #include <KoColorProfile.h>
0016 #include <KoDocumentResourceManager.h>
0017 
0018 #include <FlakeDebug.h>
0019 
0020 #include "SvgGraphicContext.h"
0021 #include "SvgUtil.h"
0022 #include "SvgCssHelper.h"
0023 #include "SvgStyleParser.h"
0024 #include "kis_debug.h"
0025 
0026 
0027 class Q_DECL_HIDDEN SvgLoadingContext::Private
0028 {
0029 public:
0030     Private()
0031         : zIndex(0)
0032         , documentResourceManager(0)
0033         , styleParser(0)
0034     {
0035 
0036     }
0037 
0038     ~Private()
0039     {
0040         if (! gcStack.isEmpty() && !gcStack.top()->isResolutionFrame) {
0041             // Resolution frame is usually the first and is not removed.
0042             warnFlake << "the context stack is not empty (current count" << gcStack.size() << ", expected 0)";
0043         }
0044         qDeleteAll(gcStack);
0045         gcStack.clear();
0046         delete styleParser;
0047     }
0048     QStack<SvgGraphicsContext*> gcStack;
0049     QString initialXmlBaseDir;
0050     int zIndex;
0051     KoDocumentResourceManager *documentResourceManager;
0052     QHash<QString, KoShape*> loadedShapes;
0053     QHash<QString, QDomElement> definitions;
0054     QHash<QString, const KoColorProfile*> profiles;
0055     SvgCssHelper cssStyles;
0056     SvgStyleParser *styleParser;
0057     FileFetcherFunc fileFetcher;
0058 };
0059 
0060 SvgLoadingContext::SvgLoadingContext(KoDocumentResourceManager *documentResourceManager)
0061     : d(new Private())
0062 {
0063     d->documentResourceManager = documentResourceManager;
0064     d->styleParser = new SvgStyleParser(*this);
0065     Q_ASSERT(d->documentResourceManager);
0066 }
0067 
0068 SvgLoadingContext::~SvgLoadingContext()
0069 {
0070 }
0071 
0072 SvgGraphicsContext *SvgLoadingContext::currentGC() const
0073 {
0074     if (d->gcStack.isEmpty())
0075         return 0;
0076 
0077     return d->gcStack.top();
0078 }
0079 
0080 #include "parsers/SvgTransformParser.h"
0081 
0082 SvgGraphicsContext *SvgLoadingContext::pushGraphicsContext(const QDomElement &element, bool inherit)
0083 {
0084     SvgGraphicsContext *gc;
0085     // copy data from current context
0086     if (! d->gcStack.isEmpty() && inherit) {
0087         gc = new SvgGraphicsContext(*d->gcStack.top());
0088     } else {
0089         gc = new SvgGraphicsContext();
0090     }
0091 
0092     gc->textProperties.resetNonInheritableToDefault(); // some of the text properties are not inherited
0093     gc->filterId.clear(); // filters are not inherited
0094     gc->clipPathId.clear(); // clip paths are not inherited
0095     gc->clipMaskId.clear(); // clip masks are not inherited
0096     gc->display = true; // display is not inherited
0097     gc->opacity = 1.0; // opacity is not inherited
0098     gc->paintOrder = QString(); //paint order is inherited by default
0099 
0100     if (!element.isNull()) {
0101         if (element.hasAttribute("transform")) {
0102             SvgTransformParser p(element.attribute("transform"));
0103             if (p.isValid()) {
0104                 QTransform mat = p.transform();
0105                 gc->matrix = mat * gc->matrix;
0106             }
0107         }
0108         if (element.hasAttribute("xml:base"))
0109             gc->xmlBaseDir = element.attribute("xml:base");
0110         if (element.hasAttribute("xml:space"))
0111             gc->preserveWhitespace = element.attribute("xml:space") == "preserve";
0112     }
0113 
0114     d->gcStack.push(gc);
0115 
0116     return gc;
0117 }
0118 
0119 void SvgLoadingContext::popGraphicsContext()
0120 {
0121     delete(d->gcStack.pop());
0122 }
0123 
0124 void SvgLoadingContext::setInitialXmlBaseDir(const QString &baseDir)
0125 {
0126     d->initialXmlBaseDir = baseDir;
0127 }
0128 
0129 QString SvgLoadingContext::xmlBaseDir() const
0130 {
0131     SvgGraphicsContext *gc = currentGC();
0132     return (gc && !gc->xmlBaseDir.isEmpty()) ? gc->xmlBaseDir : d->initialXmlBaseDir;
0133 }
0134 
0135 QString SvgLoadingContext::absoluteFilePath(const QString &href)
0136 {
0137     QFileInfo info(href);
0138     if (! info.isRelative())
0139         return href;
0140 
0141     SvgGraphicsContext *gc = currentGC();
0142     if (!gc)
0143         return d->initialXmlBaseDir;
0144 
0145     QString baseDir = d->initialXmlBaseDir;
0146     if (! gc->xmlBaseDir.isEmpty())
0147         baseDir = absoluteFilePath(gc->xmlBaseDir);
0148 
0149     QFileInfo pathInfo(QFileInfo(baseDir).filePath());
0150 
0151     QString relFile = href;
0152     while (relFile.startsWith(QLatin1String("../"))) {
0153         relFile.remove(0, 3);
0154         pathInfo.setFile(pathInfo.dir(), QString());
0155     }
0156 
0157     QString absFile = pathInfo.absolutePath() + '/' + relFile;
0158 
0159     return absFile;
0160 }
0161 
0162 QString SvgLoadingContext::relativeFilePath(const QString &href)
0163 {
0164     const SvgGraphicsContext *gc = currentGC();
0165     if (!gc) return href;
0166 
0167     QString result = href;
0168 
0169     QFileInfo info(href);
0170     if (info.isRelative())
0171         return href;
0172 
0173 
0174     if (!gc->xmlBaseDir.isEmpty()) {
0175         result = QDir(gc->xmlBaseDir).relativeFilePath(href);
0176     } else if (!d->initialXmlBaseDir.isEmpty()) {
0177         result = QDir(d->initialXmlBaseDir).relativeFilePath(href);
0178     }
0179 
0180     return QDir::cleanPath(result);
0181 }
0182 
0183 int SvgLoadingContext::nextZIndex()
0184 {
0185     return d->zIndex++;
0186 }
0187 
0188 void SvgLoadingContext::registerShape(const QString &id, KoShape *shape)
0189 {
0190     if (!id.isEmpty())
0191         d->loadedShapes.insert(id, shape);
0192 }
0193 
0194 KoShape* SvgLoadingContext::shapeById(const QString &id)
0195 {
0196     return d->loadedShapes.value(id);
0197 }
0198 
0199 void SvgLoadingContext::addDefinition(const QDomElement &element)
0200 {
0201     const QString id = element.attribute("id");
0202     if (id.isEmpty() || d->definitions.contains(id))
0203         return;
0204     d->definitions.insert(id, element);
0205 }
0206 
0207 QDomElement SvgLoadingContext::definition(const QString &id) const
0208 {
0209     return d->definitions.value(id);
0210 }
0211 
0212 bool SvgLoadingContext::hasDefinition(const QString &id) const
0213 {
0214     return d->definitions.contains(id);
0215 }
0216 
0217 void SvgLoadingContext::addStyleSheet(const QDomElement &styleSheet)
0218 {
0219     d->cssStyles.parseStylesheet(styleSheet);
0220 }
0221 
0222 QStringList SvgLoadingContext::matchingCssStyles(const QDomElement &element) const
0223 {
0224     return d->cssStyles.matchStyles(element);
0225 }
0226 
0227 SvgStyleParser &SvgLoadingContext::styleParser()
0228 {
0229     return *d->styleParser;
0230 }
0231 
0232 void SvgLoadingContext::parseProfile(const QDomElement &element)
0233 {
0234     const QString href = element.attribute("xlink:href");
0235     const QByteArray uniqueId = QByteArray::fromHex(element.attribute("local").toLatin1());
0236     const QString name = element.attribute("name");
0237 
0238     if (element.attribute("rendering-intent", "auto") != "auto") {
0239         // WARNING: Krita does *not* treat rendering intents attributes of the profile!
0240         warnFlake << "WARNING: we do *not* treat rendering intents attributes of the profile!";
0241     }
0242 
0243     if (d->profiles.contains(name)) {
0244         debugFlake << "Profile already in the map!" << ppVar(name);
0245         return;
0246     }
0247 
0248     const KoColorProfile *profile =
0249             KoColorSpaceRegistry::instance()->profileByUniqueId(uniqueId);
0250 
0251     if (!profile && d->fileFetcher) {
0252         KoColorSpaceEngine *engine = KoColorSpaceEngineRegistry::instance()->get("icc");
0253         KIS_ASSERT(engine);
0254         if (engine) {
0255             const QString fileName = relativeFilePath(href);
0256             const QByteArray profileData = d->fileFetcher(fileName);
0257             if (!profileData.isEmpty()) {
0258                 profile = engine->addProfile(profileData);
0259 
0260                 if (profile->uniqueId() != uniqueId) {
0261                     warnFlake << "WARNING: ProfileID of the attached profile doesn't match the one mentioned in SVG element";
0262                     warnFlake << "       " << ppVar(profile->uniqueId().toHex());
0263                     warnFlake << "       " << ppVar(uniqueId.toHex());
0264                 }
0265             } else {
0266                 warnFlake << "WARNING: couldn't fetch the ICCprofile file!" << fileName;
0267             }
0268         }
0269     }
0270 
0271     if (profile) {
0272         d->profiles.insert(name, profile);
0273     } else {
0274         warnFlake << "WARNING: couldn't load SVG profile" << ppVar(name) << ppVar(href) << ppVar(uniqueId);
0275     }
0276 }
0277 
0278 QHash<QString, const KoColorProfile *> SvgLoadingContext::profiles()
0279 {
0280     return d->profiles;
0281 }
0282 
0283 bool SvgLoadingContext::isRootContext() const
0284 {
0285     KIS_ASSERT(!d->gcStack.isEmpty());
0286     return d->gcStack.size() == 1;
0287 }
0288 
0289 void SvgLoadingContext::setFileFetcher(SvgLoadingContext::FileFetcherFunc func)
0290 {
0291     d->fileFetcher = func;
0292 }
0293 
0294 QByteArray SvgLoadingContext::fetchExternalFile(const QString &url)
0295 {
0296     return d->fileFetcher ? d->fileFetcher(url) : QByteArray();
0297 }