8#include <private/qsvgvisitor_p.h>
12#include <QTextDocument>
17#include <private/qquickshape_p.h>
18#include <private/qquicktext_p.h>
19#include <private/qquicktranslate_p.h>
20#include <private/qquickitem_p.h>
22#include <private/qquickimagebase_p_p.h>
23#include <private/qquickimage_p.h>
24#include <private/qsgcurveprocessor_p.h>
26#include <private/qquadpath_p.h>
28#include <QtCore/private/qstringiterator_p.h>
31#include <QtCore/qloggingcategory.h>
32#include <QtCore/qscopedvaluerollback.h>
33#include <QtCore/qxmlstream.h>
35#include <QtSvg/private/qsvgdocument_p.h>
36#include <QtSvg/private/qsvgstyle_p.h>
37#include <QtSvg/private/qsvgfilter_p.h>
41Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations,
"qt.quick.vectorimage.animations")
43using namespace Qt::StringLiterals;
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);
69 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
70 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
71 return QColor(QColorConstants::Transparent);
75 fillColor = m_dummyPainter.brush().color();
76 fillColor.setAlphaF(m_svgState.fillOpacity);
83 return m_svgState.fillOpacity;
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();
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();
106 return m_dummyPainter.brush().transform();
111 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
112 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
113 return QColor(QColorConstants::Transparent);
117 strokeColor = m_dummyPainter.pen().brush().color();
118 strokeColor.setAlphaF(m_svgState.strokeOpacity);
125 QGradient grad = gradient;
126 QGradientStops stops;
127 for (
auto &stop : grad.stops()) {
128 stop.second.setAlphaF(stop.second.alphaF() * opacity);
132 grad.setStops(stops);
139 float penWidth = m_dummyPainter.pen().widthF();
140 return penWidth ? penWidth : 1;
145 return m_dummyPainter.pen();
155inline bool isPathContainer(
const QSvgDocument *doc,
const QSvgStructureNode *node)
157 bool foundPath =
false;
158 for (
const auto &child : node->renderers()) {
159 switch (child->type()) {
161 case QSvgNode::Switch:
163 case QSvgNode::Group:
164 case QSvgNode::AnimateColor:
165 case QSvgNode::AnimateTransform:
167 case QSvgNode::Video:
168 case QSvgNode::Image:
169 case QSvgNode::Textarea:
171 case QSvgNode::Tspan:
173 case QSvgNode::Marker:
174 case QSvgNode::Pattern:
180 case QSvgNode::Symbol:
185 case QSvgNode::Circle:
186 case QSvgNode::Ellipse:
189 case QSvgNode::Polygon:
190 case QSvgNode::Polyline:
192 if (child->hasFilter())
195 if (child->hasAnyMarker())
198 if (!child->style().isDefaultProperty(QSvgStyleProperty::Opacity))
201 if (!child->style().isDefaultProperty(QSvgStyleProperty::Transform))
204 const auto animations = doc->animator()->animationsForNode(child.get());
205 if (!animations.isEmpty()) {
213 qCDebug(lcQuickVectorImage) <<
"Unhandled type in switch" << child->type();
221static QString capStyleName(Qt::PenCapStyle style)
227 styleName = QStringLiteral(
"squarecap");
230 styleName = QStringLiteral(
"flatcap");
233 styleName = QStringLiteral(
"roundcap");
242static QString joinStyleName(Qt::PenJoinStyle style)
248 styleName = QStringLiteral(
"miterjoin");
251 styleName = QStringLiteral(
"beveljoin");
254 styleName = QStringLiteral(
"roundjoin");
256 case Qt::SvgMiterJoin:
257 styleName = QStringLiteral(
"svgmiterjoin");
266static QString dashArrayString(QList<qreal> dashArray)
268 if (dashArray.isEmpty())
271 QString dashArrayString;
272 QTextStream stream(&dashArrayString);
274 for (
int i = 0; i < dashArray.length() - 1; i++) {
275 qreal value = dashArray[i];
276 stream << value <<
", ";
279 stream << dashArray.last();
281 return dashArrayString;
285static QString
scrub(
const QString &raw)
287 QString res(raw.left(80));
289 if (!res.isEmpty()) {
290 constexpr QLatin1StringView legalSymbols(
"_-.:");
293 if (res.at(i).isLetterOrNumber() || legalSymbols.contains(res.at(i)))
297 }
while (i < res.size());
304 QQuickGenerator *generator,
305 bool assumeTrustedSource)
308 , m_assumeTrustedSource(assumeTrustedSource)
317 fillCommonNodeInfo(node, info);
323 if (node->type() == QSvgNode::Pattern) {
324 info.transform = QQuickAnimatedProperty(QVariant::fromValue(QTransform{}));
325 info.isDefaultTransform =
true;
328 if (!m_generator->generateDefsNode(info))
337 fillCommonNodeInfo(node, info);
341 m_generator->generateDefsNode(info);
346 switch (node->type()) {
347 case QSvgNode::Switch:
350 case QSvgNode::Group:
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:
370static void recurseSvgNodes(
const QSvgNode *root,
const std::function<
void(
const QSvgNode *)> &fnc)
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);
383 Q_ASSERT(m_doc !=
nullptr);
384 Q_ASSERT(m_generator !=
nullptr);
388 QSet<QString> referencedIds;
389 auto findReferencedIds = [&referencedIds](
const QSvgNode *node) {
390 if (node->hasFilter())
391 referencedIds.insert(node->filterId());
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());
403 recurseSvgNodes(doc, findReferencedIds);
405 m_pregeneratingReferencedNodes =
true;
406 for (
const QString &referencedId : referencedIds) {
407 const QSvgNode *referencedNode = m_doc->namedNode(referencedId);
408 if (referencedNode ==
nullptr)
411 if (!startDefsBlock(referencedNode))
414 traverse(referencedNode);
416 endDefsBlock(referencedNode);
419 m_pregeneratingReferencedNodes =
false;
425 qCDebug(lcQuickVectorImage) <<
"No valid QQuickGenerator is set. Genration will stop";
429 QtSvg::Options options;
430 if (m_assumeTrustedSource)
431 options.setFlag(QtSvg::AssumeTrustedSource);
433 const auto doc = m_source.isFile()
434 ? QSvgDocument::load(m_source.fileName(), options)
435 : QSvgDocument::load(m_source.data(), options);
437 QScopedValueRollback docResetter(m_doc, doc.get());
439 qCDebug(lcQuickVectorImage) <<
"Not a valid Svg source:" << m_source;
443 m_styleResolver = std::make_unique<QSvgStyleResolver>(doc.get());
444 QSvgVisitor::traverse(doc.get());
451 handleBaseNodeSetup(node);
454 fillCommonNodeInfo(node, info);
455 fillAnimationInfo(node, info);
457 m_generator->generateNode(info);
459 handleBaseNodeEnd(node);
465 handleBaseNodeSetup(node);
468 fillCommonNodeInfo(node, info);
469 fillAnimationInfo(node, info);
470 info.image = node->image();
471 info.rect = node->rect();
472 info.externalFileReference = node->filename();
474 m_generator->generateImageNode(info);
476 handleBaseNodeEnd(node);
481 QRectF rect = node->rect();
482 QPointF rads = node->radius();
484 qreal x1 = rect.left();
485 qreal x2 = rect.right();
486 qreal y1 = rect.top();
487 qreal y2 = rect.bottom();
489 qreal rx = rads.x() * rect.width() / 200;
490 qreal ry = rads.y() * rect.height() / 200;
493 p.moveTo(x1 + rx, y1);
494 p.lineTo(x2 - rx, y1);
496 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90);
499 p.lineTo(x2, y2 - ry);
500 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90);
502 p.lineTo(x1 + rx, y2);
503 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90);
505 p.lineTo(x1, y1 + ry);
506 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90);
508 handlePathNode(node, p);
513 QRectF rect = node->rect();
518 handlePathNode(node, p);
523 handlePathNode(node, node->path());
529 p.moveTo(node->line().p1());
530 p.lineTo(node->line().p2());
531 handlePathNode(node, p);
536 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
true);
537 handlePathNode(node, p);
542 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
false);
543 handlePathNode(node, p);
546QString
QSvgVisitorImpl::gradientCssDescription(
const QGradient *gradient)
548 QString cssDescription;
549 if (gradient->type() == QGradient::LinearGradient) {
550 const QLinearGradient *linearGradient =
static_cast<
const QLinearGradient *>(gradient);
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);
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',';
567 const QConicalGradient *conicalGradient =
static_cast<
const QConicalGradient *>(gradient);
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',';
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',';
580 const QStringList spreads = {
"pad"_L1,
"reflect"_L1,
"repeat"_L1 };
581 cssDescription +=
"spread:"_L1;
582 cssDescription += spreads.at(
int(gradient->spread()));
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);
591 cssDescription +=
");"_L1;
593 return cssDescription;
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(
")");
605 return cssDescription;
614 class QSvgFontEngine :
public QFontEngine
617 QSvgFontEngine(
const QString &family,
const QSvgFont *font, qreal size);
619 QFontEngine *cloneWithSize(qreal size)
const override;
621 glyph_t glyphIndex(uint ucs4)
const override;
622 int stringToCMap(
const QChar *str,
624 QGlyphLayout *glyphs,
626 ShaperFlags flags)
const override;
628 void addGlyphsToPath(glyph_t *glyphs,
629 QFixedPoint *positions,
632 QTextItem::RenderFlags flags)
override;
634 glyph_metrics_t boundingBox(glyph_t glyph)
override;
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;
645 QFixed emSquareSize()
const override;
648 const QSvgFont *m_font;
652 QSvgFontEngine::QSvgFontEngine(
const QString &family,
const QSvgFont *font, qreal size)
657 fontDef.pixelSize = size;
658 fontDef.families = QStringList(family);
661 QFixed QSvgFontEngine::emSquareSize()
const
663 return QFixed::fromReal(m_font->m_unitsPerEm);
666 glyph_t QSvgFontEngine::glyphIndex(uint ucs4)
const
668 const ushort c(ucs4);
669 if (ucs4 < USHRT_MAX && m_font->findFirstGlyphFor(QStringView(&c, 1)))
670 return glyph_t(ucs4);
675 int QSvgFontEngine::stringToCMap(
const QChar *str,
677 QGlyphLayout *glyphs,
679 ShaperFlags flags)
const
681 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
682 if (*nglyphs < len) {
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;
695 *nglyphs = ucs4Length;
696 glyphs->numGlyphs = ucs4Length;
698 if (!(flags & GlyphIndicesOnly))
699 recalcAdvances(glyphs, flags);
704 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
705 QFixedPoint *positions,
708 QTextItem::RenderFlags 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];
715 QPointF position = positions[i].toPointF();
716 const ushort c(index);
717 const QSvgGlyph *foundGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
722 QPainterPath glyphPath = foundGlyph->m_path;
725 xform.translate(position.x(), position.y());
726 xform.scale(scale, -scale);
727 glyphPath = xform.map(glyphPath);
728 path->addPath(glyphPath);
733 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
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();
746 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size)
const
748 QSvgFontEngine *otherEngine =
new QSvgFontEngine(m_family, m_font, size);
752 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags)
const
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.);
762 QFixed QSvgFontEngine::ascent()
const
764 return QFixed::fromReal(fontDef.pixelSize);
767 QFixed QSvgFontEngine::capHeight()
const
771 QFixed QSvgFontEngine::descent()
const
776 QFixed QSvgFontEngine::leading()
const
781 qreal QSvgFontEngine::maxCharWidth()
const
783 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
784 return m_font->m_horizAdvX * scale;
787 qreal QSvgFontEngine::minLeftBearing()
const
792 qreal QSvgFontEngine::minRightBearing()
const
801 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
803 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
805 return property->interpolatedValue();
810 handleBaseNodeSetup(node);
811 const bool isTextArea = node->type() == QSvgNode::Textarea;
814 bool needsRichText =
false;
815 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
816 const QGradient *mainGradient = m_styleResolver->currentFillGradient();
819 auto *fontStyle =
static_cast<QSvgFontStyle *>(node->styleProperty(QSvgStyleProperty::Font));
820 QSvgFont *svgFont =
nullptr;
821 QString svgFontFamily;
824 svgFontFamily = fontStyle->qfont().family();
825 svgFont = m_doc->svgFont(svgFontFamily);
828 QFontEngine *fontEngine =
nullptr;
829 if (svgFont !=
nullptr) {
830 fontEngine =
new QSvgFontEngine(svgFontFamily, svgFont, m_styleResolver->painter().font().pointSize());
831 fontEngine->ref.ref();
835#if QT_CONFIG(texthtmlparser)
836 bool needsPathNode = mainGradient !=
nullptr
837 || svgFont !=
nullptr
838 || m_styleResolver->currentStrokeGradient() !=
nullptr;
840 for (
const auto *tspan : node->tspans()) {
842 text += QStringLiteral(
"<br>");
848 handleBaseNodeSetup(tspan);
849 QFont font = m_styleResolver->painter().font();
851 QString styleTagContent;
853 if ((font.resolveMask() & QFont::FamilyResolved)
854 || (font.resolveMask() & QFont::FamiliesResolved)) {
855 styleTagContent += QStringLiteral(
"font-family: %1;").arg(font.family());
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()));
864 if (font.resolveMask() & QFont::SizeResolved) {
866 styleTagContent += QStringLiteral(
"font-size: %1px;").arg(
int(font.pointSizeF()));
869 if (font.resolveMask() & QFont::CapitalizationResolved
870 && font.capitalization() == QFont::SmallCaps) {
871 styleTagContent += QStringLiteral(
"font-variant: small-caps;");
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;
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;
899 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
900 styleTagContent += QStringLiteral(
"white-space: pre-wrap;");
902 QString content = tspan->text().toHtmlEscaped();
903 content.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
904 content.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
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)
913 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
915 QString spanColor = b.color().name();
916 fontTag = !spanColor.isEmpty();
918 text += QStringLiteral(
"<font color=\"%1\">").arg(spanColor);
920 QString spanColor = colorCssDescription(b.color());
921 styleTagContent += QStringLiteral(
"color:%1").arg(spanColor);
926 needsRichText = needsRichText || !styleTagContent.isEmpty();
927 if (!styleTagContent.isEmpty())
928 text += QStringLiteral(
"<span style=\"%1\">").arg(styleTagContent.toHtmlEscaped());
930 if (font.resolveMask() & QFont::WeightResolved && font.bold())
931 text += QStringLiteral(
"<b>");
933 if (font.resolveMask() & QFont::StyleResolved && font.italic())
934 text += QStringLiteral(
"<i>");
936 if (font.resolveMask() & QFont::CapitalizationResolved) {
937 switch (font.capitalization()) {
938 case QFont::AllLowercase:
939 content = content.toLower();
941 case QFont::AllUppercase:
942 content = content.toUpper();
944 case QFont::Capitalize:
947 qCWarning(lcQuickVectorImage) <<
"Title case not implemented for tspan";
955 text += QStringLiteral(
"</font>");
957 if (font.resolveMask() & QFont::StyleResolved && font.italic())
958 text += QStringLiteral(
"</i>");
960 if (font.resolveMask() & QFont::WeightResolved && font.bold())
961 text += QStringLiteral(
"</b>");
963 if (!styleTagContent.isEmpty())
964 text += QStringLiteral(
"</span>");
966 handleBaseNodeEnd(tspan);
969 if (preserveWhiteSpace && (needsRichText || m_styleResolver->currentFillGradient() !=
nullptr))
970 text = QStringLiteral(
"<span style=\"white-space: pre-wrap\">") + text + QStringLiteral(
"</span>");
972 QFont font = m_styleResolver->painter().font();
973 if (font.pixelSize() <= 0 && font.pointSize() > 0)
974 font.setPixelSize(font.pointSize());
976 font.setHintingPreference(QFont::PreferNoHinting);
978#if QT_CONFIG(texthtmlparser)
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();
987 QTextBlock block = document.firstBlock();
988 while (block.isValid()) {
989 QTextLayout *lout = block.layout();
991 if (lout !=
nullptr) {
992 QRectF boundingRect = lout->boundingRect();
998 QFont blockFont = block.charFormat().font();
999 if (svgFont !=
nullptr
1000 && blockFont.family() == svgFontFamily) {
1002 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
1003 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
1005 lout->setRawFont(rawFont);
1008 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt,
int pathIndex) {
1010 fillCommonNodeInfo(node, info, QStringLiteral(
"_path%1").arg(pathIndex));
1011 fillPathAnimationInfo(node, info);
1012 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
1014 info.fillRule = fillStyle->fillRule();
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();
1021 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1024 info.path.setDefaultValue(QVariant::fromValue(p));
1026 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
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());
1035 pen = m_styleResolver->currentStroke();
1036 if (strokeGradient ==
nullptr) {
1037 info.strokeStyle = StrokeStyle::fromPen(pen);
1038 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
1042 if (info.grad.type() == QGradient::NoGradient && m_styleResolver->currentFillGradient() !=
nullptr)
1043 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
1045 info.fillTransform = m_styleResolver->currentFillTransform();
1047 m_generator->generatePath(info, boundingRect);
1049 if (strokeGradient !=
nullptr) {
1050 PathNodeInfo strokeInfo;
1051 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke%1").arg(pathIndex));
1052 fillPathAnimationInfo(node, strokeInfo);
1054 strokeInfo.grad = *strokeGradient;
1056 QPainterPathStroker stroker(pen);
1057 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(p)));
1058 m_generator->generatePath(strokeInfo, boundingRect);
1062 qreal baselineOffset = -QFontMetricsF(font).ascent();
1063 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
1064 baselineOffset = -lout->lineAt(0).ascent();
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();
1074 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
1075 quint32 glyphIndex = glyphIndexes.at(j);
1076 const QPointF &pos = positions.at(j);
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));
1091 QList<QTextLayout::FormatRange> formats = block.textFormats();
1092 for (
int i = 0; i < formats.size(); ++i) {
1093 QTextLayout::FormatRange range = formats.at(i);
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);
1104 block = block.next();
1110 fillCommonNodeInfo(node, info);
1111 fillAnimationInfo(node, info);
1114 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1115 if (!animations.isEmpty())
1116 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1120 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1121 if (!animations.isEmpty())
1122 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1126 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1127 if (!animations.isEmpty())
1128 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
1132 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1133 if (!animations.isEmpty())
1134 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
1137 info.position = node->position();
1138 info.size = node->size();
1143 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1144 info.alignment = m_styleResolver->states().textAnchor;
1145 info.strokeColor.setDefaultValue(m_styleResolver->currentStrokeColor());
1147 m_generator->generateTextNode(info);
1150 handleBaseNodeEnd(node);
1152 if (fontEngine !=
nullptr) {
1153 fontEngine->ref.deref();
1154 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
1161 QSvgNode *link = node->link();
1164 handleBaseNodeSetup(node);
1166 fillCommonNodeInfo(node, info);
1167 fillAnimationInfo(node, info);
1169 info.startPos = node->start();
1170 m_generator->generateUseNode(info);
1171 QString oldLinkSuffix = m_linkSuffix;
1172 m_linkSuffix += QStringLiteral(
"_use") + info.id;
1174 QSvgVisitor::traverse(link);
1176 m_linkSuffix = oldLinkSuffix;
1178 m_generator->generateUseNode(info);
1179 handleBaseNodeEnd(node);
1184 QSvgNode *link = node->childToRender();
1188 QString oldLinkSuffix = m_linkSuffix;
1189 m_linkSuffix += QStringLiteral(
"_switch") + QString::number(quintptr(node), 16);
1190 QSvgVisitor::traverse(link);
1191 m_linkSuffix = oldLinkSuffix;
1204 return m_pregeneratingReferencedNodes;
1214 if (m_pregeneratingReferencedNodes) {
1215 handleBaseNodeSetup(node);
1218 fillCommonNodeInfo(node, info);
1219 fillAnimationInfo(node, info);
1223 QSvgRectF r = node->rect();
1225 info.patternRect = r;
1227 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1228 qCWarning(lcQuickVectorImage) <<
"Only user space content units supported for patterns";
1230 return m_generator->generatePatternNode(info);
1238 Q_ASSERT(m_pregeneratingReferencedNodes);
1240 handleBaseNodeSetup(node);
1243 fillCommonNodeInfo(node, info);
1244 fillAnimationInfo(node, info);
1246 QSvgRectF r = node->rect();
1248 info.patternRect = r;
1252 m_generator->generatePatternNode(info);
1257 if (m_useLevel == 0)
1260 handleBaseNodeSetup(node);
1263 fillCommonNodeInfo(node, info);
1264 fillAnimationInfo(node, info);
1266 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
1267 info.clipBox = oldTransform.mapRect(node->clipRect());
1269 QTransform xform = node->aspectRatioTransform();
1270 if (!xform.isIdentity()) {
1271 info.isDefaultTransform =
false;
1272 xform = xform * oldTransform;
1273 info.transform.setDefaultValue(QVariant::fromValue(xform));
1277 return m_generator->generateStructureNode(info);
1282 handleBaseNodeSetup(node);
1285 fillCommonNodeInfo(node, info);
1286 fillAnimationInfo(node, info);
1288 info.clipBox = node->clipRect();
1291 m_generator->generateStructureNode(info);
1296 if (!m_pregeneratingReferencedNodes)
1299 handleBaseNodeSetup(node);
1303 QSvgRectF r = node->rect();
1308 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1310 fillCommonNodeInfo(node, info);
1312 return m_generator->generateMaskNode(info);
1320 QSvgRectF r = node->rect();
1324 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1325 fillCommonNodeInfo(node, info);
1327 m_generator->generateMaskNode(info);
1329 handleBaseNodeEnd(node);
1336 if (!m_pregeneratingReferencedNodes)
1339 if (!m_filterPrimitives.isEmpty()) {
1340 qCWarning(lcQuickVectorImage) <<
"Filter defined inside a filter";
1349 if (m_filterPrimitives.isEmpty())
1352 handleBaseNodeSetup(node);
1355 fillCommonNodeInfo(node, info);
1357 info.filterRect = node->rect();
1358 if (node->filterUnits() == QtSvg::UnitTypes::objectBoundingBox)
1361 bool generatedAlpha =
false;
1362 for (
const QSvgFeFilterPrimitive *filterPrimitive : std::as_const(m_filterPrimitives)) {
1363 if (filterPrimitive->requiresSourceAlpha() && !generatedAlpha) {
1364 FilterNodeInfo::FilterStep alphaStep;
1365 alphaStep.filterType = FilterNodeInfo::Type::ColorMatrix;
1366 alphaStep.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
1369 qreal values[] = { 0.0, 0.0, 0.0, 0.0, 0.0,
1370 0.0, 0.0, 0.0, 0.0, 0.0,
1371 0.0, 0.0, 0.0, 0.0, 0.0,
1372 0.0, 0.0, 0.0, 1.0, 0.0,
1373 0.0, 0.0, 0.0, 0.0, 0.0 };
1374 QGenericMatrix<5, 5, qreal> matrix(values);
1375 alphaStep.filterParameter = QVariant::fromValue(matrix);
1376 alphaStep.outputName = info.id + QStringLiteral(
"_source_alpha");
1377 generatedAlpha =
true;
1379 info.steps.append(alphaStep);
1382 fillFilterPrimitiveInfo(node, filterPrimitive, info);
1385 m_generator->generateFilterNode(info);
1386 m_filterPrimitives.clear();
1389void QSvgVisitorImpl::fillFilterPrimitiveInfo(
const QSvgFilterContainer *node,
1390 const QSvgFeFilterPrimitive *filterPrimitive,
1394 step.filterPrimitiveRect = filterPrimitive->rect();
1396 step.outputName = info.id + QStringLiteral(
"_")
1397 + (filterPrimitive->result().isEmpty()
1398 ? QStringLiteral(
"output_") + QString::number(info.steps.size())
1399 : filterPrimitive->result());
1401 auto findInput = [&info, &filterPrimitive](
const QString &input, QString *outName) {
1402 const QString alphaSource = info.id + QStringLiteral(
"_source_alpha");
1403 if (input == QStringLiteral(
"SourceGraphic")) {
1405 }
else if (input == QStringLiteral(
"SourceAlpha")) {
1406 *outName = alphaSource;
1408 }
else if (info.steps.isEmpty()) {
1412 if (!input.isEmpty()) {
1413 *outName = info.id + QStringLiteral(
"_") + input;
1415 bool insideMergeNode = filterPrimitive->type() == QSvgNode::FeMergenode;
1416 for (
int i = info.steps.size() - 1; i >= 0; --i) {
1417 const auto &prevStep = info.steps.at(i);
1419 insideMergeNode =
false;
1423 if (!prevStep.outputName.isEmpty() && prevStep.outputName != alphaSource) {
1424 *outName = prevStep.outputName;
1433 step
.input1 = findInput(filterPrimitive->input(), &step.namedInput1);
1435 if (node->primitiveUnits() == QtSvg::UnitTypes::objectBoundingBox)
1442 if (node->primitiveUnits() == QtSvg::UnitTypes::userSpaceOnUse
1443 && filterPrimitive->rect().unitW() == QtSvg::UnitTypes::unknown) {
1447 switch (filterPrimitive->type()) {
1448 case QSvgNode::FeMerge:
1451 case QSvgNode::FeMergenode:
1454 case QSvgNode::FeBlend:
1456 const QSvgFeBlend *blend =
static_cast<
const QSvgFeBlend *>(filterPrimitive);
1457 switch (blend->mode()) {
1458 case QSvgFeBlend::Mode::Normal:
1461 case QSvgFeBlend::Mode::Multiply:
1464 case QSvgFeBlend::Mode::Screen:
1467 case QSvgFeBlend::Mode::Darken:
1470 case QSvgFeBlend::Mode::Lighten:
1475 step
.input2 = findInput(blend->input2(), &step.namedInput2);
1478 case QSvgNode::FeComposite:
1480 const QSvgFeComposite *composite =
static_cast<
const QSvgFeComposite *>(filterPrimitive);
1481 switch (composite->compositionOperator()) {
1482 case QSvgFeComposite::Operator::Over:
1485 case QSvgFeComposite::Operator::In:
1488 case QSvgFeComposite::Operator::Out:
1491 case QSvgFeComposite::Operator::Atop:
1494 case QSvgFeComposite::Operator::Xor:
1497 case QSvgFeComposite::Operator::Lighter:
1500 case QSvgFeComposite::Operator::Arithmetic:
1505 step
.input2 = findInput(composite->input2(), &step.namedInput2);
1506 step.filterParameter = composite->k();
1509 case QSvgNode::FeOffset:
1511 const QSvgFeOffset *offset =
static_cast<
const QSvgFeOffset *>(filterPrimitive);
1513 step.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
1517 case QSvgNode::FeColormatrix:
1519 const QSvgFeColorMatrix *colorMatrix =
1520 static_cast<
const QSvgFeColorMatrix *>(filterPrimitive);
1522 step.filterParameter = QVariant::fromValue(colorMatrix->matrix());
1526 case QSvgNode::FeGaussianblur:
1528 const QSvgFeGaussianBlur *gaussianBlur =
1529 static_cast<
const QSvgFeGaussianBlur *>(filterPrimitive);
1531 if (gaussianBlur->edgeMode() == QSvgFeGaussianBlur::EdgeMode::Wrap)
1532 info.wrapMode = QSGTexture::Repeat;
1534 if (!qFuzzyCompare(gaussianBlur->stdDeviationX(), gaussianBlur->stdDeviationY()))
1535 qCWarning(lcQuickVectorImage) <<
"Separate X and Y deviations not supported for gaussian blur";
1536 step.filterParameter =
std::max(gaussianBlur->stdDeviationX(),
1537 gaussianBlur->stdDeviationY());
1541 case QSvgNode::FeFlood:
1543 const QSvgFeFlood *flood =
1544 static_cast<
const QSvgFeFlood *>(filterPrimitive);
1547 step.filterParameter = flood->color();
1556 info.steps.append(step);
1561 m_filterPrimitives.append(node);
1573 constexpr bool forceSeparatePaths =
false;
1574 handleBaseNodeSetup(node);
1578 fillCommonNodeInfo(node, info);
1579 fillAnimationInfo(node, info);
1582 if (!info.isDefaultOpacity || info.opacity.isAnimated())
1583 info.opacityGroupBounds = node->bounds(m_doc);
1586 return m_generator->generateStructureNode(info);
1592 handleBaseNodeEnd(node);
1597 fillCommonNodeInfo(node, info);
1601 m_generator->generateStructureNode(info);
1606 return QStringLiteral(
"_qt_node%1").arg(m_nodeIdCounter++);
1611 handleBaseNodeSetup(node);
1614 fillCommonNodeInfo(node, info);
1615 fillAnimationInfo(node, info);
1617 const QSvgDocument *doc =
static_cast<
const QSvgDocument *>(node);
1618 info.size = doc->size();
1619 info.viewBox = doc->viewBox();
1624 if (m_generator->generateRootNode(info)) {
1625 pregenerateReferencedNodes(node);
1634 handleBaseNodeEnd(node);
1635 qCDebug(lcQuickVectorImage) <<
"REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen)
1636 << m_styleResolver->painter().pen().color().name() << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1637 << m_styleResolver->painter().pen().brush().color().name();
1640 fillCommonNodeInfo(node, info);
1643 m_generator->generateRootNode(info);
1648 QString ret = m_idForNodeId.value(id);
1649 if (ret.isEmpty()) {
1651 m_idForNodeId.insert(id, ret);
1656QString
QSvgVisitorImpl::findOrCreateId(
const QSvgNode *node,
const QString &nodeId)
1658 QString key = nodeId;
1659 const QSvgNode *n = m_nodesForKeys.value(key);
1660 if (key.isEmpty() || (n !=
nullptr && n != node))
1661 key = QString::number(quintptr(node), 16);
1663 m_nodesForKeys.insert(key, node);
1664 return findOrCreateId(key);
1669 const QString nodeId = scrub(node->nodeId());
1670 info.id = findOrCreateId(node, nodeId);
1673 info.id += idSuffix;
1675 if (!m_linkSuffix.isEmpty())
1676 info.id += m_linkSuffix;
1678 info.nodeId = nodeId;
1679 info.typeName = node->typeName();
1682 auto transform =
static_cast<QSvgTransformStyle *>(node->style().property(QSvgStyleProperty::Transform));
1684 info.transform.setDefaultValue(QVariant::fromValue(xf));
1686 auto opacity =
static_cast<QSvgOpacityStyle *>(node->style().property(QSvgStyleProperty::Opacity));
1687 info
.isDefaultOpacity = node->style().isDefaultProperty(QSvgStyleProperty::Opacity);
1688 info.opacity.setDefaultValue(!info
.isDefaultOpacity ? opacity->opacity() : 1.0);
1690 info
.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1692 if (node->hasFilter()
1694 || node->type() == QSvgNode::Type::Mask
1695 || node->type() == QSvgNode::Type::Pattern) {
1696 QImage dummy(1, 1, QImage::Format_RGB32);
1698 p.setPen(QPen(Qt::NoPen));
1701 QSvgExtraStates states(m_doc);
1702 b = node->internalBounds(&p, states);
1707 if (node->hasMask())
1708 info.maskId = findOrCreateId(node->maskId());
1710 if (node->hasFilter())
1711 info.filterId = findOrCreateId(node->filterId());
1714QList<QSvgVisitorImpl::AnimationPair>
QSvgVisitorImpl::collectAnimations(
const QSvgNode *node,
1715 const QString &propertyName)
1718 QList<AnimationPair> ret;
1719 const QList<QSvgAbstractAnimation *> animations = m_doc->animator()->animationsForNode(node);
1720 for (
const QSvgAbstractAnimation *animation : animations) {
1721 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1722 for (
const QSvgAbstractAnimatedProperty *property : properties) {
1723 if (property->propertyName() == propertyName)
1724 ret.append(std::make_pair(animation, property));
1731void QSvgVisitorImpl::applyAnimationsToProperty(
const QList<AnimationPair> &animations,
1732 QQuickAnimatedProperty *outProperty,
1733 std::function<QVariant(
const QSvgAbstractAnimatedProperty *,
int index,
int animationIndex)> calculateValue)
1735 qCDebug(lcVectorImageAnimations) <<
"Applying animations to property with default value"
1736 << outProperty->defaultValue();
1737 for (
auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1738 qCDebug(lcVectorImageAnimations) <<
" -> Add animation";
1739 const QSvgAbstractAnimation *animation = it->first;
1740 const QSvgAbstractAnimatedProperty *property = it->second;
1742 const int start = animation->start();
1743 const int repeatCount = animation->iterationCount();
1744 const int duration = animation->duration();
1746 bool freeze =
false;
1747 bool replace =
true;
1748 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1749 const QSvgAnimateNode *animateNode =
static_cast<
const QSvgAnimateNode *>(animation);
1750 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1751 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1754 qCDebug(lcVectorImageAnimations) <<
" -> Start:" << start
1755 <<
", repeatCount:" << repeatCount
1756 <<
", freeze:" << freeze
1757 <<
", replace:" << replace;
1759 QList<qreal> propertyKeyFrames = property->keyFrames();
1760 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1764 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1765 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1766 const auto &components = transformProperty->components();
1767 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1768 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1769 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1770 outAnimation.repeatCount = repeatCount;
1771 outAnimation.startOffset = start;
1773 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1775 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1776 switch (components.at(i).type) {
1777 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1778 outAnimation.subtype = QTransform::TxTranslate;
1780 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1781 outAnimation.subtype = QTransform::TxScale;
1783 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1784 outAnimation.subtype = QTransform::TxRotate;
1786 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1787 outAnimation.subtype = QTransform::TxShear;
1790 qCWarning(lcQuickVectorImage()) <<
"Unhandled transform type:" << components.at(i).type;
1794 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1797 << property->propertyName()
1798 <<
" animation subtype:"
1799 << outAnimation.subtype;
1801 outAnimations.append(outAnimation);
1804 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1805 outAnimation.repeatCount = repeatCount;
1806 outAnimation.startOffset = start;
1808 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1810 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1813 << property->propertyName();
1815 outAnimations.append(outAnimation);
1818 outProperty->beginAnimationGroup();
1819 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
1820 for (
int i = 0; i < outAnimations.size(); ++i) {
1821 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1823 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
1824 const int time = qRound(propertyKeyFrames.at(j) * duration);
1826 const QVariant value = calculateValue(property, j, i);
1827 outAnimation.frames[time] = value;
1829 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
1830 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
1831 ? easingForAnimation(easingInterface, animation->animationType())
1834 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << value;
1837 outProperty->addAnimation(outAnimation);
1842QBezier
QSvgVisitorImpl::easingForAnimation(
const QSvgEasingInterface *easingInterface,
1843 QSvgAbstractAnimation::AnimationType type)
1845 constexpr QPointF startControlPoint(0, 0);
1846 constexpr QPointF endControlPoint(1, 1);
1847 constexpr QPointF easeC1(0.25, 0.1);
1848 constexpr QPointF easeC2(0.25, 1);
1850 QBezier easing = QBezier::fromPoints(startControlPoint, startControlPoint, endControlPoint, endControlPoint);
1852#if QT_CONFIG(cssparser)
1853 if (type == QSvgAbstractAnimation::CSS) {
1854 const QSvgCssEasing *cssEasing =
static_cast<
const QSvgCssEasing *>(easingInterface);
1855 switch (cssEasing->easingFunction()) {
1856 case QSvgCssValues::EasingFunction::Ease:
1857 case QSvgCssValues::EasingFunction::EaseIn:
1858 case QSvgCssValues::EasingFunction::EaseOut:
1859 case QSvgCssValues::EasingFunction::EaseInOut:
1860 case QSvgCssValues::EasingFunction::CubicBezier:
1861 case QSvgCssValues::EasingFunction::Linear:
1863 const QSvgCssCubicBezierEasing *cssCubicEasing =
static_cast<
const QSvgCssCubicBezierEasing *>(cssEasing);
1864 QPointF c1 = cssCubicEasing->c1();
1865 QPointF c2 = cssCubicEasing->c2();
1866 easing = QBezier::fromPoints(startControlPoint, c1, c2, endControlPoint);
1869 case QSvgCssValues::EasingFunction::Steps:
1871 qCDebug(lcVectorImageAnimations) <<
"Step easing is not supported reverting to default.";
1872 easing = QBezier::fromPoints(startControlPoint, easeC1, easeC2, endControlPoint);
1886 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1887 if (!animations.isEmpty())
1888 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1892 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1893 if (!animations.isEmpty())
1894 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1898 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1899 if (!animations.isEmpty())
1900 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1904 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1905 if (!animations.isEmpty())
1906 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1912 qCDebug(lcVectorImageAnimations) <<
"Applying transform animations to property with default value"
1913 << info.transform.defaultValue();
1915 auto calculateValue = [](
const QSvgAbstractAnimatedProperty *property,
int index,
int animationIndex) {
1916 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1919 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1920 const auto &components = transformProperty->components();
1922 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1924 QVariantList parameters;
1926 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1927 switch (component.type) {
1928 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1929 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1930 component.values.value(1))));
1932 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1933 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1934 component.values.value(2))));
1935 parameters.append(QVariant::fromValue(component.values.value(0)));
1937 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1938 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1939 component.values.value(1))));
1941 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1942 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1943 component.values.value(1))));
1946 qCWarning(lcVectorImageAnimations) <<
"Unhandled transform type:" << component.type;
1949 return QVariant::fromValue(parameters);
1954 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"transform"));
1955 if (!animations.isEmpty())
1956 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1962 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"offset-distance"));
1963 auto offset =
static_cast<QSvgOffsetStyle *>(node->style().property(QSvgStyleProperty::Offset));
1965 if (animations.isEmpty())
1969 qCWarning(lcQuickVectorImage) <<
"Motion path animation: No offset path";
1973 if (animations.size() > 1) {
1974 qCWarning(lcQuickVectorImage)
1975 <<
"Not supported: More than one offset path animation on same node";
1978 const AnimationPair &animationPair = animations.first();
1980 const QSvgAbstractAnimation *animation = animationPair.first;
1981 const QSvgAbstractAnimatedProperty *property = animationPair.second;
1983 const int start = animation->start();
1984 const int repeatCount = animation->iterationCount();
1985 const int duration = animation->duration();
1987 qCDebug(lcVectorImageAnimations) <<
"Motion path animation:"
1988 <<
"start == " << start
1989 <<
", repeatCount == " << repeatCount
1990 <<
"; duration == " << duration;
1993 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1994 outAnimation.repeatCount = repeatCount;
1995 outAnimation.startOffset = start;
1997 QPainterPath originalPath = offset->path();
2001 switch (offset->rotateType()) {
2002 case QtSvg::OffsetRotateType::Auto:
2006 case QtSvg::OffsetRotateType::Angle:
2008 baseRotation = offset->rotateAngle();
2010 case QtSvg::OffsetRotateType::AutoAngle:
2012 baseRotation = offset->rotateAngle();
2014 case QtSvg::OffsetRotateType::Reverse:
2016 baseRotation = 180.0;
2018 case QtSvg::OffsetRotateType::ReverseAngle:
2020 baseRotation = offset->rotateAngle() + 180.0f;
2027 QVariantList params({ QVariant::fromValue(originalPath), adaptAngle, baseRotation });
2028 info.motionPath.setDefaultValue(params);
2030 const QList<qreal> propertyKeyFrames = property->keyFrames();
2031 outAnimation.frames[0] = qreal(0);
2032 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
2033 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
2034 const int time = qRound(propertyKeyFrames.at(j) * duration);
2036 qreal t = calculateInterpolatedValue(property, j, 0).toReal();
2037 outAnimation.frames[time] = t;
2039 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
2040 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
2041 ? easingForAnimation(easingInterface, animation->animationType())
2044 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << t;
2048 info.motionPath.addAnimation(outAnimation);
2053 fillColorAnimationInfo(node, info);
2054 fillAnimationInfo(node, info);
2060 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"opacity"));
2061 if (!animations.isEmpty())
2062 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
2065 fillTransformAnimationInfo(node, info);
2066 fillMotionPathAnimationInfo(node, info);
2071 qCDebug(lcQuickVectorImage) <<
"Before SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2072 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2073 << node->nodeId() <<
" type: " << node->typeName() <<
" " << node->type();
2075 node->applyStyle(&m_styleResolver->painter(), m_styleResolver->states());
2077 qCDebug(lcQuickVectorImage) <<
"After SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2078 <<
"stroke" << m_styleResolver->currentStrokeColor()
2079 << m_styleResolver->currentStrokeWidth() << node->nodeId();
2085 fillCommonNodeInfo(node, info);
2087 m_generator->generateNodeBase(info);
2092 node->revertStyle(&m_styleResolver->painter(), m_styleResolver->states());
2094 qCDebug(lcQuickVectorImage) <<
"After END" << node <<
"fill" << m_styleResolver->currentFillColor()
2095 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2099void QSvgVisitorImpl::handlePathNode(
const QSvgNode *node,
const QPainterPath &path)
2101 handleBaseNodeSetup(node);
2104 fillCommonNodeInfo(node, info);
2106 if (node->hasMarkerStart())
2107 info.markerStartId = findOrCreateId(node->markerStartId());
2109 if (node->hasMarkerMid())
2110 info.markerMidId = findOrCreateId(node->markerMidId());
2112 if (node->hasMarkerEnd())
2113 info.markerEndId = findOrCreateId(node->markerEndId());
2115 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
2116 auto strokeStyle =
static_cast<QSvgStrokeStyle *>(node->style().property(QSvgStyleProperty::Stroke));
2117 bool hasStrokePattern = strokeStyle
2118 && strokeStyle->paintServer()
2119 && strokeStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern;
2121 info.path.setDefaultValue(QVariant::fromValue(path));
2122 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
2123 if (strokeGradient !=
nullptr)
2124 info.strokeGrad = *strokeGradient;
2126 if (!hasStrokePattern) {
2127 info.strokeStyle = StrokeStyle::fromPen(m_styleResolver->currentStroke());
2128 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
2130 if (m_styleResolver->currentFillGradient() !=
nullptr)
2131 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
2132 info.fillTransform = m_styleResolver->currentFillTransform();
2134 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
2136 info.fillRule = fillStyle->fillRule();
2138 if (fillStyle->paintServer()
2139 && fillStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
2140 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(fillStyle->paintServer());
2141 info.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2147 info.fillTransform = paintServer->patternNode()->transform();
2151 fillPathAnimationInfo(node, info);
2153 m_generator->generatePath(info);
2155 if (hasStrokePattern) {
2157 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke"));
2159 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(strokeStyle->paintServer());
2160 strokeInfo.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2161 strokeInfo.fillTransform = paintServer->patternNode()->transform();
2163 QPainterPathStroker stroker(m_styleResolver->currentStroke());
2164 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(path)));
2165 m_generator->generatePath(strokeInfo);
2168 handleBaseNodeEnd(node);
2173 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
2175 info.markerSize = node->rect().size();
2176 info.anchorPoint = node->refP();
2177 info.clipBox = oldTransform.mapRect(node->clipRect());
2178 info.viewBox = node->viewBox();
2179 switch (node->orientation()) {
2180 case QSvgMarker::Orientation::Auto:
2183 case QSvgMarker::Orientation::AutoStartReverse:
2186 case QSvgMarker::Orientation::Value:
2191 switch (node->markerUnits()) {
2192 case QSvgMarker::MarkerUnits::UserSpaceOnUse:
2195 case QSvgMarker::MarkerUnits::StrokeWidth:
2200 info.angle = node->orientationAngle();
2202 QTransform xform = node->aspectRatioTransform();
2203 if (!xform.isIdentity()) {
2204 info.isDefaultTransform =
false;
2205 xform = xform * oldTransform;
2206 info.transform.setDefaultValue(QVariant::fromValue(xform));
2214 if (!m_pregeneratingReferencedNodes)
2217 handleBaseNodeSetup(node);
2221 fillCommonNodeInfo(node, info);
2222 fillAnimationInfo(node, info);
2223 fillMarkerInfo(node, info);
2226 return m_generator->generateMarkerNode(info);
2231 handleBaseNodeEnd(node);
2234 fillCommonNodeInfo(node, info);
2235 fillMarkerInfo(node, info);
2238 m_generator->generateMarkerNode(info);
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)
CoordinateSystem csFilterParameter
CoordinateSystem csFilterRect
PreserveAspectRatio preserveAspectRatio
bool isMaskContentRelativeCoordinates
bool isMaskRectRelativeCoordinates
bool isPatternRectRelativeCoordinates