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

source code of qtsvg/src/svg/qsvghandler.cpp