Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgParser.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2002-2005, 2007 Rob Buis <buis@kde.org>
3 * SPDX-FileCopyrightText: 2002-2004 Nicolas Goutte <nicolasg@snafu.de>
4 * SPDX-FileCopyrightText: 2005-2006 Tim Beaulen <tbscope@gmail.com>
5 * SPDX-FileCopyrightText: 2005-2009 Jan Hambrecht <jaham@gmx.net>
6 * SPDX-FileCopyrightText: 2005, 2007 Thomas Zander <zander@kde.org>
7 * SPDX-FileCopyrightText: 2006-2007 Inge Wallin <inge@lysator.liu.se>
8 * SPDX-FileCopyrightText: 2007-2008, 2010 Thorsten Zachmann <zachmann@kde.org>
9
10 * SPDX-License-Identifier: LGPL-2.0-or-later
11 */
12
13#include "SvgParser.h"
14
15#include <cmath>
16
17#include <FlakeDebug.h>
18
19#include <QColor>
20#include <QDir>
21#include <QPainter>
22#include <QPainterPath>
23#include <QRandomGenerator>
24
25#include <KoShape.h>
26#include <KoShapeRegistry.h>
27#include <KoShapeFactoryBase.h>
28#include <KoShapeGroup.h>
29#include <KoPathShape.h>
31#include <KoPathShapeLoader.h>
34#include <KoColorBackground.h>
37#include <KoPatternBackground.h>
38#include <KoClipPath.h>
39#include <KoClipMask.h>
40#include <KoXmlNS.h>
41
42#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
43#include <QXmlSimpleReader>
44#include <QXmlInputSource>
45#endif
46
47#include "SvgMeshGradient.h"
48#include "SvgMeshPatch.h"
49#include "SvgUtil.h"
50#include "SvgShape.h"
51#include "SvgGraphicContext.h"
52#include "SvgGradientHelper.h"
53#include "SvgClipPathHelper.h"
55#include "kis_pointer_utils.h"
57#include <KoMarker.h>
58
59#include <text/KoSvgTextShape.h>
61
62#include "kis_dom_utils.h"
63
64#include "kis_algebra_2d.h"
65#include "kis_debug.h"
66#include "kis_global.h"
67#include <QXmlStreamReader>
68#include <algorithm>
69
71 struct El {
72 El(const QDomElement* ue, const QString& key) :
73 m_useElement(ue), m_key(key) {
74 }
75 const QDomElement* m_useElement;
76 QString m_key;
77 };
81
82 void add(const QDomElement* useE, const QString& key) {
83 m_uses.push_back(El(useE, key));
84 }
85 bool empty() const {
86 return m_uses.empty();
87 }
88
89 void checkPendingUse(const QDomElement &b, QList<KoShape*>& shapes) {
90 KoShape* shape = 0;
91 const QString id = b.attribute("id");
92
93 if (id.isEmpty())
94 return;
95
96 // debugFlake << "Checking id: " << id;
97 auto i = std::partition(m_uses.begin(), m_uses.end(),
98 [&](const El& e) -> bool {return e.m_key != id;});
99
100 while (i != m_uses.end()) {
101 const El& el = m_uses.back();
103 // debugFlake << "Found pending use for id: " << el.m_key;
104 shape = m_parse->resolveUse(*(el.m_useElement), el.m_key);
105 if (shape) {
106 shapes.append(shape);
107 }
108 }
109 m_uses.pop_back();
110 }
111 }
112
114 while (!m_uses.empty()) {
115 const El& el = m_uses.back();
116 debugFlake << "WARNING: could not find path in <use xlink:href=\"#xxxxx\" expression. Losing data here. Key:"
117 << el.m_key;
118 m_uses.pop_back();
119 }
120 }
122 std::vector<El> m_uses;
123};
124
125
127 : m_context(documentResourceManager)
128 , m_documentResourceManager(documentResourceManager)
129{
130}
131
133{
134 for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
135 delete it.value();
136 }
137 qDeleteAll(m_defsShapes);
138}
139
140/*
141 * Qt 5.15 deprecated this way of setting the document content, however,
142 * they forgot to address reading text nodes with only white spaces, which
143 * results in bugs for SVG text parsing.
144 *
145 * See bug 513085
146 *
147 */
148#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
149QDomDocument createDocumentFromXmlInputSource(QXmlInputSource *source, QString *errorMsg, int *errorLine, int *errorColumn) {
150 QDomDocument doc;
151 QXmlSimpleReader simpleReader;
152 simpleReader.setFeature("http://qt-project.org/xml/features/report-whitespace-only-CharData", true);
153 simpleReader.setFeature("http://xml.org/sax/features/namespaces", false);
154 simpleReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
155 if (!doc.setContent(source, &simpleReader, errorMsg, errorLine, errorColumn)) {
156 return {};
157 }
158 return doc;
159}
160#endif
161
162QDomDocument SvgParser::createDocumentFromSvg(QIODevice *device, QString *errorMsg, int *errorLine, int *errorColumn)
163{
164#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
165 QXmlInputSource source(device);
166 return createDocumentFromXmlInputSource(&source, errorMsg, errorLine, errorColumn);
167#else
168 return createDocumentFromSvg(QXmlStreamReader(device), errorMsg, errorLine, errorColumn);
169#endif
170}
171
172QDomDocument SvgParser::createDocumentFromSvg(const QByteArray &data, QString *errorMsg, int *errorLine, int *errorColumn)
173{
174#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
175 QXmlInputSource source;
176 source.setData(data);
177 return createDocumentFromXmlInputSource(&source, errorMsg, errorLine, errorColumn);
178#else
179 return createDocumentFromSvg(QXmlStreamReader(data), errorMsg, errorLine, errorColumn);
180#endif
181}
182
183QDomDocument SvgParser::createDocumentFromSvg(const QString &data, QString *errorMsg, int *errorLine, int *errorColumn)
184{
185#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
186 QXmlInputSource source;
187 source.setData(data);
188 return createDocumentFromXmlInputSource(&source, errorMsg, errorLine, errorColumn);
189#else
190 return createDocumentFromSvg(QXmlStreamReader(data), errorMsg, errorLine, errorColumn);
191#endif
192}
193
194QDomDocument SvgParser::createDocumentFromSvg(QXmlStreamReader reader, QString *errorMsg, int *errorLine, int *errorColumn)
195{
196 QDomDocument doc;
197
198
199 reader.setNamespaceProcessing(false);
200#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
201 if (!doc.setContent(&reader, false, errorMsg, errorLine, errorColumn)) {
202 return {};
203 }
204#else
205 QDomDocument::ParseResult result = doc.setContent(&reader, QDomDocument::ParseOption::PreserveSpacingOnlyNodes);
206 if (!result) {
207 if (errorMsg && errorLine && errorColumn) {
208 *errorMsg = result.errorMessage;
209 *errorLine = result.errorLine;
210 *errorColumn = result.errorColumn;
211 }
212 return {};
213 }
214#endif
215 return doc;
216}
217
218void SvgParser::setXmlBaseDir(const QString &baseDir)
219{
221
223 [this](const QString &name) {
224 QStringList possibleNames;
225 possibleNames << name;
226 possibleNames << QDir::cleanPath(QDir(m_context.xmlBaseDir()).absoluteFilePath(name));
227 for (QString fileName : possibleNames) {
228 QFile file(fileName);
229 if (file.exists()) {
230 file.open(QIODevice::ReadOnly);
231 return file.readAll();
232 }
233 }
234 return QByteArray();
235 });
236}
237
238void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
239{
243 m_context.currentGC()->pixelsPerInch = pixelsPerInch;
244
245 const qreal scale = 72.0 / pixelsPerInch;
246 const QTransform t = QTransform::fromScale(scale, scale);
247 m_context.currentGC()->currentBoundingBox = boundsInPixels;
249}
250
256
258{
260}
261
266
268{
269 return m_shapes;
270}
271
273{
274 QVector<KoSvgSymbol*> symbols = m_symbols.values().toVector();
275 m_symbols.clear();
276 return symbols;
277}
278
279// Helper functions
280// ---------------------------------------------------------------------------------------
281
283{
284 SvgGradientHelper *result = 0;
285
286 // check if gradient was already parsed, and return it
287 if (m_gradients.contains(id)) {
288 result = &m_gradients[ id ];
289 }
290
291 // check if gradient was stored for later parsing
292 if (!result && m_context.hasDefinition(id)) {
293 const QDomElement &e = m_context.definition(id);
294 if (e.tagName().contains("Gradient")) {
295 result = parseGradient(m_context.definition(id));
296 } else if (e.tagName() == "meshgradient") {
298 }
299 }
300
301 return result;
302}
303
305{
307
308 // check if gradient was stored for later parsing
309 if (m_context.hasDefinition(id)) {
310 const QDomElement &e = m_context.definition(id);
311 if (e.tagName() == "pattern") {
312 result = parsePattern(m_context.definition(id), shape);
313 }
314 }
315
316 return result;
317}
318
320{
321 return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
322}
323
324// Parsing functions
325// ---------------------------------------------------------------------------------------
326
327qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
328{
329 return SvgUtil::parseUnit(m_context.currentGC(), m_context.resolvedProperties(), unit, horiz, vert, bbox);
330}
331
332qreal SvgParser::parseUnitX(const QString &unit)
333{
335}
336
337qreal SvgParser::parseUnitY(const QString &unit)
338{
340}
341
342qreal SvgParser::parseUnitXY(const QString &unit)
343{
345}
346
347qreal SvgParser::parseAngular(const QString &unit)
348{
350}
351
352
354{
355 // IMPROVEMENTS:
356 // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
357 // - A gradient inherits attributes it does not have from the referencing gradient.
358 // - Gradients with no color stops have no fill or stroke.
359 // - Gradients with one color stop have a solid color.
360
362 if (!gc) return 0;
363
364 SvgGradientHelper gradHelper;
365
366 QString gradientId = e.attribute("id");
367 if (gradientId.isEmpty()) return 0;
368
369 // check if we have this gradient already parsed
370 // copy existing gradient if it exists
371 if (m_gradients.contains(gradientId)) {
372 return &m_gradients[gradientId];
373 }
374
375 if (e.hasAttribute("xlink:href")) {
376 // strip the '#' symbol
377 QString href = e.attribute("xlink:href").mid(1);
378
379 if (!href.isEmpty()) {
380 // copy the referenced gradient if found
381 SvgGradientHelper *pGrad = findGradient(href);
382 if (pGrad) {
383 gradHelper = *pGrad;
384 }
385 }
386 }
387
388 const QGradientStops defaultStops = gradHelper.gradient()->stops();
389
390 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
392 }
393
396
397 if (e.tagName() == "linearGradient") {
398 QLinearGradient *g = new QLinearGradient();
399 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
400 g->setCoordinateMode(QGradient::ObjectBoundingMode);
401 g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
402 SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
403 g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
404 SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
405 } else {
406 g->setStart(QPointF(parseUnitX(e.attribute("x1")),
407 parseUnitY(e.attribute("y1"))));
408 g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
409 parseUnitY(e.attribute("y2"))));
410 }
411 gradHelper.setGradient(g);
412
413 } else if (e.tagName() == "radialGradient") {
414 QRadialGradient *g = new QRadialGradient();
415 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
416 g->setCoordinateMode(QGradient::ObjectBoundingMode);
417 g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
418 SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
419 g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
420 g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
421 SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
422 } else {
423 g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
424 parseUnitY(e.attribute("cy"))));
425 g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
426 parseUnitY(e.attribute("fy"))));
427 g->setRadius(parseUnitXY(e.attribute("r")));
428 }
429 gradHelper.setGradient(g);
430 } else {
431 debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName();
432 }
433
434 // handle spread method
435 QGradient::Spread spreadMethod = QGradient::PadSpread;
436 QString spreadMethodStr = e.attribute("spreadMethod");
437 if (!spreadMethodStr.isEmpty()) {
438 if (spreadMethodStr == "reflect") {
439 spreadMethod = QGradient::ReflectSpread;
440 } else if (spreadMethodStr == "repeat") {
441 spreadMethod = QGradient::RepeatSpread;
442 }
443 }
444
445 gradHelper.setSpreadMode(spreadMethod);
446
447 // Parse the color stops.
448 m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
449
450 if (e.hasAttribute("gradientTransform")) {
451 SvgTransformParser p(e.attribute("gradientTransform"));
452 if (p.isValid()) {
453 gradHelper.setTransform(p.transform());
454 }
455 }
456
458
459 m_gradients.insert(gradientId, gradHelper);
460
461 return &m_gradients[gradientId];
462}
463
465{
466 SvgGradientHelper gradHelper;
467 QString gradientId = e.attribute("id");
468 QScopedPointer<SvgMeshGradient> g(new SvgMeshGradient);
469
470 // check if we have this gradient already parsed
471 // copy existing gradient if it exists
472 if (m_gradients.contains(gradientId)) {
473 return &m_gradients[gradientId];
474 }
475
476 if (e.hasAttribute("xlink:href")) {
477 // strip the '#' symbol
478 QString href = e.attribute("xlink:href").mid(1);
479
480 if (!href.isEmpty()) {
481 // copy the referenced gradient if found
482 SvgGradientHelper *pGrad = findGradient(href);
483 if (pGrad) {
484 gradHelper = *pGrad;
485 }
486 }
487 }
488
489 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
491 }
492
493 if (e.hasAttribute("transform")) {
494 SvgTransformParser p(e.attribute("transform"));
495 if (p.isValid()) {
496 gradHelper.setTransform(p.transform());
497 }
498 }
499
500 QString type = e.attribute("type");
501 g->setType(SvgMeshGradient::BILINEAR);
502 if (!type.isEmpty() && type == "bicubic") {
503 g->setType(SvgMeshGradient::BICUBIC);
504 }
505
506 int irow = 0, icols;
507 for (int i = 0; i < e.childNodes().size(); ++i) {
508 QDomNode node = e.childNodes().at(i);
509
510 if (node.nodeName() == "meshrow") {
511
512 SvgMeshStop startingNode;
513 if (irow == 0) {
514 startingNode.point = QPointF(
515 parseUnitX(e.attribute("x")),
516 parseUnitY(e.attribute(("y"))));
517 startingNode.color = QColor();
518 }
519
520 icols = 0;
521 g->getMeshArray()->newRow();
522 for (int j = 0; j < node.childNodes().size() ; ++j) {
523 QDomNode meshpatchNode = node.childNodes().at(j);
524
525 if (meshpatchNode.nodeName() == "meshpatch") {
526 if (irow > 0) {
527 // Starting point for this would be the bottom (right) corner of the above patch
528 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Bottom, irow - 1, icols);
529 } else if (icols != 0) {
530 // Starting point for this would be the right (top) corner of the previous patch
531 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Right, irow, icols - 1);
532 }
533
534 QList<QPair<QString, QColor>> rawStops = parseMeshPatch(meshpatchNode);
535 // TODO handle the false result
536 if (!g->getMeshArray()->addPatch(rawStops, startingNode.point)) {
537 debugFlake << "WARNING: Failed to create meshpatch";
538 }
539 icols++;
540 }
541 }
542 irow++;
543 }
544 }
545 gradHelper.setMeshGradient(g.data());
546 m_gradients.insert(gradientId, gradHelper);
547
548 return &m_gradients[gradientId];
549}
550
551#define forEachElement( elem, parent ) \
552 for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \
553 if ( ( elem = _node.toElement() ).isNull() ) {} else
554
556{
557 // path and its associated color
559
561 if (!gc) return rawstops;
562
563 QDomElement e = meshpatchNode.toElement();
564
565 QDomElement stop;
566
567 forEachElement(stop, e) {
568 qreal X = 0; // dummy value, don't care, just to ensure the function won't blow up (also to avoid a Coverity issue)
569 QColor color = m_context.styleParser().parseColorStop(stop, gc, X).second;
570
571 QString pathStr = stop.attribute("path");
572
573 rawstops.append({pathStr, color});
574 }
575
576 return rawstops;
577}
578
579inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
580{
581 QTransform result =
582 patternTransform *
583 QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
584 patternTransform.inverted();
585 KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
586
587 return QPointF(result.dx(), result.dy());
588}
589
591{
599
601 if (!gc) return pattHelper;
602
603 const QString patternId = e.attribute("id");
604 if (patternId.isEmpty()) return pattHelper;
605
606 pattHelper = toQShared(new KoVectorPatternBackground);
607
608 if (e.hasAttribute("xlink:href")) {
609 // strip the '#' symbol
610 QString href = e.attribute("xlink:href").mid(1);
611
612 if (!href.isEmpty() &&href != patternId) {
613 // copy the referenced pattern if found
615 if (pPatt) {
616 pattHelper = pPatt;
617 }
618 }
619 }
620
621 pattHelper->setReferenceCoordinates(
622 KoFlake::coordinatesFromString(e.attribute("patternUnits"),
623 pattHelper->referenceCoordinates()));
624
625 pattHelper->setContentCoordinates(
626 KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
627 pattHelper->contentCoordinates()));
628
629 if (e.hasAttribute("patternTransform")) {
630 SvgTransformParser p(e.attribute("patternTransform"));
631 if (p.isValid()) {
632 pattHelper->setPatternTransform(p.transform());
633 }
634 }
635
636 if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
637 QRectF referenceRect(
638 SvgUtil::fromPercentage(e.attribute("x", "0%")),
639 SvgUtil::fromPercentage(e.attribute("y", "0%")),
640 SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
641 SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
642
643 pattHelper->setReferenceRect(referenceRect);
644 } else {
645 QRectF referenceRect(
646 parseUnitX(e.attribute("x", "0")),
647 parseUnitY(e.attribute("y", "0")),
648 parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
649 parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
650
651 pattHelper->setReferenceRect(referenceRect);
652 }
653
664 const QTransform dstShapeTransform = shape->absoluteTransformation();
665 const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
666 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
667 const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
668
670 gc = m_context.currentGC();
672
673 // start building shape tree from scratch
674 gc->matrix = QTransform();
675
676 const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
677 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
678 boundingRect.x(), boundingRect.y());
679
680
681
682 // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
683 // although we expect the pattern be reusable, but it is not so!
684 // WARNING2: the pattern shapes are stored in *User* coordinate system, although
685 // the "official" content system might be either OBB or User. It means that
686 // this baked transform should be stripped before writing the shapes back
687 // into SVG
688 if (e.hasAttribute("viewBox")) {
690 pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
691 relativeToShape.mapRect(pattHelper->referenceRect()) :
692 pattHelper->referenceRect();
693
695 pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
696
697 } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
698 gc->matrix = relativeToShape * gc->matrix;
699 }
700
701 // We do *not* apply patternTransform here! Here we only bake the untransformed
702 // version of the shape. The transformed one will be done in the very end while rendering.
703
704 QList<KoShape*> patternShapes = parseContainer(e);
705
706 if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
707 // In Krita we normalize the shapes, bake this transform into the pattern shapes
708
709 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
710
711 Q_FOREACH (KoShape *shape, patternShapes) {
712 shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
713 }
714 }
715
716 if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
717 // In Krita we normalize the shapes, bake this transform into reference rect
718 // NOTE: this is possible *only* when pattern transform is not perspective
719 // (which is always true for SVG)
720
721 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
722
723 QRectF ref = pattHelper->referenceRect();
724 ref.translate(offset);
725 pattHelper->setReferenceRect(ref);
726 }
727
729 gc = m_context.currentGC();
730
731 if (!patternShapes.isEmpty()) {
732 pattHelper->setShapes(patternShapes);
733 }
734
735 return pattHelper;
736}
737
738bool SvgParser::parseMarker(const QDomElement &e)
739{
740 const QString id = e.attribute("id");
741 if (id.isEmpty()) return false;
742
743 QScopedPointer<KoMarker> marker(new KoMarker());
744 marker->setCoordinateSystem(
745 KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
746
747 marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
748 parseUnitY(e.attribute("refY"))));
749
750 marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
751 parseUnitY(e.attribute("markerHeight", "3"))));
752
753 const QString orientation = e.attribute("orient", "0");
754
755 if (orientation == "auto") {
756 marker->setAutoOrientation(true);
757 } else {
758 marker->setExplicitOrientation(parseAngular(orientation));
759 }
760
761 // ensure that the clip path is loaded in local coordinates system
763 m_context.currentGC()->matrix = QTransform();
764 m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
765
766 KoShape *markerShape = parseGroup(e);
767
769
770 if (!markerShape) return false;
771
772 marker->setShapes({markerShape});
773
774 m_markers.insert(id, QExplicitlySharedDataPointer<KoMarker>(marker.take()));
775
776 return true;
777}
778
779bool SvgParser::parseSymbol(const QDomElement &e)
780{
781 const QString id = e.attribute("id");
782
783 if (id.isEmpty()) return false;
784
785 QScopedPointer<KoSvgSymbol> svgSymbol(new KoSvgSymbol());
786
787 // ensure that the clip path is loaded in local coordinates system
789 m_context.currentGC()->matrix = QTransform();
790 m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0);
791
792 QString title = e.firstChildElement("title").toElement().text();
793
794 QScopedPointer<KoShape> symbolShape(parseGroup(e));
795
797
798 if (!symbolShape) return false;
799
800 svgSymbol->shape = symbolShape.take();
801 svgSymbol->title = title;
802 svgSymbol->id = id;
803 if (title.isEmpty()) svgSymbol->title = id;
804
805 if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
806 debugFlake << "Symbol" << id << "seems to be empty, discarding";
807 return false;
808 }
809
810 // TODO: out default set of symbols had duplicated ids! We should
811 // make sure they are unique!
812 if (m_symbols.contains(id)) {
813 delete m_symbols[id];
814 m_symbols.remove(id);
815 }
816
817 m_symbols.insert(id, svgSymbol.take());
818
819 return true;
820}
821
822void SvgParser::parseMetadataApplyToShape(const QDomElement &e, KoShape *shape)
823{
824 const QString titleTag = "title";
825 const QString descriptionTag = "desc";
826 QDomElement title = e.firstChildElement(titleTag);
827 if (!title.isNull()) {
828 QDomText text = getTheOnlyTextChild(title);
829 if (!text.data().isEmpty()) {
830 shape->setAdditionalAttribute(titleTag, text.data());
831 }
832 }
833 QDomElement description = e.firstChildElement(descriptionTag);
834 if (!description.isNull()) {
835 QDomText text = getTheOnlyTextChild(description);
836 if (!text.data().isEmpty()) {
837 shape->setAdditionalAttribute(descriptionTag, text.data());
838 }
839 }
840}
841
842bool SvgParser::parseClipPath(const QDomElement &e)
843{
844 SvgClipPathHelper clipPath;
845
846 const QString id = e.attribute("id");
847 if (id.isEmpty()) return false;
848
849 clipPath.setClipPathUnits(
850 KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
851
852 // ensure that the clip path is loaded in local coordinates system
854 m_context.currentGC()->matrix = QTransform();
856
857 KoShape *clipShape = parseGroup(e);
858
860
861 if (!clipShape) return false;
862
863 clipPath.setShapes({clipShape});
864 m_clipPaths.insert(id, clipPath);
865
866 return true;
867}
868
869bool SvgParser::parseClipMask(const QDomElement &e)
870{
872
873 const QString id = e.attribute("id");
874 if (id.isEmpty()) return false;
875
876 clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
877 clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
878
879 QRectF maskRect;
880
881 if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
882 maskRect.setRect(
883 SvgUtil::fromPercentage(e.attribute("x", "-10%")),
884 SvgUtil::fromPercentage(e.attribute("y", "-10%")),
885 SvgUtil::fromPercentage(e.attribute("width", "120%")),
886 SvgUtil::fromPercentage(e.attribute("height", "120%")));
887 } else {
888 maskRect.setRect(
889 parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
890 parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
891 parseUnitX(e.attribute("width", "120%")),
892 parseUnitY(e.attribute("height", "120%")));
893 }
894
895 clipMask->setMaskRect(maskRect);
896
897
898 // ensure that the clip mask is loaded in local coordinates system
900 m_context.currentGC()->matrix = QTransform();
902
903 KoShape *clipShape = parseGroup(e);
904
906
907 if (!clipShape) return false;
908 clipMask->setShapes({clipShape});
909
910 m_clipMasks.insert(id, clipMask);
911 return true;
912}
913
920
921void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
922{
923 if (!shape) return;
924
926
927 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
928 applyMarkers(pathShape);
929 }
930
931 applyClipping(shape, shapeToOriginalUserCoordinates);
932 applyMaskClipping(shape, shapeToOriginalUserCoordinates);
933
934}
935
937{
938 if (!shape) return;
939
941 KIS_ASSERT(gc);
942
943 if (!dynamic_cast<KoShapeGroup*>(shape)) {
944 applyFillStyle(shape);
945 applyStrokeStyle(shape);
946 }
947
948 if (!gc->display || !gc->visible) {
959 shape->setVisible(false);
960 }
961 shape->setTransparency(1.0 - gc->opacity);
962
963 applyPaintOrder(shape);
964}
965
966
967void SvgParser::applyStyle(KoShape *obj, const QDomElement &e, const QPointF &shapeToOriginalUserCoordinates)
968{
969 applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
970}
971
972void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
973{
975 if (!gc)
976 return;
977
979
980 if (!obj)
981 return;
982
983 if (!dynamic_cast<KoShapeGroup*>(obj)) {
984 applyFillStyle(obj);
985 applyStrokeStyle(obj);
986 }
987
988 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(obj)) {
989 applyMarkers(pathShape);
990 }
991
992 applyClipping(obj, shapeToOriginalUserCoordinates);
993 applyMaskClipping(obj, shapeToOriginalUserCoordinates);
994
995 if (!gc->display || !gc->visible) {
996 obj->setVisible(false);
997 }
998 obj->setTransparency(1.0 - gc->opacity);
999 applyPaintOrder(obj);
1000}
1001
1003 const KoShape *shape,
1004 const SvgGraphicsContext *gc,
1005 QTransform *transform)
1006{
1007 QGradient *resultGradient = 0;
1008 KIS_ASSERT(transform);
1009
1010 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1011 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1012 *transform = gradient->transform();
1013 } else {
1014 if (gradient->gradient()->type() == QGradient::LinearGradient) {
1020 const QRectF boundingRect = shape->outline().boundingRect();
1021 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1022 boundingRect.x(), boundingRect.y());
1023
1024 const QTransform relativeToUser =
1025 relativeToShape * shape->transformation() * gc->matrix.inverted();
1026
1027 const QTransform userToRelative = relativeToUser.inverted();
1028
1029 const QLinearGradient *o = static_cast<const QLinearGradient*>(gradient->gradient());
1030 QLinearGradient *g = new QLinearGradient();
1031 g->setStart(userToRelative.map(o->start()));
1032 g->setFinalStop(userToRelative.map(o->finalStop()));
1033 g->setCoordinateMode(QGradient::ObjectBoundingMode);
1034 g->setStops(o->stops());
1035 g->setSpread(o->spread());
1036
1037 resultGradient = g;
1038 *transform = relativeToUser * gradient->transform() * userToRelative;
1039
1040 } else if (gradient->gradient()->type() == QGradient::RadialGradient) {
1041 // For radial and conical gradients such conversion is not possible
1042
1043 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1044 *transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
1045
1046 const QRectF outlineRect = shape->outlineRect();
1047 if (outlineRect.isEmpty()) return resultGradient;
1048
1055 QRadialGradient *rgradient = static_cast<QRadialGradient*>(resultGradient);
1056
1057 const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect);
1058 const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension));
1059
1060 const QTransform uniformizeTransform =
1061 QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) *
1062 QTransform::fromScale(maxDimension / shape->outlineRect().width(),
1063 maxDimension / shape->outlineRect().height()) *
1064 QTransform::fromTranslate(outlineRect.x(), outlineRect.y());
1065
1066 const QPointF centerLocal = transform->map(rgradient->center());
1067 const QPointF focalLocal = transform->map(rgradient->focalPoint());
1068
1069 const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize);
1070 const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize);
1071
1072 rgradient->setCenter(centerOBB);
1073 rgradient->setFocalPoint(focalOBB);
1074
1075 const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize);
1076 const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize);
1077
1078 rgradient->setCenterRadius(centerRadiusOBB);
1079 rgradient->setFocalRadius(focalRadiusOBB);
1080
1081 rgradient->setCoordinateMode(QGradient::ObjectBoundingMode);
1082
1083 // Warning: should it really be pre-multiplication?
1084 *transform = uniformizeTransform * gradient->transform();
1085 }
1086 }
1087
1088 // TODO: all gradients in Krita are rendered in a premultiplied-alpha
1089 // mode, which is against SVG standard. We need to fix that. Though
1090 // it requires deepeer changes, than just mere setting of the
1091 // QGradient's interpolation mode on loading.
1092 // resultGradient->setInterpolationMode(QGradient::ComponentInterpolation);
1093
1094 return resultGradient;
1095}
1096
1098 const KoShape *shape,
1099 const SvgGraphicsContext *gc) {
1100
1101 SvgMeshGradient *resultGradient = nullptr;
1102
1103 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1104
1105 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1106
1107 const QRectF boundingRect = shape->outline().boundingRect();
1108 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1109 boundingRect.x(), boundingRect.y());
1110
1111 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1112 // So, transform is called multiple times on the mesh and that's not nice
1113 resultGradient->setTransform(gradient->transform() * relativeToShape);
1114 } else {
1115 // NOTE: Krita's shapes use their own coordinate system. Where origin is at the top left
1116 // of the SHAPE. All the mesh patches will be rendered in the global 'user' coordinate system
1117 // where the origin is at the top left of the LAYER/DOCUMENT.
1118
1119 // Get the user coordinates of the shape
1120 const QTransform shapeglobal = shape->absoluteTransformation() * gc->matrix.inverted();
1121
1122 // Get the translation offset to shift the origin from "Shape" to "User"
1123 const QTransform translationOffset = QTransform::fromTranslate(-shapeglobal.dx(), -shapeglobal.dy());
1124
1125 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1126
1127 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1128 // So, transform is called multiple times on the mesh and that's not nice
1129 resultGradient->setTransform(gradient->transform() * translationOffset);
1130 }
1131
1132 return resultGradient;
1133}
1134
1136{
1138 if (! gc)
1139 return;
1140
1143 } else if (gc->fillType == SvgGraphicsContext::Solid) {
1145 } else if (gc->fillType == SvgGraphicsContext::Complex) {
1146 // try to find referenced gradient
1147 SvgGradientHelper *gradient = findGradient(gc->fillId);
1148 if (gradient) {
1149 QTransform transform;
1150
1151 if (gradient->isMeshGradient()) {
1153
1154 QScopedPointer<SvgMeshGradient> result(prepareMeshGradientForShape(gradient, shape, gc));
1155
1156 bg = toQShared(new KoMeshGradientBackground(result.data(), transform));
1157 shape->setBackground(bg);
1158 } else if (gradient->gradient()) {
1159 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1160 if (result) {
1162 bg = toQShared(new KoGradientBackground(result));
1163 bg->setTransform(transform);
1164 shape->setBackground(bg);
1165 }
1166 }
1167 } else {
1169 findPattern(gc->fillId, shape);
1170
1171 if (pattern) {
1172 shape->setBackground(pattern);
1173 } else {
1174 // no referenced fill found, use fallback color
1176 }
1177 }
1178 } else if (gc->fillType == SvgGraphicsContext::Inherit) {
1179 shape->setInheritBackground(true);
1180 }
1181
1182 KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
1183 if (path)
1184 path->setFillRule(gc->fillRule);
1185}
1186
1187void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
1188{
1189 const double lineWidth = srcStroke->lineWidth();
1190 QVector<qreal> dashes = srcStroke->lineDashes();
1191
1192 // apply line width to dashes and dash offset
1193 if (dashes.count() && lineWidth > 0.0) {
1194 const double dashOffset = srcStroke->dashOffset();
1195 QVector<qreal> dashes = srcStroke->lineDashes();
1196
1197 for (int i = 0; i < dashes.count(); ++i) {
1198 dashes[i] /= lineWidth;
1199 }
1200
1201 dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
1202 dstStroke->setDashOffset(dashOffset / lineWidth);
1203 } else {
1204 dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
1205 }
1206}
1207
1209{
1211 if (! gc)
1212 return;
1213
1215 KoShapeStrokeSP stroke(new KoShapeStroke());
1216 stroke->setLineWidth(0.0);
1217 const QColor color = Qt::transparent;
1218 stroke->setColor(color);
1219 shape->setStroke(stroke);
1220 } else if (gc->strokeType == SvgGraphicsContext::Solid) {
1221 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1222 applyDashes(gc->stroke, stroke);
1223 shape->setStroke(stroke);
1224 } else if (gc->strokeType == SvgGraphicsContext::Complex) {
1225 // try to find referenced gradient
1226 SvgGradientHelper *gradient = findGradient(gc->strokeId);
1227 if (gradient) {
1228 QTransform transform;
1229 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1230 if (result) {
1231 QBrush brush = *result;
1232 delete result;
1233 brush.setTransform(transform);
1234
1235 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1236 stroke->setLineBrush(brush);
1237 applyDashes(gc->stroke, stroke);
1238 shape->setStroke(stroke);
1239 }
1240 } else {
1241 // no referenced stroke found, use fallback color
1242 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1243 applyDashes(gc->stroke, stroke);
1244 shape->setStroke(stroke);
1245 }
1246 } else if (gc->strokeType == SvgGraphicsContext::Inherit) {
1247 shape->setInheritStroke(true);
1248 }
1249}
1250
1252{
1254 if (!gc)
1255 return;
1256
1257 if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
1259 }
1260
1261 if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
1263 }
1264
1265 if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
1267 }
1268
1270}
1271
1273{
1275 if (!gc)
1276 return;
1277
1278 if (!gc->paintOrder.isEmpty() && gc->paintOrder != "inherit") {
1279 QStringList paintOrder = gc->paintOrder.split(" ");
1281 Q_FOREACH(const QString p, paintOrder) {
1282 if (p == "fill") {
1283 order.append(KoShape::Fill);
1284 } else if (p == "stroke") {
1285 order.append(KoShape::Stroke);
1286 } else if (p == "markers") {
1287 order.append(KoShape::Markers);
1288 }
1289 }
1290 if (paintOrder.size() == 1 && order.isEmpty()) { // Normal
1292 }
1293 if (order.size() == 1) {
1294 if (order.first() == KoShape::Fill) {
1296 } else if (order.first() == KoShape::Stroke) {
1298 } else if (order.first() == KoShape::Markers) {
1300 }
1301 } else if (order.size() > 1) {
1302 shape->setPaintOrder(order.at(0), order.at(1));
1303 }
1304 }
1305}
1306
1307void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1308{
1310 if (! gc)
1311 return;
1312
1313 if (gc->clipPathId.isEmpty())
1314 return;
1315
1316 SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
1317 if (!clipPath || clipPath->isEmpty())
1318 return;
1319
1321
1322 Q_FOREACH (KoShape *item, clipPath->shapes()) {
1323 KoShape *clonedShape = item->cloneShape();
1324 KIS_ASSERT_RECOVER(clonedShape) { continue; }
1325
1326 shapes.append(clonedShape);
1327 }
1328
1329 if (!shapeToOriginalUserCoordinates.isNull()) {
1330 const QTransform t =
1331 QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
1332 shapeToOriginalUserCoordinates.y());
1333
1334 Q_FOREACH(KoShape *s, shapes) {
1336 }
1337 }
1338
1339 KoClipPath *clipPathObject = new KoClipPath(shapes,
1342 shape->setClipPath(clipPathObject);
1343}
1344
1345void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1346{
1348 if (!gc)
1349 return;
1350
1351 if (gc->clipMaskId.isEmpty())
1352 return;
1353
1354
1355 QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
1356 if (!originalClipMask || originalClipMask->isEmpty()) return;
1357
1358 KoClipMask *clipMask = originalClipMask->clone();
1359
1360 clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
1361
1362 shape->setClipMask(clipMask);
1363}
1364
1365KoShape* SvgParser::parseUse(const QDomElement &e, DeferredUseStore* deferredUseStore)
1366{
1367 QString href = e.attribute("xlink:href");
1368 if (href.isEmpty())
1369 return 0;
1370
1371 QString key = href.mid(1);
1372 const bool gotDef = m_context.hasDefinition(key);
1373 if (gotDef) {
1374 return resolveUse(e, key);
1375 } else if (deferredUseStore) {
1376 deferredUseStore->add(&e, key);
1377 return 0;
1378 }
1379 debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
1380 << key;
1381 return 0;
1382}
1383
1384KoShape* SvgParser::resolveUse(const QDomElement &e, const QString& key)
1385{
1386 KoShape *result = 0;
1387
1389
1390 // TODO: parse 'width' and 'height' as well
1391 gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
1392
1393 const QDomElement &referencedElement = m_context.definition(key);
1394 result = parseGroup(e, referencedElement, false);
1395
1397 return result;
1398}
1399
1401{
1402 m_shapes += shapes;
1403
1404 if (!group || shapes.isEmpty())
1405 return;
1406
1407 // not normalized
1408 KoShapeGroupCommand cmd(group, shapes, false);
1409 cmd.redo();
1410}
1411
1412QList<KoShape*> SvgParser::parseSvg(const QDomElement &e, QSizeF *fragmentSize)
1413{
1414 // check if we are the root svg element
1415 const bool isRootSvg = m_context.isRootContext();
1416
1417 // parse 'transform' field if preset
1419
1420 applyStyle(0, e, QPointF());
1421
1422 const QString w = e.attribute("width");
1423 const QString h = e.attribute("height");
1424
1425 qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
1426 qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
1427
1428 if (w.isEmpty() || h.isEmpty()) {
1429 QRectF viewRect;
1430 QTransform viewTransform_unused;
1431 QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
1432
1433 if (SvgUtil::parseViewBox(e, fakeBoundingRect,
1434 &viewRect, &viewTransform_unused)) {
1435
1436 QSizeF estimatedSize = viewRect.size();
1437
1438 if (estimatedSize.isValid()) {
1439
1440 if (!w.isEmpty()) {
1441 estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
1442 } else if (!h.isEmpty()) {
1443 estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
1444 }
1445
1446 width = estimatedSize.width();
1447 height = estimatedSize.height();
1448 }
1449 }
1450 }
1451
1452 QSizeF svgFragmentSize(QSizeF(width, height));
1453
1454 if (fragmentSize) {
1455 *fragmentSize = svgFragmentSize;
1456 }
1457
1458 gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
1459
1460 if (!isRootSvg) {
1461 // x and y attribute has no meaning for outermost svg elements
1462 const qreal x = parseUnit(e.attribute("x", "0"));
1463 const qreal y = parseUnit(e.attribute("y", "0"));
1464
1465 QTransform move = QTransform::fromTranslate(x, y);
1466 gc->matrix = move * gc->matrix;
1467 }
1468
1476 gc->pixelsPerInch = 96.0;
1477
1479
1481
1482 // First find the metadata
1483 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1484 QDomElement b = n.toElement();
1485 if (b.isNull())
1486 continue;
1487
1488 if (b.tagName() == "title") {
1489 m_documentTitle = b.text().trimmed();
1490 }
1491 else if (b.tagName() == "desc") {
1492 m_documentDescription = b.text().trimmed();
1493 }
1494 else if (b.tagName() == "metadata") {
1495 // TODO: parse the metadata
1496 }
1497 }
1498
1499
1500 // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
1501 // and as mother makes them -- if mother is inkscape.
1502 if (gc->currentBoundingBox.normalized().isValid()) {
1504 }
1505
1507
1508 return shapes;
1509}
1510
1511void SvgParser::applyViewBoxTransform(const QDomElement &element)
1512{
1514
1515 QRectF viewRect = gc->currentBoundingBox;
1516 QTransform viewTransform;
1517
1519 &viewRect, &viewTransform)) {
1520
1521 gc->matrix = viewTransform * gc->matrix;
1522 gc->currentBoundingBox = viewRect;
1523 }
1524}
1525
1527{
1529
1530 Q_FOREACH (const KoID &id, m_warnings) {
1531 warnings << id.name();
1532 }
1533
1534 return warnings;
1535}
1536
1541
1543{
1544 return m_documentTitle;
1545}
1546
1548{
1549 return m_documentDescription;
1550}
1551
1556
1557inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
1558{
1559 const QTransform shapeToOriginalUserCoordinates =
1560 shape->absoluteTransformation().inverted() *
1561 coordinateSystemOnLoading;
1562
1563 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
1564 return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
1565}
1566
1567KoShape* SvgParser::parseGroup(const QDomElement &b, const QDomElement &overrideChildrenFrom, bool createContext)
1568{
1569 if (createContext) {
1571 }
1572
1573 KoShapeGroup *group = new KoShapeGroup();
1574 group->setZIndex(m_context.nextZIndex());
1575
1576 // groups should also have their own coordinate system!
1578 const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
1579
1581
1582 QList<KoShape*> childShapes;
1583
1584 if (!overrideChildrenFrom.isNull()) {
1585 // we upload styles from both: <use> and <defs>
1586 uploadStyleToContext(overrideChildrenFrom);
1587 if (overrideChildrenFrom.tagName() == "symbol") {
1588 childShapes = {parseGroup(overrideChildrenFrom)};
1589 } else {
1590 childShapes = parseSingleElement(overrideChildrenFrom, 0);
1591 }
1592 } else {
1593 childShapes = parseContainer(b);
1594 }
1595
1596 // handle id
1597 applyId(b.attribute("id"), group);
1598
1599 if (b.hasAttribute(KoSvgTextShape_TEXTCONTOURGROUP)) {
1600 Q_FOREACH(KoShape *shape, childShapes) {
1601 if (shape->shapeId() == KoSvgTextShape_SHAPEID) {
1602 shape->setTransformation(group->transformation());
1603
1604 if (createContext) {
1606 }
1607 return shape;
1608 }
1609 }
1610 }
1611 addToGroup(childShapes, group);
1612
1613 applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
1614
1615 parseMetadataApplyToShape(b, group);
1616
1617 if (createContext) {
1619 }
1620
1621 return group;
1622}
1623
1624QDomText SvgParser::getTheOnlyTextChild(const QDomElement &e)
1625{
1626 QDomNode firstChild = e.firstChild();
1627 return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
1628 firstChild.toText() : QDomText();
1629}
1630
1632{
1633 for (auto defs = m_defsShapes.begin(); defs != m_defsShapes.end(); defs++) {
1634 KoShape *dShape = *defs;
1635 if (!dShape) continue;
1636 if (dShape->hasCommonParent(shape)) return true;
1637 }
1638 return false;
1639}
1640
1641KoShape* SvgParser::getTextPath(const QDomElement &e, bool hideShapesFromDefs) {
1642 if (e.hasAttribute("path")) {
1643 QDomElement p = e.ownerDocument().createElement("path");
1644 p.setAttribute("d", e.attribute("path"));
1645 KoShape *s = createPath(p);
1646 if (hideShapesFromDefs) {
1647 s->setTransparency(1.0);
1648 }
1649 return s;
1650 } else {
1651 QString pathId;
1652 if (e.hasAttribute("href")) {
1653 pathId = e.attribute("href").remove(0, 1);
1654 } else if (e.hasAttribute("xlink:href")) {
1655 pathId = e.attribute("xlink:href").remove(0, 1);
1656 }
1657 if (!pathId.isNull()) {
1658 KoShape *s = m_context.shapeById(pathId);
1659 if (s) {
1660 KoShape *cloned = s->cloneShape();
1661 const QTransform absTf = s->absoluteTransformation();
1662 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
1663 if(cloned && shapeInDefs(s) && hideShapesFromDefs) {
1664 cloned->setTransparency(1.0);
1665 }
1666 return cloned;
1667 }
1668 }
1669 }
1670 return nullptr;
1671}
1672
1673void SvgParser::parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader, bool hideShapesFromDefs) {
1674 QDomText t = getTheOnlyTextChild(e);
1675 if (!t.isNull()) {
1676 textLoader.loadSvgText(t, m_context);
1677 } else {
1678 textLoader.enterNodeSubtree();
1679 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1680 QDomElement b = n.toElement();
1681 if (b.tagName() == "title" || b.tagName() == "desc") continue;
1682 textLoader.nextNode();
1683 if (b.isNull()) {
1684 textLoader.loadSvgText(n.toText(), m_context);
1685 KoShape *styleDummy = new KoPathShape();
1686 applyCurrentBasicStyle(styleDummy);
1687 textLoader.setStyleInfo(styleDummy);
1688 } else {
1691 textLoader.loadSvg(b, m_context);
1692 if (b.hasChildNodes()) {
1693 parseTextChildren(b, textLoader, hideShapesFromDefs);
1694 }
1695 textLoader.setTextPathOnCurrentNode(getTextPath(b, hideShapesFromDefs));
1697 }
1698 }
1699 textLoader.leaveNodeSubtree();
1700 }
1701 KoShape *styleDummy = new KoPathShape();
1702 applyCurrentBasicStyle(styleDummy);
1703 textLoader.setStyleInfo(styleDummy);
1704}
1705
1706KoShape *SvgParser::parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape)
1707{
1708 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan" || e.tagName() == "textPath", 0);
1710 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
1711
1712 KoSvgTextShape *rootTextShape = 0;
1713
1714 bool hideShapesFromDefs = true;
1715 if (mergeIntoShape) {
1716 rootTextShape = mergeIntoShape;
1717 hideShapesFromDefs = false;
1718 } else {
1719 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
1720 rootTextShape = dynamic_cast<KoSvgTextShape*>(factory->createDefaultShape(m_documentResourceManager));
1721 }
1722 KoSvgTextLoader textLoader(rootTextShape);
1723
1724 if (rootTextShape) {
1725 m_isInsideTextSubtree = true;
1726 }
1727
1730
1731 if (rootTextShape) {
1732 if (!m_context.currentGC()->shapeInsideValue.isEmpty()) {
1734 rootTextShape->setShapesInside(shapesInside);
1735 }
1736
1737 if (!m_context.currentGC()->shapeSubtractValue.isEmpty()) {
1738 QList<KoShape*> shapesSubtract = createListOfShapesFromCSS(e, m_context.currentGC()->shapeSubtractValue, m_context, hideShapesFromDefs);
1739 rootTextShape->setShapesSubtract(shapesSubtract);
1740 }
1741 }
1742
1743 if (e.hasAttribute("krita:textVersion")) {
1744 m_context.currentGC()->textProperties.setProperty(KoSvgTextProperties::KraTextVersionId, e.attribute("krita:textVersion", "1").toInt());
1745
1747 debugFlake << "WARNING: \"krita:textVersion\" attribute appeared in non-root text shape";
1748 }
1749 }
1750
1751 parseMetadataApplyToShape(e, rootTextShape);
1752
1753 if (!mergeIntoShape) {
1754 rootTextShape->setZIndex(m_context.nextZIndex());
1755 }
1756
1759
1760 static const KoID warning("warn_text_version_1",
1761 i18nc("warning while loading SVG text",
1762 "The document has vector text created "
1763 "in Krita 4.x. When you save the document, "
1764 "the text object will be converted into "
1765 "Krita 5 format that will no longer be "
1766 "compatible with Krita 4.x"));
1767
1768 if (!m_warnings.contains(warning)) {
1769 m_warnings << warning;
1770 }
1771 }
1772
1774
1775 // 1) apply transformation only in case we are not overriding the shape!
1776 // 2) the transformation should be applied *before* the shape is added to the group!
1777 if (!mergeIntoShape) {
1778 // groups should also have their own coordinate system!
1780 const QPointF extraOffset = extraShapeOffset(rootTextShape, m_context.currentGC()->matrix);
1781
1782 // handle id
1783 applyId(e.attribute("id"), rootTextShape);
1784 applyCurrentStyle(rootTextShape, extraOffset); // apply style to this group after size is set
1785 } else {
1786 m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation();
1787 applyCurrentBasicStyle(rootTextShape);
1788 }
1789
1790 QDomText onlyTextChild = getTheOnlyTextChild(e);
1791 if (!onlyTextChild.isNull()) {
1792 textLoader.loadSvgText(onlyTextChild, m_context);
1793
1794 } else {
1795 parseTextChildren(e, textLoader, hideShapesFromDefs);
1796 }
1797
1799
1800 m_isInsideTextSubtree = false;
1801
1802 //rootTextShape->debugParsing();
1803
1804
1805 return rootTextShape;
1806}
1807
1809{
1811
1812 // are we parsing a switch container
1813 bool isSwitch = e.tagName() == "switch";
1814
1815 DeferredUseStore deferredUseStore(this);
1816
1817 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1818 QDomElement b = n.toElement();
1819 if (b.isNull()) {
1820 continue;
1821 }
1822
1823 if (isSwitch) {
1824 // if we are parsing a switch check the requiredFeatures, requiredExtensions
1825 // and systemLanguage attributes
1826 // TODO: evaluate feature list
1827 if (b.hasAttribute("requiredFeatures")) {
1828 continue;
1829 }
1830 if (b.hasAttribute("requiredExtensions")) {
1831 // we do not support any extensions
1832 continue;
1833 }
1834 if (b.hasAttribute("systemLanguage")) {
1835 // not implemented yet
1836 }
1837 }
1838
1839 QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
1840 shapes.append(currentShapes);
1841
1842 // if we are parsing a switch, stop after the first supported element
1843 if (isSwitch && !currentShapes.isEmpty())
1844 break;
1845 }
1846 return shapes;
1847}
1848
1849void SvgParser::parseDefsElement(const QDomElement &e)
1850{
1851 KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
1853}
1854
1856{
1858
1859 // save definition for later instantiation with 'use'
1861 if (deferredUseStore) {
1862 deferredUseStore->checkPendingUse(b, shapes);
1863 }
1864
1865 if (b.tagName() == "svg") {
1866 shapes += parseSvg(b);
1867 } else if (b.tagName() == "g" || b.tagName() == "a") {
1868 // treat svg link <a> as group so we don't miss its child elements
1869 shapes += parseGroup(b);
1870 } else if (b.tagName() == "symbol") {
1871 parseSymbol(b);
1872 } else if (b.tagName() == "switch") {
1874 shapes += parseContainer(b);
1876 } else if (b.tagName() == "defs") {
1877 if (b.childNodes().count() > 0) {
1883 KoShape *defsShape = parseGroup(b);
1884 defsShape->setVisible(false);
1885 m_defsShapes << defsShape; // TODO: where to delete the shape!?
1886
1887 }
1888 } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
1889 } else if (b.tagName() == "pattern") {
1890 } else if (b.tagName() == "filter") {
1891 // not supported!
1892 } else if (b.tagName() == "clipPath") {
1893 parseClipPath(b);
1894 } else if (b.tagName() == "mask") {
1895 parseClipMask(b);
1896 } else if (b.tagName() == "marker") {
1897 parseMarker(b);
1898 } else if (b.tagName() == "style") {
1900 } else if (b.tagName() == "text" || b.tagName() == "tspan" || b.tagName() == "textPath") {
1902 } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline"
1903 || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") {
1904 KoShape *shape = createObjectDirect(b);
1905
1906 if (shape) {
1907 if (!shape->outlineRect().isNull() || !shape->boundingRect().isNull()) {
1908 shapes.append(shape);
1909 } else {
1910 debugFlake << "WARNING: shape is totally empty!" << shape->shapeId() << ppVar(shape->outlineRect());
1911 debugFlake << " " << shape->shapeId() << ppVar(shape->outline());
1912 {
1913 QString string;
1914 QTextStream stream(&string);
1916 stream << b;
1917 debugFlake << " " << string;
1918 }
1919 delete shape;
1920 }
1921 }
1922 } else if (b.tagName() == "use") {
1923 KoShape* s = parseUse(b, deferredUseStore);
1924 if (s) {
1925 shapes += s;
1926 }
1927 } else if (b.tagName() == "color-profile") {
1929 } else {
1930 // this is an unknown element, so try to load it anyway
1931 // there might be a shape that handles that element
1932 KoShape *shape = createObject(b);
1933 if (shape) {
1934 shapes.append(shape);
1935 }
1936 }
1937
1938 return shapes;
1939}
1940
1941// Creating functions
1942// ---------------------------------------------------------------------------------------
1943
1944KoShape * SvgParser::createPath(const QDomElement &element)
1945{
1946 KoShape *obj = 0;
1947 if (element.tagName() == "line") {
1948 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1949 if (path) {
1950 double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
1951 double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
1952 double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
1953 double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
1954 path->clear();
1955 path->moveTo(QPointF(x1, y1));
1956 path->lineTo(QPointF(x2, y2));
1957 path->normalize();
1958 obj = path;
1959 }
1960 } else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
1961 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1962 if (path) {
1963 path->clear();
1964
1965 bool bFirst = true;
1966 QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
1967 for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
1968 QPointF point;
1970 ++it;
1971 if (it == pointList.end())
1972 break;
1974 if (bFirst) {
1975 path->moveTo(point);
1976 bFirst = false;
1977 } else
1978 path->lineTo(point);
1979 }
1980 if (element.tagName() == "polygon")
1981 path->close();
1982
1983 path->setPosition(path->normalize());
1984
1985 obj = path;
1986 }
1987 } else if (element.tagName() == "path") {
1988 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1989 if (path) {
1990 path->clear();
1991
1992 KoPathShapeLoader loader(path);
1993 loader.parseSvg(element.attribute("d"), true);
1994 path->setPosition(path->normalize());
1995
1996 QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
1997 SvgUtil::fromUserSpace(path->position().y()));
1998 QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
1999 SvgUtil::fromUserSpace(path->size().height()));
2000
2001 path->setSize(newSize);
2002 path->setPosition(newPosition);
2003
2004 if (element.hasAttribute("sodipodi:nodetypes")) {
2005 path->loadNodeTypes(element.attribute("sodipodi:nodetypes"));
2006 }
2007 obj = path;
2008 }
2009 }
2010
2011 return obj;
2012}
2013
2015{
2018
2020 if (obj) {
2022 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2023
2024 applyCurrentStyle(obj, extraOffset);
2025
2026 // handle id
2027 applyId(b.attribute("id"), obj);
2030 }
2031
2033
2034 if (obj) {
2036 }
2037 return obj;
2038}
2039
2040KoShape * SvgParser::createObject(const QDomElement &b, const SvgStyles &style)
2041{
2043
2045 if (obj) {
2047 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2048
2049 SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
2050 m_context.styleParser().parseFont(objStyle);
2051 applyStyle(obj, objStyle, extraOffset);
2052
2053 // handle id
2054 applyId(b.attribute("id"), obj);
2057 }
2058
2060
2061 if (obj) {
2063 }
2064
2065 return obj;
2066}
2067
2068KoShape * SvgParser::createShapeFromElement(const QDomElement &element, SvgLoadingContext &context)
2069{
2070 KoShape *object = 0;
2071
2072
2073 const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
2075
2076 foreach (KoShapeFactoryBase *f, factories) {
2077 KoShape *shape = f->createDefaultShape(m_documentResourceManager);
2078 if (!shape)
2079 continue;
2080
2081 SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
2082 if (!svgShape) {
2083 delete shape;
2084 continue;
2085 }
2086
2087 // reset transformation that might come from the default shape
2088 shape->setTransformation(QTransform());
2089
2090 // reset border
2091 KoShapeStrokeModelSP oldStroke = shape->stroke();
2093
2094 // reset fill
2096
2097 if (!svgShape->loadSvg(element, context)) {
2098 delete shape;
2099 continue;
2100 }
2101
2102 object = shape;
2103 break;
2104 }
2105
2106 if (!object) {
2107 object = createPath(element);
2108 }
2109
2110 return object;
2111}
2112
2113KoShape *SvgParser::createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2114{
2115 if (value.isEmpty()) {
2116 return 0;
2117 }
2118 unsigned int start = value.indexOf('(') + 1;
2119 unsigned int end = value.indexOf(')', start);
2120
2121 QString val = value.mid(start, end - start);
2122 QString fillRule;
2123 if (val.startsWith("evenodd,")) {
2124 start += QString("evenodd,").size();
2125 fillRule = "evenodd";
2126 } else if (val.startsWith("nonzero,")) {
2127 start += QString("nonzero,").size();
2128 fillRule = "nonzero";
2129 }
2130 val = value.mid(start, end - start);
2131
2132 QDomElement el;
2133 if (value.startsWith("url(")) {
2134 start = value.indexOf('#') + 1;
2135 KoShape *s = m_context.shapeById(value.mid(start, end - start));
2136 if (s) {
2137 const QTransform absTf = s->absoluteTransformation();
2138 KoShape *cloned = s->cloneShape();
2139 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
2140 // When we have a parent, the shape is inside the defs, but when not,
2141 // it's in the group we're in the currently parsing.
2142
2143 if (cloned && shapeInDefs(s) && hideShapesFromDefs) {
2144 cloned->setTransparency(1.0);
2145 }
2146 return cloned;
2147 }
2148 } else if (value.startsWith("circle(")) {
2149 el = e.ownerDocument().createElement("circle");
2150 QStringList params = val.split(" ");
2151 el.setAttribute("r", SvgUtil::parseUnitXY(context.currentGC(), context.resolvedProperties(), params.first()));
2152 if (params.contains("at")) {
2153 // 1 == "at"
2154 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(2)));
2155 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(3)));
2156 }
2157 } else if (value.startsWith("ellipse(")) {
2158 el = e.ownerDocument().createElement("ellipse");
2159 QStringList params = val.split(" ");
2160 el.setAttribute("rx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(0)));
2161 el.setAttribute("ry", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(1)));
2162 if (params.contains("at")) {
2163 // 2 == "at"
2164 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(3)));
2165 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(4)));
2166 }
2167 } else if (value.startsWith("polygon(")) {
2168 el = e.ownerDocument().createElement("polygon");
2169 QStringList points;
2170 Q_FOREACH(QString point, SvgUtil::simplifyList(val)) {
2171 bool xVal = points.size() % 2;
2172 if (xVal) {
2173 points.append(QString::number(SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), point)));
2174 } else {
2175 points.append(QString::number(SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), point)));
2176 }
2177 }
2178 el.setAttribute("points", points.join(" "));
2179 } else if (value.startsWith("path(")) {
2180 el = e.ownerDocument().createElement("path");
2181 // SVG path data is inside a string.
2182 start += 1;
2183 end -= 1;
2184 el.setAttribute("d", value.mid(start, end - start));
2185 }
2186
2187 el.setAttribute("fill-rule", fillRule);
2188 KoShape *shape = createShapeFromElement(el, context);
2189 if (shape) shape->setTransparency(1.0);
2190 return shape;
2191}
2192
2193QList<KoShape *> SvgParser::createListOfShapesFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2194{
2195 QList<KoShape*> shapeList;
2196 if (value == "auto" || value == "none") {
2197 return shapeList;
2198 }
2199 QStringList params = value.split(")");
2200 Q_FOREACH(const QString param, params) {
2201 KoShape *s = createShapeFromCSS(e, param.trimmed()+")", context, hideShapesFromDefs);
2202 if (s) {
2203 shapeList.append(s);
2204 }
2205 }
2206 return shapeList;
2207}
2208
2209KoShape *SvgParser::createShape(const QString &shapeID)
2210{
2211 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
2212 if (!factory) {
2213 debugFlake << "Could not find factory for shape id" << shapeID;
2214 return 0;
2215 }
2216
2218 if (!shape) {
2219 debugFlake << "Could not create Default shape for shape id" << shapeID;
2220 return 0;
2221 }
2222 if (shape->shapeId().isEmpty()) {
2223 shape->setShapeId(factory->id());
2224 }
2225
2226 // reset transformation that might come from the default shape
2227 shape->setTransformation(QTransform());
2228
2229 // reset border
2230 // ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
2232
2233 // reset fill
2235
2236 return shape;
2237}
2238
2239void SvgParser::applyId(const QString &id, KoShape *shape)
2240{
2241 if (id.isEmpty())
2242 return;
2243
2244 KoShape *existingShape = m_context.shapeById(id);
2245 if (existingShape) {
2246 debugFlake << "SVG contains nodes with duplicated id:" << id;
2247 // Generate a random name and just don't register the shape.
2248 // We don't use the name as a unique identifier so we don't need to
2249 // worry about the extremely rare case of name collision.
2250 const QString suffix = QString::number(QRandomGenerator::system()->bounded(0x10000000, 0x7FFFFFFF), 16);
2251 const QString newName = id + '_' + suffix;
2252 shape->setName(newName);
2253 } else {
2254 shape->setName(id);
2255 m_context.registerShape(id, shape);
2256 }
2257}
#define debugFlake
Definition FlakeDebug.h:15
float value(const T *src, size_t ch)
const Params2D p
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
QSharedPointer< KoShapeStrokeModel > KoShapeStrokeModelSP
#define KoPathShapeId
Definition KoPathShape.h:20
#define KoSvgTextShape_SHAPEID
#define KoSvgTextShape_TEXTCONTOURGROUP
QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
QGradient * prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform)
SvgMeshGradient * prepareMeshGradientForShape(SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc)
QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
#define forEachElement(elem, parent)
QMap< QString, QString > SvgStyles
Clip path used to clip shapes.
A simple solid color shape background.
const T value(const QString &id) const
T get(const QString &id) const
A gradient shape background.
Definition KoID.h:30
static MarkerCoordinateSystem coordinateSystemFromString(const QString &value)
Definition KoMarker.cpp:155
void parseSvg(const QString &svgInputData, bool process=false)
The position of a path point within a path shape.
Definition KoPathShape.h:63
void setAutoFillMarkers(bool value)
void setFillRule(Qt::FillRule fillRule)
Sets the fill rule to be used for painting the background.
void setMarker(KoMarker *marker, KoFlake::MarkerPosition pos)
void clear()
Removes all subpaths and their points from the path.
virtual KoShape * createDefaultShape(KoDocumentResourceManager *documentResources=0) const
The undo / redo command for grouping shapes.
void redo() override
redo the command
QList< KoShapeFactoryBase * > factoriesForElement(const QString &nameSpace, const QString &elementName)
static KoShapeRegistry * instance()
virtual void setPaintOrder(PaintOrder first, PaintOrder second)
setPaintOrder set the paint order. As there's only three entries in any given paintorder,...
Definition KoShape.cpp:669
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:740
void setName(const QString &name)
Definition KoShape.cpp:960
virtual QRectF outlineRect() const
Definition KoShape.cpp:566
bool hasCommonParent(const KoShape *shape) const
Definition KoShape.cpp:510
void setInheritBackground(bool value)
setInheritBackground marks a shape as inheriting the background from the parent shape....
Definition KoShape.cpp:773
void setZIndex(qint16 zIndex)
Definition KoShape.cpp:787
QString shapeId() const
Definition KoShape.cpp:880
virtual QPainterPath outline() const
Definition KoShape.cpp:559
void setClipMask(KoClipMask *clipMask)
Sets a new clip mask, removing the old one. The mask is owned by the shape.
Definition KoShape.cpp:938
void setTransparency(qreal transparency)
Definition KoShape.cpp:642
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:704
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:890
void applyAbsoluteTransformation(const QTransform &matrix)
Definition KoShape.cpp:353
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:299
virtual void setStroke(KoShapeStrokeModelSP stroke)
Definition KoShape.cpp:904
void setAdditionalAttribute(const QString &name, const QString &value)
Definition KoShape.cpp:1077
QTransform absoluteTransformation() const
Definition KoShape.cpp:335
void setTransformation(const QTransform &matrix)
Definition KoShape.cpp:374
virtual void setBackground(QSharedPointer< KoShapeBackground > background)
Definition KoShape.cpp:751
void setClipPath(KoClipPath *clipPath)
Sets a new clip path, removing the old one.
Definition KoShape.cpp:926
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:172
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:383
void setInheritStroke(bool value)
setInheritStroke marks a shape as inheriting the stroke from the parent shape. NOTE: The currently se...
Definition KoShape.cpp:913
@ Stroke
Definition KoShape.h:117
@ Markers
Definition KoShape.h:118
void setVisible(bool on)
Definition KoShape.cpp:795
void setShapeId(const QString &id)
Definition KoShape.cpp:885
void leaveNodeSubtree()
Set the current node to its parent, leaving the subtree.
bool loadSvgText(const QDomText &text, SvgLoadingContext &context)
Loads the textt into the current node.
void setTextPathOnCurrentNode(KoShape *s)
Set the textPath on the current node.
void nextNode()
Switch to next node.
void enterNodeSubtree()
Set the current node to its first child, entering the subtree.
void setStyleInfo(KoShape *s)
Set the style info from the shape. This is necessary because SVGParser only understands loading the b...
bool loadSvg(const QDomElement &element, SvgLoadingContext &context, bool root=false)
Create a new text node.
@ KraTextVersionId
Int, used for handling incorrectly saved files.
QVariant property(PropertyId id, const QVariant &defaultValue=QVariant()) const
bool hasProperty(PropertyId id) const
void setProperty(PropertyId id, const QVariant &value)
void setShapesSubtract(QList< KoShape * > shapesSubtract)
setShapesSubtract
void setShapesInside(QList< KoShape * > shapesInside)
setShapesInside
static const QString svg
Definition KoXmlNS.h:39
KoFlake::CoordinateSystem clipPathUnits() const
Returns the clip path units type.
void setClipPathUnits(KoFlake::CoordinateSystem clipPathUnits)
Set the clip path units type.
QList< KoShape * > shapes() const
void setShapes(const QList< KoShape * > &shapes)
QGradient * gradient() const
Returns the gradient.
KoFlake::CoordinateSystem gradientUnits() const
Returns gradient units type.
bool isMeshGradient() const
void setSpreadMode(const QGradient::Spread &spreadMode)
void setMeshGradient(SvgMeshGradient *g)
Sets the meshgradient.
void setGradient(QGradient *g)
Sets the gradient.
void setGradientUnits(KoFlake::CoordinateSystem units)
Sets the gradient units type.
QTransform transform() const
Returns the gradient transformation.
void setTransform(const QTransform &transform)
Sets the gradient transformation.
QScopedPointer< SvgMeshGradient > & meshgradient()
Returns the meshgradient.
QString clipPathId
the current clip path id
Qt::FillRule fillRule
the current fill rule
QRectF currentBoundingBox
the current bound box used for bounding box units
bool visible
controls visibility of the shape (inherited)
void workaroundClearInheritedFillProperties()
QString fillId
the current fill id (used for gradient/pattern fills)
KoSvgTextProperties textProperties
Stores textProperties.
QString shapeSubtractValue
String of value shape-subtract, will be parsed later.
QString strokeId
the current stroke id (used for gradient strokes)
bool display
controls display of shape
QTransform matrix
the current transformation matrix
StyleType strokeType
the current stroke type
QString paintOrder
String list indicating paint order;.
QString shapeInsideValue
String of value shape-inside, will be parsed later.
KoShapeStrokeSP stroke
the current stroke
QString clipMaskId
the current clip mask id
qreal pixelsPerInch
controls the resolution of the image raster
StyleType fillType
the current fill type
@ Complex
gradient or pattern style
qreal opacity
the shapes opacity
QColor fillColor
the current fill color. Default is black fill as per svg spec
Contains data used for loading svg.
void addDefinition(const QDomElement &element)
Adds a definition for later use.
void setInitialXmlBaseDir(const QString &baseDir)
Sets the initial xml base dir, i.e. the directory the svg file is read from.
SvgGraphicsContext * pushGraphicsContext(const QDomElement &element=QDomElement(), bool inherit=true)
Pushes a new graphics context to the stack.
void registerShape(const QString &id, KoShape *shape)
Registers a shape so it can be referenced later.
void popGraphicsContext()
Pops the current graphics context from the stack.
void addStyleSheet(const QDomElement &styleSheet)
Adds a css style sheet.
QString xmlBaseDir() const
Returns the current xml base dir.
KoShape * shapeById(const QString &id)
Returns shape with specified id.
QDomElement definition(const QString &id) const
Returns the definition with the specified id.
SvgGraphicsContext * currentGC() const
Returns the current graphics context.
KoSvgTextProperties resolvedProperties() const
These are the text properties, completely resolved, ensuring that everything is inherited and the siz...
void parseProfile(const QDomElement &element)
parses 'color-profile' tag and saves it in the context
void setFileFetcher(FileFetcherFunc func)
SvgStyleParser * styleParser
bool hasDefinition(const QString &id) const
Checks if a definition with the specified id exists.
int nextZIndex()
Returns the next z-index.
void setTransform(const QTransform &matrix)
bool parseClipPath(const QDomElement &)
Parses a clip path element.
void applyCurrentBasicStyle(KoShape *shape)
void parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader, bool hideShapesFromDefs=true)
parse children of a <text > element into the root shape.
void parseMetadataApplyToShape(const QDomElement &e, KoShape *shape)
This parses the SVG native title and desc elements and adds them into additional attributes.
SvgGradientHelper * parseGradient(const QDomElement &)
Parses a gradient element.
KoShape * createShapeFromElement(const QDomElement &element, SvgLoadingContext &context)
Creates shape from specified svg element.
QString m_documentDescription
Definition SvgParser.h:236
QList< KoShape * > createListOfShapesFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs=true)
Create a list of shapes from a CSS shapes definition with potentially multiple shapes.
qreal parseUnitX(const QString &unit)
parses a length attribute in x-direction
void applyMarkers(KoPathShape *shape)
void applyFillStyle(KoShape *shape)
Applies the current fill style to the object.
void addToGroup(QList< KoShape * > shapes, KoShapeContainer *group)
Adds list of shapes to the given group shape.
QMap< QString, SvgClipPathHelper > m_clipPaths
Definition SvgParser.h:227
bool m_inheritStrokeFillByDefault
Definition SvgParser.h:239
void applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
Applies the current clip path to the object.
bool m_isInsideTextSubtree
Definition SvgParser.h:234
bool parseClipMask(const QDomElement &e)
static QDomDocument createDocumentFromSvg(QIODevice *device, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0)
QString m_documentTitle
Definition SvgParser.h:235
QVector< KoSvgSymbol * > takeSymbols()
QList< KoShape * > m_defsShapes
Definition SvgParser.h:233
void applyStyle(KoShape *, const QDomElement &, const QPointF &shapeToOriginalUserCoordinates)
Applies styles to the given shape.
bool parseMarker(const QDomElement &e)
virtual ~SvgParser()
QMap< QString, KoSvgSymbol * > m_symbols
Definition SvgParser.h:232
void setXmlBaseDir(const QString &baseDir)
Sets the initial xml base directory (the directory form where the file is read)
void applyId(const QString &id, KoShape *shape)
Applies id to specified shape.
SvgParser(KoDocumentResourceManager *documentResourceManager)
QMap< QString, QExplicitlySharedDataPointer< KoMarker > > m_markers
Definition SvgParser.h:229
KoShape * parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape=0)
QDomText getTheOnlyTextChild(const QDomElement &e)
void applyStrokeStyle(KoShape *shape)
Applies the current stroke style to the object.
QString documentTitle() const
KoShape * createPath(const QDomElement &)
Create path object from the given xml element.
qreal parseAngular(const QString &unit)
parses a angular attribute values, result in radians
KoShape * getTextPath(const QDomElement &e, bool hideShapesFromDefs=true)
Get the path for the gives textPath element.
QSharedPointer< KoVectorPatternBackground > parsePattern(const QDomElement &e, const KoShape *__shape)
Parses a pattern element.
void setResolveTextPropertiesForTopLevel(const bool enable)
QStringList warnings() const
KoDocumentResourceManager * m_documentResourceManager
Definition SvgParser.h:230
void applyViewBoxTransform(const QDomElement &element)
SvgGradientHelper * parseMeshGradient(const QDomElement &)
Parses mesh gradient element.
void uploadStyleToContext(const QDomElement &e)
QList< KoShape * > parseSvg(const QDomElement &e, QSizeF *fragmentSize=0)
Parses a svg fragment, returning the list of top level child shapes.
std::function< QByteArray(const QString &) FileFetcherFunc)
Definition SvgParser.h:81
KoShape * parseUse(const QDomElement &, DeferredUseStore *deferredUseStore)
Parses a use element, returning a list of child shapes.
bool shapeInDefs(const KoShape *shape)
Check whether the shapes are in the defs of the SVG document.
QList< KoShape * > shapes() const
Returns the list of all shapes of the svg document.
bool parseSymbol(const QDomElement &e)
void setFileFetcher(FileFetcherFunc func)
bool m_resolveTextPropertiesForTopLevel
Definition SvgParser.h:240
KoShape * createObjectDirect(const QDomElement &b)
SvgClipPathHelper * findClipPath(const QString &id)
find clip path with given id in clip path map
qreal parseUnitY(const QString &unit)
parses a length attribute in y-direction
void setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
QSharedPointer< KoVectorPatternBackground > findPattern(const QString &id, const KoShape *shape)
find pattern with given id in pattern map
void setDefaultKraTextVersion(int version)
QMap< KoShape *, QTransform > m_shapeParentTransform
Definition SvgParser.h:238
QList< QPair< QString, QColor > > parseMeshPatch(const QDomNode &meshpatch)
Parses a single meshpatch and returns the pointer.
QList< QExplicitlySharedDataPointer< KoMarker > > knownMarkers() const
QString documentDescription() const
void applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
QList< KoShape * > m_shapes
Definition SvgParser.h:231
void applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
KoShape * createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs=true)
Creates a shape from a CSS shapes definition.
SvgLoadingContext m_context
Definition SvgParser.h:225
QVector< KoID > m_warnings
Definition SvgParser.h:237
SvgGradientHelper * findGradient(const QString &id)
find gradient with given id in gradient map
qreal parseUnitXY(const QString &unit)
parses a length attribute in xy-direction
QList< KoShape * > parseContainer(const QDomElement &)
Parses a container element, returning a list of child shapes.
QMap< QString, QSharedPointer< KoClipMask > > m_clipMasks
Definition SvgParser.h:228
KoShape * parseGroup(const QDomElement &e, const QDomElement &overrideChildrenFrom=QDomElement(), bool createContext=true)
Parses a group-like element element, saving all its topmost properties.
QList< KoShape * > parseSingleElement(const QDomElement &b, DeferredUseStore *deferredUseStore=0)
XXX.
qreal parseUnit(const QString &, bool horiz=false, bool vert=false, const QRectF &bbox=QRectF())
parses a length attribute
void setFillStrokeInheritByDefault(const bool enable)
KoShape * resolveUse(const QDomElement &e, const QString &key)
void parseDefsElement(const QDomElement &e)
void applyPaintOrder(KoShape *shape)
KoShape * createShape(const QString &shapeID)
creates a shape from the given shape id
QMap< QString, SvgGradientHelper > m_gradients
Definition SvgParser.h:226
KoShape * createObject(const QDomElement &, const SvgStyles &style=SvgStyles())
Creates an object from the given xml element.
An interface providing svg loading and saving routines.
Definition SvgShape.h:18
virtual bool loadSvg(const QDomElement &element, SvgLoadingContext &context)
Loads data from specified svg element.
Definition SvgShape.cpp:19
QPair< qreal, QColor > parseColorStop(const QDomElement &, SvgGraphicsContext *context, qreal &previousOffset)
void parseColorStops(QGradient *, const QDomElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops)
Parses gradient color stops.
void parseStyle(const SvgStyles &styles, const bool inheritByDefault=false)
Parses specified style attributes.
SvgStyles collectStyles(const QDomElement &)
Creates style map from given xml element.
void parseFont(const SvgStyles &styles)
Parses font attributes.
static qreal parseUnitX(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in x-direction
Definition SvgUtil.cpp:304
static double fromPercentage(QString s, bool *ok=nullptr)
Definition SvgUtil.cpp:64
static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit)
parses angle, result in radians!
Definition SvgUtil.cpp:332
static bool parseViewBox(const QDomElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform)
Parses a viewbox attribute into an rectangle.
Definition SvgUtil.cpp:133
static QStringList simplifyList(const QString &str)
Definition SvgUtil.cpp:450
static qreal parseUnit(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, QStringView, bool horiz=false, bool vert=false, const QRectF &bbox=QRectF())
Parses a length attribute.
Definition SvgUtil.cpp:218
static double fromUserSpace(double value)
Definition SvgUtil.cpp:29
static qreal parseUnitXY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in xy-direction
Definition SvgUtil.cpp:322
static qreal parseUnitY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in y-direction
Definition SvgUtil.cpp:313
static QString mapExtendedShapeTag(const QString &tagName, const QDomElement &element)
Definition SvgUtil.cpp:432
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define ppVar(var)
Definition kis_debug.h:155
QSharedPointer< T > toQShared(T *ptr)
auto maxDimension(Size size) -> decltype(size.width())
QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc)
double toDouble(const QString &str, bool *ok=nullptr)
void setUtf8OnStream(QTextStream &stream)
KRITAFLAKE_EXPORT QGradient * cloneGradient(const QGradient *gradient)
clones the given gradient
Definition KoFlake.cpp:17
CoordinateSystem coordinatesFromString(const QString &value, CoordinateSystem defaultValue)
@ EndMarker
Definition KoFlake.h:44
@ StartMarker
Definition KoFlake.h:42
@ MidMarker
Definition KoFlake.h:43
void setExtraShapeOffset(const QPointF &value)
QPointF point
El(const QDomElement *ue, const QString &key)
Definition SvgParser.cpp:72
const QDomElement * m_useElement
Definition SvgParser.cpp:75
void checkPendingUse(const QDomElement &b, QList< KoShape * > &shapes)
Definition SvgParser.cpp:89
DeferredUseStore(SvgParser *p)
Definition SvgParser.cpp:78
void add(const QDomElement *useE, const QString &key)
Definition SvgParser.cpp:82
std::vector< El > m_uses