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