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