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.open(QIODevice::ReadOnly)) {
230 return file.readAll();
231 }
232 }
233 return QByteArray();
234 });
235}
236
237void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
238{
242 m_context.currentGC()->pixelsPerInch = pixelsPerInch;
243
244 const qreal scale = 72.0 / pixelsPerInch;
245 const QTransform t = QTransform::fromScale(scale, scale);
246 m_context.currentGC()->currentBoundingBox = boundsInPixels;
248}
249
255
257{
259}
260
265
267{
268 return m_shapes;
269}
270
272{
273 QVector<KoSvgSymbol*> symbols = m_symbols.values().toVector();
274 m_symbols.clear();
275 return symbols;
276}
277
278// Helper functions
279// ---------------------------------------------------------------------------------------
280
282{
283 SvgGradientHelper *result = 0;
284
285 // check if gradient was already parsed, and return it
286 if (m_gradients.contains(id)) {
287 result = &m_gradients[ id ];
288 }
289
290 // check if gradient was stored for later parsing
291 if (!result && m_context.hasDefinition(id)) {
292 const QDomElement &e = m_context.definition(id);
293 if (e.tagName().contains("Gradient")) {
294 result = parseGradient(m_context.definition(id));
295 } else if (e.tagName() == "meshgradient") {
297 }
298 }
299
300 return result;
301}
302
304{
306
307 // check if gradient was stored for later parsing
308 if (m_context.hasDefinition(id)) {
309 const QDomElement &e = m_context.definition(id);
310 if (e.tagName() == "pattern") {
311 result = parsePattern(m_context.definition(id), shape);
312 }
313 }
314
315 return result;
316}
317
319{
320 return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
321}
322
323// Parsing functions
324// ---------------------------------------------------------------------------------------
325
326qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
327{
328 return SvgUtil::parseUnit(m_context.currentGC(), m_context.resolvedProperties(), unit, horiz, vert, bbox);
329}
330
331qreal SvgParser::parseUnitX(const QString &unit)
332{
334}
335
336qreal SvgParser::parseUnitY(const QString &unit)
337{
339}
340
341qreal SvgParser::parseUnitXY(const QString &unit)
342{
344}
345
346qreal SvgParser::parseAngular(const QString &unit)
347{
349}
350
351
353{
354 // IMPROVEMENTS:
355 // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
356 // - A gradient inherits attributes it does not have from the referencing gradient.
357 // - Gradients with no color stops have no fill or stroke.
358 // - Gradients with one color stop have a solid color.
359
361 if (!gc) return 0;
362
363 SvgGradientHelper gradHelper;
364
365 QString gradientId = e.attribute("id");
366 if (gradientId.isEmpty()) return 0;
367
368 // check if we have this gradient already parsed
369 // copy existing gradient if it exists
370 if (m_gradients.contains(gradientId)) {
371 return &m_gradients[gradientId];
372 }
373
374 if (e.hasAttribute("xlink:href")) {
375 // strip the '#' symbol
376 QString href = e.attribute("xlink:href").mid(1);
377
378 if (!href.isEmpty()) {
379 // copy the referenced gradient if found
380 SvgGradientHelper *pGrad = findGradient(href);
381 if (pGrad) {
382 gradHelper = *pGrad;
383 }
384 }
385 }
386
387 const QGradientStops defaultStops = gradHelper.gradient()->stops();
388
389 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
391 }
392
395
396 if (e.tagName() == "linearGradient") {
397 QLinearGradient *g = new QLinearGradient();
398 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
399 g->setCoordinateMode(QGradient::ObjectBoundingMode);
400 g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
401 SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
402 g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
403 SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
404 } else {
405 g->setStart(QPointF(parseUnitX(e.attribute("x1")),
406 parseUnitY(e.attribute("y1"))));
407 g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
408 parseUnitY(e.attribute("y2"))));
409 }
410 gradHelper.setGradient(g);
411
412 } else if (e.tagName() == "radialGradient") {
413 QRadialGradient *g = new QRadialGradient();
414 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
415 g->setCoordinateMode(QGradient::ObjectBoundingMode);
416 g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
417 SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
418 g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
419 g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
420 SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
421 } else {
422 g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
423 parseUnitY(e.attribute("cy"))));
424 g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
425 parseUnitY(e.attribute("fy"))));
426 g->setRadius(parseUnitXY(e.attribute("r")));
427 }
428 gradHelper.setGradient(g);
429 } else {
430 debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName();
431 }
432
433 // handle spread method
434 QGradient::Spread spreadMethod = QGradient::PadSpread;
435 QString spreadMethodStr = e.attribute("spreadMethod");
436 if (!spreadMethodStr.isEmpty()) {
437 if (spreadMethodStr == "reflect") {
438 spreadMethod = QGradient::ReflectSpread;
439 } else if (spreadMethodStr == "repeat") {
440 spreadMethod = QGradient::RepeatSpread;
441 }
442 }
443
444 gradHelper.setSpreadMode(spreadMethod);
445
446 // Parse the color stops.
447 m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
448
449 if (e.hasAttribute("gradientTransform")) {
450 SvgTransformParser p(e.attribute("gradientTransform"));
451 if (p.isValid()) {
452 gradHelper.setTransform(p.transform());
453 }
454 }
455
457
458 m_gradients.insert(gradientId, gradHelper);
459
460 return &m_gradients[gradientId];
461}
462
464{
465 SvgGradientHelper gradHelper;
466 QString gradientId = e.attribute("id");
467 QScopedPointer<SvgMeshGradient> g(new SvgMeshGradient);
468
469 // check if we have this gradient already parsed
470 // copy existing gradient if it exists
471 if (m_gradients.contains(gradientId)) {
472 return &m_gradients[gradientId];
473 }
474
475 if (e.hasAttribute("xlink:href")) {
476 // strip the '#' symbol
477 QString href = e.attribute("xlink:href").mid(1);
478
479 if (!href.isEmpty()) {
480 // copy the referenced gradient if found
481 SvgGradientHelper *pGrad = findGradient(href);
482 if (pGrad) {
483 gradHelper = *pGrad;
484 }
485 }
486 }
487
488 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
490 }
491
492 if (e.hasAttribute("transform")) {
493 SvgTransformParser p(e.attribute("transform"));
494 if (p.isValid()) {
495 gradHelper.setTransform(p.transform());
496 }
497 }
498
499 QString type = e.attribute("type");
500 g->setType(SvgMeshGradient::BILINEAR);
501 if (!type.isEmpty() && type == "bicubic") {
502 g->setType(SvgMeshGradient::BICUBIC);
503 }
504
505 int irow = 0, icols;
506 for (int i = 0; i < e.childNodes().size(); ++i) {
507 QDomNode node = e.childNodes().at(i);
508
509 if (node.nodeName() == "meshrow") {
510
511 SvgMeshStop startingNode;
512 if (irow == 0) {
513 startingNode.point = QPointF(
514 parseUnitX(e.attribute("x")),
515 parseUnitY(e.attribute(("y"))));
516 startingNode.color = QColor();
517 }
518
519 icols = 0;
520 g->getMeshArray()->newRow();
521 for (int j = 0; j < node.childNodes().size() ; ++j) {
522 QDomNode meshpatchNode = node.childNodes().at(j);
523
524 if (meshpatchNode.nodeName() == "meshpatch") {
525 if (irow > 0) {
526 // Starting point for this would be the bottom (right) corner of the above patch
527 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Bottom, irow - 1, icols);
528 } else if (icols != 0) {
529 // Starting point for this would be the right (top) corner of the previous patch
530 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Right, irow, icols - 1);
531 }
532
533 QList<QPair<QString, QColor>> rawStops = parseMeshPatch(meshpatchNode);
534 // TODO handle the false result
535 if (!g->getMeshArray()->addPatch(rawStops, startingNode.point)) {
536 debugFlake << "WARNING: Failed to create meshpatch";
537 }
538 icols++;
539 }
540 }
541 irow++;
542 }
543 }
544 gradHelper.setMeshGradient(g.data());
545 m_gradients.insert(gradientId, gradHelper);
546
547 return &m_gradients[gradientId];
548}
549
550#define forEachElement( elem, parent ) \
551 for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \
552 if ( ( elem = _node.toElement() ).isNull() ) {} else
553
555{
556 // path and its associated color
558
560 if (!gc) return rawstops;
561
562 QDomElement e = meshpatchNode.toElement();
563
564 QDomElement stop;
565
566 forEachElement(stop, e) {
567 qreal X = 0; // dummy value, don't care, just to ensure the function won't blow up (also to avoid a Coverity issue)
568 QColor color = m_context.styleParser().parseColorStop(stop, gc, X).second;
569
570 QString pathStr = stop.attribute("path");
571
572 rawstops.append({pathStr, color});
573 }
574
575 return rawstops;
576}
577
578inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
579{
580 QTransform result =
581 patternTransform *
582 QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
583 patternTransform.inverted();
584 KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
585
586 return QPointF(result.dx(), result.dy());
587}
588
590{
598
600 if (!gc) return pattHelper;
601
602 const QString patternId = e.attribute("id");
603 if (patternId.isEmpty()) return pattHelper;
604
605 pattHelper = toQShared(new KoVectorPatternBackground);
606
607 if (e.hasAttribute("xlink:href")) {
608 // strip the '#' symbol
609 QString href = e.attribute("xlink:href").mid(1);
610
611 if (!href.isEmpty() &&href != patternId) {
612 // copy the referenced pattern if found
614 if (pPatt) {
615 pattHelper = pPatt;
616 }
617 }
618 }
619
620 pattHelper->setReferenceCoordinates(
621 KoFlake::coordinatesFromString(e.attribute("patternUnits"),
622 pattHelper->referenceCoordinates()));
623
624 pattHelper->setContentCoordinates(
625 KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
626 pattHelper->contentCoordinates()));
627
628 if (e.hasAttribute("patternTransform")) {
629 SvgTransformParser p(e.attribute("patternTransform"));
630 if (p.isValid()) {
631 pattHelper->setPatternTransform(p.transform());
632 }
633 }
634
635 if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
636 QRectF referenceRect(
637 SvgUtil::fromPercentage(e.attribute("x", "0%")),
638 SvgUtil::fromPercentage(e.attribute("y", "0%")),
639 SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
640 SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
641
642 pattHelper->setReferenceRect(referenceRect);
643 } else {
644 QRectF referenceRect(
645 parseUnitX(e.attribute("x", "0")),
646 parseUnitY(e.attribute("y", "0")),
647 parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
648 parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
649
650 pattHelper->setReferenceRect(referenceRect);
651 }
652
663 const QTransform dstShapeTransform = shape->absoluteTransformation();
664 const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
665 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
666 const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
667
669 gc = m_context.currentGC();
671
672 // start building shape tree from scratch
673 gc->matrix = QTransform();
674
675 const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
676 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
677 boundingRect.x(), boundingRect.y());
678
679
680
681 // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
682 // although we expect the pattern be reusable, but it is not so!
683 // WARNING2: the pattern shapes are stored in *User* coordinate system, although
684 // the "official" content system might be either OBB or User. It means that
685 // this baked transform should be stripped before writing the shapes back
686 // into SVG
687 if (e.hasAttribute("viewBox")) {
689 pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
690 relativeToShape.mapRect(pattHelper->referenceRect()) :
691 pattHelper->referenceRect();
692
694 pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
695
696 } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
697 gc->matrix = relativeToShape * gc->matrix;
698 }
699
700 // We do *not* apply patternTransform here! Here we only bake the untransformed
701 // version of the shape. The transformed one will be done in the very end while rendering.
702
703 QList<KoShape*> patternShapes = parseContainer(e);
704
705 if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
706 // In Krita we normalize the shapes, bake this transform into the pattern shapes
707
708 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
709
710 Q_FOREACH (KoShape *shape, patternShapes) {
711 shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
712 }
713 }
714
715 if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
716 // In Krita we normalize the shapes, bake this transform into reference rect
717 // NOTE: this is possible *only* when pattern transform is not perspective
718 // (which is always true for SVG)
719
720 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
721
722 QRectF ref = pattHelper->referenceRect();
723 ref.translate(offset);
724 pattHelper->setReferenceRect(ref);
725 }
726
728 gc = m_context.currentGC();
729
730 if (!patternShapes.isEmpty()) {
731 pattHelper->setShapes(patternShapes);
732 }
733
734 return pattHelper;
735}
736
737bool SvgParser::parseMarker(const QDomElement &e)
738{
739 const QString id = e.attribute("id");
740 if (id.isEmpty()) return false;
741
742 std::unique_ptr<KoMarker> marker(new KoMarker());
743 marker->setCoordinateSystem(
744 KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
745
746 marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
747 parseUnitY(e.attribute("refY"))));
748
749 marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
750 parseUnitY(e.attribute("markerHeight", "3"))));
751
752 const QString orientation = e.attribute("orient", "0");
753
754 if (orientation == "auto") {
755 marker->setAutoOrientation(true);
756 } else {
757 marker->setExplicitOrientation(parseAngular(orientation));
758 }
759
760 // ensure that the clip path is loaded in local coordinates system
762 m_context.currentGC()->matrix = QTransform();
763 m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
764
765 KoShape *markerShape = parseGroup(e);
766
768
769 if (!markerShape) return false;
770
771 marker->setShapes({markerShape});
772
773 m_markers.insert(id, QExplicitlySharedDataPointer<KoMarker>(marker.release()));
774
775 return true;
776}
777
778bool SvgParser::parseSymbol(const QDomElement &e)
779{
780 const QString id = e.attribute("id");
781
782 if (id.isEmpty()) return false;
783
784 std::unique_ptr<KoSvgSymbol> svgSymbol(new KoSvgSymbol());
785
786 // ensure that the clip path is loaded in local coordinates system
788 m_context.currentGC()->matrix = QTransform();
789 m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0);
790
791 QString title = e.firstChildElement("title").toElement().text();
792
793 std::unique_ptr<KoShape> symbolShape(parseGroup(e));
794
796
797 if (!symbolShape) return false;
798
799 svgSymbol->shape = symbolShape.release();
800 svgSymbol->title = title;
801 svgSymbol->id = id;
802 if (title.isEmpty()) svgSymbol->title = id;
803
804 if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
805 debugFlake << "Symbol" << id << "seems to be empty, discarding";
806 return false;
807 }
808
809 // TODO: out default set of symbols had duplicated ids! We should
810 // make sure they are unique!
811 if (m_symbols.contains(id)) {
812 delete m_symbols[id];
813 m_symbols.remove(id);
814 }
815
816 m_symbols.insert(id, svgSymbol.release());
817
818 return true;
819}
820
821void SvgParser::parseMetadataApplyToShape(const QDomElement &e, KoShape *shape)
822{
823 const QString titleTag = "title";
824 const QString descriptionTag = "desc";
825 QDomElement title = e.firstChildElement(titleTag);
826 if (!title.isNull()) {
827 QDomText text = getTheOnlyTextChild(title);
828 if (!text.data().isEmpty()) {
829 shape->setAdditionalAttribute(titleTag, text.data());
830 }
831 }
832 QDomElement description = e.firstChildElement(descriptionTag);
833 if (!description.isNull()) {
834 QDomText text = getTheOnlyTextChild(description);
835 if (!text.data().isEmpty()) {
836 shape->setAdditionalAttribute(descriptionTag, text.data());
837 }
838 }
839}
840
841bool SvgParser::parseClipPath(const QDomElement &e)
842{
843 SvgClipPathHelper clipPath;
844
845 const QString id = e.attribute("id");
846 if (id.isEmpty()) return false;
847
848 clipPath.setClipPathUnits(
849 KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
850
851 // ensure that the clip path is loaded in local coordinates system
853 m_context.currentGC()->matrix = QTransform();
855
856 KoShape *clipShape = parseGroup(e);
857
859
860 if (!clipShape) return false;
861
862 clipPath.setShapes({clipShape});
863 m_clipPaths.insert(id, clipPath);
864
865 return true;
866}
867
868bool SvgParser::parseClipMask(const QDomElement &e)
869{
871
872 const QString id = e.attribute("id");
873 if (id.isEmpty()) return false;
874
875 clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
876 clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
877
878 QRectF maskRect;
879
880 if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
881 maskRect.setRect(
882 SvgUtil::fromPercentage(e.attribute("x", "-10%")),
883 SvgUtil::fromPercentage(e.attribute("y", "-10%")),
884 SvgUtil::fromPercentage(e.attribute("width", "120%")),
885 SvgUtil::fromPercentage(e.attribute("height", "120%")));
886 } else {
887 maskRect.setRect(
888 parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
889 parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
890 parseUnitX(e.attribute("width", "120%")),
891 parseUnitY(e.attribute("height", "120%")));
892 }
893
894 clipMask->setMaskRect(maskRect);
895
896
897 // ensure that the clip mask is loaded in local coordinates system
899 m_context.currentGC()->matrix = QTransform();
901
902 KoShape *clipShape = parseGroup(e);
903
905
906 if (!clipShape) return false;
907 clipMask->setShapes({clipShape});
908
909 m_clipMasks.insert(id, clipMask);
910 return true;
911}
912
919
920void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
921{
922 if (!shape) return;
923
925
926 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
927 applyMarkers(pathShape);
928 }
929
930 applyClipping(shape, shapeToOriginalUserCoordinates);
931 applyMaskClipping(shape, shapeToOriginalUserCoordinates);
932
933}
934
936{
937 if (!shape) return;
938
940 KIS_ASSERT(gc);
941
942 if (!dynamic_cast<KoShapeGroup*>(shape)) {
943 applyFillStyle(shape);
944 applyStrokeStyle(shape);
945 }
946
947 if (!gc->display || !gc->visible) {
958 shape->setVisible(false);
959 }
960 shape->setTransparency(1.0 - gc->opacity);
961
962 applyPaintOrder(shape);
963}
964
965
966void SvgParser::applyStyle(KoShape *obj, const QDomElement &e, const QPointF &shapeToOriginalUserCoordinates)
967{
968 applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
969}
970
971void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
972{
974 if (!gc)
975 return;
976
978
979 if (!obj)
980 return;
981
982 if (!dynamic_cast<KoShapeGroup*>(obj)) {
983 applyFillStyle(obj);
984 applyStrokeStyle(obj);
985 }
986
987 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(obj)) {
988 applyMarkers(pathShape);
989 }
990
991 applyClipping(obj, shapeToOriginalUserCoordinates);
992 applyMaskClipping(obj, shapeToOriginalUserCoordinates);
993
994 if (!gc->display || !gc->visible) {
995 obj->setVisible(false);
996 }
997 obj->setTransparency(1.0 - gc->opacity);
998 applyPaintOrder(obj);
999}
1000
1002 const KoShape *shape,
1003 const SvgGraphicsContext *gc,
1004 QTransform *transform)
1005{
1006 QGradient *resultGradient = 0;
1007 KIS_ASSERT(transform);
1008
1009 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1010 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1011 *transform = gradient->transform();
1012 } else {
1013 if (gradient->gradient()->type() == QGradient::LinearGradient) {
1019 const QRectF boundingRect = shape->outline().boundingRect();
1020 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1021 boundingRect.x(), boundingRect.y());
1022
1023 const QTransform relativeToUser =
1024 relativeToShape * shape->transformation() * gc->matrix.inverted();
1025
1026 const QTransform userToRelative = relativeToUser.inverted();
1027
1028 const QLinearGradient *o = static_cast<const QLinearGradient*>(gradient->gradient());
1029 QLinearGradient *g = new QLinearGradient();
1030 g->setStart(userToRelative.map(o->start()));
1031 g->setFinalStop(userToRelative.map(o->finalStop()));
1032 g->setCoordinateMode(QGradient::ObjectBoundingMode);
1033 g->setStops(o->stops());
1034 g->setSpread(o->spread());
1035
1036 resultGradient = g;
1037 *transform = relativeToUser * gradient->transform() * userToRelative;
1038
1039 } else if (gradient->gradient()->type() == QGradient::RadialGradient) {
1040 // For radial and conical gradients such conversion is not possible
1041
1042 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1043 *transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
1044
1045 const QRectF outlineRect = shape->outlineRect();
1046 if (outlineRect.isEmpty()) return resultGradient;
1047
1054 QRadialGradient *rgradient = static_cast<QRadialGradient*>(resultGradient);
1055
1056 const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect);
1057 const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension));
1058
1059 const QTransform uniformizeTransform =
1060 QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) *
1061 QTransform::fromScale(maxDimension / shape->outlineRect().width(),
1062 maxDimension / shape->outlineRect().height()) *
1063 QTransform::fromTranslate(outlineRect.x(), outlineRect.y());
1064
1065 const QPointF centerLocal = transform->map(rgradient->center());
1066 const QPointF focalLocal = transform->map(rgradient->focalPoint());
1067
1068 const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize);
1069 const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize);
1070
1071 rgradient->setCenter(centerOBB);
1072 rgradient->setFocalPoint(focalOBB);
1073
1074 const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize);
1075 const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize);
1076
1077 rgradient->setCenterRadius(centerRadiusOBB);
1078 rgradient->setFocalRadius(focalRadiusOBB);
1079
1080 rgradient->setCoordinateMode(QGradient::ObjectBoundingMode);
1081
1082 // Warning: should it really be pre-multiplication?
1083 *transform = uniformizeTransform * gradient->transform();
1084 }
1085 }
1086
1087 // TODO: all gradients in Krita are rendered in a premultiplied-alpha
1088 // mode, which is against SVG standard. We need to fix that. Though
1089 // it requires deepeer changes, than just mere setting of the
1090 // QGradient's interpolation mode on loading.
1091 // resultGradient->setInterpolationMode(QGradient::ComponentInterpolation);
1092
1093 return resultGradient;
1094}
1095
1097 const KoShape *shape,
1098 const SvgGraphicsContext *gc) {
1099
1100 SvgMeshGradient *resultGradient = nullptr;
1101
1102 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1103
1104 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1105
1106 const QRectF boundingRect = shape->outline().boundingRect();
1107 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1108 boundingRect.x(), boundingRect.y());
1109
1110 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1111 // So, transform is called multiple times on the mesh and that's not nice
1112 resultGradient->setTransform(gradient->transform() * relativeToShape);
1113 } else {
1114 // NOTE: Krita's shapes use their own coordinate system. Where origin is at the top left
1115 // of the SHAPE. All the mesh patches will be rendered in the global 'user' coordinate system
1116 // where the origin is at the top left of the LAYER/DOCUMENT.
1117
1118 // Get the user coordinates of the shape
1119 const QTransform shapeglobal = shape->absoluteTransformation() * gc->matrix.inverted();
1120
1121 // Get the translation offset to shift the origin from "Shape" to "User"
1122 const QTransform translationOffset = QTransform::fromTranslate(-shapeglobal.dx(), -shapeglobal.dy());
1123
1124 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1125
1126 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1127 // So, transform is called multiple times on the mesh and that's not nice
1128 resultGradient->setTransform(gradient->transform() * translationOffset);
1129 }
1130
1131 return resultGradient;
1132}
1133
1135{
1137 if (! gc)
1138 return;
1139
1142 } else if (gc->fillType == SvgGraphicsContext::Solid) {
1144 } else if (gc->fillType == SvgGraphicsContext::Complex) {
1145 // try to find referenced gradient
1146 SvgGradientHelper *gradient = findGradient(gc->fillId);
1147 if (gradient) {
1148 QTransform transform;
1149
1150 if (gradient->isMeshGradient()) {
1152
1153 QScopedPointer<SvgMeshGradient> result(prepareMeshGradientForShape(gradient, shape, gc));
1154
1155 bg = toQShared(new KoMeshGradientBackground(result.data(), transform));
1156 shape->setBackground(bg);
1157 } else if (gradient->gradient()) {
1158 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1159 if (result) {
1161 bg = toQShared(new KoGradientBackground(result));
1162 bg->setTransform(transform);
1163 shape->setBackground(bg);
1164 }
1165 }
1166 } else {
1168 findPattern(gc->fillId, shape);
1169
1170 if (pattern) {
1171 shape->setBackground(pattern);
1172 } else {
1173 // no referenced fill found, use fallback color
1175 }
1176 }
1177 } else if (gc->fillType == SvgGraphicsContext::Inherit) {
1178 shape->setInheritBackground(true);
1179 }
1180
1181 KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
1182 if (path)
1183 path->setFillRule(gc->fillRule);
1184}
1185
1186void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
1187{
1188 const double lineWidth = srcStroke->lineWidth();
1189 QVector<qreal> dashes = srcStroke->lineDashes();
1190
1191 // apply line width to dashes and dash offset
1192 if (dashes.count() && lineWidth > 0.0) {
1193 const double dashOffset = srcStroke->dashOffset();
1194 QVector<qreal> dashes = srcStroke->lineDashes();
1195
1196 for (int i = 0; i < dashes.count(); ++i) {
1197 dashes[i] /= lineWidth;
1198 }
1199
1200 dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
1201 dstStroke->setDashOffset(dashOffset / lineWidth);
1202 } else {
1203 dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
1204 }
1205}
1206
1208{
1210 if (! gc)
1211 return;
1212
1214 KoShapeStrokeSP stroke(new KoShapeStroke());
1215 stroke->setLineWidth(0.0);
1216 const QColor color = Qt::transparent;
1217 stroke->setColor(color);
1218 shape->setStroke(stroke);
1219 } else if (gc->strokeType == SvgGraphicsContext::Solid) {
1220 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1221 applyDashes(gc->stroke, stroke);
1222 shape->setStroke(stroke);
1223 } else if (gc->strokeType == SvgGraphicsContext::Complex) {
1224 // try to find referenced gradient
1225 SvgGradientHelper *gradient = findGradient(gc->strokeId);
1226 if (gradient) {
1227 QTransform transform;
1228 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1229 if (result) {
1230 QBrush brush = *result;
1231 delete result;
1232 brush.setTransform(transform);
1233
1234 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1235 stroke->setLineBrush(brush);
1236 applyDashes(gc->stroke, stroke);
1237 shape->setStroke(stroke);
1238 }
1239 } else {
1240 // no referenced stroke found, use fallback color
1241 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1242 applyDashes(gc->stroke, stroke);
1243 shape->setStroke(stroke);
1244 }
1245 } else if (gc->strokeType == SvgGraphicsContext::Inherit) {
1246 shape->setInheritStroke(true);
1247 }
1248}
1249
1251{
1253 if (!gc)
1254 return;
1255
1256 if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
1258 }
1259
1260 if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
1262 }
1263
1264 if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
1266 }
1267
1269}
1270
1272{
1274 if (!gc)
1275 return;
1276
1277 if (!gc->paintOrder.isEmpty() && gc->paintOrder != "inherit") {
1278 QStringList paintOrder = gc->paintOrder.split(" ");
1280 Q_FOREACH(const QString p, paintOrder) {
1281 if (p == "fill") {
1282 order.append(KoShape::Fill);
1283 } else if (p == "stroke") {
1284 order.append(KoShape::Stroke);
1285 } else if (p == "markers") {
1286 order.append(KoShape::Markers);
1287 }
1288 }
1289 if (paintOrder.size() == 1 && order.isEmpty()) { // Normal
1291 }
1292 if (order.size() == 1) {
1293 if (order.first() == KoShape::Fill) {
1295 } else if (order.first() == KoShape::Stroke) {
1297 } else if (order.first() == KoShape::Markers) {
1299 }
1300 } else if (order.size() > 1) {
1301 shape->setPaintOrder(order.at(0), order.at(1));
1302 }
1303 }
1304}
1305
1306void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1307{
1309 if (! gc)
1310 return;
1311
1312 if (gc->clipPathId.isEmpty())
1313 return;
1314
1315 SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
1316 if (!clipPath || clipPath->isEmpty())
1317 return;
1318
1320
1321 Q_FOREACH (KoShape *item, clipPath->shapes()) {
1322 KoShape *clonedShape = item->cloneShape();
1323 KIS_ASSERT_RECOVER(clonedShape) { continue; }
1324
1325 shapes.append(clonedShape);
1326 }
1327
1328 if (!shapeToOriginalUserCoordinates.isNull()) {
1329 const QTransform t =
1330 QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
1331 shapeToOriginalUserCoordinates.y());
1332
1333 Q_FOREACH(KoShape *s, shapes) {
1335 }
1336 }
1337
1338 KoClipPath *clipPathObject = new KoClipPath(shapes,
1341 shape->setClipPath(clipPathObject);
1342}
1343
1344void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1345{
1347 if (!gc)
1348 return;
1349
1350 if (gc->clipMaskId.isEmpty())
1351 return;
1352
1353
1354 QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
1355 if (!originalClipMask || originalClipMask->isEmpty()) return;
1356
1357 KoClipMask *clipMask = originalClipMask->clone();
1358
1359 clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
1360
1361 shape->setClipMask(clipMask);
1362}
1363
1364KoShape* SvgParser::parseUse(const QDomElement &e, DeferredUseStore* deferredUseStore)
1365{
1366 QString href = e.attribute("xlink:href");
1367 if (href.isEmpty())
1368 return 0;
1369
1370 QString key = href.mid(1);
1371 const bool gotDef = m_context.hasDefinition(key);
1372 if (gotDef) {
1373 return resolveUse(e, key);
1374 } else if (deferredUseStore) {
1375 deferredUseStore->add(&e, key);
1376 return 0;
1377 }
1378 debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
1379 << key;
1380 return 0;
1381}
1382
1383KoShape* SvgParser::resolveUse(const QDomElement &e, const QString& key)
1384{
1385 KoShape *result = 0;
1386
1388
1389 // TODO: parse 'width' and 'height' as well
1390 gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
1391
1392 const QDomElement &referencedElement = m_context.definition(key);
1393 result = parseGroup(e, referencedElement, false);
1394
1396 return result;
1397}
1398
1400{
1401 m_shapes += shapes;
1402
1403 if (!group || shapes.isEmpty())
1404 return;
1405
1406 // not normalized
1407 KoShapeGroupCommand cmd(group, shapes, false);
1408 cmd.redo();
1409}
1410
1411QList<KoShape*> SvgParser::parseSvg(const QDomElement &e, QSizeF *fragmentSize)
1412{
1413 // check if we are the root svg element
1414 const bool isRootSvg = m_context.isRootContext();
1415
1416 // parse 'transform' field if preset
1418
1419 applyStyle(0, e, QPointF());
1420
1421 const QString w = e.attribute("width");
1422 const QString h = e.attribute("height");
1423
1424 qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
1425 qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
1426
1427 if (w.isEmpty() || h.isEmpty()) {
1428 QRectF viewRect;
1429 QTransform viewTransform_unused;
1430 QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
1431
1432 if (SvgUtil::parseViewBox(e, fakeBoundingRect,
1433 &viewRect, &viewTransform_unused)) {
1434
1435 QSizeF estimatedSize = viewRect.size();
1436
1437 if (estimatedSize.isValid()) {
1438
1439 if (!w.isEmpty()) {
1440 estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
1441 } else if (!h.isEmpty()) {
1442 estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
1443 }
1444
1445 width = estimatedSize.width();
1446 height = estimatedSize.height();
1447 }
1448 }
1449 }
1450
1451 QSizeF svgFragmentSize(QSizeF(width, height));
1452
1453 if (fragmentSize) {
1454 *fragmentSize = svgFragmentSize;
1455 }
1456
1457 gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
1458
1459 if (!isRootSvg) {
1460 // x and y attribute has no meaning for outermost svg elements
1461 const qreal x = parseUnit(e.attribute("x", "0"));
1462 const qreal y = parseUnit(e.attribute("y", "0"));
1463
1464 QTransform move = QTransform::fromTranslate(x, y);
1465 gc->matrix = move * gc->matrix;
1466 }
1467
1475 gc->pixelsPerInch = 96.0;
1476
1478
1480
1481 // First find the metadata
1482 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1483 QDomElement b = n.toElement();
1484 if (b.isNull())
1485 continue;
1486
1487 if (b.tagName() == "title") {
1488 m_documentTitle = b.text().trimmed();
1489 }
1490 else if (b.tagName() == "desc") {
1491 m_documentDescription = b.text().trimmed();
1492 }
1493 else if (b.tagName() == "metadata") {
1494 // TODO: parse the metadata
1495 }
1496 }
1497
1498
1499 // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
1500 // and as mother makes them -- if mother is inkscape.
1501 if (gc->currentBoundingBox.normalized().isValid()) {
1503 }
1504
1506
1507 return shapes;
1508}
1509
1510void SvgParser::applyViewBoxTransform(const QDomElement &element)
1511{
1513
1514 QRectF viewRect = gc->currentBoundingBox;
1515 QTransform viewTransform;
1516
1518 &viewRect, &viewTransform)) {
1519
1520 gc->matrix = viewTransform * gc->matrix;
1521 gc->currentBoundingBox = viewRect;
1522 }
1523}
1524
1526{
1528
1529 Q_FOREACH (const KoID &id, m_warnings) {
1530 warnings << id.name();
1531 }
1532
1533 return warnings;
1534}
1535
1540
1542{
1543 return m_documentTitle;
1544}
1545
1547{
1548 return m_documentDescription;
1549}
1550
1555
1556inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
1557{
1558 const QTransform shapeToOriginalUserCoordinates =
1559 shape->absoluteTransformation().inverted() *
1560 coordinateSystemOnLoading;
1561
1562 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
1563 return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
1564}
1565
1566KoShape* SvgParser::parseGroup(const QDomElement &b, const QDomElement &overrideChildrenFrom, bool createContext)
1567{
1568 if (createContext) {
1570 }
1571
1572 KoShapeGroup *group = new KoShapeGroup();
1573 group->setZIndex(m_context.nextZIndex());
1574
1575 // groups should also have their own coordinate system!
1577 const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
1578
1580
1581 QList<KoShape*> childShapes;
1582
1583 if (!overrideChildrenFrom.isNull()) {
1584 // we upload styles from both: <use> and <defs>
1585 uploadStyleToContext(overrideChildrenFrom);
1586 if (overrideChildrenFrom.tagName() == "symbol") {
1587 childShapes = {parseGroup(overrideChildrenFrom)};
1588 } else {
1589 childShapes = parseSingleElement(overrideChildrenFrom, 0);
1590 }
1591 } else {
1592 childShapes = parseContainer(b);
1593 }
1594
1595 // handle id
1596 applyId(b.attribute("id"), group);
1597
1598 if (b.hasAttribute(KoSvgTextShape_TEXTCONTOURGROUP)) {
1599 Q_FOREACH(KoShape *shape, childShapes) {
1600 if (shape->shapeId() == KoSvgTextShape_SHAPEID) {
1601 shape->setTransformation(group->transformation());
1602
1603 if (createContext) {
1605 }
1606 return shape;
1607 }
1608 }
1609 }
1610 addToGroup(childShapes, group);
1611
1612 applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
1613
1614 parseMetadataApplyToShape(b, group);
1615
1616 if (createContext) {
1618 }
1619
1620 return group;
1621}
1622
1623QDomText SvgParser::getTheOnlyTextChild(const QDomElement &e)
1624{
1625 QDomNode firstChild = e.firstChild();
1626 return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
1627 firstChild.toText() : QDomText();
1628}
1629
1631{
1632 for (auto defs = m_defsShapes.begin(); defs != m_defsShapes.end(); defs++) {
1633 KoShape *dShape = *defs;
1634 if (!dShape) continue;
1635 if (dShape->hasCommonParent(shape)) return true;
1636 }
1637 return false;
1638}
1639
1640KoShape* SvgParser::getTextPath(const QDomElement &e, bool hideShapesFromDefs) {
1641 if (e.hasAttribute("path")) {
1642 QDomElement p = e.ownerDocument().createElement("path");
1643 p.setAttribute("d", e.attribute("path"));
1644 KoShape *s = createPath(p);
1645 if (hideShapesFromDefs) {
1646 s->setTransparency(1.0);
1647 }
1648 return s;
1649 } else {
1650 QString pathId;
1651 if (e.hasAttribute("href")) {
1652 pathId = e.attribute("href").remove(0, 1);
1653 } else if (e.hasAttribute("xlink:href")) {
1654 pathId = e.attribute("xlink:href").remove(0, 1);
1655 }
1656 if (!pathId.isNull()) {
1657 KoShape *s = m_context.shapeById(pathId);
1658 if (s) {
1659 KoShape *cloned = s->cloneShape();
1660 const QTransform absTf = s->absoluteTransformation();
1661 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
1662 if(cloned && shapeInDefs(s) && hideShapesFromDefs) {
1663 cloned->setTransparency(1.0);
1664 }
1665 return cloned;
1666 }
1667 }
1668 }
1669 return nullptr;
1670}
1671
1672void SvgParser::parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader, bool hideShapesFromDefs) {
1673 QDomText t = getTheOnlyTextChild(e);
1674 if (!t.isNull()) {
1675 textLoader.loadSvgText(t, m_context);
1676 } else {
1677 textLoader.enterNodeSubtree();
1678 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1679 QDomElement b = n.toElement();
1680 if (b.tagName() == "title" || b.tagName() == "desc") continue;
1681 textLoader.nextNode();
1682 if (b.isNull()) {
1683 textLoader.loadSvgText(n.toText(), m_context);
1684 KoShape *styleDummy = new KoPathShape();
1685 applyCurrentBasicStyle(styleDummy);
1686 textLoader.setStyleInfo(styleDummy);
1687 } else {
1690 textLoader.loadSvg(b, m_context);
1691 if (b.hasChildNodes()) {
1692 parseTextChildren(b, textLoader, hideShapesFromDefs);
1693 }
1694 textLoader.setTextPathOnCurrentNode(getTextPath(b, hideShapesFromDefs));
1696 }
1697 }
1698 textLoader.leaveNodeSubtree();
1699 }
1700 KoShape *styleDummy = new KoPathShape();
1701 applyCurrentBasicStyle(styleDummy);
1702 textLoader.setStyleInfo(styleDummy);
1703}
1704
1705KoShape *SvgParser::parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape)
1706{
1707 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan" || e.tagName() == "textPath", 0);
1709 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
1710
1711 KoSvgTextShape *rootTextShape = 0;
1712
1713 bool hideShapesFromDefs = true;
1714 if (mergeIntoShape) {
1715 rootTextShape = mergeIntoShape;
1716 hideShapesFromDefs = false;
1717 } else {
1718 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
1719 rootTextShape = dynamic_cast<KoSvgTextShape*>(factory->createDefaultShape(m_documentResourceManager));
1720 }
1721 KoSvgTextLoader textLoader(rootTextShape);
1722
1723 if (rootTextShape) {
1724 m_isInsideTextSubtree = true;
1725 }
1726
1729
1730 if (rootTextShape) {
1731 if (!m_context.currentGC()->shapeInsideValue.isEmpty()) {
1733 rootTextShape->setShapesInside(shapesInside);
1734 }
1735
1736 if (!m_context.currentGC()->shapeSubtractValue.isEmpty()) {
1737 QList<KoShape*> shapesSubtract = createListOfShapesFromCSS(e, m_context.currentGC()->shapeSubtractValue, m_context, hideShapesFromDefs);
1738 rootTextShape->setShapesSubtract(shapesSubtract);
1739 }
1740 }
1741
1742 if (e.hasAttribute("krita:textVersion")) {
1743 m_context.currentGC()->textProperties.setProperty(KoSvgTextProperties::KraTextVersionId, e.attribute("krita:textVersion", "1").toInt());
1744
1746 debugFlake << "WARNING: \"krita:textVersion\" attribute appeared in non-root text shape";
1747 }
1748 }
1749
1750 parseMetadataApplyToShape(e, rootTextShape);
1751
1752 if (!mergeIntoShape) {
1753 rootTextShape->setZIndex(m_context.nextZIndex());
1754 }
1755
1758
1759 static const KoID warning("warn_text_version_1",
1760 i18nc("warning while loading SVG text",
1761 "The document has vector text created "
1762 "in Krita 4.x. When you save the document, "
1763 "the text object will be converted into "
1764 "Krita 5 format that will no longer be "
1765 "compatible with Krita 4.x"));
1766
1767 if (!m_warnings.contains(warning)) {
1768 m_warnings << warning;
1769 }
1770 }
1771
1773
1774 // 1) apply transformation only in case we are not overriding the shape!
1775 // 2) the transformation should be applied *before* the shape is added to the group!
1776 if (!mergeIntoShape) {
1777 // groups should also have their own coordinate system!
1779 const QPointF extraOffset = extraShapeOffset(rootTextShape, m_context.currentGC()->matrix);
1780
1781 // handle id
1782 applyId(e.attribute("id"), rootTextShape);
1783 applyCurrentStyle(rootTextShape, extraOffset); // apply style to this group after size is set
1784 } else {
1785 m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation();
1786 applyCurrentBasicStyle(rootTextShape);
1787 }
1788
1789 QDomText onlyTextChild = getTheOnlyTextChild(e);
1790 if (!onlyTextChild.isNull()) {
1791 textLoader.loadSvgText(onlyTextChild, m_context);
1792
1793 } else {
1794 parseTextChildren(e, textLoader, hideShapesFromDefs);
1795 }
1796
1798
1799 m_isInsideTextSubtree = false;
1800
1801 //rootTextShape->debugParsing();
1802
1803
1804 return rootTextShape;
1805}
1806
1808{
1810
1811 // are we parsing a switch container
1812 bool isSwitch = e.tagName() == "switch";
1813
1814 DeferredUseStore deferredUseStore(this);
1815
1816 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1817 QDomElement b = n.toElement();
1818 if (b.isNull()) {
1819 continue;
1820 }
1821
1822 if (isSwitch) {
1823 // if we are parsing a switch check the requiredFeatures, requiredExtensions
1824 // and systemLanguage attributes
1825 // TODO: evaluate feature list
1826 if (b.hasAttribute("requiredFeatures")) {
1827 continue;
1828 }
1829 if (b.hasAttribute("requiredExtensions")) {
1830 // we do not support any extensions
1831 continue;
1832 }
1833 if (b.hasAttribute("systemLanguage")) {
1834 // not implemented yet
1835 }
1836 }
1837
1838 QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
1839 shapes.append(currentShapes);
1840
1841 // if we are parsing a switch, stop after the first supported element
1842 if (isSwitch && !currentShapes.isEmpty())
1843 break;
1844 }
1845 return shapes;
1846}
1847
1848void SvgParser::parseDefsElement(const QDomElement &e)
1849{
1850 KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
1852}
1853
1855{
1857
1858 // save definition for later instantiation with 'use'
1860 if (deferredUseStore) {
1861 deferredUseStore->checkPendingUse(b, shapes);
1862 }
1863
1864 if (b.tagName() == "svg") {
1865 shapes += parseSvg(b);
1866 } else if (b.tagName() == "g" || b.tagName() == "a") {
1867 // treat svg link <a> as group so we don't miss its child elements
1868 shapes += parseGroup(b);
1869 } else if (b.tagName() == "symbol") {
1870 parseSymbol(b);
1871 } else if (b.tagName() == "switch") {
1873 shapes += parseContainer(b);
1875 } else if (b.tagName() == "defs") {
1876 if (b.childNodes().count() > 0) {
1882 KoShape *defsShape = parseGroup(b);
1883 defsShape->setVisible(false);
1884 m_defsShapes << defsShape; // TODO: where to delete the shape!?
1885
1886 }
1887 } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
1888 } else if (b.tagName() == "pattern") {
1889 } else if (b.tagName() == "filter") {
1890 // not supported!
1891 } else if (b.tagName() == "clipPath") {
1892 parseClipPath(b);
1893 } else if (b.tagName() == "mask") {
1894 parseClipMask(b);
1895 } else if (b.tagName() == "marker") {
1896 parseMarker(b);
1897 } else if (b.tagName() == "style") {
1899 } else if (b.tagName() == "text" || b.tagName() == "tspan" || b.tagName() == "textPath") {
1901 } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline"
1902 || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") {
1903 KoShape *shape = createObjectDirect(b);
1904
1905 if (shape) {
1906 if (!shape->outlineRect().isNull() || !shape->boundingRect().isNull()) {
1907 shapes.append(shape);
1908 } else {
1909 debugFlake << "WARNING: shape is totally empty!" << shape->shapeId() << ppVar(shape->outlineRect());
1910 debugFlake << " " << shape->shapeId() << ppVar(shape->outline());
1911 {
1912 QString string;
1913 QTextStream stream(&string);
1915 stream << b;
1916 debugFlake << " " << string;
1917 }
1918 delete shape;
1919 }
1920 }
1921 } else if (b.tagName() == "use") {
1922 KoShape* s = parseUse(b, deferredUseStore);
1923 if (s) {
1924 shapes += s;
1925 }
1926 } else if (b.tagName() == "color-profile") {
1928 } else {
1929 // this is an unknown element, so try to load it anyway
1930 // there might be a shape that handles that element
1931 KoShape *shape = createObject(b);
1932 if (shape) {
1933 shapes.append(shape);
1934 }
1935 }
1936
1937 return shapes;
1938}
1939
1940// Creating functions
1941// ---------------------------------------------------------------------------------------
1942
1943KoShape * SvgParser::createPath(const QDomElement &element)
1944{
1945 KoShape *obj = 0;
1946 if (element.tagName() == "line") {
1947 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1948 if (path) {
1949 double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
1950 double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
1951 double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
1952 double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
1953 path->clear();
1954 path->moveTo(QPointF(x1, y1));
1955 path->lineTo(QPointF(x2, y2));
1956 path->normalize();
1957 obj = path;
1958 }
1959 } else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
1960 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1961 if (path) {
1962 path->clear();
1963
1964 bool bFirst = true;
1965 QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
1966 for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
1967 QPointF point;
1969 ++it;
1970 if (it == pointList.end())
1971 break;
1973 if (bFirst) {
1974 path->moveTo(point);
1975 bFirst = false;
1976 } else
1977 path->lineTo(point);
1978 }
1979 if (element.tagName() == "polygon")
1980 path->close();
1981
1982 path->setPosition(path->normalize());
1983
1984 obj = path;
1985 }
1986 } else if (element.tagName() == "path") {
1987 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1988 if (path) {
1989 path->clear();
1990
1991 KoPathShapeLoader loader(path);
1992 loader.parseSvg(element.attribute("d"), true);
1993 path->setPosition(path->normalize());
1994
1995 QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
1996 SvgUtil::fromUserSpace(path->position().y()));
1997 QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
1998 SvgUtil::fromUserSpace(path->size().height()));
1999
2000 path->setSize(newSize);
2001 path->setPosition(newPosition);
2002
2003 if (element.hasAttribute("sodipodi:nodetypes")) {
2004 path->loadNodeTypes(element.attribute("sodipodi:nodetypes"));
2005 }
2006 obj = path;
2007 }
2008 }
2009
2010 return obj;
2011}
2012
2014{
2017
2019 if (obj) {
2021 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2022
2023 applyCurrentStyle(obj, extraOffset);
2024
2025 // handle id
2026 applyId(b.attribute("id"), obj);
2029 }
2030
2032
2033 if (obj) {
2035 }
2036 return obj;
2037}
2038
2039KoShape * SvgParser::createObject(const QDomElement &b, const SvgStyles &style)
2040{
2042
2044 if (obj) {
2046 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2047
2048 SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
2049 m_context.styleParser().parseFont(objStyle);
2050 applyStyle(obj, objStyle, extraOffset);
2051
2052 // handle id
2053 applyId(b.attribute("id"), obj);
2056 }
2057
2059
2060 if (obj) {
2062 }
2063
2064 return obj;
2065}
2066
2067KoShape * SvgParser::createShapeFromElement(const QDomElement &element, SvgLoadingContext &context)
2068{
2069 KoShape *object = 0;
2070
2071
2072 const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
2074
2075 foreach (KoShapeFactoryBase *f, factories) {
2076 KoShape *shape = f->createDefaultShape(m_documentResourceManager);
2077 if (!shape)
2078 continue;
2079
2080 SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
2081 if (!svgShape) {
2082 delete shape;
2083 continue;
2084 }
2085
2086 // reset transformation that might come from the default shape
2087 shape->setTransformation(QTransform());
2088
2089 // reset border
2090 KoShapeStrokeModelSP oldStroke = shape->stroke();
2092
2093 // reset fill
2095
2096 if (!svgShape->loadSvg(element, context)) {
2097 delete shape;
2098 continue;
2099 }
2100
2101 object = shape;
2102 break;
2103 }
2104
2105 if (!object) {
2106 object = createPath(element);
2107 }
2108
2109 return object;
2110}
2111
2112KoShape *SvgParser::createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2113{
2114 if (value.isEmpty()) {
2115 return 0;
2116 }
2117 unsigned int start = value.indexOf('(') + 1;
2118 unsigned int end = value.indexOf(')', start);
2119
2120 QString val = value.mid(start, end - start);
2121 QString fillRule;
2122 if (val.startsWith("evenodd,")) {
2123 start += QString("evenodd,").size();
2124 fillRule = "evenodd";
2125 } else if (val.startsWith("nonzero,")) {
2126 start += QString("nonzero,").size();
2127 fillRule = "nonzero";
2128 }
2129 val = value.mid(start, end - start);
2130
2131 QDomElement el;
2132 if (value.startsWith("url(")) {
2133 start = value.indexOf('#') + 1;
2134 KoShape *s = m_context.shapeById(value.mid(start, end - start));
2135 if (s) {
2136 const QTransform absTf = s->absoluteTransformation();
2137 KoShape *cloned = s->cloneShape();
2138 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
2139 // When we have a parent, the shape is inside the defs, but when not,
2140 // it's in the group we're in the currently parsing.
2141
2142 if (cloned && shapeInDefs(s) && hideShapesFromDefs) {
2143 cloned->setTransparency(1.0);
2144 }
2145 return cloned;
2146 }
2147 } else if (value.startsWith("circle(")) {
2148 el = e.ownerDocument().createElement("circle");
2149 QStringList params = val.split(" ");
2150 el.setAttribute("r", SvgUtil::parseUnitXY(context.currentGC(), context.resolvedProperties(), params.first()));
2151 if (params.contains("at")) {
2152 // 1 == "at"
2153 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(2)));
2154 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(3)));
2155 }
2156 } else if (value.startsWith("ellipse(")) {
2157 el = e.ownerDocument().createElement("ellipse");
2158 QStringList params = val.split(" ");
2159 el.setAttribute("rx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(0)));
2160 el.setAttribute("ry", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(1)));
2161 if (params.contains("at")) {
2162 // 2 == "at"
2163 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(3)));
2164 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(4)));
2165 }
2166 } else if (value.startsWith("polygon(")) {
2167 el = e.ownerDocument().createElement("polygon");
2168 QStringList points;
2169 Q_FOREACH(QString point, SvgUtil::simplifyList(val)) {
2170 bool xVal = points.size() % 2;
2171 if (xVal) {
2172 points.append(QString::number(SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), point)));
2173 } else {
2174 points.append(QString::number(SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), point)));
2175 }
2176 }
2177 el.setAttribute("points", points.join(" "));
2178 } else if (value.startsWith("path(")) {
2179 el = e.ownerDocument().createElement("path");
2180 // SVG path data is inside a string.
2181 start += 1;
2182 end -= 1;
2183 el.setAttribute("d", value.mid(start, end - start));
2184 }
2185
2186 el.setAttribute("fill-rule", fillRule);
2187 KoShape *shape = createShapeFromElement(el, context);
2188 if (shape) shape->setTransparency(1.0);
2189 return shape;
2190}
2191
2192QList<KoShape *> SvgParser::createListOfShapesFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2193{
2194 QList<KoShape*> shapeList;
2195 if (value == "auto" || value == "none") {
2196 return shapeList;
2197 }
2198 QStringList params = value.split(")");
2199 Q_FOREACH(const QString param, params) {
2200 KoShape *s = createShapeFromCSS(e, param.trimmed()+")", context, hideShapesFromDefs);
2201 if (s) {
2202 shapeList.append(s);
2203 }
2204 }
2205 return shapeList;
2206}
2207
2208KoShape *SvgParser::createShape(const QString &shapeID)
2209{
2210 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
2211 if (!factory) {
2212 debugFlake << "Could not find factory for shape id" << shapeID;
2213 return 0;
2214 }
2215
2217 if (!shape) {
2218 debugFlake << "Could not create Default shape for shape id" << shapeID;
2219 return 0;
2220 }
2221 if (shape->shapeId().isEmpty()) {
2222 shape->setShapeId(factory->id());
2223 }
2224
2225 // reset transformation that might come from the default shape
2226 shape->setTransformation(QTransform());
2227
2228 // reset border
2229 // ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
2231
2232 // reset fill
2234
2235 return shape;
2236}
2237
2238void SvgParser::applyId(const QString &id, KoShape *shape)
2239{
2240 if (id.isEmpty())
2241 return;
2242
2243 KoShape *existingShape = m_context.shapeById(id);
2244 if (existingShape) {
2245 debugFlake << "SVG contains nodes with duplicated id:" << id;
2246 // Generate a random name and just don't register the shape.
2247 // We don't use the name as a unique identifier so we don't need to
2248 // worry about the extremely rare case of name collision.
2249 const QString suffix = QString::number(QRandomGenerator::system()->bounded(0x10000000, 0x7FFFFFFF), 16);
2250 const QString newName = id + '_' + suffix;
2251 shape->setName(newName);
2252 } else {
2253 shape->setName(id);
2254 m_context.registerShape(id, shape);
2255 }
2256}
#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:664
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:735
void setName(const QString &name)
Definition KoShape.cpp:955
virtual QRectF outlineRect() const
Definition KoShape.cpp:561
bool hasCommonParent(const KoShape *shape) const
Definition KoShape.cpp:505
void setInheritBackground(bool value)
setInheritBackground marks a shape as inheriting the background from the parent shape....
Definition KoShape.cpp:768
void setZIndex(qint16 zIndex)
Definition KoShape.cpp:782
QString shapeId() const
Definition KoShape.cpp:875
virtual QPainterPath outline() const
Definition KoShape.cpp:554
void setClipMask(KoClipMask *clipMask)
Sets a new clip mask, removing the old one. The mask is owned by the shape.
Definition KoShape.cpp:933
void setTransparency(qreal transparency)
Definition KoShape.cpp:637
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:699
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:885
void applyAbsoluteTransformation(const QTransform &matrix)
Definition KoShape.cpp:348
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:300
virtual void setStroke(KoShapeStrokeModelSP stroke)
Definition KoShape.cpp:899
void setAdditionalAttribute(const QString &name, const QString &value)
Definition KoShape.cpp:1072
QTransform absoluteTransformation() const
Definition KoShape.cpp:330
void setTransformation(const QTransform &matrix)
Definition KoShape.cpp:369
virtual void setBackground(QSharedPointer< KoShapeBackground > background)
Definition KoShape.cpp:746
void setClipPath(KoClipPath *clipPath)
Sets a new clip path, removing the old one.
Definition KoShape.cpp:921
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:173
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:378
void setInheritStroke(bool value)
setInheritStroke marks a shape as inheriting the stroke from the parent shape. NOTE: The currently se...
Definition KoShape.cpp:908
@ Stroke
Definition KoShape.h:117
@ Markers
Definition KoShape.h:118
void setVisible(bool on)
Definition KoShape.cpp:790
void setShapeId(const QString &id)
Definition KoShape.cpp:880
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.
KoSvgTextProperties resolvedProperties(bool onlyFontAndLineHeight=false) const
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.
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