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