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