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