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