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