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