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>
39#include <KoFilterEffect.h>
40#include "KoFilterEffectStack.h"
42#include <KoClipPath.h>
43#include <KoClipMask.h>
44#include <KoXmlNS.h>
45#include <QXmlSimpleReader>
46
47#include "SvgMeshGradient.h"
48#include "SvgMeshPatch.h"
49#include "SvgUtil.h"
50#include "SvgShape.h"
51#include "SvgGraphicContext.h"
52#include "SvgFilterHelper.h"
53#include "SvgGradientHelper.h"
54#include "SvgClipPathHelper.h"
56#include "kis_pointer_utils.h"
58#include <KoMarker.h>
59
60#include <text/KoSvgTextShape.h>
62
63#include "kis_dom_utils.h"
64
65#include "kis_algebra_2d.h"
66#include "kis_debug.h"
67#include "kis_global.h"
68#include <QXmlStreamReader>
69#include <algorithm>
70
72 struct El {
73 El(const QDomElement* ue, const QString& key) :
74 m_useElement(ue), m_key(key) {
75 }
76 const QDomElement* m_useElement;
77 QString m_key;
78 };
82
83 void add(const QDomElement* useE, const QString& key) {
84 m_uses.push_back(El(useE, key));
85 }
86 bool empty() const {
87 return m_uses.empty();
88 }
89
90 void checkPendingUse(const QDomElement &b, QList<KoShape*>& shapes) {
91 KoShape* shape = 0;
92 const QString id = b.attribute("id");
93
94 if (id.isEmpty())
95 return;
96
97 // debugFlake << "Checking id: " << id;
98 auto i = std::partition(m_uses.begin(), m_uses.end(),
99 [&](const El& e) -> bool {return e.m_key != id;});
100
101 while (i != m_uses.end()) {
102 const El& el = m_uses.back();
104 // debugFlake << "Found pending use for id: " << el.m_key;
105 shape = m_parse->resolveUse(*(el.m_useElement), el.m_key);
106 if (shape) {
107 shapes.append(shape);
108 }
109 }
110 m_uses.pop_back();
111 }
112 }
113
115 while (!m_uses.empty()) {
116 const El& el = m_uses.back();
117 debugFlake << "WARNING: could not find path in <use xlink:href=\"#xxxxx\" expression. Losing data here. Key:"
118 << el.m_key;
119 m_uses.pop_back();
120 }
121 }
123 std::vector<El> m_uses;
124};
125
126
128 : m_context(documentResourceManager)
129 , m_documentResourceManager(documentResourceManager)
130{
131}
132
134{
135 for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) {
136 delete it.value();
137 }
138 qDeleteAll(m_defsShapes);
139}
140
141QDomDocument SvgParser::createDocumentFromSvg(QIODevice *device, QString *errorMsg, int *errorLine, int *errorColumn)
142{
143 return createDocumentFromSvg(QXmlStreamReader(device), errorMsg, errorLine, errorColumn);
144}
145
146QDomDocument SvgParser::createDocumentFromSvg(const QByteArray &data, QString *errorMsg, int *errorLine, int *errorColumn)
147{
148 return createDocumentFromSvg(QXmlStreamReader(data), errorMsg, errorLine, errorColumn);
149}
150
151QDomDocument SvgParser::createDocumentFromSvg(const QString &data, QString *errorMsg, int *errorLine, int *errorColumn)
152{
153 return createDocumentFromSvg(QXmlStreamReader(data), errorMsg, errorLine, errorColumn);
154}
155
156QDomDocument SvgParser::createDocumentFromSvg(QXmlStreamReader reader, QString *errorMsg, int *errorLine, int *errorColumn)
157{
158 QDomDocument doc;
159 reader.setNamespaceProcessing(false);
160 if (!doc.setContent(&reader, false, errorMsg, errorLine, errorColumn)) {
161 return {};
162 }
163
164 return doc;
165}
166
167void SvgParser::setXmlBaseDir(const QString &baseDir)
168{
170
172 [this](const QString &name) {
173 QStringList possibleNames;
174 possibleNames << name;
175 possibleNames << QDir::cleanPath(QDir(m_context.xmlBaseDir()).absoluteFilePath(name));
176 for (QString fileName : possibleNames) {
177 QFile file(fileName);
178 if (file.exists()) {
179 file.open(QIODevice::ReadOnly);
180 return file.readAll();
181 }
182 }
183 return QByteArray();
184 });
185}
186
187void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
188{
192 m_context.currentGC()->pixelsPerInch = pixelsPerInch;
193
194 const qreal scale = 72.0 / pixelsPerInch;
195 const QTransform t = QTransform::fromScale(scale, scale);
196 m_context.currentGC()->currentBoundingBox = boundsInPixels;
198}
199
205
207{
209}
210
215
217{
218 return m_shapes;
219}
220
222{
223 QVector<KoSvgSymbol*> symbols = m_symbols.values().toVector();
224 m_symbols.clear();
225 return symbols;
226}
227
228// Helper functions
229// ---------------------------------------------------------------------------------------
230
232{
233 SvgGradientHelper *result = 0;
234
235 // check if gradient was already parsed, and return it
236 if (m_gradients.contains(id)) {
237 result = &m_gradients[ id ];
238 }
239
240 // check if gradient was stored for later parsing
241 if (!result && m_context.hasDefinition(id)) {
242 const QDomElement &e = m_context.definition(id);
243 if (e.tagName().contains("Gradient")) {
244 result = parseGradient(m_context.definition(id));
245 } else if (e.tagName() == "meshgradient") {
247 }
248 }
249
250 return result;
251}
252
254{
256
257 // check if gradient was stored for later parsing
258 if (m_context.hasDefinition(id)) {
259 const QDomElement &e = m_context.definition(id);
260 if (e.tagName() == "pattern") {
261 result = parsePattern(m_context.definition(id), shape);
262 }
263 }
264
265 return result;
266}
267
268SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href)
269{
270 // check if filter was already parsed, and return it
271 if (m_filters.contains(id))
272 return &m_filters[ id ];
273
274 // check if filter was stored for later parsing
275 if (!m_context.hasDefinition(id))
276 return 0;
277
278 const QDomElement &e = m_context.definition(id);
279 if (e.childNodes().count() == 0) {
280 QString mhref = e.attribute("xlink:href").mid(1);
281
282 if (m_context.hasDefinition(mhref))
283 return findFilter(mhref, id);
284 else
285 return 0;
286 } else {
287 // ok parse filter now
289 return 0;
290 }
291
292 // return successfully parsed filter or 0
293 QString n;
294 if (href.isEmpty())
295 n = id;
296 else
297 n = href;
298
299 if (m_filters.contains(n))
300 return &m_filters[ n ];
301 else
302 return 0;
303}
304
306{
307 return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
308}
309
310// Parsing functions
311// ---------------------------------------------------------------------------------------
312
313qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
314{
315 return SvgUtil::parseUnit(m_context.currentGC(), m_context.resolvedProperties(), unit, horiz, vert, bbox);
316}
317
318qreal SvgParser::parseUnitX(const QString &unit)
319{
321}
322
323qreal SvgParser::parseUnitY(const QString &unit)
324{
326}
327
328qreal SvgParser::parseUnitXY(const QString &unit)
329{
331}
332
333qreal SvgParser::parseAngular(const QString &unit)
334{
336}
337
338
340{
341 // IMPROVEMENTS:
342 // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
343 // - A gradient inherits attributes it does not have from the referencing gradient.
344 // - Gradients with no color stops have no fill or stroke.
345 // - Gradients with one color stop have a solid color.
346
348 if (!gc) return 0;
349
350 SvgGradientHelper gradHelper;
351
352 QString gradientId = e.attribute("id");
353 if (gradientId.isEmpty()) return 0;
354
355 // check if we have this gradient already parsed
356 // copy existing gradient if it exists
357 if (m_gradients.contains(gradientId)) {
358 return &m_gradients[gradientId];
359 }
360
361 if (e.hasAttribute("xlink:href")) {
362 // strip the '#' symbol
363 QString href = e.attribute("xlink:href").mid(1);
364
365 if (!href.isEmpty()) {
366 // copy the referenced gradient if found
367 SvgGradientHelper *pGrad = findGradient(href);
368 if (pGrad) {
369 gradHelper = *pGrad;
370 }
371 }
372 }
373
374 const QGradientStops defaultStops = gradHelper.gradient()->stops();
375
376 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
378 }
379
382
383 if (e.tagName() == "linearGradient") {
384 QLinearGradient *g = new QLinearGradient();
385 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
386 g->setCoordinateMode(QGradient::ObjectBoundingMode);
387 g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
388 SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
389 g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
390 SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
391 } else {
392 g->setStart(QPointF(parseUnitX(e.attribute("x1")),
393 parseUnitY(e.attribute("y1"))));
394 g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
395 parseUnitY(e.attribute("y2"))));
396 }
397 gradHelper.setGradient(g);
398
399 } else if (e.tagName() == "radialGradient") {
400 QRadialGradient *g = new QRadialGradient();
401 if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
402 g->setCoordinateMode(QGradient::ObjectBoundingMode);
403 g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
404 SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
405 g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
406 g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
407 SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
408 } else {
409 g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
410 parseUnitY(e.attribute("cy"))));
411 g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
412 parseUnitY(e.attribute("fy"))));
413 g->setRadius(parseUnitXY(e.attribute("r")));
414 }
415 gradHelper.setGradient(g);
416 } else {
417 debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName();
418 }
419
420 // handle spread method
421 QGradient::Spread spreadMethod = QGradient::PadSpread;
422 QString spreadMethodStr = e.attribute("spreadMethod");
423 if (!spreadMethodStr.isEmpty()) {
424 if (spreadMethodStr == "reflect") {
425 spreadMethod = QGradient::ReflectSpread;
426 } else if (spreadMethodStr == "repeat") {
427 spreadMethod = QGradient::RepeatSpread;
428 }
429 }
430
431 gradHelper.setSpreadMode(spreadMethod);
432
433 // Parse the color stops.
434 m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
435
436 if (e.hasAttribute("gradientTransform")) {
437 SvgTransformParser p(e.attribute("gradientTransform"));
438 if (p.isValid()) {
439 gradHelper.setTransform(p.transform());
440 }
441 }
442
444
445 m_gradients.insert(gradientId, gradHelper);
446
447 return &m_gradients[gradientId];
448}
449
451{
452 SvgGradientHelper gradHelper;
453 QString gradientId = e.attribute("id");
454 QScopedPointer<SvgMeshGradient> g(new SvgMeshGradient);
455
456 // check if we have this gradient already parsed
457 // copy existing gradient if it exists
458 if (m_gradients.contains(gradientId)) {
459 return &m_gradients[gradientId];
460 }
461
462 if (e.hasAttribute("xlink:href")) {
463 // strip the '#' symbol
464 QString href = e.attribute("xlink:href").mid(1);
465
466 if (!href.isEmpty()) {
467 // copy the referenced gradient if found
468 SvgGradientHelper *pGrad = findGradient(href);
469 if (pGrad) {
470 gradHelper = *pGrad;
471 }
472 }
473 }
474
475 if (e.attribute("gradientUnits") == "userSpaceOnUse") {
477 }
478
479 if (e.hasAttribute("transform")) {
480 SvgTransformParser p(e.attribute("transform"));
481 if (p.isValid()) {
482 gradHelper.setTransform(p.transform());
483 }
484 }
485
486 QString type = e.attribute("type");
487 g->setType(SvgMeshGradient::BILINEAR);
488 if (!type.isEmpty() && type == "bicubic") {
489 g->setType(SvgMeshGradient::BICUBIC);
490 }
491
492 int irow = 0, icols;
493 for (int i = 0; i < e.childNodes().size(); ++i) {
494 QDomNode node = e.childNodes().at(i);
495
496 if (node.nodeName() == "meshrow") {
497
498 SvgMeshStop startingNode;
499 if (irow == 0) {
500 startingNode.point = QPointF(
501 parseUnitX(e.attribute("x")),
502 parseUnitY(e.attribute(("y"))));
503 startingNode.color = QColor();
504 }
505
506 icols = 0;
507 g->getMeshArray()->newRow();
508 for (int j = 0; j < node.childNodes().size() ; ++j) {
509 QDomNode meshpatchNode = node.childNodes().at(j);
510
511 if (meshpatchNode.nodeName() == "meshpatch") {
512 if (irow > 0) {
513 // Starting point for this would be the bottom (right) corner of the above patch
514 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Bottom, irow - 1, icols);
515 } else if (icols != 0) {
516 // Starting point for this would be the right (top) corner of the previous patch
517 startingNode = g->getMeshArray()->getStop(SvgMeshPatch::Right, irow, icols - 1);
518 }
519
520 QList<QPair<QString, QColor>> rawStops = parseMeshPatch(meshpatchNode);
521 // TODO handle the false result
522 if (!g->getMeshArray()->addPatch(rawStops, startingNode.point)) {
523 debugFlake << "WARNING: Failed to create meshpatch";
524 }
525 icols++;
526 }
527 }
528 irow++;
529 }
530 }
531 gradHelper.setMeshGradient(g.data());
532 m_gradients.insert(gradientId, gradHelper);
533
534 return &m_gradients[gradientId];
535}
536
537#define forEachElement( elem, parent ) \
538 for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \
539 if ( ( elem = _node.toElement() ).isNull() ) {} else
540
542{
543 // path and its associated color
545
547 if (!gc) return rawstops;
548
549 QDomElement e = meshpatchNode.toElement();
550
551 QDomElement stop;
552
553 forEachElement(stop, e) {
554 qreal X = 0; // dummy value, don't care, just to ensure the function won't blow up (also to avoid a Coverity issue)
555 QColor color = m_context.styleParser().parseColorStop(stop, gc, X).second;
556
557 QString pathStr = stop.attribute("path");
558
559 rawstops.append({pathStr, color});
560 }
561
562 return rawstops;
563}
564
565inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
566{
567 QTransform result =
568 patternTransform *
569 QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
570 patternTransform.inverted();
571 KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
572
573 return QPointF(result.dx(), result.dy());
574}
575
577{
585
587 if (!gc) return pattHelper;
588
589 const QString patternId = e.attribute("id");
590 if (patternId.isEmpty()) return pattHelper;
591
592 pattHelper = toQShared(new KoVectorPatternBackground);
593
594 if (e.hasAttribute("xlink:href")) {
595 // strip the '#' symbol
596 QString href = e.attribute("xlink:href").mid(1);
597
598 if (!href.isEmpty() &&href != patternId) {
599 // copy the referenced pattern if found
601 if (pPatt) {
602 pattHelper = pPatt;
603 }
604 }
605 }
606
607 pattHelper->setReferenceCoordinates(
608 KoFlake::coordinatesFromString(e.attribute("patternUnits"),
609 pattHelper->referenceCoordinates()));
610
611 pattHelper->setContentCoordinates(
612 KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
613 pattHelper->contentCoordinates()));
614
615 if (e.hasAttribute("patternTransform")) {
616 SvgTransformParser p(e.attribute("patternTransform"));
617 if (p.isValid()) {
618 pattHelper->setPatternTransform(p.transform());
619 }
620 }
621
622 if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
623 QRectF referenceRect(
624 SvgUtil::fromPercentage(e.attribute("x", "0%")),
625 SvgUtil::fromPercentage(e.attribute("y", "0%")),
626 SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
627 SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
628
629 pattHelper->setReferenceRect(referenceRect);
630 } else {
631 QRectF referenceRect(
632 parseUnitX(e.attribute("x", "0")),
633 parseUnitY(e.attribute("y", "0")),
634 parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
635 parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
636
637 pattHelper->setReferenceRect(referenceRect);
638 }
639
650 const QTransform dstShapeTransform = shape->absoluteTransformation();
651 const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
652 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
653 const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
654
656 gc = m_context.currentGC();
658
659 // start building shape tree from scratch
660 gc->matrix = QTransform();
661
662 const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
663 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
664 boundingRect.x(), boundingRect.y());
665
666
667
668 // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
669 // although we expect the pattern be reusable, but it is not so!
670 // WARNING2: the pattern shapes are stored in *User* coordinate system, although
671 // the "official" content system might be either OBB or User. It means that
672 // this baked transform should be stripped before writing the shapes back
673 // into SVG
674 if (e.hasAttribute("viewBox")) {
676 pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
677 relativeToShape.mapRect(pattHelper->referenceRect()) :
678 pattHelper->referenceRect();
679
681 pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
682
683 } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
684 gc->matrix = relativeToShape * gc->matrix;
685 }
686
687 // We do *not* apply patternTransform here! Here we only bake the untransformed
688 // version of the shape. The transformed one will be done in the very end while rendering.
689
690 QList<KoShape*> patternShapes = parseContainer(e);
691
692 if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
693 // In Krita we normalize the shapes, bake this transform into the pattern shapes
694
695 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
696
697 Q_FOREACH (KoShape *shape, patternShapes) {
698 shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
699 }
700 }
701
702 if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
703 // In Krita we normalize the shapes, bake this transform into reference rect
704 // NOTE: this is possible *only* when pattern transform is not perspective
705 // (which is always true for SVG)
706
707 const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
708
709 QRectF ref = pattHelper->referenceRect();
710 ref.translate(offset);
711 pattHelper->setReferenceRect(ref);
712 }
713
715 gc = m_context.currentGC();
716
717 if (!patternShapes.isEmpty()) {
718 pattHelper->setShapes(patternShapes);
719 }
720
721 return pattHelper;
722}
723
724bool SvgParser::parseFilter(const QDomElement &e, const QDomElement &referencedBy)
725{
726 SvgFilterHelper filter;
727
728 // Use the filter that is referencing, or if there isn't one, the original filter
729 QDomElement b;
730 if (!referencedBy.isNull())
731 b = referencedBy;
732 else
733 b = e;
734
735 // check if we are referencing another filter
736 if (e.hasAttribute("xlink:href")) {
737 QString href = e.attribute("xlink:href").mid(1);
738 if (! href.isEmpty()) {
739 // copy the referenced filter if found
740 SvgFilterHelper *refFilter = findFilter(href);
741 if (refFilter)
742 filter = *refFilter;
743 }
744 } else {
745 filter.setContent(b);
746 }
747
748 if (b.attribute("filterUnits") == "userSpaceOnUse")
750 if (b.attribute("primitiveUnits") == "objectBoundingBox")
752
753 // parse filter region rectangle
754 if (filter.filterUnits() == KoFlake::UserSpaceOnUse) {
755 filter.setPosition(QPointF(parseUnitX(b.attribute("x")),
756 parseUnitY(b.attribute("y"))));
757 filter.setSize(QSizeF(parseUnitX(b.attribute("width")),
758 parseUnitY(b.attribute("height"))));
759 } else {
760 // x, y, width, height are in percentages of the object referencing the filter
761 // so we just parse the percentages
762 filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")),
763 SvgUtil::fromPercentage(b.attribute("y", "-0.1"))));
764 filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")),
765 SvgUtil::fromPercentage(b.attribute("height", "1.2"))));
766 }
767
768 m_filters.insert(b.attribute("id"), filter);
769
770 return true;
771}
772
773bool SvgParser::parseMarker(const QDomElement &e)
774{
775 const QString id = e.attribute("id");
776 if (id.isEmpty()) return false;
777
778 QScopedPointer<KoMarker> marker(new KoMarker());
779 marker->setCoordinateSystem(
780 KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
781
782 marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
783 parseUnitY(e.attribute("refY"))));
784
785 marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
786 parseUnitY(e.attribute("markerHeight", "3"))));
787
788 const QString orientation = e.attribute("orient", "0");
789
790 if (orientation == "auto") {
791 marker->setAutoOrientation(true);
792 } else {
793 marker->setExplicitOrientation(parseAngular(orientation));
794 }
795
796 // ensure that the clip path is loaded in local coordinates system
798 m_context.currentGC()->matrix = QTransform();
799 m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
800
801 KoShape *markerShape = parseGroup(e);
802
804
805 if (!markerShape) return false;
806
807 marker->setShapes({markerShape});
808
809 m_markers.insert(id, QExplicitlySharedDataPointer<KoMarker>(marker.take()));
810
811 return true;
812}
813
814bool SvgParser::parseSymbol(const QDomElement &e)
815{
816 const QString id = e.attribute("id");
817
818 if (id.isEmpty()) return false;
819
820 QScopedPointer<KoSvgSymbol> svgSymbol(new KoSvgSymbol());
821
822 // ensure that the clip path is loaded in local coordinates system
824 m_context.currentGC()->matrix = QTransform();
825 m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0);
826
827 QString title = e.firstChildElement("title").toElement().text();
828
829 QScopedPointer<KoShape> symbolShape(parseGroup(e));
830
832
833 if (!symbolShape) return false;
834
835 svgSymbol->shape = symbolShape.take();
836 svgSymbol->title = title;
837 svgSymbol->id = id;
838 if (title.isEmpty()) svgSymbol->title = id;
839
840 if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
841 debugFlake << "Symbol" << id << "seems to be empty, discarding";
842 return false;
843 }
844
845 // TODO: out default set of symbols had duplicated ids! We should
846 // make sure they are unique!
847 if (m_symbols.contains(id)) {
848 delete m_symbols[id];
849 m_symbols.remove(id);
850 }
851
852 m_symbols.insert(id, svgSymbol.take());
853
854 return true;
855}
856
857void SvgParser::parseMetadataApplyToShape(const QDomElement &e, KoShape *shape)
858{
859 const QString titleTag = "title";
860 const QString descriptionTag = "desc";
861 QDomElement title = e.firstChildElement(titleTag);
862 if (!title.isNull()) {
863 QDomText text = getTheOnlyTextChild(title);
864 if (!text.data().isEmpty()) {
865 shape->setAdditionalAttribute(titleTag, text.data());
866 }
867 }
868 QDomElement description = e.firstChildElement(descriptionTag);
869 if (!description.isNull()) {
870 QDomText text = getTheOnlyTextChild(description);
871 if (!text.data().isEmpty()) {
872 shape->setAdditionalAttribute(descriptionTag, text.data());
873 }
874 }
875}
876
877bool SvgParser::parseClipPath(const QDomElement &e)
878{
879 SvgClipPathHelper clipPath;
880
881 const QString id = e.attribute("id");
882 if (id.isEmpty()) return false;
883
884 clipPath.setClipPathUnits(
885 KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
886
887 // ensure that the clip path is loaded in local coordinates system
889 m_context.currentGC()->matrix = QTransform();
891
892 KoShape *clipShape = parseGroup(e);
893
895
896 if (!clipShape) return false;
897
898 clipPath.setShapes({clipShape});
899 m_clipPaths.insert(id, clipPath);
900
901 return true;
902}
903
904bool SvgParser::parseClipMask(const QDomElement &e)
905{
907
908 const QString id = e.attribute("id");
909 if (id.isEmpty()) return false;
910
911 clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
912 clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
913
914 QRectF maskRect;
915
916 if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
917 maskRect.setRect(
918 SvgUtil::fromPercentage(e.attribute("x", "-10%")),
919 SvgUtil::fromPercentage(e.attribute("y", "-10%")),
920 SvgUtil::fromPercentage(e.attribute("width", "120%")),
921 SvgUtil::fromPercentage(e.attribute("height", "120%")));
922 } else {
923 maskRect.setRect(
924 parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
925 parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
926 parseUnitX(e.attribute("width", "120%")),
927 parseUnitY(e.attribute("height", "120%")));
928 }
929
930 clipMask->setMaskRect(maskRect);
931
932
933 // ensure that the clip mask is loaded in local coordinates system
935 m_context.currentGC()->matrix = QTransform();
937
938 KoShape *clipShape = parseGroup(e);
939
941
942 if (!clipShape) return false;
943 clipMask->setShapes({clipShape});
944
945 m_clipMasks.insert(id, clipMask);
946 return true;
947}
948
955
956void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
957{
958 if (!shape) return;
959
961
962 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
963 applyMarkers(pathShape);
964 }
965
966 applyFilter(shape);
967 applyClipping(shape, shapeToOriginalUserCoordinates);
968 applyMaskClipping(shape, shapeToOriginalUserCoordinates);
969
970}
971
973{
974 if (!shape) return;
975
977 KIS_ASSERT(gc);
978
979 if (!dynamic_cast<KoShapeGroup*>(shape)) {
980 applyFillStyle(shape);
981 applyStrokeStyle(shape);
982 }
983
984 if (!gc->display || !gc->visible) {
995 shape->setVisible(false);
996 }
997 shape->setTransparency(1.0 - gc->opacity);
998
999 applyPaintOrder(shape);
1000}
1001
1002
1003void SvgParser::applyStyle(KoShape *obj, const QDomElement &e, const QPointF &shapeToOriginalUserCoordinates)
1004{
1005 applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
1006}
1007
1008void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
1009{
1011 if (!gc)
1012 return;
1013
1015
1016 if (!obj)
1017 return;
1018
1019 if (!dynamic_cast<KoShapeGroup*>(obj)) {
1020 applyFillStyle(obj);
1021 applyStrokeStyle(obj);
1022 }
1023
1024 if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(obj)) {
1025 applyMarkers(pathShape);
1026 }
1027
1028 applyFilter(obj);
1029 applyClipping(obj, shapeToOriginalUserCoordinates);
1030 applyMaskClipping(obj, shapeToOriginalUserCoordinates);
1031
1032 if (!gc->display || !gc->visible) {
1033 obj->setVisible(false);
1034 }
1035 obj->setTransparency(1.0 - gc->opacity);
1036 applyPaintOrder(obj);
1037}
1038
1040 const KoShape *shape,
1041 const SvgGraphicsContext *gc,
1042 QTransform *transform)
1043{
1044 QGradient *resultGradient = 0;
1045 KIS_ASSERT(transform);
1046
1047 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1048 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1049 *transform = gradient->transform();
1050 } else {
1051 if (gradient->gradient()->type() == QGradient::LinearGradient) {
1057 const QRectF boundingRect = shape->outline().boundingRect();
1058 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1059 boundingRect.x(), boundingRect.y());
1060
1061 const QTransform relativeToUser =
1062 relativeToShape * shape->transformation() * gc->matrix.inverted();
1063
1064 const QTransform userToRelative = relativeToUser.inverted();
1065
1066 const QLinearGradient *o = static_cast<const QLinearGradient*>(gradient->gradient());
1067 QLinearGradient *g = new QLinearGradient();
1068 g->setStart(userToRelative.map(o->start()));
1069 g->setFinalStop(userToRelative.map(o->finalStop()));
1070 g->setCoordinateMode(QGradient::ObjectBoundingMode);
1071 g->setStops(o->stops());
1072 g->setSpread(o->spread());
1073
1074 resultGradient = g;
1075 *transform = relativeToUser * gradient->transform() * userToRelative;
1076
1077 } else if (gradient->gradient()->type() == QGradient::RadialGradient) {
1078 // For radial and conical gradients such conversion is not possible
1079
1080 resultGradient = KoFlake::cloneGradient(gradient->gradient());
1081 *transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
1082
1083 const QRectF outlineRect = shape->outlineRect();
1084 if (outlineRect.isEmpty()) return resultGradient;
1085
1092 QRadialGradient *rgradient = static_cast<QRadialGradient*>(resultGradient);
1093
1094 const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect);
1095 const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension));
1096
1097 const QTransform uniformizeTransform =
1098 QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) *
1099 QTransform::fromScale(maxDimension / shape->outlineRect().width(),
1100 maxDimension / shape->outlineRect().height()) *
1101 QTransform::fromTranslate(outlineRect.x(), outlineRect.y());
1102
1103 const QPointF centerLocal = transform->map(rgradient->center());
1104 const QPointF focalLocal = transform->map(rgradient->focalPoint());
1105
1106 const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize);
1107 const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize);
1108
1109 rgradient->setCenter(centerOBB);
1110 rgradient->setFocalPoint(focalOBB);
1111
1112 const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize);
1113 const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize);
1114
1115 rgradient->setCenterRadius(centerRadiusOBB);
1116 rgradient->setFocalRadius(focalRadiusOBB);
1117
1118 rgradient->setCoordinateMode(QGradient::ObjectBoundingMode);
1119
1120 // Warning: should it really be pre-multiplication?
1121 *transform = uniformizeTransform * gradient->transform();
1122 }
1123 }
1124
1125 // NOTE: this is an internal API in Qt. SVG specs specify that we
1126 // shouldn't interpolate in pre-multiplied space.
1127 resultGradient->setInterpolationMode(QGradient::ComponentInterpolation);
1128
1129 return resultGradient;
1130}
1131
1133 const KoShape *shape,
1134 const SvgGraphicsContext *gc) {
1135
1136 SvgMeshGradient *resultGradient = nullptr;
1137
1138 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
1139
1140 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1141
1142 const QRectF boundingRect = shape->outline().boundingRect();
1143 const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
1144 boundingRect.x(), boundingRect.y());
1145
1146 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1147 // So, transform is called multiple times on the mesh and that's not nice
1148 resultGradient->setTransform(gradient->transform() * relativeToShape);
1149 } else {
1150 // NOTE: Krita's shapes use their own coordinate system. Where origin is at the top left
1151 // of the SHAPE. All the mesh patches will be rendered in the global 'user' coordinate system
1152 // where the origin is at the top left of the LAYER/DOCUMENT.
1153
1154 // Get the user coordinates of the shape
1155 const QTransform shapeglobal = shape->absoluteTransformation() * gc->matrix.inverted();
1156
1157 // Get the translation offset to shift the origin from "Shape" to "User"
1158 const QTransform translationOffset = QTransform::fromTranslate(-shapeglobal.dx(), -shapeglobal.dy());
1159
1160 resultGradient = new SvgMeshGradient(*gradient->meshgradient());
1161
1162 // NOTE: we apply translation right away, because caching hasn't been implemented for rendering, yet.
1163 // So, transform is called multiple times on the mesh and that's not nice
1164 resultGradient->setTransform(gradient->transform() * translationOffset);
1165 }
1166
1167 return resultGradient;
1168}
1169
1171{
1173 if (! gc)
1174 return;
1175
1178 } else if (gc->fillType == SvgGraphicsContext::Solid) {
1180 } else if (gc->fillType == SvgGraphicsContext::Complex) {
1181 // try to find referenced gradient
1182 SvgGradientHelper *gradient = findGradient(gc->fillId);
1183 if (gradient) {
1184 QTransform transform;
1185
1186 if (gradient->isMeshGradient()) {
1188
1189 QScopedPointer<SvgMeshGradient> result(prepareMeshGradientForShape(gradient, shape, gc));
1190
1191 bg = toQShared(new KoMeshGradientBackground(result.data(), transform));
1192 shape->setBackground(bg);
1193 } else if (gradient->gradient()) {
1194 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1195 if (result) {
1197 bg = toQShared(new KoGradientBackground(result));
1198 bg->setTransform(transform);
1199 shape->setBackground(bg);
1200 }
1201 }
1202 } else {
1204 findPattern(gc->fillId, shape);
1205
1206 if (pattern) {
1207 shape->setBackground(pattern);
1208 } else {
1209 // no referenced fill found, use fallback color
1211 }
1212 }
1213 } else if (gc->fillType == SvgGraphicsContext::Inherit) {
1214 shape->setInheritBackground(true);
1215 }
1216
1217 KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
1218 if (path)
1219 path->setFillRule(gc->fillRule);
1220}
1221
1222void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
1223{
1224 const double lineWidth = srcStroke->lineWidth();
1225 QVector<qreal> dashes = srcStroke->lineDashes();
1226
1227 // apply line width to dashes and dash offset
1228 if (dashes.count() && lineWidth > 0.0) {
1229 const double dashOffset = srcStroke->dashOffset();
1230 QVector<qreal> dashes = srcStroke->lineDashes();
1231
1232 for (int i = 0; i < dashes.count(); ++i) {
1233 dashes[i] /= lineWidth;
1234 }
1235
1236 dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
1237 dstStroke->setDashOffset(dashOffset / lineWidth);
1238 } else {
1239 dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
1240 }
1241}
1242
1244{
1246 if (! gc)
1247 return;
1248
1250 KoShapeStrokeSP stroke(new KoShapeStroke());
1251 stroke->setLineWidth(0.0);
1252 const QColor color = Qt::transparent;
1253 stroke->setColor(color);
1254 shape->setStroke(stroke);
1255 } else if (gc->strokeType == SvgGraphicsContext::Solid) {
1256 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1257 applyDashes(gc->stroke, stroke);
1258 shape->setStroke(stroke);
1259 } else if (gc->strokeType == SvgGraphicsContext::Complex) {
1260 // try to find referenced gradient
1261 SvgGradientHelper *gradient = findGradient(gc->strokeId);
1262 if (gradient) {
1263 QTransform transform;
1264 QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
1265 if (result) {
1266 QBrush brush = *result;
1267 delete result;
1268 brush.setTransform(transform);
1269
1270 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1271 stroke->setLineBrush(brush);
1272 applyDashes(gc->stroke, stroke);
1273 shape->setStroke(stroke);
1274 }
1275 } else {
1276 // no referenced stroke found, use fallback color
1277 KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
1278 applyDashes(gc->stroke, stroke);
1279 shape->setStroke(stroke);
1280 }
1281 } else if (gc->strokeType == SvgGraphicsContext::Inherit) {
1282 shape->setInheritStroke(true);
1283 }
1284}
1285
1287{
1289 if (! gc)
1290 return;
1291
1292 if (gc->filterId.isEmpty())
1293 return;
1294
1295 SvgFilterHelper *filter = findFilter(gc->filterId);
1296 if (! filter)
1297 return;
1298
1299 QDomElement content = filter->content();
1300
1301 // parse filter region
1302 QRectF bound(shape->position(), shape->size());
1303 // work on bounding box without viewbox transformation applied
1304 // so user space coordinates of bounding box and filter region match up
1305 bound = gc->viewboxTransform.inverted().mapRect(bound);
1306
1307 QRectF filterRegion(filter->position(bound), filter->size(bound));
1308
1309 // convert filter region to boundingbox units
1310 QRectF objectFilterRegion;
1311 objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound));
1312 objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound));
1313
1315 context.setShapeBoundingBox(bound);
1316 // enable units conversion
1319
1321
1322 KoFilterEffectStack *filterStack = 0;
1323
1324 QSet<QString> stdInputs;
1325 stdInputs << "SourceGraphic" << "SourceAlpha";
1326 stdInputs << "BackgroundImage" << "BackgroundAlpha";
1327 stdInputs << "FillPaint" << "StrokePaint";
1328
1329 QMap<QString, KoFilterEffect*> inputs;
1330
1331 // create the filter effects and add them to the shape
1332 for (QDomNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) {
1333 QDomElement primitive = n.toElement();
1334 KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context);
1335 if (!filterEffect) {
1336 debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet";
1337 continue;
1338 }
1339
1340 const QString input = primitive.attribute("in");
1341 if (!input.isEmpty()) {
1342 filterEffect->setInput(0, input);
1343 }
1344 const QString output = primitive.attribute("result");
1345 if (!output.isEmpty()) {
1346 filterEffect->setOutput(output);
1347 }
1348
1349 QRectF subRegion;
1350 // parse subregion
1351 if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) {
1352 const QString xa = primitive.attribute("x");
1353 const QString ya = primitive.attribute("y");
1354 const QString wa = primitive.attribute("width");
1355 const QString ha = primitive.attribute("height");
1356
1357 if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) {
1358 bool hasStdInput = false;
1359 bool isFirstEffect = filterStack == 0;
1360 // check if one of the inputs is a standard input
1361 Q_FOREACH (const QString &input, filterEffect->inputs()) {
1362 if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) {
1363 hasStdInput = true;
1364 break;
1365 }
1366 }
1367 if (hasStdInput || primitive.tagName() == "feImage") {
1368 // default to 0%, 0%, 100%, 100%
1369 subRegion.setTopLeft(QPointF(0, 0));
1370 subRegion.setSize(QSizeF(1, 1));
1371 } else {
1372 // defaults to bounding rect of all referenced nodes
1373 Q_FOREACH (const QString &input, filterEffect->inputs()) {
1374 if (!inputs.contains(input))
1375 continue;
1376
1377 KoFilterEffect *inputFilter = inputs[input];
1378 if (inputFilter)
1379 subRegion |= inputFilter->filterRect();
1380 }
1381 }
1382 } else {
1383 const qreal x = parseUnitX(xa);
1384 const qreal y = parseUnitY(ya);
1385 const qreal w = parseUnitX(wa);
1386 const qreal h = parseUnitY(ha);
1387 subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound));
1388 subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound));
1389 }
1390 } else {
1391 // x, y, width, height are in percentages of the object referencing the filter
1392 // so we just parse the percentages
1393 const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0"));
1394 const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0"));
1395 const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1"));
1396 const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1"));
1397 subRegion = QRectF(QPointF(x, y), QSizeF(w, h));
1398 }
1399
1400 filterEffect->setFilterRect(subRegion);
1401
1402 if (!filterStack)
1403 filterStack = new KoFilterEffectStack();
1404
1405 filterStack->appendFilterEffect(filterEffect);
1406 inputs[filterEffect->output()] = filterEffect;
1407 }
1408 if (filterStack) {
1409 filterStack->setClipRect(objectFilterRegion);
1410 shape->setFilterEffectStack(filterStack);
1411 }
1412}
1413
1415{
1417 if (!gc)
1418 return;
1419
1420 if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
1422 }
1423
1424 if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
1426 }
1427
1428 if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
1430 }
1431
1433}
1434
1436{
1438 if (!gc)
1439 return;
1440
1441 if (!gc->paintOrder.isEmpty() && gc->paintOrder != "inherit") {
1442 QStringList paintOrder = gc->paintOrder.split(" ");
1444 Q_FOREACH(const QString p, paintOrder) {
1445 if (p == "fill") {
1446 order.append(KoShape::Fill);
1447 } else if (p == "stroke") {
1448 order.append(KoShape::Stroke);
1449 } else if (p == "markers") {
1450 order.append(KoShape::Markers);
1451 }
1452 }
1453 if (paintOrder.size() == 1 && order.isEmpty()) { // Normal
1455 }
1456 if (order.size() == 1) {
1457 if (order.first() == KoShape::Fill) {
1459 } else if (order.first() == KoShape::Stroke) {
1461 } else if (order.first() == KoShape::Markers) {
1463 }
1464 } else if (order.size() > 1) {
1465 shape->setPaintOrder(order.at(0), order.at(1));
1466 }
1467 }
1468}
1469
1470void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1471{
1473 if (! gc)
1474 return;
1475
1476 if (gc->clipPathId.isEmpty())
1477 return;
1478
1479 SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
1480 if (!clipPath || clipPath->isEmpty())
1481 return;
1482
1484
1485 Q_FOREACH (KoShape *item, clipPath->shapes()) {
1486 KoShape *clonedShape = item->cloneShape();
1487 KIS_ASSERT_RECOVER(clonedShape) { continue; }
1488
1489 shapes.append(clonedShape);
1490 }
1491
1492 if (!shapeToOriginalUserCoordinates.isNull()) {
1493 const QTransform t =
1494 QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
1495 shapeToOriginalUserCoordinates.y());
1496
1497 Q_FOREACH(KoShape *s, shapes) {
1499 }
1500 }
1501
1502 KoClipPath *clipPathObject = new KoClipPath(shapes,
1505 shape->setClipPath(clipPathObject);
1506}
1507
1508void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
1509{
1511 if (!gc)
1512 return;
1513
1514 if (gc->clipMaskId.isEmpty())
1515 return;
1516
1517
1518 QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
1519 if (!originalClipMask || originalClipMask->isEmpty()) return;
1520
1521 KoClipMask *clipMask = originalClipMask->clone();
1522
1523 clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
1524
1525 shape->setClipMask(clipMask);
1526}
1527
1528KoShape* SvgParser::parseUse(const QDomElement &e, DeferredUseStore* deferredUseStore)
1529{
1530 QString href = e.attribute("xlink:href");
1531 if (href.isEmpty())
1532 return 0;
1533
1534 QString key = href.mid(1);
1535 const bool gotDef = m_context.hasDefinition(key);
1536 if (gotDef) {
1537 return resolveUse(e, key);
1538 } else if (deferredUseStore) {
1539 deferredUseStore->add(&e, key);
1540 return 0;
1541 }
1542 debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
1543 << key;
1544 return 0;
1545}
1546
1547KoShape* SvgParser::resolveUse(const QDomElement &e, const QString& key)
1548{
1549 KoShape *result = 0;
1550
1552
1553 // TODO: parse 'width' and 'height' as well
1554 gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
1555
1556 const QDomElement &referencedElement = m_context.definition(key);
1557 result = parseGroup(e, referencedElement, false);
1558
1560 return result;
1561}
1562
1564{
1565 m_shapes += shapes;
1566
1567 if (!group || shapes.isEmpty())
1568 return;
1569
1570 // not normalized
1571 KoShapeGroupCommand cmd(group, shapes, false);
1572 cmd.redo();
1573}
1574
1575QList<KoShape*> SvgParser::parseSvg(const QDomElement &e, QSizeF *fragmentSize)
1576{
1577 // check if we are the root svg element
1578 const bool isRootSvg = m_context.isRootContext();
1579
1580 // parse 'transform' field if preset
1582
1583 applyStyle(0, e, QPointF());
1584
1585 const QString w = e.attribute("width");
1586 const QString h = e.attribute("height");
1587
1588 qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
1589 qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
1590
1591 if (w.isEmpty() || h.isEmpty()) {
1592 QRectF viewRect;
1593 QTransform viewTransform_unused;
1594 QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
1595
1596 if (SvgUtil::parseViewBox(e, fakeBoundingRect,
1597 &viewRect, &viewTransform_unused)) {
1598
1599 QSizeF estimatedSize = viewRect.size();
1600
1601 if (estimatedSize.isValid()) {
1602
1603 if (!w.isEmpty()) {
1604 estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
1605 } else if (!h.isEmpty()) {
1606 estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
1607 }
1608
1609 width = estimatedSize.width();
1610 height = estimatedSize.height();
1611 }
1612 }
1613 }
1614
1615 QSizeF svgFragmentSize(QSizeF(width, height));
1616
1617 if (fragmentSize) {
1618 *fragmentSize = svgFragmentSize;
1619 }
1620
1621 gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
1622
1623 if (!isRootSvg) {
1624 // x and y attribute has no meaning for outermost svg elements
1625 const qreal x = parseUnit(e.attribute("x", "0"));
1626 const qreal y = parseUnit(e.attribute("y", "0"));
1627
1628 QTransform move = QTransform::fromTranslate(x, y);
1629 gc->matrix = move * gc->matrix;
1630 }
1631
1639 gc->pixelsPerInch = 96.0;
1640
1642
1644
1645 // First find the metadata
1646 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1647 QDomElement b = n.toElement();
1648 if (b.isNull())
1649 continue;
1650
1651 if (b.tagName() == "title") {
1652 m_documentTitle = b.text().trimmed();
1653 }
1654 else if (b.tagName() == "desc") {
1655 m_documentDescription = b.text().trimmed();
1656 }
1657 else if (b.tagName() == "metadata") {
1658 // TODO: parse the metadata
1659 }
1660 }
1661
1662
1663 // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
1664 // and as mother makes them -- if mother is inkscape.
1665 if (gc->currentBoundingBox.normalized().isValid()) {
1667 }
1668
1670
1671 return shapes;
1672}
1673
1674void SvgParser::applyViewBoxTransform(const QDomElement &element)
1675{
1677
1678 QRectF viewRect = gc->currentBoundingBox;
1679 QTransform viewTransform;
1680
1682 &viewRect, &viewTransform)) {
1683
1684 gc->matrix = viewTransform * gc->matrix;
1685 gc->currentBoundingBox = viewRect;
1686 }
1687}
1688
1690{
1692
1693 Q_FOREACH (const KoID &id, m_warnings) {
1694 warnings << id.name();
1695 }
1696
1697 return warnings;
1698}
1699
1704
1706{
1707 return m_documentTitle;
1708}
1709
1711{
1712 return m_documentDescription;
1713}
1714
1719
1720inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
1721{
1722 const QTransform shapeToOriginalUserCoordinates =
1723 shape->absoluteTransformation().inverted() *
1724 coordinateSystemOnLoading;
1725
1726 KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
1727 return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
1728}
1729
1730KoShape* SvgParser::parseGroup(const QDomElement &b, const QDomElement &overrideChildrenFrom, bool createContext)
1731{
1732 if (createContext) {
1734 }
1735
1736 KoShapeGroup *group = new KoShapeGroup();
1737 group->setZIndex(m_context.nextZIndex());
1738
1739 // groups should also have their own coordinate system!
1741 const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
1742
1744
1745 QList<KoShape*> childShapes;
1746
1747 if (!overrideChildrenFrom.isNull()) {
1748 // we upload styles from both: <use> and <defs>
1749 uploadStyleToContext(overrideChildrenFrom);
1750 if (overrideChildrenFrom.tagName() == "symbol") {
1751 childShapes = {parseGroup(overrideChildrenFrom)};
1752 } else {
1753 childShapes = parseSingleElement(overrideChildrenFrom, 0);
1754 }
1755 } else {
1756 childShapes = parseContainer(b);
1757 }
1758
1759 // handle id
1760 applyId(b.attribute("id"), group);
1761
1762 addToGroup(childShapes, group);
1763
1764 applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
1765
1766 parseMetadataApplyToShape(b, group);
1767
1768 if (createContext) {
1770 }
1771
1772 return group;
1773}
1774
1775QDomText SvgParser::getTheOnlyTextChild(const QDomElement &e)
1776{
1777 QDomNode firstChild = e.firstChild();
1778 return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
1779 firstChild.toText() : QDomText();
1780}
1781
1782KoShape* SvgParser::getTextPath(const QDomElement &e) {
1783 if (e.hasAttribute("path")) {
1784 QDomElement p = e.ownerDocument().createElement("path");
1785 p.setAttribute("d", e.attribute("path"));
1786 KoShape *s = createPath(p);
1787 return s;
1788 } else {
1789 QString pathId;
1790 if (e.hasAttribute("href")) {
1791 pathId = e.attribute("href").remove(0, 1);
1792 } else if (e.hasAttribute("xlink:href")) {
1793 pathId = e.attribute("xlink:href").remove(0, 1);
1794 }
1795 if (!pathId.isNull()) {
1796 KoShape *s = m_context.shapeById(pathId);
1797 if (s) {
1798 const QTransform absTf = s->absoluteTransformation();
1799 KoShape *cloned = s->cloneShape();
1800 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
1801 return cloned;
1802 }
1803 }
1804 }
1805 return nullptr;
1806}
1807
1808void SvgParser::parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader) {
1809 QDomText t = getTheOnlyTextChild(e);
1810 if (!t.isNull()) {
1811 textLoader.loadSvgText(t, m_context);
1812 } else {
1813 textLoader.enterNodeSubtree();
1814 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1815 QDomElement b = n.toElement();
1816 if (b.tagName() == "title" || b.tagName() == "desc") continue;
1817 textLoader.nextNode();
1818 if (b.isNull()) {
1819 textLoader.loadSvgText(n.toText(), m_context);
1820 KoShape *styleDummy = new KoPathShape();
1821 applyCurrentBasicStyle(styleDummy);
1822 textLoader.setStyleInfo(styleDummy);
1823 } else {
1826 textLoader.loadSvg(b, m_context);
1827 if (b.hasChildNodes()) {
1828 parseTextChildren(b, textLoader);
1829 }
1832 }
1833 }
1834 textLoader.leaveNodeSubtree();
1835 }
1836 KoShape *styleDummy = new KoPathShape();
1837 applyCurrentBasicStyle(styleDummy);
1838 textLoader.setStyleInfo(styleDummy);
1839}
1840
1841KoShape *SvgParser::parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape)
1842{
1843 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan" || e.tagName() == "textPath", 0);
1845 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
1846
1847 KoSvgTextShape *rootTextShape = 0;
1848
1849 if (mergeIntoShape) {
1850 rootTextShape = mergeIntoShape;
1851 } else {
1852 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
1853 rootTextShape = dynamic_cast<KoSvgTextShape*>(factory->createDefaultShape(m_documentResourceManager));
1854 }
1855 KoSvgTextLoader textLoader(rootTextShape);
1856
1857 if (rootTextShape) {
1858 m_isInsideTextSubtree = true;
1859 }
1860
1863
1864 if (rootTextShape) {
1865 if (!m_context.currentGC()->shapeInsideValue.isEmpty()) {
1867 rootTextShape->setShapesInside(shapesInside);
1868 }
1869
1870 if (!m_context.currentGC()->shapeSubtractValue.isEmpty()) {
1872 rootTextShape->setShapesSubtract(shapesSubtract);
1873 }
1874 }
1875
1876 if (e.hasAttribute("krita:textVersion")) {
1877 m_context.currentGC()->textProperties.setProperty(KoSvgTextProperties::KraTextVersionId, e.attribute("krita:textVersion", "1").toInt());
1878
1880 debugFlake << "WARNING: \"krita:textVersion\" attribute appeared in non-root text shape";
1881 }
1882 }
1883
1884 parseMetadataApplyToShape(e, rootTextShape);
1885
1886 if (!mergeIntoShape) {
1887 rootTextShape->setZIndex(m_context.nextZIndex());
1888 }
1889
1890
1893
1894 static const KoID warning("warn_text_version_1",
1895 i18nc("warning while loading SVG text",
1896 "The document has vector text created "
1897 "in Krita 4.x. When you save the document, "
1898 "the text object will be converted into "
1899 "Krita 5 format that will no longer be "
1900 "compatible with Krita 4.x"));
1901
1902 if (!m_warnings.contains(warning)) {
1903 m_warnings << warning;
1904 }
1905 }
1906
1908
1909 // 1) apply transformation only in case we are not overriding the shape!
1910 // 2) the transformation should be applied *before* the shape is added to the group!
1911 if (!mergeIntoShape) {
1912 // groups should also have their own coordinate system!
1914 const QPointF extraOffset = extraShapeOffset(rootTextShape, m_context.currentGC()->matrix);
1915
1916 // handle id
1917 applyId(e.attribute("id"), rootTextShape);
1918 applyCurrentStyle(rootTextShape, extraOffset); // apply style to this group after size is set
1919 } else {
1920 m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation();
1921 applyCurrentBasicStyle(rootTextShape);
1922 }
1923
1924 QDomText onlyTextChild = getTheOnlyTextChild(e);
1925 if (!onlyTextChild.isNull()) {
1926 textLoader.loadSvgText(onlyTextChild, m_context);
1927
1928 } else {
1929 parseTextChildren(e, textLoader);
1930
1931 }
1932
1934
1935 m_isInsideTextSubtree = false;
1936
1937 //rootTextShape->debugParsing();
1938 rootTextShape->relayout();
1939
1940
1941 return rootTextShape;
1942}
1943
1945{
1947
1948 // are we parsing a switch container
1949 bool isSwitch = e.tagName() == "switch";
1950
1951 DeferredUseStore deferredUseStore(this);
1952
1953 for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
1954 QDomElement b = n.toElement();
1955 if (b.isNull()) {
1956 continue;
1957 }
1958
1959 if (isSwitch) {
1960 // if we are parsing a switch check the requiredFeatures, requiredExtensions
1961 // and systemLanguage attributes
1962 // TODO: evaluate feature list
1963 if (b.hasAttribute("requiredFeatures")) {
1964 continue;
1965 }
1966 if (b.hasAttribute("requiredExtensions")) {
1967 // we do not support any extensions
1968 continue;
1969 }
1970 if (b.hasAttribute("systemLanguage")) {
1971 // not implemented yet
1972 }
1973 }
1974
1975 QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
1976 shapes.append(currentShapes);
1977
1978 // if we are parsing a switch, stop after the first supported element
1979 if (isSwitch && !currentShapes.isEmpty())
1980 break;
1981 }
1982 return shapes;
1983}
1984
1985void SvgParser::parseDefsElement(const QDomElement &e)
1986{
1987 KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
1989}
1990
1992{
1994
1995 // save definition for later instantiation with 'use'
1997 if (deferredUseStore) {
1998 deferredUseStore->checkPendingUse(b, shapes);
1999 }
2000
2001 if (b.tagName() == "svg") {
2002 shapes += parseSvg(b);
2003 } else if (b.tagName() == "g" || b.tagName() == "a") {
2004 // treat svg link <a> as group so we don't miss its child elements
2005 shapes += parseGroup(b);
2006 } else if (b.tagName() == "symbol") {
2007 parseSymbol(b);
2008 } else if (b.tagName() == "switch") {
2010 shapes += parseContainer(b);
2012 } else if (b.tagName() == "defs") {
2013 if (b.childNodes().count() > 0) {
2019 KoShape *defsShape = parseGroup(b);
2020 defsShape->setVisible(false);
2021 m_defsShapes << defsShape; // TODO: where to delete the shape!?
2022
2023 }
2024 } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
2025 } else if (b.tagName() == "pattern") {
2026 } else if (b.tagName() == "filter") {
2027 parseFilter(b);
2028 } else if (b.tagName() == "clipPath") {
2029 parseClipPath(b);
2030 } else if (b.tagName() == "mask") {
2031 parseClipMask(b);
2032 } else if (b.tagName() == "marker") {
2033 parseMarker(b);
2034 } else if (b.tagName() == "style") {
2036 } else if (b.tagName() == "text" || b.tagName() == "tspan" || b.tagName() == "textPath") {
2038 } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline"
2039 || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image") {
2040 KoShape *shape = createObjectDirect(b);
2041
2042 if (shape) {
2043 if (!shape->outlineRect().isNull() || !shape->boundingRect().isNull()) {
2044 shapes.append(shape);
2045 } else {
2046 debugFlake << "WARNING: shape is totally empty!" << shape->shapeId() << ppVar(shape->outlineRect());
2047 debugFlake << " " << shape->shapeId() << ppVar(shape->outline());
2048 {
2049 QString string;
2050 QTextStream stream(&string);
2052 stream << b;
2053 debugFlake << " " << string;
2054 }
2055 delete shape;
2056 }
2057 }
2058 } else if (b.tagName() == "use") {
2059 KoShape* s = parseUse(b, deferredUseStore);
2060 if (s) {
2061 shapes += s;
2062 }
2063 } else if (b.tagName() == "color-profile") {
2065 } else {
2066 // this is an unknown element, so try to load it anyway
2067 // there might be a shape that handles that element
2068 KoShape *shape = createObject(b);
2069 if (shape) {
2070 shapes.append(shape);
2071 }
2072 }
2073
2074 return shapes;
2075}
2076
2077// Creating functions
2078// ---------------------------------------------------------------------------------------
2079
2080KoShape * SvgParser::createPath(const QDomElement &element)
2081{
2082 KoShape *obj = 0;
2083 if (element.tagName() == "line") {
2084 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
2085 if (path) {
2086 double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
2087 double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
2088 double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
2089 double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
2090 path->clear();
2091 path->moveTo(QPointF(x1, y1));
2092 path->lineTo(QPointF(x2, y2));
2093 path->normalize();
2094 obj = path;
2095 }
2096 } else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
2097 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
2098 if (path) {
2099 path->clear();
2100
2101 bool bFirst = true;
2102 QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
2103 for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
2104 QPointF point;
2106 ++it;
2107 if (it == pointList.end())
2108 break;
2110 if (bFirst) {
2111 path->moveTo(point);
2112 bFirst = false;
2113 } else
2114 path->lineTo(point);
2115 }
2116 if (element.tagName() == "polygon")
2117 path->close();
2118
2119 path->setPosition(path->normalize());
2120
2121 obj = path;
2122 }
2123 } else if (element.tagName() == "path") {
2124 KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
2125 if (path) {
2126 path->clear();
2127
2128 KoPathShapeLoader loader(path);
2129 loader.parseSvg(element.attribute("d"), true);
2130 path->setPosition(path->normalize());
2131
2132 QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
2133 SvgUtil::fromUserSpace(path->position().y()));
2134 QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
2135 SvgUtil::fromUserSpace(path->size().height()));
2136
2137 path->setSize(newSize);
2138 path->setPosition(newPosition);
2139
2140 if (element.hasAttribute("sodipodi:nodetypes")) {
2141 path->loadNodeTypes(element.attribute("sodipodi:nodetypes"));
2142 }
2143 obj = path;
2144 }
2145 }
2146
2147 return obj;
2148}
2149
2151{
2154
2156 if (obj) {
2158 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2159
2160 applyCurrentStyle(obj, extraOffset);
2161
2162 // handle id
2163 applyId(b.attribute("id"), obj);
2166 }
2167
2169
2170 if (obj) {
2172 }
2173 return obj;
2174}
2175
2176KoShape * SvgParser::createObject(const QDomElement &b, const SvgStyles &style)
2177{
2179
2181 if (obj) {
2183 const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
2184
2185 SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
2186 m_context.styleParser().parseFont(objStyle);
2187 applyStyle(obj, objStyle, extraOffset);
2188
2189 // handle id
2190 applyId(b.attribute("id"), obj);
2193 }
2194
2196
2197 if (obj) {
2199 }
2200
2201 return obj;
2202}
2203
2204KoShape * SvgParser::createShapeFromElement(const QDomElement &element, SvgLoadingContext &context)
2205{
2206 KoShape *object = 0;
2207
2208
2209 const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
2211
2212 foreach (KoShapeFactoryBase *f, factories) {
2213 KoShape *shape = f->createDefaultShape(m_documentResourceManager);
2214 if (!shape)
2215 continue;
2216
2217 SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
2218 if (!svgShape) {
2219 delete shape;
2220 continue;
2221 }
2222
2223 // reset transformation that might come from the default shape
2224 shape->setTransformation(QTransform());
2225
2226 // reset border
2227 KoShapeStrokeModelSP oldStroke = shape->stroke();
2229
2230 // reset fill
2232
2233 if (!svgShape->loadSvg(element, context)) {
2234 delete shape;
2235 continue;
2236 }
2237
2238 object = shape;
2239 break;
2240 }
2241
2242 if (!object) {
2243 object = createPath(element);
2244 }
2245
2246 return object;
2247}
2248
2249KoShape *SvgParser::createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context)
2250{
2251 if (value.isEmpty()) {
2252 return 0;
2253 }
2254 unsigned int start = value.indexOf('(') + 1;
2255 unsigned int end = value.indexOf(')', start);
2256
2257 QString val = value.mid(start, end - start);
2258 QString fillRule;
2259 if (val.startsWith("evenodd,")) {
2260 start += QString("evenodd,").size();
2261 fillRule = "evenodd";
2262 } else if (val.startsWith("nonzero,")) {
2263 start += QString("nonzero,").size();
2264 fillRule = "nonzero";
2265 }
2266 val = value.mid(start, end - start);
2267
2268 QDomElement el;
2269 if (value.startsWith("url(")) {
2270 start = value.indexOf('#') + 1;
2271 KoShape *s = m_context.shapeById(value.mid(start, end - start));
2272 if (s) {
2273 const QTransform absTf = s->absoluteTransformation();
2274 KoShape *cloned = s->cloneShape();
2275 cloned->setTransformation(absTf * m_shapeParentTransform.value(s).inverted());
2276 return cloned;
2277 }
2278 } else if (value.startsWith("circle(")) {
2279 el = e.ownerDocument().createElement("circle");
2280 QStringList params = val.split(" ");
2281 el.setAttribute("r", SvgUtil::parseUnitXY(context.currentGC(), context.resolvedProperties(), params.first()));
2282 if (params.contains("at")) {
2283 // 1 == "at"
2284 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(2)));
2285 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(3)));
2286 }
2287 } else if (value.startsWith("ellipse(")) {
2288 el = e.ownerDocument().createElement("ellipse");
2289 QStringList params = val.split(" ");
2290 el.setAttribute("rx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(0)));
2291 el.setAttribute("ry", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(1)));
2292 if (params.contains("at")) {
2293 // 2 == "at"
2294 el.setAttribute("cx", SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), params.at(3)));
2295 el.setAttribute("cy", SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), params.at(4)));
2296 }
2297 } else if (value.startsWith("polygon(")) {
2298 el = e.ownerDocument().createElement("polygon");
2299 QStringList points;
2300 Q_FOREACH(QString point, SvgUtil::simplifyList(val)) {
2301 bool xVal = points.size() % 2;
2302 if (xVal) {
2303 points.append(QString::number(SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), point)));
2304 } else {
2305 points.append(QString::number(SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), point)));
2306 }
2307 }
2308 el.setAttribute("points", points.join(" "));
2309 } else if (value.startsWith("path(")) {
2310 el = e.ownerDocument().createElement("path");
2311 // SVG path data is inside a string.
2312 start += 1;
2313 end -= 1;
2314 el.setAttribute("d", value.mid(start, end - start));
2315 }
2316
2317 el.setAttribute("fill-rule", fillRule);
2318 return createShapeFromElement(el, context);
2319}
2320
2322{
2323 QList<KoShape*> shapeList;
2324 if (value == "auto" || value == "none") {
2325 return shapeList;
2326 }
2327 QStringList params = value.split(")");
2328 Q_FOREACH(const QString param, params) {
2329 KoShape *s = createShapeFromCSS(e, param.trimmed()+")", context);
2330 if (s) {
2331 shapeList.append(s);
2332 }
2333 }
2334 return shapeList;
2335}
2336
2337KoShape *SvgParser::createShape(const QString &shapeID)
2338{
2339 KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
2340 if (!factory) {
2341 debugFlake << "Could not find factory for shape id" << shapeID;
2342 return 0;
2343 }
2344
2346 if (!shape) {
2347 debugFlake << "Could not create Default shape for shape id" << shapeID;
2348 return 0;
2349 }
2350 if (shape->shapeId().isEmpty()) {
2351 shape->setShapeId(factory->id());
2352 }
2353
2354 // reset transformation that might come from the default shape
2355 shape->setTransformation(QTransform());
2356
2357 // reset border
2358 // ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
2360
2361 // reset fill
2363
2364 return shape;
2365}
2366
2367void SvgParser::applyId(const QString &id, KoShape *shape)
2368{
2369 if (id.isEmpty())
2370 return;
2371
2372 KoShape *existingShape = m_context.shapeById(id);
2373 if (existingShape) {
2374 debugFlake << "SVG contains nodes with duplicated id:" << id;
2375 // Generate a random name and just don't register the shape.
2376 // We don't use the name as a unique identifier so we don't need to
2377 // worry about the extremely rare case of name collision.
2378 const QString suffix = QString::number(QRandomGenerator::system()->bounded(0x10000000, 0x7FFFFFFF), 16);
2379 const QString newName = id + '_' + suffix;
2380 shape->setName(newName);
2381 } else {
2382 shape->setName(id);
2383 m_context.registerShape(id, shape);
2384 }
2385}
#define debugFlake
Definition FlakeDebug.h:15
float value(const T *src, size_t ch)
const Params2D p
QSharedPointer< KoShapeStrokeModel > KoShapeStrokeModelSP
#define KoPathShapeId
Definition KoPathShape.h:20
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.
This class provides a loading context for filter effects.
void enableFilterPrimitiveUnitsConversion(bool enable)
Enables conversion of filter primitive units.
void enableFilterUnitsConversion(bool enable)
Enables conversion of filter units.
void setShapeBoundingBox(const QRectF &shapeBound)
KoFilterEffect * createFilterEffectFromXml(const QDomElement &element, const KoFilterEffectLoadingContext &context)
static KoFilterEffectRegistry * instance()
This class manages a stack of filter effects.
void setClipRect(const QRectF &clipRect)
Sets the clipping rectangle used for this filter in bounding box units.
void appendFilterEffect(KoFilterEffect *filter)
void setOutput(const QString &output)
QList< QString > inputs
void setFilterRect(const QRectF &filterRect)
Sets the region the filter is applied to in bounding box units.
void setInput(int index, const QString &input)
Sets an existing input to a new value.
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:749
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:820
void setName(const QString &name)
Definition KoShape.cpp:1155
virtual QRectF outlineRect() const
Definition KoShape.cpp:637
void setInheritBackground(bool value)
setInheritBackground marks a shape as inheriting the background from the parent shape....
Definition KoShape.cpp:940
void setZIndex(qint16 zIndex)
Definition KoShape.cpp:954
QString shapeId() const
Definition KoShape.cpp:1057
virtual QPainterPath outline() const
Definition KoShape.cpp:630
void setClipMask(KoClipMask *clipMask)
Sets a new clip mask, removing the old one. The mask is owned by the shape.
Definition KoShape.cpp:1133
void setTransparency(qreal transparency)
Definition KoShape.cpp:722
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:784
void setFilterEffectStack(KoFilterEffectStack *filterEffectStack)
Sets the new filter effect stack, removing the old one.
Definition KoShape.cpp:1299
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:1067
void applyAbsoluteTransformation(const QTransform &matrix)
Definition KoShape.cpp:400
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:335
virtual void setStroke(KoShapeStrokeModelSP stroke)
Definition KoShape.cpp:1081
void setAdditionalAttribute(const QString &name, const QString &value)
Definition KoShape.cpp:1264
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
void setTransformation(const QTransform &matrix)
Definition KoShape.cpp:417
virtual void setBackground(QSharedPointer< KoShapeBackground > background)
Definition KoShape.cpp:918
void setClipPath(KoClipPath *clipPath)
Sets a new clip path, removing the old one.
Definition KoShape.cpp:1121
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:200
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:424
void setInheritStroke(bool value)
setInheritStroke marks a shape as inheriting the stroke from the parent shape. NOTE: The currently se...
Definition KoShape.cpp:1090
@ Stroke
Definition KoShape.h:147
@ Markers
Definition KoShape.h:148
void setVisible(bool on)
Definition KoShape.cpp:972
void setShapeId(const QString &id)
Definition KoShape.cpp:1062
QPointF position() const
Get the position of the shape in pt.
Definition KoShape.cpp:825
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
void relayout() const
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)
void setPosition(const QPointF &position)
Sets filter position.
KoFlake::CoordinateSystem filterUnits() const
Returns the filter units type.
void setContent(const QDomElement &content)
Sets the dom element containing the filter.
QSizeF size(const QRectF &objectBound) const
Returns filter size (objectBound is used when filterUnits == ObjectBoundingBox)
void setFilterUnits(KoFlake::CoordinateSystem filterUnits)
Set the filter units type.
QDomElement content() const
Return the filer element.
void setPrimitiveUnits(KoFlake::CoordinateSystem primitiveUnits)
Set the filter primitive units type.
QPointF position(const QRectF &objectBound) const
Returns filter position (objectBound is used when filterUnits == ObjectBoundingBox)
KoFlake::CoordinateSystem primitiveUnits() const
Returns the filter primitive units type.
void setSize(const QSizeF &size)
Sets filter size.
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.
QTransform viewboxTransform
view box transformation
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 filterId
the current filter id
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 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:241
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:232
bool m_inheritStrokeFillByDefault
Definition SvgParser.h:244
void applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
Applies the current clip path to the object.
bool m_isInsideTextSubtree
Definition SvgParser.h:239
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:240
QVector< KoSvgSymbol * > takeSymbols()
QList< KoShape * > m_defsShapes
Definition SvgParser.h:238
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:237
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:234
KoShape * parseTextElement(const QDomElement &e, KoSvgTextShape *mergeIntoShape=0)
QMap< QString, SvgFilterHelper > m_filters
Definition SvgParser.h:231
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
SvgFilterHelper * findFilter(const QString &id, const QString &href=QString())
find filter with given id in filter map
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:235
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.
void applyFilter(KoShape *shape)
Applies the current filter to the object.
std::function< QByteArray(const QString &) FileFetcherFunc)
Definition SvgParser.h:82
KoShape * parseUse(const QDomElement &, DeferredUseStore *deferredUseStore)
Parses a use element, returning a list of child shapes.
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:245
KoShape * createObjectDirect(const QDomElement &b)
SvgClipPathHelper * findClipPath(const QString &id)
find clip path with given id in clip path map
KoShape * getTextPath(const QDomElement &e)
Get the path for the gives textPath element.
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:243
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:236
void applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
SvgLoadingContext m_context
Definition SvgParser.h:229
KoShape * createShapeFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context)
Creates a shape from a CSS shapes definition.
QVector< KoID > m_warnings
Definition SvgParser.h:242
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:233
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
QList< KoShape * > createListOfShapesFromCSS(const QDomElement e, const QString value, SvgLoadingContext &context)
Create a list of shapes from a CSS shapes definition with potentially multiple shapes.
void setFillStrokeInheritByDefault(const bool enable)
bool parseFilter(const QDomElement &, const QDomElement &referencedBy=QDomElement())
Parses a filter element.
KoShape * resolveUse(const QDomElement &e, const QString &key)
void parseDefsElement(const QDomElement &e)
void parseTextChildren(const QDomElement &e, KoSvgTextLoader &textLoader)
parse children of a <text > element into the root shape.
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:230
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 QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound)
Definition SvgUtil.cpp:86
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:73
const QDomElement * m_useElement
Definition SvgParser.cpp:76
void checkPendingUse(const QDomElement &b, QList< KoShape * > &shapes)
Definition SvgParser.cpp:90
DeferredUseStore(SvgParser *p)
Definition SvgParser.cpp:79
void add(const QDomElement *useE, const QString &key)
Definition SvgParser.cpp:83
std::vector< El > m_uses