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