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