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>
34#include <QtSvg/private/qsvgstyle_p.h>
35#include <QtSvg/private/qsvgfilter_p.h>
39Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations,
"qt.quick.vectorimage.animations")
41using namespace Qt::StringLiterals;
48 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
49 m_dummyPainter.begin(&m_dummyImage);
50 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
51 defaultPen.setMiterLimit(4);
52 m_dummyPainter.setPen(defaultPen);
53 m_dummyPainter.setBrush(Qt::black);
66 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
67 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
68 return QColor(QColorConstants::Transparent);
72 fillColor = m_dummyPainter.brush().color();
73 fillColor.setAlphaF(m_svgState.fillOpacity);
80 return m_svgState.fillOpacity;
85 QBrush brush = m_dummyPainter.pen().brush();
86 if (brush.style() == Qt::LinearGradientPattern
87 || brush.style() == Qt::RadialGradientPattern
88 || brush.style() == Qt::ConicalGradientPattern) {
89 return brush.gradient();
96 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
97 return m_dummyPainter.brush().gradient();
103 return m_dummyPainter.brush().transform();
108 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
109 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
110 return QColor(QColorConstants::Transparent);
114 strokeColor = m_dummyPainter.pen().brush().color();
115 strokeColor.setAlphaF(m_svgState.strokeOpacity);
122 QGradient grad = gradient;
123 QGradientStops stops;
124 for (
auto &stop : grad.stops()) {
125 stop.second.setAlphaF(stop.second.alphaF() * opacity);
129 grad.setStops(stops);
136 float penWidth = m_dummyPainter.pen().widthF();
137 return penWidth ? penWidth : 1;
142 return m_dummyPainter.pen();
152inline bool isPathContainer(
const QSvgDocument *doc,
const QSvgStructureNode *node)
154 bool foundPath =
false;
155 for (
const auto &child : node->renderers()) {
156 switch (child->type()) {
158 case QSvgNode::Switch:
160 case QSvgNode::Group:
161 case QSvgNode::AnimateColor:
162 case QSvgNode::AnimateTransform:
164 case QSvgNode::Video:
165 case QSvgNode::Image:
166 case QSvgNode::Textarea:
168 case QSvgNode::Tspan:
170 case QSvgNode::Marker:
171 case QSvgNode::Pattern:
177 case QSvgNode::Symbol:
182 case QSvgNode::Circle:
183 case QSvgNode::Ellipse:
186 case QSvgNode::Polygon:
187 case QSvgNode::Polyline:
189 if (child->hasFilter())
192 if (child->hasAnyMarker())
195 if (!child->style().isDefaultProperty(QSvgStyleProperty::Opacity))
198 if (!child->style().isDefaultProperty(QSvgStyleProperty::Transform))
201 const auto animations = doc->animator()->animationsForNode(child.get());
202 if (!animations.isEmpty()) {
210 qCDebug(lcQuickVectorImage) <<
"Unhandled type in switch" << child->type();
218static QString capStyleName(Qt::PenCapStyle style)
224 styleName = QStringLiteral(
"squarecap");
227 styleName = QStringLiteral(
"flatcap");
230 styleName = QStringLiteral(
"roundcap");
239static QString joinStyleName(Qt::PenJoinStyle style)
245 styleName = QStringLiteral(
"miterjoin");
248 styleName = QStringLiteral(
"beveljoin");
251 styleName = QStringLiteral(
"roundjoin");
253 case Qt::SvgMiterJoin:
254 styleName = QStringLiteral(
"svgmiterjoin");
263static QString dashArrayString(QList<qreal> dashArray)
265 if (dashArray.isEmpty())
268 QString dashArrayString;
269 QTextStream stream(&dashArrayString);
271 for (
int i = 0; i < dashArray.length() - 1; i++) {
272 qreal value = dashArray[i];
273 stream << value <<
", ";
276 stream << dashArray.last();
278 return dashArrayString;
282static QString
scrub(
const QString &raw)
284 QString res(raw.left(80));
286 if (!res.isEmpty()) {
287 constexpr QLatin1StringView legalSymbols(
"_-.:");
290 if (res.at(i).isLetterOrNumber() || legalSymbols.contains(res.at(i)))
294 }
while (i < res.size());
301 QQuickGenerator *generator,
302 bool assumeTrustedSource)
305 , m_assumeTrustedSource(assumeTrustedSource)
315 fillCommonNodeInfo(node, info);
321 if (node->type() == QSvgNode::Pattern) {
322 info.transform = QQuickAnimatedProperty(QVariant::fromValue(QTransform{}));
323 info.isDefaultTransform =
true;
326 if (!m_generator->generateDefsNode(info))
335 fillCommonNodeInfo(node, info);
339 m_generator->generateDefsNode(info);
344 switch (node->type()) {
345 case QSvgNode::Switch:
348 case QSvgNode::Group:
350 case QSvgNode::Symbol:
351 case QSvgNode::Filter:
352 case QSvgNode::FeMerge:
353 case QSvgNode::FeMergenode:
354 case QSvgNode::FeColormatrix:
355 case QSvgNode::FeGaussianblur:
356 case QSvgNode::FeOffset:
357 case QSvgNode::FeComposite:
358 case QSvgNode::FeFlood:
359 case QSvgNode::FeBlend:
360 case QSvgNode::Marker:
361 case QSvgNode::Pattern:
368static void recurseSvgNodes(
const QSvgNode *root,
const std::function<
void(
const QSvgNode *)> &fnc)
372 if (isStructureNode(root)) {
373 const QSvgStructureNode *sn =
static_cast<
const QSvgStructureNode *>(root);
374 for (
const auto &child : sn->renderers())
375 recurseSvgNodes(child.get(), fnc);
381 Q_ASSERT(m_doc !=
nullptr);
382 Q_ASSERT(m_generator !=
nullptr);
386 QSet<QString> referencedIds;
387 auto findReferencedIds = [&referencedIds](
const QSvgNode *node) {
388 if (node->hasFilter())
389 referencedIds.insert(node->filterId());
391 referencedIds.insert(node->maskId());
392 if (node->hasMarkerStart())
393 referencedIds.insert(node->markerStartId());
394 if (node->hasMarkerMid())
395 referencedIds.insert(node->markerMidId());
396 if (node->hasMarkerEnd())
397 referencedIds.insert(node->markerEndId());
398 if (node->type() == QSvgNode::Pattern)
399 referencedIds.insert(node->nodeId());
401 recurseSvgNodes(doc, findReferencedIds);
403 m_pregeneratingReferencedNodes =
true;
404 for (
const QString &referencedId : referencedIds) {
405 const QSvgNode *referencedNode = m_doc->namedNode(referencedId);
406 if (referencedNode ==
nullptr)
409 if (!startDefsBlock(referencedNode))
412 traverse(referencedNode);
414 endDefsBlock(referencedNode);
417 m_pregeneratingReferencedNodes =
false;
423 qCDebug(lcQuickVectorImage) <<
"No valid QQuickGenerator is set. Genration will stop";
427 QtSvg::Options options;
428 if (m_assumeTrustedSource)
429 options.setFlag(QtSvg::AssumeTrustedSource);
431 const auto doc = QSvgDocument::load(m_svgFileName, options);
432 QScopedValueRollback docResetter(m_doc, doc.get());
434 qCDebug(lcQuickVectorImage) <<
"Not a valid Svg File : " << m_svgFileName;
438 QSvgVisitor::traverse(doc.get());
445 handleBaseNodeSetup(node);
448 fillCommonNodeInfo(node, info);
449 fillAnimationInfo(node, info);
451 m_generator->generateNode(info);
453 handleBaseNodeEnd(node);
459 handleBaseNodeSetup(node);
462 fillCommonNodeInfo(node, info);
463 fillAnimationInfo(node, info);
464 info.image = node->image();
465 info.rect = node->rect();
466 info.externalFileReference = node->filename();
468 m_generator->generateImageNode(info);
470 handleBaseNodeEnd(node);
475 QRectF rect = node->rect();
476 QPointF rads = node->radius();
478 qreal x1 = rect.left();
479 qreal x2 = rect.right();
480 qreal y1 = rect.top();
481 qreal y2 = rect.bottom();
483 qreal rx = rads.x() * rect.width() / 200;
484 qreal ry = rads.y() * rect.height() / 200;
487 p.moveTo(x1 + rx, y1);
488 p.lineTo(x2 - rx, y1);
490 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90);
493 p.lineTo(x2, y2 - ry);
494 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90);
496 p.lineTo(x1 + rx, y2);
497 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90);
499 p.lineTo(x1, y1 + ry);
500 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90);
502 handlePathNode(node, p);
507 QRectF rect = node->rect();
512 handlePathNode(node, p);
517 handlePathNode(node, node->path());
523 p.moveTo(node->line().p1());
524 p.lineTo(node->line().p2());
525 handlePathNode(node, p);
530 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
true);
531 handlePathNode(node, p);
536 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
false);
537 handlePathNode(node, p);
540QString
QSvgVisitorImpl::gradientCssDescription(
const QGradient *gradient)
542 QString cssDescription;
543 if (gradient->type() == QGradient::LinearGradient) {
544 const QLinearGradient *linearGradient =
static_cast<
const QLinearGradient *>(gradient);
546 cssDescription +=
" -qt-foreground: qlineargradient("_L1;
547 cssDescription +=
"x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
548 cssDescription +=
"y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
549 cssDescription +=
"x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
550 cssDescription +=
"y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
551 }
else if (gradient->type() == QGradient::RadialGradient) {
552 const QRadialGradient *radialGradient =
static_cast<
const QRadialGradient *>(gradient);
554 cssDescription +=
" -qt-foreground: qradialgradient("_L1;
555 cssDescription +=
"cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
556 cssDescription +=
"cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
557 cssDescription +=
"fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
558 cssDescription +=
"fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
559 cssDescription +=
"radius:"_L1 + QString::number(radialGradient->radius()) + u',';
561 const QConicalGradient *conicalGradient =
static_cast<
const QConicalGradient *>(gradient);
563 cssDescription +=
" -qt-foreground: qconicalgradient("_L1;
564 cssDescription +=
"cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
565 cssDescription +=
"cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
566 cssDescription +=
"angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
569 const QStringList coordinateModes = {
"logical"_L1,
"stretchtodevice"_L1,
"objectbounding"_L1,
"object"_L1 };
570 cssDescription +=
"coordinatemode:"_L1;
571 cssDescription += coordinateModes.at(
int(gradient->coordinateMode()));
572 cssDescription += u',';
574 const QStringList spreads = {
"pad"_L1,
"reflect"_L1,
"repeat"_L1 };
575 cssDescription +=
"spread:"_L1;
576 cssDescription += spreads.at(
int(gradient->spread()));
578 for (
const QGradientStop &stop : gradient->stops()) {
579 cssDescription +=
",stop:"_L1;
580 cssDescription += QString::number(stop.first);
581 cssDescription += u' ';
582 cssDescription += stop.second.name(QColor::HexArgb);
585 cssDescription +=
");"_L1;
587 return cssDescription;
592 QString cssDescription;
593 cssDescription += QStringLiteral(
"rgba(");
594 cssDescription += QString::number(color.red()) + QStringLiteral(
",");
595 cssDescription += QString::number(color.green()) + QStringLiteral(
",");
596 cssDescription += QString::number(color.blue()) + QStringLiteral(
",");
597 cssDescription += QString::number(color.alphaF()) + QStringLiteral(
")");
599 return cssDescription;
608 class QSvgFontEngine :
public QFontEngine
611 QSvgFontEngine(
const QString &family,
const QSvgFont *font, qreal size);
613 QFontEngine *cloneWithSize(qreal size)
const override;
615 glyph_t glyphIndex(uint ucs4)
const override;
616 int stringToCMap(
const QChar *str,
618 QGlyphLayout *glyphs,
620 ShaperFlags flags)
const override;
622 void addGlyphsToPath(glyph_t *glyphs,
623 QFixedPoint *positions,
626 QTextItem::RenderFlags flags)
override;
628 glyph_metrics_t boundingBox(glyph_t glyph)
override;
630 void recalcAdvances(QGlyphLayout *, ShaperFlags)
const override;
631 QFixed ascent()
const override;
632 QFixed capHeight()
const override;
633 QFixed descent()
const override;
634 QFixed leading()
const override;
635 qreal maxCharWidth()
const override;
636 qreal minLeftBearing()
const override;
637 qreal minRightBearing()
const override;
639 QFixed emSquareSize()
const override;
642 const QSvgFont *m_font;
646 QSvgFontEngine::QSvgFontEngine(
const QString &family,
const QSvgFont *font, qreal size)
651 fontDef.pixelSize = size;
652 fontDef.families = QStringList(family);
655 QFixed QSvgFontEngine::emSquareSize()
const
657 return QFixed::fromReal(m_font->m_unitsPerEm);
660 glyph_t QSvgFontEngine::glyphIndex(uint ucs4)
const
662 const ushort c(ucs4);
663 if (ucs4 < USHRT_MAX && m_font->findFirstGlyphFor(QStringView(&c, 1)))
664 return glyph_t(ucs4);
669 int QSvgFontEngine::stringToCMap(
const QChar *str,
671 QGlyphLayout *glyphs,
673 ShaperFlags flags)
const
675 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
676 if (*nglyphs < len) {
682 QStringIterator it(str, str + len);
683 while (it.hasNext()) {
684 char32_t ucs4 = it.next();
685 glyph_t index = glyphIndex(ucs4);
686 glyphs->glyphs[ucs4Length++] = index;
689 *nglyphs = ucs4Length;
690 glyphs->numGlyphs = ucs4Length;
692 if (!(flags & GlyphIndicesOnly))
693 recalcAdvances(glyphs, flags);
698 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
699 QFixedPoint *positions,
702 QTextItem::RenderFlags flags)
705 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
706 for (
int i = 0; i < nGlyphs; ++i) {
707 glyph_t index = glyphs[i];
709 QPointF position = positions[i].toPointF();
710 const ushort c(index);
711 const QSvgGlyph *foundGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
716 QPainterPath glyphPath = foundGlyph->m_path;
719 xform.translate(position.x(), position.y());
720 xform.scale(scale, -scale);
721 glyphPath = xform.map(glyphPath);
722 path->addPath(glyphPath);
727 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
732 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
733 const ushort c(glyph);
734 const QSvgGlyph *svgGlyph = m_font->findFirstGlyphFor(QStringView(&c, 1));
735 ret.width = QFixed::fromReal(svgGlyph ? svgGlyph->m_horizAdvX * scale : 0.);
736 ret.height = ascent() + descent();
740 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size)
const
742 QSvgFontEngine *otherEngine =
new QSvgFontEngine(m_family, m_font, size);
746 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags)
const
748 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
749 for (
int i = 0; i < glyphLayout->numGlyphs; i++) {
750 const ushort c(glyphLayout->glyphs[i]);
751 const QSvgGlyph *svgGl = m_font->findFirstGlyphFor(QStringView(&c, 1));
752 glyphLayout->advances[i] = QFixed::fromReal(svgGl ? svgGl->m_horizAdvX * scale : 0.);
756 QFixed QSvgFontEngine::ascent()
const
758 return QFixed::fromReal(fontDef.pixelSize);
761 QFixed QSvgFontEngine::capHeight()
const
765 QFixed QSvgFontEngine::descent()
const
770 QFixed QSvgFontEngine::leading()
const
775 qreal QSvgFontEngine::maxCharWidth()
const
777 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
778 return m_font->m_horizAdvX * scale;
781 qreal QSvgFontEngine::minLeftBearing()
const
786 qreal QSvgFontEngine::minRightBearing()
const
795 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
797 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
799 return property->interpolatedValue();
804 handleBaseNodeSetup(node);
805 const bool isTextArea = node->type() == QSvgNode::Textarea;
808 bool needsRichText =
false;
809 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
810 const QGradient *mainGradient = m_styleResolver->currentFillGradient();
813 auto *fontStyle =
static_cast<QSvgFontStyle *>(node->styleProperty(QSvgStyleProperty::Font));
814 QSvgFont *svgFont =
nullptr;
815 QString svgFontFamily;
817 svgFontFamily = fontStyle->qfont().family();
818 svgFont = node->document()->svgFont(svgFontFamily);
821 QFontEngine *fontEngine =
nullptr;
822 if (svgFont !=
nullptr) {
823 fontEngine =
new QSvgFontEngine(svgFontFamily, svgFont, m_styleResolver->painter().font().pointSize());
824 fontEngine->ref.ref();
828#if QT_CONFIG(texthtmlparser)
829 bool needsPathNode = mainGradient !=
nullptr
830 || svgFont !=
nullptr
831 || m_styleResolver->currentStrokeGradient() !=
nullptr;
833 for (
const auto *tspan : node->tspans()) {
835 text += QStringLiteral(
"<br>");
841 handleBaseNodeSetup(tspan);
842 QFont font = m_styleResolver->painter().font();
844 QString styleTagContent;
846 if ((font.resolveMask() & QFont::FamilyResolved)
847 || (font.resolveMask() & QFont::FamiliesResolved)) {
848 styleTagContent += QStringLiteral(
"font-family: %1;").arg(font.family());
851 if (font.resolveMask() & QFont::WeightResolved
852 && font.weight() != QFont::Normal
853 && font.weight() != QFont::Bold) {
854 styleTagContent += QStringLiteral(
"font-weight: %1;").arg(
int(font.weight()));
857 if (font.resolveMask() & QFont::SizeResolved) {
859 styleTagContent += QStringLiteral(
"font-size: %1px;").arg(
int(font.pointSizeF()));
862 if (font.resolveMask() & QFont::CapitalizationResolved
863 && font.capitalization() == QFont::SmallCaps) {
864 styleTagContent += QStringLiteral(
"font-variant: small-caps;");
867 if (m_styleResolver->currentFillGradient() !=
nullptr
868 && m_styleResolver->currentFillGradient() != mainGradient) {
869 const QGradient grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
870 styleTagContent += gradientCssDescription(&grad) + u';';
871#if QT_CONFIG(texthtmlparser)
872 needsPathNode =
true;
876 const QColor currentStrokeColor = m_styleResolver->currentStrokeColor();
877 if (currentStrokeColor.alpha() > 0) {
878 QString strokeColor = colorCssDescription(currentStrokeColor);
879 styleTagContent += QStringLiteral(
"-qt-stroke-color:%1;").arg(strokeColor);
880 styleTagContent += QStringLiteral(
"-qt-stroke-width:%1px;").arg(m_styleResolver->currentStrokeWidth());
881 styleTagContent += QStringLiteral(
"-qt-stroke-dasharray:%1;").arg(dashArrayString(m_styleResolver->currentStroke().dashPattern()));
882 styleTagContent += QStringLiteral(
"-qt-stroke-dashoffset:%1;").arg(m_styleResolver->currentStroke().dashOffset());
883 styleTagContent += QStringLiteral(
"-qt-stroke-lineCap:%1;").arg(capStyleName(m_styleResolver->currentStroke().capStyle()));
884 styleTagContent += QStringLiteral(
"-qt-stroke-lineJoin:%1;").arg(joinStyleName(m_styleResolver->currentStroke().joinStyle()));
885 if (m_styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || m_styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
886 styleTagContent += QStringLiteral(
"-qt-stroke-miterlimit:%1;").arg(m_styleResolver->currentStroke().miterLimit());
887#if QT_CONFIG(texthtmlparser)
888 needsPathNode =
true;
892 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
893 styleTagContent += QStringLiteral(
"white-space: pre-wrap;");
895 QString content = tspan->text().toHtmlEscaped();
896 content.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
897 content.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
899 bool fontTag =
false;
900 if (!tspan->style().isDefaultProperty(QSvgStyleProperty::Fill)) {
901 auto fill =
static_cast<QSvgFillStyle *>(tspan->style().property(QSvgStyleProperty::Fill));
902 auto &b = fill->qbrush();
903 qCDebug(lcQuickVectorImage) <<
"tspan FILL:" << b;
904 if (b.style() != Qt::NoBrush)
906 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
908 QString spanColor = b.color().name();
909 fontTag = !spanColor.isEmpty();
911 text += QStringLiteral(
"<font color=\"%1\">").arg(spanColor);
913 QString spanColor = colorCssDescription(b.color());
914 styleTagContent += QStringLiteral(
"color:%1").arg(spanColor);
919 needsRichText = needsRichText || !styleTagContent.isEmpty();
920 if (!styleTagContent.isEmpty())
921 text += QStringLiteral(
"<span style=\"%1\">").arg(styleTagContent.toHtmlEscaped());
923 if (font.resolveMask() & QFont::WeightResolved && font.bold())
924 text += QStringLiteral(
"<b>");
926 if (font.resolveMask() & QFont::StyleResolved && font.italic())
927 text += QStringLiteral(
"<i>");
929 if (font.resolveMask() & QFont::CapitalizationResolved) {
930 switch (font.capitalization()) {
931 case QFont::AllLowercase:
932 content = content.toLower();
934 case QFont::AllUppercase:
935 content = content.toUpper();
937 case QFont::Capitalize:
940 qCWarning(lcQuickVectorImage) <<
"Title case not implemented for tspan";
948 text += QStringLiteral(
"</font>");
950 if (font.resolveMask() & QFont::StyleResolved && font.italic())
951 text += QStringLiteral(
"</i>");
953 if (font.resolveMask() & QFont::WeightResolved && font.bold())
954 text += QStringLiteral(
"</b>");
956 if (!styleTagContent.isEmpty())
957 text += QStringLiteral(
"</span>");
959 handleBaseNodeEnd(tspan);
962 if (preserveWhiteSpace && (needsRichText || m_styleResolver->currentFillGradient() !=
nullptr))
963 text = QStringLiteral(
"<span style=\"white-space: pre-wrap\">") + text + QStringLiteral(
"</span>");
965 QFont font = m_styleResolver->painter().font();
966 if (font.pixelSize() <= 0 && font.pointSize() > 0)
967 font.setPixelSize(font.pointSize());
969 font.setHintingPreference(QFont::PreferNoHinting);
971#if QT_CONFIG(texthtmlparser)
973 QTextDocument document;
974 document.setHtml(text);
975 if (isTextArea && node->size().width() > 0)
976 document.setTextWidth(node->size().width());
977 document.setDefaultFont(font);
978 document.pageCount();
980 QTextBlock block = document.firstBlock();
981 while (block.isValid()) {
982 QTextLayout *lout = block.layout();
984 if (lout !=
nullptr) {
985 QRectF boundingRect = lout->boundingRect();
991 QFont blockFont = block.charFormat().font();
992 if (svgFont !=
nullptr
993 && blockFont.family() == svgFontFamily) {
995 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
996 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
998 lout->setRawFont(rawFont);
1001 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt,
int pathIndex) {
1003 fillCommonNodeInfo(node, info, QStringLiteral(
"_path%1").arg(pathIndex));
1004 fillPathAnimationInfo(node, info);
1005 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
1007 info.fillRule = fillStyle->fillRule();
1009 if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
1010 info.fillColor.setDefaultValue(fmt.foreground().color());
1011 if (fmt.foreground().gradient() !=
nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
1012 info.grad = *fmt.foreground().gradient();
1014 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1017 info.path.setDefaultValue(QVariant::fromValue(p));
1019 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
1021 if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
1022 pen = fmt.textOutline();
1023 if (strokeGradient ==
nullptr) {
1024 info.strokeStyle = StrokeStyle::fromPen(pen);
1025 info.strokeStyle.color.setDefaultValue(pen.color());
1028 pen = m_styleResolver->currentStroke();
1029 if (strokeGradient ==
nullptr) {
1030 info.strokeStyle = StrokeStyle::fromPen(pen);
1031 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
1035 if (info.grad.type() == QGradient::NoGradient && m_styleResolver->currentFillGradient() !=
nullptr)
1036 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
1038 info.fillTransform = m_styleResolver->currentFillTransform();
1040 m_generator->generatePath(info, boundingRect);
1042 if (strokeGradient !=
nullptr) {
1043 PathNodeInfo strokeInfo;
1044 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke%1").arg(pathIndex));
1045 fillPathAnimationInfo(node, strokeInfo);
1047 strokeInfo.grad = *strokeGradient;
1049 QPainterPathStroker stroker(pen);
1050 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(p)));
1051 m_generator->generatePath(strokeInfo, boundingRect);
1055 qreal baselineOffset = -QFontMetricsF(font).ascent();
1056 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
1057 baselineOffset = -lout->lineAt(0).ascent();
1059 const QPointF baselineTranslation(0.0, baselineOffset);
1060 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
1061 QList<QPainterPath> paths;
1062 for (
const QGlyphRun &glyphRun : glyphRuns) {
1063 QRawFont font = glyphRun.rawFont();
1064 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
1065 QList<QPointF> positions = glyphRun.positions();
1067 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
1068 quint32 glyphIndex = glyphIndexes.at(j);
1069 const QPointF &pos = positions.at(j);
1071 QPainterPath p = font.pathForGlyph(glyphIndex);
1072 p.translate(pos + node->position() + baselineTranslation);
1073 if (m_styleResolver->states().textAnchor == Qt::AlignHCenter)
1074 p.translate(QPointF(-0.5 * width, 0));
1075 else if (m_styleResolver->states().textAnchor == Qt::AlignRight)
1076 p.translate(QPointF(-width, 0));
1084 QList<QTextLayout::FormatRange> formats = block.textFormats();
1085 for (
int i = 0; i < formats.size(); ++i) {
1086 QTextLayout::FormatRange range = formats.at(i);
1088 QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
1089 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
1090 for (
int j = 0; j < paths.size(); ++j) {
1091 const QPainterPath &path = paths.at(j);
1092 addPathForFormat(path, range.format, j);
1097 block = block.next();
1103 fillCommonNodeInfo(node, info);
1104 fillAnimationInfo(node, info);
1107 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1108 if (!animations.isEmpty())
1109 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1113 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1114 if (!animations.isEmpty())
1115 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1119 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1120 if (!animations.isEmpty())
1121 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
1125 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1126 if (!animations.isEmpty())
1127 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
1130 info.position = node->position();
1131 info.size = node->size();
1136 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
1137 info.alignment = m_styleResolver->states().textAnchor;
1138 info.strokeColor.setDefaultValue(m_styleResolver->currentStrokeColor());
1140 m_generator->generateTextNode(info);
1143 handleBaseNodeEnd(node);
1145 if (fontEngine !=
nullptr) {
1146 fontEngine->ref.deref();
1147 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
1154 QSvgNode *link = node->link();
1157 handleBaseNodeSetup(node);
1159 QPointF startPos = node->start();
1160 fillCommonNodeInfo(node, info);
1161 fillAnimationInfo(node, info);
1162 if (!info.bounds.isNull())
1163 info.bounds.translate(-startPos);
1165 if (!startPos.isNull()) {
1167 if (!info.isDefaultTransform)
1168 xform = info.transform.defaultValue().value<QTransform>();
1169 xform.translate(startPos.x(), startPos.y());
1170 info.transform.setDefaultValue(QVariant::fromValue(xform));
1171 info.isDefaultTransform =
false;
1173 m_generator->generateUseNode(info);
1174 QString oldLinkSuffix = m_linkSuffix;
1175 m_linkSuffix += QStringLiteral(
"_use") + info.id;
1177 QSvgVisitor::traverse(link);
1179 m_linkSuffix = oldLinkSuffix;
1181 m_generator->generateUseNode(info);
1182 handleBaseNodeEnd(node);
1187 QSvgNode *link = node->childToRender();
1191 QString oldLinkSuffix = m_linkSuffix;
1192 m_linkSuffix += QStringLiteral(
"_switch") + QString::number(quintptr(node), 16);
1193 QSvgVisitor::traverse(link);
1194 m_linkSuffix = oldLinkSuffix;
1207 return m_pregeneratingReferencedNodes;
1217 if (m_pregeneratingReferencedNodes) {
1218 handleBaseNodeSetup(node);
1221 fillCommonNodeInfo(node, info);
1222 fillAnimationInfo(node, info);
1226 QSvgRectF r = node->rect();
1227 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1228 info.patternRect = r;
1230 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1231 qCWarning(lcQuickVectorImage) <<
"Only user space content units supported for patterns";
1233 return m_generator->generatePatternNode(info);
1241 Q_ASSERT(m_pregeneratingReferencedNodes);
1243 handleBaseNodeSetup(node);
1246 fillCommonNodeInfo(node, info);
1247 fillAnimationInfo(node, info);
1249 QSvgRectF r = node->rect();
1250 info.isPatternRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1251 info.patternRect = r;
1255 m_generator->generatePatternNode(info);
1260 if (m_useLevel == 0)
1263 handleBaseNodeSetup(node);
1266 fillCommonNodeInfo(node, info);
1267 fillAnimationInfo(node, info);
1269 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
1270 info.clipBox = oldTransform.mapRect(node->clipRect());
1272 QTransform xform = node->aspectRatioTransform();
1273 if (!xform.isIdentity()) {
1274 info.isDefaultTransform =
false;
1275 xform = xform * oldTransform;
1276 info.transform.setDefaultValue(QVariant::fromValue(xform));
1280 return m_generator->generateStructureNode(info);
1285 handleBaseNodeSetup(node);
1288 fillCommonNodeInfo(node, info);
1289 fillAnimationInfo(node, info);
1291 info.clipBox = node->clipRect();
1294 m_generator->generateStructureNode(info);
1299 if (!m_pregeneratingReferencedNodes)
1302 handleBaseNodeSetup(node);
1306 QSvgRectF r = node->rect();
1307 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1310 info.isMaskContentRelativeCoordinates =
1311 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1313 fillCommonNodeInfo(node, info);
1315 return m_generator->generateMaskNode(info);
1323 QSvgRectF r = node->rect();
1324 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1326 info.isMaskContentRelativeCoordinates =
1327 node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox;
1328 fillCommonNodeInfo(node, info);
1330 m_generator->generateMaskNode(info);
1332 handleBaseNodeEnd(node);
1339 if (!m_pregeneratingReferencedNodes)
1342 if (!m_filterPrimitives.isEmpty()) {
1343 qCWarning(lcQuickVectorImage) <<
"Filter defined inside a filter";
1352 if (m_filterPrimitives.isEmpty())
1355 handleBaseNodeSetup(node);
1358 fillCommonNodeInfo(node, info);
1360 info.filterRect = node->rect();
1361 if (node->filterUnits() == QtSvg::UnitTypes::objectBoundingBox)
1364 bool generatedAlpha =
false;
1365 for (
const QSvgFeFilterPrimitive *filterPrimitive : std::as_const(m_filterPrimitives)) {
1366 if (filterPrimitive->requiresSourceAlpha() && !generatedAlpha) {
1367 FilterNodeInfo::FilterStep alphaStep;
1368 alphaStep.filterType = FilterNodeInfo::Type::ColorMatrix;
1369 alphaStep.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
1372 qreal values[] = { 0.0, 0.0, 0.0, 0.0, 0.0,
1373 0.0, 0.0, 0.0, 0.0, 0.0,
1374 0.0, 0.0, 0.0, 0.0, 0.0,
1375 0.0, 0.0, 0.0, 1.0, 0.0,
1376 0.0, 0.0, 0.0, 0.0, 0.0 };
1377 QGenericMatrix<5, 5, qreal> matrix(values);
1378 alphaStep.filterParameter = QVariant::fromValue(matrix);
1379 alphaStep.outputName = info.id + QStringLiteral(
"_source_alpha");
1380 generatedAlpha =
true;
1382 info.steps.append(alphaStep);
1385 fillFilterPrimitiveInfo(node, filterPrimitive, info);
1388 m_generator->generateFilterNode(info);
1389 m_filterPrimitives.clear();
1392void QSvgVisitorImpl::fillFilterPrimitiveInfo(
const QSvgFilterContainer *node,
1393 const QSvgFeFilterPrimitive *filterPrimitive,
1397 step.filterPrimitiveRect = filterPrimitive->rect();
1399 step.outputName = info.id + QStringLiteral(
"_")
1400 + (filterPrimitive->result().isEmpty()
1401 ? QStringLiteral(
"output_") + QString::number(info.steps.size())
1402 : filterPrimitive->result());
1404 auto findInput = [&info, &filterPrimitive](
const QString &input, QString *outName) {
1405 const QString alphaSource = info.id + QStringLiteral(
"_source_alpha");
1406 if (input == QStringLiteral(
"SourceGraphic")) {
1408 }
else if (input == QStringLiteral(
"SourceAlpha")) {
1409 *outName = alphaSource;
1411 }
else if (info.steps.isEmpty()) {
1415 if (!input.isEmpty()) {
1416 *outName = info.id + QStringLiteral(
"_") + input;
1418 bool insideMergeNode = filterPrimitive->type() == QSvgNode::FeMergenode;
1419 for (
int i = info.steps.size() - 1; i >= 0; --i) {
1420 const auto &prevStep = info.steps.at(i);
1422 insideMergeNode =
false;
1426 if (!prevStep.outputName.isEmpty() && prevStep.outputName != alphaSource) {
1427 *outName = prevStep.outputName;
1436 step
.input1 = findInput(filterPrimitive->input(), &step.namedInput1);
1438 if (node->primitiveUnits() == QtSvg::UnitTypes::objectBoundingBox)
1445 if (node->primitiveUnits() == QtSvg::UnitTypes::userSpaceOnUse
1446 && filterPrimitive->rect().unitW() == QtSvg::UnitTypes::unknown) {
1450 switch (filterPrimitive->type()) {
1451 case QSvgNode::FeMerge:
1454 case QSvgNode::FeMergenode:
1457 case QSvgNode::FeBlend:
1459 const QSvgFeBlend *blend =
static_cast<
const QSvgFeBlend *>(filterPrimitive);
1460 switch (blend->mode()) {
1461 case QSvgFeBlend::Mode::Normal:
1464 case QSvgFeBlend::Mode::Multiply:
1467 case QSvgFeBlend::Mode::Screen:
1470 case QSvgFeBlend::Mode::Darken:
1473 case QSvgFeBlend::Mode::Lighten:
1478 step
.input2 = findInput(blend->input2(), &step.namedInput2);
1481 case QSvgNode::FeComposite:
1483 const QSvgFeComposite *composite =
static_cast<
const QSvgFeComposite *>(filterPrimitive);
1484 switch (composite->compositionOperator()) {
1485 case QSvgFeComposite::Operator::Over:
1488 case QSvgFeComposite::Operator::In:
1491 case QSvgFeComposite::Operator::Out:
1494 case QSvgFeComposite::Operator::Atop:
1497 case QSvgFeComposite::Operator::Xor:
1500 case QSvgFeComposite::Operator::Lighter:
1503 case QSvgFeComposite::Operator::Arithmetic:
1508 step
.input2 = findInput(composite->input2(), &step.namedInput2);
1509 step.filterParameter = composite->k();
1512 case QSvgNode::FeOffset:
1514 const QSvgFeOffset *offset =
static_cast<
const QSvgFeOffset *>(filterPrimitive);
1516 step.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
1520 case QSvgNode::FeColormatrix:
1522 const QSvgFeColorMatrix *colorMatrix =
1523 static_cast<
const QSvgFeColorMatrix *>(filterPrimitive);
1525 step.filterParameter = QVariant::fromValue(colorMatrix->matrix());
1529 case QSvgNode::FeGaussianblur:
1531 const QSvgFeGaussianBlur *gaussianBlur =
1532 static_cast<
const QSvgFeGaussianBlur *>(filterPrimitive);
1534 if (gaussianBlur->edgeMode() == QSvgFeGaussianBlur::EdgeMode::Wrap)
1535 info.wrapMode = QSGTexture::Repeat;
1537 if (!qFuzzyCompare(gaussianBlur->stdDeviationX(), gaussianBlur->stdDeviationY()))
1538 qCWarning(lcQuickVectorImage) <<
"Separate X and Y deviations not supported for gaussian blur";
1539 step.filterParameter =
std::max(gaussianBlur->stdDeviationX(),
1540 gaussianBlur->stdDeviationY());
1544 case QSvgNode::FeFlood:
1546 const QSvgFeFlood *flood =
1547 static_cast<
const QSvgFeFlood *>(filterPrimitive);
1550 step.filterParameter = flood->color();
1559 info.steps.append(step);
1564 m_filterPrimitives.append(node);
1576 constexpr bool forceSeparatePaths =
false;
1577 handleBaseNodeSetup(node);
1581 fillCommonNodeInfo(node, info);
1582 fillAnimationInfo(node, info);
1584 info.isPathContainer = isPathContainer(m_doc, node);
1587 return m_generator->generateStructureNode(info);
1593 handleBaseNodeEnd(node);
1598 fillCommonNodeInfo(node, info);
1599 info.isPathContainer = isPathContainer(m_doc, node);
1602 m_generator->generateStructureNode(info);
1607 return QStringLiteral(
"_qt_node%1").arg(m_nodeIdCounter++);
1612 handleBaseNodeSetup(node);
1615 fillCommonNodeInfo(node, info);
1616 fillAnimationInfo(node, info);
1618 const QSvgDocument *doc =
static_cast<
const QSvgDocument *>(node);
1619 info.size = doc->size();
1620 info.viewBox = doc->viewBox();
1621 info.isPathContainer = isPathContainer(doc, node);
1625 if (m_generator->generateRootNode(info)) {
1626 pregenerateReferencedNodes(node);
1635 handleBaseNodeEnd(node);
1636 qCDebug(lcQuickVectorImage) <<
"REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen)
1637 << m_styleResolver->painter().pen().color().name() << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1638 << m_styleResolver->painter().pen().brush().color().name();
1641 fillCommonNodeInfo(node, info);
1644 m_generator->generateRootNode(info);
1649 QString ret = m_idForNodeId.value(id);
1650 if (ret.isEmpty()) {
1652 m_idForNodeId.insert(id, ret);
1657QString
QSvgVisitorImpl::findOrCreateId(
const QSvgNode *node,
const QString &nodeId)
1659 QString key = nodeId;
1660 const QSvgNode *n = m_nodesForKeys.value(key);
1661 if (key.isEmpty() || (n !=
nullptr && n != node))
1662 key = QString::number(quintptr(node), 16);
1664 m_nodesForKeys.insert(key, node);
1665 return findOrCreateId(key);
1670 const QString nodeId = scrub(node->nodeId());
1671 info.id = findOrCreateId(node, nodeId);
1674 info.id += idSuffix;
1676 if (!m_linkSuffix.isEmpty())
1677 info.id += m_linkSuffix;
1679 info.nodeId = nodeId;
1680 info.typeName = node->typeName();
1683 auto transform =
static_cast<QSvgTransformStyle *>(node->style().property(QSvgStyleProperty::Transform));
1685 info.transform.setDefaultValue(QVariant::fromValue(xf));
1687 auto opacity =
static_cast<QSvgOpacityStyle *>(node->style().property(QSvgStyleProperty::Opacity));
1688 info
.isDefaultOpacity = node->style().isDefaultProperty(QSvgStyleProperty::Opacity);
1689 info.opacity.setDefaultValue(!info
.isDefaultOpacity ? opacity->opacity() : 1.0);
1691 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1693 if (node->hasFilter()
1695 || node->type() == QSvgNode::Type::Mask
1696 || node->type() == QSvgNode::Type::Pattern) {
1697 QImage dummy(1, 1, QImage::Format_RGB32);
1699 p.setPen(QPen(Qt::NoPen));
1702 QSvgExtraStates states(m_doc);
1703 b = node->internalBounds(&p, states);
1708 if (node->hasMask())
1709 info.maskId = findOrCreateId(node->maskId());
1711 if (node->hasFilter())
1712 info.filterId = findOrCreateId(node->filterId());
1715QList<QSvgVisitorImpl::AnimationPair>
QSvgVisitorImpl::collectAnimations(
const QSvgNode *node,
1716 const QString &propertyName)
1719 QList<AnimationPair> ret;
1720 const QList<QSvgAbstractAnimation *> animations = m_doc->animator()->animationsForNode(node);
1721 for (
const QSvgAbstractAnimation *animation : animations) {
1722 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1723 for (
const QSvgAbstractAnimatedProperty *property : properties) {
1724 if (property->propertyName() == propertyName)
1725 ret.append(std::make_pair(animation, property));
1732void QSvgVisitorImpl::applyAnimationsToProperty(
const QList<AnimationPair> &animations,
1733 QQuickAnimatedProperty *outProperty,
1734 std::function<QVariant(
const QSvgAbstractAnimatedProperty *,
int index,
int animationIndex)> calculateValue)
1736 qCDebug(lcVectorImageAnimations) <<
"Applying animations to property with default value"
1737 << outProperty->defaultValue();
1738 for (
auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1739 qCDebug(lcVectorImageAnimations) <<
" -> Add animation";
1740 const QSvgAbstractAnimation *animation = it->first;
1741 const QSvgAbstractAnimatedProperty *property = it->second;
1743 const int start = animation->start();
1744 const int repeatCount = animation->iterationCount();
1745 const int duration = animation->duration();
1747 bool freeze =
false;
1748 bool replace =
true;
1749 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1750 const QSvgAnimateNode *animateNode =
static_cast<
const QSvgAnimateNode *>(animation);
1751 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1752 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1755 qCDebug(lcVectorImageAnimations) <<
" -> Start:" << start
1756 <<
", repeatCount:" << repeatCount
1757 <<
", freeze:" << freeze
1758 <<
", replace:" << replace;
1760 QList<qreal> propertyKeyFrames = property->keyFrames();
1761 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1765 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1766 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1767 const auto &components = transformProperty->components();
1768 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1769 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1770 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1771 outAnimation.repeatCount = repeatCount;
1772 outAnimation.startOffset = start;
1774 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1776 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1777 switch (components.at(i).type) {
1778 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1779 outAnimation.subtype = QTransform::TxTranslate;
1781 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1782 outAnimation.subtype = QTransform::TxScale;
1784 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1785 outAnimation.subtype = QTransform::TxRotate;
1787 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1788 outAnimation.subtype = QTransform::TxShear;
1791 qCWarning(lcQuickVectorImage()) <<
"Unhandled transform type:" << components.at(i).type;
1795 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1798 << property->propertyName()
1799 <<
" animation subtype:"
1800 << outAnimation.subtype;
1802 outAnimations.append(outAnimation);
1805 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1806 outAnimation.repeatCount = repeatCount;
1807 outAnimation.startOffset = start;
1809 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1811 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1814 << property->propertyName();
1816 outAnimations.append(outAnimation);
1819 outProperty->beginAnimationGroup();
1820 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
1821 for (
int i = 0; i < outAnimations.size(); ++i) {
1822 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1824 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
1825 const int time = qRound(propertyKeyFrames.at(j) * duration);
1827 const QVariant value = calculateValue(property, j, i);
1828 outAnimation.frames[time] = value;
1830 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
1831 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
1832 ? easingForAnimation(easingInterface, animation->animationType())
1835 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << value;
1838 outProperty->addAnimation(outAnimation);
1843QBezier
QSvgVisitorImpl::easingForAnimation(
const QSvgEasingInterface *easingInterface,
1844 QSvgAbstractAnimation::AnimationType type)
1846 constexpr QPointF startControlPoint(0, 0);
1847 constexpr QPointF endControlPoint(1, 1);
1848 constexpr QPointF easeC1(0.25, 0.1);
1849 constexpr QPointF easeC2(0.25, 1);
1851 QBezier easing = QBezier::fromPoints(startControlPoint, startControlPoint, endControlPoint, endControlPoint);
1853#if QT_CONFIG(cssparser)
1854 if (type == QSvgAbstractAnimation::CSS) {
1855 const QSvgCssEasing *cssEasing =
static_cast<
const QSvgCssEasing *>(easingInterface);
1856 switch (cssEasing->easingFunction()) {
1857 case QSvgCssValues::EasingFunction::Ease:
1858 case QSvgCssValues::EasingFunction::EaseIn:
1859 case QSvgCssValues::EasingFunction::EaseOut:
1860 case QSvgCssValues::EasingFunction::EaseInOut:
1861 case QSvgCssValues::EasingFunction::CubicBezier:
1862 case QSvgCssValues::EasingFunction::Linear:
1864 const QSvgCssCubicBezierEasing *cssCubicEasing =
static_cast<
const QSvgCssCubicBezierEasing *>(cssEasing);
1865 QPointF c1 = cssCubicEasing->c1();
1866 QPointF c2 = cssCubicEasing->c2();
1867 easing = QBezier::fromPoints(startControlPoint, c1, c2, endControlPoint);
1870 case QSvgCssValues::EasingFunction::Steps:
1872 qCDebug(lcVectorImageAnimations) <<
"Step easing is not supported reverting to default.";
1873 easing = QBezier::fromPoints(startControlPoint, easeC1, easeC2, endControlPoint);
1887 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1888 if (!animations.isEmpty())
1889 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1893 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1894 if (!animations.isEmpty())
1895 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1899 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1900 if (!animations.isEmpty())
1901 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1905 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1906 if (!animations.isEmpty())
1907 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1913 qCDebug(lcVectorImageAnimations) <<
"Applying transform animations to property with default value"
1914 << info.transform.defaultValue();
1916 auto calculateValue = [](
const QSvgAbstractAnimatedProperty *property,
int index,
int animationIndex) {
1917 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1920 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1921 const auto &components = transformProperty->components();
1923 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1925 QVariantList parameters;
1927 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1928 switch (component.type) {
1929 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1930 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1931 component.values.value(1))));
1933 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1934 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1935 component.values.value(2))));
1936 parameters.append(QVariant::fromValue(component.values.value(0)));
1938 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1939 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1940 component.values.value(1))));
1942 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1943 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1944 component.values.value(1))));
1947 qCWarning(lcVectorImageAnimations) <<
"Unhandled transform type:" << component.type;
1950 return QVariant::fromValue(parameters);
1955 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"transform"));
1956 if (!animations.isEmpty())
1957 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1963 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"offset-distance"));
1964 auto offset =
static_cast<QSvgOffsetStyle *>(node->style().property(QSvgStyleProperty::Offset));
1966 if (animations.isEmpty())
1970 qCWarning(lcQuickVectorImage) <<
"Motion path animation: No offset path";
1974 if (animations.size() > 1) {
1975 qCWarning(lcQuickVectorImage)
1976 <<
"Not supported: More than one offset path animation on same node";
1979 const AnimationPair &animationPair = animations.first();
1981 const QSvgAbstractAnimation *animation = animationPair.first;
1982 const QSvgAbstractAnimatedProperty *property = animationPair.second;
1984 const int start = animation->start();
1985 const int repeatCount = animation->iterationCount();
1986 const int duration = animation->duration();
1988 qCDebug(lcVectorImageAnimations) <<
"Motion path animation:"
1989 <<
"start == " << start
1990 <<
", repeatCount == " << repeatCount
1991 <<
"; duration == " << duration;
1994 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1995 outAnimation.repeatCount = repeatCount;
1996 outAnimation.startOffset = start;
1998 QPainterPath originalPath = offset->path();
2002 switch (offset->rotateType()) {
2003 case QtSvg::OffsetRotateType::Auto:
2007 case QtSvg::OffsetRotateType::Angle:
2009 baseRotation = offset->rotateAngle();
2011 case QtSvg::OffsetRotateType::AutoAngle:
2013 baseRotation = offset->rotateAngle();
2015 case QtSvg::OffsetRotateType::Reverse:
2017 baseRotation = 180.0;
2019 case QtSvg::OffsetRotateType::ReverseAngle:
2021 baseRotation = offset->rotateAngle() + 180.0f;
2028 QVariantList params({ QVariant::fromValue(originalPath), adaptAngle, baseRotation });
2029 info.motionPath.setDefaultValue(params);
2031 const QList<qreal> propertyKeyFrames = property->keyFrames();
2032 outAnimation.frames[0] = qreal(0);
2033 const auto animationEasing = easingForAnimation(animation->easing(), animation->animationType());
2034 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
2035 const int time = qRound(propertyKeyFrames.at(j) * duration);
2037 qreal t = calculateInterpolatedValue(property, j, 0).toReal();
2038 outAnimation.frames[time] = t;
2040 const QSvgEasingInterface *easingInterface = j > 0 ? property->easingAt(j - 1) :
nullptr;
2041 outAnimation.easingPerFrame[time] = easingInterface !=
nullptr
2042 ? easingForAnimation(easingInterface, animation->animationType())
2045 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << t;
2049 info.motionPath.addAnimation(outAnimation);
2054 fillColorAnimationInfo(node, info);
2055 fillAnimationInfo(node, info);
2061 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"opacity"));
2062 if (!animations.isEmpty())
2063 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
2066 fillTransformAnimationInfo(node, info);
2067 fillMotionPathAnimationInfo(node, info);
2072 qCDebug(lcQuickVectorImage) <<
"Before SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2073 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2074 << node->nodeId() <<
" type: " << node->typeName() <<
" " << node->type();
2076 node->applyStyle(&m_styleResolver->painter(), m_styleResolver->states());
2078 qCDebug(lcQuickVectorImage) <<
"After SETUP" << node <<
"fill" << m_styleResolver->currentFillColor()
2079 <<
"stroke" << m_styleResolver->currentStrokeColor()
2080 << m_styleResolver->currentStrokeWidth() << node->nodeId();
2086 fillCommonNodeInfo(node, info);
2088 m_generator->generateNodeBase(info);
2093 node->revertStyle(&m_styleResolver->painter(), m_styleResolver->states());
2095 qCDebug(lcQuickVectorImage) <<
"After END" << node <<
"fill" << m_styleResolver->currentFillColor()
2096 <<
"stroke" << m_styleResolver->currentStrokeColor() << m_styleResolver->currentStrokeWidth()
2100void QSvgVisitorImpl::handlePathNode(
const QSvgNode *node,
const QPainterPath &path)
2102 handleBaseNodeSetup(node);
2105 fillCommonNodeInfo(node, info);
2107 if (node->hasMarkerStart())
2108 info.markerStartId = findOrCreateId(node->markerStartId());
2110 if (node->hasMarkerMid())
2111 info.markerMidId = findOrCreateId(node->markerMidId());
2113 if (node->hasMarkerEnd())
2114 info.markerEndId = findOrCreateId(node->markerEndId());
2116 const QGradient *strokeGradient = m_styleResolver->currentStrokeGradient();
2117 auto strokeStyle =
static_cast<QSvgStrokeStyle *>(node->style().property(QSvgStyleProperty::Stroke));
2118 bool hasStrokePattern = strokeStyle
2119 && strokeStyle->paintServer()
2120 && strokeStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern;
2122 info.path.setDefaultValue(QVariant::fromValue(path));
2123 info.fillColor.setDefaultValue(m_styleResolver->currentFillColor());
2124 if (strokeGradient !=
nullptr)
2125 info.strokeGrad = *strokeGradient;
2127 if (!hasStrokePattern) {
2128 info.strokeStyle = StrokeStyle::fromPen(m_styleResolver->currentStroke());
2129 info.strokeStyle.color.setDefaultValue(m_styleResolver->currentStrokeColor());
2131 if (m_styleResolver->currentFillGradient() !=
nullptr)
2132 info.grad = m_styleResolver->applyOpacityToGradient(*m_styleResolver->currentFillGradient(), m_styleResolver->currentFillOpacity());
2133 info.fillTransform = m_styleResolver->currentFillTransform();
2135 auto fillStyle =
static_cast<QSvgFillStyle *>(node->style().property(QSvgStyleProperty::Fill));
2137 info.fillRule = fillStyle->fillRule();
2139 if (fillStyle->paintServer()
2140 && fillStyle->paintServer()->type() == QSvgPaintServer::Type::Pattern) {
2141 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(fillStyle->paintServer());
2142 info.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2148 info.fillTransform = paintServer->patternNode()->transform();
2152 fillPathAnimationInfo(node, info);
2154 m_generator->generatePath(info);
2156 if (hasStrokePattern) {
2158 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke"));
2160 QSvgPatternPaint *paintServer =
static_cast<QSvgPatternPaint *>(strokeStyle->paintServer());
2161 strokeInfo.patternId = findOrCreateId(paintServer->patternNode()->nodeId());
2162 strokeInfo.fillTransform = paintServer->patternNode()->transform();
2164 QPainterPathStroker stroker(m_styleResolver->currentStroke());
2165 strokeInfo.path.setDefaultValue(QVariant::fromValue(stroker.createStroke(path)));
2166 m_generator->generatePath(strokeInfo);
2169 handleBaseNodeEnd(node);
2174 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
2176 info.markerSize = node->rect().size();
2177 info.anchorPoint = node->refP();
2178 info.clipBox = oldTransform.mapRect(node->clipRect());
2179 info.viewBox = node->viewBox();
2180 switch (node->orientation()) {
2181 case QSvgMarker::Orientation::Auto:
2184 case QSvgMarker::Orientation::AutoStartReverse:
2187 case QSvgMarker::Orientation::Value:
2192 switch (node->markerUnits()) {
2193 case QSvgMarker::MarkerUnits::UserSpaceOnUse:
2196 case QSvgMarker::MarkerUnits::StrokeWidth:
2201 info.angle = node->orientationAngle();
2203 QTransform xform = node->aspectRatioTransform();
2204 if (!xform.isIdentity()) {
2205 info.isDefaultTransform =
false;
2206 xform = xform * oldTransform;
2207 info.transform.setDefaultValue(QVariant::fromValue(xform));
2215 if (!m_pregeneratingReferencedNodes)
2218 handleBaseNodeSetup(node);
2222 fillCommonNodeInfo(node, info);
2223 fillAnimationInfo(node, info);
2224 fillMarkerInfo(node, info);
2227 return m_generator->generateMarkerNode(info);
2232 handleBaseNodeEnd(node);
2235 fillCommonNodeInfo(node, info);
2236 fillMarkerInfo(node, info);
2239 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