1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qplatformdefs.h"
5
6#include "qsvghandler_p.h"
7
8#include "qsvgtinydocument_p.h"
9#include "qsvgstructure_p.h"
10#include "qsvggraphics_p.h"
11#include "qsvgfilter_p.h"
12#include "qsvgnode_p.h"
13#include "qsvgfont_p.h"
14
15#include "qpen.h"
16#include "qpainterpath.h"
17#include "qbrush.h"
18#include "qcolor.h"
19#include "qtextformat.h"
20#include "qlist.h"
21#include "qfileinfo.h"
22#include "qfile.h"
23#include "qdir.h"
24#include "qdebug.h"
25#include "qmath.h"
26#include "qnumeric.h"
27#include <qregularexpression.h>
28#include "qtransform.h"
29#include "qvarlengtharray.h"
30#include "private/qmath_p.h"
31#include "qimagereader.h"
32
33#include "float.h"
34#include <cmath>
35
36QT_BEGIN_NAMESPACE
37
38Q_LOGGING_CATEGORY(lcSvgHandler, "qt.svg")
39
40static const char *qt_inherit_text = "inherit";
41#define QT_INHERIT QLatin1String(qt_inherit_text)
42
43static QByteArray prefixMessage(const QByteArray &msg, const QXmlStreamReader *r)
44{
45 QByteArray result;
46 if (r) {
47 if (const QFile *file = qobject_cast<const QFile *>(object: r->device()))
48 result.append(a: QFile::encodeName(fileName: QDir::toNativeSeparators(pathName: file->fileName())));
49 else
50 result.append(QByteArrayLiteral("<input>"));
51 result.append(c: ':');
52 result.append(a: QByteArray::number(r->lineNumber()));
53 if (const qint64 column = r->columnNumber()) {
54 result.append(c: ':');
55 result.append(a: QByteArray::number(column));
56 }
57 result.append(QByteArrayLiteral(": "));
58 }
59 result.append(a: msg);
60 return result;
61}
62
63static inline QByteArray msgProblemParsing(const QString &localName, const QXmlStreamReader *r)
64{
65 return prefixMessage(QByteArrayLiteral("Problem parsing ") + localName.toLocal8Bit(), r);
66}
67
68static inline QByteArray msgCouldNotResolveProperty(const QString &id, const QXmlStreamReader *r)
69{
70 return prefixMessage(QByteArrayLiteral("Could not resolve property: ") + id.toLocal8Bit(), r);
71}
72
73// ======== duplicated from qcolor_p
74
75static inline int qsvg_h2i(char hex, bool *ok = nullptr)
76{
77 if (hex >= '0' && hex <= '9')
78 return hex - '0';
79 if (hex >= 'a' && hex <= 'f')
80 return hex - 'a' + 10;
81 if (hex >= 'A' && hex <= 'F')
82 return hex - 'A' + 10;
83 if (ok)
84 *ok = false;
85 return -1;
86}
87
88static inline int qsvg_hex2int(const char *s, bool *ok = nullptr)
89{
90 return (qsvg_h2i(hex: s[0], ok) * 16) | qsvg_h2i(hex: s[1], ok);
91}
92
93static inline int qsvg_hex2int(char s, bool *ok = nullptr)
94{
95 int h = qsvg_h2i(hex: s, ok);
96 return (h * 16) | h;
97}
98
99bool qsvg_get_hex_rgb(const char *name, QRgb *rgb)
100{
101 if(name[0] != '#')
102 return false;
103 name++;
104 const size_t len = qstrlen(str: name);
105 int r, g, b;
106 bool ok = true;
107 if (len == 12) {
108 r = qsvg_hex2int(s: name, ok: &ok);
109 g = qsvg_hex2int(s: name + 4, ok: &ok);
110 b = qsvg_hex2int(s: name + 8, ok: &ok);
111 } else if (len == 9) {
112 r = qsvg_hex2int(s: name, ok: &ok);
113 g = qsvg_hex2int(s: name + 3, ok: &ok);
114 b = qsvg_hex2int(s: name + 6, ok: &ok);
115 } else if (len == 6) {
116 r = qsvg_hex2int(s: name, ok: &ok);
117 g = qsvg_hex2int(s: name + 2, ok: &ok);
118 b = qsvg_hex2int(s: name + 4, ok: &ok);
119 } else if (len == 3) {
120 r = qsvg_hex2int(s: name[0], ok: &ok);
121 g = qsvg_hex2int(s: name[1], ok: &ok);
122 b = qsvg_hex2int(s: name[2], ok: &ok);
123 } else {
124 r = g = b = -1;
125 }
126 if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255 || !ok) {
127 *rgb = 0;
128 return false;
129 }
130 *rgb = qRgb(r, g ,b);
131 return true;
132}
133
134bool qsvg_get_hex_rgb(const QChar *str, int len, QRgb *rgb)
135{
136 if (len > 13)
137 return false;
138 char tmp[16];
139 for(int i = 0; i < len; ++i)
140 tmp[i] = str[i].toLatin1();
141 tmp[len] = 0;
142 return qsvg_get_hex_rgb(name: tmp, rgb);
143}
144
145// ======== end of qcolor_p duplicate
146
147static bool parsePathDataFast(QStringView data, QPainterPath &path, bool limitLength = true);
148
149static inline QString someId(const QXmlStreamAttributes &attributes)
150{
151 QString id = attributes.value(qualifiedName: QLatin1String("id")).toString();
152 if (id.isEmpty())
153 id = attributes.value(qualifiedName: QLatin1String("xml:id")).toString();
154 return id;
155}
156
157struct QSvgAttributes
158{
159 QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler);
160
161 QString id;
162
163 QStringView color;
164 QStringView colorOpacity;
165 QStringView fill;
166 QStringView fillRule;
167 QStringView fillOpacity;
168 QStringView stroke;
169 QStringView strokeDashArray;
170 QStringView strokeDashOffset;
171 QStringView strokeLineCap;
172 QStringView strokeLineJoin;
173 QStringView strokeMiterLimit;
174 QStringView strokeOpacity;
175 QStringView strokeWidth;
176 QStringView vectorEffect;
177 QStringView fontFamily;
178 QStringView fontSize;
179 QStringView fontStyle;
180 QStringView fontWeight;
181 QStringView fontVariant;
182 QStringView textAnchor;
183 QStringView transform;
184 QStringView visibility;
185 QStringView opacity;
186 QStringView compOp;
187 QStringView display;
188 QStringView offset;
189 QStringView stopColor;
190 QStringView stopOpacity;
191 QStringView imageRendering;
192 QStringView mask;
193 QStringView markerStart;
194 QStringView markerMid;
195 QStringView markerEnd;
196 QStringView filter;
197
198
199#ifndef QT_NO_CSSPARSER
200 QList<QSvgCssAttribute> m_cssAttributes;
201#endif
202};
203
204QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler)
205{
206 for (int i = 0; i < xmlAttributes.size(); ++i) {
207 const QXmlStreamAttribute &attribute = xmlAttributes.at(i);
208 QStringView name = attribute.qualifiedName();
209 if (name.isEmpty())
210 continue;
211 QStringView value = attribute.value();
212
213 switch (name.at(n: 0).unicode()) {
214
215 case 'c':
216 if (name == QLatin1String("color"))
217 color = value;
218 else if (name == QLatin1String("color-opacity"))
219 colorOpacity = value;
220 else if (name == QLatin1String("comp-op"))
221 compOp = value;
222 break;
223
224 case 'd':
225 if (name == QLatin1String("display"))
226 display = value;
227 break;
228
229 case 'f':
230 if (name == QLatin1String("fill"))
231 fill = value;
232 else if (name == QLatin1String("fill-rule"))
233 fillRule = value;
234 else if (name == QLatin1String("fill-opacity"))
235 fillOpacity = value;
236 else if (name == QLatin1String("font-family"))
237 fontFamily = value;
238 else if (name == QLatin1String("font-size"))
239 fontSize = value;
240 else if (name == QLatin1String("font-style"))
241 fontStyle = value;
242 else if (name == QLatin1String("font-weight"))
243 fontWeight = value;
244 else if (name == QLatin1String("font-variant"))
245 fontVariant = value;
246 else if (name == QLatin1String("filter") &&
247 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
248 filter = value;
249 break;
250
251 case 'i':
252 if (name == QLatin1String("id"))
253 id = value.toString();
254 else if (name == QLatin1String("image-rendering"))
255 imageRendering = value;
256 break;
257
258 case 'm':
259 if (name == QLatin1String("mask") &&
260 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
261 mask = value;
262 if (name == QLatin1String("marker-start") &&
263 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
264 markerStart = value;
265 if (name == QLatin1String("marker-mid") &&
266 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
267 markerMid = value;
268 if (name == QLatin1String("marker-end") &&
269 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
270 markerEnd = value;
271 break;
272
273 case 'o':
274 if (name == QLatin1String("opacity"))
275 opacity = value;
276 if (name == QLatin1String("offset"))
277 offset = value;
278 break;
279
280 case 's':
281 if (name.size() > 5 && name.mid(pos: 1, n: 5) == QLatin1String("troke")) {
282 QStringView strokeRef = name.mid(pos: 6, n: name.size() - 6);
283 if (strokeRef.isEmpty())
284 stroke = value;
285 else if (strokeRef == QLatin1String("-dasharray"))
286 strokeDashArray = value;
287 else if (strokeRef == QLatin1String("-dashoffset"))
288 strokeDashOffset = value;
289 else if (strokeRef == QLatin1String("-linecap"))
290 strokeLineCap = value;
291 else if (strokeRef == QLatin1String("-linejoin"))
292 strokeLineJoin = value;
293 else if (strokeRef == QLatin1String("-miterlimit"))
294 strokeMiterLimit = value;
295 else if (strokeRef == QLatin1String("-opacity"))
296 strokeOpacity = value;
297 else if (strokeRef == QLatin1String("-width"))
298 strokeWidth = value;
299 } else if (name == QLatin1String("stop-color"))
300 stopColor = value;
301 else if (name == QLatin1String("stop-opacity"))
302 stopOpacity = value;
303 break;
304
305 case 't':
306 if (name == QLatin1String("text-anchor"))
307 textAnchor = value;
308 else if (name == QLatin1String("transform"))
309 transform = value;
310 break;
311
312 case 'v':
313 if (name == QLatin1String("vector-effect"))
314 vectorEffect = value;
315 else if (name == QLatin1String("visibility"))
316 visibility = value;
317 break;
318
319 case 'x':
320 if (name == QLatin1String("xml:id") && id.isEmpty())
321 id = value.toString();
322 break;
323
324 default:
325 break;
326 }
327 }
328
329 // If a style attribute is present, let its attribute settings override the plain attribute
330 // values. The spec seems to indicate that, and it is common behavior in svg renderers.
331#ifndef QT_NO_CSSPARSER
332 QStringView style = xmlAttributes.value(qualifiedName: QLatin1String("style"));
333 if (!style.isEmpty()) {
334 handler->parseCSStoXMLAttrs(css: style.toString(), attributes: &m_cssAttributes);
335 for (int j = 0; j < m_cssAttributes.size(); ++j) {
336 const QSvgCssAttribute &attribute = m_cssAttributes.at(i: j);
337 QStringView name = attribute.name;
338 QStringView value = attribute.value;
339 if (name.isEmpty())
340 continue;
341
342 switch (name.at(n: 0).unicode()) {
343
344 case 'c':
345 if (name == QLatin1String("color"))
346 color = value;
347 else if (name == QLatin1String("color-opacity"))
348 colorOpacity = value;
349 else if (name == QLatin1String("comp-op"))
350 compOp = value;
351 break;
352
353 case 'd':
354 if (name == QLatin1String("display"))
355 display = value;
356 break;
357
358 case 'f':
359 if (name == QLatin1String("fill"))
360 fill = value;
361 else if (name == QLatin1String("fill-rule"))
362 fillRule = value;
363 else if (name == QLatin1String("fill-opacity"))
364 fillOpacity = value;
365 else if (name == QLatin1String("font-family"))
366 fontFamily = value;
367 else if (name == QLatin1String("font-size"))
368 fontSize = value;
369 else if (name == QLatin1String("font-style"))
370 fontStyle = value;
371 else if (name == QLatin1String("font-weight"))
372 fontWeight = value;
373 else if (name == QLatin1String("font-variant"))
374 fontVariant = value;
375 else if (name == QLatin1String("filter") &&
376 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
377 filter = value;
378 break;
379
380 case 'i':
381 if (name == QLatin1String("image-rendering"))
382 imageRendering = value;
383 break;
384
385 case 'm':
386 if (name == QLatin1String("mask") &&
387 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
388 mask = value;
389 if (name == QLatin1String("marker-start") &&
390 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
391 markerStart = value;
392 if (name == QLatin1String("marker-mid") &&
393 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
394 markerMid = value;
395 if (name == QLatin1String("marker-end") &&
396 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
397 markerEnd = value;
398 break;
399
400 case 'o':
401 if (name == QLatin1String("opacity"))
402 opacity = value;
403 else if (name == QLatin1String("offset"))
404 offset = value;
405 break;
406
407 case 's':
408 if (name.size() > 5 && name.mid(pos: 1, n: 5) == QLatin1String("troke")) {
409 QStringView strokeRef = name.mid(pos: 6, n: name.size() - 6);
410 if (strokeRef.isEmpty())
411 stroke = value;
412 else if (strokeRef == QLatin1String("-dasharray"))
413 strokeDashArray = value;
414 else if (strokeRef == QLatin1String("-dashoffset"))
415 strokeDashOffset = value;
416 else if (strokeRef == QLatin1String("-linecap"))
417 strokeLineCap = value;
418 else if (strokeRef == QLatin1String("-linejoin"))
419 strokeLineJoin = value;
420 else if (strokeRef == QLatin1String("-miterlimit"))
421 strokeMiterLimit = value;
422 else if (strokeRef == QLatin1String("-opacity"))
423 strokeOpacity = value;
424 else if (strokeRef == QLatin1String("-width"))
425 strokeWidth = value;
426 } else if (name == QLatin1String("stop-color"))
427 stopColor = value;
428 else if (name == QLatin1String("stop-opacity"))
429 stopOpacity = value;
430 break;
431
432 case 't':
433 if (name == QLatin1String("text-anchor"))
434 textAnchor = value;
435 else if (name == QLatin1String("transform"))
436 transform = value;
437 break;
438
439 case 'v':
440 if (name == QLatin1String("vector-effect"))
441 vectorEffect = value;
442 else if (name == QLatin1String("visibility"))
443 visibility = value;
444 break;
445
446 default:
447 break;
448 }
449 }
450 }
451#else
452 Q_UNUSED(handler);
453#endif // QT_NO_CSSPARSER
454}
455
456#ifndef QT_NO_CSSPARSER
457
458class QSvgStyleSelector : public QCss::StyleSelector
459{
460public:
461 QSvgStyleSelector()
462 {
463 nameCaseSensitivity = Qt::CaseInsensitive;
464 }
465 virtual ~QSvgStyleSelector()
466 {
467 }
468
469 inline QString nodeToName(QSvgNode *node) const
470 {
471 return node->typeName();
472 }
473
474 inline QSvgNode *svgNode(NodePtr node) const
475 {
476 return (QSvgNode*)node.ptr;
477 }
478 inline QSvgStructureNode *nodeToStructure(QSvgNode *n) const
479 {
480 if (n &&
481 (n->type() == QSvgNode::Doc ||
482 n->type() == QSvgNode::Group ||
483 n->type() == QSvgNode::Defs ||
484 n->type() == QSvgNode::Switch)) {
485 return (QSvgStructureNode*)n;
486 }
487 return 0;
488 }
489
490 inline QSvgStructureNode *svgStructure(NodePtr node) const
491 {
492 QSvgNode *n = svgNode(node);
493 QSvgStructureNode *st = nodeToStructure(n);
494 return st;
495 }
496
497 bool nodeNameEquals(NodePtr node, const QString& nodeName) const override
498 {
499 QSvgNode *n = svgNode(node);
500 if (!n)
501 return false;
502 QString name = nodeToName(node: n);
503 return QString::compare(s1: name, s2: nodeName, cs: Qt::CaseInsensitive) == 0;
504 }
505 QString attributeValue(NodePtr node, const QCss::AttributeSelector &asel) const override
506 {
507 const QString &name = asel.name;
508 QSvgNode *n = svgNode(node);
509 if ((!n->nodeId().isEmpty() && (name == QLatin1String("id") ||
510 name == QLatin1String("xml:id"))))
511 return n->nodeId();
512 if (!n->xmlClass().isEmpty() && name == QLatin1String("class"))
513 return n->xmlClass();
514 return QString();
515 }
516 bool hasAttributes(NodePtr node) const override
517 {
518 QSvgNode *n = svgNode(node);
519 return (n &&
520 (!n->nodeId().isEmpty() || !n->xmlClass().isEmpty()));
521 }
522
523 QStringList nodeIds(NodePtr node) const override
524 {
525 QSvgNode *n = svgNode(node);
526 QString nid;
527 if (n)
528 nid = n->nodeId();
529 QStringList lst; lst.append(t: nid);
530 return lst;
531 }
532
533 QStringList nodeNames(NodePtr node) const override
534 {
535 QSvgNode *n = svgNode(node);
536 if (n)
537 return QStringList(nodeToName(node: n));
538 return QStringList();
539 }
540
541 bool isNullNode(NodePtr node) const override
542 {
543 return !node.ptr;
544 }
545
546 NodePtr parentNode(NodePtr node) const override
547 {
548 QSvgNode *n = svgNode(node);
549 NodePtr newNode;
550 newNode.ptr = 0;
551 newNode.id = 0;
552 if (n) {
553 QSvgNode *svgParent = n->parent();
554 if (svgParent) {
555 newNode.ptr = svgParent;
556 }
557 }
558 return newNode;
559 }
560 NodePtr previousSiblingNode(NodePtr node) const override
561 {
562 NodePtr newNode;
563 newNode.ptr = 0;
564 newNode.id = 0;
565
566 QSvgNode *n = svgNode(node);
567 if (!n)
568 return newNode;
569 QSvgStructureNode *svgParent = nodeToStructure(n: n->parent());
570
571 if (svgParent) {
572 newNode.ptr = svgParent->previousSiblingNode(n);
573 }
574 return newNode;
575 }
576 NodePtr duplicateNode(NodePtr node) const override
577 {
578 NodePtr n;
579 n.ptr = node.ptr;
580 n.id = node.id;
581 return n;
582 }
583 void freeNode(NodePtr node) const override
584 {
585 Q_UNUSED(node);
586 }
587};
588
589#endif // QT_NO_CSSPARSER
590
591// '0' is 0x30 and '9' is 0x39
592static inline bool isDigit(ushort ch)
593{
594 static quint16 magic = 0x3ff;
595 return ((ch >> 4) == 3) && (magic >> (ch & 15));
596}
597
598static qreal toDouble(const QChar *&str)
599{
600 const int maxLen = 255;//technically doubles can go til 308+ but whatever
601 char temp[maxLen+1];
602 int pos = 0;
603
604 if (*str == QLatin1Char('-')) {
605 temp[pos++] = '-';
606 ++str;
607 } else if (*str == QLatin1Char('+')) {
608 ++str;
609 }
610 while (isDigit(ch: str->unicode()) && pos < maxLen) {
611 temp[pos++] = str->toLatin1();
612 ++str;
613 }
614 if (*str == QLatin1Char('.') && pos < maxLen) {
615 temp[pos++] = '.';
616 ++str;
617 }
618 while (isDigit(ch: str->unicode()) && pos < maxLen) {
619 temp[pos++] = str->toLatin1();
620 ++str;
621 }
622 bool exponent = false;
623 if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
624 exponent = true;
625 temp[pos++] = 'e';
626 ++str;
627 if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
628 temp[pos++] = str->toLatin1();
629 ++str;
630 }
631 while (isDigit(ch: str->unicode()) && pos < maxLen) {
632 temp[pos++] = str->toLatin1();
633 ++str;
634 }
635 }
636
637 temp[pos] = '\0';
638
639 qreal val;
640 if (!exponent && pos < 10) {
641 int ival = 0;
642 const char *t = temp;
643 bool neg = false;
644 if(*t == '-') {
645 neg = true;
646 ++t;
647 }
648 while(*t && *t != '.') {
649 ival *= 10;
650 ival += (*t) - '0';
651 ++t;
652 }
653 if(*t == '.') {
654 ++t;
655 int div = 1;
656 while(*t) {
657 ival *= 10;
658 ival += (*t) - '0';
659 div *= 10;
660 ++t;
661 }
662 val = ((qreal)ival)/((qreal)div);
663 } else {
664 val = ival;
665 }
666 if (neg)
667 val = -val;
668 } else {
669 val = QByteArray::fromRawData(data: temp, size: pos).toDouble();
670 // Do not tolerate values too wild to be represented normally by floats
671 if (qFpClassify(val: float(val)) != FP_NORMAL)
672 val = 0;
673 }
674 return val;
675
676}
677
678static qreal toDouble(QStringView str, bool *ok = NULL)
679{
680 const QChar *c = str.constData();
681 qreal res = (c == nullptr ? qreal{} : toDouble(str&: c));
682 if (ok)
683 *ok = (c == (str.constData() + str.size()));
684 return res;
685}
686
687static QList<qreal> parseNumbersList(const QChar *&str)
688{
689 QList<qreal> points;
690 if (!str)
691 return points;
692 points.reserve(asize: 32);
693
694 while (str->isSpace())
695 ++str;
696 while (isDigit(ch: str->unicode()) ||
697 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
698 *str == QLatin1Char('.')) {
699
700 points.append(t: toDouble(str));
701
702 while (str->isSpace())
703 ++str;
704 if (*str == QLatin1Char(','))
705 ++str;
706
707 //eat the rest of space
708 while (str->isSpace())
709 ++str;
710 }
711
712 return points;
713}
714
715static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points,
716 const char *pattern = nullptr)
717{
718 const size_t patternLen = qstrlen(str: pattern);
719 while (str->isSpace())
720 ++str;
721 while (isDigit(ch: str->unicode()) ||
722 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
723 *str == QLatin1Char('.')) {
724
725 if (patternLen && pattern[points.size() % patternLen] == 'f') {
726 // flag expected, may only be 0 or 1
727 if (*str != QLatin1Char('0') && *str != QLatin1Char('1'))
728 return;
729 points.append(t: *str == QLatin1Char('0') ? 0.0 : 1.0);
730 ++str;
731 } else {
732 points.append(t: toDouble(str));
733 }
734
735 while (str->isSpace())
736 ++str;
737 if (*str == QLatin1Char(','))
738 ++str;
739
740 //eat the rest of space
741 while (str->isSpace())
742 ++str;
743 }
744}
745
746static QList<qreal> parsePercentageList(const QChar *&str)
747{
748 QList<qreal> points;
749 if (!str)
750 return points;
751
752 while (str->isSpace())
753 ++str;
754 while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) ||
755 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
756 *str == QLatin1Char('.')) {
757
758 points.append(t: toDouble(str));
759
760 while (str->isSpace())
761 ++str;
762 if (*str == QLatin1Char('%'))
763 ++str;
764 while (str->isSpace())
765 ++str;
766 if (*str == QLatin1Char(','))
767 ++str;
768
769 //eat the rest of space
770 while (str->isSpace())
771 ++str;
772 }
773
774 return points;
775}
776
777static QString idFromUrl(const QString &url)
778{
779 // The form is url(<IRI>), where IRI can be
780 // just an ID on #<id> form.
781 QString::const_iterator itr = url.constBegin();
782 QString::const_iterator end = url.constEnd();
783 QString id;
784 while (itr != end && (*itr).isSpace())
785 ++itr;
786 if (itr != end && (*itr) == QLatin1Char('('))
787 ++itr;
788 else
789 return QString();
790 while (itr != end && (*itr).isSpace())
791 ++itr;
792 if (itr != end && (*itr) == QLatin1Char('#')) {
793 id += *itr;
794 ++itr;
795 } else {
796 return QString();
797 }
798 while (itr != end && (*itr) != QLatin1Char(')')) {
799 id += *itr;
800 ++itr;
801 }
802 if (itr == end || (*itr) != QLatin1Char(')'))
803 return QString();
804 return id;
805}
806
807/**
808 * returns true when successfully set the color. false signifies
809 * that the color should be inherited
810 */
811static bool resolveColor(QStringView colorStr, QColor &color, QSvgHandler *handler)
812{
813 QStringView colorStrTr = colorStr.trimmed();
814 if (colorStrTr.isEmpty())
815 return false;
816
817 switch(colorStrTr.at(n: 0).unicode()) {
818
819 case '#':
820 {
821 // #rrggbb is very very common, so let's tackle it here
822 // rather than falling back to QColor
823 QRgb rgb;
824 bool ok = qsvg_get_hex_rgb(str: colorStrTr.constData(), len: colorStrTr.size(), rgb: &rgb);
825 if (ok)
826 color.setRgb(rgb);
827 return ok;
828 }
829 break;
830
831 case 'r':
832 {
833 // starts with "rgb(", ends with ")" and consists of at least 7 characters "rgb(,,)"
834 if (colorStrTr.size() >= 7 && colorStrTr.at(n: colorStrTr.size() - 1) == QLatin1Char(')')
835 && colorStrTr.mid(pos: 0, n: 4) == QLatin1String("rgb(")) {
836 const QChar *s = colorStrTr.constData() + 4;
837 QList<qreal> compo = parseNumbersList(str&: s);
838 //1 means that it failed after reaching non-parsable
839 //character which is going to be "%"
840 if (compo.size() == 1) {
841 s = colorStrTr.constData() + 4;
842 compo = parsePercentageList(str&: s);
843 for (int i = 0; i < compo.size(); ++i)
844 compo[i] *= (qreal)2.55;
845 }
846
847 if (compo.size() == 3) {
848 color = QColor(int(compo[0]),
849 int(compo[1]),
850 int(compo[2]));
851 return true;
852 }
853 return false;
854 }
855 }
856 break;
857
858 case 'c':
859 if (colorStrTr == QLatin1String("currentColor")) {
860 color = handler->currentColor();
861 return true;
862 }
863 break;
864 case 'i':
865 if (colorStrTr == QT_INHERIT)
866 return false;
867 break;
868 default:
869 break;
870 }
871
872 color = QColor::fromString(name: colorStrTr.toString());
873 return color.isValid();
874}
875
876static bool constructColor(QStringView colorStr, QStringView opacity,
877 QColor &color, QSvgHandler *handler)
878{
879 if (!resolveColor(colorStr, color, handler))
880 return false;
881 if (!opacity.isEmpty()) {
882 bool ok = true;
883 qreal op = qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: opacity, ok: &ok)));
884 if (!ok)
885 op = 1.0;
886 color.setAlphaF(op);
887 }
888 return true;
889}
890
891static qreal parseLength(QStringView str, QSvgHandler::LengthType *type,
892 QSvgHandler *handler, bool *ok = NULL)
893{
894 QStringView numStr = str.trimmed();
895
896 if (numStr.isEmpty()) {
897 if (ok)
898 *ok = false;
899 *type = QSvgHandler::LT_OTHER;
900 return false;
901 }
902 if (numStr.endsWith(c: QLatin1Char('%'))) {
903 numStr.chop(n: 1);
904 *type = QSvgHandler::LT_PERCENT;
905 } else if (numStr.endsWith(s: QLatin1String("px"))) {
906 numStr.chop(n: 2);
907 *type = QSvgHandler::LT_PX;
908 } else if (numStr.endsWith(s: QLatin1String("pc"))) {
909 numStr.chop(n: 2);
910 *type = QSvgHandler::LT_PC;
911 } else if (numStr.endsWith(s: QLatin1String("pt"))) {
912 numStr.chop(n: 2);
913 *type = QSvgHandler::LT_PT;
914 } else if (numStr.endsWith(s: QLatin1String("mm"))) {
915 numStr.chop(n: 2);
916 *type = QSvgHandler::LT_MM;
917 } else if (numStr.endsWith(s: QLatin1String("cm"))) {
918 numStr.chop(n: 2);
919 *type = QSvgHandler::LT_CM;
920 } else if (numStr.endsWith(s: QLatin1String("in"))) {
921 numStr.chop(n: 2);
922 *type = QSvgHandler::LT_IN;
923 } else {
924 *type = handler->defaultCoordinateSystem();
925 //type = QSvgHandler::LT_OTHER;
926 }
927 qreal len = toDouble(str: numStr, ok);
928 //qDebug()<<"len is "<<len<<", from '"<<numStr << "'";
929 return len;
930}
931
932static inline qreal convertToNumber(QStringView str, QSvgHandler *handler, bool *ok = NULL)
933{
934 QSvgHandler::LengthType type;
935 qreal num = parseLength(str: str.toString(), type: &type, handler, ok);
936 if (type == QSvgHandler::LT_PERCENT) {
937 num = num/100.0;
938 }
939 return num;
940}
941
942static bool createSvgGlyph(QSvgFont *font, const QXmlStreamAttributes &attributes)
943{
944 QStringView uncStr = attributes.value(qualifiedName: QLatin1String("unicode"));
945 QStringView havStr = attributes.value(qualifiedName: QLatin1String("horiz-adv-x"));
946 QStringView pathStr = attributes.value(qualifiedName: QLatin1String("d"));
947
948 QChar unicode = (uncStr.isEmpty()) ? u'\0' : uncStr.at(n: 0);
949 qreal havx = (havStr.isEmpty()) ? -1 : toDouble(str: havStr);
950 QPainterPath path;
951 path.setFillRule(Qt::WindingFill);
952 parsePathDataFast(data: pathStr, path);
953
954 font->addGlyph(unicode, path, horizAdvX: havx);
955
956 return true;
957}
958
959// this should really be called convertToDefaultCoordinateSystem
960// and convert when type != QSvgHandler::defaultCoordinateSystem
961static qreal convertToPixels(qreal len, bool , QSvgHandler::LengthType type)
962{
963
964 switch (type) {
965 case QSvgHandler::LT_PERCENT:
966 break;
967 case QSvgHandler::LT_PX:
968 break;
969 case QSvgHandler::LT_PC:
970 break;
971 case QSvgHandler::LT_PT:
972 return len * 1.25;
973 break;
974 case QSvgHandler::LT_MM:
975 return len * 3.543307;
976 break;
977 case QSvgHandler::LT_CM:
978 return len * 35.43307;
979 break;
980 case QSvgHandler::LT_IN:
981 return len * 90;
982 break;
983 case QSvgHandler::LT_OTHER:
984 break;
985 default:
986 break;
987 }
988 return len;
989}
990
991static void parseColor(QSvgNode *,
992 const QSvgAttributes &attributes,
993 QSvgHandler *handler)
994{
995 QColor color;
996 if (constructColor(colorStr: attributes.color, opacity: attributes.colorOpacity, color, handler)) {
997 handler->popColor();
998 handler->pushColor(color);
999 }
1000}
1001
1002static QSvgStyleProperty *styleFromUrl(QSvgNode *node, const QString &url)
1003{
1004 return node ? node->styleProperty(id: idFromUrl(url)) : 0;
1005}
1006
1007static void parseBrush(QSvgNode *node,
1008 const QSvgAttributes &attributes,
1009 QSvgHandler *handler)
1010{
1011 if (!attributes.fill.isEmpty() || !attributes.fillRule.isEmpty() || !attributes.fillOpacity.isEmpty()) {
1012 QSvgFillStyle *prop = new QSvgFillStyle;
1013
1014 //fill-rule attribute handling
1015 if (!attributes.fillRule.isEmpty() && attributes.fillRule != QT_INHERIT) {
1016 if (attributes.fillRule == QLatin1String("evenodd"))
1017 prop->setFillRule(Qt::OddEvenFill);
1018 else if (attributes.fillRule == QLatin1String("nonzero"))
1019 prop->setFillRule(Qt::WindingFill);
1020 }
1021
1022 //fill-opacity attribute handling
1023 if (!attributes.fillOpacity.isEmpty() && attributes.fillOpacity != QT_INHERIT) {
1024 prop->setFillOpacity(qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: attributes.fillOpacity))));
1025 }
1026
1027 //fill attribute handling
1028 if ((!attributes.fill.isEmpty()) && (attributes.fill != QT_INHERIT) ) {
1029 if (attributes.fill.size() > 3 && attributes.fill.mid(pos: 0, n: 3) == QLatin1String("url")) {
1030 QString value = attributes.fill.mid(pos: 3, n: attributes.fill.size() - 3).toString();
1031 QSvgStyleProperty *style = styleFromUrl(node, url: value);
1032 if (style) {
1033 if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT
1034 || style->type() == QSvgStyleProperty::PATTERN)
1035 prop->setFillStyle(reinterpret_cast<QSvgPaintStyleProperty *>(style));
1036 } else {
1037 QString id = idFromUrl(url: value);
1038 prop->setPaintStyleId(id);
1039 prop->setPaintStyleResolved(false);
1040 }
1041 } else if (attributes.fill != QLatin1String("none")) {
1042 QColor color;
1043 if (resolveColor(colorStr: attributes.fill, color, handler))
1044 prop->setBrush(QBrush(color));
1045 } else {
1046 prop->setBrush(QBrush(Qt::NoBrush));
1047 }
1048 }
1049 node->appendStyleProperty(prop, id: attributes.id);
1050 }
1051}
1052
1053
1054
1055static QTransform parseTransformationMatrix(QStringView value)
1056{
1057 if (value.isEmpty())
1058 return QTransform();
1059
1060 QTransform matrix;
1061 const QChar *str = value.constData();
1062 const QChar *end = str + value.size();
1063
1064 while (str < end) {
1065 if (str->isSpace() || *str == QLatin1Char(',')) {
1066 ++str;
1067 continue;
1068 }
1069 enum State {
1070 Matrix,
1071 Translate,
1072 Rotate,
1073 Scale,
1074 SkewX,
1075 SkewY
1076 };
1077 State state = Matrix;
1078 if (*str == QLatin1Char('m')) { //matrix
1079 const char *ident = "atrix";
1080 for (int i = 0; i < 5; ++i)
1081 if (*(++str) != QLatin1Char(ident[i]))
1082 goto error;
1083 ++str;
1084 state = Matrix;
1085 } else if (*str == QLatin1Char('t')) { //translate
1086 const char *ident = "ranslate";
1087 for (int i = 0; i < 8; ++i)
1088 if (*(++str) != QLatin1Char(ident[i]))
1089 goto error;
1090 ++str;
1091 state = Translate;
1092 } else if (*str == QLatin1Char('r')) { //rotate
1093 const char *ident = "otate";
1094 for (int i = 0; i < 5; ++i)
1095 if (*(++str) != QLatin1Char(ident[i]))
1096 goto error;
1097 ++str;
1098 state = Rotate;
1099 } else if (*str == QLatin1Char('s')) { //scale, skewX, skewY
1100 ++str;
1101 if (*str == QLatin1Char('c')) {
1102 const char *ident = "ale";
1103 for (int i = 0; i < 3; ++i)
1104 if (*(++str) != QLatin1Char(ident[i]))
1105 goto error;
1106 ++str;
1107 state = Scale;
1108 } else if (*str == QLatin1Char('k')) {
1109 if (*(++str) != QLatin1Char('e'))
1110 goto error;
1111 if (*(++str) != QLatin1Char('w'))
1112 goto error;
1113 ++str;
1114 if (*str == QLatin1Char('X'))
1115 state = SkewX;
1116 else if (*str == QLatin1Char('Y'))
1117 state = SkewY;
1118 else
1119 goto error;
1120 ++str;
1121 } else {
1122 goto error;
1123 }
1124 } else {
1125 goto error;
1126 }
1127
1128
1129 while (str < end && str->isSpace())
1130 ++str;
1131 if (*str != QLatin1Char('('))
1132 goto error;
1133 ++str;
1134 QVarLengthArray<qreal, 8> points;
1135 parseNumbersArray(str, points);
1136 if (*str != QLatin1Char(')'))
1137 goto error;
1138 ++str;
1139
1140 if(state == Matrix) {
1141 if(points.size() != 6)
1142 goto error;
1143 matrix = QTransform(points[0], points[1],
1144 points[2], points[3],
1145 points[4], points[5]) * matrix;
1146 } else if (state == Translate) {
1147 if (points.size() == 1)
1148 matrix.translate(dx: points[0], dy: 0);
1149 else if (points.size() == 2)
1150 matrix.translate(dx: points[0], dy: points[1]);
1151 else
1152 goto error;
1153 } else if (state == Rotate) {
1154 if(points.size() == 1) {
1155 matrix.rotate(a: points[0]);
1156 } else if (points.size() == 3) {
1157 matrix.translate(dx: points[1], dy: points[2]);
1158 matrix.rotate(a: points[0]);
1159 matrix.translate(dx: -points[1], dy: -points[2]);
1160 } else {
1161 goto error;
1162 }
1163 } else if (state == Scale) {
1164 if (points.size() < 1 || points.size() > 2)
1165 goto error;
1166 qreal sx = points[0];
1167 qreal sy = sx;
1168 if(points.size() == 2)
1169 sy = points[1];
1170 matrix.scale(sx, sy);
1171 } else if (state == SkewX) {
1172 if (points.size() != 1)
1173 goto error;
1174 matrix.shear(sh: qTan(v: qDegreesToRadians(degrees: points[0])), sv: 0);
1175 } else if (state == SkewY) {
1176 if (points.size() != 1)
1177 goto error;
1178 matrix.shear(sh: 0, sv: qTan(v: qDegreesToRadians(degrees: points[0])));
1179 }
1180 }
1181 error:
1182 return matrix;
1183}
1184
1185static void parsePen(QSvgNode *node,
1186 const QSvgAttributes &attributes,
1187 QSvgHandler *handler)
1188{
1189 //qDebug()<<"Node "<<node->type()<<", attrs are "<<value<<width;
1190
1191 if (!attributes.stroke.isEmpty() || !attributes.strokeDashArray.isEmpty() || !attributes.strokeDashOffset.isEmpty() || !attributes.strokeLineCap.isEmpty()
1192 || !attributes.strokeLineJoin.isEmpty() || !attributes.strokeMiterLimit.isEmpty() || !attributes.strokeOpacity.isEmpty() || !attributes.strokeWidth.isEmpty()
1193 || !attributes.vectorEffect.isEmpty()) {
1194
1195 QSvgStrokeStyle *prop = new QSvgStrokeStyle;
1196
1197 //stroke attribute handling
1198 if ((!attributes.stroke.isEmpty()) && (attributes.stroke != QT_INHERIT) ) {
1199 if (attributes.stroke.size() > 3 && attributes.stroke.mid(pos: 0, n: 3) == QLatin1String("url")) {
1200 QString value = attributes.stroke.mid(pos: 3, n: attributes.stroke.size() - 3).toString();
1201 QSvgStyleProperty *style = styleFromUrl(node, url: value);
1202 if (style) {
1203 if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT
1204 || style->type() == QSvgStyleProperty::PATTERN)
1205 prop->setStyle(reinterpret_cast<QSvgPaintStyleProperty *>(style));
1206 } else {
1207 QString id = idFromUrl(url: value);
1208 prop->setPaintStyleId(id);
1209 prop->setPaintStyleResolved(false);
1210 }
1211 } else if (attributes.stroke != QLatin1String("none")) {
1212 QColor color;
1213 if (resolveColor(colorStr: attributes.stroke, color, handler))
1214 prop->setStroke(QBrush(color));
1215 } else {
1216 prop->setStroke(QBrush(Qt::NoBrush));
1217 }
1218 }
1219
1220 //stroke-width handling
1221 if (!attributes.strokeWidth.isEmpty() && attributes.strokeWidth != QT_INHERIT) {
1222 QSvgHandler::LengthType lt;
1223 prop->setWidth(parseLength(str: attributes.strokeWidth, type: &lt, handler));
1224 }
1225
1226 //stroke-dasharray
1227 if (!attributes.strokeDashArray.isEmpty() && attributes.strokeDashArray != QT_INHERIT) {
1228 if (attributes.strokeDashArray == QLatin1String("none")) {
1229 prop->setDashArrayNone();
1230 } else {
1231 QString dashArray = attributes.strokeDashArray.toString();
1232 const QChar *s = dashArray.constData();
1233 QList<qreal> dashes = parseNumbersList(str&: s);
1234 bool allZeroes = true;
1235 for (qreal dash : dashes) {
1236 if (dash != 0.0) {
1237 allZeroes = false;
1238 break;
1239 }
1240 }
1241
1242 // if the stroke dash array contains only zeros,
1243 // force drawing of solid line.
1244 if (allZeroes == false) {
1245 // if the dash count is odd the dashes should be duplicated
1246 if ((dashes.size() & 1) != 0)
1247 dashes << QList<qreal>(dashes);
1248 prop->setDashArray(dashes);
1249 } else {
1250 prop->setDashArrayNone();
1251 }
1252 }
1253 }
1254
1255 //stroke-linejoin attribute handling
1256 if (!attributes.strokeLineJoin.isEmpty()) {
1257 if (attributes.strokeLineJoin == QLatin1String("miter"))
1258 prop->setLineJoin(Qt::SvgMiterJoin);
1259 else if (attributes.strokeLineJoin == QLatin1String("round"))
1260 prop->setLineJoin(Qt::RoundJoin);
1261 else if (attributes.strokeLineJoin == QLatin1String("bevel"))
1262 prop->setLineJoin(Qt::BevelJoin);
1263 }
1264
1265 //stroke-linecap attribute handling
1266 if (!attributes.strokeLineCap.isEmpty()) {
1267 if (attributes.strokeLineCap == QLatin1String("butt"))
1268 prop->setLineCap(Qt::FlatCap);
1269 else if (attributes.strokeLineCap == QLatin1String("round"))
1270 prop->setLineCap(Qt::RoundCap);
1271 else if (attributes.strokeLineCap == QLatin1String("square"))
1272 prop->setLineCap(Qt::SquareCap);
1273 }
1274
1275 //stroke-dashoffset attribute handling
1276 if (!attributes.strokeDashOffset.isEmpty() && attributes.strokeDashOffset != QT_INHERIT)
1277 prop->setDashOffset(toDouble(str: attributes.strokeDashOffset));
1278
1279 //vector-effect attribute handling
1280 if (!attributes.vectorEffect.isEmpty()) {
1281 if (attributes.vectorEffect == QLatin1String("non-scaling-stroke"))
1282 prop->setVectorEffect(true);
1283 else if (attributes.vectorEffect == QLatin1String("none"))
1284 prop->setVectorEffect(false);
1285 }
1286
1287 //stroke-miterlimit
1288 if (!attributes.strokeMiterLimit.isEmpty() && attributes.strokeMiterLimit != QT_INHERIT)
1289 prop->setMiterLimit(toDouble(str: attributes.strokeMiterLimit));
1290
1291 //stroke-opacity atttribute handling
1292 if (!attributes.strokeOpacity.isEmpty() && attributes.strokeOpacity != QT_INHERIT)
1293 prop->setOpacity(qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: attributes.strokeOpacity))));
1294
1295 node->appendStyleProperty(prop, id: attributes.id);
1296 }
1297}
1298
1299enum FontSizeSpec { XXSmall, XSmall, Small, Medium, Large, XLarge, XXLarge,
1300 FontSizeNone, FontSizeValue };
1301
1302static const qreal sizeTable[] =
1303{ qreal(6.9), qreal(8.3), qreal(10.0), qreal(12.0), qreal(14.4), qreal(17.3), qreal(20.7) };
1304
1305Q_STATIC_ASSERT(sizeof(sizeTable)/sizeof(sizeTable[0]) == FontSizeNone);
1306
1307static FontSizeSpec fontSizeSpec(QStringView spec)
1308{
1309 switch (spec.at(n: 0).unicode()) {
1310 case 'x':
1311 if (spec == QLatin1String("xx-small"))
1312 return XXSmall;
1313 if (spec == QLatin1String("x-small"))
1314 return XSmall;
1315 if (spec == QLatin1String("x-large"))
1316 return XLarge;
1317 if (spec == QLatin1String("xx-large"))
1318 return XXLarge;
1319 break;
1320 case 's':
1321 if (spec == QLatin1String("small"))
1322 return Small;
1323 break;
1324 case 'm':
1325 if (spec == QLatin1String("medium"))
1326 return Medium;
1327 break;
1328 case 'l':
1329 if (spec == QLatin1String("large"))
1330 return Large;
1331 break;
1332 case 'n':
1333 if (spec == QLatin1String("none"))
1334 return FontSizeNone;
1335 break;
1336 default:
1337 break;
1338 }
1339 return FontSizeValue;
1340}
1341
1342static void parseFont(QSvgNode *node,
1343 const QSvgAttributes &attributes,
1344 QSvgHandler *handler)
1345{
1346 if (attributes.fontFamily.isEmpty() && attributes.fontSize.isEmpty() && attributes.fontStyle.isEmpty() &&
1347 attributes.fontWeight.isEmpty() && attributes.fontVariant.isEmpty() && attributes.textAnchor.isEmpty())
1348 return;
1349
1350 QSvgFontStyle *fontStyle = nullptr;
1351 if (!attributes.fontFamily.isEmpty()) {
1352 QSvgTinyDocument *doc = node->document();
1353 if (doc) {
1354 QSvgFont *svgFont = doc->svgFont(family: attributes.fontFamily.toString());
1355 if (svgFont)
1356 fontStyle = new QSvgFontStyle(svgFont, doc);
1357 }
1358 }
1359 if (!fontStyle)
1360 fontStyle = new QSvgFontStyle;
1361 if (!attributes.fontFamily.isEmpty() && attributes.fontFamily != QT_INHERIT) {
1362 QString family = attributes.fontFamily.toString().trimmed();
1363 if (family.at(i: 0) == QLatin1Char('\'') || family.at(i: 0) == QLatin1Char('\"'))
1364 family = family.mid(position: 1, n: family.size() - 2);
1365 fontStyle->setFamily(family);
1366 }
1367
1368 if (!attributes.fontSize.isEmpty() && attributes.fontSize != QT_INHERIT) {
1369 // TODO: Support relative sizes 'larger' and 'smaller'.
1370 const FontSizeSpec spec = fontSizeSpec(spec: attributes.fontSize);
1371 switch (spec) {
1372 case FontSizeNone:
1373 break;
1374 case FontSizeValue: {
1375 QSvgHandler::LengthType type;
1376 qreal fs = parseLength(str: attributes.fontSize, type: &type, handler);
1377 fs = convertToPixels(len: fs, true, type);
1378 fontStyle->setSize(qMin(a: fs, b: qreal(0xffff)));
1379 }
1380 break;
1381 default:
1382 fontStyle->setSize(sizeTable[spec]);
1383 break;
1384 }
1385 }
1386
1387 if (!attributes.fontStyle.isEmpty() && attributes.fontStyle != QT_INHERIT) {
1388 if (attributes.fontStyle == QLatin1String("normal")) {
1389 fontStyle->setStyle(QFont::StyleNormal);
1390 } else if (attributes.fontStyle == QLatin1String("italic")) {
1391 fontStyle->setStyle(QFont::StyleItalic);
1392 } else if (attributes.fontStyle == QLatin1String("oblique")) {
1393 fontStyle->setStyle(QFont::StyleOblique);
1394 }
1395 }
1396
1397 if (!attributes.fontWeight.isEmpty() && attributes.fontWeight != QT_INHERIT) {
1398 bool ok = false;
1399 const int weightNum = attributes.fontWeight.toInt(ok: &ok);
1400 if (ok) {
1401 fontStyle->setWeight(weightNum);
1402 } else {
1403 if (attributes.fontWeight == QLatin1String("normal")) {
1404 fontStyle->setWeight(QFont::Normal);
1405 } else if (attributes.fontWeight == QLatin1String("bold")) {
1406 fontStyle->setWeight(QFont::Bold);
1407 } else if (attributes.fontWeight == QLatin1String("bolder")) {
1408 fontStyle->setWeight(QSvgFontStyle::BOLDER);
1409 } else if (attributes.fontWeight == QLatin1String("lighter")) {
1410 fontStyle->setWeight(QSvgFontStyle::LIGHTER);
1411 }
1412 }
1413 }
1414
1415 if (!attributes.fontVariant.isEmpty() && attributes.fontVariant != QT_INHERIT) {
1416 if (attributes.fontVariant == QLatin1String("normal"))
1417 fontStyle->setVariant(QFont::MixedCase);
1418 else if (attributes.fontVariant == QLatin1String("small-caps"))
1419 fontStyle->setVariant(QFont::SmallCaps);
1420 }
1421
1422 if (!attributes.textAnchor.isEmpty() && attributes.textAnchor != QT_INHERIT) {
1423 if (attributes.textAnchor == QLatin1String("start"))
1424 fontStyle->setTextAnchor(Qt::AlignLeft);
1425 if (attributes.textAnchor == QLatin1String("middle"))
1426 fontStyle->setTextAnchor(Qt::AlignHCenter);
1427 else if (attributes.textAnchor == QLatin1String("end"))
1428 fontStyle->setTextAnchor(Qt::AlignRight);
1429 }
1430
1431 node->appendStyleProperty(prop: fontStyle, id: attributes.id);
1432}
1433
1434static void parseTransform(QSvgNode *node,
1435 const QSvgAttributes &attributes,
1436 QSvgHandler *)
1437{
1438 if (attributes.transform.isEmpty())
1439 return;
1440 QTransform matrix = parseTransformationMatrix(value: attributes.transform.trimmed());
1441
1442 if (!matrix.isIdentity()) {
1443 node->appendStyleProperty(prop: new QSvgTransformStyle(QTransform(matrix)), id: attributes.id);
1444 }
1445
1446}
1447
1448static void parseVisibility(QSvgNode *node,
1449 const QSvgAttributes &attributes,
1450 QSvgHandler *)
1451{
1452 QSvgNode *parent = node->parent();
1453
1454 if (parent && (attributes.visibility.isEmpty() || attributes.visibility == QT_INHERIT))
1455 node->setVisible(parent->isVisible());
1456 else if (attributes.visibility == QLatin1String("hidden") || attributes.visibility == QLatin1String("collapse")) {
1457 node->setVisible(false);
1458 } else
1459 node->setVisible(true);
1460}
1461
1462static void pathArcSegment(QPainterPath &path,
1463 qreal xc, qreal yc,
1464 qreal th0, qreal th1,
1465 qreal rx, qreal ry, qreal xAxisRotation)
1466{
1467 qreal sinTh, cosTh;
1468 qreal a00, a01, a10, a11;
1469 qreal x1, y1, x2, y2, x3, y3;
1470 qreal t;
1471 qreal thHalf;
1472
1473 sinTh = qSin(v: xAxisRotation * (Q_PI / 180.0));
1474 cosTh = qCos(v: xAxisRotation * (Q_PI / 180.0));
1475
1476 a00 = cosTh * rx;
1477 a01 = -sinTh * ry;
1478 a10 = sinTh * rx;
1479 a11 = cosTh * ry;
1480
1481 thHalf = 0.5 * (th1 - th0);
1482 t = (8.0 / 3.0) * qSin(v: thHalf * 0.5) * qSin(v: thHalf * 0.5) / qSin(v: thHalf);
1483 x1 = xc + qCos(v: th0) - t * qSin(v: th0);
1484 y1 = yc + qSin(v: th0) + t * qCos(v: th0);
1485 x3 = xc + qCos(v: th1);
1486 y3 = yc + qSin(v: th1);
1487 x2 = x3 + t * qSin(v: th1);
1488 y2 = y3 - t * qCos(v: th1);
1489
1490 path.cubicTo(ctrlPt1x: a00 * x1 + a01 * y1, ctrlPt1y: a10 * x1 + a11 * y1,
1491 ctrlPt2x: a00 * x2 + a01 * y2, ctrlPt2y: a10 * x2 + a11 * y2,
1492 endPtx: a00 * x3 + a01 * y3, endPty: a10 * x3 + a11 * y3);
1493}
1494
1495// the arc handling code underneath is from XSVG (BSD license)
1496/*
1497 * Copyright 2002 USC/Information Sciences Institute
1498 *
1499 * Permission to use, copy, modify, distribute, and sell this software
1500 * and its documentation for any purpose is hereby granted without
1501 * fee, provided that the above copyright notice appear in all copies
1502 * and that both that copyright notice and this permission notice
1503 * appear in supporting documentation, and that the name of
1504 * Information Sciences Institute not be used in advertising or
1505 * publicity pertaining to distribution of the software without
1506 * specific, written prior permission. Information Sciences Institute
1507 * makes no representations about the suitability of this software for
1508 * any purpose. It is provided "as is" without express or implied
1509 * warranty.
1510 *
1511 * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
1512 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
1513 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
1514 * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
1515 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
1516 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
1517 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1518 * PERFORMANCE OF THIS SOFTWARE.
1519 *
1520 */
1521static void pathArc(QPainterPath &path,
1522 qreal rx,
1523 qreal ry,
1524 qreal x_axis_rotation,
1525 int large_arc_flag,
1526 int sweep_flag,
1527 qreal x,
1528 qreal y,
1529 qreal curx, qreal cury)
1530{
1531 const qreal Pr1 = rx * rx;
1532 const qreal Pr2 = ry * ry;
1533
1534 if (!Pr1 || !Pr2)
1535 return;
1536
1537 qreal sin_th, cos_th;
1538 qreal a00, a01, a10, a11;
1539 qreal x0, y0, x1, y1, xc, yc;
1540 qreal d, sfactor, sfactor_sq;
1541 qreal th0, th1, th_arc;
1542 int i, n_segs;
1543 qreal dx, dy, dx1, dy1, Px, Py, check;
1544
1545 rx = qAbs(t: rx);
1546 ry = qAbs(t: ry);
1547
1548 sin_th = qSin(v: x_axis_rotation * (Q_PI / 180.0));
1549 cos_th = qCos(v: x_axis_rotation * (Q_PI / 180.0));
1550
1551 dx = (curx - x) / 2.0;
1552 dy = (cury - y) / 2.0;
1553 dx1 = cos_th * dx + sin_th * dy;
1554 dy1 = -sin_th * dx + cos_th * dy;
1555 Px = dx1 * dx1;
1556 Py = dy1 * dy1;
1557 /* Spec : check if radii are large enough */
1558 check = Px / Pr1 + Py / Pr2;
1559 if (check > 1) {
1560 rx = rx * qSqrt(v: check);
1561 ry = ry * qSqrt(v: check);
1562 }
1563
1564 a00 = cos_th / rx;
1565 a01 = sin_th / rx;
1566 a10 = -sin_th / ry;
1567 a11 = cos_th / ry;
1568 x0 = a00 * curx + a01 * cury;
1569 y0 = a10 * curx + a11 * cury;
1570 x1 = a00 * x + a01 * y;
1571 y1 = a10 * x + a11 * y;
1572 /* (x0, y0) is current point in transformed coordinate space.
1573 (x1, y1) is new point in transformed coordinate space.
1574
1575 The arc fits a unit-radius circle in this space.
1576 */
1577 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
1578 if (!d)
1579 return;
1580 sfactor_sq = 1.0 / d - 0.25;
1581 if (sfactor_sq < 0) sfactor_sq = 0;
1582 sfactor = qSqrt(v: sfactor_sq);
1583 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
1584 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
1585 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
1586 /* (xc, yc) is center of the circle. */
1587
1588 th0 = qAtan2(y: y0 - yc, x: x0 - xc);
1589 th1 = qAtan2(y: y1 - yc, x: x1 - xc);
1590
1591 th_arc = th1 - th0;
1592 if (th_arc < 0 && sweep_flag)
1593 th_arc += 2 * Q_PI;
1594 else if (th_arc > 0 && !sweep_flag)
1595 th_arc -= 2 * Q_PI;
1596
1597 n_segs = qCeil(v: qAbs(t: th_arc / (Q_PI * 0.5 + 0.001)));
1598
1599 for (i = 0; i < n_segs; i++) {
1600 pathArcSegment(path, xc, yc,
1601 th0: th0 + i * th_arc / n_segs,
1602 th1: th0 + (i + 1) * th_arc / n_segs,
1603 rx, ry, xAxisRotation: x_axis_rotation);
1604 }
1605}
1606
1607static bool parsePathDataFast(QStringView dataStr, QPainterPath &path, bool limitLength)
1608{
1609 const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this
1610 qreal x0 = 0, y0 = 0; // starting point
1611 qreal x = 0, y = 0; // current point
1612 char lastMode = 0;
1613 QPointF ctrlPt;
1614 const QChar *str = dataStr.constData();
1615 const QChar *end = str + dataStr.size();
1616
1617 bool ok = true;
1618 while (ok && str != end) {
1619 while (str->isSpace() && (str + 1) != end)
1620 ++str;
1621 QChar pathElem = *str;
1622 ++str;
1623 QChar endc = *end;
1624 *const_cast<QChar *>(end) = u'\0'; // parseNumbersArray requires 0-termination that QStringView cannot guarantee
1625 const char *pattern = nullptr;
1626 if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A'))
1627 pattern = "rrrffrr";
1628 QVarLengthArray<qreal, 8> arg;
1629 parseNumbersArray(str, points&: arg, pattern);
1630 *const_cast<QChar *>(end) = endc;
1631 if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
1632 arg.append(t: 0);//dummy
1633 const qreal *num = arg.constData();
1634 int count = arg.size();
1635 while (ok && count > 0) {
1636 qreal offsetX = x; // correction offsets
1637 qreal offsetY = y; // for relative commands
1638 switch (pathElem.unicode()) {
1639 case 'm': {
1640 if (count < 2) {
1641 ok = false;
1642 break;
1643 }
1644 x = x0 = num[0] + offsetX;
1645 y = y0 = num[1] + offsetY;
1646 num += 2;
1647 count -= 2;
1648 path.moveTo(x: x0, y: y0);
1649
1650 // As per 1.2 spec 8.3.2 The "moveto" commands
1651 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
1652 // the subsequent pairs shall be treated as implicit 'lineto' commands.
1653 pathElem = QLatin1Char('l');
1654 }
1655 break;
1656 case 'M': {
1657 if (count < 2) {
1658 ok = false;
1659 break;
1660 }
1661 x = x0 = num[0];
1662 y = y0 = num[1];
1663 num += 2;
1664 count -= 2;
1665 path.moveTo(x: x0, y: y0);
1666
1667 // As per 1.2 spec 8.3.2 The "moveto" commands
1668 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
1669 // the subsequent pairs shall be treated as implicit 'lineto' commands.
1670 pathElem = QLatin1Char('L');
1671 }
1672 break;
1673 case 'z':
1674 case 'Z': {
1675 x = x0;
1676 y = y0;
1677 count--; // skip dummy
1678 num++;
1679 path.closeSubpath();
1680 }
1681 break;
1682 case 'l': {
1683 if (count < 2) {
1684 ok = false;
1685 break;
1686 }
1687 x = num[0] + offsetX;
1688 y = num[1] + offsetY;
1689 num += 2;
1690 count -= 2;
1691 path.lineTo(x, y);
1692
1693 }
1694 break;
1695 case 'L': {
1696 if (count < 2) {
1697 ok = false;
1698 break;
1699 }
1700 x = num[0];
1701 y = num[1];
1702 num += 2;
1703 count -= 2;
1704 path.lineTo(x, y);
1705 }
1706 break;
1707 case 'h': {
1708 x = num[0] + offsetX;
1709 num++;
1710 count--;
1711 path.lineTo(x, y);
1712 }
1713 break;
1714 case 'H': {
1715 x = num[0];
1716 num++;
1717 count--;
1718 path.lineTo(x, y);
1719 }
1720 break;
1721 case 'v': {
1722 y = num[0] + offsetY;
1723 num++;
1724 count--;
1725 path.lineTo(x, y);
1726 }
1727 break;
1728 case 'V': {
1729 y = num[0];
1730 num++;
1731 count--;
1732 path.lineTo(x, y);
1733 }
1734 break;
1735 case 'c': {
1736 if (count < 6) {
1737 ok = false;
1738 break;
1739 }
1740 QPointF c1(num[0] + offsetX, num[1] + offsetY);
1741 QPointF c2(num[2] + offsetX, num[3] + offsetY);
1742 QPointF e(num[4] + offsetX, num[5] + offsetY);
1743 num += 6;
1744 count -= 6;
1745 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1746 ctrlPt = c2;
1747 x = e.x();
1748 y = e.y();
1749 break;
1750 }
1751 case 'C': {
1752 if (count < 6) {
1753 ok = false;
1754 break;
1755 }
1756 QPointF c1(num[0], num[1]);
1757 QPointF c2(num[2], num[3]);
1758 QPointF e(num[4], num[5]);
1759 num += 6;
1760 count -= 6;
1761 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1762 ctrlPt = c2;
1763 x = e.x();
1764 y = e.y();
1765 break;
1766 }
1767 case 's': {
1768 if (count < 4) {
1769 ok = false;
1770 break;
1771 }
1772 QPointF c1;
1773 if (lastMode == 'c' || lastMode == 'C' ||
1774 lastMode == 's' || lastMode == 'S')
1775 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1776 else
1777 c1 = QPointF(x, y);
1778 QPointF c2(num[0] + offsetX, num[1] + offsetY);
1779 QPointF e(num[2] + offsetX, num[3] + offsetY);
1780 num += 4;
1781 count -= 4;
1782 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1783 ctrlPt = c2;
1784 x = e.x();
1785 y = e.y();
1786 break;
1787 }
1788 case 'S': {
1789 if (count < 4) {
1790 ok = false;
1791 break;
1792 }
1793 QPointF c1;
1794 if (lastMode == 'c' || lastMode == 'C' ||
1795 lastMode == 's' || lastMode == 'S')
1796 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1797 else
1798 c1 = QPointF(x, y);
1799 QPointF c2(num[0], num[1]);
1800 QPointF e(num[2], num[3]);
1801 num += 4;
1802 count -= 4;
1803 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1804 ctrlPt = c2;
1805 x = e.x();
1806 y = e.y();
1807 break;
1808 }
1809 case 'q': {
1810 if (count < 4) {
1811 ok = false;
1812 break;
1813 }
1814 QPointF c(num[0] + offsetX, num[1] + offsetY);
1815 QPointF e(num[2] + offsetX, num[3] + offsetY);
1816 num += 4;
1817 count -= 4;
1818 path.quadTo(ctrlPt: c, endPt: e);
1819 ctrlPt = c;
1820 x = e.x();
1821 y = e.y();
1822 break;
1823 }
1824 case 'Q': {
1825 if (count < 4) {
1826 ok = false;
1827 break;
1828 }
1829 QPointF c(num[0], num[1]);
1830 QPointF e(num[2], num[3]);
1831 num += 4;
1832 count -= 4;
1833 path.quadTo(ctrlPt: c, endPt: e);
1834 ctrlPt = c;
1835 x = e.x();
1836 y = e.y();
1837 break;
1838 }
1839 case 't': {
1840 if (count < 2) {
1841 ok = false;
1842 break;
1843 }
1844 QPointF e(num[0] + offsetX, num[1] + offsetY);
1845 num += 2;
1846 count -= 2;
1847 QPointF c;
1848 if (lastMode == 'q' || lastMode == 'Q' ||
1849 lastMode == 't' || lastMode == 'T')
1850 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1851 else
1852 c = QPointF(x, y);
1853 path.quadTo(ctrlPt: c, endPt: e);
1854 ctrlPt = c;
1855 x = e.x();
1856 y = e.y();
1857 break;
1858 }
1859 case 'T': {
1860 if (count < 2) {
1861 ok = false;
1862 break;
1863 }
1864 QPointF e(num[0], num[1]);
1865 num += 2;
1866 count -= 2;
1867 QPointF c;
1868 if (lastMode == 'q' || lastMode == 'Q' ||
1869 lastMode == 't' || lastMode == 'T')
1870 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1871 else
1872 c = QPointF(x, y);
1873 path.quadTo(ctrlPt: c, endPt: e);
1874 ctrlPt = c;
1875 x = e.x();
1876 y = e.y();
1877 break;
1878 }
1879 case 'a': {
1880 if (count < 7) {
1881 ok = false;
1882 break;
1883 }
1884 qreal rx = (*num++);
1885 qreal ry = (*num++);
1886 qreal xAxisRotation = (*num++);
1887 qreal largeArcFlag = (*num++);
1888 qreal sweepFlag = (*num++);
1889 qreal ex = (*num++) + offsetX;
1890 qreal ey = (*num++) + offsetY;
1891 count -= 7;
1892 qreal curx = x;
1893 qreal cury = y;
1894 pathArc(path, rx, ry, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag),
1895 sweep_flag: int(sweepFlag), x: ex, y: ey, curx, cury);
1896
1897 x = ex;
1898 y = ey;
1899 }
1900 break;
1901 case 'A': {
1902 if (count < 7) {
1903 ok = false;
1904 break;
1905 }
1906 qreal rx = (*num++);
1907 qreal ry = (*num++);
1908 qreal xAxisRotation = (*num++);
1909 qreal largeArcFlag = (*num++);
1910 qreal sweepFlag = (*num++);
1911 qreal ex = (*num++);
1912 qreal ey = (*num++);
1913 count -= 7;
1914 qreal curx = x;
1915 qreal cury = y;
1916 pathArc(path, rx, ry, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag),
1917 sweep_flag: int(sweepFlag), x: ex, y: ey, curx, cury);
1918
1919 x = ex;
1920 y = ey;
1921 }
1922 break;
1923 default:
1924 ok = false;
1925 break;
1926 }
1927 lastMode = pathElem.toLatin1();
1928 if (limitLength && path.elementCount() > maxElementCount)
1929 ok = false;
1930 }
1931 }
1932 return ok;
1933}
1934
1935static bool parseStyle(QSvgNode *node,
1936 const QXmlStreamAttributes &attributes,
1937 QSvgHandler *);
1938
1939static bool parseStyle(QSvgNode *node,
1940 const QSvgAttributes &attributes,
1941 QSvgHandler *);
1942
1943#ifndef QT_NO_CSSPARSER
1944
1945static void parseCSStoXMLAttrs(const QList<QCss::Declaration> &declarations,
1946 QXmlStreamAttributes &attributes)
1947{
1948 for (int i = 0; i < declarations.size(); ++i) {
1949 const QCss::Declaration &decl = declarations.at(i);
1950 if (decl.d->property.isEmpty())
1951 continue;
1952 QCss::Value val = decl.d->values.first();
1953 QString valueStr;
1954 const int valCount = decl.d->values.size();
1955 if (valCount != 1) {
1956 for (int i = 0; i < valCount; ++i) {
1957 valueStr += decl.d->values[i].toString();
1958 if (i + 1 < valCount)
1959 valueStr += QLatin1Char(',');
1960 }
1961 } else {
1962 valueStr = val.toString();
1963 }
1964 if (val.type == QCss::Value::Uri) {
1965 valueStr.prepend(s: QLatin1String("url("));
1966 valueStr.append(c: QLatin1Char(')'));
1967 } else if (val.type == QCss::Value::Function) {
1968 QStringList lst = val.variant.toStringList();
1969 valueStr.append(s: lst.at(i: 0));
1970 valueStr.append(c: QLatin1Char('('));
1971 for (int i = 1; i < lst.size(); ++i) {
1972 valueStr.append(s: lst.at(i));
1973 if ((i +1) < lst.size())
1974 valueStr.append(c: QLatin1Char(','));
1975 }
1976 valueStr.append(c: QLatin1Char(')'));
1977 } else if (val.type == QCss::Value::KnownIdentifier) {
1978 switch (val.variant.toInt()) {
1979 case QCss::Value_None:
1980 valueStr = QLatin1String("none");
1981 break;
1982 default:
1983 break;
1984 }
1985 }
1986
1987 attributes.append(namespaceUri: QString(), name: decl.d->property, value: valueStr);
1988 }
1989}
1990
1991void QSvgHandler::parseCSStoXMLAttrs(const QString &css, QList<QSvgCssAttribute> *attributes)
1992{
1993 // preprocess (for unicode escapes), tokenize and remove comments
1994 m_cssParser.init(css);
1995 QString key;
1996
1997 attributes->reserve(asize: 10);
1998
1999 while (m_cssParser.hasNext()) {
2000 m_cssParser.skipSpace();
2001
2002 if (!m_cssParser.hasNext())
2003 break;
2004 m_cssParser.next();
2005
2006 QString name;
2007 if (m_cssParser.hasEscapeSequences) {
2008 key = m_cssParser.lexem();
2009 name = key;
2010 } else {
2011 const QCss::Symbol &sym = m_cssParser.symbol();
2012 name = sym.text.mid(position: sym.start, n: sym.len);
2013 }
2014
2015 m_cssParser.skipSpace();
2016 if (!m_cssParser.test(t: QCss::COLON))
2017 break;
2018
2019 m_cssParser.skipSpace();
2020 if (!m_cssParser.hasNext())
2021 break;
2022
2023 QSvgCssAttribute attribute;
2024 attribute.name = name;
2025
2026 const int firstSymbol = m_cssParser.index;
2027 int symbolCount = 0;
2028 do {
2029 m_cssParser.next();
2030 ++symbolCount;
2031 } while (m_cssParser.hasNext() && !m_cssParser.test(t: QCss::SEMICOLON));
2032
2033 bool canExtractValueByRef = !m_cssParser.hasEscapeSequences;
2034 if (canExtractValueByRef) {
2035 int len = m_cssParser.symbols.at(i: firstSymbol).len;
2036 for (int i = firstSymbol + 1; i < firstSymbol + symbolCount; ++i) {
2037 len += m_cssParser.symbols.at(i).len;
2038
2039 if (m_cssParser.symbols.at(i: i - 1).start + m_cssParser.symbols.at(i: i - 1).len
2040 != m_cssParser.symbols.at(i).start) {
2041 canExtractValueByRef = false;
2042 break;
2043 }
2044 }
2045 if (canExtractValueByRef) {
2046 const QCss::Symbol &sym = m_cssParser.symbols.at(i: firstSymbol);
2047 attribute.value = sym.text.mid(position: sym.start, n: len);
2048 }
2049 }
2050 if (!canExtractValueByRef) {
2051 QString value;
2052 for (int i = firstSymbol; i < m_cssParser.index - 1; ++i)
2053 value += m_cssParser.symbols.at(i).lexem();
2054 attribute.value = value;
2055 }
2056
2057 attributes->append(t: attribute);
2058
2059 m_cssParser.skipSpace();
2060 }
2061}
2062
2063static void cssStyleLookup(QSvgNode *node,
2064 QSvgHandler *handler,
2065 QSvgStyleSelector *selector,
2066 QXmlStreamAttributes &attributes)
2067{
2068 QCss::StyleSelector::NodePtr cssNode;
2069 cssNode.ptr = node;
2070 QList<QCss::Declaration> decls = selector->declarationsForNode(node: cssNode);
2071
2072 parseCSStoXMLAttrs(declarations: decls, attributes);
2073 parseStyle(node, attributes, handler);
2074}
2075
2076static void cssStyleLookup(QSvgNode *node,
2077 QSvgHandler *handler,
2078 QSvgStyleSelector *selector)
2079{
2080 QXmlStreamAttributes attributes;
2081 cssStyleLookup(node, handler, selector, attributes);
2082}
2083
2084#endif // QT_NO_CSSPARSER
2085
2086QtSvg::Options QSvgHandler::options() const
2087{
2088 return m_options;
2089}
2090
2091bool QSvgHandler::trustedSourceMode() const
2092{
2093 return m_options.testFlag(flag: QtSvg::AssumeTrustedSource);
2094}
2095
2096static inline QStringList stringToList(const QString &str)
2097{
2098 QStringList lst = str.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
2099 return lst;
2100}
2101
2102static bool parseCoreNode(QSvgNode *node,
2103 const QXmlStreamAttributes &attributes)
2104{
2105 QStringList features;
2106 QStringList extensions;
2107 QStringList languages;
2108 QStringList formats;
2109 QStringList fonts;
2110 QString xmlClassStr;
2111
2112 for (int i = 0; i < attributes.size(); ++i) {
2113 const QXmlStreamAttribute &attribute = attributes.at(i);
2114 QStringView name = attribute.qualifiedName();
2115 if (name.isEmpty())
2116 continue;
2117 QStringView value = attribute.value();
2118 switch (name.at(n: 0).unicode()) {
2119 case 'c':
2120 if (name == QLatin1String("class"))
2121 xmlClassStr = value.toString();
2122 break;
2123 case 'r':
2124 if (name == QLatin1String("requiredFeatures"))
2125 features = stringToList(str: value.toString());
2126 else if (name == QLatin1String("requiredExtensions"))
2127 extensions = stringToList(str: value.toString());
2128 else if (name == QLatin1String("requiredFormats"))
2129 formats = stringToList(str: value.toString());
2130 else if (name == QLatin1String("requiredFonts"))
2131 fonts = stringToList(str: value.toString());
2132 break;
2133 case 's':
2134 if (name == QLatin1String("systemLanguage"))
2135 languages = stringToList(str: value.toString());
2136 break;
2137 default:
2138 break;
2139 }
2140 }
2141
2142 node->setRequiredFeatures(features);
2143 node->setRequiredExtensions(extensions);
2144 node->setRequiredLanguages(languages);
2145 node->setRequiredFormats(formats);
2146 node->setRequiredFonts(fonts);
2147 node->setNodeId(someId(attributes));
2148 node->setXmlClass(xmlClassStr);
2149
2150 return true;
2151}
2152
2153static void parseOpacity(QSvgNode *node,
2154 const QSvgAttributes &attributes,
2155 QSvgHandler *)
2156{
2157 if (attributes.opacity.isEmpty())
2158 return;
2159
2160 const QStringView value = attributes.opacity.trimmed();
2161
2162 bool ok = false;
2163 qreal op = value.toDouble(ok: &ok);
2164
2165 if (ok) {
2166 QSvgOpacityStyle *opacity = new QSvgOpacityStyle(qBound(min: qreal(0.0), val: op, max: qreal(1.0)));
2167 node->appendStyleProperty(prop: opacity, id: attributes.id);
2168 }
2169}
2170
2171static QPainter::CompositionMode svgToQtCompositionMode(const QString &op)
2172{
2173#define NOOP qDebug()<<"Operation: "<<op<<" is not implemented"
2174 if (op == QLatin1String("clear")) {
2175 return QPainter::CompositionMode_Clear;
2176 } else if (op == QLatin1String("src")) {
2177 return QPainter::CompositionMode_Source;
2178 } else if (op == QLatin1String("dst")) {
2179 return QPainter::CompositionMode_Destination;
2180 } else if (op == QLatin1String("src-over")) {
2181 return QPainter::CompositionMode_SourceOver;
2182 } else if (op == QLatin1String("dst-over")) {
2183 return QPainter::CompositionMode_DestinationOver;
2184 } else if (op == QLatin1String("src-in")) {
2185 return QPainter::CompositionMode_SourceIn;
2186 } else if (op == QLatin1String("dst-in")) {
2187 return QPainter::CompositionMode_DestinationIn;
2188 } else if (op == QLatin1String("src-out")) {
2189 return QPainter::CompositionMode_SourceOut;
2190 } else if (op == QLatin1String("dst-out")) {
2191 return QPainter::CompositionMode_DestinationOut;
2192 } else if (op == QLatin1String("src-atop")) {
2193 return QPainter::CompositionMode_SourceAtop;
2194 } else if (op == QLatin1String("dst-atop")) {
2195 return QPainter::CompositionMode_DestinationAtop;
2196 } else if (op == QLatin1String("xor")) {
2197 return QPainter::CompositionMode_Xor;
2198 } else if (op == QLatin1String("plus")) {
2199 return QPainter::CompositionMode_Plus;
2200 } else if (op == QLatin1String("multiply")) {
2201 return QPainter::CompositionMode_Multiply;
2202 } else if (op == QLatin1String("screen")) {
2203 return QPainter::CompositionMode_Screen;
2204 } else if (op == QLatin1String("overlay")) {
2205 return QPainter::CompositionMode_Overlay;
2206 } else if (op == QLatin1String("darken")) {
2207 return QPainter::CompositionMode_Darken;
2208 } else if (op == QLatin1String("lighten")) {
2209 return QPainter::CompositionMode_Lighten;
2210 } else if (op == QLatin1String("color-dodge")) {
2211 return QPainter::CompositionMode_ColorDodge;
2212 } else if (op == QLatin1String("color-burn")) {
2213 return QPainter::CompositionMode_ColorBurn;
2214 } else if (op == QLatin1String("hard-light")) {
2215 return QPainter::CompositionMode_HardLight;
2216 } else if (op == QLatin1String("soft-light")) {
2217 return QPainter::CompositionMode_SoftLight;
2218 } else if (op == QLatin1String("difference")) {
2219 return QPainter::CompositionMode_Difference;
2220 } else if (op == QLatin1String("exclusion")) {
2221 return QPainter::CompositionMode_Exclusion;
2222 } else {
2223 NOOP;
2224 }
2225
2226 return QPainter::CompositionMode_SourceOver;
2227}
2228
2229static void parseCompOp(QSvgNode *node,
2230 const QSvgAttributes &attributes,
2231 QSvgHandler *)
2232{
2233 if (attributes.compOp.isEmpty())
2234 return;
2235 QString value = attributes.compOp.toString().trimmed();
2236
2237 if (!value.isEmpty()) {
2238 QSvgCompOpStyle *compop = new QSvgCompOpStyle(svgToQtCompositionMode(op: value));
2239 node->appendStyleProperty(prop: compop, id: attributes.id);
2240 }
2241}
2242
2243static inline QSvgNode::DisplayMode displayStringToEnum(const QString &str)
2244{
2245 if (str == QLatin1String("inline")) {
2246 return QSvgNode::InlineMode;
2247 } else if (str == QLatin1String("block")) {
2248 return QSvgNode::BlockMode;
2249 } else if (str == QLatin1String("list-item")) {
2250 return QSvgNode::ListItemMode;
2251 } else if (str == QLatin1String("run-in")) {
2252 return QSvgNode::RunInMode;
2253 } else if (str == QLatin1String("compact")) {
2254 return QSvgNode::CompactMode;
2255 } else if (str == QLatin1String("marker")) {
2256 return QSvgNode::MarkerMode;
2257 } else if (str == QLatin1String("table")) {
2258 return QSvgNode::TableMode;
2259 } else if (str == QLatin1String("inline-table")) {
2260 return QSvgNode::InlineTableMode;
2261 } else if (str == QLatin1String("table-row-group")) {
2262 return QSvgNode::TableRowGroupMode;
2263 } else if (str == QLatin1String("table-header-group")) {
2264 return QSvgNode::TableHeaderGroupMode;
2265 } else if (str == QLatin1String("table-footer-group")) {
2266 return QSvgNode::TableFooterGroupMode;
2267 } else if (str == QLatin1String("table-row")) {
2268 return QSvgNode::TableRowMode;
2269 } else if (str == QLatin1String("table-column-group")) {
2270 return QSvgNode::TableColumnGroupMode;
2271 } else if (str == QLatin1String("table-column")) {
2272 return QSvgNode::TableColumnMode;
2273 } else if (str == QLatin1String("table-cell")) {
2274 return QSvgNode::TableCellMode;
2275 } else if (str == QLatin1String("table-caption")) {
2276 return QSvgNode::TableCaptionMode;
2277 } else if (str == QLatin1String("none")) {
2278 return QSvgNode::NoneMode;
2279 } else if (str == QT_INHERIT) {
2280 return QSvgNode::InheritMode;
2281 }
2282 return QSvgNode::BlockMode;
2283}
2284
2285static void parseOthers(QSvgNode *node,
2286 const QSvgAttributes &attributes,
2287 QSvgHandler *)
2288{
2289 if (attributes.display.isEmpty())
2290 return;
2291 QString displayStr = attributes.display.toString().trimmed();
2292
2293 if (!displayStr.isEmpty()) {
2294 node->setDisplayMode(displayStringToEnum(str: displayStr));
2295 }
2296}
2297
2298static void parseExtendedAttributes(QSvgNode *node,
2299 const QSvgAttributes &attributes,
2300 QSvgHandler *handler)
2301{
2302 if (handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
2303 return;
2304
2305 if (!attributes.mask.isEmpty()) {
2306 QString maskStr = attributes.mask.toString().trimmed();
2307 if (maskStr.size() > 3 && maskStr.mid(position: 0, n: 3) == QLatin1String("url"))
2308 maskStr = maskStr.mid(position: 3, n: maskStr.size() - 3);
2309 QString maskId = idFromUrl(url: maskStr);
2310 if (maskId.startsWith(c: QLatin1Char('#'))) //TODO: handle urls and ids in a single place
2311 maskId.remove(i: 0, len: 1);
2312
2313 node->setMaskId(maskId);
2314 }
2315
2316 if (!attributes.markerStart.isEmpty() &&
2317 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) {
2318 QString markerStr = attributes.markerStart.toString().trimmed();
2319 if (markerStr.size() > 3 && markerStr.mid(position: 0, n: 3) == QLatin1String("url"))
2320 markerStr = markerStr.mid(position: 3, n: markerStr.size() - 3);
2321 QString markerId = idFromUrl(url: markerStr);
2322 if (markerId.startsWith(c: QLatin1Char('#'))) //TODO: handle urls and ids in a single place
2323 markerId.remove(i: 0, len: 1);
2324 node->setMarkerStartId(markerId);
2325 }
2326 if (!attributes.markerMid.isEmpty() &&
2327 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) {
2328 QString markerStr = attributes.markerMid.toString().trimmed();
2329 if (markerStr.size() > 3 && markerStr.mid(position: 0, n: 3) == QLatin1String("url"))
2330 markerStr = markerStr.mid(position: 3, n: markerStr.size() - 3);
2331 QString markerId = idFromUrl(url: markerStr);
2332 if (markerId.startsWith(c: QLatin1Char('#'))) //TODO: handle urls and ids in a single place
2333 markerId.remove(i: 0, len: 1);
2334 node->setMarkerMidId(markerId);
2335 }
2336 if (!attributes.markerEnd.isEmpty() &&
2337 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) {
2338 QString markerStr = attributes.markerEnd.toString().trimmed();
2339 if (markerStr.size() > 3 && markerStr.mid(position: 0, n: 3) == QLatin1String("url"))
2340 markerStr = markerStr.mid(position: 3, n: markerStr.size() - 3);
2341 QString markerId = idFromUrl(url: markerStr);
2342 if (markerId.startsWith(c: QLatin1Char('#'))) //TODO: handle urls and ids in a single place
2343 markerId.remove(i: 0, len: 1);
2344 node->setMarkerEndId(markerId);
2345 }
2346
2347 if (!attributes.filter.isEmpty() &&
2348 !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) {
2349 QString filterStr = attributes.filter.toString().trimmed();
2350
2351 if (filterStr.size() > 3 && filterStr.mid(position: 0, n: 3) == QLatin1String("url"))
2352 filterStr = filterStr.mid(position: 3, n: filterStr.size() - 3);
2353 QString filterId = idFromUrl(url: filterStr);
2354 if (filterId.startsWith(c: QLatin1Char('#'))) //TODO: handle urls and ids in a single place
2355 filterId.remove(i: 0, len: 1);
2356 node->setFilterId(filterId);
2357 }
2358
2359}
2360
2361static void parseRenderingHints(QSvgNode *node,
2362 const QSvgAttributes &attributes,
2363 QSvgHandler *)
2364{
2365 if (attributes.imageRendering.isEmpty())
2366 return;
2367
2368 QString ir = attributes.imageRendering.toString().trimmed();
2369 QSvgQualityStyle *p = new QSvgQualityStyle(0);
2370 if (ir == QLatin1String("auto"))
2371 p->setImageRendering(QSvgQualityStyle::ImageRenderingAuto);
2372 else if (ir == QLatin1String("optimizeSpeed"))
2373 p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeSpeed);
2374 else if (ir == QLatin1String("optimizeQuality"))
2375 p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeQuality);
2376 node->appendStyleProperty(prop: p, id: attributes.id);
2377}
2378
2379
2380static bool parseStyle(QSvgNode *node,
2381 const QSvgAttributes &attributes,
2382 QSvgHandler *handler)
2383{
2384 parseColor(node, attributes, handler);
2385 parseBrush(node, attributes, handler);
2386 parsePen(node, attributes, handler);
2387 parseFont(node, attributes, handler);
2388 parseTransform(node, attributes, handler);
2389 parseVisibility(node, attributes, handler);
2390 parseOpacity(node, attributes, handler);
2391 parseCompOp(node, attributes, handler);
2392 parseRenderingHints(node, attributes, handler);
2393 parseOthers(node, attributes, handler);
2394 parseExtendedAttributes(node, attributes, handler);
2395
2396#if 0
2397 value = attributes.value("audio-level");
2398
2399 value = attributes.value("color-rendering");
2400
2401 value = attributes.value("display-align");
2402
2403 value = attributes.value("image-rendering");
2404
2405 value = attributes.value("line-increment");
2406
2407 value = attributes.value("pointer-events");
2408
2409 value = attributes.value("shape-rendering");
2410
2411 value = attributes.value("solid-color");
2412
2413 value = attributes.value("solid-opacity");
2414
2415 value = attributes.value("text-rendering");
2416
2417 value = attributes.value("vector-effect");
2418
2419 value = attributes.value("viewport-fill");
2420
2421 value = attributes.value("viewport-fill-opacity");
2422#endif
2423 return true;
2424}
2425
2426static bool parseStyle(QSvgNode *node,
2427 const QXmlStreamAttributes &attrs,
2428 QSvgHandler *handler)
2429{
2430 return parseStyle(node, attributes: QSvgAttributes(attrs, handler), handler);
2431}
2432
2433static bool parseAnchorNode(QSvgNode *parent,
2434 const QXmlStreamAttributes &attributes,
2435 QSvgHandler *)
2436{
2437 Q_UNUSED(parent); Q_UNUSED(attributes);
2438 return true;
2439}
2440
2441static bool parseAnimateNode(QSvgNode *parent,
2442 const QXmlStreamAttributes &attributes,
2443 QSvgHandler *)
2444{
2445 Q_UNUSED(parent); Q_UNUSED(attributes);
2446 return true;
2447}
2448
2449static int parseClockValue(QStringView str, bool *ok)
2450{
2451 int res = 0;
2452 int ms = 1000;
2453 str = str.trimmed();
2454 if (str.endsWith(s: QLatin1String("ms"))) {
2455 str.chop(n: 2);
2456 ms = 1;
2457 } else if (str.endsWith(s: QLatin1String("s"))) {
2458 str.chop(n: 1);
2459 }
2460 double val = ms * toDouble(str, ok);
2461 if (ok) {
2462 if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
2463 res = static_cast<int>(val);
2464 else
2465 *ok = false;
2466 }
2467 return res;
2468}
2469
2470static bool parseBaseAnimate(QSvgNode *parent,
2471 const QXmlStreamAttributes &attributes,
2472 QSvgAnimate *anim,
2473 QSvgHandler *handler)
2474{
2475 QString beginStr = attributes.value(qualifiedName: QLatin1String("begin")).toString();
2476 QString durStr = attributes.value(qualifiedName: QLatin1String("dur")).toString();
2477 QString endStr = attributes.value(qualifiedName: QLatin1String("end")).toString();
2478 QString repeatStr = attributes.value(qualifiedName: QLatin1String("repeatCount")).toString();
2479
2480 bool ok = true;
2481 int begin = parseClockValue(str: beginStr, ok: &ok);
2482 if (!ok)
2483 return false;
2484 int dur = parseClockValue(str: durStr, ok: &ok);
2485 if (!ok)
2486 return false;
2487 int end = parseClockValue(str: endStr, ok: &ok);
2488 if (!ok)
2489 return false;
2490 qreal repeatCount = (repeatStr == QLatin1String("indefinite")) ? -1 :
2491 qMax(a: 1.0, b: toDouble(str: repeatStr));
2492
2493 anim->setRunningTime(startMs: begin, durMs: dur, endMs: end, by: 0);
2494 anim->setRepeatCount(repeatCount);
2495
2496 parent->appendStyleProperty(prop: anim, id: QString());
2497 parent->document()->setAnimated(true);
2498
2499 handler->setAnimPeriod(start: begin, end);
2500 return true;
2501}
2502
2503static bool parseAnimateColorNode(QSvgNode *parent,
2504 const QXmlStreamAttributes &attributes,
2505 QSvgHandler *handler)
2506{
2507 QStringView fromStr = attributes.value(qualifiedName: QLatin1String("from"));
2508 QStringView toStr = attributes.value(qualifiedName: QLatin1String("to"));
2509 QString valuesStr = attributes.value(qualifiedName: QLatin1String("values")).toString();
2510 QString targetStr = attributes.value(qualifiedName: QLatin1String("attributeName")).toString();
2511 QString fillStr = attributes.value(qualifiedName: QLatin1String("fill")).toString();
2512
2513 if (targetStr != QLatin1String("fill") && targetStr != QLatin1String("stroke"))
2514 return false;
2515
2516 QList<QColor> colors;
2517 if (valuesStr.isEmpty()) {
2518 QColor startColor, endColor;
2519 resolveColor(colorStr: fromStr, color&: startColor, handler);
2520 resolveColor(colorStr: toStr, color&: endColor, handler);
2521 colors.reserve(asize: 2);
2522 colors.append(t: startColor);
2523 colors.append(t: endColor);
2524 } else {
2525 QStringList str = valuesStr.split(sep: QLatin1Char(';'));
2526 colors.reserve(asize: str.size());
2527 QStringList::const_iterator itr;
2528 for (itr = str.constBegin(); itr != str.constEnd(); ++itr) {
2529 QColor color;
2530 resolveColor(colorStr: *itr, color, handler);
2531 colors.append(t: color);
2532 }
2533 }
2534
2535 QSvgAnimateColor *anim = new QSvgAnimateColor();
2536 parseBaseAnimate(parent, attributes, anim, handler);
2537
2538 anim->setArgs(fill: (targetStr == QLatin1String("fill")), colors);
2539 anim->setFreeze(fillStr == QLatin1String("freeze"));
2540
2541 return true;
2542}
2543
2544static bool parseAimateMotionNode(QSvgNode *parent,
2545 const QXmlStreamAttributes &attributes,
2546 QSvgHandler *)
2547{
2548 Q_UNUSED(parent); Q_UNUSED(attributes);
2549 return true;
2550}
2551
2552static void parseNumberTriplet(QList<qreal> &values, const QChar *&s)
2553{
2554 QList<qreal> list = parseNumbersList(str&: s);
2555 values << list;
2556 for (int i = 3 - list.size(); i > 0; --i)
2557 values.append(t: 0.0);
2558}
2559
2560static bool parseAnimateTransformNode(QSvgNode *parent,
2561 const QXmlStreamAttributes &attributes,
2562 QSvgHandler *handler)
2563{
2564 QString typeStr = attributes.value(qualifiedName: QLatin1String("type")).toString();
2565 QString values = attributes.value(qualifiedName: QLatin1String("values")).toString();
2566 QString fillStr = attributes.value(qualifiedName: QLatin1String("fill")).toString();
2567 QString fromStr = attributes.value(qualifiedName: QLatin1String("from")).toString();
2568 QString toStr = attributes.value(qualifiedName: QLatin1String("to")).toString();
2569 QString byStr = attributes.value(qualifiedName: QLatin1String("by")).toString();
2570 QString addtv = attributes.value(qualifiedName: QLatin1String("additive")).toString();
2571
2572 QSvgAnimateTransform::Additive additive = QSvgAnimateTransform::Replace;
2573 if (addtv == QLatin1String("sum"))
2574 additive = QSvgAnimateTransform::Sum;
2575
2576 QList<qreal> vals;
2577 if (values.isEmpty()) {
2578 const QChar *s;
2579 if (fromStr.isEmpty()) {
2580 if (!byStr.isEmpty()) {
2581 // By-animation.
2582 additive = QSvgAnimateTransform::Sum;
2583 vals.append(t: 0.0);
2584 vals.append(t: 0.0);
2585 vals.append(t: 0.0);
2586 parseNumberTriplet(values&: vals, s&: s = byStr.constData());
2587 } else {
2588 // To-animation not defined.
2589 return false;
2590 }
2591 } else {
2592 if (!toStr.isEmpty()) {
2593 // From-to-animation.
2594 parseNumberTriplet(values&: vals, s&: s = fromStr.constData());
2595 parseNumberTriplet(values&: vals, s&: s = toStr.constData());
2596 } else if (!byStr.isEmpty()) {
2597 // From-by-animation.
2598 parseNumberTriplet(values&: vals, s&: s = fromStr.constData());
2599 parseNumberTriplet(values&: vals, s&: s = byStr.constData());
2600 for (int i = vals.size() - 3; i < vals.size(); ++i)
2601 vals[i] += vals[i - 3];
2602 } else {
2603 return false;
2604 }
2605 }
2606 } else {
2607 const QChar *s = values.constData();
2608 while (s && *s != QLatin1Char(0)) {
2609 parseNumberTriplet(values&: vals, s);
2610 if (*s == QLatin1Char(0))
2611 break;
2612 ++s;
2613 }
2614 }
2615 if (vals.size() % 3 != 0)
2616 return false;
2617
2618 QSvgAnimateTransform::TransformType type = QSvgAnimateTransform::Empty;
2619 if (typeStr == QLatin1String("translate")) {
2620 type = QSvgAnimateTransform::Translate;
2621 } else if (typeStr == QLatin1String("scale")) {
2622 type = QSvgAnimateTransform::Scale;
2623 } else if (typeStr == QLatin1String("rotate")) {
2624 type = QSvgAnimateTransform::Rotate;
2625 } else if (typeStr == QLatin1String("skewX")) {
2626 type = QSvgAnimateTransform::SkewX;
2627 } else if (typeStr == QLatin1String("skewY")) {
2628 type = QSvgAnimateTransform::SkewY;
2629 } else {
2630 return false;
2631 }
2632
2633 QSvgAnimateTransform *anim = new QSvgAnimateTransform();
2634 parseBaseAnimate(parent, attributes, anim, handler);
2635
2636 anim->setArgs(type, additive, args: vals);
2637 anim->setFreeze(fillStr == QLatin1String("freeze"));
2638
2639 return true;
2640}
2641
2642static QSvgNode * createAnimationNode(QSvgNode *parent,
2643 const QXmlStreamAttributes &attributes,
2644 QSvgHandler *)
2645{
2646 Q_UNUSED(parent); Q_UNUSED(attributes);
2647 return 0;
2648}
2649
2650static bool parseAudioNode(QSvgNode *parent,
2651 const QXmlStreamAttributes &attributes,
2652 QSvgHandler *)
2653{
2654 Q_UNUSED(parent); Q_UNUSED(attributes);
2655 return true;
2656}
2657
2658static QSvgNode *createCircleNode(QSvgNode *parent,
2659 const QXmlStreamAttributes &attributes,
2660 QSvgHandler *)
2661{
2662 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
2663 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
2664 const QStringView r = attributes.value(qualifiedName: QLatin1String("r"));
2665 qreal ncx = toDouble(str: cx);
2666 qreal ncy = toDouble(str: cy);
2667 qreal nr = toDouble(str: r);
2668 if (nr < 0.0)
2669 return nullptr;
2670
2671 QRectF rect(ncx-nr, ncy-nr, nr*2, nr*2);
2672 QSvgNode *circle = new QSvgCircle(parent, rect);
2673 return circle;
2674}
2675
2676static QSvgNode *createDefsNode(QSvgNode *parent,
2677 const QXmlStreamAttributes &attributes,
2678 QSvgHandler *)
2679{
2680 Q_UNUSED(attributes);
2681 QSvgDefs *defs = new QSvgDefs(parent);
2682 return defs;
2683}
2684
2685static bool parseDiscardNode(QSvgNode *parent,
2686 const QXmlStreamAttributes &attributes,
2687 QSvgHandler *)
2688{
2689 Q_UNUSED(parent); Q_UNUSED(attributes);
2690 return true;
2691}
2692
2693static QSvgNode *createEllipseNode(QSvgNode *parent,
2694 const QXmlStreamAttributes &attributes,
2695 QSvgHandler *)
2696{
2697 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
2698 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
2699 const QStringView rx = attributes.value(qualifiedName: QLatin1String("rx"));
2700 const QStringView ry = attributes.value(qualifiedName: QLatin1String("ry"));
2701 qreal ncx = toDouble(str: cx);
2702 qreal ncy = toDouble(str: cy);
2703 qreal nrx = toDouble(str: rx);
2704 qreal nry = toDouble(str: ry);
2705
2706 QRectF rect(ncx-nrx, ncy-nry, nrx*2, nry*2);
2707 QSvgNode *ellipse = new QSvgEllipse(parent, rect);
2708 return ellipse;
2709}
2710
2711static QSvgStyleProperty *createFontNode(QSvgNode *parent,
2712 const QXmlStreamAttributes &attributes,
2713 QSvgHandler *)
2714{
2715 const QStringView hax = attributes.value(qualifiedName: QLatin1String("horiz-adv-x"));
2716 QString myId = someId(attributes);
2717
2718 qreal horizAdvX = toDouble(str: hax);
2719
2720 while (parent && parent->type() != QSvgNode::Doc) {
2721 parent = parent->parent();
2722 }
2723
2724 if (parent && !myId.isEmpty()) {
2725 QSvgTinyDocument *doc = static_cast<QSvgTinyDocument*>(parent);
2726 QSvgFont *font = doc->svgFont(family: myId);
2727 if (!font) {
2728 font = new QSvgFont(horizAdvX);
2729 font->setFamilyName(myId);
2730 doc->addSvgFont(font);
2731 }
2732 return new QSvgFontStyle(font, doc);
2733 }
2734 return nullptr;
2735}
2736
2737static bool parseFontFaceNode(QSvgStyleProperty *parent,
2738 const QXmlStreamAttributes &attributes,
2739 QSvgHandler *)
2740{
2741 if (parent->type() != QSvgStyleProperty::FONT) {
2742 return false;
2743 }
2744
2745 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2746 QSvgFont *font = style->svgFont();
2747 QString name = attributes.value(qualifiedName: QLatin1String("font-family")).toString();
2748 const QStringView unitsPerEmStr = attributes.value(qualifiedName: QLatin1String("units-per-em"));
2749
2750 qreal unitsPerEm = toDouble(str: unitsPerEmStr);
2751 if (!unitsPerEm)
2752 unitsPerEm = QSvgFont::DEFAULT_UNITS_PER_EM;
2753
2754 if (!name.isEmpty())
2755 font->setFamilyName(name);
2756 font->setUnitsPerEm(unitsPerEm);
2757
2758 if (!font->familyName().isEmpty())
2759 if (!style->doc()->svgFont(family: font->familyName()))
2760 style->doc()->addSvgFont(font);
2761
2762 return true;
2763}
2764
2765static bool parseFontFaceNameNode(QSvgStyleProperty *parent,
2766 const QXmlStreamAttributes &attributes,
2767 QSvgHandler *)
2768{
2769 if (parent->type() != QSvgStyleProperty::FONT) {
2770 return false;
2771 }
2772
2773 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2774 QSvgFont *font = style->svgFont();
2775 QString name = attributes.value(qualifiedName: QLatin1String("name")).toString();
2776
2777 if (!name.isEmpty())
2778 font->setFamilyName(name);
2779
2780 if (!font->familyName().isEmpty())
2781 if (!style->doc()->svgFont(family: font->familyName()))
2782 style->doc()->addSvgFont(font);
2783
2784 return true;
2785}
2786
2787static bool parseFontFaceSrcNode(QSvgStyleProperty *parent,
2788 const QXmlStreamAttributes &attributes,
2789 QSvgHandler *)
2790{
2791 Q_UNUSED(parent); Q_UNUSED(attributes);
2792 return true;
2793}
2794
2795static bool parseFontFaceUriNode(QSvgStyleProperty *parent,
2796 const QXmlStreamAttributes &attributes,
2797 QSvgHandler *)
2798{
2799 Q_UNUSED(parent); Q_UNUSED(attributes);
2800 return true;
2801}
2802
2803static bool parseForeignObjectNode(QSvgNode *parent,
2804 const QXmlStreamAttributes &attributes,
2805 QSvgHandler *)
2806{
2807 Q_UNUSED(parent); Q_UNUSED(attributes);
2808 return true;
2809}
2810
2811static QSvgNode *createGNode(QSvgNode *parent,
2812 const QXmlStreamAttributes &attributes,
2813 QSvgHandler *)
2814{
2815 Q_UNUSED(attributes);
2816 QSvgG *node = new QSvgG(parent);
2817 return node;
2818}
2819
2820static bool parseGlyphNode(QSvgStyleProperty *parent,
2821 const QXmlStreamAttributes &attributes,
2822 QSvgHandler *)
2823{
2824 if (parent->type() != QSvgStyleProperty::FONT) {
2825 return false;
2826 }
2827
2828 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2829 QSvgFont *font = style->svgFont();
2830 createSvgGlyph(font, attributes);
2831 return true;
2832}
2833
2834static bool parseHandlerNode(QSvgNode *parent,
2835 const QXmlStreamAttributes &attributes,
2836 QSvgHandler *)
2837{
2838 Q_UNUSED(parent); Q_UNUSED(attributes);
2839 return true;
2840}
2841
2842static bool parseHkernNode(QSvgNode *parent,
2843 const QXmlStreamAttributes &attributes,
2844 QSvgHandler *)
2845{
2846 Q_UNUSED(parent); Q_UNUSED(attributes);
2847 return true;
2848}
2849
2850static QSvgNode *createImageNode(QSvgNode *parent,
2851 const QXmlStreamAttributes &attributes,
2852 QSvgHandler *handler)
2853{
2854 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
2855 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
2856 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
2857 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
2858 QString filename = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString();
2859 if (filename.isEmpty() && !handler->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly))
2860 filename = attributes.value(qualifiedName: QLatin1String("href")).toString();
2861 qreal nx = toDouble(str: x);
2862 qreal ny = toDouble(str: y);
2863 QSvgHandler::LengthType type;
2864 qreal nwidth = parseLength(str: width.toString(), type: &type, handler);
2865 nwidth = convertToPixels(len: nwidth, true, type);
2866
2867 qreal nheight = parseLength(str: height.toString(), type: &type, handler);
2868 nheight = convertToPixels(len: nheight, false, type);
2869
2870 filename = filename.trimmed();
2871 if (filename.isEmpty()) {
2872 qCWarning(lcSvgHandler) << "QSvgHandler: Image filename is empty";
2873 return 0;
2874 }
2875 if (nwidth <= 0 || nheight <= 0) {
2876 qCWarning(lcSvgHandler) << "QSvgHandler: Width or height for" << filename << "image was not greater than 0";
2877 return 0;
2878 }
2879
2880 QImage image;
2881 enum {
2882 NotLoaded,
2883 LoadedFromData,
2884 LoadedFromFile
2885 } filenameType = NotLoaded;
2886
2887 if (filename.startsWith(s: QLatin1String("data"))) {
2888 int idx = filename.lastIndexOf(s: QLatin1String("base64,"));
2889 if (idx != -1) {
2890 idx += 7;
2891 const QString dataStr = filename.mid(position: idx);
2892 QByteArray data = QByteArray::fromBase64(base64: dataStr.toLatin1());
2893 image = QImage::fromData(data);
2894 filenameType = LoadedFromData;
2895 }
2896 }
2897
2898 if (image.isNull()) {
2899 const auto *file = qobject_cast<QFile *>(object: handler->device());
2900 if (file) {
2901 QUrl url(filename);
2902 if (url.isRelative()) {
2903 QFileInfo info(file->fileName());
2904 filename = info.absoluteDir().absoluteFilePath(fileName: filename);
2905 }
2906 }
2907
2908 if (handler->trustedSourceMode() || !QImageReader::imageFormat(fileName: filename).startsWith(bv: "svg")) {
2909 image = QImage(filename);
2910 filenameType = LoadedFromFile;
2911 }
2912 }
2913
2914 if (image.isNull()) {
2915 qCWarning(lcSvgHandler) << "Could not create image from" << filename;
2916 return 0;
2917 }
2918
2919 if (image.format() == QImage::Format_ARGB32)
2920 image = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
2921
2922 QSvgNode *img = new QSvgImage(parent,
2923 image,
2924 filenameType == LoadedFromFile ? filename : QString{},
2925 QRectF(nx,
2926 ny,
2927 nwidth,
2928 nheight));
2929 return img;
2930}
2931
2932static QSvgNode *createLineNode(QSvgNode *parent,
2933 const QXmlStreamAttributes &attributes,
2934 QSvgHandler *)
2935{
2936 const QStringView x1 = attributes.value(qualifiedName: QLatin1String("x1"));
2937 const QStringView y1 = attributes.value(qualifiedName: QLatin1String("y1"));
2938 const QStringView x2 = attributes.value(qualifiedName: QLatin1String("x2"));
2939 const QStringView y2 = attributes.value(qualifiedName: QLatin1String("y2"));
2940 qreal nx1 = toDouble(str: x1);
2941 qreal ny1 = toDouble(str: y1);
2942 qreal nx2 = toDouble(str: x2);
2943 qreal ny2 = toDouble(str: y2);
2944
2945 QLineF lineBounds(nx1, ny1, nx2, ny2);
2946 QSvgNode *line = new QSvgLine(parent, lineBounds);
2947 return line;
2948}
2949
2950
2951static void parseBaseGradient(QSvgNode *node,
2952 const QXmlStreamAttributes &attributes,
2953 QSvgGradientStyle *gradProp,
2954 QSvgHandler *handler)
2955{
2956 QString link = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString();
2957 QStringView trans = attributes.value(qualifiedName: QLatin1String("gradientTransform"));
2958 QString spread = attributes.value(qualifiedName: QLatin1String("spreadMethod")).toString();
2959 QString units = attributes.value(qualifiedName: QLatin1String("gradientUnits")).toString();
2960 QStringView colorStr = attributes.value(qualifiedName: QLatin1String("color"));
2961 QStringView colorOpacityStr = attributes.value(qualifiedName: QLatin1String("color-opacity"));
2962
2963 QColor color;
2964 if (constructColor(colorStr, opacity: colorOpacityStr, color, handler)) {
2965 handler->popColor();
2966 handler->pushColor(color);
2967 }
2968
2969 QTransform matrix;
2970 QGradient *grad = gradProp->qgradient();
2971 if (node && !link.isEmpty()) {
2972 QSvgStyleProperty *prop = node->styleProperty(id: link);
2973 //qDebug()<<"inherited "<<prop<<" ("<<link<<")";
2974 if (prop && prop->type() == QSvgStyleProperty::GRADIENT) {
2975 QSvgGradientStyle *inherited =
2976 static_cast<QSvgGradientStyle*>(prop);
2977 if (!inherited->stopLink().isEmpty()) {
2978 gradProp->setStopLink(link: inherited->stopLink(), doc: handler->document());
2979 } else {
2980 grad->setStops(inherited->qgradient()->stops());
2981 gradProp->setGradientStopsSet(inherited->gradientStopsSet());
2982 }
2983
2984 matrix = inherited->qtransform();
2985 } else {
2986 gradProp->setStopLink(link, doc: handler->document());
2987 }
2988 }
2989
2990 if (!trans.isEmpty()) {
2991 matrix = parseTransformationMatrix(value: trans);
2992 gradProp->setTransform(matrix);
2993 } else if (!matrix.isIdentity()) {
2994 gradProp->setTransform(matrix);
2995 }
2996
2997 if (!spread.isEmpty()) {
2998 if (spread == QLatin1String("pad")) {
2999 grad->setSpread(QGradient::PadSpread);
3000 } else if (spread == QLatin1String("reflect")) {
3001 grad->setSpread(QGradient::ReflectSpread);
3002 } else if (spread == QLatin1String("repeat")) {
3003 grad->setSpread(QGradient::RepeatSpread);
3004 }
3005 }
3006
3007 if (units.isEmpty() || units == QLatin1String("objectBoundingBox")) {
3008 grad->setCoordinateMode(QGradient::ObjectMode);
3009 }
3010}
3011
3012static QSvgStyleProperty *createLinearGradientNode(QSvgNode *node,
3013 const QXmlStreamAttributes &attributes,
3014 QSvgHandler *handler)
3015{
3016 const QStringView x1 = attributes.value(qualifiedName: QLatin1String("x1"));
3017 const QStringView y1 = attributes.value(qualifiedName: QLatin1String("y1"));
3018 const QStringView x2 = attributes.value(qualifiedName: QLatin1String("x2"));
3019 const QStringView y2 = attributes.value(qualifiedName: QLatin1String("y2"));
3020
3021 qreal nx1 = 0.0;
3022 qreal ny1 = 0.0;
3023 qreal nx2 = 1.0;
3024 qreal ny2 = 0.0;
3025
3026 if (!x1.isEmpty())
3027 nx1 = convertToNumber(str: x1, handler);
3028 if (!y1.isEmpty())
3029 ny1 = convertToNumber(str: y1, handler);
3030 if (!x2.isEmpty())
3031 nx2 = convertToNumber(str: x2, handler);
3032 if (!y2.isEmpty())
3033 ny2 = convertToNumber(str: y2, handler);
3034
3035 QSvgNode *itr = node;
3036 while (itr && itr->type() != QSvgNode::Doc) {
3037 itr = itr->parent();
3038 }
3039
3040 QLinearGradient *grad = new QLinearGradient(nx1, ny1, nx2, ny2);
3041 grad->setInterpolationMode(QGradient::ComponentInterpolation);
3042 QSvgGradientStyle *prop = new QSvgGradientStyle(grad);
3043 parseBaseGradient(node, attributes, gradProp: prop, handler);
3044
3045 return prop;
3046}
3047
3048static bool parseMetadataNode(QSvgNode *parent,
3049 const QXmlStreamAttributes &attributes,
3050 QSvgHandler *)
3051{
3052 Q_UNUSED(parent); Q_UNUSED(attributes);
3053 return true;
3054}
3055
3056static bool parseMissingGlyphNode(QSvgStyleProperty *parent,
3057 const QXmlStreamAttributes &attributes,
3058 QSvgHandler *)
3059{
3060 if (parent->type() != QSvgStyleProperty::FONT) {
3061 return false;
3062 }
3063
3064 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
3065 QSvgFont *font = style->svgFont();
3066 createSvgGlyph(font, attributes);
3067 return true;
3068}
3069
3070static bool parseMpathNode(QSvgNode *parent,
3071 const QXmlStreamAttributes &attributes,
3072 QSvgHandler *)
3073{
3074 Q_UNUSED(parent); Q_UNUSED(attributes);
3075 return true;
3076}
3077
3078static bool parseMaskNode(QSvgNode *parent,
3079 const QXmlStreamAttributes &attributes,
3080 QSvgHandler *)
3081{
3082 Q_UNUSED(parent); Q_UNUSED(attributes);
3083 return true;
3084}
3085
3086static bool parseMarkerNode(QSvgNode *,
3087 const QXmlStreamAttributes &,
3088 QSvgHandler *)
3089{
3090 return true;
3091}
3092
3093static QSvgNode *createMaskNode(QSvgNode *parent,
3094 const QXmlStreamAttributes &attributes,
3095 QSvgHandler *handler)
3096{
3097 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
3098 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
3099 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
3100 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
3101 const QStringView mU = attributes.value(qualifiedName: QLatin1String("maskUnits"));
3102 const QStringView mCU = attributes.value(qualifiedName: QLatin1String("maskContentUnits"));
3103
3104 QtSvg::UnitTypes nmU = mU.contains(s: QLatin1String("userSpaceOnUse")) ?
3105 QtSvg::UnitTypes::userSpaceOnUse : QtSvg::UnitTypes::objectBoundingBox;
3106
3107 QtSvg::UnitTypes nmCU = mCU.contains(s: QLatin1String("objectBoundingBox")) ?
3108 QtSvg::UnitTypes::objectBoundingBox : QtSvg::UnitTypes::userSpaceOnUse;
3109
3110 bool ok;
3111 QSvgHandler::LengthType type;
3112
3113 QtSvg::UnitTypes nmUx = nmU;
3114 QtSvg::UnitTypes nmUy = nmU;
3115 QtSvg::UnitTypes nmUw = nmU;
3116 QtSvg::UnitTypes nmUh = nmU;
3117 qreal nx = parseLength(str: x.toString(), type: &type, handler, ok: &ok);
3118 nx = convertToPixels(len: nx, true, type);
3119 if (x.isEmpty() || !ok) {
3120 nx = -0.1;
3121 nmUx = QtSvg::UnitTypes::objectBoundingBox;
3122 } else if (type == QSvgHandler::LT_PERCENT && nmU == QtSvg::UnitTypes::userSpaceOnUse) {
3123 nx = nx / 100. * parent->document()->viewBox().width();
3124 } else if (type == QSvgHandler::LT_PERCENT) {
3125 nx = nx / 100.;
3126 }
3127
3128 qreal ny = parseLength(str: y.toString(), type: &type, handler, ok: &ok);
3129 ny = convertToPixels(len: ny, true, type);
3130 if (y.isEmpty() || !ok) {
3131 ny = -0.1;
3132 nmUy = QtSvg::UnitTypes::objectBoundingBox;
3133 } else if (type == QSvgHandler::LT_PERCENT && nmU == QtSvg::UnitTypes::userSpaceOnUse) {
3134 ny = ny / 100. * parent->document()->viewBox().height();
3135 } else if (type == QSvgHandler::LT_PERCENT) {
3136 ny = ny / 100.;
3137 }
3138
3139 qreal nwidth = parseLength(str: width.toString(), type: &type, handler, ok: &ok);
3140 nwidth = convertToPixels(len: nwidth, true, type);
3141 if (width.isEmpty() || !ok) {
3142 nwidth = 1.2;
3143 nmUw = QtSvg::UnitTypes::objectBoundingBox;
3144 } else if (type == QSvgHandler::LT_PERCENT && nmU == QtSvg::UnitTypes::userSpaceOnUse) {
3145 nwidth = nwidth / 100. * parent->document()->viewBox().width();
3146 } else if (type == QSvgHandler::LT_PERCENT) {
3147 nwidth = nwidth / 100.;
3148 }
3149
3150 qreal nheight = parseLength(str: height.toString(), type: &type, handler, ok: &ok);
3151 nheight = convertToPixels(len: nheight, true, type);
3152 if (height.isEmpty() || !ok) {
3153 nheight = 1.2;
3154 nmUh = QtSvg::UnitTypes::objectBoundingBox;
3155 } else if (type == QSvgHandler::LT_PERCENT && nmU == QtSvg::UnitTypes::userSpaceOnUse) {
3156 nheight = nheight / 100. * parent->document()->viewBox().height();
3157 } else if (type == QSvgHandler::LT_PERCENT) {
3158 nheight = nheight / 100.;
3159 }
3160
3161 QRectF bounds(nx, ny, nwidth, nheight);
3162 if (bounds.isEmpty())
3163 return nullptr;
3164
3165 QSvgNode *mask = new QSvgMask(parent, QSvgRectF(bounds, nmUx, nmUy, nmUw, nmUh), nmCU);
3166
3167 return mask;
3168}
3169
3170static void parseFilterBounds(QSvgNode *, const QXmlStreamAttributes &attributes,
3171 QSvgHandler *handler, QSvgRectF *rect)
3172{
3173 const QStringView xStr = attributes.value(qualifiedName: QLatin1String("x"));
3174 const QStringView yStr = attributes.value(qualifiedName: QLatin1String("y"));
3175 const QStringView widthStr = attributes.value(qualifiedName: QLatin1String("width"));
3176 const QStringView heightStr = attributes.value(qualifiedName: QLatin1String("height"));
3177
3178 qreal x = 0;
3179 if (!xStr.isEmpty()) {
3180 QSvgHandler::LengthType type;
3181 x = parseLength(str: xStr.toString(), type: &type, handler);
3182 if (type != QSvgHandler::LT_PT) {
3183 x = convertToPixels(len: x, true, type);
3184 rect->setUnitX(QtSvg::UnitTypes::userSpaceOnUse);
3185 }
3186 if (type == QSvgHandler::LT_PERCENT) {
3187 x /= 100.;
3188 rect->setUnitX(QtSvg::UnitTypes::objectBoundingBox);
3189 }
3190 rect->setX(x);
3191 }
3192 qreal y = 0;
3193 if (!yStr.isEmpty()) {
3194 QSvgHandler::LengthType type;
3195 y = parseLength(str: yStr.toString(), type: &type, handler);
3196 if (type != QSvgHandler::LT_PT) {
3197 y = convertToPixels(len: y, false, type);
3198 rect->setUnitY(QtSvg::UnitTypes::userSpaceOnUse);
3199 }
3200 if (type == QSvgHandler::LT_PERCENT) {
3201 y /= 100.;
3202 rect->setUnitX(QtSvg::UnitTypes::objectBoundingBox);
3203 }
3204 rect->setY(y);
3205 }
3206 qreal width = 0;
3207 if (!widthStr.isEmpty()) {
3208 QSvgHandler::LengthType type;
3209 width = parseLength(str: widthStr.toString(), type: &type, handler);
3210 if (type != QSvgHandler::LT_PT) {
3211 width = convertToPixels(len: width, true, type);
3212 rect->setUnitW(QtSvg::UnitTypes::userSpaceOnUse);
3213 }
3214 if (type == QSvgHandler::LT_PERCENT) {
3215 width /= 100.;
3216 rect->setUnitX(QtSvg::UnitTypes::objectBoundingBox);
3217 }
3218 rect->setWidth(width);
3219 }
3220 qreal height = 0;
3221 if (!heightStr.isEmpty()) {
3222 QSvgHandler::LengthType type;
3223 height = parseLength(str: heightStr.toString(), type: &type, handler);
3224 if (type != QSvgHandler::LT_PT) {
3225 height = convertToPixels(len: height, false, type);
3226 rect->setUnitH(QtSvg::UnitTypes::userSpaceOnUse);
3227 }
3228 if (type == QSvgHandler::LT_PERCENT) {
3229 height /= 100.;
3230 rect->setUnitX(QtSvg::UnitTypes::objectBoundingBox);
3231 }
3232 rect->setHeight(height);
3233 }
3234}
3235
3236static QSvgNode *createFilterNode(QSvgNode *parent,
3237 const QXmlStreamAttributes &attributes,
3238 QSvgHandler *handler)
3239{
3240 QString fU = attributes.value(qualifiedName: QLatin1String("filterUnits")).toString();
3241 QString pU = attributes.value(qualifiedName: QLatin1String("primitiveUnits")).toString();
3242
3243 QtSvg::UnitTypes filterUnits = fU.contains(s: QLatin1String("userSpaceOnUse")) ?
3244 QtSvg::UnitTypes::userSpaceOnUse : QtSvg::UnitTypes::objectBoundingBox;
3245
3246 QtSvg::UnitTypes primitiveUnits = pU.contains(s: QLatin1String("objectBoundingBox")) ?
3247 QtSvg::UnitTypes::objectBoundingBox : QtSvg::UnitTypes::userSpaceOnUse;
3248
3249 // https://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
3250 // If ‘x’ or ‘y’ is not specified, the effect is as if a value of -10% were specified.
3251 // If ‘width’ or ‘height’ is not specified, the effect is as if a value of 120% were specified.
3252 QSvgRectF rect;
3253 if (filterUnits == QtSvg::UnitTypes::userSpaceOnUse) {
3254 qreal width = parent->document()->viewBox().width();
3255 qreal height = parent->document()->viewBox().height();
3256 rect = QSvgRectF(QRectF(-0.1 * width, -0.1 * height, 1.2 * width, 1.2 * height),
3257 QtSvg::UnitTypes::userSpaceOnUse, QtSvg::UnitTypes::userSpaceOnUse,
3258 QtSvg::UnitTypes::userSpaceOnUse, QtSvg::UnitTypes::userSpaceOnUse);
3259 } else {
3260 rect = QSvgRectF(QRectF(-0.1, -0.1, 1.2, 1.2),
3261 QtSvg::UnitTypes::objectBoundingBox, QtSvg::UnitTypes::objectBoundingBox,
3262 QtSvg::UnitTypes::objectBoundingBox, QtSvg::UnitTypes::objectBoundingBox);
3263 }
3264
3265 parseFilterBounds(parent, attributes, handler, rect: &rect);
3266
3267 QSvgNode *filter = new QSvgFilterContainer(parent, rect, filterUnits, primitiveUnits);
3268 return filter;
3269}
3270
3271static void parseFilterAttributes(QSvgNode *parent, const QXmlStreamAttributes &attributes,
3272 QSvgHandler *handler, QString *inString, QString *outString,
3273 QSvgRectF *rect)
3274{
3275 *inString = attributes.value(qualifiedName: QLatin1String("in")).toString();
3276 *outString = attributes.value(qualifiedName: QLatin1String("result")).toString();
3277
3278 // https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion
3279 // the default subregion is 0%,0%,100%,100%, where as a special-case the percentages are
3280 // relative to the dimensions of the filter region, thus making the the default filter primitive
3281 // subregion equal to the filter region.
3282 *rect = QSvgRectF(QRectF(0, 0, 1.0, 1.0),
3283 QtSvg::UnitTypes::unknown, QtSvg::UnitTypes::unknown,
3284 QtSvg::UnitTypes::unknown, QtSvg::UnitTypes::unknown);
3285 // if we recognize unit == unknown we use the filter as a reference instead of the item, see
3286 // QSvgFeFilterPrimitive::localSubRegion
3287
3288 parseFilterBounds(parent, attributes, handler, rect);
3289}
3290
3291static QSvgNode *createFeColorMatrixNode(QSvgNode *parent,
3292 const QXmlStreamAttributes &attributes,
3293 QSvgHandler *handler)
3294{
3295 const QString typeString = attributes.value(qualifiedName: QLatin1String("type")).toString();
3296 QString valuesString = attributes.value(qualifiedName: QLatin1String("values")).toString();
3297
3298 QString inputString;
3299 QString outputString;
3300 QSvgRectF rect;
3301
3302 QSvgFeColorMatrix::ColorShiftType type;
3303 QSvgFeColorMatrix::Matrix values;
3304 values.fill(value: 0);
3305
3306 parseFilterAttributes(parent, attributes, handler,
3307 inString: &inputString, outString: &outputString, rect: &rect);
3308
3309 if (typeString.startsWith(s: QLatin1String("saturate")))
3310 type = QSvgFeColorMatrix::ColorShiftType::Saturate;
3311 else if (typeString.startsWith(s: QLatin1String("hueRotate")))
3312 type = QSvgFeColorMatrix::ColorShiftType::HueRotate;
3313 else if (typeString.startsWith(s: QLatin1String("luminanceToAlpha")))
3314 type = QSvgFeColorMatrix::ColorShiftType::LuminanceToAlpha;
3315 else
3316 type = QSvgFeColorMatrix::ColorShiftType::Matrix;
3317
3318 if (!valuesString.isEmpty()) {
3319 static QRegularExpression delimiterRE(QLatin1String("[,\\s]"));
3320 const QStringList valueStringList = valuesString.split(sep: delimiterRE, behavior: Qt::SkipEmptyParts);
3321
3322 for (int i = 0, j = 0; i < qMin(a: 20, b: valueStringList.size()); i++) {
3323 bool ok;
3324 qreal v = toDouble(str: valueStringList.at(i), ok: &ok);
3325 if (ok) {
3326 values.data()[j] = v;
3327 j++;
3328 }
3329 }
3330 } else {
3331 values.setToIdentity();
3332 }
3333
3334 QSvgNode *filter = new QSvgFeColorMatrix(parent, inputString, outputString, rect,
3335 type, values);
3336 return filter;
3337}
3338
3339static QSvgNode *createFeGaussianBlurNode(QSvgNode *parent,
3340 const QXmlStreamAttributes &attributes,
3341 QSvgHandler *handler)
3342{
3343 const QString edgeModeString = attributes.value(qualifiedName: QLatin1String("edgeMode")).toString();
3344 QString stdDeviationString = attributes.value(qualifiedName: QLatin1String("stdDeviation")).toString();
3345
3346 QString inputString;
3347 QString outputString;
3348 QSvgRectF rect;
3349
3350 QSvgFeGaussianBlur::EdgeMode edgemode = QSvgFeGaussianBlur::EdgeMode::Duplicate;
3351
3352 parseFilterAttributes(parent, attributes, handler,
3353 inString: &inputString, outString: &outputString, rect: &rect);
3354 qreal stdDeviationX = 0;
3355 qreal stdDeviationY = 0;
3356 if (stdDeviationString.contains(QStringLiteral(" "))){
3357 stdDeviationX = qMax(a: 0., b: toDouble(str: stdDeviationString.split(QStringLiteral(" ")).first()));
3358 stdDeviationY = qMax(a: 0., b: toDouble(str: stdDeviationString.split(QStringLiteral(" ")).last()));
3359 } else {
3360 stdDeviationY = stdDeviationX = qMax(a: 0., b: toDouble(str: stdDeviationString));
3361 }
3362
3363 if (edgeModeString.startsWith(s: QLatin1String("wrap")))
3364 edgemode = QSvgFeGaussianBlur::EdgeMode::Wrap;
3365 else if (edgeModeString.startsWith(s: QLatin1String("none")))
3366 edgemode = QSvgFeGaussianBlur::EdgeMode::None;
3367
3368 QSvgNode *filter = new QSvgFeGaussianBlur(parent, inputString, outputString, rect,
3369 stdDeviationX, stdDeviationY, edgemode);
3370 return filter;
3371}
3372
3373static QSvgNode *createFeOffsetNode(QSvgNode *parent,
3374 const QXmlStreamAttributes &attributes,
3375 QSvgHandler *handler)
3376{
3377 QStringView dxString = attributes.value(qualifiedName: QLatin1String("dx"));
3378 QStringView dyString = attributes.value(qualifiedName: QLatin1String("dy"));
3379
3380 QString inputString;
3381 QString outputString;
3382 QSvgRectF rect;
3383
3384 parseFilterAttributes(parent, attributes, handler,
3385 inString: &inputString, outString: &outputString, rect: &rect);
3386
3387 qreal dx = 0;
3388 if (!dxString.isEmpty()) {
3389 QSvgHandler::LengthType type;
3390 dx = parseLength(str: dxString.toString(), type: &type, handler);
3391 if (type != QSvgHandler::LT_PT)
3392 dx = convertToPixels(len: dx, true, type);
3393 }
3394
3395 qreal dy = 0;
3396 if (!dyString.isEmpty()) {
3397 QSvgHandler::LengthType type;
3398 dy = parseLength(str: dyString.toString(), type: &type, handler);
3399 if (type != QSvgHandler::LT_PT)
3400 dy = convertToPixels(len: dy, true, type);
3401 }
3402
3403 QSvgNode *filter = new QSvgFeOffset(parent, inputString, outputString, rect,
3404 dx, dy);
3405 return filter;
3406}
3407
3408static QSvgNode *createFeCompositeNode(QSvgNode *parent,
3409 const QXmlStreamAttributes &attributes,
3410 QSvgHandler *handler)
3411{
3412 QString in2String = attributes.value(qualifiedName: QLatin1String("in2")).toString();
3413 QString operatorString = attributes.value(qualifiedName: QLatin1String("operator")).toString();
3414 QString k1String = attributes.value(qualifiedName: QLatin1String("k1")).toString();
3415 QString k2String = attributes.value(qualifiedName: QLatin1String("k2")).toString();
3416 QString k3String = attributes.value(qualifiedName: QLatin1String("k3")).toString();
3417 QString k4String = attributes.value(qualifiedName: QLatin1String("k4")).toString();
3418
3419 QString inputString;
3420 QString outputString;
3421 QSvgRectF rect;
3422
3423 parseFilterAttributes(parent, attributes, handler,
3424 inString: &inputString, outString: &outputString, rect: &rect);
3425
3426 QSvgFeComposite::Operator op = QSvgFeComposite::Operator::Over;
3427 if (operatorString.startsWith(QStringLiteral("in")))
3428 op = QSvgFeComposite::Operator::In;
3429 else if (operatorString.startsWith(QStringLiteral("out")))
3430 op = QSvgFeComposite::Operator::Out;
3431 else if (operatorString.startsWith(QStringLiteral("atop")))
3432 op = QSvgFeComposite::Operator::Atop;
3433 else if (operatorString.startsWith(QStringLiteral("xor")))
3434 op = QSvgFeComposite::Operator::Xor;
3435 else if (operatorString.startsWith(QStringLiteral("lighter")))
3436 op = QSvgFeComposite::Operator::Lighter;
3437 else if (operatorString.startsWith(QStringLiteral("arithmetic")))
3438 op = QSvgFeComposite::Operator::Arithmetic;
3439
3440 QVector4D k(0, 0, 0, 0);
3441
3442 if (op == QSvgFeComposite::Operator::Arithmetic) {
3443 bool ok;
3444 qreal v = toDouble(str: k1String, ok: &ok);
3445 if (ok)
3446 k.setX(v);
3447 v = toDouble(str: k2String, ok: &ok);
3448 if (ok)
3449 k.setY(v);
3450 v = toDouble(str: k3String, ok: &ok);
3451 if (ok)
3452 k.setZ(v);
3453 v = toDouble(str: k4String, ok: &ok);
3454 if (ok)
3455 k.setW(v);
3456 }
3457
3458 QSvgNode *filter = new QSvgFeComposite(parent, inputString, outputString, rect,
3459 in2String, op, k);
3460 return filter;
3461}
3462
3463
3464static QSvgNode *createFeMergeNode(QSvgNode *parent,
3465 const QXmlStreamAttributes &attributes,
3466 QSvgHandler *handler)
3467{
3468 QString inputString;
3469 QString outputString;
3470 QSvgRectF rect;
3471
3472 parseFilterAttributes(parent, attributes, handler,
3473 inString: &inputString, outString: &outputString, rect: &rect);
3474
3475 QSvgNode *filter = new QSvgFeMerge(parent, inputString, outputString, rect);
3476 return filter;
3477}
3478
3479static QSvgNode *createFeFloodNode(QSvgNode *parent,
3480 const QXmlStreamAttributes &attributes,
3481 QSvgHandler *handler)
3482{
3483 QStringView colorStr = attributes.value(qualifiedName: QLatin1String("flood-color"));
3484 const QStringView opacityStr = attributes.value(qualifiedName: QLatin1String("flood-opacity"));
3485
3486 QColor color;
3487 if (!constructColor(colorStr, opacity: opacityStr, color, handler)) {
3488 color = QColor(Qt::black);
3489 bool ok;
3490 qreal op = qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: opacityStr, ok: &ok)));
3491 if (ok)
3492 color.setAlphaF(op);
3493 }
3494
3495 QString inputString;
3496 QString outputString;
3497 QSvgRectF rect;
3498
3499 parseFilterAttributes(parent, attributes, handler,
3500 inString: &inputString, outString: &outputString, rect: &rect);
3501
3502 QSvgNode *filter = new QSvgFeFlood(parent, inputString, outputString, rect, color);
3503 return filter;
3504}
3505
3506static QSvgNode *createFeMergeNodeNode(QSvgNode *parent,
3507 const QXmlStreamAttributes &attributes,
3508 QSvgHandler *handler)
3509{
3510 QString inputString;
3511 QString outputString;
3512 QSvgRectF rect;
3513
3514 parseFilterAttributes(parent, attributes, handler,
3515 inString: &inputString, outString: &outputString, rect: &rect);
3516
3517 QSvgNode *filter = new QSvgFeMergeNode(parent, inputString, outputString, rect);
3518 return filter;
3519}
3520
3521static QSvgNode *createFeUnsupportedNode(QSvgNode *parent,
3522 const QXmlStreamAttributes &attributes,
3523 QSvgHandler *handler)
3524{
3525 QString inputString;
3526 QString outputString;
3527 QSvgRectF rect;
3528
3529 parseFilterAttributes(parent, attributes, handler,
3530 inString: &inputString, outString: &outputString, rect: &rect);
3531
3532 QSvgNode *filter = new QSvgFeUnsupported(parent, inputString, outputString, rect);
3533 return filter;
3534}
3535
3536static bool parseSymbolLikeAttributes(const QXmlStreamAttributes &attributes, QSvgHandler *handler,
3537 QRectF *rect, QRectF *viewBox, QPointF *refPoint,
3538 QSvgSymbolLike::PreserveAspectRatios *aspect,
3539 QSvgSymbolLike::Overflow *overflow,
3540 bool marker = false)
3541{
3542 const QStringView xStr = attributes.value(qualifiedName: QLatin1String("x"));
3543 const QStringView yStr = attributes.value(qualifiedName: QLatin1String("y"));
3544 const QStringView refXStr = attributes.value(qualifiedName: QLatin1String("refX"));
3545 const QStringView refYStr = attributes.value(qualifiedName: QLatin1String("refY"));
3546 const QStringView widthStr = attributes.value(qualifiedName: QLatin1String(marker ? "markerWidth":"width"));
3547 const QStringView heightStr = attributes.value(qualifiedName: QLatin1String(marker ? "markerHeight":"height"));
3548 const QString pAspectRStr = attributes.value(qualifiedName: QLatin1String("preserveAspectRatio")).toString();
3549 const QStringView overflowStr = attributes.value(qualifiedName: QLatin1String("overflow"));
3550
3551 QString viewBoxStr = attributes.value(qualifiedName: QLatin1String("viewBox")).toString();
3552
3553
3554 qreal x = 0;
3555 if (!xStr.isEmpty()) {
3556 QSvgHandler::LengthType type;
3557 x = parseLength(str: xStr.toString(), type: &type, handler);
3558 if (type != QSvgHandler::LT_PT)
3559 x = convertToPixels(len: x, true, type);
3560 }
3561 qreal y = 0;
3562 if (!yStr.isEmpty()) {
3563 QSvgHandler::LengthType type;
3564 y = parseLength(str: yStr.toString(), type: &type, handler);
3565 if (type != QSvgHandler::LT_PT)
3566 y = convertToPixels(len: y, false, type);
3567 }
3568 qreal width = 0;
3569 if (!widthStr.isEmpty()) {
3570 QSvgHandler::LengthType type;
3571 width = parseLength(str: widthStr.toString(), type: &type, handler);
3572 if (type != QSvgHandler::LT_PT)
3573 width = convertToPixels(len: width, true, type);
3574 }
3575 qreal height = 0;
3576 if (!heightStr.isEmpty()) {
3577 QSvgHandler::LengthType type;
3578 height = parseLength(str: heightStr.toString(), type: &type, handler);
3579 if (type != QSvgHandler::LT_PT)
3580 height = convertToPixels(len: height, false, type);
3581 }
3582
3583 *rect = QRectF(x, y, width, height);
3584
3585 x = 0;
3586 if (!refXStr.isEmpty()) {
3587 QSvgHandler::LengthType type;
3588 x = parseLength(str: refXStr.toString(), type: &type, handler);
3589 if (type != QSvgHandler::LT_PT)
3590 x = convertToPixels(len: x, true, type);
3591 }
3592 y = 0;
3593 if (!refYStr.isEmpty()) {
3594 QSvgHandler::LengthType type;
3595 y = parseLength(str: refYStr.toString(), type: &type, handler);
3596 if (type != QSvgHandler::LT_PT)
3597 y = convertToPixels(len: y, false, type);
3598 }
3599 *refPoint = QPointF(x,y);
3600
3601 QStringList viewBoxValues;
3602 if (!viewBoxStr.isEmpty()) {
3603 viewBoxStr = viewBoxStr.replace(before: QLatin1Char(' '), after: QLatin1Char(','));
3604 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\r'), after: QLatin1Char(','));
3605 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\n'), after: QLatin1Char(','));
3606 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\t'), after: QLatin1Char(','));
3607 viewBoxValues = viewBoxStr.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
3608 }
3609 if (viewBoxValues.size() == 4) {
3610 QString xStr = viewBoxValues.at(i: 0).trimmed();
3611 QString yStr = viewBoxValues.at(i: 1).trimmed();
3612 QString widthStr = viewBoxValues.at(i: 2).trimmed();
3613 QString heightStr = viewBoxValues.at(i: 3).trimmed();
3614
3615 QSvgHandler::LengthType lt;
3616 qreal x = parseLength(str: xStr, type: &lt, handler);
3617 qreal y = parseLength(str: yStr, type: &lt, handler);
3618 qreal w = parseLength(str: widthStr, type: &lt, handler);
3619 qreal h = parseLength(str: heightStr, type: &lt, handler);
3620
3621 *viewBox = QRectF(x, y, w, h);
3622
3623 } else if (width > 0 && height > 0) {
3624 *viewBox = QRectF(0, 0, width, height);
3625 } else {
3626 *viewBox = handler->document()->viewBox();
3627 }
3628
3629 if (viewBox->isNull())
3630 return false;
3631
3632 QStringList pAspectRStrs = pAspectRStr.split(sep: QLatin1String(" "));
3633 QSvgSymbolLike::PreserveAspectRatio aspectX = QSvgSymbolLike::PreserveAspectRatio::xMid;
3634 QSvgSymbolLike::PreserveAspectRatio aspectY = QSvgSymbolLike::PreserveAspectRatio::yMid;
3635 QSvgSymbolLike::PreserveAspectRatio aspectMS = QSvgSymbolLike::PreserveAspectRatio::meet;
3636
3637 for (auto &pAStr : std::as_const(t&: pAspectRStrs)) {
3638 if (pAStr.startsWith(s: QLatin1String("none"))) {
3639 aspectX = QSvgSymbolLike::PreserveAspectRatio::None;
3640 aspectY = QSvgSymbolLike::PreserveAspectRatio::None;
3641 }else {
3642 if (pAStr.startsWith(s: QLatin1String("xMin")))
3643 aspectX = QSvgSymbolLike::PreserveAspectRatio::xMin;
3644 else if (pAStr.startsWith(s: QLatin1String("xMax")))
3645 aspectX = QSvgSymbolLike::PreserveAspectRatio::xMax;
3646 if (pAStr.endsWith(s: QLatin1String("YMin")))
3647 aspectY = QSvgSymbolLike::PreserveAspectRatio::yMin;
3648 else if (pAStr.endsWith(s: QLatin1String("YMax")))
3649 aspectY = QSvgSymbolLike::PreserveAspectRatio::yMax;
3650 }
3651
3652 if (pAStr.endsWith(s: QLatin1String("slice")))
3653 aspectMS = QSvgSymbolLike::PreserveAspectRatio::slice;
3654 }
3655 *aspect = aspectX | aspectY | aspectMS;
3656
3657 // overflow is not limited to the symbol element but it is often found with the symbol element.
3658 // the symbol element makes little sense without the overflow attribute so it is added here.
3659 // if we decide to remove this from QSvgSymbol, the default value should be set to visible.
3660
3661 // The default value is visible but chrome uses default value hidden.
3662 *overflow = QSvgSymbolLike::Overflow::Hidden;
3663
3664 if (overflowStr.endsWith(s: QLatin1String("auto")))
3665 *overflow = QSvgSymbolLike::Overflow::Auto;
3666 else if (overflowStr.endsWith(s: QLatin1String("visible")))
3667 *overflow = QSvgSymbolLike::Overflow::Visible;
3668 else if (overflowStr.endsWith(s: QLatin1String("hidden")))
3669 *overflow = QSvgSymbolLike::Overflow::Hidden;
3670 else if (overflowStr.endsWith(s: QLatin1String("scroll")))
3671 *overflow = QSvgSymbolLike::Overflow::Scroll;
3672
3673 return true;
3674}
3675
3676static QSvgNode *createSymbolNode(QSvgNode *parent,
3677 const QXmlStreamAttributes &attributes,
3678 QSvgHandler *handler)
3679{
3680 QRectF rect, viewBox;
3681 QPointF refP;
3682 QSvgSymbolLike::PreserveAspectRatios aspect;
3683 QSvgSymbolLike::Overflow overflow;
3684
3685 if (!parseSymbolLikeAttributes(attributes, handler, rect: &rect, viewBox: &viewBox, refPoint: &refP, aspect: &aspect, overflow: &overflow))
3686 return nullptr;
3687
3688 refP = QPointF(0, 0); //refX, refY is ignored in Symbol in Firefox and Chrome.
3689 QSvgNode *symbol = new QSvgSymbol(parent, rect, viewBox, refP, aspect, overflow);
3690 return symbol;
3691}
3692
3693static QSvgNode *createMarkerNode(QSvgNode *parent,
3694 const QXmlStreamAttributes &attributes,
3695 QSvgHandler *handler)
3696{
3697 QRectF rect, viewBox;
3698 QPointF refP;
3699 QSvgSymbolLike::PreserveAspectRatios aspect;
3700 QSvgSymbolLike::Overflow overflow;
3701
3702 const QString orientStr = attributes.value(qualifiedName: QLatin1String("orient")).toString();
3703 const QString markerUnitsStr = attributes.value(qualifiedName: QLatin1String("markerUnits")).toString();
3704
3705 qreal orientationAngle = 0;
3706 QSvgMarker::Orientation orientation;
3707 if (orientStr.startsWith(s: QLatin1String("auto-start-reverse")))
3708 orientation = QSvgMarker::Orientation::AutoStartReverse;
3709 else if (orientStr.startsWith(s: QLatin1String("auto")))
3710 orientation = QSvgMarker::Orientation::Auto;
3711 else {
3712 orientation = QSvgMarker::Orientation::Value;
3713 bool ok;
3714 qreal a;
3715 if (orientStr.endsWith(QStringLiteral("turn")))
3716 a = 360. * toDouble(str: orientStr.mid(position: 0, n: orientStr.length()-4), ok: &ok);
3717 else if (orientStr.endsWith(QStringLiteral("grad")))
3718 a = toDouble(str: orientStr.mid(position: 0, n: orientStr.length()-4), ok: &ok);
3719 else if (orientStr.endsWith(QStringLiteral("rad")))
3720 a = 180. / M_PI * toDouble(str: orientStr.mid(position: 0, n: orientStr.length()-3), ok: &ok);
3721 else
3722 a = toDouble(str: orientStr, ok: &ok);
3723 if (ok)
3724 orientationAngle = a;
3725 }
3726
3727 QSvgMarker::MarkerUnits markerUnits = QSvgMarker::MarkerUnits::StrokeWidth;
3728 if (markerUnitsStr.startsWith(s: QLatin1String("userSpaceOnUse")))
3729 markerUnits = QSvgMarker::MarkerUnits::UserSpaceOnUse;
3730
3731 if (!parseSymbolLikeAttributes(attributes, handler, rect: &rect, viewBox: &viewBox, refPoint: &refP, aspect: &aspect, overflow: &overflow, marker: true))
3732 return nullptr;
3733
3734 QSvgNode *marker = new QSvgMarker(parent, rect, viewBox, refP, aspect, overflow,
3735 orientation, orientationAngle, markerUnits);
3736 return marker;
3737}
3738
3739static QSvgNode *createPathNode(QSvgNode *parent,
3740 const QXmlStreamAttributes &attributes,
3741 QSvgHandler *handler)
3742{
3743 QStringView data = attributes.value(qualifiedName: QLatin1String("d"));
3744
3745 QPainterPath qpath;
3746 qpath.setFillRule(Qt::WindingFill);
3747 if (!parsePathDataFast(dataStr: data, path&: qpath, limitLength: !handler->trustedSourceMode()))
3748 qCWarning(lcSvgHandler, "Invalid path data; path truncated.");
3749
3750 QSvgNode *path = new QSvgPath(parent, qpath);
3751 return path;
3752}
3753
3754static QSvgNode *createPolygonNode(QSvgNode *parent,
3755 const QXmlStreamAttributes &attributes,
3756 QSvgHandler *)
3757{
3758 QString pointsStr = attributes.value(qualifiedName: QLatin1String("points")).toString();
3759
3760 //same QPolygon parsing is in createPolylineNode
3761 const QChar *s = pointsStr.constData();
3762 QList<qreal> points = parseNumbersList(str&: s);
3763 QPolygonF poly(points.size()/2);
3764 if (poly.size() < 2)
3765 return nullptr;
3766
3767 for (int i = 0; i < poly.size(); ++i)
3768 poly[i] = QPointF(points.at(i: 2 * i), points.at(i: 2 * i + 1));
3769 QSvgNode *polygon = new QSvgPolygon(parent, poly);
3770 return polygon;
3771}
3772
3773static QSvgNode *createPolylineNode(QSvgNode *parent,
3774 const QXmlStreamAttributes &attributes,
3775 QSvgHandler *)
3776{
3777 QString pointsStr = attributes.value(qualifiedName: QLatin1String("points")).toString();
3778
3779 //same QPolygon parsing is in createPolygonNode
3780 const QChar *s = pointsStr.constData();
3781 QList<qreal> points = parseNumbersList(str&: s);
3782 QPolygonF poly(points.size()/2);
3783 if (poly.size() < 2)
3784 return nullptr;
3785
3786 for (int i = 0; i < poly.size(); ++i)
3787 poly[i] = QPointF(points.at(i: 2 * i), points.at(i: 2 * i + 1));
3788
3789 QSvgNode *line = new QSvgPolyline(parent, poly);
3790 return line;
3791}
3792
3793static bool parsePrefetchNode(QSvgNode *parent,
3794 const QXmlStreamAttributes &attributes,
3795 QSvgHandler *)
3796{
3797 Q_UNUSED(parent); Q_UNUSED(attributes);
3798 return true;
3799}
3800
3801static QSvgStyleProperty *createRadialGradientNode(QSvgNode *node,
3802 const QXmlStreamAttributes &attributes,
3803 QSvgHandler *handler)
3804{
3805 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
3806 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
3807 const QStringView r = attributes.value(qualifiedName: QLatin1String("r"));
3808 const QStringView fx = attributes.value(qualifiedName: QLatin1String("fx"));
3809 const QStringView fy = attributes.value(qualifiedName: QLatin1String("fy"));
3810
3811 qreal ncx = 0.5;
3812 qreal ncy = 0.5;
3813 if (!cx.isEmpty())
3814 ncx = toDouble(str: cx);
3815 if (!cy.isEmpty())
3816 ncy = toDouble(str: cy);
3817
3818 qreal nr = 0.5;
3819 if (!r.isEmpty())
3820 nr = toDouble(str: r);
3821 if (nr <= 0.0)
3822 return nullptr;
3823
3824 qreal nfx = ncx;
3825 if (!fx.isEmpty())
3826 nfx = toDouble(str: fx);
3827 qreal nfy = ncy;
3828 if (!fy.isEmpty())
3829 nfy = toDouble(str: fy);
3830
3831 QRadialGradient *grad = new QRadialGradient(ncx, ncy, nr, nfx, nfy, 0);
3832 grad->setInterpolationMode(QGradient::ComponentInterpolation);
3833
3834 QSvgGradientStyle *prop = new QSvgGradientStyle(grad);
3835 parseBaseGradient(node, attributes, gradProp: prop, handler);
3836
3837 return prop;
3838}
3839
3840static QSvgNode *createRectNode(QSvgNode *parent,
3841 const QXmlStreamAttributes &attributes,
3842 QSvgHandler *handler)
3843{
3844 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
3845 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
3846 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
3847 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
3848 const QStringView rx = attributes.value(qualifiedName: QLatin1String("rx"));
3849 const QStringView ry = attributes.value(qualifiedName: QLatin1String("ry"));
3850
3851 bool ok = true;
3852 QSvgHandler::LengthType type;
3853 qreal nwidth = parseLength(str: width.toString(), type: &type, handler, ok: &ok);
3854 if (!ok)
3855 return nullptr;
3856 nwidth = convertToPixels(len: nwidth, true, type);
3857 qreal nheight = parseLength(str: height.toString(), type: &type, handler, ok: &ok);
3858 if (!ok)
3859 return nullptr;
3860 nheight = convertToPixels(len: nheight, true, type);
3861 qreal nrx = toDouble(str: rx);
3862 qreal nry = toDouble(str: ry);
3863
3864 QRectF bounds(toDouble(str: x), toDouble(str: y), nwidth, nheight);
3865 if (bounds.isEmpty())
3866 return nullptr;
3867
3868 if (!rx.isEmpty() && ry.isEmpty())
3869 nry = nrx;
3870 else if (!ry.isEmpty() && rx.isEmpty())
3871 nrx = nry;
3872
3873 //9.2 The 'rect' element clearly specifies it
3874 // but the case might in fact be handled because
3875 // we draw rounded rectangles differently
3876 if (nrx > bounds.width()/2)
3877 nrx = bounds.width()/2;
3878 if (nry > bounds.height()/2)
3879 nry = bounds.height()/2;
3880
3881 //we draw rounded rect from 0...99
3882 //svg from 0...bounds.width()/2 so we're adjusting the
3883 //coordinates
3884 nrx *= (100/(bounds.width()/2));
3885 nry *= (100/(bounds.height()/2));
3886
3887 QSvgNode *rect = new QSvgRect(parent, bounds, nrx, nry);
3888 return rect;
3889}
3890
3891static bool parseScriptNode(QSvgNode *parent,
3892 const QXmlStreamAttributes &attributes,
3893 QSvgHandler *)
3894{
3895 Q_UNUSED(parent); Q_UNUSED(attributes);
3896 return true;
3897}
3898
3899static bool parseSetNode(QSvgNode *parent,
3900 const QXmlStreamAttributes &attributes,
3901 QSvgHandler *)
3902{
3903 Q_UNUSED(parent); Q_UNUSED(attributes);
3904 return true;
3905}
3906
3907static QSvgStyleProperty *createSolidColorNode(QSvgNode *parent,
3908 const QXmlStreamAttributes &attributes,
3909 QSvgHandler *handler)
3910{
3911 Q_UNUSED(parent); Q_UNUSED(attributes);
3912 QStringView solidColorStr = attributes.value(qualifiedName: QLatin1String("solid-color"));
3913 QStringView solidOpacityStr = attributes.value(qualifiedName: QLatin1String("solid-opacity"));
3914
3915 if (solidOpacityStr.isEmpty())
3916 solidOpacityStr = attributes.value(qualifiedName: QLatin1String("opacity"));
3917
3918 QColor color;
3919 if (!constructColor(colorStr: solidColorStr, opacity: solidOpacityStr, color, handler))
3920 return 0;
3921 QSvgSolidColorStyle *style = new QSvgSolidColorStyle(color);
3922 return style;
3923}
3924
3925static bool parseStopNode(QSvgStyleProperty *parent,
3926 const QXmlStreamAttributes &attributes,
3927 QSvgHandler *handler)
3928{
3929 if (parent->type() != QSvgStyleProperty::GRADIENT)
3930 return false;
3931 QString nodeIdStr = someId(attributes);
3932 QString xmlClassStr = attributes.value(qualifiedName: QLatin1String("class")).toString();
3933
3934 //### nasty hack because stop gradients are not in the rendering tree
3935 // we force a dummy node with the same id and class into a rendering
3936 // tree to figure out whether the selector has a style for it
3937 // QSvgStyleSelector should be coded in a way that could avoid it
3938 QSvgAnimation anim;
3939 anim.setNodeId(nodeIdStr);
3940 anim.setXmlClass(xmlClassStr);
3941
3942 QXmlStreamAttributes xmlAttr = attributes;
3943
3944#ifndef QT_NO_CSSPARSER
3945 cssStyleLookup(node: &anim, handler, selector: handler->selector(), attributes&: xmlAttr);
3946#endif
3947 parseStyle(node: &anim, attrs: xmlAttr, handler);
3948
3949 QSvgAttributes attrs(xmlAttr, handler);
3950
3951 QSvgGradientStyle *style =
3952 static_cast<QSvgGradientStyle*>(parent);
3953 QStringView colorStr = attrs.stopColor;
3954 QColor color;
3955
3956 bool ok = true;
3957 qreal offset = convertToNumber(str: attrs.offset, handler, ok: &ok);
3958 if (!ok)
3959 offset = 0.0;
3960 QString black = QString::fromLatin1(ba: "#000000");
3961 if (colorStr.isEmpty()) {
3962 colorStr = black;
3963 }
3964
3965 constructColor(colorStr, opacity: attrs.stopOpacity, color, handler);
3966
3967 QGradient *grad = style->qgradient();
3968
3969 offset = qMin(a: qreal(1), b: qMax(a: qreal(0), b: offset)); // Clamp to range [0, 1]
3970 QGradientStops stops;
3971 if (style->gradientStopsSet()) {
3972 stops = grad->stops();
3973 // If the stop offset equals the one previously added, add an epsilon to make it greater.
3974 if (offset <= stops.back().first)
3975 offset = stops.back().first + FLT_EPSILON;
3976 }
3977
3978 // If offset is greater than one, it must be clamped to one.
3979 if (offset > 1.0) {
3980 if ((stops.size() == 1) || (stops.at(i: stops.size() - 2).first < 1.0 - FLT_EPSILON)) {
3981 stops.back().first = 1.0 - FLT_EPSILON;
3982 grad->setStops(stops);
3983 }
3984 offset = 1.0;
3985 }
3986
3987 grad->setColorAt(pos: offset, color);
3988 style->setGradientStopsSet(true);
3989 return true;
3990}
3991
3992static bool parseStyleNode(QSvgNode *parent,
3993 const QXmlStreamAttributes &attributes,
3994 QSvgHandler *handler)
3995{
3996 Q_UNUSED(parent);
3997#ifdef QT_NO_CSSPARSER
3998 Q_UNUSED(attributes);
3999 Q_UNUSED(handler);
4000#else
4001 const QStringView type = attributes.value(qualifiedName: QLatin1String("type"));
4002 if (type.compare(s: QLatin1String("text/css"), cs: Qt::CaseInsensitive) == 0 || type.isNull())
4003 handler->setInStyle(true);
4004#endif
4005
4006 return true;
4007}
4008
4009static QSvgNode *createSvgNode(QSvgNode *parent,
4010 const QXmlStreamAttributes &attributes,
4011 QSvgHandler *handler)
4012{
4013 Q_UNUSED(parent); Q_UNUSED(attributes);
4014
4015 QSvgTinyDocument *node = new QSvgTinyDocument(handler->options());
4016 const QStringView widthStr = attributes.value(qualifiedName: QLatin1String("width"));
4017 const QStringView heightStr = attributes.value(qualifiedName: QLatin1String("height"));
4018 QString viewBoxStr = attributes.value(qualifiedName: QLatin1String("viewBox")).toString();
4019
4020 QSvgHandler::LengthType type = QSvgHandler::LT_PX; // FIXME: is the default correct?
4021 qreal width = 0;
4022 if (!widthStr.isEmpty()) {
4023 width = parseLength(str: widthStr.toString(), type: &type, handler);
4024 if (type != QSvgHandler::LT_PT)
4025 width = convertToPixels(len: width, true, type);
4026 node->setWidth(len: int(width), percent: type == QSvgHandler::LT_PERCENT);
4027 }
4028 qreal height = 0;
4029 if (!heightStr.isEmpty()) {
4030 height = parseLength(str: heightStr.toString(), type: &type, handler);
4031 if (type != QSvgHandler::LT_PT)
4032 height = convertToPixels(len: height, false, type);
4033 node->setHeight(len: int(height), percent: type == QSvgHandler::LT_PERCENT);
4034 }
4035
4036 QStringList viewBoxValues;
4037 if (!viewBoxStr.isEmpty()) {
4038 viewBoxStr = viewBoxStr.replace(before: QLatin1Char(' '), after: QLatin1Char(','));
4039 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\r'), after: QLatin1Char(','));
4040 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\n'), after: QLatin1Char(','));
4041 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\t'), after: QLatin1Char(','));
4042 viewBoxValues = viewBoxStr.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
4043 }
4044 if (viewBoxValues.size() == 4) {
4045 QString xStr = viewBoxValues.at(i: 0).trimmed();
4046 QString yStr = viewBoxValues.at(i: 1).trimmed();
4047 QString widthStr = viewBoxValues.at(i: 2).trimmed();
4048 QString heightStr = viewBoxValues.at(i: 3).trimmed();
4049
4050 QSvgHandler::LengthType lt;
4051 qreal x = parseLength(str: xStr, type: &lt, handler);
4052 qreal y = parseLength(str: yStr, type: &lt, handler);
4053 qreal w = parseLength(str: widthStr, type: &lt, handler);
4054 qreal h = parseLength(str: heightStr, type: &lt, handler);
4055
4056 node->setViewBox(QRectF(x, y, w, h));
4057
4058 } else if (width && height) {
4059 if (type == QSvgHandler::LT_PT) {
4060 width = convertToPixels(len: width, false, type);
4061 height = convertToPixels(len: height, false, type);
4062 }
4063 node->setViewBox(QRectF(0, 0, width, height));
4064 }
4065 handler->setDefaultCoordinateSystem(QSvgHandler::LT_PX);
4066
4067 return node;
4068}
4069
4070static QSvgNode *createSwitchNode(QSvgNode *parent,
4071 const QXmlStreamAttributes &attributes,
4072 QSvgHandler *)
4073{
4074 Q_UNUSED(attributes);
4075 QSvgSwitch *node = new QSvgSwitch(parent);
4076 return node;
4077}
4078
4079static QSvgNode *createPatternNode(QSvgNode *parent,
4080 const QXmlStreamAttributes &attributes,
4081 QSvgHandler *handler)
4082{
4083 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
4084 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
4085 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
4086 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
4087 const QStringView patternUnits = attributes.value(qualifiedName: QLatin1String("patternUnits"));
4088 const QStringView patternContentUnits = attributes.value(qualifiedName: QLatin1String("patternContentUnits"));
4089 const QStringView patternTransform = attributes.value(qualifiedName: QLatin1String("patternTransform"));
4090
4091 QtSvg::UnitTypes nPatternUnits = patternUnits.contains(s: QLatin1String("userSpaceOnUse")) ?
4092 QtSvg::UnitTypes::userSpaceOnUse : QtSvg::UnitTypes::objectBoundingBox;
4093
4094 QtSvg::UnitTypes nPatternContentUnits = patternContentUnits.contains(s: QLatin1String("objectBoundingBox")) ?
4095 QtSvg::UnitTypes::objectBoundingBox : QtSvg::UnitTypes::userSpaceOnUse;
4096
4097 QString viewBoxStr = attributes.value(qualifiedName: QLatin1String("viewBox")).toString();
4098
4099 bool ok = false;
4100 QSvgHandler::LengthType type;
4101
4102 qreal nx = parseLength(str: x.toString(), type: &type, handler, ok: &ok);
4103 nx = convertToPixels(len: nx, true, type);
4104 if (!ok)
4105 nx = 0.0;
4106 else if (type == QSvgHandler::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
4107 nx = (nx / 100.) * handler->document()->viewBox().width();
4108 else if (type == QSvgHandler::LT_PERCENT)
4109 nx = nx / 100.;
4110
4111 qreal ny = parseLength(str: y.toString(), type: &type, handler, ok: &ok);
4112 ny = convertToPixels(len: ny, true, type);
4113 if (!ok)
4114 ny = 0.0;
4115 else if (type == QSvgHandler::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
4116 ny = (ny / 100.) * handler->document()->viewBox().height();
4117 else if (type == QSvgHandler::LT_PERCENT)
4118 ny = ny / 100.;
4119
4120 qreal nwidth = parseLength(str: width.toString(), type: &type, handler, ok: &ok);
4121 nwidth = convertToPixels(len: nwidth, true, type);
4122 if (!ok)
4123 nwidth = 0.0;
4124 else if (type == QSvgHandler::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
4125 nwidth = (nwidth / 100.) * handler->document()->viewBox().width();
4126 else if (type == QSvgHandler::LT_PERCENT)
4127 nwidth = nwidth / 100.;
4128
4129 qreal nheight = parseLength(str: height.toString(), type: &type, handler, ok: &ok);
4130 nheight = convertToPixels(len: nheight, true, type);
4131 if (!ok)
4132 nheight = 0.0;
4133 else if (type == QSvgHandler::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
4134 nheight = (nheight / 100.) * handler->document()->viewBox().height();
4135 else if (type == QSvgHandler::LT_PERCENT)
4136 nheight = nheight / 100.;
4137
4138
4139 QStringList viewBoxValues;
4140 QRectF viewBox;
4141 if (!viewBoxStr.isEmpty()) {
4142 viewBoxStr = viewBoxStr.replace(before: QLatin1Char(' '), after: QLatin1Char(','));
4143 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\r'), after: QLatin1Char(','));
4144 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\n'), after: QLatin1Char(','));
4145 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\t'), after: QLatin1Char(','));
4146 viewBoxValues = viewBoxStr.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
4147 }
4148 if (viewBoxValues.size() == 4) {
4149 QString xStr = viewBoxValues.at(i: 0).trimmed();
4150 QString yStr = viewBoxValues.at(i: 1).trimmed();
4151 QString widthStr = viewBoxValues.at(i: 2).trimmed();
4152 QString heightStr = viewBoxValues.at(i: 3).trimmed();
4153
4154 qreal x = convertToNumber(str: xStr, handler);
4155 qreal y = convertToNumber(str: yStr, handler);
4156 qreal w = convertToNumber(str: widthStr, handler);
4157 qreal h = convertToNumber(str: heightStr, handler);
4158
4159 if (w > 0 && h > 0)
4160 viewBox.setRect(ax: x, ay: y, aaw: w, aah: h);
4161 }
4162
4163 QTransform matrix;
4164 if (!patternTransform.isEmpty())
4165 matrix = parseTransformationMatrix(value: patternTransform);
4166
4167 QRectF bounds(nx, ny, nwidth, nheight);
4168 if (bounds.isEmpty())
4169 return nullptr;
4170
4171 QSvgRectF patternRectF(bounds, nPatternUnits, nPatternUnits, nPatternUnits, nPatternUnits);
4172 QSvgPattern *node = new QSvgPattern(parent, patternRectF, viewBox, nPatternContentUnits, matrix);
4173
4174 // Create a style node for the Pattern.
4175 QSvgPatternStyle *prop = new QSvgPatternStyle(node);
4176 node->appendStyleProperty(prop, id: someId(attributes));
4177
4178 return node;
4179}
4180
4181static bool parseTbreakNode(QSvgNode *parent,
4182 const QXmlStreamAttributes &,
4183 QSvgHandler *)
4184{
4185 if (parent->type() != QSvgNode::Textarea)
4186 return false;
4187 static_cast<QSvgText*>(parent)->addLineBreak();
4188 return true;
4189}
4190
4191static QSvgNode *createTextNode(QSvgNode *parent,
4192 const QXmlStreamAttributes &attributes,
4193 QSvgHandler *handler)
4194{
4195 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
4196 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
4197 //### editable and rotate not handled
4198 QSvgHandler::LengthType type;
4199 qreal nx = parseLength(str: x.toString(), type: &type, handler);
4200 nx = convertToPixels(len: nx, true, type);
4201 qreal ny = parseLength(str: y.toString(), type: &type, handler);
4202 ny = convertToPixels(len: ny, true, type);
4203
4204 QSvgNode *text = new QSvgText(parent, QPointF(nx, ny));
4205 return text;
4206}
4207
4208static QSvgNode *createTextAreaNode(QSvgNode *parent,
4209 const QXmlStreamAttributes &attributes,
4210 QSvgHandler *handler)
4211{
4212 QSvgText *node = static_cast<QSvgText *>(createTextNode(parent, attributes, handler));
4213 if (node) {
4214 QSvgHandler::LengthType type;
4215 qreal width = parseLength(str: attributes.value(qualifiedName: QLatin1String("width")), type: &type, handler);
4216 qreal height = parseLength(str: attributes.value(qualifiedName: QLatin1String("height")), type: &type, handler);
4217 node->setTextArea(QSizeF(width, height));
4218 }
4219 return node;
4220}
4221
4222static QSvgNode *createTspanNode(QSvgNode *parent,
4223 const QXmlStreamAttributes &,
4224 QSvgHandler *)
4225{
4226 return new QSvgTspan(parent);
4227}
4228
4229static QSvgNode *createUseNode(QSvgNode *parent,
4230 const QXmlStreamAttributes &attributes,
4231 QSvgHandler *handler)
4232{
4233 QString linkId = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString().remove(i: 0, len: 1);
4234 const QStringView xStr = attributes.value(qualifiedName: QLatin1String("x"));
4235 const QStringView yStr = attributes.value(qualifiedName: QLatin1String("y"));
4236 QSvgStructureNode *group = nullptr;
4237
4238 if (linkId.isEmpty())
4239 linkId = attributes.value(qualifiedName: QLatin1String("href")).toString().remove(i: 0, len: 1);
4240 switch (parent->type()) {
4241 case QSvgNode::Doc:
4242 case QSvgNode::Defs:
4243 case QSvgNode::Group:
4244 case QSvgNode::Switch:
4245 case QSvgNode::Mask:
4246 group = static_cast<QSvgStructureNode*>(parent);
4247 break;
4248 default:
4249 break;
4250 }
4251
4252 if (group) {
4253 QPointF pt;
4254 if (!xStr.isNull() || !yStr.isNull()) {
4255 QSvgHandler::LengthType type;
4256 qreal nx = parseLength(str: xStr.toString(), type: &type, handler);
4257 nx = convertToPixels(len: nx, true, type);
4258
4259 qreal ny = parseLength(str: yStr.toString(), type: &type, handler);
4260 ny = convertToPixels(len: ny, true, type);
4261 pt = QPointF(nx, ny);
4262 }
4263
4264 QSvgNode *link = group->scopeNode(id: linkId);
4265 if (link) {
4266 if (parent->isDescendantOf(parent: link))
4267 qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(linkId));
4268
4269 return new QSvgUse(pt, parent, link);
4270 }
4271
4272 //delay link resolving, link might have not been created yet
4273 return new QSvgUse(pt, parent, linkId);
4274 }
4275
4276 qCWarning(lcSvgHandler, "<use> element %s in wrong context!", qPrintable(linkId));
4277 return 0;
4278}
4279
4280static QSvgNode *createVideoNode(QSvgNode *parent,
4281 const QXmlStreamAttributes &attributes,
4282 QSvgHandler *)
4283{
4284 Q_UNUSED(parent); Q_UNUSED(attributes);
4285 return 0;
4286}
4287
4288typedef QSvgNode *(*FactoryMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
4289
4290static FactoryMethod findGroupFactory(const QString &name, QtSvg::Options options)
4291{
4292 if (name.isEmpty())
4293 return 0;
4294
4295 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
4296 switch (name.at(i: 0).unicode()) {
4297 case 'd':
4298 if (ref == QLatin1String("efs")) return createDefsNode;
4299 break;
4300 case 'f':
4301 if (ref == QLatin1String("ilter") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return createFilterNode;
4302 break;
4303 case 'g':
4304 if (ref.isEmpty()) return createGNode;
4305 break;
4306 case 'm':
4307 if (ref == QLatin1String("ask") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return createMaskNode;
4308 if (ref == QLatin1String("arker") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return createMarkerNode;
4309 break;
4310 case 's':
4311 if (ref == QLatin1String("vg")) return createSvgNode;
4312 if (ref == QLatin1String("witch")) return createSwitchNode;
4313 if (ref == QLatin1String("ymbol") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return createSymbolNode;
4314 break;
4315 case 'p':
4316 if (ref == QLatin1String("attern") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return createPatternNode;
4317 break;
4318 default:
4319 break;
4320 }
4321 return 0;
4322}
4323
4324static FactoryMethod findGraphicsFactory(const QString &name, QtSvg::Options options)
4325{
4326 Q_UNUSED(options);
4327 if (name.isEmpty())
4328 return 0;
4329
4330 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
4331 switch (name.at(i: 0).unicode()) {
4332 case 'a':
4333 if (ref == QLatin1String("nimation")) return createAnimationNode;
4334 break;
4335 case 'c':
4336 if (ref == QLatin1String("ircle")) return createCircleNode;
4337 break;
4338 case 'e':
4339 if (ref == QLatin1String("llipse")) return createEllipseNode;
4340 break;
4341 case 'i':
4342 if (ref == QLatin1String("mage")) return createImageNode;
4343 break;
4344 case 'l':
4345 if (ref == QLatin1String("ine")) return createLineNode;
4346 break;
4347 case 'p':
4348 if (ref == QLatin1String("ath")) return createPathNode;
4349 if (ref == QLatin1String("olygon")) return createPolygonNode;
4350 if (ref == QLatin1String("olyline")) return createPolylineNode;
4351 break;
4352 case 'r':
4353 if (ref == QLatin1String("ect")) return createRectNode;
4354 break;
4355 case 't':
4356 if (ref == QLatin1String("ext")) return createTextNode;
4357 if (ref == QLatin1String("extArea")) return createTextAreaNode;
4358 if (ref == QLatin1String("span")) return createTspanNode;
4359 break;
4360 case 'u':
4361 if (ref == QLatin1String("se")) return createUseNode;
4362 break;
4363 case 'v':
4364 if (ref == QLatin1String("ideo")) return createVideoNode;
4365 break;
4366 default:
4367 break;
4368 }
4369 return 0;
4370}
4371
4372static FactoryMethod findFilterFactory(const QString &name, QtSvg::Options options)
4373{
4374 if (options.testFlag(flag: QtSvg::Tiny12FeaturesOnly))
4375 return 0;
4376
4377 if (name.isEmpty())
4378 return 0;
4379
4380 if (!name.startsWith(s: QLatin1String("fe")))
4381 return 0;
4382
4383 if (name == QLatin1String("feMerge")) return createFeMergeNode;
4384 if (name == QLatin1String("feColorMatrix")) return createFeColorMatrixNode;
4385 if (name == QLatin1String("feGaussianBlur")) return createFeGaussianBlurNode;
4386 if (name == QLatin1String("feOffset")) return createFeOffsetNode;
4387 if (name == QLatin1String("feMergeNode")) return createFeMergeNodeNode;
4388 if (name == QLatin1String("feComposite")) return createFeCompositeNode;
4389 if (name == QLatin1String("feFlood")) return createFeFloodNode;
4390
4391 static const QStringList unsupportedFilters = {
4392 QStringLiteral("feBlend"),
4393 QStringLiteral("feComponentTransfer"),
4394 QStringLiteral("feConvolveMatrix"),
4395 QStringLiteral("feDiffuseLighting"),
4396 QStringLiteral("feDisplacementMap"),
4397 QStringLiteral("feDropShadow"),
4398 QStringLiteral("feFuncA"),
4399 QStringLiteral("feFuncB"),
4400 QStringLiteral("feFuncG"),
4401 QStringLiteral("feFuncR"),
4402 QStringLiteral("feImage"),
4403 QStringLiteral("feMorphology"),
4404 QStringLiteral("feSpecularLighting"),
4405 QStringLiteral("feTile"),
4406 QStringLiteral("feTurbulence")
4407 };
4408
4409 if (unsupportedFilters.contains(str: name))
4410 return createFeUnsupportedNode;
4411
4412 return 0;
4413}
4414
4415typedef bool (*ParseMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
4416
4417static ParseMethod findUtilFactory(const QString &name, QtSvg::Options options)
4418{
4419 if (name.isEmpty())
4420 return 0;
4421
4422 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
4423 switch (name.at(i: 0).unicode()) {
4424 case 'a':
4425 if (ref.isEmpty()) return parseAnchorNode;
4426 if (ref == QLatin1String("nimate")) return parseAnimateNode;
4427 if (ref == QLatin1String("nimateColor")) return parseAnimateColorNode;
4428 if (ref == QLatin1String("nimateMotion")) return parseAimateMotionNode;
4429 if (ref == QLatin1String("nimateTransform")) return parseAnimateTransformNode;
4430 if (ref == QLatin1String("udio")) return parseAudioNode;
4431 break;
4432 case 'd':
4433 if (ref == QLatin1String("iscard")) return parseDiscardNode;
4434 break;
4435 case 'f':
4436 if (ref == QLatin1String("oreignObject")) return parseForeignObjectNode;
4437 break;
4438 case 'h':
4439 if (ref == QLatin1String("andler")) return parseHandlerNode;
4440 if (ref == QLatin1String("kern")) return parseHkernNode;
4441 break;
4442 case 'm':
4443 if (ref == QLatin1String("etadata")) return parseMetadataNode;
4444 if (ref == QLatin1String("path")) return parseMpathNode;
4445 if (ref == QLatin1String("ask") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return parseMaskNode;
4446 if (ref == QLatin1String("arker") && !options.testFlag(flag: QtSvg::Tiny12FeaturesOnly)) return parseMarkerNode;
4447 break;
4448 case 'p':
4449 if (ref == QLatin1String("refetch")) return parsePrefetchNode;
4450 break;
4451 case 's':
4452 if (ref == QLatin1String("cript")) return parseScriptNode;
4453 if (ref == QLatin1String("et")) return parseSetNode;
4454 if (ref == QLatin1String("tyle")) return parseStyleNode;
4455 break;
4456 case 't':
4457 if (ref == QLatin1String("break")) return parseTbreakNode;
4458 break;
4459 default:
4460 break;
4461 }
4462 return 0;
4463}
4464
4465typedef QSvgStyleProperty *(*StyleFactoryMethod)(QSvgNode *,
4466 const QXmlStreamAttributes &,
4467 QSvgHandler *);
4468
4469static StyleFactoryMethod findStyleFactoryMethod(const QString &name)
4470{
4471 if (name.isEmpty())
4472 return 0;
4473
4474 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
4475 switch (name.at(i: 0).unicode()) {
4476 case 'f':
4477 if (ref == QLatin1String("ont")) return createFontNode;
4478 break;
4479 case 'l':
4480 if (ref == QLatin1String("inearGradient")) return createLinearGradientNode;
4481 break;
4482 case 'r':
4483 if (ref == QLatin1String("adialGradient")) return createRadialGradientNode;
4484 break;
4485 case 's':
4486 if (ref == QLatin1String("olidColor")) return createSolidColorNode;
4487 break;
4488 default:
4489 break;
4490 }
4491 return 0;
4492}
4493
4494typedef bool (*StyleParseMethod)(QSvgStyleProperty *,
4495 const QXmlStreamAttributes &,
4496 QSvgHandler *);
4497
4498static StyleParseMethod findStyleUtilFactoryMethod(const QString &name)
4499{
4500 if (name.isEmpty())
4501 return 0;
4502
4503 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
4504 switch (name.at(i: 0).unicode()) {
4505 case 'f':
4506 if (ref == QLatin1String("ont-face")) return parseFontFaceNode;
4507 if (ref == QLatin1String("ont-face-name")) return parseFontFaceNameNode;
4508 if (ref == QLatin1String("ont-face-src")) return parseFontFaceSrcNode;
4509 if (ref == QLatin1String("ont-face-uri")) return parseFontFaceUriNode;
4510 break;
4511 case 'g':
4512 if (ref == QLatin1String("lyph")) return parseGlyphNode;
4513 break;
4514 case 'm':
4515 if (ref == QLatin1String("issing-glyph")) return parseMissingGlyphNode;
4516 break;
4517 case 's':
4518 if (ref == QLatin1String("top")) return parseStopNode;
4519 break;
4520 default:
4521 break;
4522 }
4523 return 0;
4524}
4525
4526QSvgHandler::QSvgHandler(QIODevice *device, QtSvg::Options options)
4527 : xml(new QXmlStreamReader(device))
4528 , m_ownsReader(true)
4529 , m_options(options)
4530{
4531 init();
4532}
4533
4534QSvgHandler::QSvgHandler(const QByteArray &data, QtSvg::Options options)
4535 : xml(new QXmlStreamReader(data))
4536 , m_ownsReader(true)
4537 , m_options(options)
4538{
4539 init();
4540}
4541
4542QSvgHandler::QSvgHandler(QXmlStreamReader *const reader, QtSvg::Options options)
4543 : xml(reader)
4544 , m_ownsReader(false)
4545 , m_options(options)
4546{
4547 init();
4548}
4549
4550void QSvgHandler::init()
4551{
4552 m_doc = 0;
4553 m_style = 0;
4554 m_animEnd = 0;
4555 m_defaultCoords = LT_PX;
4556 m_defaultPen = QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
4557 m_defaultPen.setMiterLimit(4);
4558 parse();
4559}
4560
4561static bool detectPatternCycles(const QSvgNode *node, QList<const QSvgNode *> active = {})
4562{
4563 QSvgFillStyle *fillStyle = static_cast<QSvgFillStyle*>
4564 (node->styleProperty(type: QSvgStyleProperty::FILL));
4565 if (fillStyle && fillStyle->style() && fillStyle->style()->type() == QSvgStyleProperty::PATTERN) {
4566 QSvgPatternStyle *patternStyle = static_cast<QSvgPatternStyle *>(fillStyle->style());
4567 if (active.contains(t: patternStyle->patternNode()))
4568 return true;
4569 }
4570
4571 QSvgStrokeStyle *strokeStyle = static_cast<QSvgStrokeStyle*>
4572 (node->styleProperty(type: QSvgStyleProperty::STROKE));
4573 if (strokeStyle && strokeStyle->style() && strokeStyle->style()->type() == QSvgStyleProperty::PATTERN) {
4574 QSvgPatternStyle *patternStyle = static_cast<QSvgPatternStyle *>(strokeStyle->style());
4575 if (active.contains(t: patternStyle->patternNode()))
4576 return true;
4577 }
4578
4579 return false;
4580}
4581
4582static bool detectCycles(const QSvgNode *node, QList<const QSvgNode *> active = {})
4583{
4584 if (Q_UNLIKELY(!node))
4585 return false;
4586 switch (node->type()) {
4587 case QSvgNode::Doc:
4588 case QSvgNode::Group:
4589 case QSvgNode::Defs:
4590 case QSvgNode::Pattern:
4591 {
4592 if (node->type() == QSvgNode::Pattern)
4593 active.append(t: node);
4594
4595 auto *g = static_cast<const QSvgStructureNode*>(node);
4596 for (auto *r : g->renderers()) {
4597 if (detectCycles(node: r, active))
4598 return true;
4599 }
4600 }
4601 break;
4602 case QSvgNode::Use:
4603 {
4604 if (active.contains(t: node))
4605 return true;
4606
4607 auto *u = static_cast<const QSvgUse*>(node);
4608 auto *target = u->link();
4609 if (target) {
4610 active.append(t: u);
4611 if (detectCycles(node: target, active))
4612 return true;
4613 }
4614 }
4615 break;
4616 case QSvgNode::Rect:
4617 case QSvgNode::Ellipse:
4618 case QSvgNode::Circle:
4619 case QSvgNode::Line:
4620 case QSvgNode::Path:
4621 case QSvgNode::Polygon:
4622 case QSvgNode::Polyline:
4623 case QSvgNode::Tspan:
4624 if (detectPatternCycles(node, active))
4625 return true;
4626 break;
4627 default:
4628 break;
4629 }
4630 return false;
4631}
4632
4633static bool detectCyclesAndWarn(const QSvgNode *node) {
4634 const bool cycleFound = detectCycles(node);
4635 if (cycleFound)
4636 qCWarning(lcSvgHandler, "Cycles detected in SVG, document discarded.");
4637 return cycleFound;
4638}
4639
4640// Having too many unfinished elements will cause a stack overflow
4641// in the dtor of QSvgTinyDocument, see oss-fuzz issue 24000.
4642static const int unfinishedElementsLimit = 2048;
4643
4644void QSvgHandler::parse()
4645{
4646 xml->setNamespaceProcessing(false);
4647#ifndef QT_NO_CSSPARSER
4648 m_selector = new QSvgStyleSelector;
4649 m_inStyle = false;
4650#endif
4651 bool done = false;
4652 int remainingUnfinishedElements = unfinishedElementsLimit;
4653 while (!xml->atEnd() && !done) {
4654 switch (xml->readNext()) {
4655 case QXmlStreamReader::StartElement:
4656 // he we could/should verify the namespaces, and simply
4657 // call m_skipNodes(Unknown) if we don't know the
4658 // namespace. We do support http://www.w3.org/2000/svg
4659 // but also http://www.w3.org/2000/svg-20000303-stylable
4660 // And if the document uses an external dtd, the reported
4661 // namespaceUri is empty. The only possible strategy at
4662 // this point is to do what everyone else seems to do and
4663 // ignore the reported namespaceUri completely.
4664 if (remainingUnfinishedElements
4665 && startElement(localName: xml->name().toString(), attributes: xml->attributes())
4666 && !detectCyclesAndWarn(node: m_doc)) {
4667 --remainingUnfinishedElements;
4668 } else {
4669 delete m_doc;
4670 m_doc = nullptr;
4671 return;
4672 }
4673 break;
4674 case QXmlStreamReader::EndElement:
4675 done = endElement(localName: xml->name());
4676 ++remainingUnfinishedElements;
4677 break;
4678 case QXmlStreamReader::Characters:
4679 characters(str: xml->text());
4680 break;
4681 case QXmlStreamReader::ProcessingInstruction:
4682 processingInstruction(target: xml->processingInstructionTarget().toString(), data: xml->processingInstructionData().toString());
4683 break;
4684 default:
4685 break;
4686 }
4687 }
4688 resolvePaintServers(node: m_doc);
4689 resolveNodes();
4690 if (detectCyclesAndWarn(node: m_doc)) {
4691 delete m_doc;
4692 m_doc = nullptr;
4693 }
4694}
4695
4696bool QSvgHandler::startElement(const QString &localName,
4697 const QXmlStreamAttributes &attributes)
4698{
4699 QSvgNode *node = nullptr;
4700
4701 pushColorCopy();
4702
4703 /* The xml:space attribute may appear on any element. We do
4704 * a lookup by the qualified name here, but this is namespace aware, since
4705 * the XML namespace can only be bound to prefix "xml." */
4706 const QStringView xmlSpace(attributes.value(qualifiedName: QLatin1String("xml:space")));
4707 if (xmlSpace.isNull()) {
4708 // This element has no xml:space attribute.
4709 m_whitespaceMode.push(t: m_whitespaceMode.isEmpty() ? QSvgText::Default : m_whitespaceMode.top());
4710 } else if (xmlSpace == QLatin1String("preserve")) {
4711 m_whitespaceMode.push(t: QSvgText::Preserve);
4712 } else if (xmlSpace == QLatin1String("default")) {
4713 m_whitespaceMode.push(t: QSvgText::Default);
4714 } else {
4715 const QByteArray msg = '"' + xmlSpace.toString().toLocal8Bit()
4716 + "\" is an invalid value for attribute xml:space. "
4717 "Valid values are \"preserve\" and \"default\".";
4718 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4719 m_whitespaceMode.push(t: QSvgText::Default);
4720 }
4721
4722 if (!m_doc && localName != QLatin1String("svg"))
4723 return false;
4724
4725 if (m_doc && localName == QLatin1String("svg")) {
4726 m_skipNodes.push(t: Doc);
4727 qCWarning(lcSvgHandler) << "Skipping a nested svg element, because "
4728 "SVG Document must not contain nested svg elements in Svg Tiny 1.2";
4729 }
4730
4731 if (!m_skipNodes.isEmpty() && m_skipNodes.top() == Doc)
4732 return true;
4733
4734 if (FactoryMethod method = findGroupFactory(name: localName, options: options())) {
4735 //group
4736 node = method(m_doc ? m_nodes.top() : 0, attributes, this);
4737
4738 if (node) {
4739 if (!m_doc) {
4740 Q_ASSERT(node->type() == QSvgNode::Doc);
4741 m_doc = static_cast<QSvgTinyDocument*>(node);
4742 } else {
4743 switch (m_nodes.top()->type()) {
4744 case QSvgNode::Doc:
4745 case QSvgNode::Group:
4746 case QSvgNode::Defs:
4747 case QSvgNode::Switch:
4748 case QSvgNode::Mask:
4749 case QSvgNode::Symbol:
4750 case QSvgNode::Marker:
4751 case QSvgNode::Pattern:
4752 {
4753 QSvgStructureNode *group =
4754 static_cast<QSvgStructureNode*>(m_nodes.top());
4755 group->addChild(child: node, id: someId(attributes));
4756 }
4757 break;
4758 default:
4759 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
4760 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4761 delete node;
4762 node = 0;
4763 break;
4764 }
4765 }
4766
4767 if (node) {
4768 parseCoreNode(node, attributes);
4769#ifndef QT_NO_CSSPARSER
4770 cssStyleLookup(node, handler: this, selector: m_selector);
4771#endif
4772 parseStyle(node, attrs: attributes, handler: this);
4773 if (node->type() == QSvgNode::Filter)
4774 m_toBeResolved.append(t: node);
4775 }
4776 }
4777 } else if (FactoryMethod method = findGraphicsFactory(name: localName, options: options())) {
4778 //rendering element
4779 Q_ASSERT(!m_nodes.isEmpty());
4780 node = method(m_nodes.top(), attributes, this);
4781 if (node) {
4782 switch (m_nodes.top()->type()) {
4783 case QSvgNode::Doc:
4784 case QSvgNode::Group:
4785 case QSvgNode::Defs:
4786 case QSvgNode::Switch:
4787 case QSvgNode::Mask:
4788 case QSvgNode::Symbol:
4789 case QSvgNode::Marker:
4790 case QSvgNode::Pattern:
4791 {
4792 if (node->type() == QSvgNode::Tspan) {
4793 const QByteArray msg = QByteArrayLiteral("\'tspan\' element in wrong context.");
4794 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4795 delete node;
4796 node = 0;
4797 break;
4798 }
4799 QSvgStructureNode *group =
4800 static_cast<QSvgStructureNode*>(m_nodes.top());
4801 group->addChild(child: node, id: someId(attributes));
4802 }
4803 break;
4804 case QSvgNode::Text:
4805 case QSvgNode::Textarea:
4806 if (node->type() == QSvgNode::Tspan) {
4807 static_cast<QSvgText *>(m_nodes.top())->addTspan(tspan: static_cast<QSvgTspan *>(node));
4808 } else {
4809 const QByteArray msg = QByteArrayLiteral("\'text\' or \'textArea\' element contains invalid element type.");
4810 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4811 delete node;
4812 node = 0;
4813 }
4814 break;
4815 default:
4816 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
4817 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4818 delete node;
4819 node = 0;
4820 break;
4821 }
4822
4823 if (node) {
4824 parseCoreNode(node, attributes);
4825#ifndef QT_NO_CSSPARSER
4826 cssStyleLookup(node, handler: this, selector: m_selector);
4827#endif
4828 parseStyle(node, attrs: attributes, handler: this);
4829 if (node->type() == QSvgNode::Text || node->type() == QSvgNode::Textarea) {
4830 static_cast<QSvgText *>(node)->setWhitespaceMode(m_whitespaceMode.top());
4831 } else if (node->type() == QSvgNode::Tspan) {
4832 static_cast<QSvgTspan *>(node)->setWhitespaceMode(m_whitespaceMode.top());
4833 } else if (node->type() == QSvgNode::Use) {
4834 auto useNode = static_cast<QSvgUse *>(node);
4835 if (!useNode->isResolved())
4836 m_toBeResolved.append(t: useNode);
4837 }
4838 }
4839 }
4840 } else if (FactoryMethod method = findFilterFactory(name: localName, options: options())) {
4841 //filter nodes to be aded to be filtercontainer
4842 Q_ASSERT(!m_nodes.isEmpty());
4843 node = method(m_nodes.top(), attributes, this);
4844 if (node) {
4845 if (m_nodes.top()->type() == QSvgNode::Filter ||
4846 (m_nodes.top()->type() == QSvgNode::FeMerge && node->type() == QSvgNode::FeMergenode)) {
4847 QSvgStructureNode *container =
4848 static_cast<QSvgStructureNode*>(m_nodes.top());
4849 container->addChild(child: node, id: someId(attributes));
4850 } else {
4851 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
4852 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4853 delete node;
4854 node = 0;
4855 }
4856 }
4857 } else if (ParseMethod method = findUtilFactory(name: localName, options: options())) {
4858 Q_ASSERT(!m_nodes.isEmpty());
4859 if (!method(m_nodes.top(), attributes, this))
4860 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
4861 } else if (StyleFactoryMethod method = findStyleFactoryMethod(name: localName)) {
4862 QSvgStyleProperty *prop = method(m_nodes.top(), attributes, this);
4863 if (prop) {
4864 m_style = prop;
4865 m_nodes.top()->appendStyleProperty(prop, id: someId(attributes));
4866 } else {
4867 const QByteArray msg = QByteArrayLiteral("Could not parse node: ") + localName.toLocal8Bit();
4868 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4869 }
4870 } else if (StyleParseMethod method = findStyleUtilFactoryMethod(name: localName)) {
4871 if (m_style) {
4872 if (!method(m_style, attributes, this))
4873 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
4874 }
4875 } else {
4876 qCDebug(lcSvgHandler) << "Skipping unknown element" << localName;
4877 m_skipNodes.push(t: Unknown);
4878 return true;
4879 }
4880
4881 if (node) {
4882 m_nodes.push(t: node);
4883 m_skipNodes.push(t: Graphics);
4884 } else {
4885 //qDebug()<<"Skipping "<<localName;
4886 m_skipNodes.push(t: Style);
4887 }
4888 return true;
4889}
4890
4891bool QSvgHandler::endElement(const QStringView localName)
4892{
4893 CurrentNode node = m_skipNodes.top();
4894
4895 if (node == Doc && localName != QLatin1String("svg"))
4896 return false;
4897
4898 m_skipNodes.pop();
4899 m_whitespaceMode.pop();
4900
4901 popColor();
4902
4903 if (node == Unknown)
4904 return false;
4905
4906#ifdef QT_NO_CSSPARSER
4907 Q_UNUSED(localName);
4908#else
4909 if (m_inStyle && localName == QLatin1String("style"))
4910 m_inStyle = false;
4911#endif
4912
4913 if (node == Graphics)
4914 m_nodes.pop();
4915 else if (m_style && !m_skipNodes.isEmpty() && m_skipNodes.top() != Style)
4916 m_style = 0;
4917
4918 return ((localName == QLatin1String("svg")) && (node != Doc));
4919}
4920
4921void QSvgHandler::resolvePaintServers(QSvgNode *node, int nestedDepth)
4922{
4923 if (!node || (node->type() != QSvgNode::Doc && node->type() != QSvgNode::Group
4924 && node->type() != QSvgNode::Defs && node->type() != QSvgNode::Switch)) {
4925 return;
4926 }
4927
4928 QSvgStructureNode *structureNode = static_cast<QSvgStructureNode *>(node);
4929
4930 const QList<QSvgNode *> ren = structureNode->renderers();
4931 for (auto it = ren.begin(); it != ren.end(); ++it) {
4932 QSvgFillStyle *fill = static_cast<QSvgFillStyle *>((*it)->styleProperty(type: QSvgStyleProperty::FILL));
4933 if (fill && !fill->isPaintStyleResolved()) {
4934 QString id = fill->paintStyleId();
4935 QSvgPaintStyleProperty *style = structureNode->styleProperty(id);
4936 if (style) {
4937 fill->setFillStyle(style);
4938 } else {
4939 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
4940 fill->setBrush(Qt::NoBrush);
4941 }
4942 }
4943
4944 QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>((*it)->styleProperty(type: QSvgStyleProperty::STROKE));
4945 if (stroke && !stroke->isPaintStyleResolved()) {
4946 QString id = stroke->paintStyleId();
4947 QSvgPaintStyleProperty *style = structureNode->styleProperty(id);
4948 if (style) {
4949 stroke->setStyle(style);
4950 } else {
4951 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
4952 stroke->setStroke(Qt::NoBrush);
4953 }
4954 }
4955
4956 if (nestedDepth < 2048)
4957 resolvePaintServers(node: *it, nestedDepth: nestedDepth + 1);
4958 }
4959}
4960
4961void QSvgHandler::resolveNodes()
4962{
4963 for (QSvgNode *node : std::as_const(t&: m_toBeResolved)) {
4964 if (node->type() == QSvgNode::Use) {
4965 QSvgUse *useNode = static_cast<QSvgUse *>(node);
4966 const auto parent = useNode->parent();
4967 if (!parent)
4968 continue;
4969
4970 QSvgNode::Type t = parent->type();
4971 if (t != QSvgNode::Doc && t != QSvgNode::Defs && t != QSvgNode::Group && t != QSvgNode::Switch)
4972 continue;
4973
4974 QSvgStructureNode *group = static_cast<QSvgStructureNode *>(parent);
4975 QSvgNode *link = group->scopeNode(id: useNode->linkId());
4976 if (!link) {
4977 qCWarning(lcSvgHandler, "link #%s is undefined!", qPrintable(useNode->linkId()));
4978 continue;
4979 }
4980
4981 if (useNode->parent()->isDescendantOf(parent: link))
4982 qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(useNode->linkId()));
4983
4984 useNode->setLink(link);
4985 } else if (node->type() == QSvgNode::Filter) {
4986 QSvgFilterContainer *filter = static_cast<QSvgFilterContainer *>(node);
4987 for (const QSvgNode *renderer : filter->renderers()) {
4988 const QSvgFeFilterPrimitive *primitive = QSvgFeFilterPrimitive::castToFilterPrimitive(node: renderer);
4989 if (!primitive || primitive->type() == QSvgNode::FeUnsupported) {
4990 filter->setSupported(false);
4991 break;
4992 }
4993 }
4994 }
4995 }
4996 m_toBeResolved.clear();
4997}
4998
4999bool QSvgHandler::characters(const QStringView str)
5000{
5001#ifndef QT_NO_CSSPARSER
5002 if (m_inStyle) {
5003 QString css = str.toString();
5004 QCss::StyleSheet sheet;
5005 QCss::Parser(css).parse(styleSheet: &sheet);
5006 m_selector->styleSheets.append(t: sheet);
5007 return true;
5008 }
5009#endif
5010 if (m_skipNodes.isEmpty() || m_skipNodes.top() == Unknown || m_nodes.isEmpty())
5011 return true;
5012
5013 if (m_nodes.top()->type() == QSvgNode::Text || m_nodes.top()->type() == QSvgNode::Textarea) {
5014 static_cast<QSvgText*>(m_nodes.top())->addText(text: str.toString());
5015 } else if (m_nodes.top()->type() == QSvgNode::Tspan) {
5016 static_cast<QSvgTspan*>(m_nodes.top())->addText(text: str.toString());
5017 }
5018
5019 return true;
5020}
5021
5022QIODevice *QSvgHandler::device() const
5023{
5024 return xml->device();
5025}
5026
5027QSvgTinyDocument *QSvgHandler::document() const
5028{
5029 return m_doc;
5030}
5031
5032QSvgHandler::LengthType QSvgHandler::defaultCoordinateSystem() const
5033{
5034 return m_defaultCoords;
5035}
5036
5037void QSvgHandler::setDefaultCoordinateSystem(LengthType type)
5038{
5039 m_defaultCoords = type;
5040}
5041
5042void QSvgHandler::pushColor(const QColor &color)
5043{
5044 m_colorStack.push(t: color);
5045 m_colorTagCount.push(t: 1);
5046}
5047
5048void QSvgHandler::pushColorCopy()
5049{
5050 if (m_colorTagCount.size())
5051 ++m_colorTagCount.top();
5052 else
5053 pushColor(color: Qt::black);
5054}
5055
5056void QSvgHandler::popColor()
5057{
5058 if (m_colorTagCount.size()) {
5059 if (!--m_colorTagCount.top()) {
5060 m_colorStack.pop();
5061 m_colorTagCount.pop();
5062 }
5063 }
5064}
5065
5066QColor QSvgHandler::currentColor() const
5067{
5068 if (!m_colorStack.isEmpty())
5069 return m_colorStack.top();
5070 else
5071 return QColor(0, 0, 0);
5072}
5073
5074#ifndef QT_NO_CSSPARSER
5075
5076void QSvgHandler::setInStyle(bool b)
5077{
5078 m_inStyle = b;
5079}
5080
5081bool QSvgHandler::inStyle() const
5082{
5083 return m_inStyle;
5084}
5085
5086QSvgStyleSelector * QSvgHandler::selector() const
5087{
5088 return m_selector;
5089}
5090
5091#endif // QT_NO_CSSPARSER
5092
5093bool QSvgHandler::processingInstruction(const QString &target, const QString &data)
5094{
5095#ifdef QT_NO_CSSPARSER
5096 Q_UNUSED(target);
5097 Q_UNUSED(data);
5098#else
5099 if (target == QLatin1String("xml-stylesheet")) {
5100 QRegularExpression rx(QLatin1String("type=\\\"(.+)\\\""),
5101 QRegularExpression::InvertedGreedinessOption);
5102 QRegularExpressionMatchIterator iter = rx.globalMatch(subject: data);
5103 bool isCss = false;
5104 while (iter.hasNext()) {
5105 QRegularExpressionMatch match = iter.next();
5106 QString type = match.captured(nth: 1);
5107 if (type.toLower() == QLatin1String("text/css")) {
5108 isCss = true;
5109 }
5110 }
5111
5112 if (isCss) {
5113 QRegularExpression rx(QLatin1String("href=\\\"(.+)\\\""),
5114 QRegularExpression::InvertedGreedinessOption);
5115 QRegularExpressionMatch match = rx.match(subject: data);
5116 QString addr = match.captured(nth: 1);
5117 QFileInfo fi(addr);
5118 //qDebug()<<"External CSS file "<<fi.absoluteFilePath()<<fi.exists();
5119 if (fi.exists()) {
5120 QFile file(fi.absoluteFilePath());
5121 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
5122 return true;
5123 }
5124 QByteArray cssData = file.readAll();
5125 QString css = QString::fromUtf8(ba: cssData);
5126
5127 QCss::StyleSheet sheet;
5128 QCss::Parser(css).parse(styleSheet: &sheet);
5129 m_selector->styleSheets.append(t: sheet);
5130 }
5131
5132 }
5133 }
5134#endif
5135
5136 return true;
5137}
5138
5139void QSvgHandler::setAnimPeriod(int start, int end)
5140{
5141 Q_UNUSED(start);
5142 m_animEnd = qMax(a: end, b: m_animEnd);
5143}
5144
5145int QSvgHandler::animationDuration() const
5146{
5147 return m_animEnd;
5148}
5149
5150QSvgHandler::~QSvgHandler()
5151{
5152#ifndef QT_NO_CSSPARSER
5153 delete m_selector;
5154 m_selector = 0;
5155#endif
5156
5157 if(m_ownsReader)
5158 delete xml;
5159}
5160
5161QT_END_NAMESPACE
5162

source code of qtsvg/src/svg/qsvghandler.cpp