1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsvgvisitorimpl_p.h"
5#include "qquickgenerator_p.h"
6#include "qquicknodeinfo_p.h"
7
8#include <private/qsvgvisitor_p.h>
9
10#include <QString>
11#include <QPainter>
12#include <QTextDocument>
13#include <QTextLayout>
14#include <QMatrix4x4>
15#include <QQuickItem>
16
17#include <private/qquickshape_p.h>
18#include <private/qquicktext_p.h>
19#include <private/qquicktranslate_p.h>
20#include <private/qquickitem_p.h>
21
22#include <private/qquickimagebase_p_p.h>
23#include <private/qquickimage_p.h>
24#include <private/qsgcurveprocessor_p.h>
25
26#include <private/qquadpath_p.h>
27
28#include <QtCore/private/qstringiterator_p.h>
29
30#include "utils_p.h"
31#include <QtCore/qloggingcategory.h>
32
33#include <QtSvg/private/qsvgstyle_p.h>
34
35QT_BEGIN_NAMESPACE
36
37Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations, "qt.quick.vectorimage.animations")
38
39using namespace Qt::StringLiterals;
40
41class QSvgStyleResolver
42{
43public:
44 QSvgStyleResolver()
45 {
46 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
47 m_dummyPainter.begin(&m_dummyImage);
48 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
49 defaultPen.setMiterLimit(4);
50 m_dummyPainter.setPen(defaultPen);
51 m_dummyPainter.setBrush(Qt::black);
52 }
53
54 ~QSvgStyleResolver()
55 {
56 m_dummyPainter.end();
57 }
58
59 QPainter& painter() { return m_dummyPainter; }
60 QSvgExtraStates& states() { return m_svgState; }
61
62 QColor currentFillColor() const
63 {
64 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
65 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
66 return QColor(QColorConstants::Transparent);
67 }
68
69 QColor fillColor;
70 fillColor = m_dummyPainter.brush().color();
71 fillColor.setAlphaF(m_svgState.fillOpacity);
72
73 return fillColor;
74 }
75
76 qreal currentFillOpacity() const
77 {
78 return m_svgState.fillOpacity;
79 }
80
81 const QGradient *currentStrokeGradient() const
82 {
83 QBrush brush = m_dummyPainter.pen().brush();
84 if (brush.style() == Qt::LinearGradientPattern
85 || brush.style() == Qt::RadialGradientPattern
86 || brush.style() == Qt::ConicalGradientPattern) {
87 return brush.gradient();
88 }
89 return nullptr;
90 }
91
92 const QGradient *currentFillGradient() const
93 {
94 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
95 return m_dummyPainter.brush().gradient();
96 return nullptr;
97 }
98
99 QTransform currentFillTransform() const
100 {
101 return m_dummyPainter.brush().transform();
102 }
103
104 QColor currentStrokeColor() const
105 {
106 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
107 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
108 return QColor(QColorConstants::Transparent);
109 }
110
111 QColor strokeColor;
112 strokeColor = m_dummyPainter.pen().brush().color();
113 strokeColor.setAlphaF(m_svgState.strokeOpacity);
114
115 return strokeColor;
116 }
117
118 static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
119 {
120 QGradient grad = gradient;
121 QGradientStops stops;
122 for (auto &stop : grad.stops()) {
123 stop.second.setAlphaF(stop.second.alphaF() * opacity);
124 stops.append(t: stop);
125 }
126
127 grad.setStops(stops);
128
129 return grad;
130 }
131
132 float currentStrokeWidth() const
133 {
134 float penWidth = m_dummyPainter.pen().widthF();
135 return penWidth ? penWidth : 1;
136 }
137
138 QPen currentStroke() const
139 {
140 return m_dummyPainter.pen();
141 }
142
143protected:
144 QPainter m_dummyPainter;
145 QImage m_dummyImage;
146 QSvgExtraStates m_svgState;
147};
148
149Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
150
151namespace {
152inline bool isPathContainer(const QSvgStructureNode *node)
153{
154 bool foundPath = false;
155 for (const auto *child : node->renderers()) {
156 switch (child->type()) {
157 // nodes that shouldn't go inside Shape{}
158 case QSvgNode::Switch:
159 case QSvgNode::Doc:
160 case QSvgNode::Group:
161 case QSvgNode::AnimateColor:
162 case QSvgNode::AnimateTransform:
163 case QSvgNode::Use:
164 case QSvgNode::Video:
165 case QSvgNode::Image:
166 case QSvgNode::Textarea:
167 case QSvgNode::Text:
168 case QSvgNode::Tspan:
169 //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
170 return false;
171
172 // nodes that could go inside Shape{}
173 case QSvgNode::Defs:
174 break;
175
176 // nodes that are done as pure ShapePath{}
177 case QSvgNode::Rect:
178 case QSvgNode::Circle:
179 case QSvgNode::Ellipse:
180 case QSvgNode::Line:
181 case QSvgNode::Path:
182 case QSvgNode::Polygon:
183 case QSvgNode::Polyline:
184 {
185 if (!child->style().transform.isDefault()) {
186 //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
187 return false;
188 }
189 const QList<QSvgAbstractAnimation *> animations = child->document()->animator()->animationsForNode(node: child);
190 if (!animations.isEmpty()) {
191 //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform animation";
192 return false;
193 }
194 foundPath = true;
195 break;
196 }
197 default:
198 qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
199 break;
200 }
201 }
202 //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
203 return foundPath;
204}
205
206static QString capStyleName(Qt::PenCapStyle style)
207{
208 QString styleName;
209
210 switch (style) {
211 case Qt::SquareCap:
212 styleName = QStringLiteral("squarecap");
213 break;
214 case Qt::FlatCap:
215 styleName = QStringLiteral("flatcap");
216 break;
217 case Qt::RoundCap:
218 styleName = QStringLiteral("roundcap");
219 break;
220 default:
221 break;
222 }
223
224 return styleName;
225}
226
227static QString joinStyleName(Qt::PenJoinStyle style)
228{
229 QString styleName;
230
231 switch (style) {
232 case Qt::MiterJoin:
233 styleName = QStringLiteral("miterjoin");
234 break;
235 case Qt::BevelJoin:
236 styleName = QStringLiteral("beveljoin");
237 break;
238 case Qt::RoundJoin:
239 styleName = QStringLiteral("roundjoin");
240 break;
241 case Qt::SvgMiterJoin:
242 styleName = QStringLiteral("svgmiterjoin");
243 break;
244 default:
245 break;
246 }
247
248 return styleName;
249}
250
251static QString dashArrayString(QList<qreal> dashArray)
252{
253 if (dashArray.isEmpty())
254 return QString();
255
256 QString dashArrayString;
257 QTextStream stream(&dashArrayString);
258
259 for (int i = 0; i < dashArray.length() - 1; i++) {
260 qreal value = dashArray[i];
261 stream << value << ", ";
262 }
263
264 stream << dashArray.last();
265
266 return dashArrayString;
267}
268};
269
270QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName,
271 QQuickGenerator *generator,
272 bool assumeTrustedSource)
273 : m_svgFileName(svgFileName)
274 , m_generator(generator)
275 , m_assumeTrustedSource(assumeTrustedSource)
276{
277}
278
279bool QSvgVisitorImpl::traverse()
280{
281 if (!m_generator) {
282 qCDebug(lcQuickVectorImage) << "No valid QQuickGenerator is set. Genration will stop";
283 return false;
284 }
285
286 QtSvg::Options options;
287 if (m_assumeTrustedSource)
288 options.setFlag(flag: QtSvg::AssumeTrustedSource);
289
290 auto *doc = QSvgTinyDocument::load(file: m_svgFileName, options);
291 if (!doc) {
292 qCDebug(lcQuickVectorImage) << "Not a valid Svg File : " << m_svgFileName;
293 return false;
294 }
295
296 QSvgVisitor::traverse(node: doc);
297 return true;
298}
299
300void QSvgVisitorImpl::visitNode(const QSvgNode *node)
301{
302 handleBaseNodeSetup(node);
303
304 NodeInfo info;
305 fillCommonNodeInfo(node, info);
306 fillAnimationInfo(node, info);
307
308 m_generator->generateNode(info);
309
310 handleBaseNodeEnd(node);
311}
312
313void QSvgVisitorImpl::visitImageNode(const QSvgImage *node)
314{
315 // TODO: this requires proper asset management.
316 handleBaseNodeSetup(node);
317
318 ImageNodeInfo info;
319 fillCommonNodeInfo(node, info);
320 fillAnimationInfo(node, info);
321 info.image = node->image();
322 info.rect = node->rect();
323 info.externalFileReference = node->filename();
324
325 m_generator->generateImageNode(info);
326
327 handleBaseNodeEnd(node);
328}
329
330void QSvgVisitorImpl::visitRectNode(const QSvgRect *node)
331{
332 QRectF rect = node->rect();
333 QPointF rads = node->radius();
334 // This is using Qt::RelativeSize semantics: percentage of half rect size
335 qreal x1 = rect.left();
336 qreal x2 = rect.right();
337 qreal y1 = rect.top();
338 qreal y2 = rect.bottom();
339
340 qreal rx = rads.x() * rect.width() / 200;
341 qreal ry = rads.y() * rect.height() / 200;
342 QPainterPath p;
343
344 p.moveTo(x: x1 + rx, y: y1);
345 p.lineTo(x: x2 - rx, y: y1);
346 // qCDebug(lcQuickVectorGraphics) << "Line1" << x2 - rx << y1;
347 p.arcTo(x: x2 - rx * 2, y: y1, w: rx * 2, h: ry * 2, startAngle: 90, arcLength: -90); // ARC to x2, y1 + ry
348 // qCDebug(lcQuickVectorGraphics) << "p1" << p;
349
350 p.lineTo(x: x2, y: y2 - ry);
351 p.arcTo(x: x2 - rx * 2, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 0, arcLength: -90); // ARC to x2 - rx, y2
352
353 p.lineTo(x: x1 + rx, y: y2);
354 p.arcTo(x: x1, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 270, arcLength: -90); // ARC to x1, y2 - ry
355
356 p.lineTo(x: x1, y: y1 + ry);
357 p.arcTo(x: x1, y: y1, w: rx * 2, h: ry * 2, startAngle: 180, arcLength: -90); // ARC to x1 + rx, y1
358
359 handlePathNode(node, path: p);
360}
361
362void QSvgVisitorImpl::visitEllipseNode(const QSvgEllipse *node)
363{
364 QRectF rect = node->rect();
365
366 QPainterPath p;
367 p.addEllipse(rect);
368
369 handlePathNode(node, path: p);
370}
371
372void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
373{
374 handlePathNode(node, path: node->path());
375}
376
377void QSvgVisitorImpl::visitLineNode(const QSvgLine *node)
378{
379 QPainterPath p;
380 p.moveTo(p: node->line().p1());
381 p.lineTo(p: node->line().p2());
382 handlePathNode(node, path: p);
383}
384
385void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
386{
387 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: true);
388 handlePathNode(node, path: p);
389}
390
391void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
392{
393 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: false);
394 handlePathNode(node, path: p);
395}
396
397QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
398{
399 QString cssDescription;
400 if (gradient->type() == QGradient::LinearGradient) {
401 const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(gradient);
402
403 cssDescription += " -qt-foreground: qlineargradient("_L1;
404 cssDescription += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
405 cssDescription += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
406 cssDescription += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
407 cssDescription += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
408 } else if (gradient->type() == QGradient::RadialGradient) {
409 const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(gradient);
410
411 cssDescription += " -qt-foreground: qradialgradient("_L1;
412 cssDescription += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
413 cssDescription += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
414 cssDescription += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
415 cssDescription += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
416 cssDescription += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
417 } else {
418 const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(gradient);
419
420 cssDescription += " -qt-foreground: qconicalgradient("_L1;
421 cssDescription += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
422 cssDescription += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
423 cssDescription += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
424 }
425
426 const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
427 cssDescription += "coordinatemode:"_L1;
428 cssDescription += coordinateModes.at(i: int(gradient->coordinateMode()));
429 cssDescription += u',';
430
431 const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
432 cssDescription += "spread:"_L1;
433 cssDescription += spreads.at(i: int(gradient->spread()));
434
435 for (const QGradientStop &stop : gradient->stops()) {
436 cssDescription += ",stop:"_L1;
437 cssDescription += QString::number(stop.first);
438 cssDescription += u' ';
439 cssDescription += stop.second.name(format: QColor::HexArgb);
440 }
441
442 cssDescription += ");"_L1;
443
444 return cssDescription;
445}
446
447QString QSvgVisitorImpl::colorCssDescription(QColor color)
448{
449 QString cssDescription;
450 cssDescription += QStringLiteral("rgba(");
451 cssDescription += QString::number(color.red()) + QStringLiteral(",");
452 cssDescription += QString::number(color.green()) + QStringLiteral(",");
453 cssDescription += QString::number(color.blue()) + QStringLiteral(",");
454 cssDescription += QString::number(color.alphaF()) + QStringLiteral(")");
455
456 return cssDescription;
457}
458
459namespace {
460
461 // Simple class for representing the SVG font as a font engine
462 // We use the Proxy font engine type, which is currently unused and does not map to
463 // any specific font engine
464 // (The QSvgFont object must outlive the engine.)
465 class QSvgFontEngine : public QFontEngine
466 {
467 public:
468 QSvgFontEngine(const QSvgFont *font, qreal size);
469
470 QFontEngine *cloneWithSize(qreal size) const override;
471
472 glyph_t glyphIndex(uint ucs4) const override;
473 int stringToCMap(const QChar *str,
474 int len,
475 QGlyphLayout *glyphs,
476 int *nglyphs,
477 ShaperFlags flags) const override;
478
479 void addGlyphsToPath(glyph_t *glyphs,
480 QFixedPoint *positions,
481 int nGlyphs,
482 QPainterPath *path,
483 QTextItem::RenderFlags flags) override;
484
485 glyph_metrics_t boundingBox(glyph_t glyph) override;
486
487 void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
488 QFixed ascent() const override;
489 QFixed capHeight() const override;
490 QFixed descent() const override;
491 QFixed leading() const override;
492 qreal maxCharWidth() const override;
493 qreal minLeftBearing() const override;
494 qreal minRightBearing() const override;
495
496 QFixed emSquareSize() const override;
497
498 private:
499 const QSvgFont *m_font;
500 };
501
502 QSvgFontEngine::QSvgFontEngine(const QSvgFont *font, qreal size)
503 : QFontEngine(Proxy)
504 , m_font(font)
505 {
506 fontDef.pixelSize = size;
507 fontDef.families = QStringList(m_font->m_familyName);
508 }
509
510 QFixed QSvgFontEngine::emSquareSize() const
511 {
512 return QFixed::fromReal(r: m_font->m_unitsPerEm);
513 }
514
515 glyph_t QSvgFontEngine::glyphIndex(uint ucs4) const
516 {
517 if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(key: QChar(ushort(ucs4))))
518 return glyph_t(ucs4);
519
520 return 0;
521 }
522
523 int QSvgFontEngine::stringToCMap(const QChar *str,
524 int len,
525 QGlyphLayout *glyphs,
526 int *nglyphs,
527 ShaperFlags flags) const
528 {
529 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
530 if (*nglyphs < len) {
531 *nglyphs = len;
532 return -1;
533 }
534
535 int ucs4Length = 0;
536 QStringIterator it(str, str + len);
537 while (it.hasNext()) {
538 char32_t ucs4 = it.next();
539 glyph_t index = glyphIndex(ucs4);
540 glyphs->glyphs[ucs4Length++] = index;
541 }
542
543 *nglyphs = ucs4Length;
544 glyphs->numGlyphs = ucs4Length;
545
546 if (!(flags & GlyphIndicesOnly))
547 recalcAdvances(glyphs, flags);
548
549 return *nglyphs;
550 }
551
552 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
553 QFixedPoint *positions,
554 int nGlyphs,
555 QPainterPath *path,
556 QTextItem::RenderFlags flags)
557 {
558 Q_UNUSED(flags);
559 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
560 for (int i = 0; i < nGlyphs; ++i) {
561 glyph_t index = glyphs[i];
562 if (index > 0) {
563 QPointF position = positions[i].toPointF();
564 QPainterPath glyphPath = m_font->m_glyphs.value(key: QChar(ushort(index))).m_path;
565
566 QTransform xform;
567 xform.translate(dx: position.x(), dy: position.y());
568 xform.scale(sx: scale, sy: -scale);
569 glyphPath = xform.map(p: glyphPath);
570 path->addPath(path: glyphPath);
571 }
572 }
573 }
574
575 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
576 {
577 glyph_metrics_t ret;
578 ret.x = 0; // left bearing
579 ret.y = -ascent();
580 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
581 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph)));
582 ret.width = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale);
583 ret.height = ascent() + descent();
584 return ret;
585 }
586
587 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size) const
588 {
589 QSvgFontEngine *otherEngine = new QSvgFontEngine(m_font, size);
590 return otherEngine;
591 }
592
593 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags) const
594 {
595 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
596 for (int i = 0; i < glyphLayout->numGlyphs; i++) {
597 glyph_t glyph = glyphLayout->glyphs[i];
598 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph)));
599 glyphLayout->advances[i] = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale);
600 }
601 }
602
603 QFixed QSvgFontEngine::ascent() const
604 {
605 return QFixed::fromReal(r: fontDef.pixelSize);
606 }
607
608 QFixed QSvgFontEngine::capHeight() const
609 {
610 return ascent();
611 }
612 QFixed QSvgFontEngine::descent() const
613 {
614 return QFixed{};
615 }
616
617 QFixed QSvgFontEngine::leading() const
618 {
619 return QFixed{};
620 }
621
622 qreal QSvgFontEngine::maxCharWidth() const
623 {
624 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
625 return m_font->m_horizAdvX * scale;
626 }
627
628 qreal QSvgFontEngine::minLeftBearing() const
629 {
630 return 0.0;
631 }
632
633 qreal QSvgFontEngine::minRightBearing() const
634 {
635 return 0.0;
636 }
637}
638
639static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)
640{
641 if (index == 0)
642 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index: 1, t: 0.0);
643 else
644 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, t: 1.0);
645
646 return property->interpolatedValue();
647}
648
649void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
650{
651 handleBaseNodeSetup(node);
652 const bool isTextArea = node->type() == QSvgNode::Textarea;
653
654 QString text;
655 const QSvgFont *svgFont = styleResolver->states().svgFont;
656 bool needsRichText = false;
657 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
658 const QGradient *mainGradient = styleResolver->currentFillGradient();
659
660 QFontEngine *fontEngine = nullptr;
661 if (svgFont != nullptr) {
662 fontEngine = new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize());
663 fontEngine->ref.ref();
664 }
665
666#if QT_CONFIG(texthtmlparser)
667 bool needsPathNode = mainGradient != nullptr
668 || svgFont != nullptr
669 || styleResolver->currentStrokeGradient() != nullptr;
670#endif
671 for (const auto *tspan : node->tspans()) {
672 if (!tspan) {
673 text += QStringLiteral("<br>");
674 continue;
675 }
676
677 // Note: We cannot get the font directly from the style, since this does
678 // not apply the weight, since this is relative and depends on current state.
679 handleBaseNodeSetup(node: tspan);
680 QFont font = styleResolver->painter().font();
681
682 QString styleTagContent;
683
684 if ((font.resolveMask() & QFont::FamilyResolved)
685 || (font.resolveMask() & QFont::FamiliesResolved)) {
686 styleTagContent += QStringLiteral("font-family: %1;").arg(a: font.family());
687 }
688
689 if (font.resolveMask() & QFont::WeightResolved
690 && font.weight() != QFont::Normal
691 && font.weight() != QFont::Bold) {
692 styleTagContent += QStringLiteral("font-weight: %1;").arg(a: int(font.weight()));
693 }
694
695 if (font.resolveMask() & QFont::SizeResolved) {
696 // Pixel size stored as point size in SVG parser
697 styleTagContent += QStringLiteral("font-size: %1px;").arg(a: int(font.pointSizeF()));
698 }
699
700 if (font.resolveMask() & QFont::CapitalizationResolved
701 && font.capitalization() == QFont::SmallCaps) {
702 styleTagContent += QStringLiteral("font-variant: small-caps;");
703 }
704
705 if (styleResolver->currentFillGradient() != nullptr
706 && styleResolver->currentFillGradient() != mainGradient) {
707 const QGradient grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
708 styleTagContent += gradientCssDescription(gradient: &grad) + u';';
709#if QT_CONFIG(texthtmlparser)
710 needsPathNode = true;
711#endif
712 }
713
714 const QColor currentStrokeColor = styleResolver->currentStrokeColor();
715 if (currentStrokeColor.alpha() > 0) {
716 QString strokeColor = colorCssDescription(color: currentStrokeColor);
717 styleTagContent += QStringLiteral("-qt-stroke-color:%1;").arg(a: strokeColor);
718 styleTagContent += QStringLiteral("-qt-stroke-width:%1px;").arg(a: styleResolver->currentStrokeWidth());
719 styleTagContent += QStringLiteral("-qt-stroke-dasharray:%1;").arg(a: dashArrayString(dashArray: styleResolver->currentStroke().dashPattern()));
720 styleTagContent += QStringLiteral("-qt-stroke-dashoffset:%1;").arg(a: styleResolver->currentStroke().dashOffset());
721 styleTagContent += QStringLiteral("-qt-stroke-lineCap:%1;").arg(a: capStyleName(style: styleResolver->currentStroke().capStyle()));
722 styleTagContent += QStringLiteral("-qt-stroke-lineJoin:%1;").arg(a: joinStyleName(style: styleResolver->currentStroke().joinStyle()));
723 if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
724 styleTagContent += QStringLiteral("-qt-stroke-miterlimit:%1;").arg(a: styleResolver->currentStroke().miterLimit());
725#if QT_CONFIG(texthtmlparser)
726 needsPathNode = true;
727#endif
728 }
729
730 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
731 styleTagContent += QStringLiteral("white-space: pre-wrap;");
732
733 QString content = tspan->text().toHtmlEscaped();
734 content.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
735 content.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
736
737 bool fontTag = false;
738 if (!tspan->style().fill.isDefault()) {
739 auto &b = tspan->style().fill->qbrush();
740 qCDebug(lcQuickVectorImage) << "tspan FILL:" << b;
741 if (b.style() != Qt::NoBrush)
742 {
743 if (qFuzzyCompare(p1: b.color().alphaF() + 1.0, p2: 2.0))
744 {
745 QString spanColor = b.color().name();
746 fontTag = !spanColor.isEmpty();
747 if (fontTag)
748 text += QStringLiteral("<font color=\"%1\">").arg(a: spanColor);
749 } else {
750 QString spanColor = colorCssDescription(color: b.color());
751 styleTagContent += QStringLiteral("color:%1").arg(a: spanColor);
752 }
753 }
754 }
755
756 needsRichText = needsRichText || !styleTagContent.isEmpty();
757 if (!styleTagContent.isEmpty())
758 text += QStringLiteral("<span style=\"%1\">").arg(a: styleTagContent);
759
760 if (font.resolveMask() & QFont::WeightResolved && font.bold())
761 text += QStringLiteral("<b>");
762
763 if (font.resolveMask() & QFont::StyleResolved && font.italic())
764 text += QStringLiteral("<i>");
765
766 if (font.resolveMask() & QFont::CapitalizationResolved) {
767 switch (font.capitalization()) {
768 case QFont::AllLowercase:
769 content = content.toLower();
770 break;
771 case QFont::AllUppercase:
772 content = content.toUpper();
773 break;
774 case QFont::Capitalize:
775 // ### We need to iterate over the string and do the title case conversion,
776 // since this is not part of QString.
777 qCWarning(lcQuickVectorImage) << "Title case not implemented for tspan";
778 break;
779 default:
780 break;
781 }
782 }
783 text += content;
784 if (fontTag)
785 text += QStringLiteral("</font>");
786
787 if (font.resolveMask() & QFont::StyleResolved && font.italic())
788 text += QStringLiteral("</i>");
789
790 if (font.resolveMask() & QFont::WeightResolved && font.bold())
791 text += QStringLiteral("</b>");
792
793 if (!styleTagContent.isEmpty())
794 text += QStringLiteral("</span>");
795
796 handleBaseNodeEnd(node: tspan);
797 }
798
799 if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() != nullptr))
800 text = QStringLiteral("<span style=\"white-space: pre-wrap\">") + text + QStringLiteral("</span>");
801
802 QFont font = styleResolver->painter().font();
803 if (font.pixelSize() <= 0 && font.pointSize() > 0)
804 font.setPixelSize(font.pointSize()); // Pixel size stored as point size by SVG parser
805
806 font.setHintingPreference(QFont::PreferNoHinting);
807
808#if QT_CONFIG(texthtmlparser)
809 if (needsPathNode) {
810 QTextDocument document;
811 document.setHtml(text);
812 if (isTextArea && node->size().width() > 0)
813 document.setTextWidth(node->size().width());
814 document.setDefaultFont(font);
815 document.pageCount(); // Force layout
816
817 QTextBlock block = document.firstBlock();
818 while (block.isValid()) {
819 QTextLayout *lout = block.layout();
820
821 if (lout != nullptr) {
822 QRectF boundingRect = lout->boundingRect();
823
824 // If this block has requested the current SVG font, we override it
825 // (note that this limits the text to one svg font, but this is also the case
826 // in the QPainter at the moment, and needs a more centralized solution in Qt Svg
827 // first)
828 QFont blockFont = block.charFormat().font();
829 if (svgFont != nullptr
830 && blockFont.family() == svgFont->m_familyName) {
831 QRawFont rawFont;
832 QRawFontPrivate *rawFontD = QRawFontPrivate::get(font: rawFont);
833 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
834
835 lout->setRawFont(rawFont);
836 }
837
838 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt) {
839 PathNodeInfo info;
840 fillCommonNodeInfo(node, info);
841 fillPathAnimationInfo(node, info);
842 auto fillStyle = node->style().fill;
843 if (fillStyle)
844 info.fillRule = fillStyle->fillRule();
845
846 if (fmt.hasProperty(propertyId: QTextCharFormat::ForegroundBrush)) {
847 info.fillColor.setDefaultValue(fmt.foreground().color());
848 if (fmt.foreground().gradient() != nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
849 info.grad = *fmt.foreground().gradient();
850 } else {
851 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
852 }
853
854 info.painterPath = p;
855
856 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
857 QPen pen;
858 if (fmt.hasProperty(propertyId: QTextCharFormat::TextOutline)) {
859 pen = fmt.textOutline();
860 if (strokeGradient == nullptr) {
861 info.strokeStyle = StrokeStyle::fromPen(p: pen);
862 info.strokeStyle.color.setDefaultValue(pen.color());
863 }
864 } else {
865 pen = styleResolver->currentStroke();
866 if (strokeGradient == nullptr) {
867 info.strokeStyle = StrokeStyle::fromPen(p: pen);
868 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
869 }
870 }
871
872 if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr)
873 info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
874
875 info.fillTransform = styleResolver->currentFillTransform();
876
877 m_generator->generatePath(info, overrideBoundingRect: boundingRect);
878
879 if (strokeGradient != nullptr) {
880 PathNodeInfo strokeInfo;
881 fillCommonNodeInfo(node, info&: strokeInfo);
882 fillPathAnimationInfo(node, info&: strokeInfo);
883
884 strokeInfo.grad = *strokeGradient;
885
886 QPainterPathStroker stroker(pen);
887 strokeInfo.painterPath = stroker.createStroke(path: p);
888 m_generator->generatePath(info: strokeInfo, overrideBoundingRect: boundingRect);
889 }
890 };
891
892 qreal baselineOffset = -QFontMetricsF(font).ascent();
893 if (lout->lineCount() > 0 && lout->lineAt(i: 0).isValid())
894 baselineOffset = -lout->lineAt(i: 0).ascent();
895
896 const QPointF baselineTranslation(0.0, baselineOffset);
897 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
898 QList<QPainterPath> paths;
899 for (const QGlyphRun &glyphRun : glyphRuns) {
900 QRawFont font = glyphRun.rawFont();
901 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
902 QList<QPointF> positions = glyphRun.positions();
903
904 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
905 quint32 glyphIndex = glyphIndexes.at(i: j);
906 const QPointF &pos = positions.at(i: j);
907
908 QPainterPath p = font.pathForGlyph(glyphIndex);
909 p.translate(offset: pos + node->position() + baselineTranslation);
910 if (styleResolver->states().textAnchor == Qt::AlignHCenter)
911 p.translate(offset: QPointF(-0.5 * width, 0));
912 else if (styleResolver->states().textAnchor == Qt::AlignRight)
913 p.translate(offset: QPointF(-width, 0));
914 paths.append(t: p);
915 }
916 }
917
918 return paths;
919 };
920
921 QList<QTextLayout::FormatRange> formats = block.textFormats();
922 for (int i = 0; i < formats.size(); ++i) {
923 QTextLayout::FormatRange range = formats.at(i);
924
925 QList<QGlyphRun> glyphRuns = lout->glyphRuns(from: range.start, length: range.length);
926 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
927 for (const QPainterPath &path : paths)
928 addPathForFormat(path, range.format);
929 }
930 }
931
932 block = block.next();
933 }
934 } else
935#endif
936 {
937 TextNodeInfo info;
938 fillCommonNodeInfo(node, info);
939 fillAnimationInfo(node, info);
940
941 {
942 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill"));
943 if (!animations.isEmpty())
944 applyAnimationsToProperty(animations, property: &info.fillColor, calculateValue: calculateInterpolatedValue);
945 }
946
947 {
948 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity"));
949 if (!animations.isEmpty())
950 applyAnimationsToProperty(animations, property: &info.fillOpacity, calculateValue: calculateInterpolatedValue);
951 }
952
953 {
954 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke"));
955 if (!animations.isEmpty())
956 applyAnimationsToProperty(animations, property: &info.strokeColor, calculateValue: calculateInterpolatedValue);
957 }
958
959 {
960 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity"));
961 if (!animations.isEmpty())
962 applyAnimationsToProperty(animations, property: &info.strokeOpacity, calculateValue: calculateInterpolatedValue);
963 }
964
965 info.position = node->position();
966 info.size = node->size();
967 info.font = font;
968 info.text = text;
969 info.isTextArea = isTextArea;
970 info.needsRichText = needsRichText;
971 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
972 info.alignment = styleResolver->states().textAnchor;
973 info.strokeColor.setDefaultValue(styleResolver->currentStrokeColor());
974
975 m_generator->generateTextNode(info);
976 }
977
978 handleBaseNodeEnd(node);
979
980 if (fontEngine != nullptr) {
981 fontEngine->ref.deref();
982 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
983 delete fontEngine;
984 }
985}
986
987void QSvgVisitorImpl::visitUseNode(const QSvgUse *node)
988{
989 QSvgNode *link = node->link();
990 if (!link)
991 return;
992
993 handleBaseNodeSetup(node);
994 UseNodeInfo info;
995 fillCommonNodeInfo(node, info);
996 fillAnimationInfo(node, info);
997
998 info.stage = StructureNodeStage::Start;
999 info.startPos = node->start();
1000
1001 m_generator->generateUseNode(info);
1002
1003 QSvgVisitor::traverse(node: link);
1004
1005 info.stage = StructureNodeStage::End;
1006 m_generator->generateUseNode(info);
1007 handleBaseNodeEnd(node);
1008}
1009
1010bool QSvgVisitorImpl::visitSwitchNodeStart(const QSvgSwitch *node)
1011{
1012 QSvgNode *link = node->childToRender();
1013 if (!link)
1014 return false;
1015
1016 QSvgVisitor::traverse(node: link);
1017
1018 return false;
1019}
1020
1021void QSvgVisitorImpl::visitSwitchNodeEnd(const QSvgSwitch *node)
1022{
1023 Q_UNUSED(node);
1024}
1025
1026bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node)
1027{
1028 Q_UNUSED(node)
1029
1030 return m_generator->generateDefsNode(info: NodeInfo{});
1031}
1032
1033bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node)
1034{
1035 constexpr bool forceSeparatePaths = false;
1036 handleBaseNodeSetup(node);
1037
1038 StructureNodeInfo info;
1039
1040 fillCommonNodeInfo(node, info);
1041 fillAnimationInfo(node, info);
1042 info.forceSeparatePaths = forceSeparatePaths;
1043 info.isPathContainer = isPathContainer(node);
1044 info.stage = StructureNodeStage::Start;
1045
1046 return m_generator->generateStructureNode(info);
1047}
1048
1049void QSvgVisitorImpl::visitStructureNodeEnd(const QSvgStructureNode *node)
1050{
1051 handleBaseNodeEnd(node);
1052 // qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen) << m_styleResolver->painter().pen().color().name()
1053 // << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush) << m_styleResolver->painter().pen().brush().color().name();
1054
1055 StructureNodeInfo info;
1056 fillCommonNodeInfo(node, info);
1057 info.isPathContainer = isPathContainer(node);
1058 info.stage = StructureNodeStage::End;
1059
1060 m_generator->generateStructureNode(info);
1061}
1062
1063bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgTinyDocument *node)
1064{
1065 handleBaseNodeSetup(node);
1066
1067 StructureNodeInfo info;
1068 fillCommonNodeInfo(node, info);
1069 fillAnimationInfo(node, info);
1070
1071 const QSvgTinyDocument *doc = static_cast<const QSvgTinyDocument *>(node);
1072 info.size = doc->size();
1073 info.viewBox = doc->viewBox();
1074 info.isPathContainer = isPathContainer(node);
1075 info.forceSeparatePaths = false;
1076 info.stage = StructureNodeStage::Start;
1077
1078 return m_generator->generateRootNode(info);
1079}
1080
1081void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgTinyDocument *node)
1082{
1083 handleBaseNodeEnd(node);
1084 qCDebug(lcQuickVectorImage) << "REVERT" << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen)
1085 << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1086 << styleResolver->painter().pen().brush().color().name();
1087
1088 StructureNodeInfo info;
1089 fillCommonNodeInfo(node, info);
1090 info.stage = StructureNodeStage::End;
1091
1092 m_generator->generateRootNode(info);
1093}
1094
1095void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
1096{
1097 info.nodeId = node->nodeId();
1098 info.typeName = node->typeName();
1099 info.isDefaultTransform = node->style().transform.isDefault();
1100 info.transform.setDefaultValue(QVariant::fromValue(value: !info.isDefaultTransform
1101 ? node->style().transform->qtransform()
1102 : QTransform()));
1103 info.isDefaultOpacity = node->style().opacity.isDefault();
1104 info.opacity.setDefaultValue(!info.isDefaultOpacity ? node->style().opacity->opacity() : 1.0);
1105 info.isVisible = node->isVisible();
1106 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1107}
1108
1109QList<QSvgVisitorImpl::AnimationPair> QSvgVisitorImpl::collectAnimations(const QSvgNode *node,
1110 const QString &propertyName)
1111{
1112 QList<AnimationPair> ret;
1113 const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
1114 for (const QSvgAbstractAnimation *animation : animations) {
1115 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1116 for (const QSvgAbstractAnimatedProperty *property : properties) {
1117 if (property->propertyName() == propertyName)
1118 ret.append(t: std::make_pair(x&: animation, y&: property));
1119 }
1120 }
1121
1122 return ret;
1123}
1124
1125void QSvgVisitorImpl::applyAnimationsToProperty(const QList<AnimationPair> &animations,
1126 QQuickAnimatedProperty *outProperty,
1127 std::function<QVariant(const QSvgAbstractAnimatedProperty *, int index, int animationIndex)> calculateValue)
1128{
1129 qCDebug(lcVectorImageAnimations) << "Applying animations to property with default value"
1130 << outProperty->defaultValue();
1131 for (auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1132 qCDebug(lcVectorImageAnimations) << " -> Add animation";
1133 const QSvgAbstractAnimation *animation = it->first;
1134 const QSvgAbstractAnimatedProperty *property = it->second;
1135
1136 const int start = animation->start();
1137 const int repeatCount = animation->iterationCount();
1138 const int duration = animation->duration();
1139
1140 bool freeze = false;
1141 bool replace = true;
1142 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1143 const QSvgAnimateNode *animateNode = static_cast<const QSvgAnimateNode *>(animation);
1144 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1145 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1146 }
1147
1148 qCDebug(lcVectorImageAnimations) << " -> Start:" << start
1149 << ", repeatCount:" << repeatCount
1150 << ", freeze:" << freeze
1151 << ", replace:" << replace;
1152
1153 QList<qreal> propertyKeyFrames = property->keyFrames();
1154 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1155
1156 // For transform animations, we register the type of the transform in the animation
1157 // (this assumes that each animation is only for a single part of the transform)
1158 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1159 const auto *transformProperty = static_cast<const QSvgAnimatedPropertyTransform *>(property);
1160 const auto &components = transformProperty->components();
1161 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1162 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1163 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1164 outAnimation.repeatCount = repeatCount;
1165 outAnimation.startOffset = start;
1166 if (freeze)
1167 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1168 if (replace)
1169 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1170 switch (components.at(i).type) {
1171 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1172 outAnimation.subtype = QTransform::TxTranslate;
1173 break;
1174 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1175 outAnimation.subtype = QTransform::TxScale;
1176 break;
1177 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1178 outAnimation.subtype = QTransform::TxRotate;
1179 break;
1180 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1181 outAnimation.subtype = QTransform::TxShear;
1182 break;
1183 default:
1184 qCWarning(lcQuickVectorImage()) << "Unhandled transform type:" << components.at(i).type;
1185 break;
1186 }
1187
1188 qDebug(catFunc: lcVectorImageAnimations) << " -> Property type:"
1189 << property->type()
1190 << " name:"
1191 << property->propertyName()
1192 << " animation subtype:"
1193 << outAnimation.subtype;
1194
1195 outAnimations.append(t: outAnimation);
1196 }
1197 } else {
1198 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1199 outAnimation.repeatCount = repeatCount;
1200 outAnimation.startOffset = start;
1201 if (freeze)
1202 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1203
1204 qDebug(catFunc: lcVectorImageAnimations) << " -> Property type:"
1205 << property->type()
1206 << " name:"
1207 << property->propertyName();
1208
1209 outAnimations.append(t: outAnimation);
1210 }
1211
1212 outProperty->beginAnimationGroup();
1213 for (int i = 0; i < outAnimations.size(); ++i) {
1214 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1215
1216 for (int j = 0; j < propertyKeyFrames.size(); ++j) {
1217 const int time = qRound(d: propertyKeyFrames.at(i: j) * duration);
1218
1219 const QVariant value = calculateValue(property, j, i);
1220 outAnimation.frames[time] = value;
1221 qCDebug(lcVectorImageAnimations) << " -> Frame " << time << " is " << value;
1222 }
1223
1224 outProperty->addAnimation(animation: outAnimation);
1225 }
1226 }
1227}
1228
1229void QSvgVisitorImpl::fillColorAnimationInfo(const QSvgNode *node, PathNodeInfo &info)
1230{
1231 // Collect all animations affecting fill
1232 {
1233 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill"));
1234 if (!animations.isEmpty())
1235 applyAnimationsToProperty(animations, outProperty: &info.fillColor, calculateValue: calculateInterpolatedValue);
1236 }
1237
1238 {
1239 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity"));
1240 if (!animations.isEmpty())
1241 applyAnimationsToProperty(animations, outProperty: &info.fillOpacity, calculateValue: calculateInterpolatedValue);
1242 }
1243
1244 {
1245 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke"));
1246 if (!animations.isEmpty())
1247 applyAnimationsToProperty(animations, outProperty: &info.strokeStyle.color, calculateValue: calculateInterpolatedValue);
1248 }
1249
1250 {
1251 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity"));
1252 if (!animations.isEmpty())
1253 applyAnimationsToProperty(animations, outProperty: &info.strokeStyle.opacity, calculateValue: calculateInterpolatedValue);
1254 }
1255}
1256
1257void QSvgVisitorImpl::fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info)
1258{
1259 qCDebug(lcVectorImageAnimations) << "Applying transform animations to property with default value"
1260 << info.transform.defaultValue();
1261
1262 auto calculateValue = [](const QSvgAbstractAnimatedProperty *property, int index, int animationIndex) {
1263 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1264 return QVariant{};
1265
1266 const auto *transformProperty = static_cast<const QSvgAnimatedPropertyTransform *>(property);
1267 const auto &components = transformProperty->components();
1268
1269 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1270
1271 QVariantList parameters;
1272
1273 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(i: componentIndex);
1274 switch (component.type) {
1275 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1276 parameters.append(t: QVariant::fromValue(value: QPointF(component.values.value(i: 0),
1277 component.values.value(i: 1))));
1278 break;
1279 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1280 parameters.append(t: QVariant::fromValue(value: QPointF(component.values.value(i: 1),
1281 component.values.value(i: 2))));
1282 parameters.append(t: QVariant::fromValue(value: component.values.value(i: 0)));
1283 break;
1284 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1285 parameters.append(t: QVariant::fromValue(value: QPointF(component.values.value(i: 0),
1286 component.values.value(i: 1))));
1287 break;
1288 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1289 parameters.append(t: QVariant::fromValue(value: QPointF(component.values.value(i: 0),
1290 component.values.value(i: 1))));
1291 break;
1292 default:
1293 qCWarning(lcVectorImageAnimations) << "Unhandled transform type:" << component.type;
1294 };
1295
1296 return QVariant::fromValue(value: parameters);
1297 };
1298
1299
1300 {
1301 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("transform"));
1302 if (!animations.isEmpty())
1303 applyAnimationsToProperty(animations, outProperty: &info.transform, calculateValue);
1304 }
1305}
1306
1307void QSvgVisitorImpl::fillPathAnimationInfo(const QSvgNode *node, PathNodeInfo &info)
1308{
1309 fillColorAnimationInfo(node, info);
1310 fillAnimationInfo(node, info);
1311}
1312
1313void QSvgVisitorImpl::fillAnimationInfo(const QSvgNode *node, NodeInfo &info)
1314{
1315 {
1316 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("opacity"));
1317 if (!animations.isEmpty())
1318 applyAnimationsToProperty(animations, outProperty: &info.opacity, calculateValue: calculateInterpolatedValue);
1319 }
1320
1321 fillTransformAnimationInfo(node, info);
1322}
1323
1324void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
1325{
1326 qCDebug(lcQuickVectorImage) << "Before SETUP" << node << "fill" << styleResolver->currentFillColor()
1327 << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1328 << node->nodeId() << " type: " << node->typeName() << " " << node->type();
1329
1330 node->applyStyle(p: &styleResolver->painter(), states&: styleResolver->states());
1331
1332 qCDebug(lcQuickVectorImage) << "After SETUP" << node << "fill" << styleResolver->currentFillColor()
1333 << "stroke" << styleResolver->currentStrokeColor()
1334 << styleResolver->currentStrokeWidth() << node->nodeId();
1335}
1336
1337void QSvgVisitorImpl::handleBaseNode(const QSvgNode *node)
1338{
1339 NodeInfo info;
1340 fillCommonNodeInfo(node, info);
1341
1342 m_generator->generateNodeBase(info);
1343}
1344
1345void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
1346{
1347 node->revertStyle(p: &styleResolver->painter(), states&: styleResolver->states());
1348
1349 qCDebug(lcQuickVectorImage) << "After END" << node << "fill" << styleResolver->currentFillColor()
1350 << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1351 << node->nodeId();
1352}
1353
1354void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
1355{
1356 handleBaseNodeSetup(node);
1357
1358 PathNodeInfo info;
1359 fillCommonNodeInfo(node, info);
1360 auto fillStyle = node->style().fill;
1361 if (fillStyle)
1362 info.fillRule = fillStyle->fillRule();
1363
1364 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
1365
1366 info.painterPath = path;
1367 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
1368 if (strokeGradient == nullptr) {
1369 info.strokeStyle = StrokeStyle::fromPen(p: styleResolver->currentStroke());
1370 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
1371 }
1372 if (styleResolver->currentFillGradient() != nullptr)
1373 info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity());
1374 info.fillTransform = styleResolver->currentFillTransform();
1375
1376 fillPathAnimationInfo(node, info);
1377
1378 m_generator->generatePath(info);
1379
1380 if (strokeGradient != nullptr) {
1381 PathNodeInfo strokeInfo;
1382 fillCommonNodeInfo(node, info&: strokeInfo);
1383
1384 strokeInfo.grad = *strokeGradient;
1385
1386 QPainterPathStroker stroker(styleResolver->currentStroke());
1387 strokeInfo.painterPath = stroker.createStroke(path);
1388 m_generator->generatePath(info: strokeInfo);
1389 }
1390
1391 handleBaseNodeEnd(node);
1392}
1393
1394QT_END_NAMESPACE
1395

source code of qtdeclarative/src/quickvectorimage/generator/qsvgvisitorimpl.cpp