8#include <private/qsgcurveprocessor_p.h>
9#include <private/qquickshape_p.h>
10#include <private/qquadpath_p.h>
11#include <private/qquickitem_p.h>
12#include <private/qquickimagebase_p_p.h>
13#include <private/qquicktext_p.h>
14#include <private/qquicktranslate_p.h>
15#include <private/qquickimage_p.h>
17#include <QtCore/qloggingcategory.h>
18#include <QtCore/qdir.h>
22QQuickQmlGenerator::QQuickQmlGenerator(
const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags,
const QString &outFileName)
23 : QQuickGenerator(fileName, flags)
24 , outputFileName(outFileName)
26 m_result.open(QIODevice::ReadWrite);
29QQuickQmlGenerator::~QQuickQmlGenerator()
33bool QQuickQmlGenerator::save()
36 if (!outputFileName.isEmpty()) {
37 QFileInfo fileInfo(outputFileName);
38 QDir dir(fileInfo.absolutePath());
39 if (!dir.exists() && !dir.mkpath(QStringLiteral(
"."))) {
40 qCWarning(lcQuickVectorImage) <<
"Failed to create path" << dir.absolutePath();
43 QFile outFile(outputFileName);
44 if (outFile.open(QIODevice::WriteOnly)) {
45 outFile.write(m_result.data());
48 qCWarning(lcQuickVectorImage) <<
"Failed to write to file" << outFile.fileName();
54 if (lcQuickVectorImage().isDebugEnabled())
55 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
60void QQuickQmlGenerator::setShapeTypeName(
const QString &name)
62 m_shapeTypeName = name.toLatin1();
65QString QQuickQmlGenerator::shapeTypeName()
const
67 return QString::fromLatin1(m_shapeTypeName);
70void QQuickQmlGenerator::setCommentString(
const QString commentString)
72 m_commentString = commentString;
75QString QQuickQmlGenerator::commentString()
const
77 return m_commentString;
80QString QQuickQmlGenerator::generateNodeBase(
const NodeInfo &info)
82 if (!info.nodeId.isEmpty())
83 stream() <<
"objectName: \"" << info.nodeId <<
"\"";
85 QString idString = info.id;
86 if (!idString.isEmpty()) {
89 if (m_generatedIds.contains(idString))
90 idString += QStringLiteral(
"_%1").arg(m_generatedIds.size());
92 m_generatedIds.insert(idString);
94 stream() <<
"id: " << idString;
97 if (!info.bounds.isNull()) {
98 stream() <<
"property var originalBounds: Qt.rect("
99 << info.bounds.x() <<
", "
100 << info.bounds.y() <<
", "
101 << info.bounds.width() <<
", "
102 << info.bounds.height() <<
")";
103 stream() <<
"implicitWidth: originalBounds.width";
104 stream() <<
"implicitHeight: originalBounds.height";
107 if (!info.isDefaultOpacity)
108 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
110 if (!info.maskId.isEmpty()) {
111 stream() <<
"layer.enabled: true";
112 stream() <<
"visible: false";
113 stream() <<
"layer.textureSize: " << info.maskId <<
"_" << info.id <<
"_mask.textureSize";
114 stream() <<
"layer.sourceRect: " << info.maskId <<
".maskRect(" << info.id <<
")";
116 generateItemAnimations(idString, info);
122void QQuickQmlGenerator::generateNodeEnd(
const NodeInfo &info)
127 if (!info.maskId.isEmpty())
128 generateMaskUse(info);
131void QQuickQmlGenerator::generateItemAnimations(
const QString &idString,
const NodeInfo &info)
133 const bool hasTransform = info.transform.isAnimated()
134 || !info.maskId.isEmpty()
135 || !info.isDefaultTransform
136 || !info.transformReferenceId.isEmpty();
139 stream() <<
"transform: TransformGroup {";
142 if (!idString.isEmpty()) {
143 stream() <<
"id: " << idString <<
"_transform_base_group";
145 if (!info.maskId.isEmpty())
146 stream() <<
"Translate { x: " << idString <<
".sourceX; y: " << idString <<
".sourceY }";
148 if (info.transform.isAnimated()) {
149 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
150 stream() <<
"TransformGroup {";
153 if (!idString.isEmpty())
154 stream() <<
"id: " << idString <<
"_transform_group_" << groupIndex;
156 int animationStart = info.transform.animationGroup(groupIndex);
157 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
158 ? info.transform.animationGroup(groupIndex + 1)
159 : info.transform.animationCount();
161 for (
int i = nextAnimationStart - 1; i >= animationStart; --i) {
162 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
163 if (animation.frames.isEmpty())
166 const QVariantList ¶meters = animation.frames.first().value<QVariantList>();
167 switch (animation.subtype) {
168 case QTransform::TxTranslate:
169 if (animation.isConstant()) {
170 const QPointF translation = parameters.value(0).value<QPointF>();
171 if (!translation.isNull())
172 stream() <<
"Translate { x: " << translation.x() <<
"; y: " << translation.y() <<
" }";
174 stream() <<
"Translate { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
177 case QTransform::TxScale:
178 if (animation.isConstant()) {
179 const QPointF scale = parameters.value(0).value<QPointF>();
180 if (scale != QPointF(1, 1))
181 stream() <<
"Scale { xScale: " << scale.x() <<
"; yScale: " << scale.y() <<
" }";
183 stream() <<
"Scale { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
"}";
186 case QTransform::TxRotate:
187 if (animation.isConstant()) {
188 const QPointF center = parameters.value(0).value<QPointF>();
189 const qreal angle = parameters.value(1).toReal();
190 if (!qFuzzyIsNull(angle))
191 stream() <<
"Rotation { angle: " << angle <<
"; origin.x: " << center.x() <<
"; origin.y: " << center.y() <<
" }";
193 stream() <<
"Rotation { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
196 case QTransform::TxShear:
197 if (animation.isConstant()) {
198 const QPointF skew = parameters.value(0).value<QPointF>();
200 stream() <<
"Shear { xAngle: " << skew.x() <<
"; yAngle: " << skew.y() <<
" }";
202 stream() <<
"Shear { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
216 if (!info.isDefaultTransform) {
217 QTransform xf = info.transform.defaultValue().value<QTransform>();
218 if (xf.type() <= QTransform::TxTranslate) {
219 stream() <<
"Translate { x: " << xf.dx() <<
"; y: " << xf.dy() <<
"}";
221 stream() <<
"Matrix4x4 { matrix: ";
222 generateTransform(xf);
223 stream(SameLine) <<
"}";
227 if (!info.transformReferenceId.isEmpty())
228 stream() <<
"Matrix4x4 { matrix: " << info.transformReferenceId <<
".transformMatrix }";
233 generateAnimateTransform(idString, info);
236 generatePropertyAnimation(info.opacity, idString, QStringLiteral(
"opacity"));
237 generatePropertyAnimation(info.visibility, idString, QStringLiteral(
"visible"));
240void QQuickQmlGenerator::generateMaskUse(
const NodeInfo &info)
242 Q_ASSERT(!info.maskId.isEmpty());
245 stream() <<
"ShaderEffectSource {";
248 const QString maskId = info.maskId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_mask");
249 stream() <<
"id: " << maskId;
250 stream() <<
"sourceItem: " << info.maskId;
251 stream() <<
"visible: false";
253 stream() <<
"ItemSpy {";
255 stream() <<
"id: " << maskId <<
"_itemspy";
256 stream() <<
"anchors.fill: parent";
259 stream() <<
"textureSize: " << maskId <<
"_itemspy.requiredTextureSize";
261 stream() <<
"sourceRect: " << info.maskId <<
".maskRect(" << info.id <<
")";
262 stream() <<
"width: sourceRect.width";
263 stream() <<
"height: sourceRect.height";
268 stream() <<
"ShaderEffect {";
271 const QString maskShaderId = maskId + QStringLiteral(
"_se");
272 stream() <<
"id:" << maskShaderId;
274 stream() <<
"property real sourceX: " << maskId <<
".sourceRect.x";
275 stream() <<
"property real sourceY: " << maskId <<
".sourceRect.y";
276 stream() <<
"width: " << maskId <<
".sourceRect.width";
277 stream() <<
"height: " << maskId <<
".sourceRect.height";
279 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/luminancemask.frag.qsb\"";
280 stream() <<
"property var source: " << info.id;
281 stream() <<
"property var maskSource: " << maskId;
283 generateItemAnimations(maskShaderId, info);
289bool QQuickQmlGenerator::generateDefsNode(
const NodeInfo &info)
296void QQuickQmlGenerator::generateImageNode(
const ImageNodeInfo &info)
298 if (!isNodeVisible(info))
301 const QFileInfo outputFileInfo(outputFileName);
302 const QDir outputDir(outputFileInfo.absolutePath());
306 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
307 filePath = m_assetFileDirectory;
308 if (filePath.isEmpty())
309 filePath = outputDir.absolutePath();
311 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
314 QDir fileDir(filePath);
315 if (!fileDir.exists()) {
316 if (!fileDir.mkpath(QStringLiteral(
".")))
317 qCWarning(lcQuickVectorImage) <<
"Failed to create image resource directory:" << filePath;
320 filePath += QStringLiteral(
"%1%2.png").arg(m_assetFilePrefix.isEmpty()
321 ? QStringLiteral(
"svg_asset_")
323 .arg(info.image.cacheKey());
325 if (!info.image.save(filePath))
326 qCWarning(lcQuickVectorImage) <<
"Unabled to save image resource" << filePath;
327 qCDebug(lcQuickVectorImage) <<
"Saving copy of IMAGE" << filePath;
329 filePath = info.externalFileReference;
332 const QFileInfo assetFileInfo(filePath);
334 stream() <<
"Image {";
337 generateNodeBase(info);
338 stream() <<
"x: " << info.rect.x();
339 stream() <<
"y: " << info.rect.y();
340 stream() <<
"width: " << info.rect.width();
341 stream() <<
"height: " << info.rect.height();
342 stream() <<
"source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<
"\"";
343 generateNodeEnd(info);
346void QQuickQmlGenerator::generatePath(
const PathNodeInfo &info,
const QRectF &overrideBoundingRect)
348 if (!isNodeVisible(info))
351 if (m_inShapeItemLevel > 0) {
352 if (!info.isDefaultTransform)
353 qWarning() <<
"Skipped transform for node" << info.nodeId <<
"type" << info.typeName <<
"(this is not supposed to happen)";
354 optimizePaths(info, overrideBoundingRect);
356 m_inShapeItemLevel++;
357 stream() << shapeName() <<
" {";
360 generateNodeBase(info);
362 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
363 stream() <<
"preferredRendererType: Shape.CurveRenderer";
364 optimizePaths(info, overrideBoundingRect);
366 generateNodeEnd(info);
367 m_inShapeItemLevel--;
371void QQuickQmlGenerator::generateGradient(
const QGradient *grad)
373 if (grad->type() == QGradient::LinearGradient) {
374 auto *linGrad =
static_cast<
const QLinearGradient *>(grad);
375 stream() <<
"fillGradient: LinearGradient {";
378 QRectF gradRect(linGrad->start(), linGrad->finalStop());
380 stream() <<
"x1: " << gradRect.left();
381 stream() <<
"y1: " << gradRect.top();
382 stream() <<
"x2: " << gradRect.right();
383 stream() <<
"y2: " << gradRect.bottom();
384 for (
auto &stop : linGrad->stops())
385 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
386 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
389 }
else if (grad->type() == QGradient::RadialGradient) {
390 auto *radGrad =
static_cast<
const QRadialGradient*>(grad);
391 stream() <<
"fillGradient: RadialGradient {";
394 stream() <<
"centerX: " << radGrad->center().x();
395 stream() <<
"centerY: " << radGrad->center().y();
396 stream() <<
"centerRadius: " << radGrad->radius();
397 stream() <<
"focalX:" << radGrad->focalPoint().x();
398 stream() <<
"focalY:" << radGrad->focalPoint().y();
399 for (
auto &stop : radGrad->stops())
400 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
401 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
407void QQuickQmlGenerator::generateAnimationBindings()
410 if (Q_UNLIKELY(!isRuntimeGenerator()))
411 prefix = QStringLiteral(
".animations");
413 stream() <<
"loops: " << m_topLevelIdString << prefix <<
".loops";
414 stream() <<
"paused: " << m_topLevelIdString << prefix <<
".paused";
415 stream() <<
"running: true";
418 stream() <<
"onLoopsChanged: { if (running) { restart() } }";
421void QQuickQmlGenerator::generateEasing(
const QQuickAnimatedProperty::PropertyAnimation &animation,
int time)
423 if (animation.easingPerFrame.contains(time)) {
424 QBezier bezier = animation.easingPerFrame.value(time);
425 QPointF c1 = bezier.pt2();
426 QPointF c2 = bezier.pt3();
428 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
430 int nextIdx = m_easings.size();
431 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
433 id = QString(QLatin1String(
"easing_%1")).arg(nextIdx, 2, 10, QLatin1Char(
'0'));
434 stream() <<
"easing: " << m_topLevelIdString <<
"." << id;
439void QQuickQmlGenerator::generatePropertyAnimation(
const QQuickAnimatedProperty &property,
440 const QString &targetName,
441 const QString &propertyName,
442 AnimationType animationType)
444 if (!property.isAnimated())
447 QString mainAnimationId = targetName
448 + QStringLiteral(
"_")
450 + QStringLiteral(
"_animation");
451 mainAnimationId.replace(QLatin1Char(
'.'), QLatin1Char(
'_'));
454 if (Q_UNLIKELY(!isRuntimeGenerator()))
455 prefix = QStringLiteral(
".animations");
457 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
459 stream() <<
"ParallelAnimation {";
462 stream() <<
"id: " << mainAnimationId;
464 generateAnimationBindings();
466 for (
int i = 0; i < property.animationCount(); ++i) {
467 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
469 stream() <<
"SequentialAnimation {";
472 const int startOffset = animation.startOffset;
474 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
476 stream() <<
"SequentialAnimation {";
479 const int repeatCount = animation.repeatCount;
481 stream() <<
"loops: Animation.Infinite";
483 stream() <<
"loops: " << repeatCount;
485 int previousTime = 0;
486 QVariant previousValue;
487 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
488 const int time = it.key();
489 const int frameTime = time - previousTime;
490 const QVariant &value = it.value();
492 if (previousValue.isValid() && previousValue == value) {
494 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
495 }
else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
498 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
499 stream() <<
"ScriptAction {";
502 stream() <<
"script:" << targetName <<
"." << propertyName <<
" = " << value.toString();
507 generateAnimatedPropertySetter(targetName,
517 previousValue = value;
520 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
521 stream() <<
"ScriptAction {";
523 stream() <<
"script: ";
525 switch (animationType) {
526 case AnimationType::Auto:
527 stream(SameLine) << targetName <<
"." << propertyName <<
" = ";
529 case AnimationType::ColorOpacity:
530 stream(SameLine) << targetName <<
"." << propertyName <<
".a = ";
534 QVariant value = property.defaultValue();
535 if (value.typeId() == QMetaType::QColor)
536 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
538 stream(SameLine) << value.toReal();
555void QQuickQmlGenerator::generateTransform(
const QTransform &xf)
558 stream(SameLine) <<
"PlanarTransform.fromAffineMatrix("
559 << xf.m11() <<
", " << xf.m12() <<
", "
560 << xf.m21() <<
", " << xf.m22() <<
", "
561 << xf.dx() <<
", " << xf.dy() <<
")";
564 stream(SameLine) <<
"Qt.matrix4x4(";
566 const auto *data = m.data();
567 for (
int i = 0; i < 4; i++) {
568 stream() << data[i] <<
", " << data[i+4] <<
", " << data[i+8] <<
", " << data[i+12];
570 stream(SameLine) <<
", ";
572 stream(SameLine) <<
")";
577void QQuickQmlGenerator::outputShapePath(
const PathNodeInfo &info,
const QPainterPath *painterPath,
const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector,
const QRectF &boundingRect)
579 Q_UNUSED(pathSelector)
580 Q_ASSERT(painterPath || quadPath);
582 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
583 const bool noPen = strokeColor == QColorConstants::Transparent
584 && !info.strokeStyle.color.isAnimated()
585 && !info.strokeStyle.opacity.isAnimated();
586 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
589 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
590 const bool noFill = info.grad.type() == QGradient::NoGradient
591 && fillColor == QColorConstants::Transparent
592 && !info.fillColor.isAnimated()
593 && !info.fillOpacity.isAnimated();
594 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
600 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
601 stream() <<
"ShapePath {";
604 QString shapePathId = info.id;
605 if (pathSelector & QQuickVectorImageGenerator::FillPath)
606 shapePathId += QStringLiteral(
"_fill");
607 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
608 shapePathId += QStringLiteral(
"_stroke");
610 stream() <<
"id: " << shapePathId;
612 if (!info.nodeId.isEmpty()) {
613 switch (pathSelector) {
614 case QQuickVectorImageGenerator::FillPath:
615 stream() <<
"objectName: \"svg_fill_path:" << info.nodeId <<
"\"";
617 case QQuickVectorImageGenerator::StrokePath:
618 stream() <<
"objectName: \"svg_stroke_path:" << info.nodeId <<
"\"";
620 case QQuickVectorImageGenerator::FillAndStroke:
621 stream() <<
"objectName: \"svg_path:" << info.nodeId <<
"\"";
626 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
627 stream() <<
"strokeColor: \"transparent\"";
629 stream() <<
"strokeColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
630 stream() <<
"strokeWidth: " << info.strokeStyle.width;
631 stream() <<
"capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
632 stream() <<
"joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
633 stream() <<
"miterLimit: " << info.strokeStyle.miterLimit;
634 if (info.strokeStyle.dashArray.length() != 0) {
635 stream() <<
"strokeStyle: " <<
"ShapePath.DashLine";
636 stream() <<
"dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
637 stream() <<
"dashOffset: " << info.strokeStyle.dashOffset;
641 QTransform fillTransform = info.fillTransform;
642 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
643 stream() <<
"fillColor: \"transparent\"";
644 }
else if (info.grad.type() != QGradient::NoGradient) {
645 generateGradient(&info.grad);
646 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
647 QTransform objectToUserSpace;
648 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
649 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
650 fillTransform *= objectToUserSpace;
653 stream() <<
"fillColor: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
656 if (!fillTransform.isIdentity()) {
657 const QTransform &xf = fillTransform;
658 stream() <<
"fillTransform: ";
659 if (info.fillTransform.type() == QTransform::TxTranslate)
660 stream(SameLine) <<
"PlanarTransform.fromTranslate(" << xf.dx() <<
", " << xf.dy() <<
")";
661 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
662 stream(SameLine) <<
"PlanarTransform.fromScale(" << xf.m11() <<
", " << xf.m22() <<
")";
664 generateTransform(xf);
667 if (info.trim.enabled) {
668 stream() <<
"trim.start: " << info.trim.start.defaultValue().toReal();
669 stream() <<
"trim.end: " << info.trim.end.defaultValue().toReal();
670 stream() <<
"trim.offset: " << info.trim.offset.defaultValue().toReal();
674 if (fillRule == QQuickShapePath::WindingFill)
675 stream() <<
"fillRule: ShapePath.WindingFill";
677 stream() <<
"fillRule: ShapePath.OddEvenFill";
681 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
682 if (!hintStr.isEmpty())
685 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
686 stream() <<
"PathSvg { path: \"" << svgPathString <<
"\" }";
691 if (info.trim.enabled) {
692 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"start"));
693 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"end"));
694 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"offset"));
697 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeColor"));
698 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral(
"strokeColor"), AnimationType::ColorOpacity);
699 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillColor"));
700 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral(
"fillColor"), AnimationType::ColorOpacity);
703void QQuickQmlGenerator::generateNode(
const NodeInfo &info)
705 if (!isNodeVisible(info))
708 stream() <<
"// Missing Implementation for SVG Node: " << info.typeName;
709 stream() <<
"// Adding an empty Item and skipping";
710 stream() <<
"Item {";
712 generateNodeBase(info);
713 generateNodeEnd(info);
716void QQuickQmlGenerator::generateTextNode(
const TextNodeInfo &info)
718 if (!isNodeVisible(info))
721 static int counter = 0;
722 stream() <<
"Item {";
724 generateNodeBase(info);
726 if (!info.isTextArea)
727 stream() <<
"Item { id: textAlignItem_" << counter <<
"; x: " << info.position.x() <<
"; y: " << info.position.y() <<
"}";
729 stream() <<
"Text {";
733 const QString textItemId = QStringLiteral(
"_qt_textItem_%1").arg(counter);
734 stream() <<
"id: " << textItemId;
736 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral(
"color"));
737 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral(
"color"), AnimationType::ColorOpacity);
738 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral(
"styleColor"));
739 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral(
"styleColor"), AnimationType::ColorOpacity);
741 if (info.isTextArea) {
742 stream() <<
"x: " << info.position.x();
743 stream() <<
"y: " << info.position.y();
744 if (info.size.width() > 0)
745 stream() <<
"width: " << info.size.width();
746 if (info.size.height() > 0)
747 stream() <<
"height: " << info.size.height();
748 stream() <<
"wrapMode: Text.Wrap";
749 stream() <<
"clip: true";
751 QString hAlign = QStringLiteral(
"left");
752 stream() <<
"anchors.baseline: textAlignItem_" << counter <<
".top";
753 switch (info.alignment) {
754 case Qt::AlignHCenter:
755 hAlign = QStringLiteral(
"horizontalCenter");
758 hAlign = QStringLiteral(
"right");
761 qCDebug(lcQuickVectorImage) <<
"Unexpected text alignment" << info.alignment;
766 stream() <<
"anchors." << hAlign <<
": textAlignItem_" << counter <<
".left";
770 stream() <<
"color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) <<
"\"";
771 stream() <<
"textFormat:" << (info.needsRichText ?
"Text.RichText" :
"Text.StyledText");
773 QString s = info.text;
774 s.replace(QLatin1Char(
'"'), QLatin1String(
"\\\""));
775 stream() <<
"text: \"" << s <<
"\"";
776 stream() <<
"font.family: \"" << info.font.family() <<
"\"";
777 if (info.font.pixelSize() > 0)
778 stream() <<
"font.pixelSize:" << info.font.pixelSize();
779 else if (info.font.pointSize() > 0)
780 stream() <<
"font.pixelSize:" << info.font.pointSizeF();
781 if (info.font.underline())
782 stream() <<
"font.underline: true";
783 if (info.font.weight() != QFont::Normal)
784 stream() <<
"font.weight: " <<
int(info.font.weight());
785 if (info.font.italic())
786 stream() <<
"font.italic: true";
787 switch (info.font.hintingPreference()) {
788 case QFont::PreferFullHinting:
789 stream() <<
"font.hintingPreference: Font.PreferFullHinting";
791 case QFont::PreferVerticalHinting:
792 stream() <<
"font.hintingPreference: Font.PreferVerticalHinting";
794 case QFont::PreferNoHinting:
795 stream() <<
"font.hintingPreference: Font.PreferNoHinting";
797 case QFont::PreferDefaultHinting:
798 stream() <<
"font.hintingPreference: Font.PreferDefaultHinting";
802 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
803 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
804 stream() <<
"styleColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
805 stream() <<
"style: Text.Outline";
811 generateNodeEnd(info);
814void QQuickQmlGenerator::generateUseNode(
const UseNodeInfo &info)
816 if (!isNodeVisible(info))
819 if (info.stage == StructureNodeStage::Start) {
820 stream() <<
"Item {";
822 generateNodeBase(info);
823 stream() <<
"x: " << info.startPos.x();
824 stream() <<
"y: " << info.startPos.y();
826 generateNodeEnd(info);
830void QQuickQmlGenerator::generatePathContainer(
const StructureNodeInfo &info)
833 stream() << shapeName() <<
" {";
835 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
836 stream() <<
"preferredRendererType: Shape.CurveRenderer";
839 m_inShapeItemLevel++;
842void QQuickQmlGenerator::generateAnimatedPropertySetter(
const QString &targetName,
843 const QString &propertyName,
844 const QVariant &value,
845 const QQuickAnimatedProperty::PropertyAnimation &animation,
848 AnimationType animationType)
851 switch (animationType) {
852 case AnimationType::Auto:
853 if (value.typeId() == QMetaType::QColor)
854 stream() <<
"ColorAnimation {";
856 stream() <<
"PropertyAnimation {";
858 case AnimationType::ColorOpacity:
859 stream() <<
"ColorOpacityAnimation {";
864 stream() <<
"duration: " << frameTime;
865 stream() <<
"target: " << targetName;
866 stream() <<
"property: \"" << propertyName <<
"\"";
868 if (value.typeId() == QMetaType::QVector3D) {
869 const QVector3D &v = value.value<QVector3D>();
870 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
871 }
else if (value.typeId() == QMetaType::QColor) {
872 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
874 stream(SameLine) << value.toReal();
876 generateEasing(animation, time);
880 stream() <<
"ScriptAction {";
882 stream() <<
"script:" << targetName <<
"." << propertyName;
883 if (animationType == AnimationType::ColorOpacity)
884 stream(SameLine) <<
".a";
886 stream(SameLine) <<
" = ";
887 if (value.typeId() == QMetaType::QVector3D) {
888 const QVector3D &v = value.value<QVector3D>();
889 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
890 }
else if (value.typeId() == QMetaType::QColor) {
891 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
893 stream(SameLine) << value.toReal();
900void QQuickQmlGenerator::generateAnimateTransform(
const QString &targetName,
const NodeInfo &info)
902 if (!info.transform.isAnimated())
905 const QString mainAnimationId = targetName
906 + QStringLiteral(
"_transform_animation");
909 if (Q_UNLIKELY(!isRuntimeGenerator()))
910 prefix = QStringLiteral(
".animations");
911 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
913 stream() <<
"ParallelAnimation {";
916 stream() <<
"id:" << mainAnimationId;
918 generateAnimationBindings();
919 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
920 int animationStart = info.transform.animationGroup(groupIndex);
921 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
922 ? info.transform.animationGroup(groupIndex + 1)
923 : info.transform.animationCount();
926 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
927 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
928 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
930 stream() <<
"SequentialAnimation {";
933 const int startOffset = firstAnimation.startOffset;
935 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
937 const int repeatCount = firstAnimation.repeatCount;
939 stream() <<
"loops: Animation.Infinite";
941 stream() <<
"loops: " << repeatCount;
944 stream() <<
"ScriptAction {";
947 stream() <<
"script: " << targetName <<
"_transform_base_group"
948 <<
".activateOverride(" << targetName <<
"_transform_group_" << groupIndex <<
")";
954 stream() <<
"ParallelAnimation {";
957 for (
int i = animationStart; i < nextAnimationStart; ++i) {
958 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
959 if (animation.isConstant())
961 bool hasRotationCenter =
false;
962 if (animation.subtype == QTransform::TxRotate) {
963 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
964 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
965 if (!center.isNull()) {
966 hasRotationCenter =
true;
972 stream() <<
"SequentialAnimation {";
975 int previousTime = 0;
976 QVariantList previousParameters;
977 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
978 const int time = it.key();
979 const int frameTime = time - previousTime;
980 const QVariantList ¶meters = it.value().value<QVariantList>();
981 if (parameters.isEmpty())
984 if (parameters == previousParameters) {
986 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
988 stream() <<
"ParallelAnimation {";
991 const QString propertyTargetName = targetName
992 + QStringLiteral(
"_transform_")
993 + QString::number(groupIndex)
994 + QStringLiteral(
"_")
995 + QString::number(i);
997 switch (animation.subtype) {
998 case QTransform::TxTranslate:
1000 const QPointF translation = parameters.first().value<QPointF>();
1002 generateAnimatedPropertySetter(propertyTargetName,
1003 QStringLiteral(
"x"),
1008 generateAnimatedPropertySetter(propertyTargetName,
1009 QStringLiteral(
"y"),
1016 case QTransform::TxScale:
1018 const QPointF scale = parameters.first().value<QPointF>();
1019 generateAnimatedPropertySetter(propertyTargetName,
1020 QStringLiteral(
"xScale"),
1025 generateAnimatedPropertySetter(propertyTargetName,
1026 QStringLiteral(
"yScale"),
1033 case QTransform::TxRotate:
1035 Q_ASSERT(parameters.size() == 2);
1036 const qreal angle = parameters.value(1).toReal();
1037 if (hasRotationCenter) {
1038 const QPointF center = parameters.value(0).value<QPointF>();
1039 generateAnimatedPropertySetter(propertyTargetName,
1040 QStringLiteral(
"origin"),
1041 QVector3D(center.x(), center.y(), 0.0),
1046 generateAnimatedPropertySetter(propertyTargetName,
1047 QStringLiteral(
"angle"),
1054 case QTransform::TxShear:
1056 const QPointF skew = parameters.first().value<QPointF>();
1058 generateAnimatedPropertySetter(propertyTargetName,
1059 QStringLiteral(
"xAngle"),
1065 generateAnimatedPropertySetter(propertyTargetName,
1066 QStringLiteral(
"yAngle"),
1081 previousTime = time;
1082 previousParameters = parameters;
1094 if (firstAnimation.repeatCount >= 0) {
1095 stream() <<
"ScriptAction {";
1098 stream() <<
"script: {";
1102 stream() << targetName <<
"_transform_base_group.deactivate("
1103 << targetName <<
"_transform_group_" << groupIndex <<
")";
1104 }
else if (!replace) {
1105 stream() << targetName <<
"_transform_base_group.deactivateOverride("
1106 << targetName <<
"_transform_group_" << groupIndex <<
")";
1124bool QQuickQmlGenerator::generateStructureNode(
const StructureNodeInfo &info)
1126 if (!isNodeVisible(info))
1129 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1130 if (info.stage == StructureNodeStage::Start) {
1131 if (isPathContainer) {
1132 generatePathContainer(info);
1133 }
else if (!info.customItemType.isEmpty()) {
1134 stream() << info.customItemType <<
" {";
1136 stream() <<
"Item { // Structure node";
1139 if (!info.viewBox.isEmpty()) {
1141 stream() <<
"transform: [";
1143 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1145 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1146 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1153 generateNodeBase(info);
1155 generateNodeEnd(info);
1156 if (isPathContainer)
1157 m_inShapeItemLevel--;
1163bool QQuickQmlGenerator::generateMaskNode(
const MaskNodeInfo &info)
1166 if (info.stage == StructureNodeStage::Start) {
1167 stream() <<
"Item {";
1170 generateNodeBase(info);
1172 stream() <<
"visible: false";
1173 stream() <<
"width: originalBounds.width";
1174 stream() <<
"height: originalBounds.height";
1176 stream() <<
"property real maskX: " << info.maskRect.left();
1177 stream() <<
"property real maskY: " << info.maskRect.top();
1178 stream() <<
"property real maskWidth: " << info.maskRect.width();
1179 stream() <<
"property real maskHeight: " << info.maskRect.height();
1181 stream() <<
"function maskRect(other) {";
1184 stream() <<
"return ";
1185 if (info.isMaskRectRelativeCoordinates) {
1188 << info.id <<
".maskX * other.originalBounds.width + other.originalBounds.x,"
1189 << info.id <<
".maskY * other.originalBounds.height + other.originalBounds.y,"
1190 << info.id <<
".maskWidth * other.originalBounds.width,"
1191 << info.id <<
".maskHeight * other.originalBounds.height)";
1195 << info.id <<
".maskX, "
1196 << info.id <<
".maskY, "
1197 << info.id <<
".maskWidth, "
1198 << info.id <<
".maskHeight)";
1205 generateNodeEnd(info);
1211bool QQuickQmlGenerator::generateRootNode(
const StructureNodeInfo &info)
1213 const QStringList comments = m_commentString.split(u'\n');
1215 if (!isNodeVisible(info)) {
1218 if (comments.isEmpty()) {
1219 stream() <<
"// Generated from SVG";
1221 for (
const auto &comment : comments)
1222 stream() <<
"// " << comment;
1225 stream() <<
"import QtQuick";
1226 stream() <<
"import QtQuick.Shapes" << Qt::endl;
1227 stream() <<
"Item {";
1230 double w = info.size.width();
1231 double h = info.size.height();
1233 stream() <<
"implicitWidth: " << w;
1235 stream() <<
"implicitHeight: " << h;
1243 if (info.stage == StructureNodeStage::Start) {
1246 if (comments.isEmpty())
1247 stream() <<
"// Generated from SVG";
1249 for (
const auto &comment : comments)
1250 stream() <<
"// " << comment;
1252 stream() <<
"import QtQuick";
1253 stream() <<
"import QtQuick.VectorImage";
1254 stream() <<
"import QtQuick.VectorImage.Helpers";
1255 stream() <<
"import QtQuick.Shapes";
1256 for (
const auto &import : std::as_const(m_extraImports))
1257 stream() <<
"import " << import;
1259 stream() << Qt::endl <<
"Item {";
1262 double w = info.size.width();
1263 double h = info.size.height();
1265 stream() <<
"implicitWidth: " << w;
1267 stream() <<
"implicitHeight: " << h;
1269 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1270 stream() <<
"component AnimationsInfo : QtObject";
1275 stream() <<
"property bool paused: false";
1276 stream() <<
"property int loops: 1";
1277 stream() <<
"signal restart()";
1279 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1282 stream() <<
"property AnimationsInfo animations : AnimationsInfo {}";
1285 if (!info.viewBox.isEmpty()) {
1286 stream() <<
"transform: [";
1288 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1290 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1291 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1296 if (!info.forceSeparatePaths && info.isPathContainer) {
1297 m_topLevelIdString = QStringLiteral(
"__qt_toplevel");
1298 stream() <<
"id: " << m_topLevelIdString;
1300 generatePathContainer(info);
1303 generateNodeBase(info);
1305 m_topLevelIdString = generateNodeBase(info);
1306 if (m_topLevelIdString.isEmpty())
1307 qCWarning(lcQuickVectorImage) <<
"No ID specified for top level item";
1310 if (m_inShapeItemLevel > 0) {
1311 m_inShapeItemLevel--;
1316 for (
const auto [coords, id] : m_easings.asKeyValueRange()) {
1317 stream() <<
"property easingCurve " << id <<
": { type: Easing.BezierSpline; bezierCurve: [ ";
1318 for (
auto coord : coords)
1319 stream(SameLine) << coord <<
", ";
1320 stream(SameLine) <<
"1, 1 ] }";
1323 generateNodeEnd(info);
1330QStringView QQuickQmlGenerator::indent()
1332 static QString indentString;
1333 int indentWidth = m_indentLevel * 4;
1334 if (indentWidth > indentString.size())
1335 indentString.fill(QLatin1Char(
' '), indentWidth * 2);
1336 return QStringView(indentString).first(indentWidth);
1339QTextStream &QQuickQmlGenerator::stream(
int flags)
1341 if (m_stream.device() ==
nullptr)
1342 m_stream.setDevice(&m_result);
1343 else if (!(flags & StreamFlags::SameLine))
1344 m_stream << Qt::endl << indent();
1348const char *QQuickQmlGenerator::shapeName()
const
1350 return m_shapeTypeName.isEmpty() ?
"Shape" : m_shapeTypeName.constData();