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