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#include <QtSvg/private/qsvgfilter_p.h>
35
37
38Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations, "qt.quick.vectorimage.animations")
39
40using namespace Qt::StringLiterals;
41
43{
44public:
46 {
47 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
48 m_dummyPainter.begin(&m_dummyImage);
49 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
50 defaultPen.setMiterLimit(4);
51 m_dummyPainter.setPen(defaultPen);
52 m_dummyPainter.setBrush(Qt::black);
53 }
54
56 {
57 m_dummyPainter.end();
58 }
59
60 QPainter& painter() { return m_dummyPainter; }
61 QSvgExtraStates& states() { return m_svgState; }
62
64 {
65 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
66 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
67 return QColor(QColorConstants::Transparent);
68 }
69
70 QColor fillColor;
71 fillColor = m_dummyPainter.brush().color();
72 fillColor.setAlphaF(m_svgState.fillOpacity);
73
74 return fillColor;
75 }
76
78 {
79 return m_svgState.fillOpacity;
80 }
81
82 const QGradient *currentStrokeGradient() const
83 {
84 QBrush brush = m_dummyPainter.pen().brush();
85 if (brush.style() == Qt::LinearGradientPattern
86 || brush.style() == Qt::RadialGradientPattern
87 || brush.style() == Qt::ConicalGradientPattern) {
88 return brush.gradient();
89 }
90 return nullptr;
91 }
92
93 const QGradient *currentFillGradient() const
94 {
95 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
96 return m_dummyPainter.brush().gradient();
97 return nullptr;
98 }
99
101 {
102 return m_dummyPainter.brush().transform();
103 }
104
106 {
107 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
108 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
109 return QColor(QColorConstants::Transparent);
110 }
111
112 QColor strokeColor;
113 strokeColor = m_dummyPainter.pen().brush().color();
114 strokeColor.setAlphaF(m_svgState.strokeOpacity);
115
116 return strokeColor;
117 }
118
119 static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
120 {
121 QGradient grad = gradient;
122 QGradientStops stops;
123 for (auto &stop : grad.stops()) {
124 stop.second.setAlphaF(stop.second.alphaF() * opacity);
125 stops.append(stop);
126 }
127
128 grad.setStops(stops);
129
130 return grad;
131 }
132
133 float currentStrokeWidth() const
134 {
135 float penWidth = m_dummyPainter.pen().widthF();
136 return penWidth ? penWidth : 1;
137 }
138
140 {
141 return m_dummyPainter.pen();
142 }
143
144protected:
148};
149
150namespace {
151inline bool isPathContainer(const QSvgStructureNode *node)
152{
153 bool foundPath = false;
154 for (const auto &child : node->renderers()) {
155 switch (child->type()) {
156 // nodes that shouldn't go inside Shape{}
157 case QSvgNode::Switch:
158 case QSvgNode::Doc:
159 case QSvgNode::Group:
160 case QSvgNode::AnimateColor:
161 case QSvgNode::AnimateTransform:
162 case QSvgNode::Use:
163 case QSvgNode::Video:
164 case QSvgNode::Image:
165 case QSvgNode::Textarea:
166 case QSvgNode::Text:
167 case QSvgNode::Tspan:
168 case QSvgNode::Mask:
169 case QSvgNode::Marker:
170 case QSvgNode::Pattern:
171 //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
172 return false;
173
174 // nodes that could go inside Shape{}
175 case QSvgNode::Defs:
176 case QSvgNode::Symbol:
177 break;
178
179 // nodes that are done as pure ShapePath{}
180 case QSvgNode::Rect:
181 case QSvgNode::Circle:
182 case QSvgNode::Ellipse:
183 case QSvgNode::Line:
184 case QSvgNode::Path:
185 case QSvgNode::Polygon:
186 case QSvgNode::Polyline:
187 {
188 if (child->hasFilter())
189 return false;
190
191 if (child->hasAnyMarker())
192 return false;
193
194 if (!child->style().isDefaultProperty(QSvgStyleProperty::Opacity))
195 return false;
196
197 if (!child->style().isDefaultProperty(QSvgStyleProperty::Transform))
198 return false;
199
200 const auto animations = child->document()->animator()->animationsForNode(child.get());
201 if (!animations.isEmpty()) {
202 //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform animation";
203 return false;
204 }
205 foundPath = true;
206 break;
207 }
208 default:
209 qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
210 break;
211 }
212 }
213 //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
214 return foundPath;
215}
216
217static QString capStyleName(Qt::PenCapStyle style)
218{
219 QString styleName;
220
221 switch (style) {
222 case Qt::SquareCap:
223 styleName = QStringLiteral("squarecap");
224 break;
225 case Qt::FlatCap:
226 styleName = QStringLiteral("flatcap");
227 break;
228 case Qt::RoundCap:
229 styleName = QStringLiteral("roundcap");
230 break;
231 default:
232 break;
233 }
234
235 return styleName;
236}
237
238static QString joinStyleName(Qt::PenJoinStyle style)
239{
240 QString styleName;
241
242 switch (style) {
243 case Qt::MiterJoin:
244 styleName = QStringLiteral("miterjoin");
245 break;
246 case Qt::BevelJoin:
247 styleName = QStringLiteral("beveljoin");
248 break;
249 case Qt::RoundJoin:
250 styleName = QStringLiteral("roundjoin");
251 break;
252 case Qt::SvgMiterJoin:
253 styleName = QStringLiteral("svgmiterjoin");
254 break;
255 default:
256 break;
257 }
258
259 return styleName;
260}
261
262static QString dashArrayString(QList<qreal> dashArray)
263{
264 if (dashArray.isEmpty())
265 return QString();
266
267 QString dashArrayString;
268 QTextStream stream(&dashArrayString);
269
270 for (int i = 0; i < dashArray.length() - 1; i++) {
271 qreal value = dashArray[i];
272 stream << value << ", ";
273 }
274
275 stream << dashArray.last();
276
277 return dashArrayString;
278}
279};
280
281static QString scrub(const QString &raw)
282{
283 QString res(raw.left(80));
284
285 if (!res.isEmpty()) {
286 constexpr QLatin1StringView legalSymbols("_-.:"); // Only valid SVG id characters
287 qsizetype i = 0;
288 do {
289 if (res.at(i).isLetterOrNumber() || legalSymbols.contains(res.at(i)))
290 i++;
291 else
292 res.remove(i, 1);
293 } while (i < res.size());
294 }
295
296 return res;
297}
298
299QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName,
300 QQuickGenerator *generator,
301 bool assumeTrustedSource)
304 , m_assumeTrustedSource(assumeTrustedSource)
306{
307}
308
309QSvgVisitorImpl::~QSvgVisitorImpl() = default;
310
311bool QSvgVisitorImpl::startDefsBlock(const QSvgNode *node)
312{
314 fillCommonNodeInfo(node, info);
315
317
318 // Pattern transforms handled through the fill transform, the transform property is ignored, so
319 // we overwrite it with identity.
320 if (node->type() == QSvgNode::Pattern) {
321 info.transform = QQuickAnimatedProperty(QVariant::fromValue(QTransform{}));
322 info.isDefaultTransform = true;
323 }
324
325 if (!m_generator->generateDefsNode(info))
326 return false;
327
328 return true;
329}
330
331void QSvgVisitorImpl::endDefsBlock(const QSvgNode *node)
332{
334 fillCommonNodeInfo(node, info);
335
337
338 m_generator->generateDefsNode(info);
339}
340
341static inline bool isStructureNode(const QSvgNode *node)
342{
343 switch (node->type()) {
344 case QSvgNode::Switch:
345 case QSvgNode::Doc:
346 case QSvgNode::Defs:
347 case QSvgNode::Group:
348 case QSvgNode::Mask:
349 case QSvgNode::Symbol:
350 case QSvgNode::Filter:
351 case QSvgNode::FeMerge:
352 case QSvgNode::FeMergenode:
353 case QSvgNode::FeColormatrix:
354 case QSvgNode::FeGaussianblur:
355 case QSvgNode::FeOffset:
356 case QSvgNode::FeComposite:
357 case QSvgNode::FeFlood:
358 case QSvgNode::FeBlend:
359 case QSvgNode::Marker:
360 case QSvgNode::Pattern:
361 return true;
362 default:
363 return false;
364 }
365}
366
367static void recurseSvgNodes(const QSvgNode *root, const std::function<void(const QSvgNode *)> &fnc)
368{
369 fnc(root);
370
371 if (isStructureNode(root)) {
372 const QSvgStructureNode *sn = static_cast<const QSvgStructureNode *>(root);
373 for (const auto &child : sn->renderers())
374 recurseSvgNodes(child.get(), fnc);
375 }
376}
377
378void QSvgVisitorImpl::pregenerateReferencedNodes(const QSvgNode *doc)
379{
380 Q_ASSERT(m_generator != nullptr);
381
382 // Find any node which is referenced from elsewhere and generate a Component definition
383 // for it
384 QSet<QString> referencedIds;
385 auto findReferencedIds = [&referencedIds](const QSvgNode *node) {
386 if (node->hasFilter())
387 referencedIds.insert(node->filterId());
388 if (node->hasMask())
389 referencedIds.insert(node->maskId());
390 if (node->hasMarkerStart())
391 referencedIds.insert(node->markerStartId());
392 if (node->hasMarkerMid())
393 referencedIds.insert(node->markerMidId());
394 if (node->hasMarkerEnd())
395 referencedIds.insert(node->markerEndId());
396 if (node->type() == QSvgNode::Pattern)
397 referencedIds.insert(node->nodeId());
398 };
399 recurseSvgNodes(doc, findReferencedIds);
400
401 m_pregeneratingReferencedNodes = true;
402 for (const QString &referencedId : referencedIds) {
403 const QSvgNode *referencedNode = doc->document()->namedNode(referencedId);
404 if (referencedNode == nullptr)
405 continue;
406
407 if (!startDefsBlock(referencedNode))
408 return;
409
410 traverse(referencedNode);
411
412 endDefsBlock(referencedNode);
413 }
414
415 m_pregeneratingReferencedNodes = false;
416}
417
419{
420 if (!m_generator) {
421 qCDebug(lcQuickVectorImage) << "No valid QQuickGenerator is set. Genration will stop";
422 return false;
423 }
424
425 QtSvg::Options options;
426 if (m_assumeTrustedSource)
427 options.setFlag(QtSvg::AssumeTrustedSource);
428
429 const auto doc = QSvgDocument::load(m_svgFileName, options);
430 if (!doc) {
431 qCDebug(lcQuickVectorImage) << "Not a valid Svg File : " << m_svgFileName;
432 return false;
433 }
434
435 QSvgVisitor::traverse(doc.get());
436
437 return true;
438}
439
440void QSvgVisitorImpl::visitNode(const QSvgNode *node)
441{
442 handleBaseNodeSetup(node);
443
444 NodeInfo info;
445 fillCommonNodeInfo(node, info);
446 fillAnimationInfo(node, info);
447
448 m_generator->generateNode(info);
449
450 handleBaseNodeEnd(node);
451}
452
453void QSvgVisitorImpl::visitImageNode(const QSvgImage *node)
454{
455 // TODO: this requires proper asset management.
456 handleBaseNodeSetup(node);
457
458 ImageNodeInfo info;
459 fillCommonNodeInfo(node, info);
460 fillAnimationInfo(node, info);
461 info.image = node->image();
462 info.rect = node->rect();
463 info.externalFileReference = node->filename();
464
465 m_generator->generateImageNode(info);
466
467 handleBaseNodeEnd(node);
468}
469
470void QSvgVisitorImpl::visitRectNode(const QSvgRect *node)
471{
472 QRectF rect = node->rect();
473 QPointF rads = node->radius();
474 // This is using Qt::RelativeSize semantics: percentage of half rect size
475 qreal x1 = rect.left();
476 qreal x2 = rect.right();
477 qreal y1 = rect.top();
478 qreal y2 = rect.bottom();
479
480 qreal rx = rads.x() * rect.width() / 200;
481 qreal ry = rads.y() * rect.height() / 200;
482 QPainterPath p;
483
484 p.moveTo(x1 + rx, y1);
485 p.lineTo(x2 - rx, y1);
486 // qCDebug(lcQuickVectorGraphics) << "Line1" << x2 - rx << y1;
487 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90); // ARC to x2, y1 + ry
488 // qCDebug(lcQuickVectorGraphics) << "p1" << p;
489
490 p.lineTo(x2, y2 - ry);
491 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90); // ARC to x2 - rx, y2
492
493 p.lineTo(x1 + rx, y2);
494 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90); // ARC to x1, y2 - ry
495
496 p.lineTo(x1, y1 + ry);
497 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90); // ARC to x1 + rx, y1
498
499 handlePathNode(node, p);
500}
501
502void QSvgVisitorImpl::visitEllipseNode(const QSvgEllipse *node)
503{
504 QRectF rect = node->rect();
505
506 QPainterPath p;
507 p.addEllipse(rect);
508
509 handlePathNode(node, p);
510}
511
512void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
513{
514 handlePathNode(node, node->path());
515}
516
517void QSvgVisitorImpl::visitLineNode(const QSvgLine *node)
518{
519 QPainterPath p;
520 p.moveTo(node->line().p1());
521 p.lineTo(node->line().p2());
522 handlePathNode(node, p);
523}
524
525void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
526{
527 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), true);
528 handlePathNode(node, p);
529}
530
531void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
532{
533 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), false);
534 handlePathNode(node, p);
535}
536
537QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
538{
539 QString cssDescription;
540 if (gradient->type() == QGradient::LinearGradient) {
541 const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(gradient);
542
543 cssDescription += " -qt-foreground: qlineargradient("_L1;
544 cssDescription += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
545 cssDescription += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
546 cssDescription += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
547 cssDescription += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
548 } else if (gradient->type() == QGradient::RadialGradient) {
549 const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(gradient);
550
551 cssDescription += " -qt-foreground: qradialgradient("_L1;
552 cssDescription += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
553 cssDescription += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
554 cssDescription += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
555 cssDescription += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
556 cssDescription += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
557 } else {
558 const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(gradient);
559
560 cssDescription += " -qt-foreground: qconicalgradient("_L1;
561 cssDescription += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
562 cssDescription += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
563 cssDescription += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
564 }
565
566 const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
567 cssDescription += "coordinatemode:"_L1;
568 cssDescription += coordinateModes.at(int(gradient->coordinateMode()));
569 cssDescription += u',';
570
571 const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
572 cssDescription += "spread:"_L1;
573 cssDescription += spreads.at(int(gradient->spread()));
574
575 for (const QGradientStop &stop : gradient->stops()) {
576 cssDescription += ",stop:"_L1;
577 cssDescription += QString::number(stop.first);
578 cssDescription += u' ';
579 cssDescription += stop.second.name(QColor::HexArgb);
580 }
581
582 cssDescription += ");"_L1;
583
584 return cssDescription;
585}
586
587QString QSvgVisitorImpl::colorCssDescription(QColor color)
588{
589 QString cssDescription;
590 cssDescription += QStringLiteral("rgba(");
591 cssDescription += QString::number(color.red()) + QStringLiteral(",");
592 cssDescription += QString::number(color.green()) + QStringLiteral(",");
593 cssDescription += QString::number(color.blue()) + QStringLiteral(",");
594 cssDescription += QString::number(color.alphaF()) + QStringLiteral(")");
595
596 return cssDescription;
597}
598
599namespace {
600
601 // Simple class for representing the SVG font as a font engine
602 // We use the Proxy font engine type, which is currently unused and does not map to
603 // any specific font engine
604 // (The QSvgFont object must outlive the engine.)
605 class QSvgFontEngine : public QFontEngine
606 {
607 public:
608 QSvgFontEngine(const QSvgFont *font, qreal size);
609
610 QFontEngine *cloneWithSize(qreal size) const override;
611
612 glyph_t glyphIndex(uint ucs4) const override;
613 int stringToCMap(const QChar *str,
614 int len,
615 QGlyphLayout *glyphs,
616 int *nglyphs,
617 ShaperFlags flags) const override;
618
619 void addGlyphsToPath(glyph_t *glyphs,
620 QFixedPoint *positions,
621 int nGlyphs,
622 QPainterPath *path,
623 QTextItem::RenderFlags flags) override;
624
625 glyph_metrics_t boundingBox(glyph_t glyph) override;
626
627 void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
628 QFixed ascent() const override;
629 QFixed capHeight() const override;
630 QFixed descent() const override;
631 QFixed leading() const override;
632 qreal maxCharWidth() const override;
633 qreal minLeftBearing() const override;
634 qreal minRightBearing() const override;
635
636 QFixed emSquareSize() const override;
637
638 private:
639 const QSvgFont *m_font;
640 };
641
642 QSvgFontEngine::QSvgFontEngine(const QSvgFont *font, qreal size)
643 : QFontEngine(Proxy)
644 , m_font(font)
645 {
646 fontDef.pixelSize = size;
647 fontDef.families = QStringList(m_font->m_familyName);
648 }
649
650 QFixed QSvgFontEngine::emSquareSize() const
651 {
652 return QFixed::fromReal(m_font->m_unitsPerEm);
653 }
654
655 glyph_t QSvgFontEngine::glyphIndex(uint ucs4) const
656 {
657 const ushort c(ucs4);
658 if (ucs4 < USHRT_MAX && m_font->findFirstGlyphFor(QStringView(&c, 1)))
659 return glyph_t(ucs4);
660
661 return 0;
662 }
663
664 int QSvgFontEngine::stringToCMap(const QChar *str,
665 int len,
666 QGlyphLayout *glyphs,
667 int *nglyphs,
668 ShaperFlags flags) const
669 {
670 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
671 if (*nglyphs < len) {
672 *nglyphs = len;
673 return -1;
674 }
675
676 int ucs4Length = 0;
677 QStringIterator it(str, str + len);
678 while (it.hasNext()) {
679 char32_t ucs4 = it.next();
680 glyph_t index = glyphIndex(ucs4);
681 glyphs->glyphs[ucs4Length++] = index;
682 }
683
684 *nglyphs = ucs4Length;
685 glyphs->numGlyphs = ucs4Length;
686
687 if (!(flags & GlyphIndicesOnly))
688 recalcAdvances(glyphs, flags);
689
690 return *nglyphs;
691 }
692
693 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
694 QFixedPoint *positions,
695 int nGlyphs,
696 QPainterPath *path,
697 QTextItem::RenderFlags flags)
698 {
699 Q_UNUSED(flags);
700 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
701 for (int i = 0; i < nGlyphs; ++i) {
702 glyph_t index = glyphs[i];
703 if (index > 0) {
704 QPointF position = positions[i].toPointF();
705 const ushort c(index);
706 const QSvgGlyph *foundGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
707
708 if (!foundGlyph)
709 continue;
710
711 QPainterPath glyphPath = foundGlyph->m_path;
712
713 QTransform xform;
714 xform.translate(position.x(), position.y());
715 xform.scale(scale, -scale);
716 glyphPath = xform.map(glyphPath);
717 path->addPath(glyphPath);
718 }
719 }
720 }
721
722 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
723 {
724 glyph_metrics_t ret;
725 ret.x = 0; // left bearing
726 ret.y = -ascent();
727 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
728 const ushort c(glyph);
729 const QSvgGlyph *svgGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
730 ret.width = QFixed::fromReal(svgGlyph ? svgGlyph->m_horizAdvX * scale : 0.);
731 ret.height = ascent() + descent();
732 return ret;
733 }
734
735 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size) const
736 {
737 QSvgFontEngine *otherEngine = new QSvgFontEngine(m_font, size);
738 return otherEngine;
739 }
740
741 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags) const
742 {
743 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
744 for (int i = 0; i < glyphLayout->numGlyphs; i++) {
745 const ushort c(glyphLayout->glyphs[i]);
746 const QSvgGlyph *svgGl = m_font->findFirstGlyphFor(QStringView(&c, 1));
747 glyphLayout->advances[i] = QFixed::fromReal(svgGl ? svgGl->m_horizAdvX * scale : 0.);
748 }
749 }
750
751 QFixed QSvgFontEngine::ascent() const
752 {
753 return QFixed::fromReal(fontDef.pixelSize);
754 }
755
756 QFixed QSvgFontEngine::capHeight() const
757 {
758 return ascent();
759 }
760 QFixed QSvgFontEngine::descent() const
761 {
762 return QFixed{};
763 }
764
765 QFixed QSvgFontEngine::leading() const
766 {
767 return QFixed{};
768 }
769
770 qreal QSvgFontEngine::maxCharWidth() const
771 {
772 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
773 return m_font->m_horizAdvX * scale;
774 }
775
776 qreal QSvgFontEngine::minLeftBearing() const
777 {
778 return 0.0;
779 }
780
781 qreal QSvgFontEngine::minRightBearing() const
782 {
783 return 0.0;
784 }
785}
786
787static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)
788{
789 if (index == 0)
790 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
791 else
792 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
793
794 return property->interpolatedValue();
795}
796
797void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
798{
799 handleBaseNodeSetup(node);
800 const bool isTextArea = node->type() == QSvgNode::Textarea;
801
802 QString text;
803 const QSvgFont *svgFont = m_styleResolver->states().svgFont;
804 bool needsRichText = false;
805 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
806 const QGradient *mainGradient = m_styleResolver->currentFillGradient();
807
808 QFontEngine *fontEngine = nullptr;
809 if (svgFont != nullptr) {
810 fontEngine = new QSvgFontEngine(svgFont, m_styleResolver->painter().font().pointSize());
811 fontEngine->ref.ref();
812 }
813
814#if QT_CONFIG(texthtmlparser)
815 bool needsPathNode = mainGradient != nullptr
816 || svgFont != nullptr
817 || m_styleResolver->currentStrokeGradient() != nullptr;
818#endif
819 for (const auto *tspan : node->tspans()) {
820 if (!tspan) {
821 text += QStringLiteral("<br>");
822 continue;
823 }
824
825 // Note: We cannot get the font directly from the style, since this does
826 // not apply the weight, since this is relative and depends on current state.
827 handleBaseNodeSetup(tspan);
828 QFont font = m_styleResolver->painter().font();
829
830 QString styleTagContent;
831
832 if ((font.resolveMask() & QFont::FamilyResolved)
833 || (font.resolveMask() & QFont::FamiliesResolved)) {
834 styleTagContent += QStringLiteral("font-family: %1;").arg(font.family());
835 }
836
837 if (font.resolveMask() & QFont::WeightResolved
838 && font.weight() != QFont::Normal
839 && font.weight() != QFont::Bold) {
840 styleTagContent += QStringLiteral("font-weight: %1;").arg(int(font.weight()));
841 }
842
843 if (font.resolveMask() & QFont::SizeResolved) {
844 // Pixel size stored as point size in SVG parser
845 styleTagContent += QStringLiteral("font-size: %1px;").arg(int(font.pointSizeF()));
846 }
847
848 if (font.resolveMask() & QFont::CapitalizationResolved
849 && font.capitalization() == QFont::SmallCaps) {
850 styleTagContent += QStringLiteral("font-variant: small-caps;");
851 }
852
853 if (m_styleResolver->currentFillGradient() != nullptr
854 && m_styleResolver->currentFillGradient() != mainGradient) {
855 const QGradient grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
856 styleTagContent += gradientCssDescription(&grad) + u';';
857#if QT_CONFIG(texthtmlparser)
858 needsPathNode = true;
859#endif
860 }
861
862 const QColor currentStrokeColor = m_styleResolver->currentStrokeColor();
863 if (currentStrokeColor.alpha() > 0) {
864 QString strokeColor = colorCssDescription(currentStrokeColor);
865 styleTagContent += QStringLiteral("-qt-stroke-color:%1;").arg(strokeColor);
866 styleTagContent += QStringLiteral("-qt-stroke-width:%1px;").arg(m_styleResolver->currentStrokeWidth());
867 styleTagContent += QStringLiteral("-qt-stroke-dasharray:%1;").arg(dashArrayString(m_styleResolver->currentStroke().dashPattern()));
868 styleTagContent += QStringLiteral("-qt-stroke-dashoffset:%1;").arg(m_styleResolver->currentStroke().dashOffset());
869 styleTagContent += QStringLiteral("-qt-stroke-lineCap:%1;").arg(capStyleName(m_styleResolver->currentStroke().capStyle()));
870 styleTagContent += QStringLiteral("-qt-stroke-lineJoin:%1;").arg(joinStyleName(m_styleResolver->currentStroke().joinStyle()));
871 if (m_styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || m_styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
872 styleTagContent += QStringLiteral("-qt-stroke-miterlimit:%1;").arg(m_styleResolver->currentStroke().miterLimit());
873#if QT_CONFIG(texthtmlparser)
874 needsPathNode = true;
875#endif
876 }
877
878 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
879 styleTagContent += QStringLiteral("white-space: pre-wrap;");
880
881 QString content = tspan->text().toHtmlEscaped();
882 content.replace(QLatin1Char('\t'), QLatin1Char(' '));
883 content.replace(QLatin1Char('\n'), QLatin1Char(' '));
884
885 bool fontTag = false;
886 if (!tspan->style().isDefaultProperty(QSvgStyleProperty::Fill)) {
887 auto fill = static_cast<QSvgFillStyle *>(tspan->style().property(QSvgStyleProperty::Fill));
888 auto &b = fill->qbrush();
889 qCDebug(lcQuickVectorImage) << "tspan FILL:" << b;
890 if (b.style() != Qt::NoBrush)
891 {
892 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
893 {
894 QString spanColor = b.color().name();
895 fontTag = !spanColor.isEmpty();
896 if (fontTag)
897 text += QStringLiteral("<font color=\"%1\">").arg(spanColor);
898 } else {
899 QString spanColor = colorCssDescription(b.color());
900 styleTagContent += QStringLiteral("color:%1").arg(spanColor);
901 }
902 }
903 }
904
905 needsRichText = needsRichText || !styleTagContent.isEmpty();
906 if (!styleTagContent.isEmpty())
907 text += QStringLiteral("<span style=\"%1\">").arg(styleTagContent.toHtmlEscaped());
908
909 if (font.resolveMask() & QFont::WeightResolved && font.bold())
910 text += QStringLiteral("<b>");
911
912 if (font.resolveMask() & QFont::StyleResolved && font.italic())
913 text += QStringLiteral("<i>");
914
915 if (font.resolveMask() & QFont::CapitalizationResolved) {
916 switch (font.capitalization()) {
917 case QFont::AllLowercase:
918 content = content.toLower();
919 break;
920 case QFont::AllUppercase:
921 content = content.toUpper();
922 break;
923 case QFont::Capitalize:
924 // ### We need to iterate over the string and do the title case conversion,
925 // since this is not part of QString.
926 qCWarning(lcQuickVectorImage) << "Title case not implemented for tspan";
927 break;
928 default:
929 break;
930 }
931 }
932 text += content;
933 if (fontTag)
934 text += QStringLiteral("</font>");
935
936 if (font.resolveMask() & QFont::StyleResolved && font.italic())
937 text += QStringLiteral("</i>");
938
939 if (font.resolveMask() & QFont::WeightResolved && font.bold())
940 text += QStringLiteral("</b>");
941
942 if (!styleTagContent.isEmpty())
943 text += QStringLiteral("</span>");
944
945 handleBaseNodeEnd(tspan);
946 }
947
948 if (preserveWhiteSpace && (needsRichText || m_styleResolver->currentFillGradient() != nullptr))
949 text = QStringLiteral("<span style=\"white-space: pre-wrap\">") + text + QStringLiteral("</span>");
950
951 QFont font = m_styleResolver->painter().font();
952 if (font.pixelSize() <= 0 && font.pointSize() > 0)
953 font.setPixelSize(font.pointSize()); // Pixel size stored as point size by SVG parser
954
955 font.setHintingPreference(QFont::PreferNoHinting);
956
957#if QT_CONFIG(texthtmlparser)
958 if (needsPathNode) {
959 QTextDocument document;
960 document.setHtml(text);
961 if (isTextArea && node->size().width() > 0)
962 document.setTextWidth(node->size().width());
963 document.setDefaultFont(font);
964 document.pageCount(); // Force layout
965
966 QTextBlock block = document.firstBlock();
967 while (block.isValid()) {
968 QTextLayout *lout = block.layout();
969
970 if (lout != nullptr) {
971 QRectF boundingRect = lout->boundingRect();
972
973 // If this block has requested the current SVG font, we override it
974 // (note that this limits the text to one svg font, but this is also the case
975 // in the QPainter at the moment, and needs a more centralized solution in Qt Svg
976 // first)
977 QFont blockFont = block.charFormat().font();
978 if (svgFont != nullptr
979 && blockFont.family() == svgFont->m_familyName) {
980 QRawFont rawFont;
981 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
982 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
983
984 lout->setRawFont(rawFont);
985 }
986
987 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt, int pathIndex) {
988 PathNodeInfo info;
989 fillCommonNodeInfo(node, info, QStringLiteral("_path%1").arg(pathIndex));
990 fillPathAnimationInfo(node, info);
991 auto fillStyle = static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
992 if (fillStyle)
993 info.fillRule = fillStyle->fillRule();
994
995 if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
996 info.fillColor.setDefaultValue(fmt.foreground().color());
997 if (fmt.foreground().gradient() != nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
998 info.grad = *fmt.foreground().gradient();
999 } else {
1000 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1001 }
1002
1003 info.path.setDefaultValue(QVariant::fromValue(p));
1004
1005 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
1006 QPen pen;
1007 if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
1008 pen = fmt.textOutline();
1009 if (strokeGradient == nullptr) {
1010 info.strokeStyle = StrokeStyle::fromPen(pen);
1011 info.strokeStyle.color.setDefaultValue(pen.color());
1012 }
1013 } else {
1014 pen = m_styleResolver->currentStroke();
1015 if (strokeGradient == nullptr) {
1016 info.strokeStyle = StrokeStyle::fromPen(pen);
1017 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
1018 }
1019 }
1020
1021 if (info.grad.type() == QGradient::NoGradient && m_styleResolver->currentFillGradient() != nullptr)
1022 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
1023
1024 info.fillTransform = m_styleResolver->currentFillTransform();
1025
1026 m_generator->generatePath(info, boundingRect);
1027
1028 if (strokeGradient != nullptr) {
1029 PathNodeInfo strokeInfo;
1030 fillCommonNodeInfo(node, strokeInfo, QStringLiteral("_stroke%1").arg(pathIndex));
1031 fillPathAnimationInfo(node, strokeInfo);
1032
1033 strokeInfo.grad = *strokeGradient;
1034
1035 QPainterPathStroker stroker(pen);
1036 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(p)));
1037 m_generator->generatePath(strokeInfo, boundingRect);
1038 }
1039 };
1040
1041 qreal baselineOffset = -QFontMetricsF(font).ascent();
1042 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
1043 baselineOffset = -lout->lineAt(0).ascent();
1044
1045 const QPointF baselineTranslation(0.0, baselineOffset);
1046 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
1047 QList<QPainterPath> paths;
1048 for (const QGlyphRun &glyphRun : glyphRuns) {
1049 QRawFont font = glyphRun.rawFont();
1050 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
1051 QList<QPointF> positions = glyphRun.positions();
1052
1053 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
1054 quint32 glyphIndex = glyphIndexes.at(j);
1055 const QPointF &pos = positions.at(j);
1056
1057 QPainterPath p = font.pathForGlyph(glyphIndex);
1058 p.translate(pos + node->position() + baselineTranslation);
1059 if (m_styleResolver->states().textAnchor == Qt::AlignHCenter)
1060 p.translate(QPointF(-0.5 * width, 0));
1061 else if (m_styleResolver->states().textAnchor == Qt::AlignRight)
1062 p.translate(QPointF(-width, 0));
1063 paths.append(p);
1064 }
1065 }
1066
1067 return paths;
1068 };
1069
1070 QList<QTextLayout::FormatRange> formats = block.textFormats();
1071 for (int i = 0; i < formats.size(); ++i) {
1072 QTextLayout::FormatRange range = formats.at(i);
1073
1074 QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
1075 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
1076 for (int j = 0; j < paths.size(); ++j) {
1077 const QPainterPath &path = paths.at(j);
1078 addPathForFormat(path, range.format, j);
1079 }
1080 }
1081 }
1082
1083 block = block.next();
1084 }
1085 } else
1086#endif
1087 {
1088 TextNodeInfo info;
1089 fillCommonNodeInfo(node, info);
1090 fillAnimationInfo(node, info);
1091
1092 {
1093 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill"));
1094 if (!animations.isEmpty())
1095 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1096 }
1097
1098 {
1099 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity"));
1100 if (!animations.isEmpty())
1101 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1102 }
1103
1104 {
1105 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke"));
1106 if (!animations.isEmpty())
1107 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
1108 }
1109
1110 {
1111 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity"));
1112 if (!animations.isEmpty())
1113 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
1114 }
1115
1116 info.position = node->position();
1117 info.size = node->size();
1118 info.font = font;
1119 info.text = text;
1120 info.isTextArea = isTextArea;
1121 info.needsRichText = needsRichText;
1122 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1123 info.alignment = m_styleResolver->states().textAnchor;
1124 info.strokeColor.setDefaultValue(m_styleResolver->currentStrokeColor());
1125
1126 m_generator->generateTextNode(info);
1127 }
1128
1129 handleBaseNodeEnd(node);
1130
1131 if (fontEngine != nullptr) {
1132 fontEngine->ref.deref();
1133 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
1134 delete fontEngine;
1135 }
1136}
1137
1138void QSvgVisitorImpl::visitUseNode(const QSvgUse *node)
1139{
1140 QSvgNode *link = node->link();
1141 if (!link)
1142 return;
1143 handleBaseNodeSetup(node);
1144 UseNodeInfo info;
1145 QPointF startPos = node->start();
1146 fillCommonNodeInfo(node, info);
1147 fillAnimationInfo(node, info);
1148 if (!info.bounds.isNull())
1149 info.bounds.translate(-startPos);
1151 if (!startPos.isNull()) {
1152 QTransform xform;
1153 if (!info.isDefaultTransform)
1154 xform = info.transform.defaultValue().value<QTransform>();
1155 xform.translate(startPos.x(), startPos.y());
1156 info.transform.setDefaultValue(QVariant::fromValue(xform));
1157 info.isDefaultTransform = false;
1158 }
1159 m_generator->generateUseNode(info);
1160 QString oldLinkSuffix = m_linkSuffix;
1161 m_linkSuffix += QStringLiteral("_use") + info.id;
1162 m_useLevel++;
1163 QSvgVisitor::traverse(link);
1164 m_useLevel--;
1165 m_linkSuffix = oldLinkSuffix;
1167 m_generator->generateUseNode(info);
1168 handleBaseNodeEnd(node);
1169}
1170
1171bool QSvgVisitorImpl::visitSwitchNodeStart(const QSvgSwitch *node)
1172{
1173 QSvgNode *link = node->childToRender();
1174 if (!link)
1175 return false;
1176
1177 QString oldLinkSuffix = m_linkSuffix;
1178 m_linkSuffix += QStringLiteral("_switch") + QString::number(quintptr(node), 16);
1179 QSvgVisitor::traverse(link);
1180 m_linkSuffix = oldLinkSuffix;
1181
1182 return false;
1183}
1184
1185void QSvgVisitorImpl::visitSwitchNodeEnd(const QSvgSwitch *node)
1186{
1187 Q_UNUSED(node);
1188}
1189
1190bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node)
1191{
1192 Q_UNUSED(node);
1193 return m_pregeneratingReferencedNodes;
1194}
1195
1196void QSvgVisitorImpl::visitDefsNodeEnd(const QSvgDefs *node)
1197{
1198 Q_UNUSED(node);
1199}
1200
1201bool QSvgVisitorImpl::visitPatternNodeStart(const QSvgPattern *node)
1202{
1203 if (m_pregeneratingReferencedNodes) {
1204 handleBaseNodeSetup(node);
1205
1206 PatternNodeInfo info;
1207 fillCommonNodeInfo(node, info);
1208 fillAnimationInfo(node, info);
1209
1210 info.stage = StructureNodeStage::Start;
1211
1212 QSvgRectF r = node->rect();
1213 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1214 info.patternRect = r;
1215
1216 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1217 qCWarning(lcQuickVectorImage) << "Only user space content units supported for patterns";
1218
1219 return m_generator->generatePatternNode(info);
1220 } else {
1221 return false;
1222 }
1223}
1224
1225void QSvgVisitorImpl::visitPatternNodeEnd(const QSvgPattern *node)
1226{
1227 Q_ASSERT(m_pregeneratingReferencedNodes);
1228
1229 handleBaseNodeSetup(node);
1230
1231 PatternNodeInfo info;
1232 fillCommonNodeInfo(node, info);
1233 fillAnimationInfo(node, info);
1234
1235 QSvgRectF r = node->rect();
1236 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1237 info.patternRect = r;
1238
1239 info.stage = StructureNodeStage::End;
1240
1241 m_generator->generatePatternNode(info);
1242}
1243
1244bool QSvgVisitorImpl::visitSymbolNodeStart(const QSvgSymbol *node)
1245{
1246 if (m_useLevel == 0)
1247 return false;
1248
1249 handleBaseNodeSetup(node);
1250
1251 StructureNodeInfo info;
1252 fillCommonNodeInfo(node, info);
1253 fillAnimationInfo(node, info);
1254
1255 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
1256 info.clipBox = oldTransform.mapRect(node->clipRect());
1257
1258 QTransform xform = node->aspectRatioTransform();
1259 if (!xform.isIdentity()) {
1260 info.isDefaultTransform = false;
1261 xform = xform * oldTransform;
1262 info.transform.setDefaultValue(QVariant::fromValue(xform));
1263 }
1265
1266 return m_generator->generateStructureNode(info);
1267}
1268
1269void QSvgVisitorImpl::visitSymbolNodeEnd(const QSvgSymbol *node)
1270{
1271 handleBaseNodeSetup(node);
1272
1273 StructureNodeInfo info;
1274 fillCommonNodeInfo(node, info);
1275 fillAnimationInfo(node, info);
1276
1277 info.clipBox = node->clipRect();
1279
1280 m_generator->generateStructureNode(info);
1281}
1282
1283bool QSvgVisitorImpl::visitMaskNodeStart(const QSvgMask *node)
1284{
1285 if (!m_pregeneratingReferencedNodes)
1286 return false;
1287
1288 handleBaseNodeSetup(node);
1289
1290 MaskNodeInfo info;
1291
1292 QSvgRectF r = node->rect();
1293 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1294 info.maskRect = r;
1295
1296 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1297 qCWarning(lcQuickVectorImage) << "Only user space content units supported for masks";
1298
1299 fillCommonNodeInfo(node, info);
1300
1301 return m_generator->generateMaskNode(info);
1302}
1303
1304void QSvgVisitorImpl::visitMaskNodeEnd(const QSvgMask *node)
1305{
1306 MaskNodeInfo info;
1308
1309 QSvgRectF r = node->rect();
1310 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1311 info.maskRect = r;
1312 fillCommonNodeInfo(node, info);
1313
1314 m_generator->generateMaskNode(info);
1315
1316 handleBaseNodeEnd(node);
1317}
1318
1319bool QSvgVisitorImpl::visitFilterNodeStart(const QSvgFilterContainer *node)
1320{
1321 Q_UNUSED(node)
1322
1323 if (!m_pregeneratingReferencedNodes)
1324 return false;
1325
1326 if (!m_filterPrimitives.isEmpty()) {
1327 qCWarning(lcQuickVectorImage) << "Filter defined inside a filter";
1328 return false;
1329 }
1330
1331 return true;
1332}
1333
1334void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
1335{
1336 if (m_filterPrimitives.isEmpty())
1337 return;
1338
1339 handleBaseNodeSetup(node);
1340
1341 FilterNodeInfo info;
1342 fillCommonNodeInfo(node, info);
1343
1344 info.filterRect = node->rect();
1345 if (node->filterUnits() == QtSvg::UnitTypes::objectBoundingBox)
1347
1348 bool generatedAlpha = false;
1349 for (const QSvgFeFilterPrimitive *filterPrimitive : std::as_const(m_filterPrimitives)) {
1350 if (filterPrimitive->requiresSourceAlpha() && !generatedAlpha) {
1351 FilterNodeInfo::FilterStep alphaStep;
1352 alphaStep.filterType = FilterNodeInfo::Type::ColorMatrix;
1353 alphaStep.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
1354
1355 // Isolate alpha
1356 qreal values[] = { 0.0, 0.0, 0.0, 0.0, 0.0,
1357 0.0, 0.0, 0.0, 0.0, 0.0,
1358 0.0, 0.0, 0.0, 0.0, 0.0,
1359 0.0, 0.0, 0.0, 1.0, 0.0,
1360 0.0, 0.0, 0.0, 0.0, 0.0 };
1361 QGenericMatrix<5, 5, qreal> matrix(values);
1362 alphaStep.filterParameter = QVariant::fromValue(matrix);
1363 alphaStep.outputName = info.id + QStringLiteral("_source_alpha");
1364 generatedAlpha = true;
1365
1366 info.steps.append(alphaStep);
1367 }
1368
1369 fillFilterPrimitiveInfo(node, filterPrimitive, info);
1370 }
1371
1372 m_generator->generateFilterNode(info);
1373 m_filterPrimitives.clear();
1374}
1375
1376void QSvgVisitorImpl::fillFilterPrimitiveInfo(const QSvgFilterContainer *node,
1377 const QSvgFeFilterPrimitive *filterPrimitive,
1378 FilterNodeInfo &info)
1379{
1381 step.filterPrimitiveRect = filterPrimitive->rect();
1382
1383 step.outputName = info.id + QStringLiteral("_")
1384 + (filterPrimitive->result().isEmpty()
1385 ? QStringLiteral("output_") + QString::number(info.steps.size())
1386 : filterPrimitive->result());
1387
1388 auto findInput = [&info, &filterPrimitive](const QString &input, QString *outName) {
1389 const QString alphaSource = info.id + QStringLiteral("_source_alpha");
1390 if (input == QStringLiteral("SourceGraphic")) {
1392 } else if (input == QStringLiteral("SourceAlpha")) {
1393 *outName = alphaSource;
1395 } else if (info.steps.isEmpty()) {
1397 }
1398
1399 if (!input.isEmpty()) {
1400 *outName = info.id + QStringLiteral("_") + input;
1401 } else {
1402 bool insideMergeNode = filterPrimitive->type() == QSvgNode::FeMergenode;
1403 for (int i = info.steps.size() - 1; i >= 0; --i) {
1404 const auto &prevStep = info.steps.at(i);
1405 if (insideMergeNode && prevStep.filterType == FilterNodeInfo::Type::Merge) {
1406 insideMergeNode = false;
1407 continue;
1408 }
1409
1410 if (!prevStep.outputName.isEmpty() && prevStep.outputName != alphaSource) {
1411 *outName = prevStep.outputName;
1412 break;
1413 }
1414 }
1415 }
1416
1418 };
1419
1420 step.input1 = findInput(filterPrimitive->input(), &step.namedInput1);
1421
1422 if (node->primitiveUnits() == QtSvg::UnitTypes::objectBoundingBox)
1424
1425 // We special-case the default filter primitive rect as is done in Qt Svg.
1426 // The default is to match the filter's rect and this is represented by making
1427 // the types of the filter primitive's rect QtSvg::UnitTypes::unknown. Since
1428 // this is not generally handled in Qt Svg, we also just special case it here.
1429 if (node->primitiveUnits() == QtSvg::UnitTypes::userSpaceOnUse
1430 && filterPrimitive->rect().unitW() == QtSvg::UnitTypes::unknown) {
1432 }
1433
1434 switch (filterPrimitive->type()) {
1435 case QSvgNode::FeMerge:
1437 break;
1438 case QSvgNode::FeMergenode:
1440 break;
1441 case QSvgNode::FeBlend:
1442 {
1443 const QSvgFeBlend *blend = static_cast<const QSvgFeBlend *>(filterPrimitive);
1444 switch (blend->mode()) {
1445 case QSvgFeBlend::Mode::Normal:
1447 break;
1448 case QSvgFeBlend::Mode::Multiply:
1450 break;
1451 case QSvgFeBlend::Mode::Screen:
1453 break;
1454 case QSvgFeBlend::Mode::Darken:
1456 break;
1457 case QSvgFeBlend::Mode::Lighten:
1459 break;
1460 }
1461
1462 step.input2 = findInput(blend->input2(), &step.namedInput2);
1463 break;
1464 }
1465 case QSvgNode::FeComposite:
1466 {
1467 const QSvgFeComposite *composite = static_cast<const QSvgFeComposite *>(filterPrimitive);
1468 switch (composite->compositionOperator()) {
1469 case QSvgFeComposite::Operator::Over:
1471 break;
1472 case QSvgFeComposite::Operator::In:
1474 break;
1475 case QSvgFeComposite::Operator::Out:
1477 break;
1478 case QSvgFeComposite::Operator::Atop:
1480 break;
1481 case QSvgFeComposite::Operator::Xor:
1483 break;
1484 case QSvgFeComposite::Operator::Lighter:
1486 break;
1487 case QSvgFeComposite::Operator::Arithmetic:
1489 break;
1490 };
1491
1492 step.input2 = findInput(composite->input2(), &step.namedInput2);
1493 step.filterParameter = composite->k();
1494 break;
1495 }
1496 case QSvgNode::FeOffset:
1497 {
1498 const QSvgFeOffset *offset = static_cast<const QSvgFeOffset *>(filterPrimitive);
1500 step.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
1501 break;
1502
1503 }
1504 case QSvgNode::FeColormatrix:
1505 {
1506 const QSvgFeColorMatrix *colorMatrix =
1507 static_cast<const QSvgFeColorMatrix *>(filterPrimitive);
1509 step.filterParameter = QVariant::fromValue(colorMatrix->matrix());
1510 break;
1511 }
1512
1513 case QSvgNode::FeGaussianblur:
1514 {
1515 const QSvgFeGaussianBlur *gaussianBlur =
1516 static_cast<const QSvgFeGaussianBlur *>(filterPrimitive);
1517
1518 if (gaussianBlur->edgeMode() == QSvgFeGaussianBlur::EdgeMode::Wrap)
1519 info.wrapMode = QSGTexture::Repeat;
1521 if (!qFuzzyCompare(gaussianBlur->stdDeviationX(), gaussianBlur->stdDeviationY()))
1522 qCWarning(lcQuickVectorImage) << "Separate X and Y deviations not supported for gaussian blur";
1523 step.filterParameter = std::max(gaussianBlur->stdDeviationX(),
1524 gaussianBlur->stdDeviationY());
1525 break;
1526 }
1527
1528 case QSvgNode::FeFlood:
1529 {
1530 const QSvgFeFlood *flood =
1531 static_cast<const QSvgFeFlood *>(filterPrimitive);
1532
1534 step.filterParameter = flood->color();
1535 break;
1536 }
1537 default:
1538 // Create a dummy filter node to make sure bindings still work for unsupported filters
1540 break;
1541 }
1542
1543 info.steps.append(step);
1544}
1545
1546bool QSvgVisitorImpl::visitFeFilterPrimitiveNodeStart(const QSvgFeFilterPrimitive *node)
1547{
1548 m_filterPrimitives.append(node);
1549 return true;
1550}
1551
1552void QSvgVisitorImpl::visitFeFilterPrimitiveNodeEnd(const QSvgFeFilterPrimitive *node)
1553{
1554 Q_UNUSED(node);
1555}
1556
1557bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node)
1558{
1559 constexpr bool forceSeparatePaths = false;
1560 handleBaseNodeSetup(node);
1561
1562 StructureNodeInfo info;
1563
1564 fillCommonNodeInfo(node, info);
1565 fillAnimationInfo(node, info);
1566 info.forceSeparatePaths = forceSeparatePaths;
1567 info.isPathContainer = isPathContainer(node);
1569
1570 return m_generator->generateStructureNode(info);
1571}
1572
1573void QSvgVisitorImpl::visitStructureNodeEnd(const QSvgStructureNode *node)
1574{
1575 handleBaseNodeEnd(node);
1576 // qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen) << m_styleResolver->painter().pen().color().name()
1577 // << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush) << m_styleResolver->painter().pen().brush().color().name();
1578
1579 StructureNodeInfo info;
1580 fillCommonNodeInfo(node, info);
1581 info.isPathContainer = isPathContainer(node);
1583
1584 m_generator->generateStructureNode(info);
1585}
1586
1587QString QSvgVisitorImpl::nextNodeId() const
1588{
1589 return QStringLiteral("_qt_node%1").arg(m_nodeIdCounter++);
1590}
1591
1592bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgDocument *node)
1593{
1594 handleBaseNodeSetup(node);
1595
1596 StructureNodeInfo info;
1597 fillCommonNodeInfo(node, info);
1598 fillAnimationInfo(node, info);
1599
1600 const QSvgDocument *doc = static_cast<const QSvgDocument *>(node);
1601 info.size = doc->size();
1602 info.viewBox = doc->viewBox();
1603 info.isPathContainer = isPathContainer(node);
1604 info.forceSeparatePaths = false;
1606
1607 if (m_generator->generateRootNode(info)) {
1608 pregenerateReferencedNodes(node);
1609 return true;
1610 } else {
1611 return false;
1612 }
1613}
1614
1615void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgDocument *node)
1616{
1617 handleBaseNodeEnd(node);
1618 qCDebug(lcQuickVectorImage) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen)
1619 << m_styleResolver->painter().pen().color().name() << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1620 << m_styleResolver->painter().pen().brush().color().name();
1621
1622 StructureNodeInfo info;
1623 fillCommonNodeInfo(node, info);
1625
1626 m_generator->generateRootNode(info);
1627}
1628
1629QString QSvgVisitorImpl::findOrCreateId(const QString &id)
1630{
1631 QString ret = m_idForNodeId.value(id);
1632 if (ret.isEmpty()) {
1633 ret = nextNodeId();
1634 m_idForNodeId.insert(id, ret);
1635 }
1636 return ret;
1637}
1638
1639QString QSvgVisitorImpl::findOrCreateId(const QSvgNode *node, const QString &nodeId)
1640{
1641 QString key = nodeId;
1642 const QSvgNode *n = m_nodesForKeys.value(key);
1643 if (key.isEmpty() || (n != nullptr && n != node))
1644 key = QString::number(quintptr(node), 16);
1645
1646 m_nodesForKeys.insert(key, node);
1647 return findOrCreateId(key);
1648}
1649
1650void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info, const QString &idSuffix)
1651{
1652 const QString nodeId = scrub(node->nodeId());
1653 info.id = findOrCreateId(node, nodeId);
1654
1655 // Internal disambiguation when multiple items come from the same node
1656 info.id += idSuffix;
1657
1658 if (!m_linkSuffix.isEmpty())
1659 info.id += m_linkSuffix;
1660
1661 info.nodeId = nodeId;
1662 info.typeName = node->typeName();
1663 info.isDefaultTransform = node->style().isDefaultProperty(QSvgStyleProperty::Transform);
1664
1665 auto transform = static_cast<QSvgTransformStyle *>(node->style().property(QSvgStyleProperty::Transform));
1666 QTransform xf = !info.isDefaultTransform ? transform->qtransform() : QTransform();
1667 info.transform.setDefaultValue(QVariant::fromValue(xf));
1668
1669 auto opacity = static_cast<QSvgOpacityStyle *>(node->style().property(QSvgStyleProperty::Opacity));
1670 info.isDefaultOpacity = node->style().isDefaultProperty(QSvgStyleProperty::Opacity);
1671 info.opacity.setDefaultValue(!info.isDefaultOpacity ? opacity->opacity() : 1.0);
1672 info.isVisible = node->isVisible();
1673 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1674
1675 if (node->hasFilter()
1676 || node->hasMask()
1677 || node->type() == QSvgNode::Type::Mask
1678 || node->type() == QSvgNode::Type::Pattern) {
1679 QImage dummy(1, 1, QImage::Format_RGB32);
1680 QPainter p(&dummy);
1681 QSvgExtraStates states;
1682 p.setPen(QPen(Qt::NoPen));
1683 info.bounds = node->internalBounds(&p, states);
1684 }
1685
1686 if (node->hasMask())
1687 info.maskId = findOrCreateId(node->maskId());
1688
1689 if (node->hasFilter())
1690 info.filterId = findOrCreateId(node->filterId());
1691}
1692
1693QList<QSvgVisitorImpl::AnimationPair> QSvgVisitorImpl::collectAnimations(const QSvgNode *node,
1694 const QString &propertyName)
1695{
1696 QList<AnimationPair> ret;
1697 const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
1698 for (const QSvgAbstractAnimation *animation : animations) {
1699 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1700 for (const QSvgAbstractAnimatedProperty *property : properties) {
1701 if (property->propertyName() == propertyName)
1702 ret.append(std::make_pair(animation, property));
1703 }
1704 }
1705
1706 return ret;
1707}
1708
1709void QSvgVisitorImpl::applyAnimationsToProperty(const QList<AnimationPair> &animations,
1710 QQuickAnimatedProperty *outProperty,
1711 std::function<QVariant(const QSvgAbstractAnimatedProperty *, int index, int animationIndex)> calculateValue)
1712{
1713 qCDebug(lcVectorImageAnimations) << "Applying animations to property with default value"
1714 << outProperty->defaultValue();
1715 for (auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1716 qCDebug(lcVectorImageAnimations) << " -> Add animation";
1717 const QSvgAbstractAnimation *animation = it->first;
1718 const QSvgAbstractAnimatedProperty *property = it->second;
1719
1720 const int start = animation->start();
1721 const int repeatCount = animation->iterationCount();
1722 const int duration = animation->duration();
1723
1724 bool freeze = false;
1725 bool replace = true;
1726 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1727 const QSvgAnimateNode *animateNode = static_cast<const QSvgAnimateNode *>(animation);
1728 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1729 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1730 }
1731
1732 qCDebug(lcVectorImageAnimations) << " -> Start:" << start
1733 << ", repeatCount:" << repeatCount
1734 << ", freeze:" << freeze
1735 << ", replace:" << replace;
1736
1737 QList<qreal> propertyKeyFrames = property->keyFrames();
1738 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1739
1740 // For transform animations, we register the type of the transform in the animation
1741 // (this assumes that each animation is only for a single part of the transform)
1742 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1743 const auto *transformProperty = static_cast<const QSvgAnimatedPropertyTransform *>(property);
1744 const auto &components = transformProperty->components();
1745 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1746 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1747 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1748 outAnimation.repeatCount = repeatCount;
1749 outAnimation.startOffset = start;
1750 if (freeze)
1751 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1752 if (replace)
1753 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1754 switch (components.at(i).type) {
1755 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1756 outAnimation.subtype = QTransform::TxTranslate;
1757 break;
1758 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1759 outAnimation.subtype = QTransform::TxScale;
1760 break;
1761 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1762 outAnimation.subtype = QTransform::TxRotate;
1763 break;
1764 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1765 outAnimation.subtype = QTransform::TxShear;
1766 break;
1767 default:
1768 qCWarning(lcQuickVectorImage()) << "Unhandled transform type:" << components.at(i).type;
1769 break;
1770 }
1771
1772 qDebug(lcVectorImageAnimations) << " -> Property type:"
1773 << property->type()
1774 << " name:"
1775 << property->propertyName()
1776 << " animation subtype:"
1777 << outAnimation.subtype;
1778
1779 outAnimations.append(outAnimation);
1780 }
1781 } else {
1782 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1783 outAnimation.repeatCount = repeatCount;
1784 outAnimation.startOffset = start;
1785 if (freeze)
1786 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1787
1788 qDebug(lcVectorImageAnimations) << " -> Property type:"
1789 << property->type()
1790 << " name:"
1791 << property->propertyName();
1792
1793 outAnimations.append(outAnimation);
1794 }
1795
1796 outProperty->beginAnimationGroup();
1797 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
1798 for (int i = 0; i < outAnimations.size(); ++i) {
1799 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1800
1801 for (int j = 0; j < propertyKeyFrames.size(); ++j) {
1802 const int time = qRound(propertyKeyFrames.at(j) * duration);
1803
1804 const QVariant value = calculateValue(property, j, i);
1805 outAnimation.frames[time] = value;
1806
1807 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) : nullptr;
1808 outAnimation.easingPerFrame[time] = easingInterface != nullptr
1809 ? easingForAnimation(easingInterface, animation->animationType())
1810 : animationEasing;
1811
1812 qCDebug(lcVectorImageAnimations) << " -> Frame " << time << " is " << value;
1813 }
1814
1815 outProperty->addAnimation(outAnimation);
1816 }
1817 }
1818}
1819
1820QBezier QSvgVisitorImpl::easingForAnimation(const QSvgEasingInterface *easingInterface,
1821 QSvgAbstractAnimation::AnimationType type)
1822{
1823 constexpr QPointF startControlPoint(0, 0);
1824 constexpr QPointF endControlPoint(1, 1);
1825 constexpr QPointF easeC1(0.25, 0.1);
1826 constexpr QPointF easeC2(0.25, 1);
1827
1828 QBezier easing = QBezier::fromPoints(startControlPoint, startControlPoint, endControlPoint, endControlPoint);
1829
1830#if QT_CONFIG(cssparser)
1831 if (type == QSvgAbstractAnimation::CSS) {
1832 const QSvgCssEasing *cssEasing = static_cast<const QSvgCssEasing *>(easingInterface);
1833 switch (cssEasing->easingFunction()) {
1834 case QSvgCssValues::EasingFunction::Ease:
1835 case QSvgCssValues::EasingFunction::EaseIn:
1836 case QSvgCssValues::EasingFunction::EaseOut:
1837 case QSvgCssValues::EasingFunction::EaseInOut:
1838 case QSvgCssValues::EasingFunction::CubicBezier:
1839 case QSvgCssValues::EasingFunction::Linear:
1840 {
1841 const QSvgCssCubicBezierEasing *cssCubicEasing = static_cast<const QSvgCssCubicBezierEasing *>(cssEasing);
1842 QPointF c1 = cssCubicEasing->c1();
1843 QPointF c2 = cssCubicEasing->c2();
1844 easing = QBezier::fromPoints(startControlPoint, c1, c2, endControlPoint);
1845 break;
1846 }
1847 case QSvgCssValues::EasingFunction::Steps:
1848 {
1849 qCDebug(lcVectorImageAnimations) << "Step easing is not supported reverting to default.";
1850 easing = QBezier::fromPoints(startControlPoint, easeC1, easeC2, endControlPoint);
1851 break;
1852 }
1853 }
1854 }
1855#endif
1856
1857 return easing;
1858}
1859
1860void QSvgVisitorImpl::fillColorAnimationInfo(const QSvgNode *node, PathNodeInfo &info)
1861{
1862 // Collect all animations affecting fill
1863 {
1864 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill"));
1865 if (!animations.isEmpty())
1866 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1867 }
1868
1869 {
1870 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity"));
1871 if (!animations.isEmpty())
1872 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1873 }
1874
1875 {
1876 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke"));
1877 if (!animations.isEmpty())
1878 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1879 }
1880
1881 {
1882 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity"));
1883 if (!animations.isEmpty())
1884 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1885 }
1886}
1887
1888void QSvgVisitorImpl::fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info)
1889{
1890 qCDebug(lcVectorImageAnimations) << "Applying transform animations to property with default value"
1891 << info.transform.defaultValue();
1892
1893 auto calculateValue = [](const QSvgAbstractAnimatedProperty *property, int index, int animationIndex) {
1894 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1895 return QVariant{};
1896
1897 const auto *transformProperty = static_cast<const QSvgAnimatedPropertyTransform *>(property);
1898 const auto &components = transformProperty->components();
1899
1900 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1901
1902 QVariantList parameters;
1903
1904 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1905 switch (component.type) {
1906 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1907 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1908 component.values.value(1))));
1909 break;
1910 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1911 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1912 component.values.value(2))));
1913 parameters.append(QVariant::fromValue(component.values.value(0)));
1914 break;
1915 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1916 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1917 component.values.value(1))));
1918 break;
1919 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1920 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1921 component.values.value(1))));
1922 break;
1923 default:
1924 qCWarning(lcVectorImageAnimations) << "Unhandled transform type:" << component.type;
1925 };
1926
1927 return QVariant::fromValue(parameters);
1928 };
1929
1930
1931 {
1932 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("transform"));
1933 if (!animations.isEmpty())
1934 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1935 }
1936}
1937
1938void QSvgVisitorImpl::fillMotionPathAnimationInfo(const QSvgNode *node, NodeInfo &info)
1939{
1940 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("offset-distance"));
1941 auto offset = static_cast<QSvgOffsetStyle *>(node->style().property(QSvgStyleProperty::Offset));
1942
1943 if (animations.isEmpty())
1944 return;
1945
1946 if (!offset) {
1947 qCWarning(lcQuickVectorImage) << "Motion path animation: No offset path";
1948 return;
1949 }
1950
1951 if (animations.size() > 1) {
1952 qCWarning(lcQuickVectorImage)
1953 << "Not supported: More than one offset path animation on same node";
1954 }
1955
1956 const AnimationPair &animationPair = animations.first();
1957
1958 const QSvgAbstractAnimation *animation = animationPair.first;
1959 const QSvgAbstractAnimatedProperty *property = animationPair.second;
1960
1961 const int start = animation->start();
1962 const int repeatCount = animation->iterationCount();
1963 const int duration = animation->duration();
1964
1965 qCDebug(lcVectorImageAnimations) << "Motion path animation:"
1966 << "start == " << start
1967 << ", repeatCount == " << repeatCount
1968 << "; duration == " << duration;
1969
1970
1971 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1972 outAnimation.repeatCount = repeatCount;
1973 outAnimation.startOffset = start;
1974
1975 QPainterPath originalPath = offset->path();
1976
1977 qreal baseRotation;
1978 bool adaptAngle;
1979 switch (offset->rotateType()) {
1980 case QtSvg::OffsetRotateType::Auto:
1981 adaptAngle = true;
1982 baseRotation = 0.0;
1983 break;
1984 case QtSvg::OffsetRotateType::Angle:
1985 adaptAngle = false;
1986 baseRotation = offset->rotateAngle();
1987 break;
1988 case QtSvg::OffsetRotateType::AutoAngle:
1989 adaptAngle = true;
1990 baseRotation = offset->rotateAngle();
1991 break;
1992 case QtSvg::OffsetRotateType::Reverse:
1993 adaptAngle = true;
1994 baseRotation = 180.0;
1995 break;
1996 case QtSvg::OffsetRotateType::ReverseAngle:
1997 adaptAngle = true;
1998 baseRotation = offset->rotateAngle() + 180.0f;
1999 break;
2000 default:
2001 Q_UNREACHABLE();
2002 }
2003
2004 // Default value holds additional parameters
2005 QVariantList params({ QVariant::fromValue(originalPath), adaptAngle, baseRotation });
2006 info.motionPath.setDefaultValue(params);
2007
2008 const QList<qreal> propertyKeyFrames = property->keyFrames();
2009 outAnimation.frames[0] = qreal(0);
2010 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
2011 for (int j = 0; j < propertyKeyFrames.size(); ++j) {
2012 const int time = qRound(propertyKeyFrames.at(j) * duration);
2013 if (time >= 0) {
2014 qreal t = calculateInterpolatedValue(property, j, 0).toReal();
2015 outAnimation.frames[time] = t;
2016
2017 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) : nullptr;
2018 outAnimation.easingPerFrame[time] = easingInterface != nullptr
2019 ? easingForAnimation(easingInterface, animation->animationType())
2020 : animationEasing;
2021
2022 qCDebug(lcVectorImageAnimations) << " -> Frame " << time << " is " << t;
2023 }
2024 }
2025
2026 info.motionPath.addAnimation(outAnimation);
2027}
2028
2029void QSvgVisitorImpl::fillPathAnimationInfo(const QSvgNode *node, PathNodeInfo &info)
2030{
2031 fillColorAnimationInfo(node, info);
2032 fillAnimationInfo(node, info);
2033}
2034
2035void QSvgVisitorImpl::fillAnimationInfo(const QSvgNode *node, NodeInfo &info)
2036{
2037 {
2038 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("opacity"));
2039 if (!animations.isEmpty())
2040 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
2041 }
2042
2043 fillTransformAnimationInfo(node, info);
2044 fillMotionPathAnimationInfo(node, info);
2045}
2046
2047void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
2048{
2049 qCDebug(lcQuickVectorImage) << "Before SETUP" << node << "fill" << m_styleResolver->currentFillColor()
2050 << "stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2051 << node->nodeId() << " type: " << node->typeName() << " " << node->type();
2052
2053 node->applyStyle(&m_styleResolver->painter(), m_styleResolver->states());
2054
2055 qCDebug(lcQuickVectorImage) << "After SETUP" << node << "fill" << m_styleResolver->currentFillColor()
2056 << "stroke" << m_styleResolver->currentStrokeColor()
2057 << m_styleResolver->currentStrokeWidth() << node->nodeId();
2058}
2059
2060void QSvgVisitorImpl::handleBaseNode(const QSvgNode *node)
2061{
2062 NodeInfo info;
2063 fillCommonNodeInfo(node, info);
2064
2065 m_generator->generateNodeBase(info);
2066}
2067
2068void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
2069{
2070 node->revertStyle(&m_styleResolver->painter(), m_styleResolver->states());
2071
2072 qCDebug(lcQuickVectorImage) << "After END" << node << "fill" << m_styleResolver->currentFillColor()
2073 << "stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2074 << node->nodeId();
2075}
2076
2077void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
2078{
2079 handleBaseNodeSetup(node);
2080
2081 PathNodeInfo info;
2082 fillCommonNodeInfo(node, info);
2083
2084 if (node->hasMarkerStart())
2085 info.markerStartId = findOrCreateId(node->markerStartId());
2086
2087 if (node->hasMarkerMid())
2088 info.markerMidId = findOrCreateId(node->markerMidId());
2089
2090 if (node->hasMarkerEnd())
2091 info.markerEndId = findOrCreateId(node->markerEndId());
2092
2093 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
2094 auto strokeStyle = static_cast<QSvgStrokeStyle *>(node->style().property(QSvgStyleProperty::Stroke));
2095 bool hasStrokePattern = strokeStyle
2096 && strokeStyle->paintServer()
2097 && strokeStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern;
2098
2099 info.path.setDefaultValue(QVariant::fromValue(path));
2100 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
2101 if (strokeGradient != nullptr)
2102 info.strokeGrad = *strokeGradient;
2103
2104 if (!hasStrokePattern) {
2105 info.strokeStyle = StrokeStyle::fromPen(m_styleResolver->currentStroke());
2106 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
2107 }
2108 if (m_styleResolver->currentFillGradient() != nullptr)
2109 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
2110 info.fillTransform = m_styleResolver->currentFillTransform();
2111
2112 auto fillStyle = static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
2113 if (fillStyle) {
2114 info.fillRule = fillStyle->fillRule();
2115
2116 if (fillStyle->paintServer()
2117 && fillStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
2118 QSvgPatternPaint *paintServer = static_cast<QSvgPatternPaint *>(fillStyle->paintServer());
2119 info.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2120
2121 // The fill transform in the style resolver is a calculated transform which contains
2122 // the inverse of the QPainter's world transform at the given time to negate any other
2123 // transform set. We avoid this by generating the pattern definition in isolation and
2124 // ignore its transform, so we just use the raw pattern transform from the input here.
2125 info.fillTransform = paintServer->patternNode()->transform();
2126 }
2127 }
2128
2129 fillPathAnimationInfo(node, info);
2130
2131 m_generator->generatePath(info);
2132
2133 if (hasStrokePattern) {
2134 PathNodeInfo strokeInfo;
2135 fillCommonNodeInfo(node, strokeInfo, QStringLiteral("_stroke"));
2136
2137 QSvgPatternPaint *paintServer = static_cast<QSvgPatternPaint *>(strokeStyle->paintServer());
2138 strokeInfo.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2139 strokeInfo.fillTransform = paintServer->patternNode()->transform();
2140
2141 QPainterPathStroker stroker(m_styleResolver->currentStroke());
2142 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(path)));
2143 m_generator->generatePath(strokeInfo);
2144 }
2145
2146 handleBaseNodeEnd(node);
2147}
2148
2149void QSvgVisitorImpl::fillMarkerInfo(const QSvgMarker *node, MarkerNodeInfo &info)
2150{
2151 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
2152
2153 info.markerSize = node->rect().size();
2154 info.anchorPoint = node->refP();
2155 info.clipBox = oldTransform.mapRect(node->clipRect());
2156 info.viewBox = node->viewBox();
2157 switch (node->orientation()) {
2158 case QSvgMarker::Orientation::Auto:
2160 break;
2161 case QSvgMarker::Orientation::AutoStartReverse:
2163 break;
2164 case QSvgMarker::Orientation::Value:
2166 break;
2167 }
2168
2169 switch (node->markerUnits()) {
2170 case QSvgMarker::MarkerUnits::UserSpaceOnUse:
2172 break;
2173 case QSvgMarker::MarkerUnits::StrokeWidth:
2175 break;
2176 }
2177
2178 info.angle = node->orientationAngle();
2179
2180 QTransform xform = node->aspectRatioTransform();
2181 if (!xform.isIdentity()) {
2182 info.isDefaultTransform = false;
2183 xform = xform * oldTransform;
2184 info.transform.setDefaultValue(QVariant::fromValue(xform));
2185 }
2186
2187 info.preserveAspectRatio = MarkerNodeInfo::PreserveAspectRatio(node->preserveAspectRatios().toInt());
2188}
2189
2190bool QSvgVisitorImpl::visitMarkerNodeStart(const QSvgMarker *node)
2191{
2192 if (!m_pregeneratingReferencedNodes)
2193 return false;
2194
2195 handleBaseNodeSetup(node);
2196
2197 MarkerNodeInfo info;
2198
2199 fillCommonNodeInfo(node, info);
2200 fillAnimationInfo(node, info);
2201 fillMarkerInfo(node, info);
2202 info.stage = StructureNodeStage::Start;
2203
2204 return m_generator->generateMarkerNode(info);
2205}
2206
2207void QSvgVisitorImpl::visitMarkerNodeEnd(const QSvgMarker *node)
2208{
2209 handleBaseNodeEnd(node);
2210
2211 MarkerNodeInfo info;
2212 fillCommonNodeInfo(node, info);
2213 fillMarkerInfo(node, info);
2214 info.stage = StructureNodeStage::End;
2215
2216 m_generator->generateMarkerNode(info);
2217}
2218
2219QT_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
bool visitFilterNodeStart(const QSvgFilterContainer *node) override
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
void visitMarkerNodeEnd(const QSvgMarker *node) override
bool visitDefsNodeStart(const QSvgDefs *node) override
void visitDefsNodeEnd(const QSvgDefs *node) override
bool visitMaskNodeStart(const QSvgMask *node) override
void visitDocumentNodeEnd(const QSvgDocument *node) override
~QSvgVisitorImpl() override
void visitStructureNodeEnd(const QSvgStructureNode *node) override
void visitRectNode(const QSvgRect *node) override
void visitFeFilterPrimitiveNodeEnd(const QSvgFeFilterPrimitive *node) override
void visitLineNode(const QSvgLine *node) override
void visitNode(const QSvgNode *node) override
bool visitMarkerNodeStart(const QSvgMarker *node) override
void visitPolylineNode(const QSvgPolyline *node) override
QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator, bool assumeTrustedSource)
void visitUseNode(const QSvgUse *node) override
bool visitSymbolNodeStart(const QSvgSymbol *node) override
void visitPatternNodeEnd(const QSvgPattern *) override
bool visitDocumentNodeStart(const QSvgDocument *node) override
void visitImageNode(const QSvgImage *node) override
void visitFilterNodeEnd(const QSvgFilterContainer *node) override
bool visitPatternNodeStart(const QSvgPattern *) override
void visitTextNode(const QSvgText *node) override
void visitSwitchNodeEnd(const QSvgSwitch *node) override
void visitSymbolNodeEnd(const QSvgSymbol *node) override
bool visitFeFilterPrimitiveNodeStart(const QSvgFeFilterPrimitive *node) override
bool visitStructureNodeStart(const QSvgStructureNode *node) override
Combined button and popup list for selecting options.
static QString scrub(const QString &raw)
static bool isStructureNode(const QSvgNode *node)
static void recurseSvgNodes(const QSvgNode *root, const std::function< void(const QSvgNode *)> &fnc)
static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)