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:
179 case QSvgNode::Circle:
180 case QSvgNode::Ellipse:
183 case QSvgNode::Polygon:
184 case QSvgNode::Polyline:
186 if (!child->style().transform.isDefault()) {
190 const QList<QSvgAbstractAnimation *> animations = child->document()->animator()->animationsForNode(child);
191 if (!animations.isEmpty()) {
199 qCDebug(lcQuickVectorImage) <<
"Unhandled type in switch" << child->type();
207static QString capStyleName(Qt::PenCapStyle style)
213 styleName = QStringLiteral(
"squarecap");
216 styleName = QStringLiteral(
"flatcap");
219 styleName = QStringLiteral(
"roundcap");
228static QString joinStyleName(Qt::PenJoinStyle style)
234 styleName = QStringLiteral(
"miterjoin");
237 styleName = QStringLiteral(
"beveljoin");
240 styleName = QStringLiteral(
"roundjoin");
242 case Qt::SvgMiterJoin:
243 styleName = QStringLiteral(
"svgmiterjoin");
252static QString dashArrayString(QList<qreal> dashArray)
254 if (dashArray.isEmpty())
257 QString dashArrayString;
258 QTextStream stream(&dashArrayString);
260 for (
int i = 0; i < dashArray.length() - 1; i++) {
261 qreal value = dashArray[i];
262 stream << value <<
", ";
265 stream << dashArray.last();
267 return dashArrayString;
272 QQuickGenerator *generator,
273 bool assumeTrustedSource)
276 , m_assumeTrustedSource(assumeTrustedSource)
283 qCDebug(lcQuickVectorImage) <<
"No valid QQuickGenerator is set. Genration will stop";
287 QtSvg::Options options;
288 if (m_assumeTrustedSource)
289 options.setFlag(QtSvg::AssumeTrustedSource);
291 auto *doc = QSvgTinyDocument::load(m_svgFileName, options);
293 qCDebug(lcQuickVectorImage) <<
"Not a valid Svg File : " << m_svgFileName;
297 QSvgVisitor::traverse(doc);
303 handleBaseNodeSetup(node);
306 fillCommonNodeInfo(node, info);
307 fillAnimationInfo(node, info);
309 m_generator->generateNode(info);
311 handleBaseNodeEnd(node);
317 handleBaseNodeSetup(node);
320 fillCommonNodeInfo(node, info);
321 fillAnimationInfo(node, info);
322 info.image = node->image();
323 info.rect = node->rect();
324 info.externalFileReference = node->filename();
326 m_generator->generateImageNode(info);
328 handleBaseNodeEnd(node);
333 QRectF rect = node->rect();
334 QPointF rads = node->radius();
336 qreal x1 = rect.left();
337 qreal x2 = rect.right();
338 qreal y1 = rect.top();
339 qreal y2 = rect.bottom();
341 qreal rx = rads.x() * rect.width() / 200;
342 qreal ry = rads.y() * rect.height() / 200;
345 p.moveTo(x1 + rx, y1);
346 p.lineTo(x2 - rx, y1);
348 p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90);
351 p.lineTo(x2, y2 - ry);
352 p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90);
354 p.lineTo(x1 + rx, y2);
355 p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90);
357 p.lineTo(x1, y1 + ry);
358 p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90);
360 handlePathNode(node, p);
365 QRectF rect = node->rect();
370 handlePathNode(node, p);
375 handlePathNode(node, node->path());
381 p.moveTo(node->line().p1());
382 p.lineTo(node->line().p2());
383 handlePathNode(node, p);
388 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
true);
389 handlePathNode(node, p);
394 QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(),
false);
395 handlePathNode(node, p);
398QString
QSvgVisitorImpl::gradientCssDescription(
const QGradient *gradient)
400 QString cssDescription;
401 if (gradient->type() == QGradient::LinearGradient) {
402 const QLinearGradient *linearGradient =
static_cast<
const QLinearGradient *>(gradient);
404 cssDescription +=
" -qt-foreground: qlineargradient("_L1;
405 cssDescription +=
"x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
406 cssDescription +=
"y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
407 cssDescription +=
"x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
408 cssDescription +=
"y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
409 }
else if (gradient->type() == QGradient::RadialGradient) {
410 const QRadialGradient *radialGradient =
static_cast<
const QRadialGradient *>(gradient);
412 cssDescription +=
" -qt-foreground: qradialgradient("_L1;
413 cssDescription +=
"cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
414 cssDescription +=
"cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
415 cssDescription +=
"fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
416 cssDescription +=
"fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
417 cssDescription +=
"radius:"_L1 + QString::number(radialGradient->radius()) + u',';
419 const QConicalGradient *conicalGradient =
static_cast<
const QConicalGradient *>(gradient);
421 cssDescription +=
" -qt-foreground: qconicalgradient("_L1;
422 cssDescription +=
"cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
423 cssDescription +=
"cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
424 cssDescription +=
"angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
427 const QStringList coordinateModes = {
"logical"_L1,
"stretchtodevice"_L1,
"objectbounding"_L1,
"object"_L1 };
428 cssDescription +=
"coordinatemode:"_L1;
429 cssDescription += coordinateModes.at(
int(gradient->coordinateMode()));
430 cssDescription += u',';
432 const QStringList spreads = {
"pad"_L1,
"reflect"_L1,
"repeat"_L1 };
433 cssDescription +=
"spread:"_L1;
434 cssDescription += spreads.at(
int(gradient->spread()));
436 for (
const QGradientStop &stop : gradient->stops()) {
437 cssDescription +=
",stop:"_L1;
438 cssDescription += QString::number(stop.first);
439 cssDescription += u' ';
440 cssDescription += stop.second.name(QColor::HexArgb);
443 cssDescription +=
");"_L1;
445 return cssDescription;
450 QString cssDescription;
451 cssDescription += QStringLiteral(
"rgba(");
452 cssDescription += QString::number(color.red()) + QStringLiteral(
",");
453 cssDescription += QString::number(color.green()) + QStringLiteral(
",");
454 cssDescription += QString::number(color.blue()) + QStringLiteral(
",");
455 cssDescription += QString::number(color.alphaF()) + QStringLiteral(
")");
457 return cssDescription;
466 class QSvgFontEngine :
public QFontEngine
469 QSvgFontEngine(
const QSvgFont *font, qreal size);
471 QFontEngine *cloneWithSize(qreal size)
const override;
473 glyph_t glyphIndex(uint ucs4)
const override;
474 int stringToCMap(
const QChar *str,
476 QGlyphLayout *glyphs,
478 ShaperFlags flags)
const override;
480 void addGlyphsToPath(glyph_t *glyphs,
481 QFixedPoint *positions,
484 QTextItem::RenderFlags flags)
override;
486 glyph_metrics_t boundingBox(glyph_t glyph)
override;
488 void recalcAdvances(QGlyphLayout *, ShaperFlags)
const override;
489 QFixed ascent()
const override;
490 QFixed capHeight()
const override;
491 QFixed descent()
const override;
492 QFixed leading()
const override;
493 qreal maxCharWidth()
const override;
494 qreal minLeftBearing()
const override;
495 qreal minRightBearing()
const override;
497 QFixed emSquareSize()
const override;
500 const QSvgFont *m_font;
503 QSvgFontEngine::QSvgFontEngine(
const QSvgFont *font, qreal size)
507 fontDef.pixelSize = size;
508 fontDef.families = QStringList(m_font->m_familyName);
511 QFixed QSvgFontEngine::emSquareSize()
const
513 return QFixed::fromReal(m_font->m_unitsPerEm);
516 glyph_t QSvgFontEngine::glyphIndex(uint ucs4)
const
518 if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(QChar(ushort(ucs4))))
519 return glyph_t(ucs4);
524 int QSvgFontEngine::stringToCMap(
const QChar *str,
526 QGlyphLayout *glyphs,
528 ShaperFlags flags)
const
530 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
531 if (*nglyphs < len) {
537 QStringIterator it(str, str + len);
538 while (it.hasNext()) {
539 char32_t ucs4 = it.next();
540 glyph_t index = glyphIndex(ucs4);
541 glyphs->glyphs[ucs4Length++] = index;
544 *nglyphs = ucs4Length;
545 glyphs->numGlyphs = ucs4Length;
547 if (!(flags & GlyphIndicesOnly))
548 recalcAdvances(glyphs, flags);
553 void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
554 QFixedPoint *positions,
557 QTextItem::RenderFlags flags)
560 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
561 for (
int i = 0; i < nGlyphs; ++i) {
562 glyph_t index = glyphs[i];
564 QPointF position = positions[i].toPointF();
565 QPainterPath glyphPath = m_font->m_glyphs.value(QChar(ushort(index))).m_path;
568 xform.translate(position.x(), position.y());
569 xform.scale(scale, -scale);
570 glyphPath = xform.map(glyphPath);
571 path->addPath(glyphPath);
576 glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
581 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
582 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
583 ret.width = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
584 ret.height = ascent() + descent();
588 QFontEngine *QSvgFontEngine::cloneWithSize(qreal size)
const
590 QSvgFontEngine *otherEngine =
new QSvgFontEngine(m_font, size);
594 void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags)
const
596 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
597 for (
int i = 0; i < glyphLayout->numGlyphs; i++) {
598 glyph_t glyph = glyphLayout->glyphs[i];
599 const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
600 glyphLayout->advances[i] = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
604 QFixed QSvgFontEngine::ascent()
const
606 return QFixed::fromReal(fontDef.pixelSize);
609 QFixed QSvgFontEngine::capHeight()
const
613 QFixed QSvgFontEngine::descent()
const
618 QFixed QSvgFontEngine::leading()
const
623 qreal QSvgFontEngine::maxCharWidth()
const
625 const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
626 return m_font->m_horizAdvX * scale;
629 qreal QSvgFontEngine::minLeftBearing()
const
634 qreal QSvgFontEngine::minRightBearing()
const
643 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(1, 0.0);
645 const_cast<QSvgAbstractAnimatedProperty *>(property)->interpolate(index, 1.0);
647 return property->interpolatedValue();
652 handleBaseNodeSetup(node);
653 const bool isTextArea = node->type() == QSvgNode::Textarea;
656 const QSvgFont *svgFont = styleResolver->states().svgFont;
657 bool needsRichText =
false;
658 bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
659 const QGradient *mainGradient = styleResolver->currentFillGradient();
661 QFontEngine *fontEngine =
nullptr;
662 if (svgFont !=
nullptr) {
663 fontEngine =
new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize());
664 fontEngine->ref.ref();
667#if QT_CONFIG(texthtmlparser)
668 bool needsPathNode = mainGradient !=
nullptr
669 || svgFont !=
nullptr
670 || styleResolver->currentStrokeGradient() !=
nullptr;
672 for (
const auto *tspan : node->tspans()) {
674 text += QStringLiteral(
"<br>");
680 handleBaseNodeSetup(tspan);
681 QFont font = styleResolver->painter().font();
683 QString styleTagContent;
685 if ((font.resolveMask() & QFont::FamilyResolved)
686 || (font.resolveMask() & QFont::FamiliesResolved)) {
687 styleTagContent += QStringLiteral(
"font-family: %1;").arg(font.family());
690 if (font.resolveMask() & QFont::WeightResolved
691 && font.weight() != QFont::Normal
692 && font.weight() != QFont::Bold) {
693 styleTagContent += QStringLiteral(
"font-weight: %1;").arg(
int(font.weight()));
696 if (font.resolveMask() & QFont::SizeResolved) {
698 styleTagContent += QStringLiteral(
"font-size: %1px;").arg(
int(font.pointSizeF()));
701 if (font.resolveMask() & QFont::CapitalizationResolved
702 && font.capitalization() == QFont::SmallCaps) {
703 styleTagContent += QStringLiteral(
"font-variant: small-caps;");
706 if (styleResolver->currentFillGradient() !=
nullptr
707 && styleResolver->currentFillGradient() != mainGradient) {
708 const QGradient grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
709 styleTagContent += gradientCssDescription(&grad) + u';';
710#if QT_CONFIG(texthtmlparser)
711 needsPathNode =
true;
715 const QColor currentStrokeColor = styleResolver->currentStrokeColor();
716 if (currentStrokeColor.alpha() > 0) {
717 QString strokeColor = colorCssDescription(currentStrokeColor);
718 styleTagContent += QStringLiteral(
"-qt-stroke-color:%1;").arg(strokeColor);
719 styleTagContent += QStringLiteral(
"-qt-stroke-width:%1px;").arg(styleResolver->currentStrokeWidth());
720 styleTagContent += QStringLiteral(
"-qt-stroke-dasharray:%1;").arg(dashArrayString(styleResolver->currentStroke().dashPattern()));
721 styleTagContent += QStringLiteral(
"-qt-stroke-dashoffset:%1;").arg(styleResolver->currentStroke().dashOffset());
722 styleTagContent += QStringLiteral(
"-qt-stroke-lineCap:%1;").arg(capStyleName(styleResolver->currentStroke().capStyle()));
723 styleTagContent += QStringLiteral(
"-qt-stroke-lineJoin:%1;").arg(joinStyleName(styleResolver->currentStroke().joinStyle()));
724 if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
725 styleTagContent += QStringLiteral(
"-qt-stroke-miterlimit:%1;").arg(styleResolver->currentStroke().miterLimit());
726#if QT_CONFIG(texthtmlparser)
727 needsPathNode =
true;
731 if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
732 styleTagContent += QStringLiteral(
"white-space: pre-wrap;");
734 QString content = tspan->text().toHtmlEscaped();
735 content.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
736 content.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
738 bool fontTag =
false;
739 if (!tspan->style().fill.isDefault()) {
740 auto &b = tspan->style().fill->qbrush();
741 qCDebug(lcQuickVectorImage) <<
"tspan FILL:" << b;
742 if (b.style() != Qt::NoBrush)
744 if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
746 QString spanColor = b.color().name();
747 fontTag = !spanColor.isEmpty();
749 text += QStringLiteral(
"<font color=\"%1\">").arg(spanColor);
751 QString spanColor = colorCssDescription(b.color());
752 styleTagContent += QStringLiteral(
"color:%1").arg(spanColor);
757 needsRichText = needsRichText || !styleTagContent.isEmpty();
758 if (!styleTagContent.isEmpty())
759 text += QStringLiteral(
"<span style=\"%1\">").arg(styleTagContent);
761 if (font.resolveMask() & QFont::WeightResolved && font.bold())
762 text += QStringLiteral(
"<b>");
764 if (font.resolveMask() & QFont::StyleResolved && font.italic())
765 text += QStringLiteral(
"<i>");
767 if (font.resolveMask() & QFont::CapitalizationResolved) {
768 switch (font.capitalization()) {
769 case QFont::AllLowercase:
770 content = content.toLower();
772 case QFont::AllUppercase:
773 content = content.toUpper();
775 case QFont::Capitalize:
778 qCWarning(lcQuickVectorImage) <<
"Title case not implemented for tspan";
786 text += QStringLiteral(
"</font>");
788 if (font.resolveMask() & QFont::StyleResolved && font.italic())
789 text += QStringLiteral(
"</i>");
791 if (font.resolveMask() & QFont::WeightResolved && font.bold())
792 text += QStringLiteral(
"</b>");
794 if (!styleTagContent.isEmpty())
795 text += QStringLiteral(
"</span>");
797 handleBaseNodeEnd(tspan);
800 if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() !=
nullptr))
801 text = QStringLiteral(
"<span style=\"white-space: pre-wrap\">") + text + QStringLiteral(
"</span>");
803 QFont font = styleResolver->painter().font();
804 if (font.pixelSize() <= 0 && font.pointSize() > 0)
805 font.setPixelSize(font.pointSize());
807 font.setHintingPreference(QFont::PreferNoHinting);
809#if QT_CONFIG(texthtmlparser)
811 QTextDocument document;
812 document.setHtml(text);
813 if (isTextArea && node->size().width() > 0)
814 document.setTextWidth(node->size().width());
815 document.setDefaultFont(font);
816 document.pageCount();
818 QTextBlock block = document.firstBlock();
819 while (block.isValid()) {
820 QTextLayout *lout = block.layout();
822 if (lout !=
nullptr) {
823 QRectF boundingRect = lout->boundingRect();
829 QFont blockFont = block.charFormat().font();
830 if (svgFont !=
nullptr
831 && blockFont.family() == svgFont->m_familyName) {
833 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
834 rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
836 lout->setRawFont(rawFont);
839 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt,
int pathIndex) {
841 fillCommonNodeInfo(node, info, QStringLiteral(
"_path%1").arg(pathIndex));
842 fillPathAnimationInfo(node, info);
843 auto fillStyle = node->style().fill;
845 info.fillRule = fillStyle->fillRule();
847 if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
848 info.fillColor.setDefaultValue(fmt.foreground().color());
849 if (fmt.foreground().gradient() !=
nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
850 info.grad = *fmt.foreground().gradient();
852 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
855 info.painterPath = p;
857 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
859 if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
860 pen = fmt.textOutline();
861 if (strokeGradient ==
nullptr) {
862 info.strokeStyle = StrokeStyle::fromPen(pen);
863 info.strokeStyle.color.setDefaultValue(pen.color());
866 pen = styleResolver->currentStroke();
867 if (strokeGradient ==
nullptr) {
868 info.strokeStyle = StrokeStyle::fromPen(pen);
869 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
873 if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() !=
nullptr)
874 info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
876 info.fillTransform = styleResolver->currentFillTransform();
878 m_generator->generatePath(info, boundingRect);
880 if (strokeGradient !=
nullptr) {
881 PathNodeInfo strokeInfo;
882 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke%1").arg(pathIndex));
883 fillPathAnimationInfo(node, strokeInfo);
885 strokeInfo.grad = *strokeGradient;
887 QPainterPathStroker stroker(pen);
888 strokeInfo.painterPath = stroker.createStroke(p);
889 m_generator->generatePath(strokeInfo, boundingRect);
893 qreal baselineOffset = -QFontMetricsF(font).ascent();
894 if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
895 baselineOffset = -lout->lineAt(0).ascent();
897 const QPointF baselineTranslation(0.0, baselineOffset);
898 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
899 QList<QPainterPath> paths;
900 for (
const QGlyphRun &glyphRun : glyphRuns) {
901 QRawFont font = glyphRun.rawFont();
902 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
903 QList<QPointF> positions = glyphRun.positions();
905 for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
906 quint32 glyphIndex = glyphIndexes.at(j);
907 const QPointF &pos = positions.at(j);
909 QPainterPath p = font.pathForGlyph(glyphIndex);
910 p.translate(pos + node->position() + baselineTranslation);
911 if (styleResolver->states().textAnchor == Qt::AlignHCenter)
912 p.translate(QPointF(-0.5 * width, 0));
913 else if (styleResolver->states().textAnchor == Qt::AlignRight)
914 p.translate(QPointF(-width, 0));
922 QList<QTextLayout::FormatRange> formats = block.textFormats();
923 for (
int i = 0; i < formats.size(); ++i) {
924 QTextLayout::FormatRange range = formats.at(i);
926 QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
927 QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
928 for (
int j = 0; j < paths.size(); ++j) {
929 const QPainterPath &path = paths.at(j);
930 addPathForFormat(path, range.format, j);
935 block = block.next();
941 fillCommonNodeInfo(node, info);
942 fillAnimationInfo(node, info);
945 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
946 if (!animations.isEmpty())
947 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
951 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
952 if (!animations.isEmpty())
953 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
957 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
958 if (!animations.isEmpty())
959 applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue);
963 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
964 if (!animations.isEmpty())
965 applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue);
968 info.position = node->position();
969 info.size = node->size();
974 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
975 info.alignment = styleResolver->states().textAnchor;
976 info.strokeColor.setDefaultValue(styleResolver->currentStrokeColor());
978 m_generator->generateTextNode(info);
981 handleBaseNodeEnd(node);
983 if (fontEngine !=
nullptr) {
984 fontEngine->ref.deref();
985 Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
992 QSvgNode *link = node->link();
996 handleBaseNodeSetup(node);
998 fillCommonNodeInfo(node, info);
999 fillAnimationInfo(node, info);
1002 info.startPos = node->start();
1004 m_generator->generateUseNode(info);
1006 QString oldLinkSuffix = m_linkSuffix;
1007 m_linkSuffix += QStringLiteral(
"_use") + info.id;
1008 QSvgVisitor::traverse(link);
1009 m_linkSuffix = oldLinkSuffix;
1012 m_generator->generateUseNode(info);
1013 handleBaseNodeEnd(node);
1018 QSvgNode *link = node->childToRender();
1022 QString oldLinkSuffix = m_linkSuffix;
1023 m_linkSuffix += QStringLiteral(
"_switch") + QString::number(quintptr(node), 16);
1024 QSvgVisitor::traverse(link);
1025 m_linkSuffix = oldLinkSuffix;
1039 return m_generator->generateDefsNode(NodeInfo{});
1044 handleBaseNodeSetup(node);
1048 QSvgRectF r = node->rect();
1049 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1052 if (node->contentUnits() == QtSvg::UnitTypes::objectBoundingBox)
1053 qCWarning(lcQuickVectorImage) <<
"Only user space content units supported for masks";
1055 fillCommonNodeInfo(node, info);
1057 return m_generator->generateMaskNode(info);
1065 QSvgRectF r = node->rect();
1066 info.isMaskRectRelativeCoordinates = r.unitX() == QtSvg::UnitTypes::objectBoundingBox;
1068 fillCommonNodeInfo(node, info);
1070 m_generator->generateMaskNode(info);
1072 handleBaseNodeEnd(node);
1077 constexpr bool forceSeparatePaths =
false;
1078 handleBaseNodeSetup(node);
1082 fillCommonNodeInfo(node, info);
1083 fillAnimationInfo(node, info);
1085 info.isPathContainer = isPathContainer(node);
1088 return m_generator->generateStructureNode(info);
1093 handleBaseNodeEnd(node);
1098 fillCommonNodeInfo(node, info);
1099 info.isPathContainer = isPathContainer(node);
1102 m_generator->generateStructureNode(info);
1107 return QStringLiteral(
"_qt_node%1").arg(m_nodeIdCounter++);
1112 handleBaseNodeSetup(node);
1115 fillCommonNodeInfo(node, info);
1116 fillAnimationInfo(node, info);
1118 const QSvgTinyDocument *doc =
static_cast<
const QSvgTinyDocument *>(node);
1119 info.size = doc->size();
1120 info.viewBox = doc->viewBox();
1121 info.isPathContainer = isPathContainer(node);
1125 return m_generator->generateRootNode(info);
1130 handleBaseNodeEnd(node);
1131 qCDebug(lcQuickVectorImage) <<
"REVERT" << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen)
1132 << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush)
1133 << styleResolver->painter().pen().brush().color().name();
1136 fillCommonNodeInfo(node, info);
1139 m_generator->generateRootNode(info);
1144 const QString key = node->nodeId().isEmpty()
1145 ? QString::number(quintptr(node), 16)
1147 info.id = m_idForNodeId.value(key);
1148 if (info.id.isEmpty()) {
1149 info.id = nextNodeId();
1150 m_idForNodeId.insert(key, info.id);
1154 info.id += idSuffix;
1156 if (!m_linkSuffix.isEmpty())
1157 info.id += m_linkSuffix;
1159 if (node->hasMask() || node->type() == QSvgNode::Type::Mask)
1160 info.bounds = node->bounds();
1161 info.nodeId = node->nodeId();
1162 info.typeName = node->typeName();
1164 info.transform.setDefaultValue(QVariant::fromValue(!info.isDefaultTransform
1165 ? node->style().transform->qtransform()
1168 info.opacity.setDefaultValue(!info
.isDefaultOpacity ? node->style().opacity->opacity() : 1.0);
1170 info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
1172 if (node->hasMask()) {
1173 info.maskId = m_idForNodeId.value(node->maskId());
1174 if (info.maskId.isEmpty()) {
1175 info.maskId = nextNodeId();
1176 m_idForNodeId.insert(node->maskId(), info.maskId);
1181QList<QSvgVisitorImpl::AnimationPair>
QSvgVisitorImpl::collectAnimations(
const QSvgNode *node,
1182 const QString &propertyName)
1184 QList<AnimationPair> ret;
1185 const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
1186 for (
const QSvgAbstractAnimation *animation : animations) {
1187 const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
1188 for (
const QSvgAbstractAnimatedProperty *property : properties) {
1189 if (property->propertyName() == propertyName)
1190 ret.append(std::make_pair(animation, property));
1197void QSvgVisitorImpl::applyAnimationsToProperty(
const QList<AnimationPair> &animations,
1198 QQuickAnimatedProperty *outProperty,
1199 std::function<QVariant(
const QSvgAbstractAnimatedProperty *,
int index,
int animationIndex)> calculateValue)
1201 qCDebug(lcVectorImageAnimations) <<
"Applying animations to property with default value"
1202 << outProperty->defaultValue();
1203 for (
auto it = animations.constBegin(); it != animations.constEnd(); ++it) {
1204 qCDebug(lcVectorImageAnimations) <<
" -> Add animation";
1205 const QSvgAbstractAnimation *animation = it->first;
1206 const QSvgAbstractAnimatedProperty *property = it->second;
1208 const int start = animation->start();
1209 const int repeatCount = animation->iterationCount();
1210 const int duration = animation->duration();
1212 bool freeze =
false;
1213 bool replace =
true;
1214 if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
1215 const QSvgAnimateNode *animateNode =
static_cast<
const QSvgAnimateNode *>(animation);
1216 freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
1217 replace = animateNode->additiveType() == QSvgAnimateNode::Replace;
1220 qCDebug(lcVectorImageAnimations) <<
" -> Start:" << start
1221 <<
", repeatCount:" << repeatCount
1222 <<
", freeze:" << freeze
1223 <<
", replace:" << replace;
1225 QList<qreal> propertyKeyFrames = property->keyFrames();
1226 QList<QQuickAnimatedProperty::PropertyAnimation> outAnimations;
1230 if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
1231 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1232 const auto &components = transformProperty->components();
1233 Q_ASSERT(q20::cmp_greater_equal(components.size(),transformProperty->transformCount()));
1234 for (uint i = 0; i < transformProperty->transformCount(); ++i) {
1235 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1236 outAnimation.repeatCount = repeatCount;
1237 outAnimation.startOffset = start;
1239 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1241 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1242 switch (components.at(i).type) {
1243 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1244 outAnimation.subtype = QTransform::TxTranslate;
1246 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1247 outAnimation.subtype = QTransform::TxScale;
1249 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1250 outAnimation.subtype = QTransform::TxRotate;
1252 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1253 outAnimation.subtype = QTransform::TxShear;
1256 qCWarning(lcQuickVectorImage()) <<
"Unhandled transform type:" << components.at(i).type;
1260 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1263 << property->propertyName()
1264 <<
" animation subtype:"
1265 << outAnimation.subtype;
1267 outAnimations.append(outAnimation);
1270 QQuickAnimatedProperty::PropertyAnimation outAnimation;
1271 outAnimation.repeatCount = repeatCount;
1272 outAnimation.startOffset = start;
1274 outAnimation.flags |= QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1276 qDebug(lcVectorImageAnimations) <<
" -> Property type:"
1279 << property->propertyName();
1281 outAnimations.append(outAnimation);
1284 outProperty->beginAnimationGroup();
1285 for (
int i = 0; i < outAnimations.size(); ++i) {
1286 QQuickAnimatedProperty::PropertyAnimation outAnimation = outAnimations.at(i);
1288 for (
int j = 0; j < propertyKeyFrames.size(); ++j) {
1289 const int time = qRound(propertyKeyFrames.at(j) * duration);
1291 const QVariant value = calculateValue(property, j, i);
1292 outAnimation.frames[time] = value;
1293 qCDebug(lcVectorImageAnimations) <<
" -> Frame " << time <<
" is " << value;
1296 outProperty->addAnimation(outAnimation);
1305 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill"));
1306 if (!animations.isEmpty())
1307 applyAnimationsToProperty(animations, &info.fillColor, calculateInterpolatedValue);
1311 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"fill-opacity"));
1312 if (!animations.isEmpty())
1313 applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue);
1317 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke"));
1318 if (!animations.isEmpty())
1319 applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue);
1323 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"stroke-opacity"));
1324 if (!animations.isEmpty())
1325 applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue);
1331 qCDebug(lcVectorImageAnimations) <<
"Applying transform animations to property with default value"
1332 << info.transform.defaultValue();
1334 auto calculateValue = [](
const QSvgAbstractAnimatedProperty *property,
int index,
int animationIndex) {
1335 if (property->type() != QSvgAbstractAnimatedProperty::Transform)
1338 const auto *transformProperty =
static_cast<
const QSvgAnimatedPropertyTransform *>(property);
1339 const auto &components = transformProperty->components();
1341 const int componentIndex = index * transformProperty->transformCount() + animationIndex;
1343 QVariantList parameters;
1345 const QSvgAnimatedPropertyTransform::TransformComponent &component = components.at(componentIndex);
1346 switch (component.type) {
1347 case QSvgAnimatedPropertyTransform::TransformComponent::Translate:
1348 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1349 component.values.value(1))));
1351 case QSvgAnimatedPropertyTransform::TransformComponent::Rotate:
1352 parameters.append(QVariant::fromValue(QPointF(component.values.value(1),
1353 component.values.value(2))));
1354 parameters.append(QVariant::fromValue(component.values.value(0)));
1356 case QSvgAnimatedPropertyTransform::TransformComponent::Scale:
1357 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1358 component.values.value(1))));
1360 case QSvgAnimatedPropertyTransform::TransformComponent::Skew:
1361 parameters.append(QVariant::fromValue(QPointF(component.values.value(0),
1362 component.values.value(1))));
1365 qCWarning(lcVectorImageAnimations) <<
"Unhandled transform type:" << component.type;
1368 return QVariant::fromValue(parameters);
1373 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"transform"));
1374 if (!animations.isEmpty())
1375 applyAnimationsToProperty(animations, &info.transform, calculateValue);
1381 fillColorAnimationInfo(node, info);
1382 fillAnimationInfo(node, info);
1388 QList<AnimationPair> animations = collectAnimations(node, QStringLiteral(
"opacity"));
1389 if (!animations.isEmpty())
1390 applyAnimationsToProperty(animations, &info.opacity, calculateInterpolatedValue);
1393 fillTransformAnimationInfo(node, info);
1398 qCDebug(lcQuickVectorImage) <<
"Before SETUP" << node <<
"fill" << styleResolver->currentFillColor()
1399 <<
"stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1400 << node->nodeId() <<
" type: " << node->typeName() <<
" " << node->type();
1402 node->applyStyle(&styleResolver->painter(), styleResolver->states());
1404 qCDebug(lcQuickVectorImage) <<
"After SETUP" << node <<
"fill" << styleResolver->currentFillColor()
1405 <<
"stroke" << styleResolver->currentStrokeColor()
1406 << styleResolver->currentStrokeWidth() << node->nodeId();
1412 fillCommonNodeInfo(node, info);
1414 m_generator->generateNodeBase(info);
1419 node->revertStyle(&styleResolver->painter(), styleResolver->states());
1421 qCDebug(lcQuickVectorImage) <<
"After END" << node <<
"fill" << styleResolver->currentFillColor()
1422 <<
"stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
1426void QSvgVisitorImpl::handlePathNode(
const QSvgNode *node,
const QPainterPath &path)
1428 handleBaseNodeSetup(node);
1431 fillCommonNodeInfo(node, info);
1432 auto fillStyle = node->style().fill;
1434 info.fillRule = fillStyle->fillRule();
1436 const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
1438 info.painterPath = path;
1439 info.fillColor.setDefaultValue(styleResolver->currentFillColor());
1440 if (strokeGradient ==
nullptr) {
1441 info.strokeStyle = StrokeStyle::fromPen(styleResolver->currentStroke());
1442 info.strokeStyle.color.setDefaultValue(styleResolver->currentStrokeColor());
1444 if (styleResolver->currentFillGradient() !=
nullptr)
1445 info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
1446 info.fillTransform = styleResolver->currentFillTransform();
1448 fillPathAnimationInfo(node, info);
1450 m_generator->generatePath(info);
1452 if (strokeGradient !=
nullptr) {
1454 fillCommonNodeInfo(node, strokeInfo, QStringLiteral(
"_stroke"));
1456 strokeInfo.grad = *strokeGradient;
1458 QPainterPathStroker stroker(styleResolver->currentStroke());
1459 strokeInfo.painterPath = stroker.createStroke(path);
1460 m_generator->generatePath(strokeInfo);
1463 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
void visitImageNode(const QSvgImage *node) override
void visitTextNode(const QSvgText *node) override
void visitSwitchNodeEnd(const QSvgSwitch *node) override
bool visitStructureNodeStart(const QSvgStructureNode *node) override
static QVariant calculateInterpolatedValue(const QSvgAbstractAnimatedProperty *property, int index, int)