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