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 // NOTE: this is an internal API in Qt. SVG specs specify that we
1089 // shouldn't interpolate in pre-multiplied space.
1090 resultGradient->setInterpolationMode(QGradient::ComponentInterpolation);
1091
1092 return resultGradient;
1093}
1094
1096 const KoShape *shape,
1097 const SvgGraphicsContext *gc) {
1098
1099 SvgMeshGradient *resultGradient = nullptr;
1100
1101 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1102
1103 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1104
1105 const QRectF boundingRect = shape->outline().boundingRect();
1106 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1107 boundingRect.x(), boundingRect.y());
1108
1109 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1110 // So, transform is called multiple times on the mesh and that's not nice
1111 resultGradient->setTransform(gradient->transform() * relativeToShape);
1112 } else {
1113 // NOTE: Krita's shapes use their own coordinate system. Where origin is at the top left
1114 // of the SHAPE. All the mesh patches will be rendered in the global 'user' coordinate system
1115 // where the origin is at the top left of the LAYER/DOCUMENT.
1116
1117 // Get the user coordinates of the shape
1118 const QTransform shapeglobal = shape->absoluteTransformation() * gc->matrix.inverted();
1119
1120 // Get the translation offset to shift the origin from "Shape" to "User"
1121 const QTransform translationOffset = QTransform::fromTranslate(-shapeglobal.dx(), -shapeglobal.dy());
1122
1123 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1124
1125 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1126 // So, transform is called multiple times on the mesh and that's not nice
1127 resultGradient->setTransform(gradient->transform() * translationOffset);
1128 }
1129
1130 return resultGradient;
1131}
1132
1134{
1136 if (! gc)
1137 return;
1138
1141 } else if (gc->fillType == SvgGraphicsContext::Solid) {
1143 } else if (gc->fillType == SvgGraphicsContext::Complex) {
1144 // try to find referenced gradient
1145 SvgGradientHelper *gradient = findGradient(gc->fillId);
1146 if (gradient) {
1147 QTransform transform;
1148
1149 if (gradient->isMeshGradient()) {
1151
1152 QScopedPointer<SvgMeshGradient> result(prepareMeshGradientForShape(gradient, shape, gc));
1153
1154 bg = toQShared(new KoMeshGradientBackground(result.data(), transform));
1155 shape->setBackground(bg);
1156 } else if (gradient->gradient()) {
1157 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1158 if (result) {
1160 bg = toQShared(new KoGradientBackground(result));
1161 bg->setTransform(transform);
1162 shape->setBackground(bg);
1163 }
1164 }
1165 } else {
1167 findPattern(gc->fillId, shape);
1168
1169 if (pattern) {
1170 shape->setBackground(pattern);
1171 } else {
1172 // no referenced fill found, use fallback color
1174 }
1175 }
1176 } else if (gc->fillType == SvgGraphicsContext::Inherit) {
1177 shape->setInheritBackground(true);
1178 }
1179
1180 KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
1181 if (path)
1182 path->setFillRule(gc->fillRule);
1183}
1184
1185void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
1186{
1187 const double lineWidth = srcStroke->lineWidth();
1188 QVector<qreal> dashes = srcStroke->lineDashes();
1189
1190 // apply line width to dashes and dash offset
1191 if (dashes.count() && lineWidth > 0.0) {
1192 const double dashOffset = srcStroke->dashOffset();
1193 QVector<qreal> dashes = srcStroke->lineDashes();
1194
1195 for (int i = 0; i < dashes.count(); ++i) {
1196 dashes[i] /= lineWidth;
1197 }
1198
1199 dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
1200 dstStroke->setDashOffset(dashOffset / lineWidth);
1201 } else {
1202 dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
1203 }
1204}
1205
1207{
1209 if (! gc)
1210 return;
1211
1213 KoShapeStrokeSP stroke(new KoShapeStroke());
1214 stroke->setLineWidth(0.0);
1215 const QColor color = Qt::transparent;
1216 stroke->setColor(color);
1217 shape->setStroke(stroke);
1218 } else if (gc->strokeType == SvgGraphicsContext::Solid) {
1219 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1220 applyDashes(gc->stroke, stroke);
1221 shape->setStroke(stroke);
1222 } else if (gc->strokeType == SvgGraphicsContext::Complex) {
1223 // try to find referenced gradient
1224 SvgGradientHelper *gradient = findGradient(gc->strokeId);
1225 if (gradient) {
1226 QTransform transform;
1227 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1228 if (result) {
1229 QBrush brush = *result;
1230 delete result;
1231 brush.setTransform(transform);
1232
1233 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1234 stroke->setLineBrush(brush);
1235 applyDashes(gc->stroke, stroke);
1236 shape->setStroke(stroke);
1237 }
1238 } else {
1239 // no referenced stroke found, use fallback color
1240 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1241 applyDashes(gc->stroke, stroke);
1242 shape->setStroke(stroke);
1243 }
1244 } else if (gc->strokeType == SvgGraphicsContext::Inherit) {
1245 shape->setInheritStroke(true);
1246 }
1247}
1248
1250{
1252 if (!gc)
1253 return;
1254
1255 if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
1257 }
1258
1259 if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
1261 }
1262
1263 if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
1265 }
1266
1268}
1269
1271{
1273 if (!gc)
1274 return;
1275
1276 if (!gc->paintOrder.isEmpty() && gc->paintOrder != "inherit") {
1277 QStringList paintOrder = gc->paintOrder.split(" ");
1279 Q_FOREACH(const QString p, paintOrder) {
1280 if (p == "fill") {
1281 order.append(KoShape::Fill);
1282 } else if (p == "stroke") {
1283 order.append(KoShape::Stroke);
1284 } else if (p == "markers") {
1285 order.append(KoShape::Markers);
1286 }
1287 }
1288 if (paintOrder.size() == 1 && order.isEmpty()) { // Normal
1290 }
1291 if (order.size() == 1) {
1292 if (order.first() == KoShape::Fill) {
1294 } else if (order.first() == KoShape::Stroke) {
1296 } else if (order.first() == KoShape::Markers) {
1298 }
1299 } else if (order.size() > 1) {
1300 shape->setPaintOrder(order.at(0), order.at(1));
1301 }
1302 }
1303}
1304
1305void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1306{
1308 if (! gc)
1309 return;
1310
1311 if (gc->clipPathId.isEmpty())
1312 return;
1313
1314 SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
1315 if (!clipPath || clipPath->isEmpty())
1316 return;
1317
1319
1320 Q_FOREACH (KoShape *item, clipPath->shapes()) {
1321 KoShape *clonedShape = item->cloneShape();
1322 KIS_ASSERT_RECOVER(clonedShape) { continue; }
1323
1324 shapes.append(clonedShape);
1325 }
1326
1327 if (!shapeToOriginalUserCoordinates.isNull()) {
1328 const QTransform t =
1329 QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
1330 shapeToOriginalUserCoordinates.y());
1331
1332 Q_FOREACH(KoShape *s, shapes) {
1334 }
1335 }
1336
1337 KoClipPath *clipPathObject = new KoClipPath(shapes,
1340 shape->setClipPath(clipPathObject);
1341}
1342
1343void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1344{
1346 if (!gc)
1347 return;
1348
1349 if (gc->clipMaskId.isEmpty())
1350 return;
1351
1352
1353 QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
1354 if (!originalClipMask || originalClipMask->isEmpty()) return;
1355
1356 KoClipMask *clipMask = originalClipMask->clone();
1357
1358 clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
1359
1360 shape->setClipMask(clipMask);
1361}
1362
1363KoShape* SvgParser::parseUse(const QDomElement &e, DeferredUseStore* deferredUseStore)
1364{
1365 QString href = e.attribute("xlink:href");
1366 if (href.isEmpty())
1367 return 0;
1368
1369 QString key = href.mid(1);
1370 const bool gotDef = m_context.hasDefinition(key);
1371 if (gotDef) {
1372 return resolveUse(e, key);
1373 } else if (deferredUseStore) {
1374 deferredUseStore->add(&e, key);
1375 return 0;
1376 }
1377 debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
1378 << key;
1379 return 0;
1380}
1381
1382KoShape* SvgParser::resolveUse(const QDomElement &e, const QString& key)
1383{
1384 KoShape *result = 0;
1385
1387
1388 // TODO: parse 'width' and 'height' as well
1389 gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
1390
1391 const QDomElement &referencedElement = m_context.definition(key);
1392 result = parseGroup(e, referencedElement, false);
1393
1395 return result;
1396}
1397
1399{
1400 m_shapes += shapes;
1401
1402 if (!group || shapes.isEmpty())
1403 return;
1404
1405 // not normalized
1406 KoShapeGroupCommand cmd(group, shapes, false);
1407 cmd.redo();
1408}
1409
1410QList<KoShape*> SvgParser::parseSvg(const QDomElement &e, QSizeF *fragmentSize)
1411{
1412 // check if we are the root svg element
1413 const bool isRootSvg = m_context.isRootContext();
1414
1415 // parse 'transform' field if preset
1417
1418 applyStyle(0, e, QPointF());
1419
1420 const QString w = e.attribute("width");
1421 const QString h = e.attribute("height");
1422
1423 qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
1424 qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
1425
1426 if (w.isEmpty() || h.isEmpty()) {
1427 QRectF viewRect;
1428 QTransform viewTransform_unused;
1429 QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
1430
1431 if (SvgUtil::parseViewBox(e, fakeBoundingRect,
1432 &viewRect, &viewTransform_unused)) {
1433
1434 QSizeF estimatedSize = viewRect.size();
1435
1436 if (estimatedSize.isValid()) {
1437
1438 if (!w.isEmpty()) {
1439 estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
1440 } else if (!h.isEmpty()) {
1441 estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
1442 }
1443
1444 width = estimatedSize.width();
1445 height = estimatedSize.height();
1446 }
1447 }
1448 }
1449
1450 QSizeF svgFragmentSize(QSizeF(width, height));
1451
1452 if (fragmentSize) {
1453 *fragmentSize = svgFragmentSize;
1454 }
1455
1456 gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
1457
1458 if (!isRootSvg) {
1459 // x and y attribute has no meaning for outermost svg elements
1460 const qreal x = parseUnit(e.attribute("x", "0"));
1461 const qreal y = parseUnit(e.attribute("y", "0"));
1462
1463 QTransform move = QTransform::fromTranslate(x, y);
1464 gc->matrix = move * gc->matrix;
1465 }
1466
1474 gc->pixelsPerInch = 96.0;
1475
1477
1479
1480 // First find the metadata
1481 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1482 QDomElement b = n.toElement();
1483 if (b.isNull())
1484 continue;
1485
1486 if (b.tagName() == "title") {
1487 m_documentTitle = b.text().trimmed();
1488 }
1489 else if (b.tagName() == "desc") {
1490 m_documentDescription = b.text().trimmed();
1491 }
1492 else if (b.tagName() == "metadata") {
1493 // TODO: parse the metadata
1494 }
1495 }
1496
1497
1498 // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
1499 // and as mother makes them -- if mother is inkscape.
1500 if (gc->currentBoundingBox.normalized().isValid()) {
1502 }
1503
1505
1506 return shapes;
1507}
1508
1509void SvgParser::applyViewBoxTransform(const QDomElement &element)
1510{
1512
1513 QRectF viewRect = gc->currentBoundingBox;
1514 QTransform viewTransform;
1515
1517 &viewRect, &viewTransform)) {
1518
1519 gc->matrix = viewTransform * gc->matrix;
1520 gc->currentBoundingBox = viewRect;
1521 }
1522}
1523
1525{
1527
1528 Q_FOREACH (const KoID &id, m_warnings) {
1529 warnings << id.name();
1530 }
1531
1532 return warnings;
1533}
1534
1539
1541{
1542 return m_documentTitle;
1543}
1544
1546{
1547 return m_documentDescription;
1548}
1549
1554
1555inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
1556{
1557 const QTransform shapeToOriginalUserCoordinates =
1558 shape->absoluteTransformation().inverted() *
1559 coordinateSystemOnLoading;
1560
1561 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
1562 return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
1563}
1564
1565KoShape* SvgParser::parseGroup(const QDomElement &b, const QDomElement &overrideChildrenFrom, bool createContext)
1566{
1567 if (createContext) {
1569 }
1570
1571 KoShapeGroup *group = new KoShapeGroup();
1572 group->setZIndex(m_context.nextZIndex());
1573
1574 // groups should also have their own coordinate system!
1576 const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
1577
1579
1580 QList<KoShape*> childShapes;
1581
1582 if (!overrideChildrenFrom.isNull()) {
1583 // we upload styles from both: <use> and <defs>
1584 uploadStyleToContext(overrideChildrenFrom);
1585 if (overrideChildrenFrom.tagName() == "symbol") {
1586 childShapes = {parseGroup(overrideChildrenFrom)};
1587 } else {
1588 childShapes = parseSingleElement(overrideChildrenFrom, 0);
1589 }
1590 } else {
1591 childShapes = parseContainer(b);
1592 }
1593
1594 // handle id
1595 applyId(b.attribute("id"), group);
1596
1597 if (b.hasAttribute(KoSvgTextShape_TEXTCONTOURGROUP)) {
1598 Q_FOREACH(KoShape *shape, childShapes) {
1599 if (shape->shapeId() == KoSvgTextShape_SHAPEID) {
1600 shape->setTransformation(group->transformation());
1601
1602 if (createContext) {
1604 }
1605 return shape;
1606 }
1607 }
1608 }
1609 addToGroup(childShapes, group);
1610
1611 applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
1612
1613 parseMetadataApplyToShape(b, group);
1614
1615 if (createContext) {
1617 }
1618
1619 return group;
1620}
1621
1622QDomText SvgParser::getTheOnlyTextChild(const QDomElement &e)
1623{
1624 QDomNode firstChild = e.firstChild();
1625 return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
1626 firstChild.toText() : QDomText();
1627}
1628
1630{
1631 for (auto defs = m_defsShapes.begin(); defs != m_defsShapes.end(); defs++) {
1632 KoShape *dShape = *defs;
1633 if (!dShape) continue;
1634 if (dShape->hasCommonParent(shape)) return true;
1635 }
1636 return false;
1637}
1638
1639KoShape* SvgParser::getTextPath(const QDomElement &e, bool hideShapesFromDefs) {
1640 if (e.hasAttribute("path")) {
1641 QDomElement p = e.ownerDocument().createElement("path");
1642 p.setAttribute("d", e.attribute("path"));
1643 KoShape *s = createPath(p);
1644 if (hideShapesFromDefs) {
1645 s->setTransparency(1.0);
1646 }
1647 return s;
1648 } else {
1649 QString pathId;
1650 if (e.hasAttribute("href")) {
1651 pathId = e.attribute("href").remove(0, 1);
1652 } else if (e.hasAttribute("xlink:href")) {
1653 pathId = e.attribute("xlink:href").remove(0, 1);
1654 }
1655 if (!pathId.isNull()) {
1656 KoShape *s = m_context.shapeById(pathId);
1657 if (s) {
1658 KoShape *cloned = s->cloneShape();
1659 const QTransform absTf = s->absoluteTransformation();
1660 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
1661 if(cloned && shapeInDefs(s) && hideShapesFromDefs) {
1662 cloned->setTransparency(1.0);
1663 }
1664 return cloned;
1665 }
1666 }
1667 }
1668 return nullptr;
1669}
1670
1671void SvgParser::parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader, bool hideShapesFromDefs) {
1672 QDomText t = getTheOnlyTextChild(e);
1673 if (!t.isNull()) {
1674 textLoader.loadSvgText(t, m_context);
1675 } else {
1676 textLoader.enterNodeSubtree();
1677 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1678 QDomElement b = n.toElement();
1679 if (b.tagName() == "title" || b.tagName() == "desc") continue;
1680 textLoader.nextNode();
1681 if (b.isNull()) {
1682 textLoader.loadSvgText(n.toText(), m_context);
1683 KoShape *styleDummy = new KoPathShape();
1684 applyCurrentBasicStyle(styleDummy);
1685 textLoader.setStyleInfo(styleDummy);
1686 } else {
1689 textLoader.loadSvg(b, m_context);
1690 if (b.hasChildNodes()) {
1691 parseTextChildren(b, textLoader, hideShapesFromDefs);
1692 }
1693 textLoader.setTextPathOnCurrentNode(getTextPath(b, hideShapesFromDefs));
1695 }
1696 }
1697 textLoader.leaveNodeSubtree();
1698 }
1699 KoShape *styleDummy = new KoPathShape();
1700 applyCurrentBasicStyle(styleDummy);
1701 textLoader.setStyleInfo(styleDummy);
1702}
1703
1704KoShape *SvgParser::parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape)
1705{
1706 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan" || e.tagName() == "textPath", 0);
1708 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
1709
1710 KoSvgTextShape *rootTextShape = 0;
1711
1712 bool hideShapesFromDefs = true;
1713 if (mergeIntoShape) {
1714 rootTextShape = mergeIntoShape;
1715 hideShapesFromDefs = false;
1716 } else {
1717 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
1718 rootTextShape = dynamic_cast<KoSvgTextShape*>(factory->createDefaultShape(m_documentResourceManager));
1719 }
1720 KoSvgTextLoader textLoader(rootTextShape);
1721
1722 if (rootTextShape) {
1723 m_isInsideTextSubtree = true;
1724 }
1725
1728
1729 if (rootTextShape) {
1730 if (!m_context.currentGC()->shapeInsideValue.isEmpty()) {
1732 rootTextShape->setShapesInside(shapesInside);
1733 }
1734
1735 if (!m_context.currentGC()->shapeSubtractValue.isEmpty()) {
1736 QList<KoShape*> shapesSubtract = createListOfShapesFromCSS(e, m_context.currentGC()->shapeSubtractValue, m_context, hideShapesFromDefs);
1737 rootTextShape->setShapesSubtract(shapesSubtract);
1738 }
1739 }
1740
1741 if (e.hasAttribute("krita:textVersion")) {
1742 m_context.currentGC()->textProperties.setProperty(KoSvgTextProperties::KraTextVersionId, e.attribute("krita:textVersion", "1").toInt());
1743
1745 debugFlake << "WARNING: \"krita:textVersion\" attribute appeared in non-root text shape";
1746 }
1747 }
1748
1749 parseMetadataApplyToShape(e, rootTextShape);
1750
1751 if (!mergeIntoShape) {
1752 rootTextShape->setZIndex(m_context.nextZIndex());
1753 }
1754
1757
1758 static const KoID warning("warn_text_version_1",
1759 i18nc("warning while loading SVG text",
1760 "The document has vector text created "
1761 "in Krita 4.x. When you save the document, "
1762 "the text object will be converted into "
1763 "Krita 5 format that will no longer be "
1764 "compatible with Krita 4.x"));
1765
1766 if (!m_warnings.contains(warning)) {
1767 m_warnings << warning;
1768 }
1769 }
1770
1772
1773 // 1) apply transformation only in case we are not overriding the shape!
1774 // 2) the transformation should be applied *before* the shape is added to the group!
1775 if (!mergeIntoShape) {
1776 // groups should also have their own coordinate system!
1778 const QPointF extraOffset = extraShapeOffset(rootTextShape, m_context.currentGC()->matrix);
1779
1780 // handle id
1781 applyId(e.attribute("id"), rootTextShape);
1782 applyCurrentStyle(rootTextShape, extraOffset); // apply style to this group after size is set
1783 } else {
1784 m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation();
1785 applyCurrentBasicStyle(rootTextShape);
1786 }
1787
1788 QDomText onlyTextChild = getTheOnlyTextChild(e);
1789 if (!onlyTextChild.isNull()) {
1790 textLoader.loadSvgText(onlyTextChild, m_context);
1791
1792 } else {
1793 parseTextChildren(e, textLoader, hideShapesFromDefs);
1794 }
1795
1797
1798 m_isInsideTextSubtree = false;
1799
1800 //rootTextShape->debugParsing();
1801
1802
1803 return rootTextShape;
1804}
1805
1807{
1809
1810 // are we parsing a switch container
1811 bool isSwitch = e.tagName() == "switch";
1812
1813 DeferredUseStore deferredUseStore(this);
1814
1815 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1816 QDomElement b = n.toElement();
1817 if (b.isNull()) {
1818 continue;
1819 }
1820
1821 if (isSwitch) {
1822 // if we are parsing a switch check the requiredFeatures, requiredExtensions
1823 // and systemLanguage attributes
1824 // TODO: evaluate feature list
1825 if (b.hasAttribute("requiredFeatures")) {
1826 continue;
1827 }
1828 if (b.hasAttribute("requiredExtensions")) {
1829 // we do not support any extensions
1830 continue;
1831 }
1832 if (b.hasAttribute("systemLanguage")) {
1833 // not implemented yet
1834 }
1835 }
1836
1837 QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
1838 shapes.append(currentShapes);
1839
1840 // if we are parsing a switch, stop after the first supported element
1841 if (isSwitch && !currentShapes.isEmpty())
1842 break;
1843 }
1844 return shapes;
1845}
1846
1847void SvgParser::parseDefsElement(const QDomElement &e)
1848{
1849 KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
1851}
1852
1854{
1856
1857 // save definition for later instantiation with 'use'
1859 if (deferredUseStore) {
1860 deferredUseStore->checkPendingUse(b, shapes);
1861 }
1862
1863 if (b.tagName() == "svg") {
1864 shapes += parseSvg(b);
1865 } else if (b.tagName() == "g" || b.tagName() == "a") {
1866 // treat svg link <a> as group so we don't miss its child elements
1867 shapes += parseGroup(b);
1868 } else if (b.tagName() == "symbol") {
1869 parseSymbol(b);
1870 } else if (b.tagName() == "switch") {
1872 shapes += parseContainer(b);
1874 } else if (b.tagName() == "defs") {
1875 if (b.childNodes().count() > 0) {
1881 KoShape *defsShape = parseGroup(b);
1882 defsShape->setVisible(false);
1883 m_defsShapes << defsShape; // TODO: where to delete the shape!?
1884
1885 }
1886 } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
1887 } else if (b.tagName() == "pattern") {
1888 } else if (b.tagName() == "filter") {
1889 // not supported!
1890 } else if (b.tagName() == "clipPath") {
1891 parseClipPath(b);
1892 } else if (b.tagName() == "mask") {
1893 parseClipMask(b);
1894 } else if (b.tagName() == "marker") {
1895 parseMarker(b);
1896 } else if (b.tagName() == "style") {
1898 } else if (b.tagName() == "text" || b.tagName() == "tspan" || b.tagName() == "textPath") {
1900 } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline"
1901 || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") {
1902 KoShape *shape = createObjectDirect(b);
1903
1904 if (shape) {
1905 if (!shape->outlineRect().isNull() || !shape->boundingRect().isNull()) {
1906 shapes.append(shape);
1907 } else {
1908 debugFlake << "WARNING: shape is totally empty!" << shape->shapeId() << ppVar(shape->outlineRect());
1909 debugFlake << " " << shape->shapeId() << ppVar(shape->outline());
1910 {
1911 QString string;
1912 QTextStream stream(&string);
1914 stream << b;
1915 debugFlake << " " << string;
1916 }
1917 delete shape;
1918 }
1919 }
1920 } else if (b.tagName() == "use") {
1921 KoShape* s = parseUse(b, deferredUseStore);
1922 if (s) {
1923 shapes += s;
1924 }
1925 } else if (b.tagName() == "color-profile") {
1927 } else {
1928 // this is an unknown element, so try to load it anyway
1929 // there might be a shape that handles that element
1930 KoShape *shape = createObject(b);
1931 if (shape) {
1932 shapes.append(shape);
1933 }
1934 }
1935
1936 return shapes;
1937}
1938
1939// Creating functions
1940// ---------------------------------------------------------------------------------------
1941
1942KoShape * SvgParser::createPath(const QDomElement &element)
1943{
1944 KoShape *obj = 0;
1945 if (element.tagName() == "line") {
1946 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1947 if (path) {
1948 double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
1949 double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
1950 double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
1951 double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
1952 path->clear();
1953 path->moveTo(QPointF(x1, y1));
1954 path->lineTo(QPointF(x2, y2));
1955 path->normalize();
1956 obj = path;
1957 }
1958 } else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
1959 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1960 if (path) {
1961 path->clear();
1962
1963 bool bFirst = true;
1964 QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
1965 for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
1966 QPointF point;
1968 ++it;
1969 if (it == pointList.end())
1970 break;
1972 if (bFirst) {
1973 path->moveTo(point);
1974 bFirst = false;
1975 } else
1976 path->lineTo(point);
1977 }
1978 if (element.tagName() == "polygon")
1979 path->close();
1980
1981 path->setPosition(path->normalize());
1982
1983 obj = path;
1984 }
1985 } else if (element.tagName() == "path") {
1986 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
1987 if (path) {
1988 path->clear();
1989
1990 KoPathShapeLoader loader(path);
1991 loader.parseSvg(element.attribute("d"), true);
1992 path->setPosition(path->normalize());
1993
1994 QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
1995 SvgUtil::fromUserSpace(path->position().y()));
1996 QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
1997 SvgUtil::fromUserSpace(path->size().height()));
1998
1999 path->setSize(newSize);
2000 path->setPosition(newPosition);
2001
2002 if (element.hasAttribute("sodipodi:nodetypes")) {
2003 path->loadNodeTypes(element.attribute("sodipodi:nodetypes"));
2004 }
2005 obj = path;
2006 }
2007 }
2008
2009 return obj;
2010}
2011
2013{
2016
2018 if (obj) {
2020 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2021
2022 applyCurrentStyle(obj, extraOffset);
2023
2024 // handle id
2025 applyId(b.attribute("id"), obj);
2028 }
2029
2031
2032 if (obj) {
2034 }
2035 return obj;
2036}
2037
2038KoShape * SvgParser::createObject(const QDomElement &b, const SvgStyles &style)
2039{
2041
2043 if (obj) {
2045 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2046
2047 SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
2048 m_context.styleParser().parseFont(objStyle);
2049 applyStyle(obj, objStyle, extraOffset);
2050
2051 // handle id
2052 applyId(b.attribute("id"), obj);
2055 }
2056
2058
2059 if (obj) {
2061 }
2062
2063 return obj;
2064}
2065
2066KoShape * SvgParser::createShapeFromElement(const QDomElement &element, SvgLoadingContext &context)
2067{
2068 KoShape *object = 0;
2069
2070
2071 const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
2073
2074 foreach (KoShapeFactoryBase *f, factories) {
2075 KoShape *shape = f->createDefaultShape(m_documentResourceManager);
2076 if (!shape)
2077 continue;
2078
2079 SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
2080 if (!svgShape) {
2081 delete shape;
2082 continue;
2083 }
2084
2085 // reset transformation that might come from the default shape
2086 shape->setTransformation(QTransform());
2087
2088 // reset border
2089 KoShapeStrokeModelSP oldStroke = shape->stroke();
2091
2092 // reset fill
2094
2095 if (!svgShape->loadSvg(element, context)) {
2096 delete shape;
2097 continue;
2098 }
2099
2100 object = shape;
2101 break;
2102 }
2103
2104 if (!object) {
2105 object = createPath(element);
2106 }
2107
2108 return object;
2109}
2110
2111KoShape *SvgParser::createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2112{
2113 if (value.isEmpty()) {
2114 return 0;
2115 }
2116 unsigned int start = value.indexOf('(') + 1;
2117 unsigned int end = value.indexOf(')', start);
2118
2119 QString val = value.mid(start, end - start);
2120 QString fillRule;
2121 if (val.startsWith("evenodd,")) {
2122 start += QString("evenodd,").size();
2123 fillRule = "evenodd";
2124 } else if (val.startsWith("nonzero,")) {
2125 start += QString("nonzero,").size();
2126 fillRule = "nonzero";
2127 }
2128 val = value.mid(start, end - start);
2129
2130 QDomElement el;
2131 if (value.startsWith("url(")) {
2132 start = value.indexOf('#') + 1;
2133 KoShape *s = m_context.shapeById(value.mid(start, end - start));
2134 if (s) {
2135 const QTransform absTf = s->absoluteTransformation();
2136 KoShape *cloned = s->cloneShape();
2137 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
2138 // When we have a parent, the shape is inside the defs, but when not,
2139 // it's in the group we're in the currently parsing.
2140
2141 if (cloned && shapeInDefs(s) && hideShapesFromDefs) {
2142 cloned->setTransparency(1.0);
2143 }
2144 return cloned;
2145 }
2146 } else if (value.startsWith("circle(")) {
2147 el = e.ownerDocument().createElement("circle");
2148 QStringList params = val.split(" ");
2149 el.setAttribute("r", SvgUtil::parseUnitXY(context.currentGC(), context.resolvedProperties(), params.first()));
2150 if (params.contains("at")) {
2151 // 1 == "at"
2152 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(2)));
2153 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(3)));
2154 }
2155 } else if (value.startsWith("ellipse(")) {
2156 el = e.ownerDocument().createElement("ellipse");
2157 QStringList params = val.split(" ");
2158 el.setAttribute("rx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(0)));
2159 el.setAttribute("ry", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(1)));
2160 if (params.contains("at")) {
2161 // 2 == "at"
2162 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(3)));
2163 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(4)));
2164 }
2165 } else if (value.startsWith("polygon(")) {
2166 el = e.ownerDocument().createElement("polygon");
2167 QStringList points;
2168 Q_FOREACH(QString point, SvgUtil::simplifyList(val)) {
2169 bool xVal = points.size() % 2;
2170 if (xVal) {
2171 points.append(QString::number(SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), point)));
2172 } else {
2173 points.append(QString::number(SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), point)));
2174 }
2175 }
2176 el.setAttribute("points", points.join(" "));
2177 } else if (value.startsWith("path(")) {
2178 el = e.ownerDocument().createElement("path");
2179 // SVG path data is inside a string.
2180 start += 1;
2181 end -= 1;
2182 el.setAttribute("d", value.mid(start, end - start));
2183 }
2184
2185 el.setAttribute("fill-rule", fillRule);
2186 KoShape *shape = createShapeFromElement(el, context);
2187 if (shape) shape->setTransparency(1.0);
2188 return shape;
2189}
2190
2191QList<KoShape *> SvgParser::createListOfShapesFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context, bool hideShapesFromDefs)
2192{
2193 QList<KoShape*> shapeList;
2194 if (value == "auto" || value == "none") {
2195 return shapeList;
2196 }
2197 QStringList params = value.split(")");
2198 Q_FOREACH(const QString param, params) {
2199 KoShape *s = createShapeFromCSS(e, param.trimmed()+")", context, hideShapesFromDefs);
2200 if (s) {
2201 shapeList.append(s);
2202 }
2203 }
2204 return shapeList;
2205}
2206
2207KoShape *SvgParser::createShape(const QString &shapeID)
2208{
2209 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
2210 if (!factory) {
2211 debugFlake << "Could not find factory for shape id" << shapeID;
2212 return 0;
2213 }
2214
2216 if (!shape) {
2217 debugFlake << "Could not create Default shape for shape id" << shapeID;
2218 return 0;
2219 }
2220 if (shape->shapeId().isEmpty()) {
2221 shape->setShapeId(factory->id());
2222 }
2223
2224 // reset transformation that might come from the default shape
2225 shape->setTransformation(QTransform());
2226
2227 // reset border
2228 // ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
2230
2231 // reset fill
2233
2234 return shape;
2235}
2236
2237void SvgParser::applyId(const QString &id, KoShape *shape)
2238{
2239 if (id.isEmpty())
2240 return;
2241
2242 KoShape *existingShape = m_context.shapeById(id);
2243 if (existingShape) {
2244 debugFlake << "SVG contains nodes with duplicated id:" << id;
2245 // Generate a random name and just don't register the shape.
2246 // We don't use the name as a unique identifier so we don't need to
2247 // worry about the extremely rare case of name collision.
2248 const QString suffix = QString::number(QRandomGenerator::system()->bounded(0x10000000, 0x7FFFFFFF), 16);
2249 const QString newName = id + '_' + suffix;
2250 shape->setName(newName);
2251 } else {
2252 shape->setName(id);
2253 m_context.registerShape(id, shape);
2254 }
2255}
#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