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 }