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