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>
33#include <QtSvg/private/qsvgstyle_p.h>
34#include <QtSvg/private/qsvgfilter_p.h>
38Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations,
"qt.quick.vectorimage.animations")
40using namespace Qt::StringLiterals;
47 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
48 m_dummyPainter.begin(&m_dummyImage);
49 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
50 defaultPen.setMiterLimit(4);
51 m_dummyPainter.setPen(defaultPen);
52 m_dummyPainter.setBrush(Qt::black);
65 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
66 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
67 return QColor(QColorConstants::Transparent);
71 fillColor = m_dummyPainter.brush().color();
72 fillColor.setAlphaF(m_svgState.fillOpacity);
79 return m_svgState.fillOpacity;
84 QBrush brush = m_dummyPainter.pen().brush();
85 if (brush.style() == Qt::LinearGradientPattern
86 || brush.style() == Qt::RadialGradientPattern
87 || brush.style() == Qt::ConicalGradientPattern) {
88 return brush.gradient();
95 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
96 return m_dummyPainter.brush().gradient();
102 return m_dummyPainter.brush().transform();
107 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
108 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
109 return QColor(QColorConstants::Transparent);
113 strokeColor = m_dummyPainter.pen().brush().color();
114 strokeColor.setAlphaF(m_svgState.strokeOpacity);
121 QGradient grad = gradient;
122 QGradientStops stops;
123 for (
auto &stop : grad.stops()) {
124 stop.second.setAlphaF(stop.second.alphaF() * opacity);
128 grad.setStops(stops);
135 float penWidth = m_dummyPainter.pen().widthF();
136 return penWidth ? penWidth : 1;
141 return m_dummyPainter.pen();
151inline bool isPathContainer(
const QSvgStructureNode *node)
153 bool foundPath =
false;
154 for (
const auto &child : node->renderers()) {
155 switch (child->type()) {
157 case QSvgNode::Switch:
159 case QSvgNode::Group:
160 case QSvgNode::AnimateColor:
161 case QSvgNode::AnimateTransform:
163 case QSvgNode::Video:
164 case QSvgNode::Image:
165 case QSvgNode::Textarea:
167 case QSvgNode::Tspan:
169 case QSvgNode::Marker:
170 case QSvgNode::Pattern:
176 case QSvgNode::Symbol:
181 case QSvgNode::Circle:
182 case QSvgNode::Ellipse:
185 case QSvgNode::Polygon:
186 case QSvgNode::Polyline:
188 if (child->hasFilter())
191 if (child->hasAnyMarker())
194 if (!child->style().isDefaultProperty(QSvgStyleProperty::Opacity))
197 if (!child->style().isDefaultProperty(QSvgStyleProperty::Transform))
200 const auto animations = child->document()->animator()->animationsForNode(child.get());
201 if (!animations.isEmpty()) {
209 qCDebug(lcQuickVectorImage) <<
"Unhandled type in switch" << child->type();
217static QString capStyleName(Qt::PenCapStyle style)
223 styleName = QStringLiteral(
"squarecap");
226 styleName = QStringLiteral(
"flatcap");
229 styleName = QStringLiteral(
"roundcap");
238static QString joinStyleName(Qt::PenJoinStyle style)
244 styleName = QStringLiteral(
"miterjoin");
247 styleName = QStringLiteral(
"beveljoin");
250 styleName = QStringLiteral(
"roundjoin");
252 case Qt::SvgMiterJoin:
253 styleName = QStringLiteral(
"svgmiterjoin");
262static QString dashArrayString(QList<qreal> dashArray)
264 if (dashArray.isEmpty())
267 QString dashArrayString;
268 QTextStream stream(&dashArrayString);
270 for (
int i = 0; i < dashArray.length() - 1; i++) {
271 qreal value = dashArray[i];
272 stream << value <<
", ";
275 stream << dashArray.last();
277 return dashArrayString;
281static QString
scrub(
const QString &raw)
283 QString res(raw.left(80));
285 if (!res.isEmpty()) {
286 constexpr QLatin1StringView legalSymbols(
"_-.:");
289 if (res.at(i).isLetterOrNumber() || legalSymbols.contains(res.at(i)))
293 }
while (i < res.size());
300 QQuickGenerator *generator,
301 bool assumeTrustedSource)
304 , m_assumeTrustedSource(assumeTrustedSource)
314 fillCommonNodeInfo(node, info);
320 if (node->type() == QSvgNode::Pattern) {
321 info.transform = QQuickAnimatedProperty(QVariant::fromValue(QTransform{}));
322 info.isDefaultTransform =
true;
325 if (!m_generator->generateDefsNode(info))
334 fillCommonNodeInfo(node, info);
338 m_generator->generateDefsNode(info);
343 switch (node->type()) {
344 case QSvgNode::Switch:
347 case QSvgNode::Group:
349 case QSvgNode::Symbol:
350 case QSvgNode::Filter:
351 case QSvgNode::FeMerge:
352 case QSvgNode::FeMergenode:
353 case QSvgNode::FeColormatrix:
354 case QSvgNode::FeGaussianblur:
355 case QSvgNode::FeOffset:
356 case QSvgNode::FeComposite:
357 case QSvgNode::FeFlood:
358 case QSvgNode::FeBlend:
359 case QSvgNode::Marker:
360 case QSvgNode::Pattern:
367static void recurseSvgNodes(
const QSvgNode *root,
const std::function<
void(
const QSvgNode *)> &fnc)
371 if (isStructureNode(root)) {
372 const QSvgStructureNode *sn =
static_cast<
const QSvgStructureNode *>(root);
373 for (
const auto &child : sn->renderers())
374 recurseSvgNodes(child.get(), fnc);
380 Q_ASSERT(m_generator !=
nullptr);
384 QSet<QString> referencedIds;
385 auto findReferencedIds = [&referencedIds](
const QSvgNode *node) {
386 if (node->hasFilter())
387 referencedIds.insert(node->filterId());
389 referencedIds.insert(node->maskId());
390 if (node->hasMarkerStart())
391 referencedIds.insert(node->markerStartId());
392 if (node->hasMarkerMid())
393 referencedIds.insert(node->markerMidId());
394 if (node->hasMarkerEnd())
395 referencedIds.insert(node->markerEndId());
396 if (node->type() == QSvgNode::Pattern)
397 referencedIds.insert(node->nodeId());
399 recurseSvgNodes(doc, findReferencedIds);
401 m_pregeneratingReferencedNodes =
true;
402 for (
const QString &referencedId : referencedIds) {
403 const QSvgNode *referencedNode = doc->document()->namedNode(referencedId);
404 if (referencedNode ==
nullptr)
407 if (!startDefsBlock(referencedNode))
410 traverse(referencedNode);
412 endDefsBlock(referencedNode);
415 m_pregeneratingReferencedNodes =
false;
421 qCDebug(lcQuickVectorImage) <<
"No valid QQuickGenerator is set. Genration will stop";
425 QtSvg::Options options;
426 if (m_assumeTrustedSource)
427 options.setFlag(QtSvg::AssumeTrustedSource);
429 const auto doc = QSvgDocument::load(m_svgFileName, options);
431 qCDebug(lcQuickVectorImage) <<
"Not a valid Svg File : " << m_svgFileName;
435 QSvgVisitor::traverse(doc.get());
442 handleBaseNodeSetup(node);
445 fillCommonNodeInfo(node, info);
446 fillAnimationInfo(node, info);
448 m_generator->generateNode(info);
450 handleBaseNodeEnd(node);
456 handleBaseNodeSetup(node);
459 fillCommonNodeInfo(node, info);
460 fillAnimationInfo(node, info);
461 info.image = node->image();
462 info.rect = node->rect();
463 info.externalFileReference = node->filename();
465 m_generator->generateImageNode(info);
467 handleBaseNodeEnd(node);
472 QRectF rect = node->rect();
473 QPointF rads = node->radius();
475 qreal x1 = rect.left();
476 qreal x2 = rect.right();
477 qreal y1 = rect.top();
478 qreal y2 = rect.bottom();
480 qreal rx = rads.x() * rect.width() / 200;
481 qreal ry = rads.y() * rect.height() / 200;
484 p.moveTo(x1 + rx, y1);
485 p.lineTo(x2 - rx, y1);
487 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90);
490 p.lineTo(x2, y2 - ry);
491 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90);
493 p.lineTo(x1 + rx, y2);
494 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90);
496 p.lineTo(x1, y1 + ry);
497 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90);
499 handlePathNode(node, p);
504 QRectF rect = node->rect();
509 handlePathNode(node, p);
514 handlePathNode(node, node->path());
520 p.moveTo(node->line().p1());
521 p.lineTo(node->line().p2());
522 handlePathNode(node, p);
527 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
true);
528 handlePathNode(node, p);
533 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
false);
534 handlePathNode(node, p);
537QString
QSvgVisitorImpl::gradientCssDescription(
const QGradient *gradient)
539 QString cssDescription;
540 if (gradient->type() == QGradient::LinearGradient) {
541 const QLinearGradient *linearGradient =
static_cast<
const QLinearGradient *>(gradient);
543 cssDescription +=
" -qt-foreground: qlineargradient("_L1;
544 cssDescription +=
"x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
545 cssDescription +=
"y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
546 cssDescription +=
"x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
547 cssDescription +=
"y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
548 }
else if (gradient->type() == QGradient::RadialGradient) {
549 const QRadialGradient *radialGradient =
static_cast<
const QRadialGradient *>(gradient);
551 cssDescription +=
" -qt-foreground: qradialgradient("_L1;
552 cssDescription +=
"cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
553 cssDescription +=
"cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
554 cssDescription +=
"fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
555 cssDescription +=
"fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
556 cssDescription +=
"radius:"_L1 + QString::number(radialGradient->radius()) + u',';
558 const QConicalGradient *conicalGradient =
static_cast<
const QConicalGradient *>(gradient);
560 cssDescription +=
" -qt-foreground: qconicalgradient("_L1;
561 cssDescription +=
"cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
562 cssDescription +=
"cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
563 cssDescription +=
"angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
566 const QStringList coordinateModes = {
"logical"_L1,
"stretchtodevice"_L1,
"objectbounding"_L1,
"object"_L1 };
567 cssDescription +=
"coordinatemode:"_L1;
568 cssDescription += coordinateModes.at(
int(gradient->coordinateMode()));
569 cssDescription += u',';
571 const QStringList spreads = {
"pad"_L1,
"reflect"_L1,
"repeat"_L1 };
572 cssDescription +=
"spread:"_L1;
573 cssDescription += spreads.at(
int(gradient->spread()));
575 for (
const QGradientStop &stop : gradient->stops()) {
576 cssDescription +=
",stop:"_L1;
577 cssDescription += QString::number(stop.first);
578 cssDescription += u' ';
579 cssDescription += stop.second.name(QColor::HexArgb);
582 cssDescription +=
");"_L1;
584 return cssDescription;
589 QString cssDescription;
590 cssDescription += QStringLiteral(
"rgba(");
591 cssDescription += QString::number(color.red()) + QStringLiteral(
",");
592 cssDescription += QString::number(color.green()) + QStringLiteral(
",");
593 cssDescription += QString::number(color.blue()) + QStringLiteral(
",");
594 cssDescription += QString::number(color.alphaF()) + QStringLiteral(
")");
596 return cssDescription;
605 class QSvgFontEngine :
public QFontEngine
608 QSvgFontEngine(
const QSvgFont *font, qreal size);
610 QFontEngine *cloneWithSize(qreal size)
const override;
612 glyph_t glyphIndex(uint ucs4)
const override;
613 int stringToCMap(
const QChar *str,
615 QGlyphLayout *glyphs,
617 ShaperFlags flags)
const override;
619 void addGlyphsToPath(glyph_t *glyphs,
620 QFixedPoint *positions,
623 QTextItem::RenderFlags flags)
override;
625 glyph_metrics_t boundingBox(glyph_t glyph)
override;
627 void recalcAdvances(QGlyphLayout *, ShaperFlags)
const override;
628 QFixed ascent()
const override;
629 QFixed capHeight()
const override;
630 QFixed descent()
const override;
631 QFixed leading()
const override;
632 qreal maxCharWidth()
const override;
633 qreal minLeftBearing()
const override;
634 qreal minRightBearing()
const override;
636 QFixed emSquareSize()
const override;
639 const QSvgFont *m_font;
642 QSvgFontEngine::QSvgFontEngine(
const QSvgFont *font, qreal size)
646 fontDef.pixelSize = size;
647 fontDef.families = QStringList(m_font->m_familyName);
650 QFixed QSvgFontEngine::emSquareSize()
const
652 return QFixed::fromReal(m_font->m_unitsPerEm);
655 glyph_t QSvgFontEngine::glyphIndex(uint ucs4)
const
657 const ushort c(ucs4);
658 if (ucs4 < USHRT_MAX && m_font->findFirstGlyphFor(QStringView(&c, 1)))
659 return glyph_t(ucs4);
664 int QSvgFontEngine::stringToCMap(
const QChar *str,
666 QGlyphLayout *glyphs,
668 ShaperFlags flags)
const
670 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
671 if (*nglyphs < len) {
677 QStringIterator it(str, str + len);
678 while (it.hasNext()) {
679 char32_t ucs4 = it.next();
680 glyph_t index = glyphIndex(ucs4);
681 glyphs->glyphs[ucs4Length++] = index;
684 *nglyphs = ucs4Length;
685 glyphs->numGlyphs = ucs4Length;
687 if (!(flags & GlyphIndicesOnly))
688 recalcAdvances(glyphs, flags);
693 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
694 QFixedPoint *positions,
697 QTextItem::RenderFlags flags)
700 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
701 for (
int i = 0; i < nGlyphs; ++i) {
702 glyph_t index = glyphs[i];
704 QPointF position = positions[i].toPointF();
705 const ushort c(index);
706 const QSvgGlyph *foundGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
711 QPainterPath glyphPath = foundGlyph->m_path;
714 xform.translate(position.x(), position.y());
715 xform.scale(scale, -scale);
716 glyphPath = xform.map(glyphPath);
717 path->addPath(glyphPath);
722 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
727 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
728 const ushort c(glyph);
729 const QSvgGlyph *svgGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
730 ret.width = QFixed::fromReal(svgGlyph ? svgGlyph->m_horizAdvX * scale : 0.);
731 ret.height = ascent() + descent();
735 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size)
const
737 QSvgFontEngine *otherEngine =
new QSvgFontEngine(m_font, size);
741 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags)
const
743 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
744 for (
int i = 0; i < glyphLayout->numGlyphs; i++) {
745 const ushort c(glyphLayout->glyphs[i]);
746 const QSvgGlyph *svgGl = m_font->findFirstGlyphFor(QStringView(&c, 1));
747 glyphLayout->advances[i] = QFixed::fromReal(svgGl ? svgGl->m_horizAdvX * scale : 0.);
751 QFixed QSvgFontEngine::ascent()
const
753 return QFixed::fromReal(fontDef.pixelSize);
756 QFixed QSvgFontEngine::capHeight()
const
760 QFixed QSvgFontEngine::descent()
const
765 QFixed QSvgFontEngine::leading()
const
770 qreal QSvgFontEngine::maxCharWidth()
const
772 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
773 return m_font->m_horizAdvX * scale;
776 qreal QSvgFontEngine::minLeftBearing()
const
781 qreal QSvgFontEngine::minRightBearing()
const
790 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
792 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
794 return property->interpolatedValue();
799 handleBaseNodeSetup(node);
800 const bool isTextArea = node->type() == QSvgNode::Textarea;
803 const QSvgFont *svgFont = m_styleResolver->states().svgFont;
804 bool needsRichText =
false;
805 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
806 const QGradient *mainGradient = m_styleResolver->currentFillGradient();
808 QFontEngine *fontEngine =
nullptr;
809 if (svgFont !=
nullptr) {
810 fontEngine =
new QSvgFontEngine(svgFont, m_styleResolver->painter().font().pointSize());
811 fontEngine->ref.ref();
814#if QT_CONFIG(texthtmlparser)
815 bool needsPathNode = mainGradient !=
nullptr
816 || svgFont !=
nullptr
817 || m_styleResolver->currentStrokeGradient() !=
nullptr;
819 for (
const auto *tspan : node->tspans()) {
821 text += QStringLiteral(
"<br>");
827 handleBaseNodeSetup(tspan);
828 QFont font = m_styleResolver->painter().font();
830 QString styleTagContent;
832 if ((font.resolveMask() & QFont::FamilyResolved)
833 || (font.resolveMask() & QFont::FamiliesResolved)) {
834 styleTagContent += QStringLiteral(
"font-family: %1;").arg(font.family());
837 if (font.resolveMask() & QFont::WeightResolved
838 && font.weight() != QFont::Normal
839 && font.weight() != QFont::Bold) {
840 styleTagContent += QStringLiteral(
"font-weight: %1;").arg(
int(font.weight()));
843 if (font.resolveMask() & QFont::SizeResolved) {
845 styleTagContent += QStringLiteral(
"font-size: %1px;").arg(
int(font.pointSizeF()));
848 if (font.resolveMask() & QFont::CapitalizationResolved
849 && font.capitalization() == QFont::SmallCaps) {
850 styleTagContent += QStringLiteral(
"font-variant: small-caps;");
853 if (m_styleResolver->currentFillGradient() !=
nullptr
854 && m_styleResolver->currentFillGradient() != mainGradient) {
855 const QGradient grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
856 styleTagContent += gradientCssDescription(&grad) + u';';
857#if QT_CONFIG(texthtmlparser)
858 needsPathNode =
true;
862 const QColor currentStrokeColor = m_styleResolver->currentStrokeColor();
863 if (currentStrokeColor.alpha() > 0) {
864 QString strokeColor = colorCssDescription(currentStrokeColor);
865 styleTagContent += QStringLiteral(
"-qt-stroke-color:%1;").arg(strokeColor);
866 styleTagContent += QStringLiteral(
"-qt-stroke-width:%1px;").arg(m_styleResolver->currentStrokeWidth());
867 styleTagContent += QStringLiteral(
"-qt-stroke-dasharray:%1;").arg(dashArrayString(m_styleResolver->currentStroke().dashPattern()));
868 styleTagContent += QStringLiteral(
"-qt-stroke-dashoffset:%1;").arg(m_styleResolver->currentStroke().dashOffset());
869 styleTagContent += QStringLiteral(
"-qt-stroke-lineCap:%1;").arg(capStyleName(m_styleResolver->currentStroke().capStyle()));
870 styleTagContent += QStringLiteral(
"-qt-stroke-lineJoin:%1;").arg(joinStyleName(m_styleResolver->currentStroke().joinStyle()));
871 if (m_styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || m_styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
872 styleTagContent += QStringLiteral(
"-qt-stroke-miterlimit:%1;").arg(m_styleResolver->currentStroke().miterLimit());
873#if QT_CONFIG(texthtmlparser)
874 needsPathNode =
true;
878 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
879 styleTagContent += QStringLiteral(
"white-space: pre-wrap;");
881 QString content = tspan->text().toHtmlEscaped();
882 content.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
883 content.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
885 bool fontTag =
false;
886 if (!tspan->style().isDefaultProperty(QSvgStyleProperty::Fill)) {
887 auto fill =
static_cast<QSvgFillStyle *>(tspan->style().property(QSvgStyleProperty::Fill));
888 auto &b = fill->qbrush();
889 qCDebug(lcQuickVectorImage) <<
"tspan FILL:" << b;
890 if (b.style() != Qt::NoBrush)
892 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
894 QString spanColor = b.color().name();
895 fontTag = !spanColor.isEmpty();
897 text += QStringLiteral(
"<font color=\"%1\">").arg(spanColor);
899 QString spanColor = colorCssDescription(b.color());
900 styleTagContent += QStringLiteral(
"color:%1").arg(spanColor);
905 needsRichText = needsRichText || !styleTagContent.isEmpty();
906 if (!styleTagContent.isEmpty())
907 text += QStringLiteral(
"<span style=\"%1\">").arg(styleTagContent.toHtmlEscaped());
909 if (font.resolveMask() & QFont::WeightResolved && font.bold())
910 text += QStringLiteral(
"<b>");
912 if (font.resolveMask() & QFont::StyleResolved && font.italic())
913 text += QStringLiteral(
"<i>");
915 if (font.resolveMask() & QFont::CapitalizationResolved) {
916 switch (font.capitalization()) {
917 case QFont::AllLowercase:
918 content = content.toLower();
920 case QFont::AllUppercase:
921 content = content.toUpper();
923 case QFont::Capitalize:
926 qCWarning(lcQuickVectorImage) <<
"Title case not implemented for tspan";
934 text += QStringLiteral(
"</font>");
936 if (font.resolveMask() & QFont::StyleResolved && font.italic())
937 text += QStringLiteral(
"</i>");
939 if (font.resolveMask() & QFont::WeightResolved && font.bold())
940 text += QStringLiteral(
"</b>");
942 if (!styleTagContent.isEmpty())
943 text += QStringLiteral(
"</span>");
945 handleBaseNodeEnd(tspan);
948 if (preserveWhiteSpace && (needsRichText || m_styleResolver->currentFillGradient() !=
nullptr))
949 text = QStringLiteral(
"<span style=\"white-space: pre-wrap\">") + text + QStringLiteral(
"</span>");
951 QFont font = m_styleResolver->painter().font();
952 if (font.pixelSize() <= 0 && font.pointSize() > 0)
953 font.setPixelSize(font.pointSize());
955 font.setHintingPreference(QFont::PreferNoHinting);
957#if QT_CONFIG(texthtmlparser)
959 QTextDocument document;
960 document.setHtml(text);
961 if (isTextArea && node->size().width() > 0)
962 document.setTextWidth(node->size().width());
963 document.setDefaultFont(font);
964 document.pageCount();
966 QTextBlock block = document.firstBlock();
967 while (block.isValid()) {
968 QTextLayout *lout = block.layout();
970 if (lout !=
nullptr) {
971 QRectF boundingRect = lout->boundingRect();
977 QFont blockFont = block.charFormat().font();
978 if (svgFont !=
nullptr
979 && blockFont.family() == svgFont->m_familyName) {
981 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
982 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
984 lout->setRawFont(rawFont);
987 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt,
int pathIndex) {
989 fillCommonNodeInfo(node, info, QStringLiteral(
"_path%1").arg(pathIndex));
990 fillPathAnimationInfo(node, info);
991 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
993 info.fillRule = fillStyle->fillRule();
995 if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
996 info.fillColor.setDefaultValue(fmt.foreground().color());
997 if (fmt.foreground().gradient() !=
nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
998 info.grad = *fmt.foreground().gradient();
1000 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1003 info.path.setDefaultValue(QVariant::fromValue(p));
1005 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
1007 if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
1008 pen = fmt.textOutline();
1009 if (strokeGradient ==
nullptr) {
1010 info.strokeStyle = StrokeStyle::fromPen(pen);
1011 info.strokeStyle.color.setDefaultValue(pen.color());
1014 pen = m_styleResolver->currentStroke();
1015 if (strokeGradient ==
nullptr) {
1016 info.strokeStyle = StrokeStyle::fromPen(pen);
1017 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
1021 if (info.grad.type() == QGradient::NoGradient && m_styleResolver->currentFillGradient() !=
nullptr)
1022 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
1024 info.fillTransform = m_styleResolver->currentFillTransform();
1026 m_generator->generatePath(info, boundingRect);
1028 if (strokeGradient !=
nullptr) {
1029 PathNodeInfo strokeInfo;
1030 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke%1").arg(pathIndex));
1031 fillPathAnimationInfo(node, strokeInfo);
1033 strokeInfo.grad = *strokeGradient;
1035 QPainterPathStroker stroker(pen);
1036 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(p)));
1037 m_generator->generatePath(strokeInfo, boundingRect);
1041 qreal baselineOffset = -QFontMetricsF(font).ascent();
1042 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
1043 baselineOffset = -lout->lineAt(0).ascent();
1045 const QPointF baselineTranslation(0.0, baselineOffset);
1046 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
1047 QList<QPainterPath> paths;
1048 for (
const QGlyphRun &glyphRun : glyphRuns) {
1049 QRawFont font = glyphRun.rawFont();
1050 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
1051 QList<QPointF> positions = glyphRun.positions();
1053 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
1054 quint32 glyphIndex = glyphIndexes.at(j);
1055 const QPointF &pos = positions.at(j);
1057 QPainterPath p = font.pathForGlyph(glyphIndex);
1058 p.translate(pos + node->position() + baselineTranslation);
1059 if (m_styleResolver->states().textAnchor == Qt::AlignHCenter)
1060 p.translate(QPointF(-0.5 * width, 0));
1061 else if (m_styleResolver->states().textAnchor == Qt::AlignRight)
1062 p.translate(QPointF(-width, 0));
1070 QList<QTextLayout::FormatRange> formats = block.textFormats();
1071 for (
int i = 0; i < formats.size(); ++i) {
1072 QTextLayout::FormatRange range = formats.at(i);
1074 QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
1075 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
1076 for (
int j = 0; j < paths.size(); ++j) {
1077 const QPainterPath &path = paths.at(j);
1078 addPathForFormat(path, range.format, j);
1083 block = block.next();
1089 fillCommonNodeInfo(node, info);
1090 fillAnimationInfo(node, info);
1093 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1094 if (!animations.isEmpty())
1095 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1099 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1100 if (!animations.isEmpty())
1101 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1105 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1106 if (!animations.isEmpty())
1107 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
1111 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1112 if (!animations.isEmpty())
1113 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
1116 info.position = node->position();
1117 info.size = node->size();
1122 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1123 info.alignment = m_styleResolver->states().textAnchor;
1124 info.strokeColor.setDefaultValue(m_styleResolver->currentStrokeColor());
1126 m_generator->generateTextNode(info);
1129 handleBaseNodeEnd(node);
1131 if (fontEngine !=
nullptr) {
1132 fontEngine->ref.deref();
1133 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
1140 QSvgNode *link = node->link();
1143 handleBaseNodeSetup(node);
1145 QPointF startPos = node->start();
1146 fillCommonNodeInfo(node, info);
1147 fillAnimationInfo(node, info);
1148 if (!info.bounds.isNull())
1149 info.bounds.translate(-startPos);
1151 if (!startPos.isNull()) {
1153 if (!info.isDefaultTransform)
1154 xform = info.transform.defaultValue().value<QTransform>();
1155 xform.translate(startPos.x(), startPos.y());
1156 info.transform.setDefaultValue(QVariant::fromValue(xform));
1157 info.isDefaultTransform =
false;
1159 m_generator->generateUseNode(info);
1160 QString oldLinkSuffix = m_linkSuffix;
1161 m_linkSuffix += QStringLiteral(
"_use") + info.id;
1163 QSvgVisitor::traverse(link);
1165 m_linkSuffix = oldLinkSuffix;
1167 m_generator->generateUseNode(info);
1168 handleBaseNodeEnd(node);
1173 QSvgNode *link = node->childToRender();
1177 QString oldLinkSuffix = m_linkSuffix;
1178 m_linkSuffix += QStringLiteral(
"_switch") + QString::number(quintptr(node), 16);
1179 QSvgVisitor::traverse(link);
1180 m_linkSuffix = oldLinkSuffix;
1193 return m_pregeneratingReferencedNodes;
1203 if (m_pregeneratingReferencedNodes) {
1204 handleBaseNodeSetup(node);
1207 fillCommonNodeInfo(node, info);
1208 fillAnimationInfo(node, info);
1212 QSvgRectF r = node->rect();
1213 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1214 info.patternRect = r;
1216 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1217 qCWarning(lcQuickVectorImage) <<
"Only user space content units supported for patterns";
1219 return m_generator->generatePatternNode(info);
1227 Q_ASSERT(m_pregeneratingReferencedNodes);
1229 handleBaseNodeSetup(node);
1232 fillCommonNodeInfo(node, info);
1233 fillAnimationInfo(node, info);
1235 QSvgRectF r = node->rect();
1236 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1237 info.patternRect = r;
1241 m_generator->generatePatternNode(info);
1246 if (m_useLevel == 0)
1249 handleBaseNodeSetup(node);
1252 fillCommonNodeInfo(node, info);
1253 fillAnimationInfo(node, info);
1255 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
1256 info.clipBox = oldTransform.mapRect(node->clipRect());
1258 QTransform xform = node->aspectRatioTransform();
1259 if (!xform.isIdentity()) {
1260 info.isDefaultTransform =
false;
1261 xform = xform * oldTransform;
1262 info.transform.setDefaultValue(QVariant::fromValue(xform));
1266 return m_generator->generateStructureNode(info);
1271 handleBaseNodeSetup(node);
1274 fillCommonNodeInfo(node, info);
1275 fillAnimationInfo(node, info);
1277 info.clipBox = node->clipRect();
1280 m_generator->generateStructureNode(info);
1285 if (!m_pregeneratingReferencedNodes)
1288 handleBaseNodeSetup(node);
1292 QSvgRectF r = node->rect();
1293 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1296 info.isMaskContentRelativeCoordinates =
1297 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1299 fillCommonNodeInfo(node, info);
1301 return m_generator->generateMaskNode(info);
1309 QSvgRectF r = node->rect();
1310 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1312 info.isMaskContentRelativeCoordinates =
1313 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1314 fillCommonNodeInfo(node, info);
1316 m_generator->generateMaskNode(info);
1318 handleBaseNodeEnd(node);
1325 if (!m_pregeneratingReferencedNodes)
1328 if (!m_filterPrimitives.isEmpty()) {
1329 qCWarning(lcQuickVectorImage) <<
"Filter defined inside a filter";
1338 if (m_filterPrimitives.isEmpty())
1341 handleBaseNodeSetup(node);
1344 fillCommonNodeInfo(node, info);
1346 info.filterRect = node->rect();
1347 if (node->filterUnits() == QtSvg::UnitTypes::objectBoundingBox)
1350 bool generatedAlpha =
false;
1351 for (
const QSvgFeFilterPrimitive *filterPrimitive : std::as_const(m_filterPrimitives)) {
1352 if (filterPrimitive->requiresSourceAlpha() && !generatedAlpha) {
1353 FilterNodeInfo::FilterStep alphaStep;
1354 alphaStep.filterType = FilterNodeInfo::Type::ColorMatrix;
1355 alphaStep.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
1358 qreal values[] = { 0.0, 0.0, 0.0, 0.0, 0.0,
1359 0.0, 0.0, 0.0, 0.0, 0.0,
1360 0.0, 0.0, 0.0, 0.0, 0.0,
1361 0.0, 0.0, 0.0, 1.0, 0.0,
1362 0.0, 0.0, 0.0, 0.0, 0.0 };
1363 QGenericMatrix<5, 5, qreal> matrix(values);
1364 alphaStep.filterParameter = QVariant::fromValue(matrix);
1365 alphaStep.outputName = info.id + QStringLiteral(
"_source_alpha");
1366 generatedAlpha =
true;
1368 info.steps.append(alphaStep);
1371 fillFilterPrimitiveInfo(node, filterPrimitive, info);
1374 m_generator->generateFilterNode(info);
1375 m_filterPrimitives.clear();
1378void QSvgVisitorImpl::fillFilterPrimitiveInfo(
const QSvgFilterContainer *node,
1379 const QSvgFeFilterPrimitive *filterPrimitive,
1383 step.filterPrimitiveRect = filterPrimitive->rect();
1385 step.outputName = info.id + QStringLiteral(
"_")
1386 + (filterPrimitive->result().isEmpty()
1387 ? QStringLiteral(
"output_") + QString::number(info.steps.size())
1388 : filterPrimitive->result());
1390 auto findInput = [&info, &filterPrimitive](
const QString &input, QString *outName) {
1391 const QString alphaSource = info.id + QStringLiteral(
"_source_alpha");
1392 if (input == QStringLiteral(
"SourceGraphic")) {
1394 }
else if (input == QStringLiteral(
"SourceAlpha")) {
1395 *outName = alphaSource;
1397 }
else if (info.steps.isEmpty()) {
1401 if (!input.isEmpty()) {
1402 *outName = info.id + QStringLiteral(
"_") + input;
1404 bool insideMergeNode = filterPrimitive->type() == QSvgNode::FeMergenode;
1405 for (
int i = info.steps.size() - 1; i >= 0; --i) {
1406 const auto &prevStep = info.steps.at(i);
1408 insideMergeNode =
false;
1412 if (!prevStep.outputName.isEmpty() && prevStep.outputName != alphaSource) {
1413 *outName = prevStep.outputName;
1422 step
.input1 = findInput(filterPrimitive->input(), &step.namedInput1);
1424 if (node->primitiveUnits() == QtSvg::UnitTypes::objectBoundingBox)
1431 if (node->primitiveUnits() == QtSvg::UnitTypes::userSpaceOnUse
1432 && filterPrimitive->rect().unitW() == QtSvg::UnitTypes::unknown) {
1436 switch (filterPrimitive->type()) {
1437 case QSvgNode::FeMerge:
1440 case QSvgNode::FeMergenode:
1443 case QSvgNode::FeBlend:
1445 const QSvgFeBlend *blend =
static_cast<
const QSvgFeBlend *>(filterPrimitive);
1446 switch (blend->mode()) {
1447 case QSvgFeBlend::Mode::Normal:
1450 case QSvgFeBlend::Mode::Multiply:
1453 case QSvgFeBlend::Mode::Screen:
1456 case QSvgFeBlend::Mode::Darken:
1459 case QSvgFeBlend::Mode::Lighten:
1464 step
.input2 = findInput(blend->input2(), &step.namedInput2);
1467 case QSvgNode::FeComposite:
1469 const QSvgFeComposite *composite =
static_cast<
const QSvgFeComposite *>(filterPrimitive);
1470 switch (composite->compositionOperator()) {
1471 case QSvgFeComposite::Operator::Over:
1474 case QSvgFeComposite::Operator::In:
1477 case QSvgFeComposite::Operator::Out:
1480 case QSvgFeComposite::Operator::Atop:
1483 case QSvgFeComposite::Operator::Xor:
1486 case QSvgFeComposite::Operator::Lighter:
1489 case QSvgFeComposite::Operator::Arithmetic:
1494 step
.input2 = findInput(composite->input2(), &step.namedInput2);
1495 step.filterParameter = composite->k();
1498 case QSvgNode::FeOffset:
1500 const QSvgFeOffset *offset =
static_cast<
const QSvgFeOffset *>(filterPrimitive);
1502 step.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
1506 case QSvgNode::FeColormatrix:
1508 const QSvgFeColorMatrix *colorMatrix =
1509 static_cast<
const QSvgFeColorMatrix *>(filterPrimitive);
1511 step.filterParameter = QVariant::fromValue(colorMatrix->matrix());
1515 case QSvgNode::FeGaussianblur:
1517 const QSvgFeGaussianBlur *gaussianBlur =
1518 static_cast<
const QSvgFeGaussianBlur *>(filterPrimitive);
1520 if (gaussianBlur->edgeMode() == QSvgFeGaussianBlur::EdgeMode::Wrap)
1521 info.wrapMode = QSGTexture::Repeat;
1523 if (!qFuzzyCompare(gaussianBlur->stdDeviationX(), gaussianBlur->stdDeviationY()))
1524 qCWarning(lcQuickVectorImage) <<
"Separate X and Y deviations not supported for gaussian blur";
1525 step.filterParameter = std::max(gaussianBlur->stdDeviationX(),
1526 gaussianBlur->stdDeviationY());
1530 case QSvgNode::FeFlood:
1532 const QSvgFeFlood *flood =
1533 static_cast<
const QSvgFeFlood *>(filterPrimitive);
1536 step.filterParameter = flood->color();
1545 info.steps.append(step);
1550 m_filterPrimitives.append(node);
1561 constexpr bool forceSeparatePaths =
false;
1562 handleBaseNodeSetup(node);
1566 fillCommonNodeInfo(node, info);
1567 fillAnimationInfo(node, info);
1569 info.isPathContainer = isPathContainer(node);
1572 return m_generator->generateStructureNode(info);
1577 handleBaseNodeEnd(node);
1582 fillCommonNodeInfo(node, info);
1583 info.isPathContainer = isPathContainer(node);
1586 m_generator->generateStructureNode(info);
1591 return QStringLiteral(
"_qt_node%1").arg(m_nodeIdCounter++);
1596 handleBaseNodeSetup(node);
1599 fillCommonNodeInfo(node, info);
1600 fillAnimationInfo(node, info);
1602 const QSvgDocument *doc =
static_cast<
const QSvgDocument *>(node);
1603 info.size = doc->size();
1604 info.viewBox = doc->viewBox();
1605 info.isPathContainer = isPathContainer(node);
1609 if (m_generator->generateRootNode(info)) {
1610 pregenerateReferencedNodes(node);
1619 handleBaseNodeEnd(node);
1620 qCDebug(lcQuickVectorImage) <<
"REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen)
1621 << m_styleResolver->painter().pen().color().name() << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1622 << m_styleResolver->painter().pen().brush().color().name();
1625 fillCommonNodeInfo(node, info);
1628 m_generator->generateRootNode(info);
1633 QString ret = m_idForNodeId.value(id);
1634 if (ret.isEmpty()) {
1636 m_idForNodeId.insert(id, ret);
1641QString
QSvgVisitorImpl::findOrCreateId(
const QSvgNode *node,
const QString &nodeId)
1643 QString key = nodeId;
1644 const QSvgNode *n = m_nodesForKeys.value(key);
1645 if (key.isEmpty() || (n !=
nullptr && n != node))
1646 key = QString::number(quintptr(node), 16);
1648 m_nodesForKeys.insert(key, node);
1649 return findOrCreateId(key);
1654 const QString nodeId = scrub(node->nodeId());
1655 info.id = findOrCreateId(node, nodeId);
1658 info.id += idSuffix;
1660 if (!m_linkSuffix.isEmpty())
1661 info.id += m_linkSuffix;
1663 info.nodeId = nodeId;
1664 info.typeName = node->typeName();
1667 auto transform =
static_cast<QSvgTransformStyle *>(node->style().property(QSvgStyleProperty::Transform));
1669 info.transform.setDefaultValue(QVariant::fromValue(xf));
1671 auto opacity =
static_cast<QSvgOpacityStyle *>(node->style().property(QSvgStyleProperty::Opacity));
1672 info
.isDefaultOpacity = node->style().isDefaultProperty(QSvgStyleProperty::Opacity);
1673 info.opacity.setDefaultValue(!info
.isDefaultOpacity ? opacity->opacity() : 1.0);
1675 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1677 if (node->hasFilter()
1679 || node->type() == QSvgNode::Type::Mask
1680 || node->type() == QSvgNode::Type::Pattern) {
1681 QImage dummy(1, 1, QImage::Format_RGB32);
1683 QSvgExtraStates states;
1684 p.setPen(QPen(Qt::NoPen));
1685 info.bounds = node->internalBounds(&p, states);
1688 if (node->hasMask())
1689 info.maskId = findOrCreateId(node->maskId());
1691 if (node->hasFilter())
1692 info.filterId = findOrCreateId(node->filterId());
1695QList<QSvgVisitorImpl::AnimationPair>
QSvgVisitorImpl::collectAnimations(
const QSvgNode *node,
1696 const QString &propertyName)
1698 QList<AnimationPair> ret;
1699 const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
1700 for (
const QSvgAbstractAnimation *animation : animations) {
1701 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1702 for (
const QSvgAbstractAnimatedProperty *property : properties) {
1703 if (property->propertyName() == propertyName)
1704 ret.append(std::make_pair(animation, property));
1711void QSvgVisitorImpl::applyAnimationsToProperty(
const QList<AnimationPair> &animations,
1712 QQuickAnimatedProperty *outProperty,
1713 std::function<QVariant(
const QSvgAbstractAnimatedProperty *,
int index,
int animationIndex)> calculateValue)
1715 qCDebug(lcVectorImageAnimations) <<
"Applying animations to property with default value"
1716 << outProperty->defaultValue();
1717 for (
auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1718 qCDebug(lcVectorImageAnimations) <<
" -> Add animation";
1719 const QSvgAbstractAnimation *animation = it->first;
1720 const QSvgAbstractAnimatedProperty *property = it->second;
1722 const int start = animation->start();
1723 const int repeatCount = animation->iterationCount();
1724 const int duration = animation->duration();
1726 bool freeze =
false;
1727 bool replace =
true;
1728 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1729 const QSvgAnimateNode *animateNode =
static_cast<
const QSvgAnimateNode *>(animation);
1730 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1731 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1734 qCDebug(lcVectorImageAnimations) <<
" -> Start:" << start
1735 <<
", repeatCount:" << repeatCount
1736 <<
", freeze:" << freeze
1737 <<
", replace:" << replace;
1739 QList<qreal> propertyKeyFrames = property->keyFrames();
1740 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1744 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1745 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1746 const auto &components = transformProperty->components();
1747 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1748 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1749 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1750 outAnimation.repeatCount = repeatCount;
1751 outAnimation.startOffset = start;
1753 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1755 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1756 switch (components.at(i).type) {
1757 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1758 outAnimation.subtype = QTransform::TxTranslate;
1760 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1761 outAnimation.subtype = QTransform::TxScale;
1763 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1764 outAnimation.subtype = QTransform::TxRotate;
1766 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1767 outAnimation.subtype = QTransform::TxShear;
1770 qCWarning(lcQuickVectorImage()) <<
"Unhandled transform type:" << components.at(i).type;
1774 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1777 << property->propertyName()
1778 <<
" animation subtype:"
1779 << outAnimation.subtype;
1781 outAnimations.append(outAnimation);
1784 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1785 outAnimation.repeatCount = repeatCount;
1786 outAnimation.startOffset = start;
1788 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1790 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1793 << property->propertyName();
1795 outAnimations.append(outAnimation);
1798 outProperty->beginAnimationGroup();
1799 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
1800 for (
int i = 0; i < outAnimations.size(); ++i) {
1801 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1803 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
1804 const int time = qRound(propertyKeyFrames.at(j) * duration);
1806 const QVariant value = calculateValue(property, j, i);
1807 outAnimation.frames[time] = value;
1809 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
1810 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
1811 ? easingForAnimation(easingInterface, animation->animationType())
1814 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << value;
1817 outProperty->addAnimation(outAnimation);
1822QBezier
QSvgVisitorImpl::easingForAnimation(
const QSvgEasingInterface *easingInterface,
1823 QSvgAbstractAnimation::AnimationType type)
1825 constexpr QPointF startControlPoint(0, 0);
1826 constexpr QPointF endControlPoint(1, 1);
1827 constexpr QPointF easeC1(0.25, 0.1);
1828 constexpr QPointF easeC2(0.25, 1);
1830 QBezier easing = QBezier::fromPoints(startControlPoint, startControlPoint, endControlPoint, endControlPoint);
1832#if QT_CONFIG(cssparser)
1833 if (type == QSvgAbstractAnimation::CSS) {
1834 const QSvgCssEasing *cssEasing =
static_cast<
const QSvgCssEasing *>(easingInterface);
1835 switch (cssEasing->easingFunction()) {
1836 case QSvgCssValues::EasingFunction::Ease:
1837 case QSvgCssValues::EasingFunction::EaseIn:
1838 case QSvgCssValues::EasingFunction::EaseOut:
1839 case QSvgCssValues::EasingFunction::EaseInOut:
1840 case QSvgCssValues::EasingFunction::CubicBezier:
1841 case QSvgCssValues::EasingFunction::Linear:
1843 const QSvgCssCubicBezierEasing *cssCubicEasing =
static_cast<
const QSvgCssCubicBezierEasing *>(cssEasing);
1844 QPointF c1 = cssCubicEasing->c1();
1845 QPointF c2 = cssCubicEasing->c2();
1846 easing = QBezier::fromPoints(startControlPoint, c1, c2, endControlPoint);
1849 case QSvgCssValues::EasingFunction::Steps:
1851 qCDebug(lcVectorImageAnimations) <<
"Step easing is not supported reverting to default.";
1852 easing = QBezier::fromPoints(startControlPoint, easeC1, easeC2, endControlPoint);
1866 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1867 if (!animations.isEmpty())
1868 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1872 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1873 if (!animations.isEmpty())
1874 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1878 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1879 if (!animations.isEmpty())
1880 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1884 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1885 if (!animations.isEmpty())
1886 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1892 qCDebug(lcVectorImageAnimations) <<
"Applying transform animations to property with default value"
1893 << info.transform.defaultValue();
1895 auto calculateValue = [](
const QSvgAbstractAnimatedProperty *property,
int index,
int animationIndex) {
1896 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1899 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1900 const auto &components = transformProperty->components();
1902 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1904 QVariantList parameters;
1906 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1907 switch (component.type) {
1908 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1909 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1910 component.values.value(1))));
1912 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1913 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1914 component.values.value(2))));
1915 parameters.append(QVariant::fromValue(component.values.value(0)));
1917 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1918 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1919 component.values.value(1))));
1921 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1922 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1923 component.values.value(1))));
1926 qCWarning(lcVectorImageAnimations) <<
"Unhandled transform type:" << component.type;
1929 return QVariant::fromValue(parameters);
1934 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"transform"));
1935 if (!animations.isEmpty())
1936 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1942 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"offset-distance"));
1943 auto offset =
static_cast<QSvgOffsetStyle *>(node->style().property(QSvgStyleProperty::Offset));
1945 if (animations.isEmpty())
1949 qCWarning(lcQuickVectorImage) <<
"Motion path animation: No offset path";
1953 if (animations.size() > 1) {
1954 qCWarning(lcQuickVectorImage)
1955 <<
"Not supported: More than one offset path animation on same node";
1958 const AnimationPair &animationPair = animations.first();
1960 const QSvgAbstractAnimation *animation = animationPair.first;
1961 const QSvgAbstractAnimatedProperty *property = animationPair.second;
1963 const int start = animation->start();
1964 const int repeatCount = animation->iterationCount();
1965 const int duration = animation->duration();
1967 qCDebug(lcVectorImageAnimations) <<
"Motion path animation:"
1968 <<
"start == " << start
1969 <<
", repeatCount == " << repeatCount
1970 <<
"; duration == " << duration;
1973 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1974 outAnimation.repeatCount = repeatCount;
1975 outAnimation.startOffset = start;
1977 QPainterPath originalPath = offset->path();
1981 switch (offset->rotateType()) {
1982 case QtSvg::OffsetRotateType::Auto:
1986 case QtSvg::OffsetRotateType::Angle:
1988 baseRotation = offset->rotateAngle();
1990 case QtSvg::OffsetRotateType::AutoAngle:
1992 baseRotation = offset->rotateAngle();
1994 case QtSvg::OffsetRotateType::Reverse:
1996 baseRotation = 180.0;
1998 case QtSvg::OffsetRotateType::ReverseAngle:
2000 baseRotation = offset->rotateAngle() + 180.0f;
2007 QVariantList params({ QVariant::fromValue(originalPath), adaptAngle, baseRotation });
2008 info.motionPath.setDefaultValue(params);
2010 const QList<qreal> propertyKeyFrames = property->keyFrames();
2011 outAnimation.frames[0] = qreal(0);
2012 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
2013 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
2014 const int time = qRound(propertyKeyFrames.at(j) * duration);
2016 qreal t = calculateInterpolatedValue(property, j, 0).toReal();
2017 outAnimation.frames[time] = t;
2019 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
2020 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
2021 ? easingForAnimation(easingInterface, animation->animationType())
2024 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << t;
2028 info.motionPath.addAnimation(outAnimation);
2033 fillColorAnimationInfo(node, info);
2034 fillAnimationInfo(node, info);
2040 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"opacity"));
2041 if (!animations.isEmpty())
2042 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
2045 fillTransformAnimationInfo(node, info);
2046 fillMotionPathAnimationInfo(node, info);
2051 qCDebug(lcQuickVectorImage) <<
"Before SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2052 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2053 << node->nodeId() <<
" type: " << node->typeName() <<
" " << node->type();
2055 node->applyStyle(&m_styleResolver->painter(), m_styleResolver->states());
2057 qCDebug(lcQuickVectorImage) <<
"After SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2058 <<
"stroke" << m_styleResolver->currentStrokeColor()
2059 << m_styleResolver->currentStrokeWidth() << node->nodeId();
2065 fillCommonNodeInfo(node, info);
2067 m_generator->generateNodeBase(info);
2072 node->revertStyle(&m_styleResolver->painter(), m_styleResolver->states());
2074 qCDebug(lcQuickVectorImage) <<
"After END" << node <<
"fill" << m_styleResolver->currentFillColor()
2075 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2079void QSvgVisitorImpl::handlePathNode(
const QSvgNode *node,
const QPainterPath &path)
2081 handleBaseNodeSetup(node);
2084 fillCommonNodeInfo(node, info);
2086 if (node->hasMarkerStart())
2087 info.markerStartId = findOrCreateId(node->markerStartId());
2089 if (node->hasMarkerMid())
2090 info.markerMidId = findOrCreateId(node->markerMidId());
2092 if (node->hasMarkerEnd())
2093 info.markerEndId = findOrCreateId(node->markerEndId());
2095 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
2096 auto strokeStyle =
static_cast<QSvgStrokeStyle *>(node->style().property(QSvgStyleProperty::Stroke));
2097 bool hasStrokePattern = strokeStyle
2098 && strokeStyle->paintServer()
2099 && strokeStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern;
2101 info.path.setDefaultValue(QVariant::fromValue(path));
2102 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
2103 if (strokeGradient !=
nullptr)
2104 info.strokeGrad = *strokeGradient;
2106 if (!hasStrokePattern) {
2107 info.strokeStyle = StrokeStyle::fromPen(m_styleResolver->currentStroke());
2108 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
2110 if (m_styleResolver->currentFillGradient() !=
nullptr)
2111 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
2112 info.fillTransform = m_styleResolver->currentFillTransform();
2114 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
2116 info.fillRule = fillStyle->fillRule();
2118 if (fillStyle->paintServer()
2119 && fillStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
2120 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(fillStyle->paintServer());
2121 info.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2127 info.fillTransform = paintServer->patternNode()->transform();
2131 fillPathAnimationInfo(node, info);
2133 m_generator->generatePath(info);
2135 if (hasStrokePattern) {
2137 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke"));
2139 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(strokeStyle->paintServer());
2140 strokeInfo.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2141 strokeInfo.fillTransform = paintServer->patternNode()->transform();
2143 QPainterPathStroker stroker(m_styleResolver->currentStroke());
2144 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(path)));
2145 m_generator->generatePath(strokeInfo);
2148 handleBaseNodeEnd(node);
2153 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
2155 info.markerSize = node->rect().size();
2156 info.anchorPoint = node->refP();
2157 info.clipBox = oldTransform.mapRect(node->clipRect());
2158 info.viewBox = node->viewBox();
2159 switch (node->orientation()) {
2160 case QSvgMarker::Orientation::Auto:
2163 case QSvgMarker::Orientation::AutoStartReverse:
2166 case QSvgMarker::Orientation::Value:
2171 switch (node->markerUnits()) {
2172 case QSvgMarker::MarkerUnits::UserSpaceOnUse:
2175 case QSvgMarker::MarkerUnits::StrokeWidth:
2180 info.angle = node->orientationAngle();
2182 QTransform xform = node->aspectRatioTransform();
2183 if (!xform.isIdentity()) {
2184 info.isDefaultTransform =
false;
2185 xform = xform * oldTransform;
2186 info.transform.setDefaultValue(QVariant::fromValue(xform));
2194 if (!m_pregeneratingReferencedNodes)
2197 handleBaseNodeSetup(node);
2201 fillCommonNodeInfo(node, info);
2202 fillAnimationInfo(node, info);
2203 fillMarkerInfo(node, info);
2206 return m_generator->generateMarkerNode(info);
2211 handleBaseNodeEnd(node);
2214 fillCommonNodeInfo(node, info);
2215 fillMarkerInfo(node, info);
2218 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
QColor currentStrokeColor() const
bool visitFilterNodeStart(const QSvgFilterContainer *node) override
void visitPolygonNode(const QSvgPolygon *node) override
bool visitSwitchNodeStart(const QSvgSwitch *node) override
void visitMaskNodeEnd(const QSvgMask *node) override
void visitEllipseNode(const QSvgEllipse *node) override
void visitPathNode(const QSvgPath *node) override
void visitMarkerNodeEnd(const QSvgMarker *node) override
bool visitDefsNodeStart(const QSvgDefs *node) override
void visitDefsNodeEnd(const QSvgDefs *node) override
bool visitMaskNodeStart(const QSvgMask *node) override
void visitDocumentNodeEnd(const QSvgDocument *node) override
~QSvgVisitorImpl() override
void visitStructureNodeEnd(const QSvgStructureNode *node) override
void visitRectNode(const QSvgRect *node) override
void visitFeFilterPrimitiveNodeEnd(const QSvgFeFilterPrimitive *node) override
void visitLineNode(const QSvgLine *node) override
void visitNode(const QSvgNode *node) override
bool visitMarkerNodeStart(const QSvgMarker *node) override
void visitPolylineNode(const QSvgPolyline *node) override
QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator, bool assumeTrustedSource)
void visitUseNode(const QSvgUse *node) override
bool visitSymbolNodeStart(const QSvgSymbol *node) override
void visitPatternNodeEnd(const QSvgPattern *) override
bool visitDocumentNodeStart(const QSvgDocument *node) override
void visitImageNode(const QSvgImage *node) override
void visitFilterNodeEnd(const QSvgFilterContainer *node) override
bool visitPatternNodeStart(const QSvgPattern *) override
void visitTextNode(const QSvgText *node) override
void visitSwitchNodeEnd(const QSvgSwitch *node) override
void visitSymbolNodeEnd(const QSvgSymbol *node) override
bool visitFeFilterPrimitiveNodeStart(const QSvgFeFilterPrimitive *node) override
bool visitStructureNodeStart(const QSvgStructureNode *node) override
Combined button and popup list for selecting options.
static QString scrub(const QString &raw)
static bool isStructureNode(const QSvgNode *node)
static void recurseSvgNodes(const QSvgNode *root, const std::function< void(const QSvgNode *)> &fnc)
static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)
CoordinateSystem csFilterParameter
CoordinateSystem csFilterRect
PreserveAspectRatio preserveAspectRatio