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