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

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