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