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