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 default:
987 return sizeTable[spec];
988 }
989
990 Q_UNREACHABLE_RETURN(std::nullopt);
991}
992
993static std::optional<int> parseFontWeight(QStringView s)
994{
995 // https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-weight-prop
996 // Value: normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
997
998 if (s.isEmpty() || s == tokens::inherit)
999 return std::nullopt;
1000
1001 if (s == tokens::normal)
1002 return QFont::Normal;
1003 if (s == tokens::bold)
1004 return QFont::Bold;
1005 if (s == tokens::bolder)
1006 return QSvgFontStyle::BOLDER;
1007 if (s == tokens::lighter)
1008 return QSvgFontStyle::LIGHTER;
1009
1010 bool ok = false;
1011 const int num = s.toInt(&ok);
1012 if (ok)
1013 return num;
1014
1015 return std::nullopt;
1016}
1017
1019{
1020 // https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-variant-prop
1021 // Value: normal |
1022 // none |
1023 // [
1024 // <common-lig-values> ||
1025 // <discretionary-lig-values> ||
1026 // <historical-lig-values> ||
1027 // <contextual-alt-values> ||
1028 // [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] ||
1029 // <numeric-figure-values> ||
1030 // <numeric-spacing-values> ||
1031 // <numeric-fraction-values> ||
1032 // ordinal ||
1033 // slashed-zero ||
1034 // <east-asian-variant-values> ||
1035 // <east-asian-width-values> ||
1036 // ruby ||
1037 // [ sub | super ]
1038 // ]
1039
1040 // TODO: implement parsing of sub-properties, and values other than normal and small-caps
1041
1042 auto s = attributes.fontVariant;
1043
1044 if (s == tokens::normal)
1045 return QFont::MixedCase;
1046 if (s == tokens::small_caps)
1047 return QFont::SmallCaps;
1048
1049 return std::nullopt; // incl. empty and tokens::inherit
1050}
1051
1052static std::optional<Qt::Alignment> parseTextAnchor(QStringView s)
1053{
1054 // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-anchor#formal_syntax
1055 // text-anchor =
1056 // start |
1057 // middle |
1058 // end
1059
1060 if (s == tokens::start)
1061 return Qt::AlignLeft;
1062 if (s == tokens::middle)
1063 return Qt::AlignHCenter;
1064 if (s == tokens::end)
1065 return Qt::AlignRight;
1066
1067 return std::nullopt; // incl. empty and tokens::inherit
1068}
1069
1070static void parseFont(QSvgNode *node,
1071 const QSvgAttributes &attributes,
1072 QSvgHandler *handler)
1073{
1074 auto parsedFontSize = parseFontSize(attributes.fontSize);
1075 auto parsedFontStyle = parseFontStyle(attributes.fontStyle);
1076 auto parsedFontWeight = parseFontWeight(attributes.fontWeight);
1077 auto parsedFontVariant = parseFontVariant(attributes);
1078 auto parsedTextAnchor = parseTextAnchor(attributes.textAnchor);
1079
1080 if (attributes.fontFamily.isEmpty() && !parsedFontSize && !parsedFontStyle &&
1081 !parsedFontWeight && !parsedFontVariant && !parsedTextAnchor)
1082 return;
1083
1084 QSvgFontStylePtr fontStyle;
1085 if (!attributes.fontFamily.isEmpty()) {
1086 QSvgDocument *doc = handler->document();
1087 if (doc) {
1088 QSvgFont *svgFont = doc->svgFont(attributes.fontFamily.toString());
1089 if (svgFont)
1090 fontStyle = std::make_unique<QSvgFontStyle>(svgFont);
1091 }
1092 }
1093 if (!fontStyle)
1094 fontStyle = std::make_unique<QSvgFontStyle>();
1095 if (!attributes.fontFamily.isEmpty() && attributes.fontFamily != tokens::inherit) {
1096 QStringView family = attributes.fontFamily.trimmed();
1097 if (!family.isEmpty() && (family.at(0) == QLatin1Char('\'') || family.at(0) == QLatin1Char('\"')))
1098 family = family.mid(1, family.size() - 2);
1099 fontStyle->setFamily(family.toString());
1100 }
1101
1102 if (parsedFontSize)
1103 fontStyle->setSize(*parsedFontSize);
1104
1105 if (parsedFontStyle)
1106 fontStyle->setStyle(*parsedFontStyle);
1107
1108 if (parsedFontWeight)
1109 fontStyle->setWeight(*parsedFontWeight);
1110
1111 if (parsedFontVariant)
1112 fontStyle->setVariant(*parsedFontVariant);
1113
1114 if (parsedTextAnchor)
1115 fontStyle->setTextAnchor(*parsedTextAnchor);
1116
1117 node->appendStyleProperty(std::move(fontStyle));
1118}
1119
1120static void parseTransform(QSvgNode *node,
1121 const QSvgAttributes &attributes,
1122 QSvgHandler *)
1123{
1124 if (attributes.transform.isEmpty())
1125 return;
1126 QTransform matrix = parseTransformationMatrix(attributes.transform.trimmed());
1127
1128 if (!matrix.isIdentity()) {
1129 node->appendStyleProperty(std::make_unique<QSvgTransformStyle>(QTransform(matrix)));
1130 }
1131
1132}
1133
1134static void parseVisibility(QSvgNode *node,
1135 const QSvgAttributes &attributes,
1136 QSvgHandler *)
1137{
1138 QSvgNode *parent = node->parent();
1139
1140 if (parent && (attributes.visibility.isEmpty() || attributes.visibility == tokens::inherit))
1141 node->setVisible(parent->isVisible());
1142 else if (attributes.visibility == QLatin1String("hidden") || attributes.visibility == QLatin1String("collapse")) {
1143 node->setVisible(false);
1144 } else
1145 node->setVisible(true);
1146}
1147
1148static bool parseStyle(QSvgNode *node,
1149 const QXmlStreamAttributes &attributes,
1150 QSvgHandler *handler);
1151
1152static int parseClockValue(QStringView str, bool *ok)
1153{
1154 int res = 0;
1155 int ms = 1000;
1156 str = str.trimmed();
1157 if (str.endsWith(QLatin1String("ms"))) {
1158 str.chop(2);
1159 ms = 1;
1160 } else if (str.endsWith(QLatin1String("s"))) {
1161 str.chop(1);
1162 }
1163 double val = ms * QSvgUtils::toDouble(str, ok);
1164 if (ok) {
1165 if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
1166 res = static_cast<int>(val);
1167 else
1168 *ok = false;
1169 }
1170 return res;
1171}
1172
1173#ifndef QT_NO_CSSPARSER
1174
1175static void parseCssAnimations(QSvgNode *node,
1176 const QXmlStreamAttributes &attributes,
1177 QSvgHandler *handler)
1178{
1179 QSvgCssProperties cssAnimProps(attributes);
1180 QList<QSvgAnimationProperty> parsedProperties = cssAnimProps.animations();
1181
1182 for (auto &property : parsedProperties) {
1183 QSvgCssAnimation *anim = handler->cssHandler().createAnimation(property.name);
1184 if (!anim)
1185 continue;
1186
1187 anim->setRunningTime(property.delay, property.duration);
1188 anim->setIterationCount(property.iteration);
1189 QSvgCssEasingPtr easing = handler->cssHandler().createEasing(property.easingFunction, property.easingValues);
1190 anim->setEasing(std::move(easing));
1191
1192 handler->setAnimPeriod(property.delay, property.delay + property.duration);
1193 handler->document()->animator()->appendAnimation(node, anim);
1194 handler->document()->setAnimated(true);
1195 }
1196}
1197
1198static void parseOffsetPath(QSvgNode *node,
1199 const QXmlStreamAttributes &attributes)
1200{
1201 QSvgCssProperties cssProperties(attributes);
1202 QSvgOffsetProperty offset = cssProperties.offset();
1203
1204 if (!offset.path)
1205 return;
1206
1207 QSvgOffsetStylePtr offsetStyle = std::make_unique<QSvgOffsetStyle>();
1208 offsetStyle->setPath(offset.path.value());
1209 offsetStyle->setRotateAngle(offset.angle);
1210 offsetStyle->setRotateType(offset.rotateType);
1211 offsetStyle->setDistance(offset.distance);
1212 node->appendStyleProperty(std::move(offsetStyle));
1213}
1214
1215#endif // QT_NO_CSSPARSER
1216
1217QtSvg::Options QSvgHandler::options() const
1218{
1219 return m_options;
1220}
1221
1222QtSvg::AnimatorType QSvgHandler::animatorType() const
1223{
1224 return m_animatorType;
1225}
1226
1227bool QSvgHandler::trustedSourceMode() const
1228{
1229 return m_options.testFlag(QtSvg::AssumeTrustedSource);
1230}
1231
1232static inline QStringList stringToList(const QString &str)
1233{
1234 QStringList lst = str.split(QLatin1Char(','), Qt::SkipEmptyParts);
1235 return lst;
1236}
1237
1238static bool parseCoreNode(QSvgNode *node,
1239 const QXmlStreamAttributes &attributes)
1240{
1241 QStringList features;
1242 QStringList extensions;
1243 QStringList languages;
1244 QStringList formats;
1245 QStringList fonts;
1246 QStringView xmlClassStr;
1247
1248 for (const QXmlStreamAttribute &attribute : attributes) {
1249 QStringView name = attribute.qualifiedName();
1250 if (name.isEmpty())
1251 continue;
1252 QStringView value = attribute.value();
1253 switch (name.at(0).unicode()) {
1254 case 'c':
1255 if (name == QLatin1String("class"))
1256 xmlClassStr = value;
1257 break;
1258 case 'r':
1259 if (name == QLatin1String("requiredFeatures"))
1260 features = stringToList(value.toString());
1261 else if (name == QLatin1String("requiredExtensions"))
1262 extensions = stringToList(value.toString());
1263 else if (name == QLatin1String("requiredFormats"))
1264 formats = stringToList(value.toString());
1265 else if (name == QLatin1String("requiredFonts"))
1266 fonts = stringToList(value.toString());
1267 break;
1268 case 's':
1269 if (name == QLatin1String("systemLanguage"))
1270 languages = stringToList(value.toString());
1271 break;
1272 default:
1273 break;
1274 }
1275 }
1276
1277 node->setRequiredFeatures(features);
1278 node->setRequiredExtensions(extensions);
1279 node->setRequiredLanguages(languages);
1280 node->setRequiredFormats(formats);
1281 node->setRequiredFonts(fonts);
1282 node->setNodeId(someId(attributes));
1283 node->setXmlClass(xmlClassStr.toString());
1284
1285 return true;
1286}
1287
1288static void parseOpacity(QSvgNode *node,
1289 const QSvgAttributes &attributes,
1290 QSvgHandler *)
1291{
1292 if (attributes.opacity.isEmpty())
1293 return;
1294
1295 const QStringView value = attributes.opacity.trimmed();
1296
1297 bool ok = false;
1298 qreal op = value.toDouble(&ok);
1299
1300 if (ok) {
1301 QSvgOpacityStylePtr opacity = std::make_unique<QSvgOpacityStyle>(qBound(qreal(0.0), op, qreal(1.0)));
1302 node->appendStyleProperty(std::move(opacity));
1303 }
1304}
1305
1307{
1308 if (op == tokens::compOp::clear)
1309 return QPainter::CompositionMode_Clear;
1310 else if (op == tokens::compOp::src)
1311 return QPainter::CompositionMode_Source;
1312 else if (op == tokens::compOp::dst)
1313 return QPainter::CompositionMode_Destination;
1314 else if (op == tokens::compOp::srcOver)
1315 return QPainter::CompositionMode_SourceOver;
1316 else if (op == tokens::compOp::dstOver)
1317 return QPainter::CompositionMode_DestinationOver;
1318 else if (op == tokens::compOp::srcIn)
1319 return QPainter::CompositionMode_SourceIn;
1320 else if (op == tokens::compOp::dstIn)
1321 return QPainter::CompositionMode_DestinationIn;
1322 else if (op == tokens::compOp::srcOut)
1323 return QPainter::CompositionMode_SourceOut;
1324 else if (op == tokens::compOp::dstOut)
1325 return QPainter::CompositionMode_DestinationOut;
1326 else if (op == tokens::compOp::srcAtop)
1327 return QPainter::CompositionMode_SourceAtop;
1328 else if (op == tokens::compOp::dstAtop)
1329 return QPainter::CompositionMode_DestinationAtop;
1330 else if (op == tokens::compOp::xorOp)
1331 return QPainter::CompositionMode_Xor;
1332 else if (op == tokens::compOp::plus)
1333 return QPainter::CompositionMode_Plus;
1334 else if (op == tokens::compOp::multiply)
1335 return QPainter::CompositionMode_Multiply;
1336 else if (op == tokens::compOp::screen)
1337 return QPainter::CompositionMode_Screen;
1338 else if (op == tokens::compOp::overlay)
1339 return QPainter::CompositionMode_Overlay;
1340 else if (op == tokens::compOp::darken)
1341 return QPainter::CompositionMode_Darken;
1342 else if (op == tokens::compOp::lighten)
1343 return QPainter::CompositionMode_Lighten;
1344 else if (op == tokens::compOp::colorDodge)
1345 return QPainter::CompositionMode_ColorDodge;
1346 else if (op == tokens::compOp::colorBurn)
1347 return QPainter::CompositionMode_ColorBurn;
1348 else if (op == tokens::compOp::hardLight)
1349 return QPainter::CompositionMode_HardLight;
1350 else if (op == tokens::compOp::softLight)
1351 return QPainter::CompositionMode_SoftLight;
1352 else if (op == tokens::compOp::difference)
1353 return QPainter::CompositionMode_Difference;
1354 else if (op == tokens::compOp::exclusion)
1355 return QPainter::CompositionMode_Exclusion;
1356
1357 qCWarning(lcSvgHandler) << "Composition mode not supported : "_L1 << op;
1358 return QPainter::CompositionMode_SourceOver;
1359}
1360
1361static void parseCompOp(QSvgNode *node,
1362 const QSvgAttributes &attributes,
1363 QSvgHandler *)
1364{
1365 if (attributes.compOp.isEmpty())
1366 return;
1367 QStringView value = attributes.compOp.trimmed();
1368
1369 if (!value.isEmpty()) {
1370 QSvgCompOpStylePtr compop = std::make_unique<QSvgCompOpStyle>(svgToQtCompositionMode(value));
1371 node->appendStyleProperty(std::move(compop));
1372 }
1373}
1374
1375static QSvgNode::DisplayMode displayStringToEnum(const QStringView str)
1376{
1377 if (str == QLatin1String("inline")) {
1378 return QSvgNode::InlineMode;
1379 } else if (str == QLatin1String("block")) {
1380 return QSvgNode::BlockMode;
1381 } else if (str == QLatin1String("list-item")) {
1382 return QSvgNode::ListItemMode;
1383 } else if (str == QLatin1String("run-in")) {
1384 return QSvgNode::RunInMode;
1385 } else if (str == QLatin1String("compact")) {
1386 return QSvgNode::CompactMode;
1387 } else if (str == QLatin1String("marker")) {
1388 return QSvgNode::MarkerMode;
1389 } else if (str == QLatin1String("table")) {
1390 return QSvgNode::TableMode;
1391 } else if (str == QLatin1String("inline-table")) {
1392 return QSvgNode::InlineTableMode;
1393 } else if (str == QLatin1String("table-row-group")) {
1394 return QSvgNode::TableRowGroupMode;
1395 } else if (str == QLatin1String("table-header-group")) {
1396 return QSvgNode::TableHeaderGroupMode;
1397 } else if (str == QLatin1String("table-footer-group")) {
1398 return QSvgNode::TableFooterGroupMode;
1399 } else if (str == QLatin1String("table-row")) {
1400 return QSvgNode::TableRowMode;
1401 } else if (str == QLatin1String("table-column-group")) {
1402 return QSvgNode::TableColumnGroupMode;
1403 } else if (str == QLatin1String("table-column")) {
1404 return QSvgNode::TableColumnMode;
1405 } else if (str == QLatin1String("table-cell")) {
1406 return QSvgNode::TableCellMode;
1407 } else if (str == QLatin1String("table-caption")) {
1408 return QSvgNode::TableCaptionMode;
1409 } else if (str == QLatin1String("none")) {
1410 return QSvgNode::NoneMode;
1411 } else if (str == tokens::inherit) {
1412 return QSvgNode::InheritMode;
1413 }
1414 return QSvgNode::BlockMode;
1415}
1416
1417static void parseOthers(QSvgNode *node,
1418 const QSvgAttributes &attributes,
1419 QSvgHandler *)
1420{
1421 if (attributes.display.isEmpty())
1422 return;
1423 QStringView displayStr = attributes.display.trimmed();
1424
1425 if (!displayStr.isEmpty()) {
1426 node->setDisplayMode(displayStringToEnum(displayStr));
1427 }
1428}
1429
1430static std::optional<QStringView> getAttributeId(const QStringView &attribute)
1431{
1432 if (attribute.isEmpty())
1433 return std::nullopt;
1434
1435 return idFromFuncIRI(attribute);
1436}
1437
1438static void parseExtendedAttributes(QSvgNode *node,
1439 const QSvgAttributes &attributes,
1440 QSvgHandler *handler)
1441{
1442 if (handler->options().testFlag(QtSvg::Tiny12FeaturesOnly))
1443 return;
1444
1445 if (auto id = getAttributeId(attributes.mask))
1446 node->setMaskId(id->toString());
1447 if (auto id = getAttributeId(attributes.markerStart))
1448 node->setMarkerStartId(id->toString());
1449 if (auto id = getAttributeId(attributes.markerMid))
1450 node->setMarkerMidId(id->toString());
1451 if (auto id = getAttributeId(attributes.markerEnd))
1452 node->setMarkerEndId(id->toString());
1453 if (auto id = getAttributeId(attributes.filter))
1454 node->setFilterId(id->toString());
1455}
1456
1457static void parseRenderingHints(QSvgNode *node,
1458 const QSvgAttributes &attributes,
1459 QSvgHandler *)
1460{
1461 if (attributes.imageRendering.isEmpty())
1462 return;
1463
1464 QStringView ir = attributes.imageRendering.trimmed();
1465 QSvgQualityStylePtr quality = std::make_unique<QSvgQualityStyle>(0);
1466 if (ir == QLatin1String("auto"))
1467 quality->setImageRendering(QSvgQualityStyle::ImageRenderingAuto);
1468 else if (ir == QLatin1String("optimizeSpeed"))
1469 quality->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeSpeed);
1470 else if (ir == QLatin1String("optimizeQuality"))
1471 quality->setImageRendering(QSvgQualityStyle::ImageRenderingOptimizeQuality);
1472 node->appendStyleProperty(std::move(quality));
1473}
1474
1475static bool parseStyle(QSvgNode *node,
1476 const QXmlStreamAttributes &attributes,
1477 QSvgHandler *handler)
1478{
1479 // Get style in the following order :
1480 // 1) values from svg attributes
1481 // 2) CSS style
1482 // 3) values defined in the svg "style" property
1483 QSvgAttributes svgAttributes(attributes, handler);
1484
1485#ifndef QT_NO_CSSPARSER
1486 QXmlStreamAttributes cssAttributes;
1487 handler->cssHandler().styleLookup(node, cssAttributes);
1488
1489 QStringView style = attributes.value(QLatin1String("style"));
1490 if (!style.isEmpty())
1491 handler->cssHandler().parseCSStoXMLAttrs(style.toString(), cssAttributes);
1492 svgAttributes.setAttributes(cssAttributes, handler);
1493
1494 parseOffsetPath(node, cssAttributes);
1495 if (!handler->options().testFlag(QtSvg::DisableCSSAnimations))
1496 parseCssAnimations(node, cssAttributes, handler);
1497#endif
1498
1499 parseColor(node, svgAttributes, handler);
1500 parseBrush(node, svgAttributes, handler);
1501 parsePen(node, svgAttributes, handler);
1502 parseFont(node, svgAttributes, handler);
1503 parseTransform(node, svgAttributes, handler);
1504 parseVisibility(node, svgAttributes, handler);
1505 parseOpacity(node, svgAttributes, handler);
1506 parseCompOp(node, svgAttributes, handler);
1507 parseRenderingHints(node, svgAttributes, handler);
1508 parseOthers(node, svgAttributes, handler);
1509 parseExtendedAttributes(node, svgAttributes, handler);
1510
1511 return true;
1512}
1513
1514static bool parseAnchorNode(QSvgNode *parent,
1515 const QXmlStreamAttributes &attributes,
1516 QSvgHandler *)
1517{
1518 Q_UNUSED(parent); Q_UNUSED(attributes);
1519 return true;
1520}
1521
1522static bool parseBaseAnimate(QSvgNode *,
1523 const QXmlStreamAttributes &attributes,
1524 QSvgAnimateNode *anim,
1525 QSvgHandler *handler)
1526{
1527 const QStringView beginStr = attributes.value(QLatin1String("begin"));
1528 const QStringView durStr = attributes.value(QLatin1String("dur"));
1529 const QStringView endStr = attributes.value(QLatin1String("end"));
1530 const QStringView repeatStr = attributes.value(QLatin1String("repeatCount"));
1531 const QStringView fillStr = attributes.value(QLatin1String("fill"));
1532 const QStringView addtv = attributes.value(QLatin1String("additive"));
1533 QStringView linkId = attributes.value(QLatin1String("xlink:href"));
1534
1535 if (linkId.isEmpty())
1536 linkId = attributes.value(QLatin1String("href"));
1537
1538 linkId = idFromIRI(linkId);
1539
1540 bool ok = true;
1541 int begin = parseClockValue(beginStr, &ok);
1542 if (!ok)
1543 return false;
1544 int dur = parseClockValue(durStr, &ok);
1545 if (!ok)
1546 return false;
1547 int end = parseClockValue(endStr, &ok);
1548 if (!ok)
1549 return false;
1550 qreal repeatCount = (repeatStr == QLatin1String("indefinite")) ? -1 :
1551 qMax(1.0, QSvgUtils::toDouble(repeatStr));
1552
1553 QSvgAnimateNode::Fill fill = (fillStr == QLatin1String("freeze")) ? QSvgAnimateNode::Freeze :
1554 QSvgAnimateNode::Remove;
1555
1556 QSvgAnimateNode::Additive additive = (addtv == QLatin1String("sum")) ? QSvgAnimateNode::Sum :
1557 QSvgAnimateNode::Replace;
1558
1559 anim->setRunningTime(begin, dur, end, 0);
1560 anim->setRepeatCount(repeatCount);
1561 anim->setFill(fill);
1562 anim->setAdditiveType(additive);
1563 anim->setLinkId(linkId.toString());
1564
1565 handler->document()->setAnimated(true);
1566
1567 handler->setAnimPeriod(begin, begin + dur);
1568 return true;
1569}
1570
1571static void generateKeyFrames(QList<qreal> &keyFrames, uint count)
1572{
1573 if (count < 2)
1574 return;
1575
1576 qreal spacing = 1.0f / (count - 1);
1577 for (uint i = 0; i < count; i++) {
1578 keyFrames.append(i * spacing);
1579 }
1580}
1581
1582static QSvgNode *createAnimateColorNode(QSvgNode *parent,
1583 const QXmlStreamAttributes &attributes,
1584 QSvgHandler *handler)
1585{
1586 const QStringView fromStr = attributes.value(QLatin1String("from"));
1587 const QStringView toStr = attributes.value(QLatin1String("to"));
1588 const QStringView valuesStr = attributes.value(QLatin1String("values"));
1589 const QString targetStr = attributes.value(QLatin1String("attributeName")).toString();
1590
1591 if (targetStr != QLatin1String("fill") && targetStr != QLatin1String("stroke"))
1592 return nullptr;
1593
1594 QList<QColor> colors;
1595 if (valuesStr.isEmpty()) {
1596 QColor startColor, endColor;
1597 resolveColor(fromStr, startColor, handler);
1598 resolveColor(toStr, endColor, handler);
1599 colors.reserve(2);
1600 colors.append(startColor);
1601 colors.append(endColor);
1602 } else {
1603 for (auto part : qTokenize(valuesStr, u';')) {
1604 QColor color;
1605 resolveColor(part, color, handler);
1606 colors.append(color);
1607 }
1608 }
1609
1610 QSvgAnimatedPropertyColor *prop = static_cast<QSvgAnimatedPropertyColor *>
1611 (QSvgAbstractAnimatedProperty::createAnimatedProperty(targetStr));
1612 if (!prop)
1613 return nullptr;
1614
1615 prop->setColors(colors);
1616
1617 QList<qreal> keyFrames;
1618 generateKeyFrames(keyFrames, colors.size());
1619 prop->setKeyFrames(keyFrames);
1620
1621 QSvgAnimateColor *anim = new QSvgAnimateColor(parent);
1622 anim->appendProperty(prop);
1623
1624 if (!parseBaseAnimate(parent, attributes, anim, handler)) {
1625 delete anim;
1626 return nullptr;
1627 }
1628
1629 return anim;
1630}
1631
1632static QSvgNode *createAnimateMotionNode(QSvgNode *parent,
1633 const QXmlStreamAttributes &attributes,
1634 QSvgHandler *)
1635{
1636 Q_UNUSED(parent); Q_UNUSED(attributes);
1637 return nullptr;
1638}
1639
1640static void parseNumberTriplet(QList<qreal> &values, QStringView *s)
1641{
1642 QList<qreal> list = parseNumbersList(s);
1643 values << list;
1644 for (int i = 3 - list.size(); i > 0; --i)
1645 values.append(0.0);
1646}
1647
1648static void parseNumberTriplet(QList<qreal> &values, QStringView s)
1649{
1650 parseNumberTriplet(values, &s);
1651}
1652
1653QSvgNode *createAnimateTransformNode(QSvgNode *parent,
1654 const QXmlStreamAttributes &attributes,
1655 QSvgHandler *handler)
1656{
1657 const QStringView typeStr = attributes.value(QLatin1String("type"));
1658 const QStringView values = attributes.value(QLatin1String("values"));
1659 const QStringView fromStr = attributes.value(QLatin1String("from"));
1660 const QStringView toStr = attributes.value(QLatin1String("to"));
1661 const QStringView byStr = attributes.value(QLatin1String("by"));
1662
1663 QList<qreal> vals;
1664 if (values.isEmpty()) {
1665 if (fromStr.isEmpty()) {
1666 if (!byStr.isEmpty()) {
1667 vals.append(0.0);
1668 vals.append(0.0);
1669 vals.append(0.0);
1670 parseNumberTriplet(vals, byStr);
1671 } else {
1672 // To-animation not defined.
1673 return nullptr;
1674 }
1675 } else {
1676 if (!toStr.isEmpty()) {
1677 // From-to-animation.
1678 parseNumberTriplet(vals, fromStr);
1679 parseNumberTriplet(vals, toStr);
1680 } else if (!byStr.isEmpty()) {
1681 // From-by-animation.
1682 parseNumberTriplet(vals, fromStr);
1683 parseNumberTriplet(vals, byStr);
1684 for (int i = vals.size() - 3; i < vals.size(); ++i)
1685 vals[i] += vals[i - 3];
1686 } else {
1687 return nullptr;
1688 }
1689 }
1690 } else {
1691 QStringView s = values;
1692 while (!s.isEmpty()) {
1693 parseNumberTriplet(vals, &s);
1694 if (!s.isEmpty())
1695 s.slice(1);
1696 }
1697 }
1698 if (vals.size() % 3 != 0)
1699 return nullptr;
1700
1701
1702 QList<QSvgAnimatedPropertyTransform::TransformComponent> components;
1703 for (int i = 0; i <= vals.size() - 3; i += 3) {
1704 QSvgAnimatedPropertyTransform::TransformComponent component;
1705 if (typeStr == QLatin1String("translate")) {
1706 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Translate;
1707 component.values.append(vals.at(i));
1708 component.values.append(vals.at(i + 1));
1709 } else if (typeStr == QLatin1String("scale")) {
1710 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Scale;
1711 component.values.append(vals.at(i));
1712 component.values.append(vals.at(i + 1));
1713 } else if (typeStr == QLatin1String("rotate")) {
1714 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Rotate;
1715 component.values.append(vals.at(i));
1716 component.values.append(vals.at(i + 1));
1717 component.values.append(vals.at(i + 2));
1718 } else if (typeStr == QLatin1String("skewX")) {
1719 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew;
1720 component.values.append(vals.at(i));
1721 component.values.append(0);
1722 } else if (typeStr == QLatin1String("skewY")) {
1723 component.type = QSvgAnimatedPropertyTransform::TransformComponent::Skew;
1724 component.values.append(0);
1725 component.values.append(vals.at(i));
1726 } else {
1727 return nullptr;
1728 }
1729 components.append(component);
1730 }
1731
1732 QSvgAnimatedPropertyTransform *prop = static_cast<QSvgAnimatedPropertyTransform *>
1733 (QSvgAbstractAnimatedProperty::createAnimatedProperty(QLatin1String("transform")));
1734 if (!prop)
1735 return nullptr;
1736
1737 prop->appendComponents(components);
1738 // <animateTransform> always has one component per key frame
1739 prop->setTransformCount(1);
1740 QList<qreal> keyFrames;
1741 generateKeyFrames(keyFrames, vals.size() / 3);
1742 prop->setKeyFrames(keyFrames);
1743
1744 QSvgAnimateTransform *anim = new QSvgAnimateTransform(parent);
1745 anim->appendProperty(prop);
1746
1747 if (!parseBaseAnimate(parent, attributes, anim, handler)) {
1748 delete anim;
1749 return nullptr;
1750 }
1751
1752 return anim;
1753}
1754
1755static QSvgNode *createAnimateNode(QSvgNode *parent,
1756 const QXmlStreamAttributes &attributes,
1757 QSvgHandler *)
1758{
1759 Q_UNUSED(parent); Q_UNUSED(attributes);
1760 return nullptr;
1761}
1762
1763static bool parseAudioNode(QSvgNode *parent,
1764 const QXmlStreamAttributes &attributes,
1765 QSvgHandler *)
1766{
1767 Q_UNUSED(parent); Q_UNUSED(attributes);
1768 return true;
1769}
1770
1771static QSvgNode *createCircleNode(QSvgNode *parent,
1772 const QXmlStreamAttributes &attributes,
1773 QSvgHandler *)
1774{
1775 const QStringView cx = attributes.value(QLatin1String("cx"));
1776 const QStringView cy = attributes.value(QLatin1String("cy"));
1777 const QStringView r = attributes.value(QLatin1String("r"));
1778 qreal ncx = QSvgUtils::toDouble(cx);
1779 qreal ncy = QSvgUtils::toDouble(cy);
1780 qreal nr = QSvgUtils::toDouble(r);
1781 if (nr < 0.0)
1782 return nullptr;
1783
1784 QRectF rect(ncx-nr, ncy-nr, nr*2, nr*2);
1785 QSvgNode *circle = new QSvgCircle(parent, rect);
1786 return circle;
1787}
1788
1789static QSvgNode *createDefsNode(QSvgNode *parent,
1790 const QXmlStreamAttributes &attributes,
1791 QSvgHandler *)
1792{
1793 Q_UNUSED(attributes);
1794 QSvgDefs *defs = new QSvgDefs(parent);
1795 return defs;
1796}
1797
1798static bool parseDiscardNode(QSvgNode *parent,
1799 const QXmlStreamAttributes &attributes,
1800 QSvgHandler *)
1801{
1802 Q_UNUSED(parent); Q_UNUSED(attributes);
1803 return true;
1804}
1805
1806static QSvgNode *createEllipseNode(QSvgNode *parent,
1807 const QXmlStreamAttributes &attributes,
1808 QSvgHandler *)
1809{
1810 const QStringView cx = attributes.value(QLatin1String("cx"));
1811 const QStringView cy = attributes.value(QLatin1String("cy"));
1812 const QStringView rx = attributes.value(QLatin1String("rx"));
1813 const QStringView ry = attributes.value(QLatin1String("ry"));
1814 qreal ncx = QSvgUtils::toDouble(cx);
1815 qreal ncy = QSvgUtils::toDouble(cy);
1816 qreal nrx = QSvgUtils::toDouble(rx);
1817 qreal nry = QSvgUtils::toDouble(ry);
1818
1819 QRectF rect(ncx-nrx, ncy-nry, nrx*2, nry*2);
1820 QSvgNode *ellipse = new QSvgEllipse(parent, rect);
1821 return ellipse;
1822}
1823
1824static QSvgStyleProperty *createFontNode(const QXmlStreamAttributes &attributes,
1825 QSvgHandler *handler)
1826{
1827 const QStringView hax = attributes.value(QLatin1String("horiz-adv-x"));
1828 QString myId = someId(attributes);
1829
1830 qreal horizAdvX = QSvgUtils::toDouble(hax);
1831
1832 if (!myId.isEmpty()) {
1833 QSvgDocument *doc = handler->document();
1834 QSvgFont *font = doc->svgFont(myId);
1835 if (!font) {
1836 font = new QSvgFont(horizAdvX);
1837 font->setFamilyName(myId);
1838 doc->addSvgFont(font);
1839 }
1840 return new QSvgFontStyle(font);
1841 }
1842 return nullptr;
1843}
1844
1845static bool parseFontFaceNode(QSvgStyleProperty *parent,
1846 const QXmlStreamAttributes &attributes,
1847 QSvgHandler *handler)
1848{
1849 if (parent->type() != QSvgStyleProperty::Font) {
1850 return false;
1851 }
1852
1853 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
1854 QSvgFont *font = style->svgFont();
1855 const QStringView name = attributes.value(QLatin1String("font-family"));
1856 const QStringView unitsPerEmStr = attributes.value(QLatin1String("units-per-em"));
1857
1858 /*TODO: Fix toDouble and use the ok flag for testing instead because 0 is a valid
1859 * value for unitsPerEm. "units-per-em: <number>" as per definition
1860 */
1861 bool ok = false;
1862 qreal unitsPerEm = QSvgUtils::toDouble(unitsPerEmStr, &ok);
1863 if (!qFuzzyIsNull(unitsPerEm))
1864 font->setUnitsPerEm(unitsPerEm);
1865
1866 if (!name.isEmpty())
1867 font->setFamilyName(name.toString());
1868
1869 if (!font->familyName().isEmpty())
1870 if (!handler->document()->svgFont(font->familyName()))
1871 handler->document()->addSvgFont(font);
1872
1873 return true;
1874}
1875
1876static bool parseFontFaceNameNode(QSvgStyleProperty *parent,
1877 const QXmlStreamAttributes &attributes,
1878 QSvgHandler *handler)
1879{
1880 if (parent->type() != QSvgStyleProperty::Font) {
1881 return false;
1882 }
1883
1884 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
1885 QSvgFont *font = style->svgFont();
1886 const QStringView name = attributes.value(QLatin1String("name"));
1887
1888 if (!name.isEmpty())
1889 font->setFamilyName(name.toString());
1890
1891 if (!font->familyName().isEmpty())
1892 if (!handler->document()->svgFont(font->familyName()))
1893 handler->document()->addSvgFont(font);
1894
1895 return true;
1896}
1897
1898static bool parseFontFaceSrcNode(QSvgStyleProperty *parent,
1899 const QXmlStreamAttributes &attributes,
1900 QSvgHandler *)
1901{
1902 Q_UNUSED(parent); Q_UNUSED(attributes);
1903 return true;
1904}
1905
1906static bool parseFontFaceUriNode(QSvgStyleProperty *parent,
1907 const QXmlStreamAttributes &attributes,
1908 QSvgHandler *)
1909{
1910 Q_UNUSED(parent); Q_UNUSED(attributes);
1911 return true;
1912}
1913
1914static bool parseForeignObjectNode(QSvgNode *parent,
1915 const QXmlStreamAttributes &attributes,
1916 QSvgHandler *)
1917{
1918 Q_UNUSED(parent); Q_UNUSED(attributes);
1919 return true;
1920}
1921
1922static QSvgNode *createGNode(QSvgNode *parent,
1923 const QXmlStreamAttributes &attributes,
1924 QSvgHandler *)
1925{
1926 Q_UNUSED(attributes);
1927 QSvgG *node = new QSvgG(parent);
1928 return node;
1929}
1930
1931static bool parseGlyphNode(QSvgStyleProperty *parent,
1932 const QXmlStreamAttributes &attributes,
1933 QSvgHandler *)
1934{
1935 if (parent->type() != QSvgStyleProperty::Font) {
1936 return false;
1937 }
1938
1939 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
1940 QSvgFont *font = style->svgFont();
1941 return createSvgGlyph(font, attributes, false);
1942}
1943
1944static bool parseHandlerNode(QSvgNode *parent,
1945 const QXmlStreamAttributes &attributes,
1946 QSvgHandler *)
1947{
1948 Q_UNUSED(parent); Q_UNUSED(attributes);
1949 return true;
1950}
1951
1952static bool parseHkernNode(QSvgNode *parent,
1953 const QXmlStreamAttributes &attributes,
1954 QSvgHandler *)
1955{
1956 Q_UNUSED(parent); Q_UNUSED(attributes);
1957 return true;
1958}
1959
1960static QSvgNode *createImageNode(QSvgNode *parent,
1961 const QXmlStreamAttributes &attributes,
1962 QSvgHandler *handler)
1963{
1964 const QStringView x = attributes.value(QLatin1String("x"));
1965 const QStringView y = attributes.value(QLatin1String("y"));
1966 const QStringView width = attributes.value(QLatin1String("width"));
1967 const QStringView height = attributes.value(QLatin1String("height"));
1968 QString filename = attributes.value(QLatin1String("xlink:href")).toString();
1969 if (filename.isEmpty() && !handler->options().testFlag(QtSvg::Tiny12FeaturesOnly))
1970 filename = attributes.value(QLatin1String("href")).toString();
1971 qreal nx = QSvgUtils::toDouble(x);
1972 qreal ny = QSvgUtils::toDouble(y);
1973 QSvgUtils::LengthType type;
1974 qreal nwidth = QSvgUtils::parseLength(width.toString(), &type);
1975 nwidth = QSvgUtils::convertToPixels(nwidth, true, type);
1976
1977 qreal nheight = QSvgUtils::parseLength(height.toString(), &type);
1978 nheight = QSvgUtils::convertToPixels(nheight, false, type);
1979
1980 filename = filename.trimmed();
1981 if (filename.isEmpty()) {
1982 qCWarning(lcSvgHandler) << "QSvgHandler: Image filename is empty";
1983 return 0;
1984 }
1985 if (nwidth <= 0 || nheight <= 0) {
1986 qCWarning(lcSvgHandler) << "QSvgHandler: Width or height for" << filename << "image was not greater than 0";
1987 return 0;
1988 }
1989
1990 QImage image;
1991 enum {
1992 NotLoaded,
1993 LoadedFromData,
1994 LoadedFromFile
1995 } filenameType = NotLoaded;
1996
1997 if (filename.startsWith(QLatin1String("data"))) {
1998 QString mimeType;
1999 QByteArray data;
2000 if (qDecodeDataUrl(QUrl{filename}, mimeType, data)) {
2001 image = QImage::fromData(data);
2002 filenameType = LoadedFromData;
2003 }
2004 }
2005
2006 if (image.isNull()) {
2007 const auto *file = qobject_cast<QFile *>(handler->device());
2008 if (file) {
2009 QUrl url(filename);
2010 if (url.isRelative()) {
2011 QFileInfo info(file->fileName());
2012 filename = info.absoluteDir().absoluteFilePath(filename);
2013 }
2014 }
2015
2016 if (handler->trustedSourceMode() || !QImageReader::imageFormat(filename).startsWith("svg")) {
2017 image = QImage(filename);
2018 filenameType = LoadedFromFile;
2019 }
2020 }
2021
2022 if (image.isNull()) {
2023 qCWarning(lcSvgHandler) << "Could not create image from" << filename;
2024 return 0;
2025 }
2026
2027 if (image.format() == QImage::Format_ARGB32)
2028 image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
2029
2030 if (filenameType != LoadedFromFile)
2031 filename = QString();
2032 QSvgNode *img = new QSvgImage(parent,
2033 image,
2034 filename,
2035 QRectF(nx,
2036 ny,
2037 nwidth,
2038 nheight));
2039 return img;
2040}
2041
2042static QSvgNode *createLineNode(QSvgNode *parent,
2043 const QXmlStreamAttributes &attributes,
2044 QSvgHandler *)
2045{
2046 const QStringView x1 = attributes.value(QLatin1String("x1"));
2047 const QStringView y1 = attributes.value(QLatin1String("y1"));
2048 const QStringView x2 = attributes.value(QLatin1String("x2"));
2049 const QStringView y2 = attributes.value(QLatin1String("y2"));
2050 qreal nx1 = QSvgUtils::toDouble(x1);
2051 qreal ny1 = QSvgUtils::toDouble(y1);
2052 qreal nx2 = QSvgUtils::toDouble(x2);
2053 qreal ny2 = QSvgUtils::toDouble(y2);
2054
2055 QLineF lineBounds(nx1, ny1, nx2, ny2);
2056 QSvgNode *line = new QSvgLine(parent, lineBounds);
2057 return line;
2058}
2059
2060
2061static void parseBaseGradient(const QXmlStreamAttributes &attributes,
2062 QSvgGradientPaint *gradProp,
2063 QSvgHandler *handler)
2064{
2065 QStringView linkId = attributes.value(QLatin1String("xlink:href"));
2066 const QStringView trans = attributes.value(QLatin1String("gradientTransform"));
2067 const QStringView spread = attributes.value(QLatin1String("spreadMethod"));
2068 const QStringView units = attributes.value(QLatin1String("gradientUnits"));
2069 const QStringView colorStr = attributes.value(QLatin1String("color"));
2070 const QStringView colorOpacityStr = attributes.value(QLatin1String("color-opacity"));
2071
2072 QColor color;
2073 if (constructColor(colorStr, colorOpacityStr, color, handler)) {
2074 handler->popColor();
2075 handler->pushColor(color);
2076 }
2077
2078 QTransform matrix;
2079 QGradient *grad = gradProp->qgradient();
2080 linkId = idFromIRI(linkId);
2081
2082 if (!linkId.isEmpty()) {
2083 QSvgPaintServerSharedPtr paintServer = handler->document()->paintServer(linkId);
2084 if (paintServer && paintServer->type() == QSvgPaintServer::Type::Gradient) {
2085 QSvgGradientPaint *inherited =
2086 static_cast<QSvgGradientPaint*>(paintServer.get());
2087 if (!inherited->stopLink().isEmpty()) {
2088 gradProp->setStopLink(inherited->stopLink(), handler->document());
2089 } else {
2090 grad->setStops(inherited->qgradient()->stops());
2091 gradProp->setGradientStopsSet(inherited->gradientStopsSet());
2092 }
2093
2094 matrix = inherited->qtransform();
2095 } else {
2096 gradProp->setStopLink(linkId.toString(), handler->document());
2097 }
2098 }
2099
2100 if (!trans.isEmpty()) {
2101 matrix = parseTransformationMatrix(trans);
2102 gradProp->setTransform(matrix);
2103 } else if (!matrix.isIdentity()) {
2104 gradProp->setTransform(matrix);
2105 }
2106
2107 if (!spread.isEmpty()) {
2108 if (spread == QLatin1String("pad")) {
2109 grad->setSpread(QGradient::PadSpread);
2110 } else if (spread == QLatin1String("reflect")) {
2111 grad->setSpread(QGradient::ReflectSpread);
2112 } else if (spread == QLatin1String("repeat")) {
2113 grad->setSpread(QGradient::RepeatSpread);
2114 }
2115 }
2116
2117 if (units.isEmpty() || units == QLatin1String("objectBoundingBox")) {
2118 grad->setCoordinateMode(QGradient::ObjectMode);
2119 }
2120}
2121
2122
2123static QSvgPaintServerSharedPtr createLinearGradientNode(const QXmlStreamAttributes &attributes,
2124 QSvgHandler *handler)
2125{
2126 const QStringView x1 = attributes.value(QLatin1String("x1"));
2127 const QStringView y1 = attributes.value(QLatin1String("y1"));
2128 const QStringView x2 = attributes.value(QLatin1String("x2"));
2129 const QStringView y2 = attributes.value(QLatin1String("y2"));
2130
2131 qreal nx1 = 0.0;
2132 qreal ny1 = 0.0;
2133 qreal nx2 = 1.0;
2134 qreal ny2 = 0.0;
2135
2136 if (!x1.isEmpty())
2137 nx1 = convertToNumber(x1);
2138 if (!y1.isEmpty())
2139 ny1 = convertToNumber(y1);
2140 if (!x2.isEmpty())
2141 nx2 = convertToNumber(x2);
2142 if (!y2.isEmpty())
2143 ny2 = convertToNumber(y2);
2144
2145 auto grad = std::make_unique<QLinearGradient>(nx1, ny1, nx2, ny2);
2146 grad->setInterpolationMode(QGradient::ComponentInterpolation);
2147
2148 QSvgGradientPaintSharedPtr paintServer = std::make_shared<QSvgGradientPaint>(std::move(grad));
2149 parseBaseGradient(attributes, paintServer.get(), handler);
2150
2151 return paintServer;
2152}
2153
2154static bool parseMetadataNode(QSvgNode *parent,
2155 const QXmlStreamAttributes &attributes,
2156 QSvgHandler *)
2157{
2158 Q_UNUSED(parent); Q_UNUSED(attributes);
2159 return true;
2160}
2161
2162static bool parseMissingGlyphNode(QSvgStyleProperty *parent,
2163 const QXmlStreamAttributes &attributes,
2164 QSvgHandler *)
2165{
2166 if (parent->type() != QSvgStyleProperty::Font) {
2167 return false;
2168 }
2169
2170 QSvgFontStyle *style = static_cast<QSvgFontStyle*>(parent);
2171 QSvgFont *font = style->svgFont();
2172 return createSvgGlyph(font, attributes, true);
2173}
2174
2175static bool parseMpathNode(QSvgNode *parent,
2176 const QXmlStreamAttributes &attributes,
2177 QSvgHandler *)
2178{
2179 Q_UNUSED(parent); Q_UNUSED(attributes);
2180 return true;
2181}
2182
2183static bool parseMaskNode(QSvgNode *parent,
2184 const QXmlStreamAttributes &attributes,
2185 QSvgHandler *)
2186{
2187 Q_UNUSED(parent); Q_UNUSED(attributes);
2188 return true;
2189}
2190
2191static bool parseMarkerNode(QSvgNode *,
2192 const QXmlStreamAttributes &,
2193 QSvgHandler *)
2194{
2195 return true;
2196}
2197
2198static QSvgNode *createMaskNode(QSvgNode *parent,
2199 const QXmlStreamAttributes &attributes,
2200 QSvgHandler *handler)
2201{
2202 const QStringView x = attributes.value(QLatin1String("x"));
2203 const QStringView y = attributes.value(QLatin1String("y"));
2204 const QStringView width = attributes.value(QLatin1String("width"));
2205 const QStringView height = attributes.value(QLatin1String("height"));
2206 const QStringView mU = attributes.value(QLatin1String("maskUnits"));
2207 const QStringView mCU = attributes.value(QLatin1String("maskContentUnits"));
2208
2209 QtSvg::UnitTypes nmU = mU.contains(QLatin1String("userSpaceOnUse")) ?
2211
2212 QtSvg::UnitTypes nmCU = mCU.contains(QLatin1String("objectBoundingBox")) ?
2214
2215 bool ok;
2216 QSvgUtils::LengthType type;
2217
2218 QtSvg::UnitTypes nmUx = nmU;
2219 QtSvg::UnitTypes nmUy = nmU;
2220 QtSvg::UnitTypes nmUw = nmU;
2221 QtSvg::UnitTypes nmUh = nmU;
2222 qreal nx = QSvgUtils::parseLength(x, &type, &ok);
2223 nx = QSvgUtils::convertToPixels(nx, true, type);
2224 if (x.isEmpty() || !ok) {
2225 nx = -0.1;
2228 nx = nx / 100. * handler->document()->viewBox().width();
2229 } else if (type == QSvgUtils::LengthType::LT_PERCENT) {
2230 nx = nx / 100.;
2231 }
2232
2233 qreal ny = QSvgUtils::parseLength(y, &type, &ok);
2234 ny = QSvgUtils::convertToPixels(ny, true, type);
2235 if (y.isEmpty() || !ok) {
2236 ny = -0.1;
2239 ny = ny / 100. * handler->document()->viewBox().height();
2240 } else if (type == QSvgUtils::LengthType::LT_PERCENT) {
2241 ny = ny / 100.;
2242 }
2243
2244 qreal nwidth = QSvgUtils::parseLength(width, &type, &ok);
2245 nwidth = QSvgUtils::convertToPixels(nwidth, true, type);
2246 if (width.isEmpty() || !ok) {
2247 nwidth = 1.2;
2250 nwidth = nwidth / 100. * handler->document()->viewBox().width();
2251 } else if (type == QSvgUtils::LengthType::LT_PERCENT) {
2252 nwidth = nwidth / 100.;
2253 }
2254
2255 qreal nheight = QSvgUtils::parseLength(height, &type, &ok);
2256 nheight = QSvgUtils::convertToPixels(nheight, true, type);
2257 if (height.isEmpty() || !ok) {
2258 nheight = 1.2;
2261 nheight = nheight / 100. * handler->document()->viewBox().height();
2262 } else if (type == QSvgUtils::LengthType::LT_PERCENT) {
2263 nheight = nheight / 100.;
2264 }
2265
2266 QRectF bounds(nx, ny, nwidth, nheight);
2267 if (bounds.isEmpty())
2268 return nullptr;
2269
2270 QSvgNode *mask = new QSvgMask(parent, QSvgRectF(bounds, nmUx, nmUy, nmUw, nmUh), nmCU);
2271
2272 return mask;
2273}
2274
2275static void parseFilterBounds(const QXmlStreamAttributes &attributes, QSvgRectF *rect)
2276{
2277 const QStringView xStr = attributes.value(QLatin1String("x"));
2278 const QStringView yStr = attributes.value(QLatin1String("y"));
2279 const QStringView widthStr = attributes.value(QLatin1String("width"));
2280 const QStringView heightStr = attributes.value(QLatin1String("height"));
2281
2282 qreal x = 0;
2283 if (!xStr.isEmpty()) {
2284 QSvgUtils::LengthType type;
2285 x = QSvgUtils::parseLength(xStr, &type);
2286 if (type != QSvgUtils::LengthType::LT_PT) {
2287 x = QSvgUtils::convertToPixels(x, true, type);
2288 rect->setUnitX(QtSvg::UnitTypes::userSpaceOnUse);
2289 }
2291 x /= 100.;
2293 }
2294 rect->setX(x);
2295 }
2296 qreal y = 0;
2297 if (!yStr.isEmpty()) {
2298 QSvgUtils::LengthType type;
2299 y = QSvgUtils::parseLength(yStr, &type);
2300 if (type != QSvgUtils::LengthType::LT_PT) {
2301 y = QSvgUtils::convertToPixels(y, false, type);
2302 rect->setUnitY(QtSvg::UnitTypes::userSpaceOnUse);
2303 }
2305 y /= 100.;
2307 }
2308 rect->setY(y);
2309 }
2310 qreal width = 0;
2311 if (!widthStr.isEmpty()) {
2312 QSvgUtils::LengthType type;
2313 width = QSvgUtils::parseLength(widthStr, &type);
2314 if (type != QSvgUtils::LengthType::LT_PT) {
2315 width = QSvgUtils::convertToPixels(width, true, type);
2316 rect->setUnitW(QtSvg::UnitTypes::userSpaceOnUse);
2317 }
2319 width /= 100.;
2321 }
2322 rect->setWidth(width);
2323 }
2324 qreal height = 0;
2325 if (!heightStr.isEmpty()) {
2326 QSvgUtils::LengthType type;
2327 height = QSvgUtils::parseLength(heightStr, &type);
2328 if (type != QSvgUtils::LengthType::LT_PT) {
2329 height = QSvgUtils::convertToPixels(height, false, type);
2330 rect->setUnitH(QtSvg::UnitTypes::userSpaceOnUse);
2331 }
2333 height /= 100.;
2335 }
2336 rect->setHeight(height);
2337 }
2338}
2339
2340static QSvgNode *createFilterNode(QSvgNode *parent,
2341 const QXmlStreamAttributes &attributes,
2342 QSvgHandler *handler)
2343{
2344 const QStringView fU = attributes.value(QLatin1String("filterUnits"));
2345 const QStringView pU = attributes.value(QLatin1String("primitiveUnits"));
2346
2347 const QtSvg::UnitTypes filterUnits = fU.contains(QLatin1String("userSpaceOnUse")) ?
2349
2350 const QtSvg::UnitTypes primitiveUnits = pU.contains(QLatin1String("objectBoundingBox")) ?
2352
2353 // https://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
2354 // If ‘x’ or ‘y’ is not specified, the effect is as if a value of -10% were specified.
2355 // If ‘width’ or ‘height’ is not specified, the effect is as if a value of 120% were specified.
2356 QSvgRectF rect;
2357 if (filterUnits == QtSvg::UnitTypes::userSpaceOnUse) {
2358 qreal width = handler->document()->viewBox().width();
2359 qreal height = handler->document()->viewBox().height();
2360 rect = QSvgRectF(QRectF(-0.1 * width, -0.1 * height, 1.2 * width, 1.2 * height),
2363 } else {
2364 rect = QSvgRectF(QRectF(-0.1, -0.1, 1.2, 1.2),
2367 }
2368
2369 parseFilterBounds(attributes, &rect);
2370
2371 QSvgNode *filter = new QSvgFilterContainer(parent, rect, filterUnits, primitiveUnits);
2372 return filter;
2373}
2374
2375static void parseFilterAttributes(const QXmlStreamAttributes &attributes, QString *inString,
2376 QString *outString, QSvgRectF *rect)
2377{
2378 *inString = attributes.value(QLatin1String("in")).toString();
2379 *outString = attributes.value(QLatin1String("result")).toString();
2380
2381 // https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion
2382 // the default subregion is 0%,0%,100%,100%, where as a special-case the percentages are
2383 // relative to the dimensions of the filter region, thus making the the default filter primitive
2384 // subregion equal to the filter region.
2385 *rect = QSvgRectF(QRectF(0, 0, 1.0, 1.0),
2388 // if we recognize unit == unknown we use the filter as a reference instead of the item, see
2389 // QSvgFeFilterPrimitive::localSubRegion
2390
2391 parseFilterBounds(attributes, rect);
2392}
2393
2394static QSvgNode *createFeColorMatrixNode(QSvgNode *parent,
2395 const QXmlStreamAttributes &attributes,
2396 QSvgHandler *)
2397{
2398 const QStringView typeString = attributes.value(QLatin1String("type"));
2399 const QStringView valuesString = attributes.value(QLatin1String("values"));
2400
2401 QString inputString;
2402 QString outputString;
2403 QSvgRectF rect;
2404
2405 QSvgFeColorMatrix::ColorShiftType type;
2406 QSvgFeColorMatrix::Matrix values;
2407 values.fill(0);
2408
2409 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2410
2411 if (typeString.startsWith(QLatin1String("saturate")))
2412 type = QSvgFeColorMatrix::ColorShiftType::Saturate;
2413 else if (typeString.startsWith(QLatin1String("hueRotate")))
2414 type = QSvgFeColorMatrix::ColorShiftType::HueRotate;
2415 else if (typeString.startsWith(QLatin1String("luminanceToAlpha")))
2416 type = QSvgFeColorMatrix::ColorShiftType::LuminanceToAlpha;
2417 else
2418 type = QSvgFeColorMatrix::ColorShiftType::Matrix;
2419
2420 if (!valuesString.isEmpty()) {
2421 const auto valueStringList = splitWithDelimiter(valuesString);
2422 for (int i = 0, j = 0; i < qMin(20, valueStringList.size()); i++) {
2423 bool ok;
2424 qreal v = QSvgUtils::toDouble(valueStringList.at(i), &ok);
2425 if (ok) {
2426 values.data()[j] = v;
2427 j++;
2428 }
2429 }
2430 } else {
2431 values.setToIdentity();
2432 }
2433
2434 QSvgNode *filter = new QSvgFeColorMatrix(parent, inputString, outputString, rect,
2435 type, values);
2436 return filter;
2437}
2438
2439static QSvgNode *createFeGaussianBlurNode(QSvgNode *parent,
2440 const QXmlStreamAttributes &attributes,
2441 QSvgHandler *)
2442{
2443 const QStringView edgeModeString = attributes.value(QLatin1String("edgeMode"));
2444 const QStringView stdDeviationString = attributes.value(QLatin1String("stdDeviation"));
2445
2446 QString inputString;
2447 QString outputString;
2448 QSvgRectF rect;
2449
2450 QSvgFeGaussianBlur::EdgeMode edgemode = QSvgFeGaussianBlur::EdgeMode::Duplicate;
2451
2452 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2453 qreal stdDeviationX = 0;
2454 qreal stdDeviationY = 0;
2455 if (stdDeviationString.contains(QStringLiteral(" "))){
2456 stdDeviationX = qMax(0., QSvgUtils::toDouble(stdDeviationString.split(u" ").constFirst()));
2457 stdDeviationY = qMax(0., QSvgUtils::toDouble(stdDeviationString.split(u" ").constLast()));
2458 } else {
2459 stdDeviationY = stdDeviationX = qMax(0., QSvgUtils::toDouble(stdDeviationString));
2460 }
2461
2462 if (edgeModeString.startsWith(QLatin1String("wrap")))
2463 edgemode = QSvgFeGaussianBlur::EdgeMode::Wrap;
2464 else if (edgeModeString.startsWith(QLatin1String("none")))
2465 edgemode = QSvgFeGaussianBlur::EdgeMode::None;
2466
2467 QSvgNode *filter = new QSvgFeGaussianBlur(parent, inputString, outputString, rect,
2468 stdDeviationX, stdDeviationY, edgemode);
2469 return filter;
2470}
2471
2472static QSvgNode *createFeOffsetNode(QSvgNode *parent,
2473 const QXmlStreamAttributes &attributes,
2474 QSvgHandler *)
2475{
2476 QStringView dxString = attributes.value(QLatin1String("dx"));
2477 QStringView dyString = attributes.value(QLatin1String("dy"));
2478
2479 QString inputString;
2480 QString outputString;
2481 QSvgRectF rect;
2482
2483 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2484
2485 qreal dx = 0;
2486 if (!dxString.isEmpty()) {
2487 QSvgUtils::LengthType type;
2488 dx = QSvgUtils::parseLength(dxString, &type);
2489 if (type != QSvgUtils::LengthType::LT_PT)
2490 dx = QSvgUtils::convertToPixels(dx, true, type);
2491 }
2492
2493 qreal dy = 0;
2494 if (!dyString.isEmpty()) {
2495 QSvgUtils::LengthType type;
2496 dy = QSvgUtils::parseLength(dyString, &type);
2497 if (type != QSvgUtils::LengthType::LT_PT)
2498 dy = QSvgUtils::convertToPixels(dy, true, type);
2499 }
2500
2501 QSvgNode *filter = new QSvgFeOffset(parent, inputString, outputString, rect,
2502 dx, dy);
2503 return filter;
2504}
2505
2506static QSvgNode *createFeCompositeNode(QSvgNode *parent,
2507 const QXmlStreamAttributes &attributes,
2508 QSvgHandler *)
2509{
2510 const QStringView in2String = attributes.value(QLatin1String("in2"));
2511 const QStringView operatorString = attributes.value(QLatin1String("operator"));
2512 const QStringView k1String = attributes.value(QLatin1String("k1"));
2513 const QStringView k2String = attributes.value(QLatin1String("k2"));
2514 const QStringView k3String = attributes.value(QLatin1String("k3"));
2515 const QStringView k4String = attributes.value(QLatin1String("k4"));
2516
2517 QString inputString;
2518 QString outputString;
2519 QSvgRectF rect;
2520
2521 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2522
2523 QSvgFeComposite::Operator op = QSvgFeComposite::Operator::Over;
2524 if (operatorString.startsWith(QLatin1String("in")))
2525 op = QSvgFeComposite::Operator::In;
2526 else if (operatorString.startsWith(QLatin1String("out")))
2527 op = QSvgFeComposite::Operator::Out;
2528 else if (operatorString.startsWith(QLatin1String("atop")))
2529 op = QSvgFeComposite::Operator::Atop;
2530 else if (operatorString.startsWith(QLatin1String("xor")))
2531 op = QSvgFeComposite::Operator::Xor;
2532 else if (operatorString.startsWith(QLatin1String("lighter")))
2533 op = QSvgFeComposite::Operator::Lighter;
2534 else if (operatorString.startsWith(QLatin1String("arithmetic")))
2535 op = QSvgFeComposite::Operator::Arithmetic;
2536
2537 QVector4D k(0, 0, 0, 0);
2538
2539 if (op == QSvgFeComposite::Operator::Arithmetic) {
2540 bool ok;
2541 qreal v = QSvgUtils::toDouble(k1String, &ok);
2542 if (ok)
2543 k.setX(v);
2544 v = QSvgUtils::toDouble(k2String, &ok);
2545 if (ok)
2546 k.setY(v);
2547 v = QSvgUtils::toDouble(k3String, &ok);
2548 if (ok)
2549 k.setZ(v);
2550 v = QSvgUtils::toDouble(k4String, &ok);
2551 if (ok)
2552 k.setW(v);
2553 }
2554
2555 QSvgNode *filter = new QSvgFeComposite(parent, inputString, outputString, rect,
2556 in2String.toString(), op, k);
2557 return filter;
2558}
2559
2560
2561static QSvgNode *createFeMergeNode(QSvgNode *parent,
2562 const QXmlStreamAttributes &attributes,
2563 QSvgHandler *)
2564{
2565 QString inputString;
2566 QString outputString;
2567 QSvgRectF rect;
2568
2569 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2570
2571 QSvgNode *filter = new QSvgFeMerge(parent, inputString, outputString, rect);
2572 return filter;
2573}
2574
2575static QSvgNode *createFeFloodNode(QSvgNode *parent,
2576 const QXmlStreamAttributes &attributes,
2577 QSvgHandler *handler)
2578{
2579 QStringView colorStr = attributes.value(QLatin1String("flood-color"));
2580 const QStringView opacityStr = attributes.value(QLatin1String("flood-opacity"));
2581
2582 QColor color;
2583 if (!constructColor(colorStr, opacityStr, color, handler)) {
2584 color = QColor(Qt::black);
2585 if (opacityStr.isEmpty())
2586 color.setAlphaF(1.0);
2587 else
2588 setAlpha(opacityStr, &color);
2589 }
2590
2591 QString inputString;
2592 QString outputString;
2593 QSvgRectF rect;
2594
2595 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2596
2597 QSvgNode *filter = new QSvgFeFlood(parent, inputString, outputString, rect, color);
2598 return filter;
2599}
2600
2601static QSvgNode *createFeMergeNodeNode(QSvgNode *parent,
2602 const QXmlStreamAttributes &attributes,
2603 QSvgHandler *)
2604{
2605 QString inputString;
2606 QString outputString;
2607 QSvgRectF rect;
2608
2609 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2610
2611 QSvgNode *filter = new QSvgFeMergeNode(parent, inputString, outputString, rect);
2612 return filter;
2613}
2614
2615static QSvgNode *createFeBlendNode(QSvgNode *parent,
2616 const QXmlStreamAttributes &attributes,
2617 QSvgHandler *)
2618{
2619 const QStringView in2String = attributes.value(QLatin1String("in2"));
2620 const QStringView modeString = attributes.value(QLatin1String("mode"));
2621
2622 QString inputString;
2623 QString outputString;
2624 QSvgRectF rect;
2625
2626 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2627
2628 QSvgFeBlend::Mode mode = QSvgFeBlend::Mode::Normal;
2629 if (modeString.startsWith(QLatin1StringView("multiply")))
2630 mode = QSvgFeBlend::Mode::Multiply;
2631 else if (modeString.startsWith(QLatin1StringView("screen")))
2632 mode = QSvgFeBlend::Mode::Screen;
2633 else if (modeString.startsWith(QLatin1StringView("darken")))
2634 mode = QSvgFeBlend::Mode::Darken;
2635 else if (modeString.startsWith(QLatin1StringView("lighten")))
2636 mode = QSvgFeBlend::Mode::Lighten;
2637
2638 QSvgNode *filter = new QSvgFeBlend(parent, inputString, outputString, rect,
2639 in2String.toString(), mode);
2640 return filter;
2641}
2642
2643static QSvgNode *createFeUnsupportedNode(QSvgNode *parent,
2644 const QXmlStreamAttributes &attributes,
2645 QSvgHandler *)
2646{
2647 QString inputString;
2648 QString outputString;
2649 QSvgRectF rect;
2650
2651 parseFilterAttributes(attributes, &inputString, &outputString, &rect);
2652
2653 QSvgNode *filter = new QSvgFeUnsupported(parent, inputString, outputString, rect);
2654 return filter;
2655}
2656
2657static std::optional<QRectF> parseViewBox(QStringView str)
2658{
2659 QList<QStringView> viewBoxValues;
2660
2661 if (!str.isEmpty())
2662 viewBoxValues = splitWithDelimiter(str);
2663 if (viewBoxValues.size() == 4) {
2664 QSvgUtils::LengthType type;
2665 qreal x = QSvgUtils::parseLength(viewBoxValues.at(0).trimmed(), &type);
2666 qreal y = QSvgUtils::parseLength(viewBoxValues.at(1).trimmed(), &type);
2667 qreal w = QSvgUtils::parseLength(viewBoxValues.at(2).trimmed(), &type);
2668 qreal h = QSvgUtils::parseLength(viewBoxValues.at(3).trimmed(), &type);
2669 return QRectF(x, y, w, h);
2670 }
2671 return std::nullopt;
2672}
2673
2674static bool parseSymbolLikeAttributes(const QXmlStreamAttributes &attributes, QSvgHandler *handler,
2675 QRectF *rect, QRectF *viewBox, QPointF *refPoint,
2676 QSvgSymbolLike::PreserveAspectRatios *aspect,
2677 QSvgSymbolLike::Overflow *overflow,
2678 bool marker = false)
2679{
2680 const QStringView xStr = attributes.value(QLatin1String("x"));
2681 const QStringView yStr = attributes.value(QLatin1String("y"));
2682 const QStringView refXStr = attributes.value(QLatin1String("refX"));
2683 const QStringView refYStr = attributes.value(QLatin1String("refY"));
2684 const QStringView widthStr = attributes.value(marker ? QLatin1String("markerWidth")
2685 : QLatin1String("width"));
2686 const QStringView heightStr = attributes.value(marker ? QLatin1String("markerHeight")
2687 : QLatin1String("height"));
2688 const QStringView pAspectRStr = attributes.value(QLatin1String("preserveAspectRatio"));
2689 const QStringView overflowStr = attributes.value(QLatin1String("overflow"));
2690 const QStringView viewBoxStr = attributes.value(QLatin1String("viewBox"));
2691
2692
2693 qreal x = 0;
2694 if (!xStr.isEmpty()) {
2695 QSvgUtils::LengthType type;
2696 x = QSvgUtils::parseLength(xStr, &type);
2697 if (type != QSvgUtils::LengthType::LT_PT)
2698 x = QSvgUtils::convertToPixels(x, true, type);
2699 }
2700 qreal y = 0;
2701 if (!yStr.isEmpty()) {
2702 QSvgUtils::LengthType type;
2703 y = QSvgUtils::parseLength(yStr, &type);
2704 if (type != QSvgUtils::LengthType::LT_PT)
2705 y = QSvgUtils::convertToPixels(y, false, type);
2706 }
2707 qreal width = 0;
2708 if (!widthStr.isEmpty()) {
2709 QSvgUtils::LengthType type;
2710 width = QSvgUtils::parseLength(widthStr, &type);
2711 if (type != QSvgUtils::LengthType::LT_PT)
2712 width = QSvgUtils::convertToPixels(width, true, type);
2713 }
2714 qreal height = 0;
2715 if (!heightStr.isEmpty()) {
2716 QSvgUtils::LengthType type;
2717 height = QSvgUtils::parseLength(heightStr, &type);
2718 if (type != QSvgUtils::LengthType::LT_PT)
2719 height = QSvgUtils::convertToPixels(height, false, type);
2720 }
2721
2722 *rect = QRectF(x, y, width, height);
2723
2724 x = 0;
2725 if (!refXStr.isEmpty()) {
2726 QSvgUtils::LengthType type;
2727 x = QSvgUtils::parseLength(refXStr, &type);
2728 if (type != QSvgUtils::LengthType::LT_PT)
2729 x = QSvgUtils::convertToPixels(x, true, type);
2730 }
2731 y = 0;
2732 if (!refYStr.isEmpty()) {
2733 QSvgUtils::LengthType type;
2734 y = QSvgUtils::parseLength(refYStr, &type);
2735 if (type != QSvgUtils::LengthType::LT_PT)
2736 y = QSvgUtils::convertToPixels(y, false, type);
2737 }
2738 *refPoint = QPointF(x,y);
2739
2740 auto viewBoxResult = parseViewBox(viewBoxStr);
2741 if (viewBoxResult)
2742 *viewBox = *viewBoxResult;
2743 else if (width > 0 && height > 0)
2744 *viewBox = QRectF(0, 0, width, height);
2745 else
2746 *viewBox = handler->document()->viewBox();
2747
2748 if (viewBox->isNull())
2749 return false;
2750
2751 auto pAspectRStrs = pAspectRStr.split(u" ");
2752 QSvgSymbolLike::PreserveAspectRatio aspectX = QSvgSymbolLike::PreserveAspectRatio::xMid;
2753 QSvgSymbolLike::PreserveAspectRatio aspectY = QSvgSymbolLike::PreserveAspectRatio::yMid;
2754 QSvgSymbolLike::PreserveAspectRatio aspectMS = QSvgSymbolLike::PreserveAspectRatio::meet;
2755
2756 for (auto &pAStr : std::as_const(pAspectRStrs)) {
2757 if (pAStr.startsWith(QLatin1String("none"))) {
2758 aspectX = QSvgSymbolLike::PreserveAspectRatio::None;
2759 aspectY = QSvgSymbolLike::PreserveAspectRatio::None;
2760 }else {
2761 if (pAStr.startsWith(QLatin1String("xMin")))
2762 aspectX = QSvgSymbolLike::PreserveAspectRatio::xMin;
2763 else if (pAStr.startsWith(QLatin1String("xMax")))
2764 aspectX = QSvgSymbolLike::PreserveAspectRatio::xMax;
2765 if (pAStr.endsWith(QLatin1String("YMin")))
2766 aspectY = QSvgSymbolLike::PreserveAspectRatio::yMin;
2767 else if (pAStr.endsWith(QLatin1String("YMax")))
2768 aspectY = QSvgSymbolLike::PreserveAspectRatio::yMax;
2769 }
2770
2771 if (pAStr.endsWith(QLatin1String("slice")))
2772 aspectMS = QSvgSymbolLike::PreserveAspectRatio::slice;
2773 }
2774 *aspect = aspectX | aspectY | aspectMS;
2775
2776 // overflow is not limited to the symbol element but it is often found with the symbol element.
2777 // the symbol element makes little sense without the overflow attribute so it is added here.
2778 // if we decide to remove this from QSvgSymbol, the default value should be set to visible.
2779
2780 // The default value is visible but chrome uses default value hidden.
2781 *overflow = QSvgSymbolLike::Overflow::Hidden;
2782
2783 if (overflowStr.endsWith(QLatin1String("auto")))
2784 *overflow = QSvgSymbolLike::Overflow::Auto;
2785 else if (overflowStr.endsWith(QLatin1String("visible")))
2786 *overflow = QSvgSymbolLike::Overflow::Visible;
2787 else if (overflowStr.endsWith(QLatin1String("hidden")))
2788 *overflow = QSvgSymbolLike::Overflow::Hidden;
2789 else if (overflowStr.endsWith(QLatin1String("scroll")))
2790 *overflow = QSvgSymbolLike::Overflow::Scroll;
2791
2792 return true;
2793}
2794
2795static QSvgNode *createSymbolNode(QSvgNode *parent,
2796 const QXmlStreamAttributes &attributes,
2797 QSvgHandler *handler)
2798{
2799 QRectF rect, viewBox;
2800 QPointF refP;
2801 QSvgSymbolLike::PreserveAspectRatios aspect;
2802 QSvgSymbolLike::Overflow overflow;
2803
2804 if (!parseSymbolLikeAttributes(attributes, handler, &rect, &viewBox, &refP, &aspect, &overflow))
2805 return nullptr;
2806
2807 refP = QPointF(0, 0); //refX, refY is ignored in Symbol in Firefox and Chrome.
2808 QSvgNode *symbol = new QSvgSymbol(parent, rect, viewBox, refP, aspect, overflow);
2809 return symbol;
2810}
2811
2812static QSvgNode *createMarkerNode(QSvgNode *parent,
2813 const QXmlStreamAttributes &attributes,
2814 QSvgHandler *handler)
2815{
2816 QRectF rect, viewBox;
2817 QPointF refP;
2818 QSvgSymbolLike::PreserveAspectRatios aspect;
2819 QSvgSymbolLike::Overflow overflow;
2820
2821 const QStringView orientStr = attributes.value(QLatin1String("orient"));
2822 const QStringView markerUnitsStr = attributes.value(QLatin1String("markerUnits"));
2823
2824 qreal orientationAngle = 0;
2825 QSvgMarker::Orientation orientation;
2826 if (orientStr.startsWith(QLatin1String("auto-start-reverse")))
2827 orientation = QSvgMarker::Orientation::AutoStartReverse;
2828 else if (orientStr.startsWith(QLatin1String("auto")))
2829 orientation = QSvgMarker::Orientation::Auto;
2830 else {
2831 orientation = QSvgMarker::Orientation::Value;
2832 bool ok;
2833 qreal a;
2834 if (orientStr.endsWith(QLatin1String("turn")))
2835 a = 360. * QSvgUtils::toDouble(orientStr.mid(0, orientStr.length()-4), &ok);
2836 else if (orientStr.endsWith(QLatin1String("grad")))
2837 a = QSvgUtils::toDouble(orientStr.mid(0, orientStr.length()-4), &ok);
2838 else if (orientStr.endsWith(QLatin1String("rad")))
2839 a = 180. / M_PI * QSvgUtils::toDouble(orientStr.mid(0, orientStr.length()-3), &ok);
2840 else
2841 a = QSvgUtils::toDouble(orientStr, &ok);
2842 if (ok)
2843 orientationAngle = a;
2844 }
2845
2846 QSvgMarker::MarkerUnits markerUnits = QSvgMarker::MarkerUnits::StrokeWidth;
2847 if (markerUnitsStr.startsWith(QLatin1String("userSpaceOnUse")))
2848 markerUnits = QSvgMarker::MarkerUnits::UserSpaceOnUse;
2849
2850 if (!parseSymbolLikeAttributes(attributes, handler, &rect, &viewBox, &refP, &aspect, &overflow, true))
2851 return nullptr;
2852
2853 QSvgNode *marker = new QSvgMarker(parent, rect, viewBox, refP, aspect, overflow,
2854 orientation, orientationAngle, markerUnits);
2855 return marker;
2856}
2857
2858static QSvgNode *createPathNode(QSvgNode *parent,
2859 const QXmlStreamAttributes &attributes,
2860 QSvgHandler *handler)
2861{
2862 QStringView data = attributes.value(QLatin1String("d"));
2863
2864 std::optional<QPainterPath> qpath = QSvgUtils::parsePathDataFast(data,
2865 !handler->trustedSourceMode());
2866 if (!qpath) {
2867 qCWarning(lcSvgHandler, "Invalid path data; path truncated.");
2868 return nullptr;
2869 }
2870
2871 qpath.value().setFillRule(Qt::WindingFill);
2872 QSvgNode *path = new QSvgPath(parent, qpath.value());
2873 return path;
2874}
2875
2876static QSvgNode *createPolyNode(QSvgNode *parent,
2877 const QXmlStreamAttributes &attributes,
2878 bool createLine)
2879{
2880 QStringView pointsStr = attributes.value(QLatin1String("points"));
2881 const QList<qreal> points = parseNumbersList(&pointsStr);
2882 if (points.size() < 4)
2883 return nullptr;
2884 QPolygonF poly(points.size()/2);
2885 for (int i = 0; i < poly.size(); ++i)
2886 poly[i] = QPointF(points.at(2 * i), points.at(2 * i + 1));
2887 if (createLine)
2888 return new QSvgPolyline(parent, poly);
2889 else
2890 return new QSvgPolygon(parent, poly);
2891}
2892
2893static QSvgNode *createPolygonNode(QSvgNode *parent,
2894 const QXmlStreamAttributes &attributes,
2895 QSvgHandler *)
2896{
2897 return createPolyNode(parent, attributes, false);
2898}
2899
2900static QSvgNode *createPolylineNode(QSvgNode *parent,
2901 const QXmlStreamAttributes &attributes,
2902 QSvgHandler *)
2903{
2904 return createPolyNode(parent, attributes, true);
2905}
2906
2907static bool parsePrefetchNode(QSvgNode *parent,
2908 const QXmlStreamAttributes &attributes,
2909 QSvgHandler *)
2910{
2911 Q_UNUSED(parent); Q_UNUSED(attributes);
2912 return true;
2913}
2914
2915static QSvgPaintServerSharedPtr createRadialGradientNode(const QXmlStreamAttributes &attributes,
2916 QSvgHandler *handler)
2917{
2918 const QStringView cx = attributes.value(QLatin1String("cx"));
2919 const QStringView cy = attributes.value(QLatin1String("cy"));
2920 const QStringView r = attributes.value(QLatin1String("r"));
2921 const QStringView fx = attributes.value(QLatin1String("fx"));
2922 const QStringView fy = attributes.value(QLatin1String("fy"));
2923
2924 qreal ncx = 0.5;
2925 qreal ncy = 0.5;
2926 if (!cx.isEmpty())
2927 ncx = convertToNumber(cx);
2928 if (!cy.isEmpty())
2929 ncy = convertToNumber(cy);
2930
2931 qreal nr = 0.5;
2932 if (!r.isEmpty())
2933 nr = convertToNumber(r);
2934 if (nr <= 0.0)
2935 return nullptr;
2936
2937 qreal nfx = ncx;
2938 if (!fx.isEmpty())
2939 nfx = convertToNumber(fx);
2940 qreal nfy = ncy;
2941 if (!fy.isEmpty())
2942 nfy = convertToNumber(fy);
2943
2944 auto grad = std::make_unique<QRadialGradient>(ncx, ncy, nr, nfx, nfy, 0);
2945 grad->setInterpolationMode(QGradient::ComponentInterpolation);
2946
2947 QSvgGradientPaintSharedPtr paintServer = std::make_shared<QSvgGradientPaint>(std::move(grad));
2948 parseBaseGradient(attributes, paintServer.get(), handler);
2949
2950 return paintServer;
2951}
2952
2953static QSvgNode *createRectNode(QSvgNode *parent,
2954 const QXmlStreamAttributes &attributes,
2955 QSvgHandler *)
2956{
2957 const QStringView x = attributes.value(QLatin1String("x"));
2958 const QStringView y = attributes.value(QLatin1String("y"));
2959 const QStringView width = attributes.value(QLatin1String("width"));
2960 const QStringView height = attributes.value(QLatin1String("height"));
2961 const QStringView rx = attributes.value(QLatin1String("rx"));
2962 const QStringView ry = attributes.value(QLatin1String("ry"));
2963
2964 bool ok = true;
2965 QSvgUtils::LengthType type;
2966 qreal nwidth = QSvgUtils::parseLength(width, &type, &ok);
2967 if (!ok)
2968 return nullptr;
2969 nwidth = QSvgUtils::convertToPixels(nwidth, true, type);
2970 qreal nheight = QSvgUtils::parseLength(height, &type, &ok);
2971 if (!ok)
2972 return nullptr;
2973 nheight = QSvgUtils::convertToPixels(nheight, true, type);
2974 qreal nrx = QSvgUtils::toDouble(rx);
2975 qreal nry = QSvgUtils::toDouble(ry);
2976
2977 QRectF bounds(QSvgUtils::toDouble(x), QSvgUtils::toDouble(y), nwidth, nheight);
2978 if (bounds.isEmpty())
2979 return nullptr;
2980
2981 if (!rx.isEmpty() && ry.isEmpty())
2982 nry = nrx;
2983 else if (!ry.isEmpty() && rx.isEmpty())
2984 nrx = nry;
2985
2986 //9.2 The 'rect' element clearly specifies it
2987 // but the case might in fact be handled because
2988 // we draw rounded rectangles differently
2989 if (nrx > bounds.width()/2)
2990 nrx = bounds.width()/2;
2991 if (nry > bounds.height()/2)
2992 nry = bounds.height()/2;
2993
2994 //we draw rounded rect from 0...99
2995 //svg from 0...bounds.width()/2 so we're adjusting the
2996 //coordinates
2997 nrx *= (100/(bounds.width()/2));
2998 nry *= (100/(bounds.height()/2));
2999
3000 QSvgNode *rect = new QSvgRect(parent, bounds, nrx, nry);
3001 return rect;
3002}
3003
3004static bool parseScriptNode(QSvgNode *parent,
3005 const QXmlStreamAttributes &attributes,
3006 QSvgHandler *)
3007{
3008 Q_UNUSED(parent); Q_UNUSED(attributes);
3009 return true;
3010}
3011
3012static bool parseSetNode(QSvgNode *parent,
3013 const QXmlStreamAttributes &attributes,
3014 QSvgHandler *)
3015{
3016 Q_UNUSED(parent); Q_UNUSED(attributes);
3017 return true;
3018}
3019
3020static QSvgPaintServerSharedPtr createSolidColorNode(const QXmlStreamAttributes &attributes,
3021 QSvgHandler *handler)
3022{
3023 Q_UNUSED(attributes);
3024 QStringView solidColorStr = attributes.value(QLatin1String("solid-color"));
3025 QStringView solidOpacityStr = attributes.value(QLatin1String("solid-opacity"));
3026
3027 if (solidOpacityStr.isEmpty())
3028 solidOpacityStr = attributes.value(QLatin1String("opacity"));
3029
3030 QColor color;
3031 if (!constructColor(solidColorStr, solidOpacityStr, color, handler))
3032 return 0;
3033 std::shared_ptr<QSvgSolidColorPaint> paintServer = std::make_shared<QSvgSolidColorPaint>(color);
3034 return paintServer;
3035}
3036
3037static bool parseStopNode(QSvgPaintServer *paintServer,
3038 const QXmlStreamAttributes &attributes,
3039 QSvgHandler *handler)
3040{
3041 if (paintServer->type() != QSvgPaintServer::Type::Gradient)
3042 return false;
3043 QString nodeIdStr = someId(attributes);
3044 QString xmlClassStr = attributes.value(QLatin1String("class")).toString();
3045
3046 //### nasty hack because stop gradients are not in the rendering tree
3047 // we force a dummy node with the same id and class into a rendering
3048 // tree to figure out whether the selector has a style for it
3049 // QSvgStyleSelector should be coded in a way that could avoid it
3050 QSvgDummyNode dummy;
3051 dummy.setNodeId(nodeIdStr);
3052 dummy.setXmlClass(xmlClassStr);
3053
3054 QSvgAttributes attrs(attributes, handler);
3055
3056#ifndef QT_NO_CSSPARSER
3057 QXmlStreamAttributes cssAttributes;
3058 handler->cssHandler().styleLookup(&dummy, cssAttributes);
3059 attrs.setAttributes(cssAttributes, handler);
3060
3061 QXmlStreamAttributes styleCssAttributes;
3062 QStringView style = attributes.value(QLatin1String("style"));
3063 if (!style.isEmpty())
3064 handler->cssHandler().parseCSStoXMLAttrs(style.toString(), styleCssAttributes);
3065 attrs.setAttributes(styleCssAttributes, handler);
3066#endif
3067
3068 //TODO: Handle style parsing for gradients stop like the rest of the nodes.
3069 parseColor(&dummy, attrs, handler);
3070
3071 QSvgGradientPaint *gradientStyle = static_cast<QSvgGradientPaint*>(paintServer);
3072 QStringView colorStr = attrs.stopColor;
3073 QColor color;
3074
3075 bool ok = true;
3076 qreal offset = convertToNumber(attrs.offset, &ok);
3077 if (!ok)
3078 offset = 0.0;
3079
3080 if (!constructColor(colorStr, attrs.stopOpacity, color, handler)) {
3081 color = Qt::black;
3082 if (!attrs.stopOpacity.isEmpty())
3083 setAlpha(attrs.stopOpacity, &color);
3084 }
3085
3086 QGradient *grad = gradientStyle->qgradient();
3087
3088 offset = qMin(qreal(1), qMax(qreal(0), offset)); // Clamp to range [0, 1]
3089 QGradientStops stops;
3090 if (gradientStyle->gradientStopsSet()) {
3091 stops = grad->stops();
3092 // If the stop offset equals the one previously added, add an epsilon to make it greater.
3093 if (offset <= stops.back().first)
3094 offset = stops.back().first + FLT_EPSILON;
3095 }
3096
3097 // If offset is greater than one, it must be clamped to one.
3098 if (offset > 1.0) {
3099 if ((stops.size() == 1) || (stops.at(stops.size() - 2).first < 1.0 - FLT_EPSILON)) {
3100 stops.back().first = 1.0 - FLT_EPSILON;
3101 grad->setStops(stops);
3102 }
3103 offset = 1.0;
3104 }
3105
3106 grad->setColorAt(offset, color);
3107 gradientStyle->setGradientStopsSet(true);
3108 return true;
3109}
3110
3111static bool parseStyleNode(QSvgNode *parent,
3112 const QXmlStreamAttributes &attributes,
3113 QSvgHandler *handler)
3114{
3115 Q_UNUSED(parent);
3116#ifdef QT_NO_CSSPARSER
3117 Q_UNUSED(attributes);
3118 Q_UNUSED(handler);
3119#else
3120 const QStringView type = attributes.value(QLatin1String("type"));
3121 if (type.compare(QLatin1String("text/css"), Qt::CaseInsensitive) == 0 || type.isNull())
3122 handler->setInStyle(true);
3123#endif
3124
3125 return true;
3126}
3127
3128static QSvgNode *createSvgNode(QSvgNode *parent,
3129 const QXmlStreamAttributes &attributes,
3130 QSvgHandler *handler)
3131{
3132 Q_UNUSED(parent); Q_UNUSED(attributes);
3133
3134 QSvgDocument *node = new QSvgDocument(handler->options(), handler->animatorType());
3135 const QStringView widthStr = attributes.value(QLatin1String("width"));
3136 const QStringView heightStr = attributes.value(QLatin1String("height"));
3137 const QStringView viewBoxStr = attributes.value(QLatin1String("viewBox"));
3138
3139 QSvgUtils::LengthType type = QSvgUtils::LengthType::LT_PX; // FIXME: is the default correct?
3140 qreal width = 0;
3141 if (!widthStr.isEmpty()) {
3142 width = QSvgUtils::parseLength(widthStr, &type);
3143 if (type != QSvgUtils::LengthType::LT_PT)
3144 width = QSvgUtils::convertToPixels(width, true, type);
3145 node->setWidth(int(width), type == QSvgUtils::LengthType::LT_PERCENT);
3146 }
3147 qreal height = 0;
3148 if (!heightStr.isEmpty()) {
3149 height = QSvgUtils::parseLength(heightStr, &type);
3150 if (type != QSvgUtils::LengthType::LT_PT)
3151 height = QSvgUtils::convertToPixels(height, false, type);
3152 node->setHeight(int(height), type == QSvgUtils::LengthType::LT_PERCENT);
3153 }
3154
3155 auto viewBoxResult = parseViewBox(viewBoxStr);
3156 if (viewBoxResult) {
3157 node->setViewBox(*viewBoxResult);
3158 } else if (width && height) {
3159 if (type == QSvgUtils::LengthType::LT_PT) {
3160 width = QSvgUtils::convertToPixels(width, false, type);
3161 height = QSvgUtils::convertToPixels(height, false, type);
3162 }
3163 node->setViewBox(QRectF(0, 0, width, height));
3164 }
3165 handler->setDefaultCoordinateSystem(QSvgUtils::LengthType::LT_PX);
3166
3167 return node;
3168}
3169
3170static QSvgNode *createSwitchNode(QSvgNode *parent,
3171 const QXmlStreamAttributes &attributes,
3172 QSvgHandler *)
3173{
3174 Q_UNUSED(attributes);
3175 QSvgSwitch *node = new QSvgSwitch(parent);
3176 return node;
3177}
3178
3179static QSvgNode *createPatternNode(QSvgNode *parent,
3180 const QXmlStreamAttributes &attributes,
3181 QSvgHandler *handler)
3182{
3183 const QStringView x = attributes.value(QLatin1String("x"));
3184 const QStringView y = attributes.value(QLatin1String("y"));
3185 const QStringView width = attributes.value(QLatin1String("width"));
3186 const QStringView height = attributes.value(QLatin1String("height"));
3187 const QStringView patternUnits = attributes.value(QLatin1String("patternUnits"));
3188 const QStringView patternContentUnits = attributes.value(QLatin1String("patternContentUnits"));
3189 const QStringView patternTransform = attributes.value(QLatin1String("patternTransform"));
3190
3191 QtSvg::UnitTypes nPatternUnits = patternUnits.contains(QLatin1String("userSpaceOnUse")) ?
3193
3194 QtSvg::UnitTypes nPatternContentUnits = patternContentUnits.contains(QLatin1String("objectBoundingBox")) ?
3196
3197 const QStringView viewBoxStr = attributes.value(QLatin1String("viewBox"));
3198
3199 bool ok = false;
3200 QSvgUtils::LengthType type;
3201
3202 qreal nx = QSvgUtils::parseLength(x, &type, &ok);
3203 nx = QSvgUtils::convertToPixels(nx, true, type);
3204 if (!ok)
3205 nx = 0.0;
3206 else if (type == QSvgUtils::LengthType::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
3207 nx = (nx / 100.) * handler->document()->viewBox().width();
3208 else if (type == QSvgUtils::LengthType::LT_PERCENT)
3209 nx = nx / 100.;
3210
3211 qreal ny = QSvgUtils::parseLength(y, &type, &ok);
3212 ny = QSvgUtils::convertToPixels(ny, true, type);
3213 if (!ok)
3214 ny = 0.0;
3215 else if (type == QSvgUtils::LengthType::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
3216 ny = (ny / 100.) * handler->document()->viewBox().height();
3217 else if (type == QSvgUtils::LengthType::LT_PERCENT)
3218 ny = ny / 100.;
3219
3220 qreal nwidth = QSvgUtils::parseLength(width, &type, &ok);
3221 nwidth = QSvgUtils::convertToPixels(nwidth, true, type);
3222 if (!ok)
3223 nwidth = 0.0;
3224 else if (type == QSvgUtils::LengthType::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
3225 nwidth = (nwidth / 100.) * handler->document()->viewBox().width();
3226 else if (type == QSvgUtils::LengthType::LT_PERCENT)
3227 nwidth = nwidth / 100.;
3228
3229 qreal nheight = QSvgUtils::parseLength(height, &type, &ok);
3230 nheight = QSvgUtils::convertToPixels(nheight, true, type);
3231 if (!ok)
3232 nheight = 0.0;
3233 else if (type == QSvgUtils::LengthType::LT_PERCENT && nPatternUnits == QtSvg::UnitTypes::userSpaceOnUse)
3234 nheight = (nheight / 100.) * handler->document()->viewBox().height();
3235 else if (type == QSvgUtils::LengthType::LT_PERCENT)
3236 nheight = nheight / 100.;
3237
3238 QRectF viewBox;
3239 auto viewBoxResult = parseViewBox(viewBoxStr);
3240 if (viewBoxResult) {
3241 if (viewBoxResult->width() > 0 && viewBoxResult->height() > 0)
3242 viewBox = *viewBoxResult;
3243 }
3244
3245 QTransform matrix;
3246 if (!patternTransform.isEmpty())
3247 matrix = parseTransformationMatrix(patternTransform);
3248
3249 QRectF bounds(nx, ny, nwidth, nheight);
3250 if (bounds.isEmpty())
3251 return nullptr;
3252
3253 QSvgRectF patternRectF(bounds, nPatternUnits, nPatternUnits, nPatternUnits, nPatternUnits);
3254 QSvgPattern *node = new QSvgPattern(parent, patternRectF, viewBox, nPatternContentUnits, matrix);
3255
3256 // Create a style node for the Pattern.
3257 QSvgPaintServerSharedPtr prop = std::make_shared<QSvgPatternPaint>(node);
3258 handler->document()->addPaintServer(std::move(prop), someId(attributes));
3259
3260 return node;
3261}
3262
3263static bool parseTbreakNode(QSvgNode *parent,
3264 const QXmlStreamAttributes &,
3265 QSvgHandler *)
3266{
3267 if (parent->type() != QSvgNode::Textarea)
3268 return false;
3269 static_cast<QSvgText*>(parent)->addLineBreak();
3270 return true;
3271}
3272
3273static QSvgNode *createTextNode(QSvgNode *parent,
3274 const QXmlStreamAttributes &attributes,
3275 QSvgHandler *)
3276{
3277 const QStringView x = attributes.value(QLatin1String("x"));
3278 const QStringView y = attributes.value(QLatin1String("y"));
3279 //### editable and rotate not handled
3280 QSvgUtils::LengthType type;
3281 qreal nx = QSvgUtils::parseLength(x, &type);
3282 nx = QSvgUtils::convertToPixels(nx, true, type);
3283 qreal ny = QSvgUtils::parseLength(y, &type);
3284 ny = QSvgUtils::convertToPixels(ny, true, type);
3285
3286 QSvgNode *text = new QSvgText(parent, QPointF(nx, ny));
3287 return text;
3288}
3289
3290static QSvgNode *createTextAreaNode(QSvgNode *parent,
3291 const QXmlStreamAttributes &attributes,
3292 QSvgHandler *handler)
3293{
3294 QSvgText *node = static_cast<QSvgText *>(createTextNode(parent, attributes, handler));
3295 if (node) {
3296 QSvgUtils::LengthType type;
3297 qreal width = QSvgUtils::parseLength(attributes.value(QLatin1String("width")), &type);
3298 qreal height = QSvgUtils::parseLength(attributes.value(QLatin1String("height")), &type);
3299 node->setTextArea(QSizeF(width, height));
3300 }
3301 return node;
3302}
3303
3304static QSvgNode *createTspanNode(QSvgNode *parent,
3305 const QXmlStreamAttributes &,
3306 QSvgHandler *)
3307{
3308 return new QSvgTspan(parent);
3309}
3310
3311static QSvgNode *createUseNode(QSvgNode *parent,
3312 const QXmlStreamAttributes &attributes,
3313 QSvgHandler *handler)
3314{
3315 QStringView linkId = attributes.value(QLatin1String("xlink:href"));
3316 const QStringView xStr = attributes.value(QLatin1String("x"));
3317 const QStringView yStr = attributes.value(QLatin1String("y"));
3318 QSvgStructureNode *group = nullptr;
3319
3320 if (linkId.isEmpty())
3321 linkId = attributes.value(QLatin1String("href"));
3322 QString linkIdStr = idFromIRI(linkId).toString();
3323
3324 switch (parent->type()) {
3325 case QSvgNode::Doc:
3326 case QSvgNode::Defs:
3327 case QSvgNode::Group:
3328 case QSvgNode::Switch:
3329 case QSvgNode::Mask:
3330 case QSvgNode::Symbol:
3331 case QSvgNode::Marker:
3332 case QSvgNode::Pattern:
3333 group = static_cast<QSvgStructureNode*>(parent);
3334 break;
3335 default:
3336 break;
3337 }
3338
3339 if (group) {
3340 QPointF pt;
3341 if (!xStr.isNull() || !yStr.isNull()) {
3342 QSvgUtils::LengthType type;
3343 qreal nx = QSvgUtils::parseLength(xStr, &type);
3344 nx = QSvgUtils::convertToPixels(nx, true, type);
3345
3346 qreal ny = QSvgUtils::parseLength(yStr, &type);
3347 ny = QSvgUtils::convertToPixels(ny, true, type);
3348 pt = QPointF(nx, ny);
3349 }
3350
3351 QSvgNode *link = handler->document()->namedNode(linkIdStr);
3352 if (link) {
3353 if (parent->isDescendantOf(link))
3354 qCWarning(lcSvgHandler, "link %ls is recursive!", qUtf16Printable(linkIdStr));
3355
3356 return new QSvgUse(pt, parent, link);
3357 }
3358
3359 //delay link resolving, link might have not been created yet
3360 return new QSvgUse(pt, parent, linkIdStr);
3361 }
3362
3363 qCWarning(lcSvgHandler, "<use> element %ls in wrong context!", qUtf16Printable(linkIdStr));
3364 return 0;
3365}
3366
3367static QSvgNode *createVideoNode(QSvgNode *parent,
3368 const QXmlStreamAttributes &attributes,
3369 QSvgHandler *)
3370{
3371 Q_UNUSED(parent); Q_UNUSED(attributes);
3372 return 0;
3373}
3374
3375typedef QSvgNode *(*FactoryMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
3376
3377static FactoryMethod findGroupFactory(const QStringView name, QtSvg::Options options)
3378{
3379 if (name.isEmpty())
3380 return 0;
3381
3382 QStringView ref = name.mid(1);
3383 switch (name.at(0).unicode()) {
3384 case 'd':
3385 if (ref == QLatin1String("efs")) return createDefsNode;
3386 break;
3387 case 'f':
3388 if (ref == QLatin1String("ilter") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return createFilterNode;
3389 break;
3390 case 'g':
3391 if (ref.isEmpty()) return createGNode;
3392 break;
3393 case 'm':
3394 if (ref == QLatin1String("ask") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return createMaskNode;
3395 if (ref == QLatin1String("arker") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return createMarkerNode;
3396 break;
3397 case 's':
3398 if (ref == QLatin1String("vg")) return createSvgNode;
3399 if (ref == QLatin1String("witch")) return createSwitchNode;
3400 if (ref == QLatin1String("ymbol") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return createSymbolNode;
3401 break;
3402 case 'p':
3403 if (ref == QLatin1String("attern") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return createPatternNode;
3404 break;
3405 default:
3406 break;
3407 }
3408 return 0;
3409}
3410
3411static FactoryMethod findGraphicsFactory(const QStringView name, QtSvg::Options options)
3412{
3413 Q_UNUSED(options);
3414 if (name.isEmpty())
3415 return 0;
3416
3417 QStringView ref = name.mid(1);
3418 switch (name.at(0).unicode()) {
3419 case 'c':
3420 if (ref == QLatin1String("ircle")) return createCircleNode;
3421 break;
3422 case 'e':
3423 if (ref == QLatin1String("llipse")) return createEllipseNode;
3424 break;
3425 case 'i':
3426 if (ref == QLatin1String("mage")) return createImageNode;
3427 break;
3428 case 'l':
3429 if (ref == QLatin1String("ine")) return createLineNode;
3430 break;
3431 case 'p':
3432 if (ref == QLatin1String("ath")) return createPathNode;
3433 if (ref == QLatin1String("olygon")) return createPolygonNode;
3434 if (ref == QLatin1String("olyline")) return createPolylineNode;
3435 break;
3436 case 'r':
3437 if (ref == QLatin1String("ect")) return createRectNode;
3438 break;
3439 case 't':
3440 if (ref == QLatin1String("ext")) return createTextNode;
3441 if (ref == QLatin1String("extArea")) return createTextAreaNode;
3442 if (ref == QLatin1String("span")) return createTspanNode;
3443 break;
3444 case 'u':
3445 if (ref == QLatin1String("se")) return createUseNode;
3446 break;
3447 case 'v':
3448 if (ref == QLatin1String("ideo")) return createVideoNode;
3449 break;
3450 default:
3451 break;
3452 }
3453 return 0;
3454}
3455
3456static FactoryMethod findFilterFactory(const QStringView name, QtSvg::Options options)
3457{
3458 if (options.testFlag(QtSvg::Tiny12FeaturesOnly))
3459 return 0;
3460
3461 if (name.isEmpty())
3462 return 0;
3463
3464 if (!name.startsWith(QLatin1String("fe")))
3465 return 0;
3466
3467 if (name == QLatin1String("feMerge")) return createFeMergeNode;
3468 if (name == QLatin1String("feColorMatrix")) return createFeColorMatrixNode;
3469 if (name == QLatin1String("feGaussianBlur")) return createFeGaussianBlurNode;
3470 if (name == QLatin1String("feOffset")) return createFeOffsetNode;
3471 if (name == QLatin1String("feMergeNode")) return createFeMergeNodeNode;
3472 if (name == QLatin1String("feComposite")) return createFeCompositeNode;
3473 if (name == QLatin1String("feFlood")) return createFeFloodNode;
3474 if (name == QLatin1String("feBlend")) return createFeBlendNode;
3475
3476 static const QStringList unsupportedFilters = {
3477 QStringLiteral("feComponentTransfer"),
3478 QStringLiteral("feConvolveMatrix"),
3479 QStringLiteral("feDiffuseLighting"),
3480 QStringLiteral("feDisplacementMap"),
3481 QStringLiteral("feDropShadow"),
3482 QStringLiteral("feFuncA"),
3483 QStringLiteral("feFuncB"),
3484 QStringLiteral("feFuncG"),
3485 QStringLiteral("feFuncR"),
3486 QStringLiteral("feImage"),
3487 QStringLiteral("feMorphology"),
3488 QStringLiteral("feSpecularLighting"),
3489 QStringLiteral("feTile"),
3490 QStringLiteral("feTurbulence")
3491 };
3492
3493 if (unsupportedFilters.contains(name))
3494 return createFeUnsupportedNode;
3495
3496 return 0;
3497}
3498
3499typedef QSvgNode *(*AnimationMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
3500
3501static AnimationMethod findAnimationFactory(const QStringView name, QtSvg::Options options)
3502{
3503 if (name.isEmpty() || options.testFlag(QtSvg::DisableSMILAnimations))
3504 return 0;
3505
3506 QStringView ref = name.mid(1);
3507
3508 switch (name.at(0).unicode()) {
3509 case 'a':
3510 if (ref == QLatin1String("nimate")) return createAnimateNode;
3511 if (ref == QLatin1String("nimateColor")) return createAnimateColorNode;
3512 if (ref == QLatin1String("nimateMotion")) return createAnimateMotionNode;
3513 if (ref == QLatin1String("nimateTransform")) return createAnimateTransformNode;
3514 break;
3515 default:
3516 break;
3517 }
3518
3519 return 0;
3520}
3521
3522typedef bool (*ParseMethod)(QSvgNode *, const QXmlStreamAttributes &, QSvgHandler *);
3523
3524static ParseMethod findUtilFactory(const QStringView name, QtSvg::Options options)
3525{
3526 if (name.isEmpty())
3527 return 0;
3528
3529 QStringView ref = name.mid(1);
3530 switch (name.at(0).unicode()) {
3531 case 'a':
3532 if (ref.isEmpty()) return parseAnchorNode;
3533 if (ref == QLatin1String("udio")) return parseAudioNode;
3534 break;
3535 case 'd':
3536 if (ref == QLatin1String("iscard")) return parseDiscardNode;
3537 break;
3538 case 'f':
3539 if (ref == QLatin1String("oreignObject")) return parseForeignObjectNode;
3540 break;
3541 case 'h':
3542 if (ref == QLatin1String("andler")) return parseHandlerNode;
3543 if (ref == QLatin1String("kern")) return parseHkernNode;
3544 break;
3545 case 'm':
3546 if (ref == QLatin1String("etadata")) return parseMetadataNode;
3547 if (ref == QLatin1String("path")) return parseMpathNode;
3548 if (ref == QLatin1String("ask") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return parseMaskNode;
3549 if (ref == QLatin1String("arker") && !options.testFlag(QtSvg::Tiny12FeaturesOnly)) return parseMarkerNode;
3550 break;
3551 case 'p':
3552 if (ref == QLatin1String("refetch")) return parsePrefetchNode;
3553 break;
3554 case 's':
3555 if (ref == QLatin1String("cript")) return parseScriptNode;
3556 if (ref == QLatin1String("et")) return parseSetNode;
3557 if (ref == QLatin1String("tyle")) return parseStyleNode;
3558 break;
3559 case 't':
3560 if (ref == QLatin1String("break")) return parseTbreakNode;
3561 break;
3562 default:
3563 break;
3564 }
3565 return 0;
3566}
3567
3568typedef QSvgStyleProperty *(*StyleFactoryMethod)(const QXmlStreamAttributes &,
3569 QSvgHandler *);
3570
3571static StyleFactoryMethod findStyleFactoryMethod(const QStringView name)
3572{
3573 if (name.isEmpty())
3574 return 0;
3575
3576 QStringView ref = name.mid(1);
3577 switch (name.at(0).unicode()) {
3578 case 'f':
3579 if (ref == QLatin1String("ont")) return createFontNode;
3580 break;
3581 default:
3582 break;
3583 }
3584 return 0;
3585}
3586
3587typedef bool (*StyleParseMethod)(QSvgStyleProperty *,
3588 const QXmlStreamAttributes &,
3589 QSvgHandler *);
3590
3591static StyleParseMethod findStyleUtilFactoryMethod(const QStringView name)
3592{
3593 if (name.isEmpty())
3594 return 0;
3595
3596 QStringView ref = name.mid(1);
3597 switch (name.at(0).unicode()) {
3598 case 'f':
3599 if (ref == QLatin1String("ont-face")) return parseFontFaceNode;
3600 if (ref == QLatin1String("ont-face-name")) return parseFontFaceNameNode;
3601 if (ref == QLatin1String("ont-face-src")) return parseFontFaceSrcNode;
3602 if (ref == QLatin1String("ont-face-uri")) return parseFontFaceUriNode;
3603 break;
3604 case 'g':
3605 if (ref == QLatin1String("lyph")) return parseGlyphNode;
3606 break;
3607 case 'm':
3608 if (ref == QLatin1String("issing-glyph")) return parseMissingGlyphNode;
3609 break;
3610 default:
3611 break;
3612 }
3613 return 0;
3614}
3615
3616typedef QSvgPaintServerSharedPtr (*PaintServerFactoryMethod)(const QXmlStreamAttributes &,
3617 QSvgHandler *);
3618
3620{
3621 if (name.isEmpty())
3622 return nullptr;
3623
3624 QStringView ref = name.sliced(1);
3625 switch (name.at(0).unicode()) {
3626 case 'l':
3627 if (ref == QLatin1String("inearGradient")) return createLinearGradientNode;
3628 break;
3629 case 'r':
3630 if (ref == QLatin1String("adialGradient")) return createRadialGradientNode;
3631 break;
3632 case 's':
3633 if (ref == QLatin1String("olidColor")) return createSolidColorNode;
3634 break;
3635 default:
3636 break;
3637 }
3638 return nullptr;
3639}
3640
3641typedef bool (*PaintServerParseMethod)(QSvgPaintServer *,
3642 const QXmlStreamAttributes &,
3643 QSvgHandler *);
3644
3646{
3647 if (name.isEmpty())
3648 return 0;
3649
3650 QStringView ref = name.sliced(1);
3651 switch (name.at(0).unicode()) {
3652 case 's':
3653 if (ref == QLatin1String("top")) return parseStopNode;
3654 break;
3655 default:
3656 break;
3657 }
3658 return 0;
3659}
3660
3661QSvgHandler::QSvgHandler(QIODevice *device, QtSvg::Options options,
3662 QtSvg::AnimatorType type)
3663 : xml(new QXmlStreamReader(device))
3664 , m_ownsReader(true)
3665 , m_options(options)
3666 , m_animatorType(type)
3667{
3668 init();
3669}
3670
3671QSvgHandler::QSvgHandler(const QByteArray &data, QtSvg::Options options,
3672 QtSvg::AnimatorType type)
3673 : xml(new QXmlStreamReader(data))
3674 , m_ownsReader(true)
3675 , m_options(options)
3676 , m_animatorType(type)
3677{
3678 init();
3679}
3680
3681QSvgHandler::QSvgHandler(QXmlStreamReader *const reader, QtSvg::Options options,
3682 QtSvg::AnimatorType type)
3683 : xml(reader)
3684 , m_ownsReader(false)
3685 , m_options(options)
3686 , m_animatorType(type)
3687{
3688 init();
3689}
3690
3691void QSvgHandler::init()
3692{
3693 m_doc = 0;
3694 m_animEnd = 0;
3695 m_defaultCoords = QSvgUtils::LT_PX;
3696 m_defaultPen = QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
3697 m_defaultPen.setMiterLimit(4);
3698 parse();
3699}
3700
3701static bool detectPatternCycles(const QSvgNode *node, QList<const QSvgNode *> &linkable)
3702{
3703 const QSvgFillStyle *fillStyle = static_cast<const QSvgFillStyle*>
3704 (node->styleProperty(QSvgStyleProperty::Fill));
3705 if (fillStyle && fillStyle->paintServer()
3706 && fillStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
3707 QSvgPatternPaint *patternStyle = static_cast<QSvgPatternPaint *>(fillStyle->paintServer());
3708 if (linkable.contains(patternStyle->patternNode()))
3709 return true;
3710 }
3711
3712 const QSvgStrokeStyle *strokeStyle = static_cast<const QSvgStrokeStyle*>
3713 (node->styleProperty(QSvgStyleProperty::Stroke));
3714 if (strokeStyle && strokeStyle->paintServer()
3715 && strokeStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
3716 QSvgPatternPaint *patternStyle = static_cast<QSvgPatternPaint *>(strokeStyle->paintServer());
3717 if (linkable.contains(patternStyle->patternNode()))
3718 return true;
3719 }
3720
3721 return false;
3722}
3723
3724/* The function goes through a node and its descendants to
3725 * find any circular references in the parsed SVG file. It
3726 * is important for this to happen non-recursively to avoid
3727 * stack overflows.
3728 * The function maintains two lists of nodes. The list "linkable"
3729 * is used to track patterns and uses because these are the nodes
3730 * that can be referenced by other nodes.
3731 * Example :
3732 * <pattern id="pat1" />
3733 * <rect fill="url(#pat1)" />
3734 * </pattern>
3735 *
3736 * The other list of nodes is a stack to traverse the tree
3737 * non-recursively, the std::pair stored in the stack will
3738 * indicate whether a pattern or use has been visited and
3739 * added to the "linkable" list or not. If the bool is set to true,
3740 * this element can be popped out from the "linkable" list. */
3741static bool detectCycles(const QSvgNode *n)
3742{
3743 if (Q_UNLIKELY(!n))
3744 return false;
3745
3746 QList<const QSvgNode *> linkable;
3747 using NodeState = std::pair<const QSvgNode *, bool>;
3748 QStack<NodeState> nodes;
3749 nodes.push({n, false});
3750
3751 do {
3752 auto current = nodes.pop();
3753 if (current.second) {
3754 Q_ASSERT(!linkable.isEmpty() && current.first == linkable.back());
3755 linkable.pop_back();
3756 continue;
3757 }
3758
3759 switch (current.first->type()) {
3760 case QSvgNode::Doc:
3761 case QSvgNode::Group:
3762 case QSvgNode::Defs:
3763 case QSvgNode::Pattern:
3764 {
3765 if (current.first->type() == QSvgNode::Pattern) {
3766 linkable.append(current.first);
3767 nodes.push({current.first, true});
3768 }
3769 auto *g = static_cast<const QSvgStructureNode*>(current.first);
3770 for (auto it = g->renderers().crbegin(); it != g->renderers().crend(); it++)
3771 nodes.push({it->get(), false});
3772 }
3773 break;
3774 case QSvgNode::Use:
3775 {
3776 if (linkable.contains(current.first))
3777 return true;
3778 auto *u = static_cast<const QSvgUse*>(current.first);
3779 auto *target = u->link();
3780 if (target) {
3781 linkable.append(u);
3782 nodes.push({u, true});
3783 nodes.push({target, false});
3784 }
3785 }
3786 break;
3787 case QSvgNode::Rect:
3788 case QSvgNode::Ellipse:
3789 case QSvgNode::Circle:
3790 case QSvgNode::Line:
3791 case QSvgNode::Path:
3792 case QSvgNode::Polygon:
3793 case QSvgNode::Polyline:
3794 case QSvgNode::Tspan:
3795 if (detectPatternCycles(current.first, linkable))
3796 return true;
3797 break;
3798 default:
3799 break;
3800 }
3801 } while (!nodes.isEmpty());
3802 return false;
3803}
3804
3805static bool detectCyclesAndWarn(const QSvgNode *node) {
3806 const bool cycleFound = detectCycles(node);
3807 if (cycleFound)
3808 qCWarning(lcSvgHandler, "Cycles detected in SVG, document discarded.");
3809 return cycleFound;
3810}
3811
3812void QSvgHandler::parse()
3813{
3814 xml->setNamespaceProcessing(false);
3815#ifndef QT_NO_CSSPARSER
3816 m_inStyle = false;
3817#endif
3818 bool done = false;
3819 while (!xml->atEnd() && !done) {
3820 switch (xml->readNext()) {
3821 case QXmlStreamReader::StartElement:
3822 // he we could/should verify the namespaces, and simply
3823 // call m_skipNodes(Unknown) if we don't know the
3824 // namespace. We do support http://www.w3.org/2000/svg
3825 // but also http://www.w3.org/2000/svg-20000303-stylable
3826 // And if the document uses an external dtd, the reported
3827 // namespaceUri is empty. The only possible strategy at
3828 // this point is to do what everyone else seems to do and
3829 // ignore the reported namespaceUri completely.
3830 if (!startElement(xml->name(), xml->attributes())) {
3831 delete m_doc;
3832 m_doc = nullptr;
3833 return;
3834 }
3835 break;
3836 case QXmlStreamReader::EndElement:
3837 done = endElement(xml->name());
3838 break;
3839 case QXmlStreamReader::Characters:
3840 characters(xml->text());
3841 break;
3842 case QXmlStreamReader::ProcessingInstruction:
3843 processingInstruction(xml->processingInstructionTarget(), xml->processingInstructionData());
3844 break;
3845 default:
3846 break;
3847 }
3848 }
3849
3850 if (!m_doc)
3851 return;
3852
3853 resolvePaintServers();
3854 resolveNodes();
3855 if (detectCyclesAndWarn(m_doc)) {
3856 delete m_doc;
3857 m_doc = nullptr;
3858 }
3859}
3860
3861bool QSvgHandler::startElement(const QStringView localName,
3862 const QXmlStreamAttributes &attributes)
3863{
3864 QSvgNode *node = nullptr;
3865
3866 pushColorCopy();
3867
3868 /* The xml:space attribute may appear on any element. We do
3869 * a lookup by the qualified name here, but this is namespace aware, since
3870 * the XML namespace can only be bound to prefix "xml." */
3871 const QStringView xmlSpace(attributes.value(QLatin1String("xml:space")));
3872 if (xmlSpace.isNull()) {
3873 // This element has no xml:space attribute.
3874 m_whitespaceMode.push(m_whitespaceMode.isEmpty() ? QSvgText::Default : m_whitespaceMode.top());
3875 } else if (xmlSpace == QLatin1String("preserve")) {
3876 m_whitespaceMode.push(QSvgText::Preserve);
3877 } else if (xmlSpace == QLatin1String("default")) {
3878 m_whitespaceMode.push(QSvgText::Default);
3879 } else {
3880 const QByteArray msg = '"' + xmlSpace.toLocal8Bit()
3881 + "\" is an invalid value for attribute xml:space. "
3882 "Valid values are \"preserve\" and \"default\".";
3883 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3884 m_whitespaceMode.push(QSvgText::Default);
3885 }
3886
3887 if (!m_doc && localName != QLatin1String("svg"))
3888 return false;
3889
3890 if (m_doc && localName == QLatin1String("svg")) {
3891 m_skipNodes.push(Doc);
3892 qCWarning(lcSvgHandler) << "Skipping a nested svg element, because "
3893 "SVG Document must not contain nested svg elements in Svg Tiny 1.2";
3894 }
3895
3896 if (!m_skipNodes.isEmpty() && m_skipNodes.top() == Doc)
3897 return true;
3898
3899 if (FactoryMethod method = findGroupFactory(localName, options())) {
3900 //group
3901 if (!m_doc) {
3902 node = method(nullptr, attributes, this);
3903 if (node) {
3904 Q_ASSERT(node->type() == QSvgNode::Doc);
3905 m_doc = static_cast<QSvgDocument*>(node);
3906 }
3907 } else {
3908 switch (m_nodes.top()->type()) {
3909 case QSvgNode::Doc:
3910 case QSvgNode::Group:
3911 case QSvgNode::Defs:
3912 case QSvgNode::Switch:
3913 case QSvgNode::Mask:
3914 case QSvgNode::Symbol:
3915 case QSvgNode::Marker:
3916 case QSvgNode::Pattern:
3917 {
3918 node = method(m_nodes.top(), attributes, this);
3919 if (node) {
3920 QSvgStructureNode *group =
3921 static_cast<QSvgStructureNode*>(m_nodes.top());
3922 group->addChild(std::unique_ptr<QSvgNode>(node), someId(attributes));
3923 }
3924 }
3925 break;
3926 default:
3927 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
3928 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3929 break;
3930 }
3931 }
3932
3933 if (node) {
3934 parseCoreNode(node, attributes);
3935 parseStyle(node, attributes, this);
3936 if (node->type() == QSvgNode::Filter)
3937 m_toBeResolved.append(node);
3938 }
3939 } else if (FactoryMethod method = findGraphicsFactory(localName, options())) {
3940 //rendering element
3941 Q_ASSERT(!m_nodes.isEmpty());
3942 switch (m_nodes.top()->type()) {
3943 case QSvgNode::Doc:
3944 case QSvgNode::Group:
3945 case QSvgNode::Defs:
3946 case QSvgNode::Switch:
3947 case QSvgNode::Mask:
3948 case QSvgNode::Symbol:
3949 case QSvgNode::Marker:
3950 case QSvgNode::Pattern:
3951 {
3952 if (localName == QLatin1String("tspan")) {
3953 const QByteArray msg = QByteArrayLiteral("\'tspan\' element in wrong context.");
3954 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3955 break;
3956 }
3957 node = method(m_nodes.top(), attributes, this);
3958 if (node) {
3959 QSvgStructureNode *group =
3960 static_cast<QSvgStructureNode*>(m_nodes.top());
3961 group->addChild(std::unique_ptr<QSvgNode>(node), someId(attributes));
3962 }
3963 }
3964 break;
3965 case QSvgNode::Text:
3966 case QSvgNode::Textarea:
3967 if (localName == QLatin1String("tspan")) {
3968 node = method(m_nodes.top(), attributes, this);
3969 if (node) {
3970 static_cast<QSvgText *>(m_nodes.top())->addTspan(static_cast<QSvgTspan *>(node));
3971 }
3972 } else {
3973 const QByteArray msg = QByteArrayLiteral("\'text\' or \'textArea\' element contains invalid element type.");
3974 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3975 }
3976 break;
3977 default:
3978 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
3979 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
3980 break;
3981 }
3982
3983 if (node) {
3984 parseCoreNode(node, attributes);
3985 parseStyle(node, attributes, this);
3986 if (node->type() == QSvgNode::Text || node->type() == QSvgNode::Textarea) {
3987 static_cast<QSvgText *>(node)->setWhitespaceMode(m_whitespaceMode.top());
3988 } else if (node->type() == QSvgNode::Tspan) {
3989 static_cast<QSvgTspan *>(node)->setWhitespaceMode(m_whitespaceMode.top());
3990 } else if (node->type() == QSvgNode::Use) {
3991 auto useNode = static_cast<QSvgUse *>(node);
3992 if (!useNode->isResolved())
3993 m_toBeResolved.append(useNode);
3994 }
3995 }
3996 } else if (FactoryMethod method = findFilterFactory(localName, options())) {
3997 //filter nodes to be aded to be filtercontainer
3998 Q_ASSERT(!m_nodes.isEmpty());
3999 if (m_nodes.top()->type() == QSvgNode::Filter ||
4000 (m_nodes.top()->type() == QSvgNode::FeMerge && localName == QLatin1String("feMergeNode"))) {
4001 node = method(m_nodes.top(), attributes, this);
4002 if (node) {
4003 QSvgStructureNode *container =
4004 static_cast<QSvgStructureNode*>(m_nodes.top());
4005 container->addChild(std::unique_ptr<QSvgNode>(node), someId(attributes));
4006 }
4007 } else {
4008 const QByteArray msg = QByteArrayLiteral("Could not add child element to parent element because the types are incorrect.");
4009 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4010 }
4011 } else if (AnimationMethod method = findAnimationFactory(localName, options())) {
4012 Q_ASSERT(!m_nodes.isEmpty());
4013 node = method(m_nodes.top(), attributes, this);
4014 if (node) {
4015 QSvgAnimateNode *anim = static_cast<QSvgAnimateNode *>(node);
4016 if (anim->linkId().isEmpty())
4017 m_doc->animator()->appendAnimation(m_nodes.top(), anim);
4018 else if (m_doc->namedNode(anim->linkId()))
4019 m_doc->animator()->appendAnimation(m_doc->namedNode(anim->linkId()), anim);
4020 else
4021 m_toBeResolved.append(anim);
4022 }
4023 } else if (ParseMethod method = findUtilFactory(localName, options())) {
4024 Q_ASSERT(!m_nodes.isEmpty());
4025 if (!method(m_nodes.top(), attributes, this))
4026 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
4027 } else if (StyleFactoryMethod method = findStyleFactoryMethod(localName)) {
4028 QSvgStyleProperty *prop = method(attributes, this);
4029 if (prop) {
4030 m_style = prop;
4031 m_nodes.top()->appendStyleProperty(std::unique_ptr<QSvgStyleProperty>(prop));
4032 } else {
4033 const QByteArray msg = QByteArrayLiteral("Could not parse node: ") + localName.toLocal8Bit();
4034 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4035 }
4036 } else if (PaintServerFactoryMethod method = findPaintServerFactoryMethod(localName)) {
4037 QSvgPaintServerSharedPtr paintServer = method(attributes, this);
4038 if (paintServer) {
4039 m_paintServer = paintServer;
4040 m_doc->addPaintServer(std::move(paintServer), someId(attributes));
4041 } else {
4042 const QByteArray msg = QByteArrayLiteral("Could not parse node: ") + localName.toLocal8Bit();
4043 qCWarning(lcSvgHandler, "%s", prefixMessage(msg, xml).constData());
4044 }
4045 } else if (StyleParseMethod method = findStyleUtilFactoryMethod(localName)) {
4046 if (m_style) {
4047 if (!method(m_style, attributes, this))
4048 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
4049 }
4050 } else if (PaintServerParseMethod method = findPaintServerUtilFactoryMethod(localName)) {
4051 if (m_paintServer) {
4052 if (!method(m_paintServer.get(), attributes, this))
4053 qCWarning(lcSvgHandler, "%s", msgProblemParsing(localName, xml).constData());
4054 }
4055 } else {
4056 qCDebug(lcSvgHandler) << "Skipping unknown element" << localName;
4057 m_skipNodes.push(Unknown);
4058 return true;
4059 }
4060
4061 if (node) {
4062 m_nodes.push(node);
4063 m_skipNodes.push(Graphics);
4064 } else {
4065 //qDebug()<<"Skipping "<<localName;
4066 m_skipNodes.push(Style);
4067 }
4068 return true;
4069}
4070
4071bool QSvgHandler::endElement(const QStringView localName)
4072{
4073 CurrentNode node = m_skipNodes.top();
4074
4075 if (node == Doc && localName != QLatin1String("svg"))
4076 return false;
4077
4078 m_skipNodes.pop();
4079 m_whitespaceMode.pop();
4080
4081 popColor();
4082
4083 if (node == Unknown)
4084 return false;
4085
4086#ifdef QT_NO_CSSPARSER
4087 Q_UNUSED(localName);
4088#else
4089 if (m_inStyle && localName == QLatin1String("style"))
4090 m_inStyle = false;
4091#endif
4092
4093 if (node == Graphics)
4094 m_nodes.pop();
4095 else if (m_style && !m_skipNodes.isEmpty() && m_skipNodes.top() != Style)
4096 m_style = nullptr;
4097
4098 return ((localName == QLatin1String("svg")) && (node != Doc));
4099}
4100
4101void QSvgHandler::resolvePaintServers()
4102{
4103 for (QSvgStyleProperty *prop : std::as_const(m_unresolvedStyles)) {
4104 if (prop->type() == QSvgStyleProperty::Fill) {
4105 QSvgFillStyle *fill = static_cast<QSvgFillStyle *>(prop);
4106 QString id = fill->paintStyleId();
4107 QSvgPaintServerSharedPtr paintServer = m_doc->paintServer(id);
4108 if (paintServer) {
4109 fill->setPaintServer(std::move(paintServer));
4110 } else {
4111 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
4112 fill->setBrush(Qt::NoBrush);
4113 }
4114 } else if (prop->type() == QSvgStyleProperty::Stroke) {
4115 QSvgStrokeStyle *stroke = static_cast<QSvgStrokeStyle *>(prop);
4116 QString id = stroke->paintStyleId();
4117 QSvgPaintServerSharedPtr paintServer = m_doc->paintServer(id);
4118 if (paintServer) {
4119 stroke->setPaintServer(std::move(paintServer));
4120 } else {
4121 qCWarning(lcSvgHandler, "%s", msgCouldNotResolveProperty(id, xml).constData());
4122 stroke->setStroke(Qt::NoBrush);
4123 }
4124 }
4125 }
4126
4127 m_unresolvedStyles.clear();
4128}
4129
4130void QSvgHandler::resolveNodes()
4131{
4132 for (QSvgNode *node : std::as_const(m_toBeResolved)) {
4133 if (node->type() == QSvgNode::Use) {
4134 QSvgUse *useNode = static_cast<QSvgUse *>(node);
4135 const auto parent = useNode->parent();
4136 if (!parent)
4137 continue;
4138
4139 QSvgNode::Type t = parent->type();
4140 if (t != QSvgNode::Doc && t != QSvgNode::Defs && t != QSvgNode::Group && t != QSvgNode::Switch)
4141 continue;
4142
4143 QSvgNode *link = m_doc->namedNode(useNode->linkId());
4144 if (!link) {
4145 qCWarning(lcSvgHandler, "link #%s is undefined!", qPrintable(useNode->linkId()));
4146 continue;
4147 }
4148
4149 if (useNode->parent()->isDescendantOf(link))
4150 qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(useNode->linkId()));
4151
4152 useNode->setLink(link);
4153 } else if (node->type() == QSvgNode::Filter) {
4154 QSvgFilterContainer *filter = static_cast<QSvgFilterContainer *>(node);
4155 for (auto &renderer : filter->renderers()) {
4156 const QSvgFeFilterPrimitive *primitive = QSvgFeFilterPrimitive::castToFilterPrimitive(renderer.get());
4157 if (!primitive || primitive->type() == QSvgNode::FeUnsupported) {
4158 filter->setSupported(false);
4159 break;
4160 }
4161 }
4162 } else if (node->type() == QSvgNode::AnimateTransform || node->type() == QSvgNode::AnimateColor) {
4163 QSvgAnimateNode *anim = static_cast<QSvgAnimateNode *>(node);
4164 QSvgNode *targetNode = m_doc->namedNode(anim->linkId());
4165 if (targetNode) {
4166 m_doc->animator()->appendAnimation(targetNode, anim);
4167 } else {
4168 qCWarning(lcSvgHandler, "Cannot find target for link #%s!",
4169 qPrintable(anim->linkId()));
4170 delete anim;
4171 }
4172 }
4173 }
4174 m_toBeResolved.clear();
4175}
4176
4177bool QSvgHandler::characters(const QStringView str)
4178{
4179#ifndef QT_NO_CSSPARSER
4180 if (m_inStyle) {
4181 m_cssHandler.parseStyleSheet(str);
4182 return true;
4183 }
4184#endif
4185 if (m_skipNodes.isEmpty() || m_skipNodes.top() == Unknown || m_nodes.isEmpty())
4186 return true;
4187
4188 if (m_nodes.top()->type() == QSvgNode::Text || m_nodes.top()->type() == QSvgNode::Textarea) {
4189 static_cast<QSvgText*>(m_nodes.top())->addText(str);
4190 } else if (m_nodes.top()->type() == QSvgNode::Tspan) {
4191 static_cast<QSvgTspan*>(m_nodes.top())->addText(str);
4192 }
4193
4194 return true;
4195}
4196
4197QIODevice *QSvgHandler::device() const
4198{
4199 return xml->device();
4200}
4201
4202QSvgDocument *QSvgHandler::document() const
4203{
4204 return m_doc;
4205}
4206
4207QSvgUtils::LengthType QSvgHandler::defaultCoordinateSystem() const
4208{
4209 return m_defaultCoords;
4210}
4211
4212void QSvgHandler::setDefaultCoordinateSystem(QSvgUtils::LengthType type)
4213{
4214 m_defaultCoords = type;
4215}
4216
4217void QSvgHandler::pushColor(const QColor &color)
4218{
4219 m_colorStack.push(color);
4220 m_colorTagCount.push(1);
4221}
4222
4223void QSvgHandler::pushColorCopy()
4224{
4225 if (m_colorTagCount.size())
4226 ++m_colorTagCount.top();
4227 else
4228 pushColor(Qt::black);
4229}
4230
4231void QSvgHandler::popColor()
4232{
4233 if (m_colorTagCount.size()) {
4234 if (!--m_colorTagCount.top()) {
4235 m_colorStack.pop();
4236 m_colorTagCount.pop();
4237 }
4238 }
4239}
4240
4241QColor QSvgHandler::currentColor() const
4242{
4243 if (!m_colorStack.isEmpty())
4244 return m_colorStack.top();
4245 else
4246 return QColor(0, 0, 0);
4247}
4248
4249void QSvgHandler::pushUnresolvedStyle(QSvgStyleProperty *prop)
4250{
4251 m_unresolvedStyles.append(prop);
4252}
4253
4254#ifndef QT_NO_CSSPARSER
4255
4256void QSvgHandler::setInStyle(bool b)
4257{
4258 m_inStyle = b;
4259}
4260
4261bool QSvgHandler::inStyle() const
4262{
4263 return m_inStyle;
4264}
4265
4266QSvgCssHandler &QSvgHandler::cssHandler()
4267{
4268 return m_cssHandler;
4269}
4270
4271#endif // QT_NO_CSSPARSER
4272
4273bool QSvgHandler::processingInstruction(const QStringView target, const QStringView data)
4274{
4275#ifdef QT_NO_CSSPARSER
4276 Q_UNUSED(target);
4277 Q_UNUSED(data);
4278#else
4279 if (target == QLatin1String("xml-stylesheet")) {
4280 static const QRegularExpression rx(QStringLiteral("type=\\\"(.+)\\\""),
4281 QRegularExpression::InvertedGreedinessOption);
4282 QRegularExpressionMatchIterator iter = rx.globalMatchView(data);
4283 bool isCss = false;
4284 while (iter.hasNext()) {
4285 QRegularExpressionMatch match = iter.next();
4286 QString type = match.captured(1);
4287 if (type.toLower() == QLatin1String("text/css")) {
4288 isCss = true;
4289 }
4290 }
4291
4292 if (isCss) {
4293 static const QRegularExpression rx(QStringLiteral("href=\\\"(.+)\\\""),
4294 QRegularExpression::InvertedGreedinessOption);
4295 QRegularExpressionMatch match = rx.matchView(data);
4296 QString addr = match.captured(1);
4297 QFileInfo fi(addr);
4298 //qDebug()<<"External CSS file "<<fi.absoluteFilePath()<<fi.exists();
4299 if (fi.exists()) {
4300 QFile file(fi.absoluteFilePath());
4301 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
4302 return true;
4303 }
4304 QByteArray cssData = file.readAll();
4305 QString css = QString::fromUtf8(cssData);
4306 m_cssHandler.parseStyleSheet(css);
4307 }
4308
4309 }
4310 }
4311#endif
4312
4313 return true;
4314}
4315
4316void QSvgHandler::setAnimPeriod(int start, int end)
4317{
4318 Q_UNUSED(start);
4319 m_animEnd = qMax(end, m_animEnd);
4320}
4321
4322int QSvgHandler::animationDuration() const
4323{
4324 return m_animEnd;
4325}
4326
4327QSvgHandler::~QSvgHandler()
4328{
4329 if(m_ownsReader)
4330 delete xml;
4331}
4332
4333QT_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:1683
#define QStringLiteral(str)
Definition qstring.h:1825
#define qUtf16Printable(string)
Definition qstring.h:1695
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