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 "qsvgnode_p.h"
12#include "qsvgfont_p.h"
13
14#include "qpen.h"
15#include "qpainterpath.h"
16#include "qbrush.h"
17#include "qcolor.h"
18#include "qtextformat.h"
19#include "qlist.h"
20#include "qfileinfo.h"
21#include "qfile.h"
22#include "qdir.h"
23#include "qdebug.h"
24#include "qmath.h"
25#include "qnumeric.h"
26#include <qregularexpression.h>
27#include "qtransform.h"
28#include "qvarlengtharray.h"
29#include "private/qmath_p.h"
30
31#include "float.h"
32#include <cmath>
33
34QT_BEGIN_NAMESPACE
35
36Q_LOGGING_CATEGORY(lcSvgHandler, "qt.svg")
37
38static const char *qt_inherit_text = "inherit";
39#define QT_INHERIT QLatin1String(qt_inherit_text)
40
41static QByteArray prefixMessage(const QByteArray &msg, const QXmlStreamReader *r)
42{
43 QByteArray result;
44 if (r) {
45 if (const QFile *file = qobject_cast<const QFile *>(object: r->device()))
46 result.append(a: QFile::encodeName(fileName: QDir::toNativeSeparators(pathName: file->fileName())));
47 else
48 result.append(QByteArrayLiteral("<input>"));
49 result.append(c: ':');
50 result.append(a: QByteArray::number(r->lineNumber()));
51 if (const qint64 column = r->columnNumber()) {
52 result.append(c: ':');
53 result.append(a: QByteArray::number(column));
54 }
55 result.append(QByteArrayLiteral(": "));
56 }
57 result.append(a: msg);
58 return result;
59}
60
61static inline QByteArray msgProblemParsing(const QString &localName, const QXmlStreamReader *r)
62{
63 return prefixMessage(QByteArrayLiteral("Problem parsing ") + localName.toLocal8Bit(), r);
64}
65
66static inline QByteArray msgCouldNotResolveProperty(const QString &id, const QXmlStreamReader *r)
67{
68 return prefixMessage(QByteArrayLiteral("Could not resolve property: ") + id.toLocal8Bit(), r);
69}
70
71// ======== duplicated from qcolor_p
72
73static inline int qsvg_h2i(char hex, bool *ok = nullptr)
74{
75 if (hex >= '0' && hex <= '9')
76 return hex - '0';
77 if (hex >= 'a' && hex <= 'f')
78 return hex - 'a' + 10;
79 if (hex >= 'A' && hex <= 'F')
80 return hex - 'A' + 10;
81 if (ok)
82 *ok = false;
83 return -1;
84}
85
86static inline int qsvg_hex2int(const char *s, bool *ok = nullptr)
87{
88 return (qsvg_h2i(hex: s[0], ok) * 16) | qsvg_h2i(hex: s[1], ok);
89}
90
91static inline int qsvg_hex2int(char s, bool *ok = nullptr)
92{
93 int h = qsvg_h2i(hex: s, ok);
94 return (h * 16) | h;
95}
96
97bool qsvg_get_hex_rgb(const char *name, QRgb *rgb)
98{
99 if(name[0] != '#')
100 return false;
101 name++;
102 const size_t len = qstrlen(str: name);
103 int r, g, b;
104 bool ok = true;
105 if (len == 12) {
106 r = qsvg_hex2int(s: name, ok: &ok);
107 g = qsvg_hex2int(s: name + 4, ok: &ok);
108 b = qsvg_hex2int(s: name + 8, ok: &ok);
109 } else if (len == 9) {
110 r = qsvg_hex2int(s: name, ok: &ok);
111 g = qsvg_hex2int(s: name + 3, ok: &ok);
112 b = qsvg_hex2int(s: name + 6, ok: &ok);
113 } else if (len == 6) {
114 r = qsvg_hex2int(s: name, ok: &ok);
115 g = qsvg_hex2int(s: name + 2, ok: &ok);
116 b = qsvg_hex2int(s: name + 4, ok: &ok);
117 } else if (len == 3) {
118 r = qsvg_hex2int(s: name[0], ok: &ok);
119 g = qsvg_hex2int(s: name[1], ok: &ok);
120 b = qsvg_hex2int(s: name[2], ok: &ok);
121 } else {
122 r = g = b = -1;
123 }
124 if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255 || !ok) {
125 *rgb = 0;
126 return false;
127 }
128 *rgb = qRgb(r, g ,b);
129 return true;
130}
131
132bool qsvg_get_hex_rgb(const QChar *str, int len, QRgb *rgb)
133{
134 if (len > 13)
135 return false;
136 char tmp[16];
137 for(int i = 0; i < len; ++i)
138 tmp[i] = str[i].toLatin1();
139 tmp[len] = 0;
140 return qsvg_get_hex_rgb(name: tmp, rgb);
141}
142
143// ======== end of qcolor_p duplicate
144
145static bool parsePathDataFast(QStringView data, QPainterPath &path);
146
147static inline QString someId(const QXmlStreamAttributes &attributes)
148{
149 QString id = attributes.value(qualifiedName: QLatin1String("id")).toString();
150 if (id.isEmpty())
151 id = attributes.value(qualifiedName: QLatin1String("xml:id")).toString();
152 return id;
153}
154
155struct QSvgAttributes
156{
157 QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler);
158
159 QString id;
160
161 QStringView color;
162 QStringView colorOpacity;
163 QStringView fill;
164 QStringView fillRule;
165 QStringView fillOpacity;
166 QStringView stroke;
167 QStringView strokeDashArray;
168 QStringView strokeDashOffset;
169 QStringView strokeLineCap;
170 QStringView strokeLineJoin;
171 QStringView strokeMiterLimit;
172 QStringView strokeOpacity;
173 QStringView strokeWidth;
174 QStringView vectorEffect;
175 QStringView fontFamily;
176 QStringView fontSize;
177 QStringView fontStyle;
178 QStringView fontWeight;
179 QStringView fontVariant;
180 QStringView textAnchor;
181 QStringView transform;
182 QStringView visibility;
183 QStringView opacity;
184 QStringView compOp;
185 QStringView display;
186 QStringView offset;
187 QStringView stopColor;
188 QStringView stopOpacity;
189 QStringView imageRendering;
190
191#ifndef QT_NO_CSSPARSER
192 QList<QSvgCssAttribute> m_cssAttributes;
193#endif
194};
195
196QSvgAttributes::QSvgAttributes(const QXmlStreamAttributes &xmlAttributes, QSvgHandler *handler)
197{
198 for (int i = 0; i < xmlAttributes.size(); ++i) {
199 const QXmlStreamAttribute &attribute = xmlAttributes.at(i);
200 QStringView name = attribute.qualifiedName();
201 if (name.isEmpty())
202 continue;
203 QStringView value = attribute.value();
204
205 switch (name.at(n: 0).unicode()) {
206
207 case 'c':
208 if (name == QLatin1String("color"))
209 color = value;
210 else if (name == QLatin1String("color-opacity"))
211 colorOpacity = value;
212 else if (name == QLatin1String("comp-op"))
213 compOp = value;
214 break;
215
216 case 'd':
217 if (name == QLatin1String("display"))
218 display = value;
219 break;
220
221 case 'f':
222 if (name == QLatin1String("fill"))
223 fill = value;
224 else if (name == QLatin1String("fill-rule"))
225 fillRule = value;
226 else if (name == QLatin1String("fill-opacity"))
227 fillOpacity = value;
228 else if (name == QLatin1String("font-family"))
229 fontFamily = value;
230 else if (name == QLatin1String("font-size"))
231 fontSize = value;
232 else if (name == QLatin1String("font-style"))
233 fontStyle = value;
234 else if (name == QLatin1String("font-weight"))
235 fontWeight = value;
236 else if (name == QLatin1String("font-variant"))
237 fontVariant = value;
238 break;
239
240 case 'i':
241 if (name == QLatin1String("id"))
242 id = value.toString();
243 else if (name == QLatin1String("image-rendering"))
244 imageRendering = value;
245 break;
246
247 case 'o':
248 if (name == QLatin1String("opacity"))
249 opacity = value;
250 if (name == QLatin1String("offset"))
251 offset = value;
252 break;
253
254 case 's':
255 if (name.size() > 5 && name.mid(pos: 1, n: 5) == QLatin1String("troke")) {
256 QStringView strokeRef = name.mid(pos: 6, n: name.size() - 6);
257 if (strokeRef.isEmpty())
258 stroke = value;
259 else if (strokeRef == QLatin1String("-dasharray"))
260 strokeDashArray = value;
261 else if (strokeRef == QLatin1String("-dashoffset"))
262 strokeDashOffset = value;
263 else if (strokeRef == QLatin1String("-linecap"))
264 strokeLineCap = value;
265 else if (strokeRef == QLatin1String("-linejoin"))
266 strokeLineJoin = value;
267 else if (strokeRef == QLatin1String("-miterlimit"))
268 strokeMiterLimit = value;
269 else if (strokeRef == QLatin1String("-opacity"))
270 strokeOpacity = value;
271 else if (strokeRef == QLatin1String("-width"))
272 strokeWidth = value;
273 } else if (name == QLatin1String("stop-color"))
274 stopColor = value;
275 else if (name == QLatin1String("stop-opacity"))
276 stopOpacity = value;
277 break;
278
279 case 't':
280 if (name == QLatin1String("text-anchor"))
281 textAnchor = value;
282 else if (name == QLatin1String("transform"))
283 transform = value;
284 break;
285
286 case 'v':
287 if (name == QLatin1String("vector-effect"))
288 vectorEffect = value;
289 else if (name == QLatin1String("visibility"))
290 visibility = value;
291 break;
292
293 case 'x':
294 if (name == QLatin1String("xml:id") && id.isEmpty())
295 id = value.toString();
296 break;
297
298 default:
299 break;
300 }
301 }
302
303 // If a style attribute is present, let its attribute settings override the plain attribute
304 // values. The spec seems to indicate that, and it is common behavior in svg renderers.
305#ifndef QT_NO_CSSPARSER
306 QStringView style = xmlAttributes.value(qualifiedName: QLatin1String("style"));
307 if (!style.isEmpty()) {
308 handler->parseCSStoXMLAttrs(css: style.toString(), attributes: &m_cssAttributes);
309 for (int j = 0; j < m_cssAttributes.size(); ++j) {
310 const QSvgCssAttribute &attribute = m_cssAttributes.at(i: j);
311 QStringView name = attribute.name;
312 QStringView value = attribute.value;
313 if (name.isEmpty())
314 continue;
315
316 switch (name.at(n: 0).unicode()) {
317
318 case 'c':
319 if (name == QLatin1String("color"))
320 color = value;
321 else if (name == QLatin1String("color-opacity"))
322 colorOpacity = value;
323 else if (name == QLatin1String("comp-op"))
324 compOp = value;
325 break;
326
327 case 'd':
328 if (name == QLatin1String("display"))
329 display = value;
330 break;
331
332 case 'f':
333 if (name == QLatin1String("fill"))
334 fill = value;
335 else if (name == QLatin1String("fill-rule"))
336 fillRule = value;
337 else if (name == QLatin1String("fill-opacity"))
338 fillOpacity = value;
339 else if (name == QLatin1String("font-family"))
340 fontFamily = value;
341 else if (name == QLatin1String("font-size"))
342 fontSize = value;
343 else if (name == QLatin1String("font-style"))
344 fontStyle = value;
345 else if (name == QLatin1String("font-weight"))
346 fontWeight = value;
347 else if (name == QLatin1String("font-variant"))
348 fontVariant = value;
349 break;
350 case 'i':
351 if (name == QLatin1String("image-rendering"))
352 imageRendering = value;
353 break;
354
355 case 'o':
356 if (name == QLatin1String("opacity"))
357 opacity = value;
358 else if (name == QLatin1String("offset"))
359 offset = value;
360 break;
361
362 case 's':
363 if (name.size() > 5 && name.mid(pos: 1, n: 5) == QLatin1String("troke")) {
364 QStringView strokeRef = name.mid(pos: 6, n: name.size() - 6);
365 if (strokeRef.isEmpty())
366 stroke = value;
367 else if (strokeRef == QLatin1String("-dasharray"))
368 strokeDashArray = value;
369 else if (strokeRef == QLatin1String("-dashoffset"))
370 strokeDashOffset = value;
371 else if (strokeRef == QLatin1String("-linecap"))
372 strokeLineCap = value;
373 else if (strokeRef == QLatin1String("-linejoin"))
374 strokeLineJoin = value;
375 else if (strokeRef == QLatin1String("-miterlimit"))
376 strokeMiterLimit = value;
377 else if (strokeRef == QLatin1String("-opacity"))
378 strokeOpacity = value;
379 else if (strokeRef == QLatin1String("-width"))
380 strokeWidth = value;
381 } else if (name == QLatin1String("stop-color"))
382 stopColor = value;
383 else if (name == QLatin1String("stop-opacity"))
384 stopOpacity = value;
385 break;
386
387 case 't':
388 if (name == QLatin1String("text-anchor"))
389 textAnchor = value;
390 else if (name == QLatin1String("transform"))
391 transform = value;
392 break;
393
394 case 'v':
395 if (name == QLatin1String("vector-effect"))
396 vectorEffect = value;
397 else if (name == QLatin1String("visibility"))
398 visibility = value;
399 break;
400
401 default:
402 break;
403 }
404 }
405 }
406#else
407 Q_UNUSED(handler);
408#endif // QT_NO_CSSPARSER
409}
410
411#ifndef QT_NO_CSSPARSER
412
413static const char * QSvgStyleSelector_nodeString[] = {
414 "svg",
415 "g",
416 "defs",
417 "switch",
418 "animation",
419 "arc",
420 "circle",
421 "ellipse",
422 "image",
423 "line",
424 "path",
425 "polygon",
426 "polyline",
427 "rect",
428 "text",
429 "textarea",
430 "tspan",
431 "use",
432 "video"
433};
434
435class QSvgStyleSelector : public QCss::StyleSelector
436{
437public:
438 QSvgStyleSelector()
439 {
440 nameCaseSensitivity = Qt::CaseInsensitive;
441 }
442 virtual ~QSvgStyleSelector()
443 {
444 }
445
446 inline QString nodeToName(QSvgNode *node) const
447 {
448 return QLatin1String(QSvgStyleSelector_nodeString[node->type()]);
449 }
450
451 inline QSvgNode *svgNode(NodePtr node) const
452 {
453 return (QSvgNode*)node.ptr;
454 }
455 inline QSvgStructureNode *nodeToStructure(QSvgNode *n) const
456 {
457 if (n &&
458 (n->type() == QSvgNode::DOC ||
459 n->type() == QSvgNode::G ||
460 n->type() == QSvgNode::DEFS ||
461 n->type() == QSvgNode::SWITCH)) {
462 return (QSvgStructureNode*)n;
463 }
464 return 0;
465 }
466
467 inline QSvgStructureNode *svgStructure(NodePtr node) const
468 {
469 QSvgNode *n = svgNode(node);
470 QSvgStructureNode *st = nodeToStructure(n);
471 return st;
472 }
473
474 bool nodeNameEquals(NodePtr node, const QString& nodeName) const override
475 {
476 QSvgNode *n = svgNode(node);
477 if (!n)
478 return false;
479 QString name = nodeToName(node: n);
480 return QString::compare(s1: name, s2: nodeName, cs: Qt::CaseInsensitive) == 0;
481 }
482 QString attributeValue(NodePtr node, const QCss::AttributeSelector &asel) const override
483 {
484 const QString &name = asel.name;
485 QSvgNode *n = svgNode(node);
486 if ((!n->nodeId().isEmpty() && (name == QLatin1String("id") ||
487 name == QLatin1String("xml:id"))))
488 return n->nodeId();
489 if (!n->xmlClass().isEmpty() && name == QLatin1String("class"))
490 return n->xmlClass();
491 return QString();
492 }
493 bool hasAttributes(NodePtr node) const override
494 {
495 QSvgNode *n = svgNode(node);
496 return (n &&
497 (!n->nodeId().isEmpty() || !n->xmlClass().isEmpty()));
498 }
499
500 QStringList nodeIds(NodePtr node) const override
501 {
502 QSvgNode *n = svgNode(node);
503 QString nid;
504 if (n)
505 nid = n->nodeId();
506 QStringList lst; lst.append(t: nid);
507 return lst;
508 }
509
510 QStringList nodeNames(NodePtr node) const override
511 {
512 QSvgNode *n = svgNode(node);
513 if (n)
514 return QStringList(nodeToName(node: n));
515 return QStringList();
516 }
517
518 bool isNullNode(NodePtr node) const override
519 {
520 return !node.ptr;
521 }
522
523 NodePtr parentNode(NodePtr node) const override
524 {
525 QSvgNode *n = svgNode(node);
526 NodePtr newNode;
527 newNode.ptr = 0;
528 newNode.id = 0;
529 if (n) {
530 QSvgNode *svgParent = n->parent();
531 if (svgParent) {
532 newNode.ptr = svgParent;
533 }
534 }
535 return newNode;
536 }
537 NodePtr previousSiblingNode(NodePtr node) const override
538 {
539 NodePtr newNode;
540 newNode.ptr = 0;
541 newNode.id = 0;
542
543 QSvgNode *n = svgNode(node);
544 if (!n)
545 return newNode;
546 QSvgStructureNode *svgParent = nodeToStructure(n: n->parent());
547
548 if (svgParent) {
549 newNode.ptr = svgParent->previousSiblingNode(n);
550 }
551 return newNode;
552 }
553 NodePtr duplicateNode(NodePtr node) const override
554 {
555 NodePtr n;
556 n.ptr = node.ptr;
557 n.id = node.id;
558 return n;
559 }
560 void freeNode(NodePtr node) const override
561 {
562 Q_UNUSED(node);
563 }
564};
565
566#endif // QT_NO_CSSPARSER
567
568// '0' is 0x30 and '9' is 0x39
569static inline bool isDigit(ushort ch)
570{
571 static quint16 magic = 0x3ff;
572 return ((ch >> 4) == 3) && (magic >> (ch & 15));
573}
574
575static qreal toDouble(const QChar *&str)
576{
577 const int maxLen = 255;//technically doubles can go til 308+ but whatever
578 char temp[maxLen+1];
579 int pos = 0;
580
581 if (*str == QLatin1Char('-')) {
582 temp[pos++] = '-';
583 ++str;
584 } else if (*str == QLatin1Char('+')) {
585 ++str;
586 }
587 while (isDigit(ch: str->unicode()) && pos < maxLen) {
588 temp[pos++] = str->toLatin1();
589 ++str;
590 }
591 if (*str == QLatin1Char('.') && pos < maxLen) {
592 temp[pos++] = '.';
593 ++str;
594 }
595 while (isDigit(ch: str->unicode()) && pos < maxLen) {
596 temp[pos++] = str->toLatin1();
597 ++str;
598 }
599 bool exponent = false;
600 if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
601 exponent = true;
602 temp[pos++] = 'e';
603 ++str;
604 if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
605 temp[pos++] = str->toLatin1();
606 ++str;
607 }
608 while (isDigit(ch: str->unicode()) && pos < maxLen) {
609 temp[pos++] = str->toLatin1();
610 ++str;
611 }
612 }
613
614 temp[pos] = '\0';
615
616 qreal val;
617 if (!exponent && pos < 10) {
618 int ival = 0;
619 const char *t = temp;
620 bool neg = false;
621 if(*t == '-') {
622 neg = true;
623 ++t;
624 }
625 while(*t && *t != '.') {
626 ival *= 10;
627 ival += (*t) - '0';
628 ++t;
629 }
630 if(*t == '.') {
631 ++t;
632 int div = 1;
633 while(*t) {
634 ival *= 10;
635 ival += (*t) - '0';
636 div *= 10;
637 ++t;
638 }
639 val = ((qreal)ival)/((qreal)div);
640 } else {
641 val = ival;
642 }
643 if (neg)
644 val = -val;
645 } else {
646 val = QByteArray::fromRawData(data: temp, size: pos).toDouble();
647 // Do not tolerate values too wild to be represented normally by floats
648 if (qFpClassify(val: float(val)) != FP_NORMAL)
649 val = 0;
650 }
651 return val;
652
653}
654
655static qreal toDouble(QStringView str, bool *ok = NULL)
656{
657 const QChar *c = str.constData();
658 qreal res = (c == nullptr ? qreal{} : toDouble(str&: c));
659 if (ok)
660 *ok = (c == (str.constData() + str.size()));
661 return res;
662}
663
664static QList<qreal> parseNumbersList(const QChar *&str)
665{
666 QList<qreal> points;
667 if (!str)
668 return points;
669 points.reserve(asize: 32);
670
671 while (str->isSpace())
672 ++str;
673 while (isDigit(ch: str->unicode()) ||
674 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
675 *str == QLatin1Char('.')) {
676
677 points.append(t: toDouble(str));
678
679 while (str->isSpace())
680 ++str;
681 if (*str == QLatin1Char(','))
682 ++str;
683
684 //eat the rest of space
685 while (str->isSpace())
686 ++str;
687 }
688
689 return points;
690}
691
692static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points,
693 const char *pattern = nullptr)
694{
695 const size_t patternLen = qstrlen(str: pattern);
696 while (str->isSpace())
697 ++str;
698 while (isDigit(ch: str->unicode()) ||
699 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
700 *str == QLatin1Char('.')) {
701
702 if (patternLen && pattern[points.size() % patternLen] == 'f') {
703 // flag expected, may only be 0 or 1
704 if (*str != QLatin1Char('0') && *str != QLatin1Char('1'))
705 return;
706 points.append(t: *str == QLatin1Char('0') ? 0.0 : 1.0);
707 ++str;
708 } else {
709 points.append(t: toDouble(str));
710 }
711
712 while (str->isSpace())
713 ++str;
714 if (*str == QLatin1Char(','))
715 ++str;
716
717 //eat the rest of space
718 while (str->isSpace())
719 ++str;
720 }
721}
722
723static QList<qreal> parsePercentageList(const QChar *&str)
724{
725 QList<qreal> points;
726 if (!str)
727 return points;
728
729 while (str->isSpace())
730 ++str;
731 while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) ||
732 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
733 *str == QLatin1Char('.')) {
734
735 points.append(t: toDouble(str));
736
737 while (str->isSpace())
738 ++str;
739 if (*str == QLatin1Char('%'))
740 ++str;
741 while (str->isSpace())
742 ++str;
743 if (*str == QLatin1Char(','))
744 ++str;
745
746 //eat the rest of space
747 while (str->isSpace())
748 ++str;
749 }
750
751 return points;
752}
753
754static QString idFromUrl(const QString &url)
755{
756 // The form is url(<IRI>), where IRI can be
757 // just an ID on #<id> form.
758 QString::const_iterator itr = url.constBegin();
759 QString::const_iterator end = url.constEnd();
760 QString id;
761 while (itr != end && (*itr).isSpace())
762 ++itr;
763 if (itr != end && (*itr) == QLatin1Char('('))
764 ++itr;
765 else
766 return QString();
767 while (itr != end && (*itr).isSpace())
768 ++itr;
769 if (itr != end && (*itr) == QLatin1Char('#')) {
770 id += *itr;
771 ++itr;
772 } else {
773 return QString();
774 }
775 while (itr != end && (*itr) != QLatin1Char(')')) {
776 id += *itr;
777 ++itr;
778 }
779 if (itr == end || (*itr) != QLatin1Char(')'))
780 return QString();
781 return id;
782}
783
784/**
785 * returns true when successfully set the color. false signifies
786 * that the color should be inherited
787 */
788static bool resolveColor(QStringView colorStr, QColor &color, QSvgHandler *handler)
789{
790 QStringView colorStrTr = colorStr.trimmed();
791 if (colorStrTr.isEmpty())
792 return false;
793
794 switch(colorStrTr.at(n: 0).unicode()) {
795
796 case '#':
797 {
798 // #rrggbb is very very common, so let's tackle it here
799 // rather than falling back to QColor
800 QRgb rgb;
801 bool ok = qsvg_get_hex_rgb(str: colorStrTr.constData(), len: colorStrTr.size(), rgb: &rgb);
802 if (ok)
803 color.setRgb(rgb);
804 return ok;
805 }
806 break;
807
808 case 'r':
809 {
810 // starts with "rgb(", ends with ")" and consists of at least 7 characters "rgb(,,)"
811 if (colorStrTr.size() >= 7 && colorStrTr.at(n: colorStrTr.size() - 1) == QLatin1Char(')')
812 && colorStrTr.mid(pos: 0, n: 4) == QLatin1String("rgb(")) {
813 const QChar *s = colorStrTr.constData() + 4;
814 QList<qreal> compo = parseNumbersList(str&: s);
815 //1 means that it failed after reaching non-parsable
816 //character which is going to be "%"
817 if (compo.size() == 1) {
818 s = colorStrTr.constData() + 4;
819 compo = parsePercentageList(str&: s);
820 for (int i = 0; i < compo.size(); ++i)
821 compo[i] *= (qreal)2.55;
822 }
823
824 if (compo.size() == 3) {
825 color = QColor(int(compo[0]),
826 int(compo[1]),
827 int(compo[2]));
828 return true;
829 }
830 return false;
831 }
832 }
833 break;
834
835 case 'c':
836 if (colorStrTr == QLatin1String("currentColor")) {
837 color = handler->currentColor();
838 return true;
839 }
840 break;
841 case 'i':
842 if (colorStrTr == QT_INHERIT)
843 return false;
844 break;
845 default:
846 break;
847 }
848
849 color = QColor::fromString(name: colorStrTr.toString());
850 return color.isValid();
851}
852
853static bool constructColor(QStringView colorStr, QStringView opacity,
854 QColor &color, QSvgHandler *handler)
855{
856 if (!resolveColor(colorStr, color, handler))
857 return false;
858 if (!opacity.isEmpty()) {
859 bool ok = true;
860 qreal op = qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: opacity, ok: &ok)));
861 if (!ok)
862 op = 1.0;
863 color.setAlphaF(op);
864 }
865 return true;
866}
867
868static qreal parseLength(QStringView str, QSvgHandler::LengthType &type,
869 QSvgHandler *handler, bool *ok = NULL)
870{
871 QStringView numStr = str.trimmed();
872
873 if (numStr.endsWith(c: QLatin1Char('%'))) {
874 numStr.chop(n: 1);
875 type = QSvgHandler::LT_PERCENT;
876 } else if (numStr.endsWith(s: QLatin1String("px"))) {
877 numStr.chop(n: 2);
878 type = QSvgHandler::LT_PX;
879 } else if (numStr.endsWith(s: QLatin1String("pc"))) {
880 numStr.chop(n: 2);
881 type = QSvgHandler::LT_PC;
882 } else if (numStr.endsWith(s: QLatin1String("pt"))) {
883 numStr.chop(n: 2);
884 type = QSvgHandler::LT_PT;
885 } else if (numStr.endsWith(s: QLatin1String("mm"))) {
886 numStr.chop(n: 2);
887 type = QSvgHandler::LT_MM;
888 } else if (numStr.endsWith(s: QLatin1String("cm"))) {
889 numStr.chop(n: 2);
890 type = QSvgHandler::LT_CM;
891 } else if (numStr.endsWith(s: QLatin1String("in"))) {
892 numStr.chop(n: 2);
893 type = QSvgHandler::LT_IN;
894 } else {
895 type = handler->defaultCoordinateSystem();
896 //type = QSvgHandler::LT_OTHER;
897 }
898 qreal len = toDouble(str: numStr, ok);
899 //qDebug()<<"len is "<<len<<", from '"<<numStr << "'";
900 return len;
901}
902
903static inline qreal convertToNumber(QStringView str, QSvgHandler *handler, bool *ok = NULL)
904{
905 QSvgHandler::LengthType type;
906 qreal num = parseLength(str: str.toString(), type, handler, ok);
907 if (type == QSvgHandler::LT_PERCENT) {
908 num = num/100.0;
909 }
910 return num;
911}
912
913static bool createSvgGlyph(QSvgFont *font, const QXmlStreamAttributes &attributes)
914{
915 QStringView uncStr = attributes.value(qualifiedName: QLatin1String("unicode"));
916 QStringView havStr = attributes.value(qualifiedName: QLatin1String("horiz-adv-x"));
917 QStringView pathStr = attributes.value(qualifiedName: QLatin1String("d"));
918
919 QChar unicode = (uncStr.isEmpty()) ? u'\0' : uncStr.at(n: 0);
920 qreal havx = (havStr.isEmpty()) ? -1 : toDouble(str: havStr);
921 QPainterPath path;
922 path.setFillRule(Qt::WindingFill);
923 parsePathDataFast(data: pathStr, path);
924
925 font->addGlyph(unicode, path, horizAdvX: havx);
926
927 return true;
928}
929
930// this should really be called convertToDefaultCoordinateSystem
931// and convert when type != QSvgHandler::defaultCoordinateSystem
932static qreal convertToPixels(qreal len, bool , QSvgHandler::LengthType type)
933{
934
935 switch (type) {
936 case QSvgHandler::LT_PERCENT:
937 break;
938 case QSvgHandler::LT_PX:
939 break;
940 case QSvgHandler::LT_PC:
941 break;
942 case QSvgHandler::LT_PT:
943 return len * 1.25;
944 break;
945 case QSvgHandler::LT_MM:
946 return len * 3.543307;
947 break;
948 case QSvgHandler::LT_CM:
949 return len * 35.43307;
950 break;
951 case QSvgHandler::LT_IN:
952 return len * 90;
953 break;
954 case QSvgHandler::LT_OTHER:
955 break;
956 default:
957 break;
958 }
959 return len;
960}
961
962static void parseColor(QSvgNode *,
963 const QSvgAttributes &attributes,
964 QSvgHandler *handler)
965{
966 QColor color;
967 if (constructColor(colorStr: attributes.color, opacity: attributes.colorOpacity, color, handler)) {
968 handler->popColor();
969 handler->pushColor(color);
970 }
971}
972
973static QSvgStyleProperty *styleFromUrl(QSvgNode *node, const QString &url)
974{
975 return node ? node->styleProperty(id: idFromUrl(url)) : 0;
976}
977
978static void parseBrush(QSvgNode *node,
979 const QSvgAttributes &attributes,
980 QSvgHandler *handler)
981{
982 if (!attributes.fill.isEmpty() || !attributes.fillRule.isEmpty() || !attributes.fillOpacity.isEmpty()) {
983 QSvgFillStyle *prop = new QSvgFillStyle;
984
985 //fill-rule attribute handling
986 if (!attributes.fillRule.isEmpty() && attributes.fillRule != QT_INHERIT) {
987 if (attributes.fillRule == QLatin1String("evenodd"))
988 prop->setFillRule(Qt::OddEvenFill);
989 else if (attributes.fillRule == QLatin1String("nonzero"))
990 prop->setFillRule(Qt::WindingFill);
991 }
992
993 //fill-opacity attribute handling
994 if (!attributes.fillOpacity.isEmpty() && attributes.fillOpacity != QT_INHERIT) {
995 prop->setFillOpacity(qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: attributes.fillOpacity))));
996 }
997
998 //fill attribute handling
999 if ((!attributes.fill.isEmpty()) && (attributes.fill != QT_INHERIT) ) {
1000 if (attributes.fill.size() > 3 && attributes.fill.mid(pos: 0, n: 3) == QLatin1String("url")) {
1001 QString value = attributes.fill.mid(pos: 3, n: attributes.fill.size() - 3).toString();
1002 QSvgStyleProperty *style = styleFromUrl(node, url: value);
1003 if (style) {
1004 if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT)
1005 prop->setFillStyle(reinterpret_cast<QSvgFillStyleProperty *>(style));
1006 } else {
1007 QString id = idFromUrl(url: value);
1008 prop->setGradientId(id);
1009 prop->setGradientResolved(false);
1010 }
1011 } else if (attributes.fill != QLatin1String("none")) {
1012 QColor color;
1013 if (resolveColor(colorStr: attributes.fill, color, handler))
1014 prop->setBrush(QBrush(color));
1015 } else {
1016 prop->setBrush(QBrush(Qt::NoBrush));
1017 }
1018 }
1019 node->appendStyleProperty(prop, id: attributes.id);
1020 }
1021}
1022
1023
1024
1025static QTransform parseTransformationMatrix(QStringView value)
1026{
1027 if (value.isEmpty())
1028 return QTransform();
1029
1030 QTransform matrix;
1031 const QChar *str = value.constData();
1032 const QChar *end = str + value.size();
1033
1034 while (str < end) {
1035 if (str->isSpace() || *str == QLatin1Char(',')) {
1036 ++str;
1037 continue;
1038 }
1039 enum State {
1040 Matrix,
1041 Translate,
1042 Rotate,
1043 Scale,
1044 SkewX,
1045 SkewY
1046 };
1047 State state = Matrix;
1048 if (*str == QLatin1Char('m')) { //matrix
1049 const char *ident = "atrix";
1050 for (int i = 0; i < 5; ++i)
1051 if (*(++str) != QLatin1Char(ident[i]))
1052 goto error;
1053 ++str;
1054 state = Matrix;
1055 } else if (*str == QLatin1Char('t')) { //translate
1056 const char *ident = "ranslate";
1057 for (int i = 0; i < 8; ++i)
1058 if (*(++str) != QLatin1Char(ident[i]))
1059 goto error;
1060 ++str;
1061 state = Translate;
1062 } else if (*str == QLatin1Char('r')) { //rotate
1063 const char *ident = "otate";
1064 for (int i = 0; i < 5; ++i)
1065 if (*(++str) != QLatin1Char(ident[i]))
1066 goto error;
1067 ++str;
1068 state = Rotate;
1069 } else if (*str == QLatin1Char('s')) { //scale, skewX, skewY
1070 ++str;
1071 if (*str == QLatin1Char('c')) {
1072 const char *ident = "ale";
1073 for (int i = 0; i < 3; ++i)
1074 if (*(++str) != QLatin1Char(ident[i]))
1075 goto error;
1076 ++str;
1077 state = Scale;
1078 } else if (*str == QLatin1Char('k')) {
1079 if (*(++str) != QLatin1Char('e'))
1080 goto error;
1081 if (*(++str) != QLatin1Char('w'))
1082 goto error;
1083 ++str;
1084 if (*str == QLatin1Char('X'))
1085 state = SkewX;
1086 else if (*str == QLatin1Char('Y'))
1087 state = SkewY;
1088 else
1089 goto error;
1090 ++str;
1091 } else {
1092 goto error;
1093 }
1094 } else {
1095 goto error;
1096 }
1097
1098
1099 while (str < end && str->isSpace())
1100 ++str;
1101 if (*str != QLatin1Char('('))
1102 goto error;
1103 ++str;
1104 QVarLengthArray<qreal, 8> points;
1105 parseNumbersArray(str, points);
1106 if (*str != QLatin1Char(')'))
1107 goto error;
1108 ++str;
1109
1110 if(state == Matrix) {
1111 if(points.size() != 6)
1112 goto error;
1113 matrix = QTransform(points[0], points[1],
1114 points[2], points[3],
1115 points[4], points[5]) * matrix;
1116 } else if (state == Translate) {
1117 if (points.size() == 1)
1118 matrix.translate(dx: points[0], dy: 0);
1119 else if (points.size() == 2)
1120 matrix.translate(dx: points[0], dy: points[1]);
1121 else
1122 goto error;
1123 } else if (state == Rotate) {
1124 if(points.size() == 1) {
1125 matrix.rotate(a: points[0]);
1126 } else if (points.size() == 3) {
1127 matrix.translate(dx: points[1], dy: points[2]);
1128 matrix.rotate(a: points[0]);
1129 matrix.translate(dx: -points[1], dy: -points[2]);
1130 } else {
1131 goto error;
1132 }
1133 } else if (state == Scale) {
1134 if (points.size() < 1 || points.size() > 2)
1135 goto error;
1136 qreal sx = points[0];
1137 qreal sy = sx;
1138 if(points.size() == 2)
1139 sy = points[1];
1140 matrix.scale(sx, sy);
1141 } else if (state == SkewX) {
1142 if (points.size() != 1)
1143 goto error;
1144 matrix.shear(sh: qTan(v: qDegreesToRadians(degrees: points[0])), sv: 0);
1145 } else if (state == SkewY) {
1146 if (points.size() != 1)
1147 goto error;
1148 matrix.shear(sh: 0, sv: qTan(v: qDegreesToRadians(degrees: points[0])));
1149 }
1150 }
1151 error:
1152 return matrix;
1153}
1154
1155static void parsePen(QSvgNode *node,
1156 const QSvgAttributes &attributes,
1157 QSvgHandler *handler)
1158{
1159 //qDebug()<<"Node "<<node->type()<<", attrs are "<<value<<width;
1160
1161 if (!attributes.stroke.isEmpty() || !attributes.strokeDashArray.isEmpty() || !attributes.strokeDashOffset.isEmpty() || !attributes.strokeLineCap.isEmpty()
1162 || !attributes.strokeLineJoin.isEmpty() || !attributes.strokeMiterLimit.isEmpty() || !attributes.strokeOpacity.isEmpty() || !attributes.strokeWidth.isEmpty()
1163 || !attributes.vectorEffect.isEmpty()) {
1164
1165 QSvgStrokeStyle *prop = new QSvgStrokeStyle;
1166
1167 //stroke attribute handling
1168 if ((!attributes.stroke.isEmpty()) && (attributes.stroke != QT_INHERIT) ) {
1169 if (attributes.stroke.size() > 3 && attributes.stroke.mid(pos: 0, n: 3) == QLatin1String("url")) {
1170 QString value = attributes.stroke.mid(pos: 3, n: attributes.stroke.size() - 3).toString();
1171 QSvgStyleProperty *style = styleFromUrl(node, url: value);
1172 if (style) {
1173 if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT)
1174 prop->setStyle(reinterpret_cast<QSvgFillStyleProperty *>(style));
1175 } else {
1176 QString id = idFromUrl(url: value);
1177 prop->setGradientId(id);
1178 prop->setGradientResolved(false);
1179 }
1180 } else if (attributes.stroke != QLatin1String("none")) {
1181 QColor color;
1182 if (resolveColor(colorStr: attributes.stroke, color, handler))
1183 prop->setStroke(QBrush(color));
1184 } else {
1185 prop->setStroke(QBrush(Qt::NoBrush));
1186 }
1187 }
1188
1189 //stroke-width handling
1190 if (!attributes.strokeWidth.isEmpty() && attributes.strokeWidth != QT_INHERIT) {
1191 QSvgHandler::LengthType lt;
1192 prop->setWidth(parseLength(str: attributes.strokeWidth, type&: lt, handler));
1193 }
1194
1195 //stroke-dasharray
1196 if (!attributes.strokeDashArray.isEmpty() && attributes.strokeDashArray != QT_INHERIT) {
1197 if (attributes.strokeDashArray == QLatin1String("none")) {
1198 prop->setDashArrayNone();
1199 } else {
1200 QString dashArray = attributes.strokeDashArray.toString();
1201 const QChar *s = dashArray.constData();
1202 QList<qreal> dashes = parseNumbersList(str&: s);
1203 // if the dash count is odd the dashes should be duplicated
1204 if ((dashes.size() & 1) != 0)
1205 dashes << QList<qreal>(dashes);
1206 prop->setDashArray(dashes);
1207 }
1208 }
1209
1210 //stroke-linejoin attribute handling
1211 if (!attributes.strokeLineJoin.isEmpty()) {
1212 if (attributes.strokeLineJoin == QLatin1String("miter"))
1213 prop->setLineJoin(Qt::SvgMiterJoin);
1214 else if (attributes.strokeLineJoin == QLatin1String("round"))
1215 prop->setLineJoin(Qt::RoundJoin);
1216 else if (attributes.strokeLineJoin == QLatin1String("bevel"))
1217 prop->setLineJoin(Qt::BevelJoin);
1218 }
1219
1220 //stroke-linecap attribute handling
1221 if (!attributes.strokeLineCap.isEmpty()) {
1222 if (attributes.strokeLineCap == QLatin1String("butt"))
1223 prop->setLineCap(Qt::FlatCap);
1224 else if (attributes.strokeLineCap == QLatin1String("round"))
1225 prop->setLineCap(Qt::RoundCap);
1226 else if (attributes.strokeLineCap == QLatin1String("square"))
1227 prop->setLineCap(Qt::SquareCap);
1228 }
1229
1230 //stroke-dashoffset attribute handling
1231 if (!attributes.strokeDashOffset.isEmpty() && attributes.strokeDashOffset != QT_INHERIT)
1232 prop->setDashOffset(toDouble(str: attributes.strokeDashOffset));
1233
1234 //vector-effect attribute handling
1235 if (!attributes.vectorEffect.isEmpty()) {
1236 if (attributes.vectorEffect == QLatin1String("non-scaling-stroke"))
1237 prop->setVectorEffect(true);
1238 else if (attributes.vectorEffect == QLatin1String("none"))
1239 prop->setVectorEffect(false);
1240 }
1241
1242 //stroke-miterlimit
1243 if (!attributes.strokeMiterLimit.isEmpty() && attributes.strokeMiterLimit != QT_INHERIT)
1244 prop->setMiterLimit(toDouble(str: attributes.strokeMiterLimit));
1245
1246 //stroke-opacity atttribute handling
1247 if (!attributes.strokeOpacity.isEmpty() && attributes.strokeOpacity != QT_INHERIT)
1248 prop->setOpacity(qMin(a: qreal(1.0), b: qMax(a: qreal(0.0), b: toDouble(str: attributes.strokeOpacity))));
1249
1250 node->appendStyleProperty(prop, id: attributes.id);
1251 }
1252}
1253
1254enum FontSizeSpec { XXSmall, XSmall, Small, Medium, Large, XLarge, XXLarge,
1255 FontSizeNone, FontSizeValue };
1256
1257static const qreal sizeTable[] =
1258{ qreal(6.9), qreal(8.3), qreal(10.0), qreal(12.0), qreal(14.4), qreal(17.3), qreal(20.7) };
1259
1260Q_STATIC_ASSERT(sizeof(sizeTable)/sizeof(sizeTable[0]) == FontSizeNone);
1261
1262static FontSizeSpec fontSizeSpec(QStringView spec)
1263{
1264 switch (spec.at(n: 0).unicode()) {
1265 case 'x':
1266 if (spec == QLatin1String("xx-small"))
1267 return XXSmall;
1268 if (spec == QLatin1String("x-small"))
1269 return XSmall;
1270 if (spec == QLatin1String("x-large"))
1271 return XLarge;
1272 if (spec == QLatin1String("xx-large"))
1273 return XXLarge;
1274 break;
1275 case 's':
1276 if (spec == QLatin1String("small"))
1277 return Small;
1278 break;
1279 case 'm':
1280 if (spec == QLatin1String("medium"))
1281 return Medium;
1282 break;
1283 case 'l':
1284 if (spec == QLatin1String("large"))
1285 return Large;
1286 break;
1287 case 'n':
1288 if (spec == QLatin1String("none"))
1289 return FontSizeNone;
1290 break;
1291 default:
1292 break;
1293 }
1294 return FontSizeValue;
1295}
1296
1297static void parseFont(QSvgNode *node,
1298 const QSvgAttributes &attributes,
1299 QSvgHandler *handler)
1300{
1301 if (attributes.fontFamily.isEmpty() && attributes.fontSize.isEmpty() && attributes.fontStyle.isEmpty() &&
1302 attributes.fontWeight.isEmpty() && attributes.fontVariant.isEmpty() && attributes.textAnchor.isEmpty())
1303 return;
1304
1305 QSvgTinyDocument *doc = node->document();
1306 QSvgFontStyle *fontStyle = nullptr;
1307 if (!attributes.fontFamily.isEmpty()) {
1308 QSvgFont *svgFont = doc->svgFont(family: attributes.fontFamily.toString());
1309 if (svgFont)
1310 fontStyle = new QSvgFontStyle(svgFont, doc);
1311 }
1312 if (!fontStyle)
1313 fontStyle = new QSvgFontStyle;
1314 if (!attributes.fontFamily.isEmpty() && attributes.fontFamily != QT_INHERIT) {
1315 QString family = attributes.fontFamily.toString().trimmed();
1316 if (family.at(i: 0) == QLatin1Char('\'') || family.at(i: 0) == QLatin1Char('\"'))
1317 family = family.mid(position: 1, n: family.size() - 2);
1318 fontStyle->setFamily(family);
1319 }
1320
1321 if (!attributes.fontSize.isEmpty() && attributes.fontSize != QT_INHERIT) {
1322 // TODO: Support relative sizes 'larger' and 'smaller'.
1323 const FontSizeSpec spec = fontSizeSpec(spec: attributes.fontSize);
1324 switch (spec) {
1325 case FontSizeNone:
1326 break;
1327 case FontSizeValue: {
1328 QSvgHandler::LengthType type;
1329 qreal fs = parseLength(str: attributes.fontSize, type, handler);
1330 fs = convertToPixels(len: fs, true, type);
1331 fontStyle->setSize(qMin(a: fs, b: qreal(0xffff)));
1332 }
1333 break;
1334 default:
1335 fontStyle->setSize(sizeTable[spec]);
1336 break;
1337 }
1338 }
1339
1340 if (!attributes.fontStyle.isEmpty() && attributes.fontStyle != QT_INHERIT) {
1341 if (attributes.fontStyle == QLatin1String("normal")) {
1342 fontStyle->setStyle(QFont::StyleNormal);
1343 } else if (attributes.fontStyle == QLatin1String("italic")) {
1344 fontStyle->setStyle(QFont::StyleItalic);
1345 } else if (attributes.fontStyle == QLatin1String("oblique")) {
1346 fontStyle->setStyle(QFont::StyleOblique);
1347 }
1348 }
1349
1350 if (!attributes.fontWeight.isEmpty() && attributes.fontWeight != QT_INHERIT) {
1351 bool ok = false;
1352 const int weightNum = attributes.fontWeight.toInt(ok: &ok);
1353 if (ok) {
1354 fontStyle->setWeight(weightNum);
1355 } else {
1356 if (attributes.fontWeight == QLatin1String("normal")) {
1357 fontStyle->setWeight(QFont::Normal);
1358 } else if (attributes.fontWeight == QLatin1String("bold")) {
1359 fontStyle->setWeight(QFont::Bold);
1360 } else if (attributes.fontWeight == QLatin1String("bolder")) {
1361 fontStyle->setWeight(QSvgFontStyle::BOLDER);
1362 } else if (attributes.fontWeight == QLatin1String("lighter")) {
1363 fontStyle->setWeight(QSvgFontStyle::LIGHTER);
1364 }
1365 }
1366 }
1367
1368 if (!attributes.fontVariant.isEmpty() && attributes.fontVariant != QT_INHERIT) {
1369 if (attributes.fontVariant == QLatin1String("normal"))
1370 fontStyle->setVariant(QFont::MixedCase);
1371 else if (attributes.fontVariant == QLatin1String("small-caps"))
1372 fontStyle->setVariant(QFont::SmallCaps);
1373 }
1374
1375 if (!attributes.textAnchor.isEmpty() && attributes.textAnchor != QT_INHERIT) {
1376 if (attributes.textAnchor == QLatin1String("start"))
1377 fontStyle->setTextAnchor(Qt::AlignLeft);
1378 if (attributes.textAnchor == QLatin1String("middle"))
1379 fontStyle->setTextAnchor(Qt::AlignHCenter);
1380 else if (attributes.textAnchor == QLatin1String("end"))
1381 fontStyle->setTextAnchor(Qt::AlignRight);
1382 }
1383
1384 node->appendStyleProperty(prop: fontStyle, id: attributes.id);
1385}
1386
1387static void parseTransform(QSvgNode *node,
1388 const QSvgAttributes &attributes,
1389 QSvgHandler *)
1390{
1391 if (attributes.transform.isEmpty())
1392 return;
1393 QTransform matrix = parseTransformationMatrix(value: attributes.transform.trimmed());
1394
1395 if (!matrix.isIdentity()) {
1396 node->appendStyleProperty(prop: new QSvgTransformStyle(QTransform(matrix)), id: attributes.id);
1397 }
1398
1399}
1400
1401static void parseVisibility(QSvgNode *node,
1402 const QSvgAttributes &attributes,
1403 QSvgHandler *)
1404{
1405 QSvgNode *parent = node->parent();
1406
1407 if (parent && (attributes.visibility.isEmpty() || attributes.visibility == QT_INHERIT))
1408 node->setVisible(parent->isVisible());
1409 else if (attributes.visibility == QLatin1String("hidden") || attributes.visibility == QLatin1String("collapse")) {
1410 node->setVisible(false);
1411 } else
1412 node->setVisible(true);
1413}
1414
1415static void pathArcSegment(QPainterPath &path,
1416 qreal xc, qreal yc,
1417 qreal th0, qreal th1,
1418 qreal rx, qreal ry, qreal xAxisRotation)
1419{
1420 qreal sinTh, cosTh;
1421 qreal a00, a01, a10, a11;
1422 qreal x1, y1, x2, y2, x3, y3;
1423 qreal t;
1424 qreal thHalf;
1425
1426 sinTh = qSin(v: xAxisRotation * (Q_PI / 180.0));
1427 cosTh = qCos(v: xAxisRotation * (Q_PI / 180.0));
1428
1429 a00 = cosTh * rx;
1430 a01 = -sinTh * ry;
1431 a10 = sinTh * rx;
1432 a11 = cosTh * ry;
1433
1434 thHalf = 0.5 * (th1 - th0);
1435 t = (8.0 / 3.0) * qSin(v: thHalf * 0.5) * qSin(v: thHalf * 0.5) / qSin(v: thHalf);
1436 x1 = xc + qCos(v: th0) - t * qSin(v: th0);
1437 y1 = yc + qSin(v: th0) + t * qCos(v: th0);
1438 x3 = xc + qCos(v: th1);
1439 y3 = yc + qSin(v: th1);
1440 x2 = x3 + t * qSin(v: th1);
1441 y2 = y3 - t * qCos(v: th1);
1442
1443 path.cubicTo(ctrlPt1x: a00 * x1 + a01 * y1, ctrlPt1y: a10 * x1 + a11 * y1,
1444 ctrlPt2x: a00 * x2 + a01 * y2, ctrlPt2y: a10 * x2 + a11 * y2,
1445 endPtx: a00 * x3 + a01 * y3, endPty: a10 * x3 + a11 * y3);
1446}
1447
1448// the arc handling code underneath is from XSVG (BSD license)
1449/*
1450 * Copyright 2002 USC/Information Sciences Institute
1451 *
1452 * Permission to use, copy, modify, distribute, and sell this software
1453 * and its documentation for any purpose is hereby granted without
1454 * fee, provided that the above copyright notice appear in all copies
1455 * and that both that copyright notice and this permission notice
1456 * appear in supporting documentation, and that the name of
1457 * Information Sciences Institute not be used in advertising or
1458 * publicity pertaining to distribution of the software without
1459 * specific, written prior permission. Information Sciences Institute
1460 * makes no representations about the suitability of this software for
1461 * any purpose. It is provided "as is" without express or implied
1462 * warranty.
1463 *
1464 * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
1465 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
1466 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
1467 * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
1468 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
1469 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
1470 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1471 * PERFORMANCE OF THIS SOFTWARE.
1472 *
1473 */
1474static void pathArc(QPainterPath &path,
1475 qreal rx,
1476 qreal ry,
1477 qreal x_axis_rotation,
1478 int large_arc_flag,
1479 int sweep_flag,
1480 qreal x,
1481 qreal y,
1482 qreal curx, qreal cury)
1483{
1484 const qreal Pr1 = rx * rx;
1485 const qreal Pr2 = ry * ry;
1486
1487 if (!Pr1 || !Pr2)
1488 return;
1489
1490 qreal sin_th, cos_th;
1491 qreal a00, a01, a10, a11;
1492 qreal x0, y0, x1, y1, xc, yc;
1493 qreal d, sfactor, sfactor_sq;
1494 qreal th0, th1, th_arc;
1495 int i, n_segs;
1496 qreal dx, dy, dx1, dy1, Px, Py, check;
1497
1498 rx = qAbs(t: rx);
1499 ry = qAbs(t: ry);
1500
1501 sin_th = qSin(v: x_axis_rotation * (Q_PI / 180.0));
1502 cos_th = qCos(v: x_axis_rotation * (Q_PI / 180.0));
1503
1504 dx = (curx - x) / 2.0;
1505 dy = (cury - y) / 2.0;
1506 dx1 = cos_th * dx + sin_th * dy;
1507 dy1 = -sin_th * dx + cos_th * dy;
1508 Px = dx1 * dx1;
1509 Py = dy1 * dy1;
1510 /* Spec : check if radii are large enough */
1511 check = Px / Pr1 + Py / Pr2;
1512 if (check > 1) {
1513 rx = rx * qSqrt(v: check);
1514 ry = ry * qSqrt(v: check);
1515 }
1516
1517 a00 = cos_th / rx;
1518 a01 = sin_th / rx;
1519 a10 = -sin_th / ry;
1520 a11 = cos_th / ry;
1521 x0 = a00 * curx + a01 * cury;
1522 y0 = a10 * curx + a11 * cury;
1523 x1 = a00 * x + a01 * y;
1524 y1 = a10 * x + a11 * y;
1525 /* (x0, y0) is current point in transformed coordinate space.
1526 (x1, y1) is new point in transformed coordinate space.
1527
1528 The arc fits a unit-radius circle in this space.
1529 */
1530 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
1531 if (!d)
1532 return;
1533 sfactor_sq = 1.0 / d - 0.25;
1534 if (sfactor_sq < 0) sfactor_sq = 0;
1535 sfactor = qSqrt(v: sfactor_sq);
1536 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
1537 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
1538 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
1539 /* (xc, yc) is center of the circle. */
1540
1541 th0 = qAtan2(y: y0 - yc, x: x0 - xc);
1542 th1 = qAtan2(y: y1 - yc, x: x1 - xc);
1543
1544 th_arc = th1 - th0;
1545 if (th_arc < 0 && sweep_flag)
1546 th_arc += 2 * Q_PI;
1547 else if (th_arc > 0 && !sweep_flag)
1548 th_arc -= 2 * Q_PI;
1549
1550 n_segs = qCeil(v: qAbs(t: th_arc / (Q_PI * 0.5 + 0.001)));
1551
1552 for (i = 0; i < n_segs; i++) {
1553 pathArcSegment(path, xc, yc,
1554 th0: th0 + i * th_arc / n_segs,
1555 th1: th0 + (i + 1) * th_arc / n_segs,
1556 rx, ry, xAxisRotation: x_axis_rotation);
1557 }
1558}
1559
1560static bool parsePathDataFast(QStringView dataStr, QPainterPath &path)
1561{
1562 const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this
1563 qreal x0 = 0, y0 = 0; // starting point
1564 qreal x = 0, y = 0; // current point
1565 char lastMode = 0;
1566 QPointF ctrlPt;
1567 const QChar *str = dataStr.constData();
1568 const QChar *end = str + dataStr.size();
1569
1570 bool ok = true;
1571 while (ok && str != end) {
1572 while (str->isSpace() && (str + 1) != end)
1573 ++str;
1574 QChar pathElem = *str;
1575 ++str;
1576 QChar endc = *end;
1577 *const_cast<QChar *>(end) = u'\0'; // parseNumbersArray requires 0-termination that QStringView cannot guarantee
1578 const char *pattern = nullptr;
1579 if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A'))
1580 pattern = "rrrffrr";
1581 QVarLengthArray<qreal, 8> arg;
1582 parseNumbersArray(str, points&: arg, pattern);
1583 *const_cast<QChar *>(end) = endc;
1584 if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
1585 arg.append(t: 0);//dummy
1586 const qreal *num = arg.constData();
1587 int count = arg.size();
1588 while (ok && count > 0) {
1589 qreal offsetX = x; // correction offsets
1590 qreal offsetY = y; // for relative commands
1591 switch (pathElem.unicode()) {
1592 case 'm': {
1593 if (count < 2) {
1594 ok = false;
1595 break;
1596 }
1597 x = x0 = num[0] + offsetX;
1598 y = y0 = num[1] + offsetY;
1599 num += 2;
1600 count -= 2;
1601 path.moveTo(x: x0, y: y0);
1602
1603 // As per 1.2 spec 8.3.2 The "moveto" commands
1604 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
1605 // the subsequent pairs shall be treated as implicit 'lineto' commands.
1606 pathElem = QLatin1Char('l');
1607 }
1608 break;
1609 case 'M': {
1610 if (count < 2) {
1611 ok = false;
1612 break;
1613 }
1614 x = x0 = num[0];
1615 y = y0 = num[1];
1616 num += 2;
1617 count -= 2;
1618 path.moveTo(x: x0, y: y0);
1619
1620 // As per 1.2 spec 8.3.2 The "moveto" commands
1621 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
1622 // the subsequent pairs shall be treated as implicit 'lineto' commands.
1623 pathElem = QLatin1Char('L');
1624 }
1625 break;
1626 case 'z':
1627 case 'Z': {
1628 x = x0;
1629 y = y0;
1630 count--; // skip dummy
1631 num++;
1632 path.closeSubpath();
1633 }
1634 break;
1635 case 'l': {
1636 if (count < 2) {
1637 ok = false;
1638 break;
1639 }
1640 x = num[0] + offsetX;
1641 y = num[1] + offsetY;
1642 num += 2;
1643 count -= 2;
1644 path.lineTo(x, y);
1645
1646 }
1647 break;
1648 case 'L': {
1649 if (count < 2) {
1650 ok = false;
1651 break;
1652 }
1653 x = num[0];
1654 y = num[1];
1655 num += 2;
1656 count -= 2;
1657 path.lineTo(x, y);
1658 }
1659 break;
1660 case 'h': {
1661 x = num[0] + offsetX;
1662 num++;
1663 count--;
1664 path.lineTo(x, y);
1665 }
1666 break;
1667 case 'H': {
1668 x = num[0];
1669 num++;
1670 count--;
1671 path.lineTo(x, y);
1672 }
1673 break;
1674 case 'v': {
1675 y = num[0] + offsetY;
1676 num++;
1677 count--;
1678 path.lineTo(x, y);
1679 }
1680 break;
1681 case 'V': {
1682 y = num[0];
1683 num++;
1684 count--;
1685 path.lineTo(x, y);
1686 }
1687 break;
1688 case 'c': {
1689 if (count < 6) {
1690 ok = false;
1691 break;
1692 }
1693 QPointF c1(num[0] + offsetX, num[1] + offsetY);
1694 QPointF c2(num[2] + offsetX, num[3] + offsetY);
1695 QPointF e(num[4] + offsetX, num[5] + offsetY);
1696 num += 6;
1697 count -= 6;
1698 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1699 ctrlPt = c2;
1700 x = e.x();
1701 y = e.y();
1702 break;
1703 }
1704 case 'C': {
1705 if (count < 6) {
1706 ok = false;
1707 break;
1708 }
1709 QPointF c1(num[0], num[1]);
1710 QPointF c2(num[2], num[3]);
1711 QPointF e(num[4], num[5]);
1712 num += 6;
1713 count -= 6;
1714 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1715 ctrlPt = c2;
1716 x = e.x();
1717 y = e.y();
1718 break;
1719 }
1720 case 's': {
1721 if (count < 4) {
1722 ok = false;
1723 break;
1724 }
1725 QPointF c1;
1726 if (lastMode == 'c' || lastMode == 'C' ||
1727 lastMode == 's' || lastMode == 'S')
1728 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1729 else
1730 c1 = QPointF(x, y);
1731 QPointF c2(num[0] + offsetX, num[1] + offsetY);
1732 QPointF e(num[2] + offsetX, num[3] + offsetY);
1733 num += 4;
1734 count -= 4;
1735 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1736 ctrlPt = c2;
1737 x = e.x();
1738 y = e.y();
1739 break;
1740 }
1741 case 'S': {
1742 if (count < 4) {
1743 ok = false;
1744 break;
1745 }
1746 QPointF c1;
1747 if (lastMode == 'c' || lastMode == 'C' ||
1748 lastMode == 's' || lastMode == 'S')
1749 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1750 else
1751 c1 = QPointF(x, y);
1752 QPointF c2(num[0], num[1]);
1753 QPointF e(num[2], num[3]);
1754 num += 4;
1755 count -= 4;
1756 path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: e);
1757 ctrlPt = c2;
1758 x = e.x();
1759 y = e.y();
1760 break;
1761 }
1762 case 'q': {
1763 if (count < 4) {
1764 ok = false;
1765 break;
1766 }
1767 QPointF c(num[0] + offsetX, num[1] + offsetY);
1768 QPointF e(num[2] + offsetX, num[3] + offsetY);
1769 num += 4;
1770 count -= 4;
1771 path.quadTo(ctrlPt: c, endPt: e);
1772 ctrlPt = c;
1773 x = e.x();
1774 y = e.y();
1775 break;
1776 }
1777 case 'Q': {
1778 if (count < 4) {
1779 ok = false;
1780 break;
1781 }
1782 QPointF c(num[0], num[1]);
1783 QPointF e(num[2], num[3]);
1784 num += 4;
1785 count -= 4;
1786 path.quadTo(ctrlPt: c, endPt: e);
1787 ctrlPt = c;
1788 x = e.x();
1789 y = e.y();
1790 break;
1791 }
1792 case 't': {
1793 if (count < 2) {
1794 ok = false;
1795 break;
1796 }
1797 QPointF e(num[0] + offsetX, num[1] + offsetY);
1798 num += 2;
1799 count -= 2;
1800 QPointF c;
1801 if (lastMode == 'q' || lastMode == 'Q' ||
1802 lastMode == 't' || lastMode == 'T')
1803 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1804 else
1805 c = QPointF(x, y);
1806 path.quadTo(ctrlPt: c, endPt: e);
1807 ctrlPt = c;
1808 x = e.x();
1809 y = e.y();
1810 break;
1811 }
1812 case 'T': {
1813 if (count < 2) {
1814 ok = false;
1815 break;
1816 }
1817 QPointF e(num[0], num[1]);
1818 num += 2;
1819 count -= 2;
1820 QPointF c;
1821 if (lastMode == 'q' || lastMode == 'Q' ||
1822 lastMode == 't' || lastMode == 'T')
1823 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1824 else
1825 c = QPointF(x, y);
1826 path.quadTo(ctrlPt: c, endPt: e);
1827 ctrlPt = c;
1828 x = e.x();
1829 y = e.y();
1830 break;
1831 }
1832 case 'a': {
1833 if (count < 7) {
1834 ok = false;
1835 break;
1836 }
1837 qreal rx = (*num++);
1838 qreal ry = (*num++);
1839 qreal xAxisRotation = (*num++);
1840 qreal largeArcFlag = (*num++);
1841 qreal sweepFlag = (*num++);
1842 qreal ex = (*num++) + offsetX;
1843 qreal ey = (*num++) + offsetY;
1844 count -= 7;
1845 qreal curx = x;
1846 qreal cury = y;
1847 pathArc(path, rx, ry, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag),
1848 sweep_flag: int(sweepFlag), x: ex, y: ey, curx, cury);
1849
1850 x = ex;
1851 y = ey;
1852 }
1853 break;
1854 case 'A': {
1855 if (count < 7) {
1856 ok = false;
1857 break;
1858 }
1859 qreal rx = (*num++);
1860 qreal ry = (*num++);
1861 qreal xAxisRotation = (*num++);
1862 qreal largeArcFlag = (*num++);
1863 qreal sweepFlag = (*num++);
1864 qreal ex = (*num++);
1865 qreal ey = (*num++);
1866 count -= 7;
1867 qreal curx = x;
1868 qreal cury = y;
1869 pathArc(path, rx, ry, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag),
1870 sweep_flag: int(sweepFlag), x: ex, y: ey, curx, cury);
1871
1872 x = ex;
1873 y = ey;
1874 }
1875 break;
1876 default:
1877 ok = false;
1878 break;
1879 }
1880 lastMode = pathElem.toLatin1();
1881 if (path.elementCount() > maxElementCount)
1882 ok = false;
1883 }
1884 }
1885 return ok;
1886}
1887
1888static bool parseStyle(QSvgNode *node,
1889 const QXmlStreamAttributes &attributes,
1890 QSvgHandler *);
1891
1892static bool parseStyle(QSvgNode *node,
1893 const QSvgAttributes &attributes,
1894 QSvgHandler *);
1895
1896#ifndef QT_NO_CSSPARSER
1897
1898static void parseCSStoXMLAttrs(const QList<QCss::Declaration> &declarations,
1899 QXmlStreamAttributes &attributes)
1900{
1901 for (int i = 0; i < declarations.size(); ++i) {
1902 const QCss::Declaration &decl = declarations.at(i);
1903 if (decl.d->property.isEmpty())
1904 continue;
1905 QCss::Value val = decl.d->values.first();
1906 QString valueStr;
1907 const int valCount = decl.d->values.size();
1908 if (valCount != 1) {
1909 for (int i = 0; i < valCount; ++i) {
1910 valueStr += decl.d->values[i].toString();
1911 if (i + 1 < valCount)
1912 valueStr += QLatin1Char(',');
1913 }
1914 } else {
1915 valueStr = val.toString();
1916 }
1917 if (val.type == QCss::Value::Uri) {
1918 valueStr.prepend(s: QLatin1String("url("));
1919 valueStr.append(c: QLatin1Char(')'));
1920 } else if (val.type == QCss::Value::Function) {
1921 QStringList lst = val.variant.toStringList();
1922 valueStr.append(s: lst.at(i: 0));
1923 valueStr.append(c: QLatin1Char('('));
1924 for (int i = 1; i < lst.size(); ++i) {
1925 valueStr.append(s: lst.at(i));
1926 if ((i +1) < lst.size())
1927 valueStr.append(c: QLatin1Char(','));
1928 }
1929 valueStr.append(c: QLatin1Char(')'));
1930 } else if (val.type == QCss::Value::KnownIdentifier) {
1931 switch (val.variant.toInt()) {
1932 case QCss::Value_None:
1933 valueStr = QLatin1String("none");
1934 break;
1935 default:
1936 break;
1937 }
1938 }
1939
1940 attributes.append(namespaceUri: QString(), name: decl.d->property, value: valueStr);
1941 }
1942}
1943
1944void QSvgHandler::parseCSStoXMLAttrs(const QString &css, QList<QSvgCssAttribute> *attributes)
1945{
1946 // preprocess (for unicode escapes), tokenize and remove comments
1947 m_cssParser.init(css);
1948 QString key;
1949
1950 attributes->reserve(asize: 10);
1951
1952 while (m_cssParser.hasNext()) {
1953 m_cssParser.skipSpace();
1954
1955 if (!m_cssParser.hasNext())
1956 break;
1957 m_cssParser.next();
1958
1959 QString name;
1960 if (m_cssParser.hasEscapeSequences) {
1961 key = m_cssParser.lexem();
1962 name = key;
1963 } else {
1964 const QCss::Symbol &sym = m_cssParser.symbol();
1965 name = sym.text.mid(position: sym.start, n: sym.len);
1966 }
1967
1968 m_cssParser.skipSpace();
1969 if (!m_cssParser.test(t: QCss::COLON))
1970 break;
1971
1972 m_cssParser.skipSpace();
1973 if (!m_cssParser.hasNext())
1974 break;
1975
1976 QSvgCssAttribute attribute;
1977 attribute.name = name;
1978
1979 const int firstSymbol = m_cssParser.index;
1980 int symbolCount = 0;
1981 do {
1982 m_cssParser.next();
1983 ++symbolCount;
1984 } while (m_cssParser.hasNext() && !m_cssParser.test(t: QCss::SEMICOLON));
1985
1986 bool canExtractValueByRef = !m_cssParser.hasEscapeSequences;
1987 if (canExtractValueByRef) {
1988 int len = m_cssParser.symbols.at(i: firstSymbol).len;
1989 for (int i = firstSymbol + 1; i < firstSymbol + symbolCount; ++i) {
1990 len += m_cssParser.symbols.at(i).len;
1991
1992 if (m_cssParser.symbols.at(i: i - 1).start + m_cssParser.symbols.at(i: i - 1).len
1993 != m_cssParser.symbols.at(i).start) {
1994 canExtractValueByRef = false;
1995 break;
1996 }
1997 }
1998 if (canExtractValueByRef) {
1999 const QCss::Symbol &sym = m_cssParser.symbols.at(i: firstSymbol);
2000 attribute.value = sym.text.mid(position: sym.start, n: len);
2001 }
2002 }
2003 if (!canExtractValueByRef) {
2004 QString value;
2005 for (int i = firstSymbol; i < m_cssParser.index - 1; ++i)
2006 value += m_cssParser.symbols.at(i).lexem();
2007 attribute.value = value;
2008 }
2009
2010 attributes->append(t: attribute);
2011
2012 m_cssParser.skipSpace();
2013 }
2014}
2015
2016static void cssStyleLookup(QSvgNode *node,
2017 QSvgHandler *handler,
2018 QSvgStyleSelector *selector,
2019 QXmlStreamAttributes &attributes)
2020{
2021 QCss::StyleSelector::NodePtr cssNode;
2022 cssNode.ptr = node;
2023 QList<QCss::Declaration> decls = selector->declarationsForNode(node: cssNode);
2024
2025 parseCSStoXMLAttrs(declarations: decls, attributes);
2026 parseStyle(node, attributes, handler);
2027}
2028
2029static void cssStyleLookup(QSvgNode *node,
2030 QSvgHandler *handler,
2031 QSvgStyleSelector *selector)
2032{
2033 QXmlStreamAttributes attributes;
2034 cssStyleLookup(node, handler, selector, attributes);
2035}
2036
2037#endif // QT_NO_CSSPARSER
2038
2039static inline QStringList stringToList(const QString &str)
2040{
2041 QStringList lst = str.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
2042 return lst;
2043}
2044
2045static bool parseCoreNode(QSvgNode *node,
2046 const QXmlStreamAttributes &attributes)
2047{
2048 QStringList features;
2049 QStringList extensions;
2050 QStringList languages;
2051 QStringList formats;
2052 QStringList fonts;
2053 QString xmlClassStr;
2054
2055 for (int i = 0; i < attributes.size(); ++i) {
2056 const QXmlStreamAttribute &attribute = attributes.at(i);
2057 QStringView name = attribute.qualifiedName();
2058 if (name.isEmpty())
2059 continue;
2060 QStringView value = attribute.value();
2061 switch (name.at(n: 0).unicode()) {
2062 case 'c':
2063 if (name == QLatin1String("class"))
2064 xmlClassStr = value.toString();
2065 break;
2066 case 'r':
2067 if (name == QLatin1String("requiredFeatures"))
2068 features = stringToList(str: value.toString());
2069 else if (name == QLatin1String("requiredExtensions"))
2070 extensions = stringToList(str: value.toString());
2071 else if (name == QLatin1String("requiredFormats"))
2072 formats = stringToList(str: value.toString());
2073 else if (name == QLatin1String("requiredFonts"))
2074 fonts = stringToList(str: value.toString());
2075 break;
2076 case 's':
2077 if (name == QLatin1String("systemLanguage"))
2078 languages = stringToList(str: value.toString());
2079 break;
2080 default:
2081 break;
2082 }
2083 }
2084
2085 node->setRequiredFeatures(features);
2086 node->setRequiredExtensions(extensions);
2087 node->setRequiredLanguages(languages);
2088 node->setRequiredFormats(formats);
2089 node->setRequiredFonts(fonts);
2090 node->setNodeId(someId(attributes));
2091 node->setXmlClass(xmlClassStr);
2092
2093 return true;
2094}
2095
2096static void parseOpacity(QSvgNode *node,
2097 const QSvgAttributes &attributes,
2098 QSvgHandler *)
2099{
2100 if (attributes.opacity.isEmpty())
2101 return;
2102
2103 const QStringView value = attributes.opacity.trimmed();
2104
2105 bool ok = false;
2106 qreal op = value.toDouble(ok: &ok);
2107
2108 if (ok) {
2109 QSvgOpacityStyle *opacity = new QSvgOpacityStyle(qBound(min: qreal(0.0), val: op, max: qreal(1.0)));
2110 node->appendStyleProperty(prop: opacity, id: attributes.id);
2111 }
2112}
2113
2114static QPainter::CompositionMode svgToQtCompositionMode(const QString &op)
2115{
2116#define NOOP qDebug()<<"Operation: "<<op<<" is not implemented"
2117 if (op == QLatin1String("clear")) {
2118 return QPainter::CompositionMode_Clear;
2119 } else if (op == QLatin1String("src")) {
2120 return QPainter::CompositionMode_Source;
2121 } else if (op == QLatin1String("dst")) {
2122 return QPainter::CompositionMode_Destination;
2123 } else if (op == QLatin1String("src-over")) {
2124 return QPainter::CompositionMode_SourceOver;
2125 } else if (op == QLatin1String("dst-over")) {
2126 return QPainter::CompositionMode_DestinationOver;
2127 } else if (op == QLatin1String("src-in")) {
2128 return QPainter::CompositionMode_SourceIn;
2129 } else if (op == QLatin1String("dst-in")) {
2130 return QPainter::CompositionMode_DestinationIn;
2131 } else if (op == QLatin1String("src-out")) {
2132 return QPainter::CompositionMode_SourceOut;
2133 } else if (op == QLatin1String("dst-out")) {
2134 return QPainter::CompositionMode_DestinationOut;
2135 } else if (op == QLatin1String("src-atop")) {
2136 return QPainter::CompositionMode_SourceAtop;
2137 } else if (op == QLatin1String("dst-atop")) {
2138 return QPainter::CompositionMode_DestinationAtop;
2139 } else if (op == QLatin1String("xor")) {
2140 return QPainter::CompositionMode_Xor;
2141 } else if (op == QLatin1String("plus")) {
2142 return QPainter::CompositionMode_Plus;
2143 } else if (op == QLatin1String("multiply")) {
2144 return QPainter::CompositionMode_Multiply;
2145 } else if (op == QLatin1String("screen")) {
2146 return QPainter::CompositionMode_Screen;
2147 } else if (op == QLatin1String("overlay")) {
2148 return QPainter::CompositionMode_Overlay;
2149 } else if (op == QLatin1String("darken")) {
2150 return QPainter::CompositionMode_Darken;
2151 } else if (op == QLatin1String("lighten")) {
2152 return QPainter::CompositionMode_Lighten;
2153 } else if (op == QLatin1String("color-dodge")) {
2154 return QPainter::CompositionMode_ColorDodge;
2155 } else if (op == QLatin1String("color-burn")) {
2156 return QPainter::CompositionMode_ColorBurn;
2157 } else if (op == QLatin1String("hard-light")) {
2158 return QPainter::CompositionMode_HardLight;
2159 } else if (op == QLatin1String("soft-light")) {
2160 return QPainter::CompositionMode_SoftLight;
2161 } else if (op == QLatin1String("difference")) {
2162 return QPainter::CompositionMode_Difference;
2163 } else if (op == QLatin1String("exclusion")) {
2164 return QPainter::CompositionMode_Exclusion;
2165 } else {
2166 NOOP;
2167 }
2168
2169 return QPainter::CompositionMode_SourceOver;
2170}
2171
2172static void parseCompOp(QSvgNode *node,
2173 const QSvgAttributes &attributes,
2174 QSvgHandler *)
2175{
2176 if (attributes.compOp.isEmpty())
2177 return;
2178 QString value = attributes.compOp.toString().trimmed();
2179
2180 if (!value.isEmpty()) {
2181 QSvgCompOpStyle *compop = new QSvgCompOpStyle(svgToQtCompositionMode(op: value));
2182 node->appendStyleProperty(prop: compop, id: attributes.id);
2183 }
2184}
2185
2186static inline QSvgNode::DisplayMode displayStringToEnum(const QString &str)
2187{
2188 if (str == QLatin1String("inline")) {
2189 return QSvgNode::InlineMode;
2190 } else if (str == QLatin1String("block")) {
2191 return QSvgNode::BlockMode;
2192 } else if (str == QLatin1String("list-item")) {
2193 return QSvgNode::ListItemMode;
2194 } else if (str == QLatin1String("run-in")) {
2195 return QSvgNode::RunInMode;
2196 } else if (str == QLatin1String("compact")) {
2197 return QSvgNode::CompactMode;
2198 } else if (str == QLatin1String("marker")) {
2199 return QSvgNode::MarkerMode;
2200 } else if (str == QLatin1String("table")) {
2201 return QSvgNode::TableMode;
2202 } else if (str == QLatin1String("inline-table")) {
2203 return QSvgNode::InlineTableMode;
2204 } else if (str == QLatin1String("table-row-group")) {
2205 return QSvgNode::TableRowGroupMode;
2206 } else if (str == QLatin1String("table-header-group")) {
2207 return QSvgNode::TableHeaderGroupMode;
2208 } else if (str == QLatin1String("table-footer-group")) {
2209 return QSvgNode::TableFooterGroupMode;
2210 } else if (str == QLatin1String("table-row")) {
2211 return QSvgNode::TableRowMode;
2212 } else if (str == QLatin1String("table-column-group")) {
2213 return QSvgNode::TableColumnGroupMode;
2214 } else if (str == QLatin1String("table-column")) {
2215 return QSvgNode::TableColumnMode;
2216 } else if (str == QLatin1String("table-cell")) {
2217 return QSvgNode::TableCellMode;
2218 } else if (str == QLatin1String("table-caption")) {
2219 return QSvgNode::TableCaptionMode;
2220 } else if (str == QLatin1String("none")) {
2221 return QSvgNode::NoneMode;
2222 } else if (str == QT_INHERIT) {
2223 return QSvgNode::InheritMode;
2224 }
2225 return QSvgNode::BlockMode;
2226}
2227
2228static void parseOthers(QSvgNode *node,
2229 const QSvgAttributes &attributes,
2230 QSvgHandler *)
2231{
2232 if (attributes.display.isEmpty())
2233 return;
2234 QString displayStr = attributes.display.toString().trimmed();
2235
2236 if (!displayStr.isEmpty()) {
2237 node->setDisplayMode(displayStringToEnum(str: displayStr));
2238 }
2239}
2240
2241static void parseRenderingHints(QSvgNode *node,
2242 const QSvgAttributes &attributes,
2243 QSvgHandler *)
2244{
2245 if (attributes.imageRendering.isEmpty())
2246 return;
2247
2248 QString ir = attributes.imageRendering.toString().trimmed();
2249 QSvgQualityStyle *p = new QSvgQualityStyle(0);
2250 if (ir == QLatin1String("auto"))
2251 p->setImageRendering(QSvgQualityStyle::ImageRenderingAuto);
2252 else if (ir == QLatin1String("optimizeSpeed"))
2253 p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeSpeed);
2254 else if (ir == QLatin1String("optimizeQuality"))
2255 p->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeQuality);
2256 node->appendStyleProperty(prop: p, id: attributes.id);
2257}
2258
2259
2260static bool parseStyle(QSvgNode *node,
2261 const QSvgAttributes &attributes,
2262 QSvgHandler *handler)
2263{
2264 parseColor(node, attributes, handler);
2265 parseBrush(node, attributes, handler);
2266 parsePen(node, attributes, handler);
2267 parseFont(node, attributes, handler);
2268 parseTransform(node, attributes, handler);
2269 parseVisibility(node, attributes, handler);
2270 parseOpacity(node, attributes, handler);
2271 parseCompOp(node, attributes, handler);
2272 parseRenderingHints(node, attributes, handler);
2273 parseOthers(node, attributes, handler);
2274
2275#if 0
2276 value = attributes.value("audio-level");
2277
2278 value = attributes.value("color-rendering");
2279
2280 value = attributes.value("display-align");
2281
2282 value = attributes.value("image-rendering");
2283
2284 value = attributes.value("line-increment");
2285
2286 value = attributes.value("pointer-events");
2287
2288 value = attributes.value("shape-rendering");
2289
2290 value = attributes.value("solid-color");
2291
2292 value = attributes.value("solid-opacity");
2293
2294 value = attributes.value("text-rendering");
2295
2296 value = attributes.value("vector-effect");
2297
2298 value = attributes.value("viewport-fill");
2299
2300 value = attributes.value("viewport-fill-opacity");
2301#endif
2302 return true;
2303}
2304
2305static bool parseStyle(QSvgNode *node,
2306 const QXmlStreamAttributes &attrs,
2307 QSvgHandler *handler)
2308{
2309 return parseStyle(node, attributes: QSvgAttributes(attrs, handler), handler);
2310}
2311
2312static bool parseAnchorNode(QSvgNode *parent,
2313 const QXmlStreamAttributes &attributes,
2314 QSvgHandler *)
2315{
2316 Q_UNUSED(parent); Q_UNUSED(attributes);
2317 return true;
2318}
2319
2320static bool parseAnimateNode(QSvgNode *parent,
2321 const QXmlStreamAttributes &attributes,
2322 QSvgHandler *)
2323{
2324 Q_UNUSED(parent); Q_UNUSED(attributes);
2325 return true;
2326}
2327
2328static int parseClockValue(QStringView str, bool *ok)
2329{
2330 int res = 0;
2331 int ms = 1000;
2332 str = str.trimmed();
2333 if (str.endsWith(s: QLatin1String("ms"))) {
2334 str.chop(n: 2);
2335 ms = 1;
2336 } else if (str.endsWith(s: QLatin1String("s"))) {
2337 str.chop(n: 1);
2338 }
2339 double val = ms * toDouble(str, ok);
2340 if (ok) {
2341 if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
2342 res = static_cast<int>(val);
2343 else
2344 *ok = false;
2345 }
2346 return res;
2347}
2348
2349static bool parseAnimateColorNode(QSvgNode *parent,
2350 const QXmlStreamAttributes &attributes,
2351 QSvgHandler *handler)
2352{
2353 QStringView fromStr = attributes.value(qualifiedName: QLatin1String("from"));
2354 QStringView toStr = attributes.value(qualifiedName: QLatin1String("to"));
2355 QString valuesStr = attributes.value(qualifiedName: QLatin1String("values")).toString();
2356 QString beginStr = attributes.value(qualifiedName: QLatin1String("begin")).toString();
2357 QString durStr = attributes.value(qualifiedName: QLatin1String("dur")).toString();
2358 QString targetStr = attributes.value(qualifiedName: QLatin1String("attributeName")).toString();
2359 QString repeatStr = attributes.value(qualifiedName: QLatin1String("repeatCount")).toString();
2360 QString fillStr = attributes.value(qualifiedName: QLatin1String("fill")).toString();
2361
2362 QList<QColor> colors;
2363 if (valuesStr.isEmpty()) {
2364 QColor startColor, endColor;
2365 resolveColor(colorStr: fromStr, color&: startColor, handler);
2366 resolveColor(colorStr: toStr, color&: endColor, handler);
2367 colors.reserve(asize: 2);
2368 colors.append(t: startColor);
2369 colors.append(t: endColor);
2370 } else {
2371 QStringList str = valuesStr.split(sep: QLatin1Char(';'));
2372 colors.reserve(asize: str.size());
2373 QStringList::const_iterator itr;
2374 for (itr = str.constBegin(); itr != str.constEnd(); ++itr) {
2375 QColor color;
2376 resolveColor(colorStr: *itr, color, handler);
2377 colors.append(t: color);
2378 }
2379 }
2380
2381 bool ok = true;
2382 int begin = parseClockValue(str: beginStr, ok: &ok);
2383 if (!ok)
2384 return false;
2385 int end = begin + parseClockValue(str: durStr, ok: &ok);
2386 if (!ok || end <= begin)
2387 return false;
2388
2389 QSvgAnimateColor *anim = new QSvgAnimateColor(begin, end, 0);
2390 anim->setArgs(fill: (targetStr == QLatin1String("fill")), colors);
2391 anim->setFreeze(fillStr == QLatin1String("freeze"));
2392 anim->setRepeatCount(
2393 (repeatStr == QLatin1String("indefinite")) ? -1 :
2394 (repeatStr == QLatin1String("")) ? 1 : toDouble(str: repeatStr));
2395
2396 parent->appendStyleProperty(prop: anim, id: someId(attributes));
2397 parent->document()->setAnimated(true);
2398 handler->setAnimPeriod(start: begin, end);
2399 return true;
2400}
2401
2402static bool parseAimateMotionNode(QSvgNode *parent,
2403 const QXmlStreamAttributes &attributes,
2404 QSvgHandler *)
2405{
2406 Q_UNUSED(parent); Q_UNUSED(attributes);
2407 return true;
2408}
2409
2410static void parseNumberTriplet(QList<qreal> &values, const QChar *&s)
2411{
2412 QList<qreal> list = parseNumbersList(str&: s);
2413 values << list;
2414 for (int i = 3 - list.size(); i > 0; --i)
2415 values.append(t: 0.0);
2416}
2417
2418static bool parseAnimateTransformNode(QSvgNode *parent,
2419 const QXmlStreamAttributes &attributes,
2420 QSvgHandler *handler)
2421{
2422 QString typeStr = attributes.value(qualifiedName: QLatin1String("type")).toString();
2423 QString values = attributes.value(qualifiedName: QLatin1String("values")).toString();
2424 QString beginStr = attributes.value(qualifiedName: QLatin1String("begin")).toString();
2425 QString durStr = attributes.value(qualifiedName: QLatin1String("dur")).toString();
2426 QString repeatStr = attributes.value(qualifiedName: QLatin1String("repeatCount")).toString();
2427 QString fillStr = attributes.value(qualifiedName: QLatin1String("fill")).toString();
2428 QString fromStr = attributes.value(qualifiedName: QLatin1String("from")).toString();
2429 QString toStr = attributes.value(qualifiedName: QLatin1String("to")).toString();
2430 QString byStr = attributes.value(qualifiedName: QLatin1String("by")).toString();
2431 QString addtv = attributes.value(qualifiedName: QLatin1String("additive")).toString();
2432
2433 QSvgAnimateTransform::Additive additive = QSvgAnimateTransform::Replace;
2434 if (addtv == QLatin1String("sum"))
2435 additive = QSvgAnimateTransform::Sum;
2436
2437 QList<qreal> vals;
2438 if (values.isEmpty()) {
2439 const QChar *s;
2440 if (fromStr.isEmpty()) {
2441 if (!byStr.isEmpty()) {
2442 // By-animation.
2443 additive = QSvgAnimateTransform::Sum;
2444 vals.append(t: 0.0);
2445 vals.append(t: 0.0);
2446 vals.append(t: 0.0);
2447 parseNumberTriplet(values&: vals, s&: s = byStr.constData());
2448 } else {
2449 // To-animation not defined.
2450 return false;
2451 }
2452 } else {
2453 if (!toStr.isEmpty()) {
2454 // From-to-animation.
2455 parseNumberTriplet(values&: vals, s&: s = fromStr.constData());
2456 parseNumberTriplet(values&: vals, s&: s = toStr.constData());
2457 } else if (!byStr.isEmpty()) {
2458 // From-by-animation.
2459 parseNumberTriplet(values&: vals, s&: s = fromStr.constData());
2460 parseNumberTriplet(values&: vals, s&: s = byStr.constData());
2461 for (int i = vals.size() - 3; i < vals.size(); ++i)
2462 vals[i] += vals[i - 3];
2463 } else {
2464 return false;
2465 }
2466 }
2467 } else {
2468 const QChar *s = values.constData();
2469 while (s && *s != QLatin1Char(0)) {
2470 parseNumberTriplet(values&: vals, s);
2471 if (*s == QLatin1Char(0))
2472 break;
2473 ++s;
2474 }
2475 }
2476 if (vals.size() % 3 != 0)
2477 return false;
2478
2479 bool ok = true;
2480 int begin = parseClockValue(str: beginStr, ok: &ok);
2481 if (!ok)
2482 return false;
2483 int end = begin + parseClockValue(str: durStr, ok: &ok);
2484 if (!ok || end <= begin)
2485 return false;
2486
2487 QSvgAnimateTransform::TransformType type = QSvgAnimateTransform::Empty;
2488 if (typeStr == QLatin1String("translate")) {
2489 type = QSvgAnimateTransform::Translate;
2490 } else if (typeStr == QLatin1String("scale")) {
2491 type = QSvgAnimateTransform::Scale;
2492 } else if (typeStr == QLatin1String("rotate")) {
2493 type = QSvgAnimateTransform::Rotate;
2494 } else if (typeStr == QLatin1String("skewX")) {
2495 type = QSvgAnimateTransform::SkewX;
2496 } else if (typeStr == QLatin1String("skewY")) {
2497 type = QSvgAnimateTransform::SkewY;
2498 } else {
2499 return false;
2500 }
2501
2502 QSvgAnimateTransform *anim = new QSvgAnimateTransform(begin, end, 0);
2503 anim->setArgs(type, additive, args: vals);
2504 anim->setFreeze(fillStr == QLatin1String("freeze"));
2505 anim->setRepeatCount(
2506 (repeatStr == QLatin1String("indefinite"))? -1 :
2507 (repeatStr == QLatin1String(""))? 1 : toDouble(str: repeatStr));
2508
2509 parent->appendStyleProperty(prop: anim, id: someId(attributes));
2510 parent->document()->setAnimated(true);
2511 handler->setAnimPeriod(start: begin, end);
2512 return true;
2513}
2514
2515static QSvgNode * createAnimationNode(QSvgNode *parent,
2516 const QXmlStreamAttributes &attributes,
2517 QSvgHandler *)
2518{
2519 Q_UNUSED(parent); Q_UNUSED(attributes);
2520 return 0;
2521}
2522
2523static bool parseAudioNode(QSvgNode *parent,
2524 const QXmlStreamAttributes &attributes,
2525 QSvgHandler *)
2526{
2527 Q_UNUSED(parent); Q_UNUSED(attributes);
2528 return true;
2529}
2530
2531static QSvgNode *createCircleNode(QSvgNode *parent,
2532 const QXmlStreamAttributes &attributes,
2533 QSvgHandler *)
2534{
2535 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
2536 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
2537 const QStringView r = attributes.value(qualifiedName: QLatin1String("r"));
2538 qreal ncx = toDouble(str: cx);
2539 qreal ncy = toDouble(str: cy);
2540 qreal nr = toDouble(str: r);
2541 if (nr < 0.0)
2542 return nullptr;
2543
2544 QRectF rect(ncx-nr, ncy-nr, nr*2, nr*2);
2545 QSvgNode *circle = new QSvgCircle(parent, rect);
2546 return circle;
2547}
2548
2549static QSvgNode *createDefsNode(QSvgNode *parent,
2550 const QXmlStreamAttributes &attributes,
2551 QSvgHandler *)
2552{
2553 Q_UNUSED(attributes);
2554 QSvgDefs *defs = new QSvgDefs(parent);
2555 return defs;
2556}
2557
2558static bool parseDescNode(QSvgNode *parent,
2559 const QXmlStreamAttributes &attributes,
2560 QSvgHandler *)
2561{
2562 Q_UNUSED(parent); Q_UNUSED(attributes);
2563 return true;
2564}
2565
2566static bool parseDiscardNode(QSvgNode *parent,
2567 const QXmlStreamAttributes &attributes,
2568 QSvgHandler *)
2569{
2570 Q_UNUSED(parent); Q_UNUSED(attributes);
2571 return true;
2572}
2573
2574static QSvgNode *createEllipseNode(QSvgNode *parent,
2575 const QXmlStreamAttributes &attributes,
2576 QSvgHandler *)
2577{
2578 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
2579 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
2580 const QStringView rx = attributes.value(qualifiedName: QLatin1String("rx"));
2581 const QStringView ry = attributes.value(qualifiedName: QLatin1String("ry"));
2582 qreal ncx = toDouble(str: cx);
2583 qreal ncy = toDouble(str: cy);
2584 qreal nrx = toDouble(str: rx);
2585 qreal nry = toDouble(str: ry);
2586
2587 QRectF rect(ncx-nrx, ncy-nry, nrx*2, nry*2);
2588 QSvgNode *ellipse = new QSvgEllipse(parent, rect);
2589 return ellipse;
2590}
2591
2592static QSvgStyleProperty *createFontNode(QSvgNode *parent,
2593 const QXmlStreamAttributes &attributes,
2594 QSvgHandler *)
2595{
2596 const QStringView hax = attributes.value(qualifiedName: QLatin1String("horiz-adv-x"));
2597 QString myId = someId(attributes);
2598
2599 qreal horizAdvX = toDouble(str: hax);
2600
2601 while (parent && parent->type() != QSvgNode::DOC) {
2602 parent = parent->parent();
2603 }
2604
2605 if (parent && !myId.isEmpty()) {
2606 QSvgTinyDocument *doc = static_cast<QSvgTinyDocument*>(parent);
2607 QSvgFont *font = doc->svgFont(family: myId);
2608 if (!font) {
2609 font = new QSvgFont(horizAdvX);
2610 font->setFamilyName(myId);
2611 doc->addSvgFont(font);
2612 }
2613 return new QSvgFontStyle(font, doc);
2614 }
2615 return nullptr;
2616}
2617
2618static bool parseFontFaceNode(QSvgStyleProperty *parent,
2619 const QXmlStreamAttributes &attributes,
2620 QSvgHandler *)
2621{
2622 if (parent->type() != QSvgStyleProperty::FONT) {
2623 return false;
2624 }
2625
2626 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2627 QSvgFont *font = style->svgFont();
2628 QString name = attributes.value(qualifiedName: QLatin1String("font-family")).toString();
2629 const QStringView unitsPerEmStr = attributes.value(qualifiedName: QLatin1String("units-per-em"));
2630
2631 qreal unitsPerEm = toDouble(str: unitsPerEmStr);
2632 if (!unitsPerEm)
2633 unitsPerEm = QSvgFont::DEFAULT_UNITS_PER_EM;
2634
2635 if (!name.isEmpty())
2636 font->setFamilyName(name);
2637 font->setUnitsPerEm(unitsPerEm);
2638
2639 if (!font->familyName().isEmpty())
2640 if (!style->doc()->svgFont(family: font->familyName()))
2641 style->doc()->addSvgFont(font);
2642
2643 return true;
2644}
2645
2646static bool parseFontFaceNameNode(QSvgStyleProperty *parent,
2647 const QXmlStreamAttributes &attributes,
2648 QSvgHandler *)
2649{
2650 if (parent->type() != QSvgStyleProperty::FONT) {
2651 return false;
2652 }
2653
2654 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2655 QSvgFont *font = style->svgFont();
2656 QString name = attributes.value(qualifiedName: QLatin1String("name")).toString();
2657
2658 if (!name.isEmpty())
2659 font->setFamilyName(name);
2660
2661 if (!font->familyName().isEmpty())
2662 if (!style->doc()->svgFont(family: font->familyName()))
2663 style->doc()->addSvgFont(font);
2664
2665 return true;
2666}
2667
2668static bool parseFontFaceSrcNode(QSvgStyleProperty *parent,
2669 const QXmlStreamAttributes &attributes,
2670 QSvgHandler *)
2671{
2672 Q_UNUSED(parent); Q_UNUSED(attributes);
2673 return true;
2674}
2675
2676static bool parseFontFaceUriNode(QSvgStyleProperty *parent,
2677 const QXmlStreamAttributes &attributes,
2678 QSvgHandler *)
2679{
2680 Q_UNUSED(parent); Q_UNUSED(attributes);
2681 return true;
2682}
2683
2684static bool parseForeignObjectNode(QSvgNode *parent,
2685 const QXmlStreamAttributes &attributes,
2686 QSvgHandler *)
2687{
2688 Q_UNUSED(parent); Q_UNUSED(attributes);
2689 return true;
2690}
2691
2692static QSvgNode *createGNode(QSvgNode *parent,
2693 const QXmlStreamAttributes &attributes,
2694 QSvgHandler *)
2695{
2696 Q_UNUSED(attributes);
2697 QSvgG *node = new QSvgG(parent);
2698 return node;
2699}
2700
2701static bool parseGlyphNode(QSvgStyleProperty *parent,
2702 const QXmlStreamAttributes &attributes,
2703 QSvgHandler *)
2704{
2705 if (parent->type() != QSvgStyleProperty::FONT) {
2706 return false;
2707 }
2708
2709 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2710 QSvgFont *font = style->svgFont();
2711 createSvgGlyph(font, attributes);
2712 return true;
2713}
2714
2715static bool parseHandlerNode(QSvgNode *parent,
2716 const QXmlStreamAttributes &attributes,
2717 QSvgHandler *)
2718{
2719 Q_UNUSED(parent); Q_UNUSED(attributes);
2720 return true;
2721}
2722
2723static bool parseHkernNode(QSvgNode *parent,
2724 const QXmlStreamAttributes &attributes,
2725 QSvgHandler *)
2726{
2727 Q_UNUSED(parent); Q_UNUSED(attributes);
2728 return true;
2729}
2730
2731static QSvgNode *createImageNode(QSvgNode *parent,
2732 const QXmlStreamAttributes &attributes,
2733 QSvgHandler *handler)
2734{
2735 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
2736 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
2737 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
2738 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
2739 QString filename = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString();
2740 qreal nx = toDouble(str: x);
2741 qreal ny = toDouble(str: y);
2742 QSvgHandler::LengthType type;
2743 qreal nwidth = parseLength(str: width.toString(), type, handler);
2744 nwidth = convertToPixels(len: nwidth, true, type);
2745
2746 qreal nheight = parseLength(str: height.toString(), type, handler);
2747 nheight = convertToPixels(len: nheight, false, type);
2748
2749 filename = filename.trimmed();
2750 if (filename.isEmpty()) {
2751 qCWarning(lcSvgHandler) << "QSvgHandler: Image filename is empty";
2752 return 0;
2753 }
2754 if (nwidth <= 0 || nheight <= 0) {
2755 qCWarning(lcSvgHandler) << "QSvgHandler: Width or height for" << filename << "image was not greater than 0";
2756 return 0;
2757 }
2758
2759 QImage image;
2760 if (filename.startsWith(s: QLatin1String("data"))) {
2761 int idx = filename.lastIndexOf(s: QLatin1String("base64,"));
2762 if (idx != -1) {
2763 idx += 7;
2764 const QString dataStr = filename.mid(position: idx);
2765 QByteArray data = QByteArray::fromBase64(base64: dataStr.toLatin1());
2766 image = QImage::fromData(data);
2767 } else {
2768 qCDebug(lcSvgHandler) << "QSvgHandler::createImageNode: Unrecognized inline image format!";
2769 }
2770 } else {
2771 const auto *file = qobject_cast<QFile *>(object: handler->device());
2772 if (file) {
2773 QUrl url(filename);
2774 if (url.isRelative()) {
2775 QFileInfo info(file->fileName());
2776 filename = info.absoluteDir().absoluteFilePath(fileName: filename);
2777 }
2778 }
2779 image = QImage(filename);
2780 }
2781
2782 if (image.isNull()) {
2783 qCWarning(lcSvgHandler) << "Could not create image from" << filename;
2784 return 0;
2785 }
2786
2787 if (image.format() == QImage::Format_ARGB32)
2788 image = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
2789
2790 QSvgNode *img = new QSvgImage(parent,
2791 image,
2792 QRectF(nx,
2793 ny,
2794 nwidth,
2795 nheight));
2796 return img;
2797}
2798
2799static QSvgNode *createLineNode(QSvgNode *parent,
2800 const QXmlStreamAttributes &attributes,
2801 QSvgHandler *)
2802{
2803 const QStringView x1 = attributes.value(qualifiedName: QLatin1String("x1"));
2804 const QStringView y1 = attributes.value(qualifiedName: QLatin1String("y1"));
2805 const QStringView x2 = attributes.value(qualifiedName: QLatin1String("x2"));
2806 const QStringView y2 = attributes.value(qualifiedName: QLatin1String("y2"));
2807 qreal nx1 = toDouble(str: x1);
2808 qreal ny1 = toDouble(str: y1);
2809 qreal nx2 = toDouble(str: x2);
2810 qreal ny2 = toDouble(str: y2);
2811
2812 QLineF lineBounds(nx1, ny1, nx2, ny2);
2813 QSvgNode *line = new QSvgLine(parent, lineBounds);
2814 return line;
2815}
2816
2817
2818static void parseBaseGradient(QSvgNode *node,
2819 const QXmlStreamAttributes &attributes,
2820 QSvgGradientStyle *gradProp,
2821 QSvgHandler *handler)
2822{
2823 QString link = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString();
2824 QStringView trans = attributes.value(qualifiedName: QLatin1String("gradientTransform"));
2825 QString spread = attributes.value(qualifiedName: QLatin1String("spreadMethod")).toString();
2826 QString units = attributes.value(qualifiedName: QLatin1String("gradientUnits")).toString();
2827 QStringView colorStr = attributes.value(qualifiedName: QLatin1String("color"));
2828 QStringView colorOpacityStr = attributes.value(qualifiedName: QLatin1String("color-opacity"));
2829
2830 QColor color;
2831 if (constructColor(colorStr, opacity: colorOpacityStr, color, handler)) {
2832 handler->popColor();
2833 handler->pushColor(color);
2834 }
2835
2836 QTransform matrix;
2837 QGradient *grad = gradProp->qgradient();
2838 if (node && !link.isEmpty()) {
2839 QSvgStyleProperty *prop = node->styleProperty(id: link);
2840 //qDebug()<<"inherited "<<prop<<" ("<<link<<")";
2841 if (prop && prop->type() == QSvgStyleProperty::GRADIENT) {
2842 QSvgGradientStyle *inherited =
2843 static_cast<QSvgGradientStyle*>(prop);
2844 if (!inherited->stopLink().isEmpty()) {
2845 gradProp->setStopLink(link: inherited->stopLink(), doc: handler->document());
2846 } else {
2847 grad->setStops(inherited->qgradient()->stops());
2848 gradProp->setGradientStopsSet(inherited->gradientStopsSet());
2849 }
2850
2851 matrix = inherited->qtransform();
2852 } else {
2853 gradProp->setStopLink(link, doc: handler->document());
2854 }
2855 }
2856
2857 if (!trans.isEmpty()) {
2858 matrix = parseTransformationMatrix(value: trans);
2859 gradProp->setTransform(matrix);
2860 } else if (!matrix.isIdentity()) {
2861 gradProp->setTransform(matrix);
2862 }
2863
2864 if (!spread.isEmpty()) {
2865 if (spread == QLatin1String("pad")) {
2866 grad->setSpread(QGradient::PadSpread);
2867 } else if (spread == QLatin1String("reflect")) {
2868 grad->setSpread(QGradient::ReflectSpread);
2869 } else if (spread == QLatin1String("repeat")) {
2870 grad->setSpread(QGradient::RepeatSpread);
2871 }
2872 }
2873
2874 if (units.isEmpty() || units == QLatin1String("objectBoundingBox")) {
2875 grad->setCoordinateMode(QGradient::ObjectMode);
2876 }
2877}
2878
2879static QSvgStyleProperty *createLinearGradientNode(QSvgNode *node,
2880 const QXmlStreamAttributes &attributes,
2881 QSvgHandler *handler)
2882{
2883 const QStringView x1 = attributes.value(qualifiedName: QLatin1String("x1"));
2884 const QStringView y1 = attributes.value(qualifiedName: QLatin1String("y1"));
2885 const QStringView x2 = attributes.value(qualifiedName: QLatin1String("x2"));
2886 const QStringView y2 = attributes.value(qualifiedName: QLatin1String("y2"));
2887
2888 qreal nx1 = 0.0;
2889 qreal ny1 = 0.0;
2890 qreal nx2 = 1.0;
2891 qreal ny2 = 0.0;
2892
2893 if (!x1.isEmpty())
2894 nx1 = convertToNumber(str: x1, handler);
2895 if (!y1.isEmpty())
2896 ny1 = convertToNumber(str: y1, handler);
2897 if (!x2.isEmpty())
2898 nx2 = convertToNumber(str: x2, handler);
2899 if (!y2.isEmpty())
2900 ny2 = convertToNumber(str: y2, handler);
2901
2902 QSvgNode *itr = node;
2903 while (itr && itr->type() != QSvgNode::DOC) {
2904 itr = itr->parent();
2905 }
2906
2907 QLinearGradient *grad = new QLinearGradient(nx1, ny1, nx2, ny2);
2908 grad->setInterpolationMode(QGradient::ComponentInterpolation);
2909 QSvgGradientStyle *prop = new QSvgGradientStyle(grad);
2910 parseBaseGradient(node, attributes, gradProp: prop, handler);
2911
2912 return prop;
2913}
2914
2915static bool parseMetadataNode(QSvgNode *parent,
2916 const QXmlStreamAttributes &attributes,
2917 QSvgHandler *)
2918{
2919 Q_UNUSED(parent); Q_UNUSED(attributes);
2920 return true;
2921}
2922
2923static bool parseMissingGlyphNode(QSvgStyleProperty *parent,
2924 const QXmlStreamAttributes &attributes,
2925 QSvgHandler *)
2926{
2927 if (parent->type() != QSvgStyleProperty::FONT) {
2928 return false;
2929 }
2930
2931 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2932 QSvgFont *font = style->svgFont();
2933 createSvgGlyph(font, attributes);
2934 return true;
2935}
2936
2937static bool parseMpathNode(QSvgNode *parent,
2938 const QXmlStreamAttributes &attributes,
2939 QSvgHandler *)
2940{
2941 Q_UNUSED(parent); Q_UNUSED(attributes);
2942 return true;
2943}
2944
2945static QSvgNode *createPathNode(QSvgNode *parent,
2946 const QXmlStreamAttributes &attributes,
2947 QSvgHandler *)
2948{
2949 QStringView data = attributes.value(qualifiedName: QLatin1String("d"));
2950
2951 QPainterPath qpath;
2952 qpath.setFillRule(Qt::WindingFill);
2953 if (!parsePathDataFast(dataStr: data, path&: qpath))
2954 qCWarning(lcSvgHandler, "Invalid path data; path truncated.");
2955
2956 QSvgNode *path = new QSvgPath(parent, qpath);
2957 return path;
2958}
2959
2960static QSvgNode *createPolygonNode(QSvgNode *parent,
2961 const QXmlStreamAttributes &attributes,
2962 QSvgHandler *)
2963{
2964 QString pointsStr = attributes.value(qualifiedName: QLatin1String("points")).toString();
2965
2966 //same QPolygon parsing is in createPolylineNode
2967 const QChar *s = pointsStr.constData();
2968 QList<qreal> points = parseNumbersList(str&: s);
2969 QPolygonF poly(points.size()/2);
2970 for (int i = 0; i < poly.size(); ++i)
2971 poly[i] = QPointF(points.at(i: 2 * i), points.at(i: 2 * i + 1));
2972 QSvgNode *polygon = new QSvgPolygon(parent, poly);
2973 return polygon;
2974}
2975
2976static QSvgNode *createPolylineNode(QSvgNode *parent,
2977 const QXmlStreamAttributes &attributes,
2978 QSvgHandler *)
2979{
2980 QString pointsStr = attributes.value(qualifiedName: QLatin1String("points")).toString();
2981
2982 //same QPolygon parsing is in createPolygonNode
2983 const QChar *s = pointsStr.constData();
2984 QList<qreal> points = parseNumbersList(str&: s);
2985 QPolygonF poly(points.size()/2);
2986 for (int i = 0; i < poly.size(); ++i)
2987 poly[i] = QPointF(points.at(i: 2 * i), points.at(i: 2 * i + 1));
2988
2989 QSvgNode *line = new QSvgPolyline(parent, poly);
2990 return line;
2991}
2992
2993static bool parsePrefetchNode(QSvgNode *parent,
2994 const QXmlStreamAttributes &attributes,
2995 QSvgHandler *)
2996{
2997 Q_UNUSED(parent); Q_UNUSED(attributes);
2998 return true;
2999}
3000
3001static QSvgStyleProperty *createRadialGradientNode(QSvgNode *node,
3002 const QXmlStreamAttributes &attributes,
3003 QSvgHandler *handler)
3004{
3005 const QStringView cx = attributes.value(qualifiedName: QLatin1String("cx"));
3006 const QStringView cy = attributes.value(qualifiedName: QLatin1String("cy"));
3007 const QStringView r = attributes.value(qualifiedName: QLatin1String("r"));
3008 const QStringView fx = attributes.value(qualifiedName: QLatin1String("fx"));
3009 const QStringView fy = attributes.value(qualifiedName: QLatin1String("fy"));
3010
3011 qreal ncx = 0.5;
3012 qreal ncy = 0.5;
3013 if (!cx.isEmpty())
3014 ncx = toDouble(str: cx);
3015 if (!cy.isEmpty())
3016 ncy = toDouble(str: cy);
3017
3018 qreal nr = 0.0;
3019 if (!r.isEmpty())
3020 nr = toDouble(str: r);
3021 if (nr <= 0.0)
3022 return nullptr;
3023
3024 qreal nfx = ncx;
3025 if (!fx.isEmpty())
3026 nfx = toDouble(str: fx);
3027 qreal nfy = ncy;
3028 if (!fy.isEmpty())
3029 nfy = toDouble(str: fy);
3030
3031 QRadialGradient *grad = new QRadialGradient(ncx, ncy, nr, nfx, nfy, 0);
3032 grad->setInterpolationMode(QGradient::ComponentInterpolation);
3033
3034 QSvgGradientStyle *prop = new QSvgGradientStyle(grad);
3035 parseBaseGradient(node, attributes, gradProp: prop, handler);
3036
3037 return prop;
3038}
3039
3040static QSvgNode *createRectNode(QSvgNode *parent,
3041 const QXmlStreamAttributes &attributes,
3042 QSvgHandler *handler)
3043{
3044 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
3045 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
3046 const QStringView width = attributes.value(qualifiedName: QLatin1String("width"));
3047 const QStringView height = attributes.value(qualifiedName: QLatin1String("height"));
3048 const QStringView rx = attributes.value(qualifiedName: QLatin1String("rx"));
3049 const QStringView ry = attributes.value(qualifiedName: QLatin1String("ry"));
3050
3051 bool ok = true;
3052 QSvgHandler::LengthType type;
3053 qreal nwidth = parseLength(str: width.toString(), type, handler, ok: &ok);
3054 if (!ok)
3055 return nullptr;
3056 nwidth = convertToPixels(len: nwidth, true, type);
3057 qreal nheight = parseLength(str: height.toString(), type, handler, ok: &ok);
3058 if (!ok)
3059 return nullptr;
3060 nheight = convertToPixels(len: nheight, true, type);
3061 qreal nrx = toDouble(str: rx);
3062 qreal nry = toDouble(str: ry);
3063
3064 QRectF bounds(toDouble(str: x), toDouble(str: y), nwidth, nheight);
3065 if (bounds.isEmpty())
3066 return nullptr;
3067
3068 if (!rx.isEmpty() && ry.isEmpty())
3069 nry = nrx;
3070 else if (!ry.isEmpty() && rx.isEmpty())
3071 nrx = nry;
3072
3073 //9.2 The 'rect' element clearly specifies it
3074 // but the case might in fact be handled because
3075 // we draw rounded rectangles differently
3076 if (nrx > bounds.width()/2)
3077 nrx = bounds.width()/2;
3078 if (nry > bounds.height()/2)
3079 nry = bounds.height()/2;
3080
3081 //we draw rounded rect from 0...99
3082 //svg from 0...bounds.width()/2 so we're adjusting the
3083 //coordinates
3084 nrx *= (100/(bounds.width()/2));
3085 nry *= (100/(bounds.height()/2));
3086
3087 QSvgNode *rect = new QSvgRect(parent, bounds,
3088 int(nrx),
3089 int(nry));
3090 return rect;
3091}
3092
3093static bool parseScriptNode(QSvgNode *parent,
3094 const QXmlStreamAttributes &attributes,
3095 QSvgHandler *)
3096{
3097 Q_UNUSED(parent); Q_UNUSED(attributes);
3098 return true;
3099}
3100
3101static bool parseSetNode(QSvgNode *parent,
3102 const QXmlStreamAttributes &attributes,
3103 QSvgHandler *)
3104{
3105 Q_UNUSED(parent); Q_UNUSED(attributes);
3106 return true;
3107}
3108
3109static QSvgStyleProperty *createSolidColorNode(QSvgNode *parent,
3110 const QXmlStreamAttributes &attributes,
3111 QSvgHandler *handler)
3112{
3113 Q_UNUSED(parent); Q_UNUSED(attributes);
3114 QStringView solidColorStr = attributes.value(qualifiedName: QLatin1String("solid-color"));
3115 QStringView solidOpacityStr = attributes.value(qualifiedName: QLatin1String("solid-opacity"));
3116
3117 if (solidOpacityStr.isEmpty())
3118 solidOpacityStr = attributes.value(qualifiedName: QLatin1String("opacity"));
3119
3120 QColor color;
3121 if (!constructColor(colorStr: solidColorStr, opacity: solidOpacityStr, color, handler))
3122 return 0;
3123 QSvgSolidColorStyle *style = new QSvgSolidColorStyle(color);
3124 return style;
3125}
3126
3127static bool parseStopNode(QSvgStyleProperty *parent,
3128 const QXmlStreamAttributes &attributes,
3129 QSvgHandler *handler)
3130{
3131 if (parent->type() != QSvgStyleProperty::GRADIENT)
3132 return false;
3133 QString nodeIdStr = someId(attributes);
3134 QString xmlClassStr = attributes.value(qualifiedName: QLatin1String("class")).toString();
3135
3136 //### nasty hack because stop gradients are not in the rendering tree
3137 // we force a dummy node with the same id and class into a rendering
3138 // tree to figure out whether the selector has a style for it
3139 // QSvgStyleSelector should be coded in a way that could avoid it
3140 QSvgAnimation anim;
3141 anim.setNodeId(nodeIdStr);
3142 anim.setXmlClass(xmlClassStr);
3143
3144 QXmlStreamAttributes xmlAttr = attributes;
3145
3146#ifndef QT_NO_CSSPARSER
3147 cssStyleLookup(node: &anim, handler, selector: handler->selector(), attributes&: xmlAttr);
3148#endif
3149 parseStyle(node: &anim, attrs: xmlAttr, handler);
3150
3151 QSvgAttributes attrs(xmlAttr, handler);
3152
3153 QSvgGradientStyle *style =
3154 static_cast<QSvgGradientStyle*>(parent);
3155 QStringView colorStr = attrs.stopColor;
3156 QColor color;
3157
3158 bool ok = true;
3159 qreal offset = convertToNumber(str: attrs.offset, handler, ok: &ok);
3160 if (!ok)
3161 offset = 0.0;
3162 QString black = QString::fromLatin1(ba: "#000000");
3163 if (colorStr.isEmpty()) {
3164 colorStr = black;
3165 }
3166
3167 constructColor(colorStr, opacity: attrs.stopOpacity, color, handler);
3168
3169 QGradient *grad = style->qgradient();
3170
3171 offset = qMin(a: qreal(1), b: qMax(a: qreal(0), b: offset)); // Clamp to range [0, 1]
3172 QGradientStops stops;
3173 if (style->gradientStopsSet()) {
3174 stops = grad->stops();
3175 // If the stop offset equals the one previously added, add an epsilon to make it greater.
3176 if (offset <= stops.back().first)
3177 offset = stops.back().first + FLT_EPSILON;
3178 }
3179
3180 // If offset is greater than one, it must be clamped to one.
3181 if (offset > 1.0) {
3182 if ((stops.size() == 1) || (stops.at(i: stops.size() - 2).first < 1.0 - FLT_EPSILON)) {
3183 stops.back().first = 1.0 - FLT_EPSILON;
3184 grad->setStops(stops);
3185 }
3186 offset = 1.0;
3187 }
3188
3189 grad->setColorAt(pos: offset, color);
3190 style->setGradientStopsSet(true);
3191 return true;
3192}
3193
3194static bool parseStyleNode(QSvgNode *parent,
3195 const QXmlStreamAttributes &attributes,
3196 QSvgHandler *handler)
3197{
3198 Q_UNUSED(parent);
3199#ifdef QT_NO_CSSPARSER
3200 Q_UNUSED(attributes);
3201 Q_UNUSED(handler);
3202#else
3203 const QStringView type = attributes.value(qualifiedName: QLatin1String("type"));
3204 if (type.compare(s: QLatin1String("text/css"), cs: Qt::CaseInsensitive) == 0 || type.isNull())
3205 handler->setInStyle(true);
3206#endif
3207
3208 return true;
3209}
3210
3211static QSvgNode *createSvgNode(QSvgNode *parent,
3212 const QXmlStreamAttributes &attributes,
3213 QSvgHandler *handler)
3214{
3215 Q_UNUSED(parent); Q_UNUSED(attributes);
3216
3217 QSvgTinyDocument *node = new QSvgTinyDocument();
3218 const QStringView widthStr = attributes.value(qualifiedName: QLatin1String("width"));
3219 const QStringView heightStr = attributes.value(qualifiedName: QLatin1String("height"));
3220 QString viewBoxStr = attributes.value(qualifiedName: QLatin1String("viewBox")).toString();
3221
3222 QSvgHandler::LengthType type = QSvgHandler::LT_PX; // FIXME: is the default correct?
3223 qreal width = 0;
3224 if (!widthStr.isEmpty()) {
3225 width = parseLength(str: widthStr.toString(), type, handler);
3226 if (type != QSvgHandler::LT_PT)
3227 width = convertToPixels(len: width, true, type);
3228 node->setWidth(len: int(width), percent: type == QSvgHandler::LT_PERCENT);
3229 }
3230 qreal height = 0;
3231 if (!heightStr.isEmpty()) {
3232 height = parseLength(str: heightStr.toString(), type, handler);
3233 if (type != QSvgHandler::LT_PT)
3234 height = convertToPixels(len: height, false, type);
3235 node->setHeight(len: int(height), percent: type == QSvgHandler::LT_PERCENT);
3236 }
3237
3238 QStringList viewBoxValues;
3239 if (!viewBoxStr.isEmpty()) {
3240 viewBoxStr = viewBoxStr.replace(before: QLatin1Char(' '), after: QLatin1Char(','));
3241 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\r'), after: QLatin1Char(','));
3242 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\n'), after: QLatin1Char(','));
3243 viewBoxStr = viewBoxStr.replace(before: QLatin1Char('\t'), after: QLatin1Char(','));
3244 viewBoxValues = viewBoxStr.split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
3245 }
3246 if (viewBoxValues.size() == 4) {
3247 QString xStr = viewBoxValues.at(i: 0).trimmed();
3248 QString yStr = viewBoxValues.at(i: 1).trimmed();
3249 QString widthStr = viewBoxValues.at(i: 2).trimmed();
3250 QString heightStr = viewBoxValues.at(i: 3).trimmed();
3251
3252 QSvgHandler::LengthType lt;
3253 qreal x = parseLength(str: xStr, type&: lt, handler);
3254 qreal y = parseLength(str: yStr, type&: lt, handler);
3255 qreal w = parseLength(str: widthStr, type&: lt, handler);
3256 qreal h = parseLength(str: heightStr, type&: lt, handler);
3257
3258 node->setViewBox(QRectF(x, y, w, h));
3259
3260 } else if (width && height) {
3261 if (type == QSvgHandler::LT_PT) {
3262 width = convertToPixels(len: width, false, type);
3263 height = convertToPixels(len: height, false, type);
3264 }
3265 node->setViewBox(QRectF(0, 0, width, height));
3266 }
3267 handler->setDefaultCoordinateSystem(QSvgHandler::LT_PX);
3268
3269 return node;
3270}
3271
3272static QSvgNode *createSwitchNode(QSvgNode *parent,
3273 const QXmlStreamAttributes &attributes,
3274 QSvgHandler *)
3275{
3276 Q_UNUSED(attributes);
3277 QSvgSwitch *node = new QSvgSwitch(parent);
3278 return node;
3279}
3280
3281static bool parseTbreakNode(QSvgNode *parent,
3282 const QXmlStreamAttributes &,
3283 QSvgHandler *)
3284{
3285 if (parent->type() != QSvgNode::TEXTAREA)
3286 return false;
3287 static_cast<QSvgText*>(parent)->addLineBreak();
3288 return true;
3289}
3290
3291static QSvgNode *createTextNode(QSvgNode *parent,
3292 const QXmlStreamAttributes &attributes,
3293 QSvgHandler *handler)
3294{
3295 const QStringView x = attributes.value(qualifiedName: QLatin1String("x"));
3296 const QStringView y = attributes.value(qualifiedName: QLatin1String("y"));
3297 //### editable and rotate not handled
3298 QSvgHandler::LengthType type;
3299 qreal nx = parseLength(str: x.toString(), type, handler);
3300 nx = convertToPixels(len: nx, true, type);
3301 qreal ny = parseLength(str: y.toString(), type, handler);
3302 ny = convertToPixels(len: ny, true, type);
3303
3304 QSvgNode *text = new QSvgText(parent, QPointF(nx, ny));
3305 return text;
3306}
3307
3308static QSvgNode *createTextAreaNode(QSvgNode *parent,
3309 const QXmlStreamAttributes &attributes,
3310 QSvgHandler *handler)
3311{
3312 QSvgText *node = static_cast<QSvgText *>(createTextNode(parent, attributes, handler));
3313 if (node) {
3314 QSvgHandler::LengthType type;
3315 qreal width = parseLength(str: attributes.value(qualifiedName: QLatin1String("width")), type, handler);
3316 qreal height = parseLength(str: attributes.value(qualifiedName: QLatin1String("height")), type, handler);
3317 node->setTextArea(QSizeF(width, height));
3318 }
3319 return node;
3320}
3321
3322static QSvgNode *createTspanNode(QSvgNode *parent,
3323 const QXmlStreamAttributes &,
3324 QSvgHandler *)
3325{
3326 return new QSvgTspan(parent);
3327}
3328
3329static bool parseTitleNode(QSvgNode *parent,
3330 const QXmlStreamAttributes &attributes,
3331 QSvgHandler *)
3332{
3333 Q_UNUSED(parent); Q_UNUSED(attributes);
3334 return true;
3335}
3336
3337static QSvgNode *createUseNode(QSvgNode *parent,
3338 const QXmlStreamAttributes &attributes,
3339 QSvgHandler *handler)
3340{
3341 QString linkId = attributes.value(qualifiedName: QLatin1String("xlink:href")).toString().remove(i: 0, len: 1);
3342 const QStringView xStr = attributes.value(qualifiedName: QLatin1String("x"));
3343 const QStringView yStr = attributes.value(qualifiedName: QLatin1String("y"));
3344 QSvgStructureNode *group = nullptr;
3345
3346 if (linkId.isEmpty())
3347 linkId = attributes.value(qualifiedName: QLatin1String("href")).toString().remove(i: 0, len: 1);
3348 switch (parent->type()) {
3349 case QSvgNode::DOC:
3350 case QSvgNode::DEFS:
3351 case QSvgNode::G:
3352 case QSvgNode::SWITCH:
3353 group = static_cast<QSvgStructureNode*>(parent);
3354 break;
3355 default:
3356 break;
3357 }
3358
3359 if (group) {
3360 QPointF pt;
3361 if (!xStr.isNull() || !yStr.isNull()) {
3362 QSvgHandler::LengthType type;
3363 qreal nx = parseLength(str: xStr.toString(), type, handler);
3364 nx = convertToPixels(len: nx, true, type);
3365
3366 qreal ny = parseLength(str: yStr.toString(), type, handler);
3367 ny = convertToPixels(len: ny, true, type);
3368 pt = QPointF(nx, ny);
3369 }
3370
3371 QSvgNode *link = group->scopeNode(id: linkId);
3372 if (link) {
3373 if (parent->isDescendantOf(parent: link))
3374 qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(linkId));
3375
3376 return new QSvgUse(pt, parent, link);
3377 }
3378
3379 //delay link resolving, link might have not been created yet
3380 return new QSvgUse(pt, parent, linkId);
3381 }
3382
3383 qCWarning(lcSvgHandler, "<use> element %s in wrong context!", qPrintable(linkId));
3384 return 0;
3385}
3386
3387static QSvgNode *createVideoNode(QSvgNode *parent,
3388 const QXmlStreamAttributes &attributes,
3389 QSvgHandler *)
3390{
3391 Q_UNUSED(parent); Q_UNUSED(attributes);
3392 return 0;
3393}
3394
3395typedef QSvgNode *(*FactoryMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
3396
3397static FactoryMethod findGroupFactory(const QString &name)
3398{
3399 if (name.isEmpty())
3400 return 0;
3401
3402 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
3403 switch (name.at(i: 0).unicode()) {
3404 case 'd':
3405 if (ref == QLatin1String("efs")) return createDefsNode;
3406 break;
3407 case 'g':
3408 if (ref.isEmpty()) return createGNode;
3409 break;
3410 case 's':
3411 if (ref == QLatin1String("vg")) return createSvgNode;
3412 if (ref == QLatin1String("witch")) return createSwitchNode;
3413 break;
3414 default:
3415 break;
3416 }
3417 return 0;
3418}
3419
3420static FactoryMethod findGraphicsFactory(const QString &name)
3421{
3422 if (name.isEmpty())
3423 return 0;
3424
3425 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
3426 switch (name.at(i: 0).unicode()) {
3427 case 'a':
3428 if (ref == QLatin1String("nimation")) return createAnimationNode;
3429 break;
3430 case 'c':
3431 if (ref == QLatin1String("ircle")) return createCircleNode;
3432 break;
3433 case 'e':
3434 if (ref == QLatin1String("llipse")) return createEllipseNode;
3435 break;
3436 case 'i':
3437 if (ref == QLatin1String("mage")) return createImageNode;
3438 break;
3439 case 'l':
3440 if (ref == QLatin1String("ine")) return createLineNode;
3441 break;
3442 case 'p':
3443 if (ref == QLatin1String("ath")) return createPathNode;
3444 if (ref == QLatin1String("olygon")) return createPolygonNode;
3445 if (ref == QLatin1String("olyline")) return createPolylineNode;
3446 break;
3447 case 'r':
3448 if (ref == QLatin1String("ect")) return createRectNode;
3449 break;
3450 case 't':
3451 if (ref == QLatin1String("ext")) return createTextNode;
3452 if (ref == QLatin1String("extArea")) return createTextAreaNode;
3453 if (ref == QLatin1String("span")) return createTspanNode;
3454 break;
3455 case 'u':
3456 if (ref == QLatin1String("se")) return createUseNode;
3457 break;
3458 case 'v':
3459 if (ref == QLatin1String("ideo")) return createVideoNode;
3460 break;
3461 default:
3462 break;
3463 }
3464 return 0;
3465}
3466
3467typedef bool (*ParseMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
3468
3469static ParseMethod findUtilFactory(const QString &name)
3470{
3471 if (name.isEmpty())
3472 return 0;
3473
3474 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
3475 switch (name.at(i: 0).unicode()) {
3476 case 'a':
3477 if (ref.isEmpty()) return parseAnchorNode;
3478 if (ref == QLatin1String("nimate")) return parseAnimateNode;
3479 if (ref == QLatin1String("nimateColor")) return parseAnimateColorNode;
3480 if (ref == QLatin1String("nimateMotion")) return parseAimateMotionNode;
3481 if (ref == QLatin1String("nimateTransform")) return parseAnimateTransformNode;
3482 if (ref == QLatin1String("udio")) return parseAudioNode;
3483 break;
3484 case 'd':
3485 if (ref == QLatin1String("esc")) return parseDescNode;
3486 if (ref == QLatin1String("iscard")) return parseDiscardNode;
3487 break;
3488 case 'f':
3489 if (ref == QLatin1String("oreignObject")) return parseForeignObjectNode;
3490 break;
3491 case 'h':
3492 if (ref == QLatin1String("andler")) return parseHandlerNode;
3493 if (ref == QLatin1String("kern")) return parseHkernNode;
3494 break;
3495 case 'm':
3496 if (ref == QLatin1String("etadata")) return parseMetadataNode;
3497 if (ref == QLatin1String("path")) return parseMpathNode;
3498 break;
3499 case 'p':
3500 if (ref == QLatin1String("refetch")) return parsePrefetchNode;
3501 break;
3502 case 's':
3503 if (ref == QLatin1String("cript")) return parseScriptNode;
3504 if (ref == QLatin1String("et")) return parseSetNode;
3505 if (ref == QLatin1String("tyle")) return parseStyleNode;
3506 break;
3507 case 't':
3508 if (ref == QLatin1String("break")) return parseTbreakNode;
3509 if (ref == QLatin1String("itle")) return parseTitleNode;
3510 break;
3511 default:
3512 break;
3513 }
3514 return 0;
3515}
3516
3517typedef QSvgStyleProperty *(*StyleFactoryMethod)(QSvgNode *,
3518 const QXmlStreamAttributes &,
3519 QSvgHandler *);
3520
3521static StyleFactoryMethod findStyleFactoryMethod(const QString &name)
3522{
3523 if (name.isEmpty())
3524 return 0;
3525
3526 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
3527 switch (name.at(i: 0).unicode()) {
3528 case 'f':
3529 if (ref == QLatin1String("ont")) return createFontNode;
3530 break;
3531 case 'l':
3532 if (ref == QLatin1String("inearGradient")) return createLinearGradientNode;
3533 break;
3534 case 'r':
3535 if (ref == QLatin1String("adialGradient")) return createRadialGradientNode;
3536 break;
3537 case 's':
3538 if (ref == QLatin1String("olidColor")) return createSolidColorNode;
3539 break;
3540 default:
3541 break;
3542 }
3543 return 0;
3544}
3545
3546typedef bool (*StyleParseMethod)(QSvgStyleProperty *,
3547 const QXmlStreamAttributes &,
3548 QSvgHandler *);
3549
3550static StyleParseMethod findStyleUtilFactoryMethod(const QString &name)
3551{
3552 if (name.isEmpty())
3553 return 0;
3554
3555 QStringView ref = QStringView{name}.mid(pos: 1, n: name.size() - 1);
3556 switch (name.at(i: 0).unicode()) {
3557 case 'f':
3558 if (ref == QLatin1String("ont-face")) return parseFontFaceNode;
3559 if (ref == QLatin1String("ont-face-name")) return parseFontFaceNameNode;
3560 if (ref == QLatin1String("ont-face-src")) return parseFontFaceSrcNode;
3561 if (ref == QLatin1String("ont-face-uri")) return parseFontFaceUriNode;
3562 break;
3563 case 'g':
3564 if (ref == QLatin1String("lyph")) return parseGlyphNode;
3565 break;
3566 case 'm':
3567 if (ref == QLatin1String("issing-glyph")) return parseMissingGlyphNode;
3568 break;
3569 case 's':
3570 if (ref == QLatin1String("top")) return parseStopNode;
3571 break;
3572 default:
3573 break;
3574 }
3575 return 0;
3576}
3577
3578QSvgHandler::QSvgHandler(QIODevice *device) : xml(new QXmlStreamReader(device))
3579 , m_ownsReader(true)
3580{
3581 init();
3582}
3583
3584QSvgHandler::QSvgHandler(const QByteArray &data) : xml(new QXmlStreamReader(data))
3585 , m_ownsReader(true)
3586{
3587 init();
3588}
3589
3590QSvgHandler::QSvgHandler(QXmlStreamReader *const reader) : xml(reader)
3591 , m_ownsReader(false)
3592{
3593 init();
3594}
3595
3596void QSvgHandler::init()
3597{
3598 m_doc = 0;
3599 m_style = 0;
3600 m_animEnd = 0;
3601 m_defaultCoords = LT_PX;
3602 m_defaultPen = QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
3603 m_defaultPen.setMiterLimit(4);
3604 parse();
3605}
3606
3607static bool detectCycles(const QSvgNode *node, QList<const QSvgUse *> active = {})
3608{
3609 switch (node->type()) {
3610 case QSvgNode::DOC:
3611 case QSvgNode::G:
3612 {
3613 auto *g = static_cast<const QSvgStructureNode*>(node);
3614 for (auto *r : g->renderers()) {
3615 if (detectCycles(node: r, active))
3616 return true;
3617 }
3618 }
3619 break;
3620 case QSvgNode::USE:
3621 {
3622 if (active.contains(t: node))
3623 return true;
3624
3625 auto *u = static_cast<const QSvgUse*>(node);
3626 auto *target = u->link();
3627 if (target) {
3628 active.append(t: u);
3629 if (detectCycles(node: target, active))
3630 return true;
3631 }
3632 }
3633 break;
3634 default:
3635 break;
3636 }
3637 return false;
3638}
3639
3640// Having too many unfinished elements will cause a stack overflow
3641// in the dtor of QSvgTinyDocument, see oss-fuzz issue 24000.
3642static const int unfinishedElementsLimit = 2048;
3643
3644void QSvgHandler::parse()
3645{
3646 xml->setNamespaceProcessing(false);
3647#ifndef QT_NO_CSSPARSER
3648 m_selector = new QSvgStyleSelector;
3649 m_inStyle = false;
3650#endif
3651 bool done = false;
3652 int remainingUnfinishedElements = unfinishedElementsLimit;
3653 while (!xml->atEnd() && !done) {
3654 switch (xml->readNext()) {
3655 case QXmlStreamReader::StartElement:
3656 // he we could/should verify the namespaces, and simply
3657 // call m_skipNodes(Unknown) if we don't know the
3658 // namespace. We do support http://www.w3.org/2000/svg
3659 // but also http://www.w3.org/2000/svg-20000303-stylable
3660 // And if the document uses an external dtd, the reported
3661 // namespaceUri is empty. The only possible strategy at
3662 // this point is to do what everyone else seems to do and
3663 // ignore the reported namespaceUri completely.
3664 if (remainingUnfinishedElements
3665 && startElement(localName: xml->name().toString(), attributes: xml->attributes())) {
3666 --remainingUnfinishedElements;
3667 } else {
3668 delete m_doc;
3669 m_doc = nullptr;
3670 return;
3671 }
3672 break;
3673 case QXmlStreamReader::EndElement:
3674 endElement(localName: xml->name());
3675 ++remainingUnfinishedElements;
3676 done = (xml->name() == QLatin1String("svg"));
3677 break;
3678 case QXmlStreamReader::Characters:
3679 characters(str: xml->text());
3680 break;
3681 case QXmlStreamReader::ProcessingInstruction:
3682 processingInstruction(target: xml->processingInstructionTarget().toString(), data: xml->processingInstructionData().toString());
3683 break;
3684 default:
3685 break;
3686 }
3687 }
3688 resolveGradients(node: m_doc);
3689 resolveNodes();
3690 if (detectCycles(node: m_doc)) {
3691 qCWarning(lcSvgHandler, "Cycles detected in SVG, document discarded.");
3692 delete m_doc;
3693 m_doc = nullptr;
3694 }
3695}
3696
3697bool QSvgHandler::startElement(const QString &localName,
3698 const QXmlStreamAttributes &attributes)
3699{
3700 QSvgNode *node = nullptr;
3701
3702 pushColorCopy();
3703
3704 /* The xml:space attribute may appear on any element. We do
3705 * a lookup by the qualified name here, but this is namespace aware, since
3706 * the XML namespace can only be bound to prefix "xml." */
3707 const QStringView xmlSpace(attributes.value(qualifiedName: QLatin1String("xml:space")));
3708 if (xmlSpace.isNull()) {
3709 // This element has no xml:space attribute.
3710 m_whitespaceMode.push(t: m_whitespaceMode.isEmpty() ? QSvgText::Default : m_whitespaceMode.top());
3711 } else if (xmlSpace == QLatin1String("preserve")) {
3712 m_whitespaceMode.push(t: QSvgText::Preserve);
3713 } else if (xmlSpace == QLatin1String("default")) {
3714 m_whitespaceMode.push(t: QSvgText::Default);
3715 } else {
3716 const QByteArray msg = '"' + xmlSpace.toString().toLocal8Bit()
3717 + "\" is an invalid value for attribute xml:space. "
3718 "Valid values are \"preserve\" and \"default\".";
3719 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3720 m_whitespaceMode.push(t: QSvgText::Default);
3721 }
3722
3723 if (!m_doc && localName != QLatin1String("svg"))
3724 return false;
3725
3726 if (FactoryMethod method = findGroupFactory(name: localName)) {
3727 //group
3728 node = method(m_doc ? m_nodes.top() : 0, attributes, this);
3729 Q_ASSERT(node);
3730 if (!m_doc) {
3731 Q_ASSERT(node->type() == QSvgNode::DOC);
3732 m_doc = static_cast<QSvgTinyDocument*>(node);
3733 } else {
3734 switch (m_nodes.top()->type()) {
3735 case QSvgNode::DOC:
3736 case QSvgNode::G:
3737 case QSvgNode::DEFS:
3738 case QSvgNode::SWITCH:
3739 {
3740 QSvgStructureNode *group =
3741 static_cast<QSvgStructureNode*>(m_nodes.top());
3742 group->addChild(child: node, id: someId(attributes));
3743 }
3744 break;
3745 default:
3746 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
3747 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3748 delete node;
3749 node = 0;
3750 break;
3751 }
3752 }
3753 if (node) {
3754 parseCoreNode(node, attributes);
3755#ifndef QT_NO_CSSPARSER
3756 cssStyleLookup(node, handler: this, selector: m_selector);
3757#endif
3758 parseStyle(node, attrs: attributes, handler: this);
3759 }
3760 } else if (FactoryMethod method = findGraphicsFactory(name: localName)) {
3761 //rendering element
3762 Q_ASSERT(!m_nodes.isEmpty());
3763 node = method(m_nodes.top(), attributes, this);
3764 if (node) {
3765 switch (m_nodes.top()->type()) {
3766 case QSvgNode::DOC:
3767 case QSvgNode::G:
3768 case QSvgNode::DEFS:
3769 case QSvgNode::SWITCH:
3770 {
3771 if (node->type() == QSvgNode::TSPAN) {
3772 const QByteArray msg = QByteArrayLiteral("\'tspan\' element in wrong context.");
3773 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3774 delete node;
3775 node = 0;
3776 break;
3777 }
3778 QSvgStructureNode *group =
3779 static_cast<QSvgStructureNode*>(m_nodes.top());
3780 group->addChild(child: node, id: someId(attributes));
3781 }
3782 break;
3783 case QSvgNode::TEXT:
3784 case QSvgNode::TEXTAREA:
3785 if (node->type() == QSvgNode::TSPAN) {
3786 static_cast<QSvgText *>(m_nodes.top())->addTspan(tspan: static_cast<QSvgTspan *>(node));
3787 } else {
3788 const QByteArray msg = QByteArrayLiteral("\'text\' or \'textArea\' element contains invalid element type.");
3789 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3790 delete node;
3791 node = 0;
3792 }
3793 break;
3794 default:
3795 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
3796 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3797 delete node;
3798 node = 0;
3799 break;
3800 }
3801
3802 if (node) {
3803 parseCoreNode(node, attributes);
3804#ifndef QT_NO_CSSPARSER
3805 cssStyleLookup(node, handler: this, selector: m_selector);
3806#endif
3807 parseStyle(node, attrs: attributes, handler: this);
3808 if (node->type() == QSvgNode::TEXT || node->type() == QSvgNode::TEXTAREA) {
3809 static_cast<QSvgText *>(node)->setWhitespaceMode(m_whitespaceMode.top());
3810 } else if (node->type() == QSvgNode::TSPAN) {
3811 static_cast<QSvgTspan *>(node)->setWhitespaceMode(m_whitespaceMode.top());
3812 } else if (node->type() == QSvgNode::USE) {
3813 auto useNode = static_cast<QSvgUse *>(node);
3814 if (!useNode->isResolved())
3815 m_toBeResolved.append(t: useNode);
3816 }
3817 }
3818 }
3819 } else if (ParseMethod method = findUtilFactory(name: localName)) {
3820 Q_ASSERT(!m_nodes.isEmpty());
3821 if (!method(m_nodes.top(), attributes, this))
3822 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
3823 } else if (StyleFactoryMethod method = findStyleFactoryMethod(name: localName)) {
3824 QSvgStyleProperty *prop = method(m_nodes.top(), attributes, this);
3825 if (prop) {
3826 m_style = prop;
3827 m_nodes.top()->appendStyleProperty(prop, id: someId(attributes));
3828 } else {
3829 const QByteArray msg = QByteArrayLiteral("Could not parse node: ") + localName.toLocal8Bit();
3830 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3831 }
3832 } else if (StyleParseMethod method = findStyleUtilFactoryMethod(name: localName)) {
3833 if (m_style) {
3834 if (!method(m_style, attributes, this))
3835 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
3836 }
3837 } else {
3838 //qCWarning(lcSvgHandler) <<"Skipping unknown element!"<<namespaceURI<<"::"<<localName;
3839 m_skipNodes.push(t: Unknown);
3840 return true;
3841 }
3842
3843 if (node) {
3844 m_nodes.push(t: node);
3845 m_skipNodes.push(t: Graphics);
3846 } else {
3847 //qDebug()<<"Skipping "<<localName;
3848 m_skipNodes.push(t: Style);
3849 }
3850 return true;
3851}
3852
3853bool QSvgHandler::endElement(const QStringView localName)
3854{
3855 CurrentNode node = m_skipNodes.top();
3856 m_skipNodes.pop();
3857 m_whitespaceMode.pop();
3858
3859 popColor();
3860
3861 if (node == Unknown) {
3862 return true;
3863 }
3864
3865#ifdef QT_NO_CSSPARSER
3866 Q_UNUSED(localName);
3867#else
3868 if (m_inStyle && localName == QLatin1String("style"))
3869 m_inStyle = false;
3870#endif
3871
3872 if (node == Graphics)
3873 m_nodes.pop();
3874 else if (m_style && !m_skipNodes.isEmpty() && m_skipNodes.top() != Style)
3875 m_style = 0;
3876
3877 return true;
3878}
3879
3880void QSvgHandler::resolveGradients(QSvgNode *node, int nestedDepth)
3881{
3882 if (!node || (node->type() != QSvgNode::DOC && node->type() != QSvgNode::G
3883 && node->type() != QSvgNode::DEFS && node->type() != QSvgNode::SWITCH)) {
3884 return;
3885 }
3886
3887 QSvgStructureNode *structureNode = static_cast<QSvgStructureNode *>(node);
3888
3889 const QList<QSvgNode *> ren = structureNode->renderers();
3890 for (auto it = ren.begin(); it != ren.end(); ++it) {
3891 QSvgFillStyle *fill = static_cast<QSvgFillStyle *>((*it)->styleProperty(type: QSvgStyleProperty::FILL));
3892 if (fill && !fill->isGradientResolved()) {
3893 QString id = fill->gradientId();
3894 QSvgFillStyleProperty *style = structureNode->styleProperty(id);
3895 if (style) {
3896 fill->setFillStyle(style);
3897 } else {
3898 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
3899 fill->setBrush(Qt::NoBrush);
3900 }
3901 }
3902
3903 QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>((*it)->styleProperty(type: QSvgStyleProperty::STROKE));
3904 if (stroke && !stroke->isGradientResolved()) {
3905 QString id = stroke->gradientId();
3906 QSvgFillStyleProperty *style = structureNode->styleProperty(id);
3907 if (style) {
3908 stroke->setStyle(style);
3909 } else {
3910 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
3911 stroke->setStroke(Qt::NoBrush);
3912 }
3913 }
3914
3915 if (nestedDepth < 2048)
3916 resolveGradients(node: *it, nestedDepth: nestedDepth + 1);
3917 }
3918}
3919
3920void QSvgHandler::resolveNodes()
3921{
3922 for (QSvgUse *useNode : std::as_const(t&: m_toBeResolved)) {
3923 const auto parent = useNode->parent();
3924 if (!parent)
3925 continue;
3926
3927 QSvgNode::Type t = parent->type();
3928 if (t != QSvgNode::DOC && t != QSvgNode::DEFS && t != QSvgNode::G && t != QSvgNode::SWITCH)
3929 continue;
3930
3931 QSvgStructureNode *group = static_cast<QSvgStructureNode *>(parent);
3932 QSvgNode *link = group->scopeNode(id: useNode->linkId());
3933 if (!link) {
3934 qCWarning(lcSvgHandler, "link #%s is undefined!", qPrintable(useNode->linkId()));
3935 continue;
3936 }
3937
3938 if (useNode->parent()->isDescendantOf(parent: link))
3939 qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(useNode->linkId()));
3940
3941 useNode->setLink(link);
3942 }
3943 m_toBeResolved.clear();
3944}
3945
3946bool QSvgHandler::characters(const QStringView str)
3947{
3948#ifndef QT_NO_CSSPARSER
3949 if (m_inStyle) {
3950 QString css = str.toString();
3951 QCss::StyleSheet sheet;
3952 QCss::Parser(css).parse(styleSheet: &sheet);
3953 m_selector->styleSheets.append(t: sheet);
3954 return true;
3955 }
3956#endif
3957 if (m_skipNodes.isEmpty() || m_skipNodes.top() == Unknown || m_nodes.isEmpty())
3958 return true;
3959
3960 if (m_nodes.top()->type() == QSvgNode::TEXT || m_nodes.top()->type() == QSvgNode::TEXTAREA) {
3961 static_cast<QSvgText*>(m_nodes.top())->addText(text: str.toString());
3962 } else if (m_nodes.top()->type() == QSvgNode::TSPAN) {
3963 static_cast<QSvgTspan*>(m_nodes.top())->addText(text: str.toString());
3964 }
3965
3966 return true;
3967}
3968
3969QIODevice *QSvgHandler::device() const
3970{
3971 return xml->device();
3972}
3973
3974QSvgTinyDocument *QSvgHandler::document() const
3975{
3976 return m_doc;
3977}
3978
3979QSvgHandler::LengthType QSvgHandler::defaultCoordinateSystem() const
3980{
3981 return m_defaultCoords;
3982}
3983
3984void QSvgHandler::setDefaultCoordinateSystem(LengthType type)
3985{
3986 m_defaultCoords = type;
3987}
3988
3989void QSvgHandler::pushColor(const QColor &color)
3990{
3991 m_colorStack.push(t: color);
3992 m_colorTagCount.push(t: 1);
3993}
3994
3995void QSvgHandler::pushColorCopy()
3996{
3997 if (m_colorTagCount.size())
3998 ++m_colorTagCount.top();
3999 else
4000 pushColor(color: Qt::black);
4001}
4002
4003void QSvgHandler::popColor()
4004{
4005 if (m_colorTagCount.size()) {
4006 if (!--m_colorTagCount.top()) {
4007 m_colorStack.pop();
4008 m_colorTagCount.pop();
4009 }
4010 }
4011}
4012
4013QColor QSvgHandler::currentColor() const
4014{
4015 if (!m_colorStack.isEmpty())
4016 return m_colorStack.top();
4017 else
4018 return QColor(0, 0, 0);
4019}
4020
4021#ifndef QT_NO_CSSPARSER
4022
4023void QSvgHandler::setInStyle(bool b)
4024{
4025 m_inStyle = b;
4026}
4027
4028bool QSvgHandler::inStyle() const
4029{
4030 return m_inStyle;
4031}
4032
4033QSvgStyleSelector * QSvgHandler::selector() const
4034{
4035 return m_selector;
4036}
4037
4038#endif // QT_NO_CSSPARSER
4039
4040bool QSvgHandler::processingInstruction(const QString &target, const QString &data)
4041{
4042#ifdef QT_NO_CSSPARSER
4043 Q_UNUSED(target);
4044 Q_UNUSED(data);
4045#else
4046 if (target == QLatin1String("xml-stylesheet")) {
4047 QRegularExpression rx(QLatin1String("type=\\\"(.+)\\\""),
4048 QRegularExpression::InvertedGreedinessOption);
4049 QRegularExpressionMatchIterator iter = rx.globalMatch(subject: data);
4050 bool isCss = false;
4051 while (iter.hasNext()) {
4052 QRegularExpressionMatch match = iter.next();
4053 QString type = match.captured(nth: 1);
4054 if (type.toLower() == QLatin1String("text/css")) {
4055 isCss = true;
4056 }
4057 }
4058
4059 if (isCss) {
4060 QRegularExpression rx(QLatin1String("href=\\\"(.+)\\\""),
4061 QRegularExpression::InvertedGreedinessOption);
4062 QRegularExpressionMatch match = rx.match(subject: data);
4063 QString addr = match.captured(nth: 1);
4064 QFileInfo fi(addr);
4065 //qDebug()<<"External CSS file "<<fi.absoluteFilePath()<<fi.exists();
4066 if (fi.exists()) {
4067 QFile file(fi.absoluteFilePath());
4068 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
4069 return true;
4070 }
4071 QByteArray cssData = file.readAll();
4072 QString css = QString::fromUtf8(ba: cssData);
4073
4074 QCss::StyleSheet sheet;
4075 QCss::Parser(css).parse(styleSheet: &sheet);
4076 m_selector->styleSheets.append(t: sheet);
4077 }
4078
4079 }
4080 }
4081#endif
4082
4083 return true;
4084}
4085
4086void QSvgHandler::setAnimPeriod(int start, int end)
4087{
4088 Q_UNUSED(start);
4089 m_animEnd = qMax(a: end, b: m_animEnd);
4090}
4091
4092int QSvgHandler::animationDuration() const
4093{
4094 return m_animEnd;
4095}
4096
4097QSvgHandler::~QSvgHandler()
4098{
4099#ifndef QT_NO_CSSPARSER
4100 delete m_selector;
4101 m_selector = 0;
4102#endif
4103
4104 if(m_ownsReader)
4105 delete xml;
4106}
4107
4108QT_END_NAMESPACE
4109

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