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>
37Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations,
"qt.quick.vectorimage.animations")
39using namespace Qt::StringLiterals;
46 m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
47 m_dummyPainter.begin(&m_dummyImage);
48 QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
49 defaultPen.setMiterLimit(4);
50 m_dummyPainter.setPen(defaultPen);
51 m_dummyPainter.setBrush(Qt::black);
64 if (m_dummyPainter.brush().style() == Qt::NoBrush ||
65 m_dummyPainter.brush().color() == QColorConstants::Transparent) {
66 return QColor(QColorConstants::Transparent);
70 fillColor = m_dummyPainter.brush().color();
71 fillColor.setAlphaF(m_svgState.fillOpacity);
78 return m_svgState.fillOpacity;
83 QBrush brush = m_dummyPainter.pen().brush();
84 if (brush.style() == Qt::LinearGradientPattern
85 || brush.style() == Qt::RadialGradientPattern
86 || brush.style() == Qt::ConicalGradientPattern) {
87 return brush.gradient();
94 if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
95 return m_dummyPainter.brush().gradient();
101 return m_dummyPainter.brush().transform();
106 if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
107 m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
108 return QColor(QColorConstants::Transparent);
112 strokeColor = m_dummyPainter.pen().brush().color();
113 strokeColor.setAlphaF(m_svgState.strokeOpacity);
120 QGradient grad = gradient;
121 QGradientStops stops;
122 for (
auto &stop : grad.stops()) {
123 stop.second.setAlphaF(stop.second.alphaF() * opacity);
127 grad.setStops(stops);
134 float penWidth = m_dummyPainter.pen().widthF();
135 return penWidth ? penWidth : 1;
140 return m_dummyPainter.pen();
152inline bool isPathContainer(
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:
175 case QSvgNode::Symbol:
180 case QSvgNode::Circle:
181 case QSvgNode::Ellipse:
184 case QSvgNode::Polygon:
185 case QSvgNode::Polyline:
187 if (!child->style().transform.isDefault()) {
191 const QList<QSvgAbstractAnimation *> animations = child->document()->animator()->animationsForNode(child);
192 if (!animations.isEmpty()) {
200 qCDebug(lcQuickVectorImage) <<
"Unhandled type in switch" << child->type();
208static QString capStyleName(Qt::PenCapStyle style)
214 styleName = QStringLiteral(
"squarecap");
217 styleName = QStringLiteral(
"flatcap");
220 styleName = QStringLiteral(
"roundcap");
229static QString joinStyleName(Qt::PenJoinStyle style)
235 styleName = QStringLiteral(
"miterjoin");
238 styleName = QStringLiteral(
"beveljoin");
241 styleName = QStringLiteral(
"roundjoin");
243 case Qt::SvgMiterJoin:
244 styleName = QStringLiteral(
"svgmiterjoin");
253static QString dashArrayString(QList<qreal> dashArray)
255 if (dashArray.isEmpty())
258 QString dashArrayString;
259 QTextStream stream(&dashArrayString);
261 for (
int i = 0; i < dashArray.length() - 1; i++) {
262 qreal value = dashArray[i];
263 stream << value <<
", ";
266 stream << dashArray.last();
268 return dashArrayString;
273 QQuickGenerator *generator,
274 bool assumeTrustedSource)
277 , m_assumeTrustedSource(assumeTrustedSource)
284 qCDebug(lcQuickVectorImage) <<
"No valid QQuickGenerator is set. Genration will stop";
288 QtSvg::Options options;
289 if (m_assumeTrustedSource)
290 options.setFlag(QtSvg::AssumeTrustedSource);
292 auto *doc = QSvgTinyDocument::load(m_svgFileName, options);
294 qCDebug(lcQuickVectorImage) <<
"Not a valid Svg File : " << m_svgFileName;
298 QSvgVisitor::traverse(doc);
304 handleBaseNodeSetup(node);
307 fillCommonNodeInfo(node, info);
308 fillAnimationInfo(node, info);
310 m_generator->generateNode(info);
312 handleBaseNodeEnd(node);
318 handleBaseNodeSetup(node);
321 fillCommonNodeInfo(node, info);
322 fillAnimationInfo(node, info);
323 info.image = node->image();
324 info.rect = node->rect();
325 info.externalFileReference = node->filename();
327 m_generator->generateImageNode(info);
329 handleBaseNodeEnd(node);
334 QRectF rect = node->rect();
335 QPointF rads = node->radius();
337 qreal x1 = rect.left();
338 qreal x2 = rect.right();
339 qreal y1 = rect.top();
340 qreal y2 = rect.bottom();
342 qreal rx = rads.x() * rect.width() / 200;
343 qreal ry = rads.y() * rect.height() / 200;
346 p.moveTo(x1 + rx, y1);
347 p.lineTo(x2 - rx, y1);
349 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90);
352 p.lineTo(x2, y2 - ry);
353 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90);
355 p.lineTo(x1 + rx, y2);
356 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90);
358 p.lineTo(x1, y1 + ry);
359 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90);
361 handlePathNode(node, p);
366 QRectF rect = node->rect();
371 handlePathNode(node, p);
376 handlePathNode(node, node->path());
382 p.moveTo(node->line().p1());
383 p.lineTo(node->line().p2());
384 handlePathNode(node, p);
389 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
true);
390 handlePathNode(node, p);
395 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
false);
396 handlePathNode(node, p);
399QString
QSvgVisitorImpl::gradientCssDescription(
const QGradient *gradient)
401 QString cssDescription;
402 if (gradient->type() == QGradient::LinearGradient) {
403 const QLinearGradient *linearGradient =
static_cast<
const QLinearGradient *>(gradient);
405 cssDescription +=
" -qt-foreground: qlineargradient("_L1;
406 cssDescription +=
"x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
407 cssDescription +=
"y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
408 cssDescription +=
"x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
409 cssDescription +=
"y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
410 }
else if (gradient->type() == QGradient::RadialGradient) {
411 const QRadialGradient *radialGradient =
static_cast<
const QRadialGradient *>(gradient);
413 cssDescription +=
" -qt-foreground: qradialgradient("_L1;
414 cssDescription +=
"cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
415 cssDescription +=
"cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
416 cssDescription +=
"fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
417 cssDescription +=
"fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
418 cssDescription +=
"radius:"_L1 + QString::number(radialGradient->radius()) + u',';
420 const QConicalGradient *conicalGradient =
static_cast<
const QConicalGradient *>(gradient);
422 cssDescription +=
" -qt-foreground: qconicalgradient("_L1;
423 cssDescription +=
"cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
424 cssDescription +=
"cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
425 cssDescription +=
"angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
428 const QStringList coordinateModes = {
"logical"_L1,
"stretchtodevice"_L1,
"objectbounding"_L1,
"object"_L1 };
429 cssDescription +=
"coordinatemode:"_L1;
430 cssDescription += coordinateModes.at(
int(gradient->coordinateMode()));
431 cssDescription += u',';
433 const QStringList spreads = {
"pad"_L1,
"reflect"_L1,
"repeat"_L1 };
434 cssDescription +=
"spread:"_L1;
435 cssDescription += spreads.at(
int(gradient->spread()));
437 for (
const QGradientStop &stop : gradient->stops()) {
438 cssDescription +=
",stop:"_L1;
439 cssDescription += QString::number(stop.first);
440 cssDescription += u' ';
441 cssDescription += stop.second.name(QColor::HexArgb);
444 cssDescription +=
");"_L1;
446 return cssDescription;
451 QString cssDescription;
452 cssDescription += QStringLiteral(
"rgba(");
453 cssDescription += QString::number(color.red()) + QStringLiteral(
",");
454 cssDescription += QString::number(color.green()) + QStringLiteral(
",");
455 cssDescription += QString::number(color.blue()) + QStringLiteral(
",");
456 cssDescription += QString::number(color.alphaF()) + QStringLiteral(
")");
458 return cssDescription;
467 class QSvgFontEngine :
public QFontEngine
470 QSvgFontEngine(
const QSvgFont *font, qreal size);
472 QFontEngine *cloneWithSize(qreal size)
const override;
474 glyph_t glyphIndex(uint ucs4)
const override;
475 int stringToCMap(
const QChar *str,
477 QGlyphLayout *glyphs,
479 ShaperFlags flags)
const override;
481 void addGlyphsToPath(glyph_t *glyphs,
482 QFixedPoint *positions,
485 QTextItem::RenderFlags flags)
override;
487 glyph_metrics_t boundingBox(glyph_t glyph)
override;
489 void recalcAdvances(QGlyphLayout *, ShaperFlags)
const override;
490 QFixed ascent()
const override;
491 QFixed capHeight()
const override;
492 QFixed descent()
const override;
493 QFixed leading()
const override;
494 qreal maxCharWidth()
const override;
495 qreal minLeftBearing()
const override;
496 qreal minRightBearing()
const override;
498 QFixed emSquareSize()
const override;
501 const QSvgFont *m_font;
504 QSvgFontEngine::QSvgFontEngine(
const QSvgFont *font, qreal size)
508 fontDef.pixelSize = size;
509 fontDef.families = QStringList(m_font->m_familyName);
512 QFixed QSvgFontEngine::emSquareSize()
const
514 return QFixed::fromReal(m_font->m_unitsPerEm);
517 glyph_t QSvgFontEngine::glyphIndex(uint ucs4)
const
519 if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(QChar(ushort(ucs4))))
520 return glyph_t(ucs4);
525 int QSvgFontEngine::stringToCMap(
const QChar *str,
527 QGlyphLayout *glyphs,
529 ShaperFlags flags)
const
531 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
532 if (*nglyphs < len) {
538 QStringIterator it(str, str + len);
539 while (it.hasNext()) {
540 char32_t ucs4 = it.next();
541 glyph_t index = glyphIndex(ucs4);
542 glyphs->glyphs[ucs4Length++] = index;
545 *nglyphs = ucs4Length;
546 glyphs->numGlyphs = ucs4Length;
548 if (!(flags & GlyphIndicesOnly))
549 recalcAdvances(glyphs, flags);
554 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
555 QFixedPoint *positions,
558 QTextItem::RenderFlags flags)
561 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
562 for (
int i = 0; i < nGlyphs; ++i) {
563 glyph_t index = glyphs[i];
565 QPointF position = positions[i].toPointF();
566 QPainterPath glyphPath = m_font->m_glyphs.value(QChar(ushort(index))).m_path;
569 xform.translate(position.x(), position.y());
570 xform.scale(scale, -scale);
571 glyphPath = xform.map(glyphPath);
572 path->addPath(glyphPath);
577 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
582 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
583 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
584 ret.width = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
585 ret.height = ascent() + descent();
589 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size)
const
591 QSvgFontEngine *otherEngine =
new QSvgFontEngine(m_font, size);
595 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags)
const
597 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
598 for (
int i = 0; i < glyphLayout->numGlyphs; i++) {
599 glyph_t glyph = glyphLayout->glyphs[i];
600 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
601 glyphLayout->advances[i] = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
605 QFixed QSvgFontEngine::ascent()
const
607 return QFixed::fromReal(fontDef.pixelSize);
610 QFixed QSvgFontEngine::capHeight()
const
614 QFixed QSvgFontEngine::descent()
const
619 QFixed QSvgFontEngine::leading()
const
624 qreal QSvgFontEngine::maxCharWidth()
const
626 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
627 return m_font->m_horizAdvX * scale;
630 qreal QSvgFontEngine::minLeftBearing()
const
635 qreal QSvgFontEngine::minRightBearing()
const
644 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
646 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
648 return property->interpolatedValue();
653 handleBaseNodeSetup(node);
654 const bool isTextArea = node->type() == QSvgNode::Textarea;
657 const QSvgFont *svgFont = styleResolver->states().svgFont;
658 bool needsRichText =
false;
659 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
660 const QGradient *mainGradient = styleResolver->currentFillGradient();
662 QFontEngine *fontEngine =
nullptr;
663 if (svgFont !=
nullptr) {
664 fontEngine =
new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize());
665 fontEngine->ref.ref();
668#if QT_CONFIG(texthtmlparser)
669 bool needsPathNode = mainGradient !=
nullptr
670 || svgFont !=
nullptr
671 || styleResolver->currentStrokeGradient() !=
nullptr;
673 for (
const auto *tspan : node->tspans()) {
675 text += QStringLiteral(
"<br>");
681 handleBaseNodeSetup(tspan);
682 QFont font = styleResolver->painter().font();
684 QString styleTagContent;
686 if ((font.resolveMask() & QFont::FamilyResolved)
687 || (font.resolveMask() & QFont::FamiliesResolved)) {
688 styleTagContent += QStringLiteral(
"font-family: %1;").arg(font.family());
691 if (font.resolveMask() & QFont::WeightResolved
692 && font.weight() != QFont::Normal
693 && font.weight() != QFont::Bold) {
694 styleTagContent += QStringLiteral(
"font-weight: %1;").arg(
int(font.weight()));
697 if (font.resolveMask() & QFont::SizeResolved) {
699 styleTagContent += QStringLiteral(
"font-size: %1px;").arg(
int(font.pointSizeF()));
702 if (font.resolveMask() & QFont::CapitalizationResolved
703 && font.capitalization() == QFont::SmallCaps) {
704 styleTagContent += QStringLiteral(
"font-variant: small-caps;");
707 if (styleResolver->currentFillGradient() !=
nullptr
708 && styleResolver->currentFillGradient() != mainGradient) {
709 const QGradient grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
710 styleTagContent += gradientCssDescription(&grad) + u';';
711#if QT_CONFIG(texthtmlparser)
712 needsPathNode =
true;
716 const QColor currentStrokeColor = styleResolver->currentStrokeColor();
717 if (currentStrokeColor.alpha() > 0) {
718 QString strokeColor = colorCssDescription(currentStrokeColor);
719 styleTagContent += QStringLiteral(
"-qt-stroke-color:%1;").arg(strokeColor);
720 styleTagContent += QStringLiteral(
"-qt-stroke-width:%1px;").arg(styleResolver->currentStrokeWidth());
721 styleTagContent += QStringLiteral(
"-qt-stroke-dasharray:%1;").arg(dashArrayString(styleResolver->currentStroke().dashPattern()));
722 styleTagContent += QStringLiteral(
"-qt-stroke-dashoffset:%1;").arg(styleResolver->currentStroke().dashOffset());
723 styleTagContent += QStringLiteral(
"-qt-stroke-lineCap:%1;").arg(capStyleName(styleResolver->currentStroke().capStyle()));
724 styleTagContent += QStringLiteral(
"-qt-stroke-lineJoin:%1;").arg(joinStyleName(styleResolver->currentStroke().joinStyle()));
725 if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
726 styleTagContent += QStringLiteral(
"-qt-stroke-miterlimit:%1;").arg(styleResolver->currentStroke().miterLimit());
727#if QT_CONFIG(texthtmlparser)
728 needsPathNode =
true;
732 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
733 styleTagContent += QStringLiteral(
"white-space: pre-wrap;");
735 QString content = tspan->text().toHtmlEscaped();
736 content.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
737 content.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
739 bool fontTag =
false;
740 if (!tspan->style().fill.isDefault()) {
741 auto &b = tspan->style().fill->qbrush();
742 qCDebug(lcQuickVectorImage) <<
"tspan FILL:" << b;
743 if (b.style() != Qt::NoBrush)
745 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
747 QString spanColor = b.color().name();
748 fontTag = !spanColor.isEmpty();
750 text += QStringLiteral(
"<font color=\"%1\">").arg(spanColor);
752 QString spanColor = colorCssDescription(b.color());
753 styleTagContent += QStringLiteral(
"color:%1").arg(spanColor);
758 needsRichText = needsRichText || !styleTagContent.isEmpty();
759 if (!styleTagContent.isEmpty())
760 text += QStringLiteral(
"<span style=\"%1\">").arg(styleTagContent);
762 if (font.resolveMask() & QFont::WeightResolved && font.bold())
763 text += QStringLiteral(
"<b>");
765 if (font.resolveMask() & QFont::StyleResolved && font.italic())
766 text += QStringLiteral(
"<i>");
768 if (font.resolveMask() & QFont::CapitalizationResolved) {
769 switch (font.capitalization()) {
770 case QFont::AllLowercase:
771 content = content.toLower();
773 case QFont::AllUppercase:
774 content = content.toUpper();
776 case QFont::Capitalize:
779 qCWarning(lcQuickVectorImage) <<
"Title case not implemented for tspan";
787 text += QStringLiteral(
"</font>");
789 if (font.resolveMask() & QFont::StyleResolved && font.italic())
790 text += QStringLiteral(
"</i>");
792 if (font.resolveMask() & QFont::WeightResolved && font.bold())
793 text += QStringLiteral(
"</b>");
795 if (!styleTagContent.isEmpty())
796 text += QStringLiteral(
"</span>");
798 handleBaseNodeEnd(tspan);
801 if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() !=
nullptr))
802 text = QStringLiteral(
"<span style=\"white-space: pre-wrap\">") + text + QStringLiteral(
"</span>");
804 QFont font = styleResolver->painter().font();
805 if (font.pixelSize() <= 0 && font.pointSize() > 0)
806 font.setPixelSize(font.pointSize());
808 font.setHintingPreference(QFont::PreferNoHinting);
810#if QT_CONFIG(texthtmlparser)
812 QTextDocument document;
813 document.setHtml(text);
814 if (isTextArea && node->size().width() > 0)
815 document.setTextWidth(node->size().width());
816 document.setDefaultFont(font);
817 document.pageCount();
819 QTextBlock block = document.firstBlock();
820 while (block.isValid()) {
821 QTextLayout *lout = block.layout();
823 if (lout !=
nullptr) {
824 QRectF boundingRect = lout->boundingRect();
830 QFont blockFont = block.charFormat().font();
831 if (svgFont !=
nullptr
832 && blockFont.family() == svgFont->m_familyName) {
834 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
835 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
837 lout->setRawFont(rawFont);
840 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt,
int pathIndex) {
842 fillCommonNodeInfo(node, info, QStringLiteral(
"_path%1").arg(pathIndex));
843 fillPathAnimationInfo(node, info);
844 auto fillStyle = node->style().fill;
846 info.fillRule = fillStyle->fillRule();
848 if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
849 info.fillColor.setDefaultValue(fmt.foreground().color());
850 if (fmt.foreground().gradient() !=
nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
851 info.grad = *fmt.foreground().gradient();
853 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
856 info.painterPath = p;
858 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
860 if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
861 pen = fmt.textOutline();
862 if (strokeGradient ==
nullptr) {
863 info.strokeStyle = StrokeStyle::fromPen(pen);
864 info.strokeStyle.color.setDefaultValue(pen.color());
867 pen = styleResolver->currentStroke();
868 if (strokeGradient ==
nullptr) {
869 info.strokeStyle = StrokeStyle::fromPen(pen);
870 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
874 if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() !=
nullptr)
875 info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
877 info.fillTransform = styleResolver->currentFillTransform();
879 m_generator->generatePath(info, boundingRect);
881 if (strokeGradient !=
nullptr) {
882 PathNodeInfo strokeInfo;
883 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke%1").arg(pathIndex));
884 fillPathAnimationInfo(node, strokeInfo);
886 strokeInfo.grad = *strokeGradient;
888 QPainterPathStroker stroker(pen);
889 strokeInfo.painterPath = stroker.createStroke(p);
890 m_generator->generatePath(strokeInfo, boundingRect);
894 qreal baselineOffset = -QFontMetricsF(font).ascent();
895 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
896 baselineOffset = -lout->lineAt(0).ascent();
898 const QPointF baselineTranslation(0.0, baselineOffset);
899 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
900 QList<QPainterPath> paths;
901 for (
const QGlyphRun &glyphRun : glyphRuns) {
902 QRawFont font = glyphRun.rawFont();
903 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
904 QList<QPointF> positions = glyphRun.positions();
906 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
907 quint32 glyphIndex = glyphIndexes.at(j);
908 const QPointF &pos = positions.at(j);
910 QPainterPath p = font.pathForGlyph(glyphIndex);
911 p.translate(pos + node->position() + baselineTranslation);
912 if (styleResolver->states().textAnchor == Qt::AlignHCenter)
913 p.translate(QPointF(-0.5 * width, 0));
914 else if (styleResolver->states().textAnchor == Qt::AlignRight)
915 p.translate(QPointF(-width, 0));
923 QList<QTextLayout::FormatRange> formats = block.textFormats();
924 for (
int i = 0; i < formats.size(); ++i) {
925 QTextLayout::FormatRange range = formats.at(i);
927 QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
928 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
929 for (
int j = 0; j < paths.size(); ++j) {
930 const QPainterPath &path = paths.at(j);
931 addPathForFormat(path, range.format, j);
936 block = block.next();
942 fillCommonNodeInfo(node, info);
943 fillAnimationInfo(node, info);
946 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
947 if (!animations.isEmpty())
948 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
952 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
953 if (!animations.isEmpty())
954 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
958 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
959 if (!animations.isEmpty())
960 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
964 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
965 if (!animations.isEmpty())
966 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
969 info.position = node->position();
970 info.size = node->size();
975 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
976 info.alignment = styleResolver->states().textAnchor;
977 info.strokeColor.setDefaultValue(styleResolver->currentStrokeColor());
979 m_generator->generateTextNode(info);
982 handleBaseNodeEnd(node);
984 if (fontEngine !=
nullptr) {
985 fontEngine->ref.deref();
986 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
993 QSvgNode *link = node->link();
997 handleBaseNodeSetup(node);
999 fillCommonNodeInfo(node, info);
1000 fillAnimationInfo(node, info);
1004 QPointF startPos = node->start();
1005 if (!startPos.isNull()) {
1007 if (!info.isDefaultTransform)
1008 xform = info.transform.defaultValue().value<QTransform>();
1009 xform.translate(startPos.x(), startPos.y());
1010 info.transform.setDefaultValue(QVariant::fromValue(xform));
1014 m_generator->generateUseNode(info);
1016 QString oldLinkSuffix = m_linkSuffix;
1017 m_linkSuffix += QStringLiteral(
"_use") + info.id;
1020 QSvgVisitor::traverse(link);
1022 m_linkSuffix = oldLinkSuffix;
1025 m_generator->generateUseNode(info);
1026 handleBaseNodeEnd(node);
1031 QSvgNode *link = node->childToRender();
1035 QString oldLinkSuffix = m_linkSuffix;
1036 m_linkSuffix += QStringLiteral(
"_switch") + QString::number(quintptr(node), 16);
1037 QSvgVisitor::traverse(link);
1038 m_linkSuffix = oldLinkSuffix;
1052 return m_generator->generateDefsNode(NodeInfo{});
1057 if (m_useLevel == 0)
1060 handleBaseNodeSetup(node);
1063 fillCommonNodeInfo(node, info);
1064 fillAnimationInfo(node, info);
1066 QTransform oldTransform = info.transform.defaultValue().value<QTransform>();
1067 info.clipBox = oldTransform.mapRect(node->clipRect());
1069 QTransform xform = node->aspectRatioTransform();
1070 if (!xform.isIdentity()) {
1072 xform = xform * oldTransform;
1073 info.transform.setDefaultValue(QVariant::fromValue(xform));
1077 return m_generator->generateStructureNode(info);
1082 Q_ASSERT(m_useLevel > 0);
1083 handleBaseNodeSetup(node);
1086 fillCommonNodeInfo(node, info);
1087 fillAnimationInfo(node, info);
1089 info.clipBox = node->clipRect();
1092 m_generator->generateStructureNode(info);
1097 handleBaseNodeSetup(node);
1101 QSvgRectF r = node->rect();
1102 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1105 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1106 qCWarning(lcQuickVectorImage) <<
"Only user space content units supported for masks";
1108 fillCommonNodeInfo(node, info);
1110 return m_generator->generateMaskNode(info);
1118 QSvgRectF r = node->rect();
1119 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1121 fillCommonNodeInfo(node, info);
1123 m_generator->generateMaskNode(info);
1125 handleBaseNodeEnd(node);
1130 constexpr bool forceSeparatePaths =
false;
1131 handleBaseNodeSetup(node);
1135 fillCommonNodeInfo(node, info);
1136 fillAnimationInfo(node, info);
1138 info.isPathContainer = isPathContainer(node);
1141 return m_generator->generateStructureNode(info);
1146 handleBaseNodeEnd(node);
1151 fillCommonNodeInfo(node, info);
1152 info.isPathContainer = isPathContainer(node);
1155 m_generator->generateStructureNode(info);
1160 return QStringLiteral(
"_qt_node%1").arg(m_nodeIdCounter++);
1165 handleBaseNodeSetup(node);
1168 fillCommonNodeInfo(node, info);
1169 fillAnimationInfo(node, info);
1171 const QSvgTinyDocument *doc =
static_cast<
const QSvgTinyDocument *>(node);
1172 info.size = doc->size();
1173 info.viewBox = doc->viewBox();
1174 info.isPathContainer = isPathContainer(node);
1178 return m_generator->generateRootNode(info);
1183 handleBaseNodeEnd(node);
1184 qCDebug(lcQuickVectorImage) <<
"REVERT" << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen)
1185 << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1186 << styleResolver->painter().pen().brush().color().name();
1189 fillCommonNodeInfo(node, info);
1192 m_generator->generateRootNode(info);
1197 const QString key = node->nodeId().isEmpty()
1198 ? QString::number(quintptr(node), 16)
1200 info.id = m_idForNodeId.value(key);
1201 if (info.id.isEmpty()) {
1202 info.id = nextNodeId();
1203 m_idForNodeId.insert(key, info.id);
1207 info.id += idSuffix;
1209 if (!m_linkSuffix.isEmpty())
1210 info.id += m_linkSuffix;
1212 info.nodeId = node->nodeId();
1213 info.typeName = node->typeName();
1216 QTransform xf = !info
.isDefaultTransform ? node->style().transform->qtransform() : QTransform();
1217 info.transform.setDefaultValue(QVariant::fromValue(xf));
1219 info.opacity.setDefaultValue(!info
.isDefaultOpacity ? node->style().opacity->opacity() : 1.0);
1221 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1223 if (node->hasMask() || node->type() == QSvgNode::Type::Mask) {
1224 info.bounds = node->bounds();
1226 if (!xf.isIdentity()) {
1228 xf = xf.inverted(&ok);
1230 info.bounds = xf.mapRect(info.bounds);
1232 qCWarning(lcQuickVectorImage)
1233 <<
"Masked item with non-invertible transform currently not supported.";
1238 if (node->hasMask()) {
1239 info.maskId = m_idForNodeId.value(node->maskId());
1240 if (info.maskId.isEmpty()) {
1241 info.maskId = nextNodeId();
1242 m_idForNodeId.insert(node->maskId(), info.maskId);
1247QList<QSvgVisitorImpl::AnimationPair>
QSvgVisitorImpl::collectAnimations(
const QSvgNode *node,
1248 const QString &propertyName)
1250 QList<AnimationPair> ret;
1251 const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
1252 for (
const QSvgAbstractAnimation *animation : animations) {
1253 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1254 for (
const QSvgAbstractAnimatedProperty *property : properties) {
1255 if (property->propertyName() == propertyName)
1256 ret.append(std::make_pair(animation, property));
1263void QSvgVisitorImpl::applyAnimationsToProperty(
const QList<AnimationPair> &animations,
1264 QQuickAnimatedProperty *outProperty,
1265 std::function<QVariant(
const QSvgAbstractAnimatedProperty *,
int index,
int animationIndex)> calculateValue)
1267 qCDebug(lcVectorImageAnimations) <<
"Applying animations to property with default value"
1268 << outProperty->defaultValue();
1269 for (
auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1270 qCDebug(lcVectorImageAnimations) <<
" -> Add animation";
1271 const QSvgAbstractAnimation *animation = it->first;
1272 const QSvgAbstractAnimatedProperty *property = it->second;
1274 const int start = animation->start();
1275 const int repeatCount = animation->iterationCount();
1276 const int duration = animation->duration();
1278 bool freeze =
false;
1279 bool replace =
true;
1280 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1281 const QSvgAnimateNode *animateNode =
static_cast<
const QSvgAnimateNode *>(animation);
1282 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1283 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1286 qCDebug(lcVectorImageAnimations) <<
" -> Start:" << start
1287 <<
", repeatCount:" << repeatCount
1288 <<
", freeze:" << freeze
1289 <<
", replace:" << replace;
1291 QList<qreal> propertyKeyFrames = property->keyFrames();
1292 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1296 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1297 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1298 const auto &components = transformProperty->components();
1299 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1300 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1301 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1302 outAnimation.repeatCount = repeatCount;
1303 outAnimation.startOffset = start;
1305 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1307 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1308 switch (components.at(i).type) {
1309 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1310 outAnimation.subtype = QTransform::TxTranslate;
1312 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1313 outAnimation.subtype = QTransform::TxScale;
1315 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1316 outAnimation.subtype = QTransform::TxRotate;
1318 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1319 outAnimation.subtype = QTransform::TxShear;
1322 qCWarning(lcQuickVectorImage()) <<
"Unhandled transform type:" << components.at(i).type;
1326 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1329 << property->propertyName()
1330 <<
" animation subtype:"
1331 << outAnimation.subtype;
1333 outAnimations.append(outAnimation);
1336 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1337 outAnimation.repeatCount = repeatCount;
1338 outAnimation.startOffset = start;
1340 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1342 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1345 << property->propertyName();
1347 outAnimations.append(outAnimation);
1350 outProperty->beginAnimationGroup();
1351 for (
int i = 0; i < outAnimations.size(); ++i) {
1352 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1354 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
1355 const int time = qRound(propertyKeyFrames.at(j) * duration);
1357 const QVariant value = calculateValue(property, j, i);
1358 outAnimation.frames[time] = value;
1359 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << value;
1362 outProperty->addAnimation(outAnimation);
1371 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1372 if (!animations.isEmpty())
1373 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1377 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1378 if (!animations.isEmpty())
1379 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1383 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1384 if (!animations.isEmpty())
1385 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1389 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1390 if (!animations.isEmpty())
1391 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1397 qCDebug(lcVectorImageAnimations) <<
"Applying transform animations to property with default value"
1398 << info.transform.defaultValue();
1400 auto calculateValue = [](
const QSvgAbstractAnimatedProperty *property,
int index,
int animationIndex) {
1401 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1404 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1405 const auto &components = transformProperty->components();
1407 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1409 QVariantList parameters;
1411 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1412 switch (component.type) {
1413 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1414 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1415 component.values.value(1))));
1417 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1418 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1419 component.values.value(2))));
1420 parameters.append(QVariant::fromValue(component.values.value(0)));
1422 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1423 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1424 component.values.value(1))));
1426 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1427 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1428 component.values.value(1))));
1431 qCWarning(lcVectorImageAnimations) <<
"Unhandled transform type:" << component.type;
1434 return QVariant::fromValue(parameters);
1439 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"transform"));
1440 if (!animations.isEmpty())
1441 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1447 fillColorAnimationInfo(node, info);
1448 fillAnimationInfo(node, info);
1454 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"opacity"));
1455 if (!animations.isEmpty())
1456 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
1459 fillTransformAnimationInfo(node, info);
1464 qCDebug(lcQuickVectorImage) <<
"Before SETUP" << node <<
"fill" << styleResolver->currentFillColor()
1465 <<
"stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1466 << node->nodeId() <<
" type: " << node->typeName() <<
" " << node->type();
1468 node->applyStyle(&styleResolver->painter(), styleResolver->states());
1470 qCDebug(lcQuickVectorImage) <<
"After SETUP" << node <<
"fill" << styleResolver->currentFillColor()
1471 <<
"stroke" << styleResolver->currentStrokeColor()
1472 << styleResolver->currentStrokeWidth() << node->nodeId();
1478 fillCommonNodeInfo(node, info);
1480 m_generator->generateNodeBase(info);
1485 node->revertStyle(&styleResolver->painter(), styleResolver->states());
1487 qCDebug(lcQuickVectorImage) <<
"After END" << node <<
"fill" << styleResolver->currentFillColor()
1488 <<
"stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1492void QSvgVisitorImpl::handlePathNode(
const QSvgNode *node,
const QPainterPath &path)
1494 handleBaseNodeSetup(node);
1497 fillCommonNodeInfo(node, info);
1498 auto fillStyle = node->style().fill;
1500 info.fillRule = fillStyle->fillRule();
1502 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
1504 info.painterPath = path;
1505 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
1506 if (strokeGradient ==
nullptr) {
1507 info.strokeStyle = StrokeStyle::fromPen(styleResolver->currentStroke());
1508 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
1510 if (styleResolver->currentFillGradient() !=
nullptr)
1511 info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
1512 info.fillTransform = styleResolver->currentFillTransform();
1514 fillPathAnimationInfo(node, info);
1516 m_generator->generatePath(info);
1518 if (strokeGradient !=
nullptr) {
1520 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke"));
1522 strokeInfo.grad = *strokeGradient;
1524 QPainterPathStroker stroker(styleResolver->currentStroke());
1525 strokeInfo.painterPath = stroker.createStroke(path);
1526 m_generator->generatePath(strokeInfo);
1529 handleBaseNodeEnd(node);
const QGradient * currentFillGradient() const
const QGradient * currentStrokeGradient() const
QColor currentFillColor() const
QSvgExtraStates & states()
static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
QTransform currentFillTransform() const
QPen currentStroke() const
qreal currentFillOpacity() const
float currentStrokeWidth() const
QSvgExtraStates m_svgState
QColor currentStrokeColor() const
void visitPolygonNode(const QSvgPolygon *node) override
bool visitSwitchNodeStart(const QSvgSwitch *node) override
void visitMaskNodeEnd(const QSvgMask *node) override
void visitEllipseNode(const QSvgEllipse *node) override
void visitPathNode(const QSvgPath *node) override
bool visitDefsNodeStart(const QSvgDefs *node) override
bool visitMaskNodeStart(const QSvgMask *node) override
void visitDocumentNodeEnd(const QSvgTinyDocument *node) override
void visitStructureNodeEnd(const QSvgStructureNode *node) override
void visitRectNode(const QSvgRect *node) override
void visitLineNode(const QSvgLine *node) override
void visitNode(const QSvgNode *node) override
void visitPolylineNode(const QSvgPolyline *node) override
QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator, bool assumeTrustedSource)
void visitUseNode(const QSvgUse *node) override
bool visitDocumentNodeStart(const QSvgTinyDocument *node) override
bool visitSymbolNodeStart(const QSvgSymbol *node) override
void visitImageNode(const QSvgImage *node) override
void visitTextNode(const QSvgText *node) override
void visitSwitchNodeEnd(const QSvgSwitch *node) override
void visitSymbolNodeEnd(const QSvgSymbol *node) override
bool visitStructureNodeStart(const QSvgStructureNode *node) override
Combined button and popup list for selecting options.
static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)