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 bool hasNonConstantTransform =
false;
143 int earliestOverrideGroup = -1;
145 if (!idString.isEmpty()) {
146 stream() <<
"id: " << idString <<
"_transform_base_group";
148 if (!info.maskId.isEmpty())
149 stream() <<
"Translate { x: " << idString <<
".sourceX; y: " << idString <<
".sourceY }";
151 if (info.transform.isAnimated()) {
152 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
153 stream() <<
"TransformGroup {";
156 if (!idString.isEmpty())
157 stream() <<
"id: " << idString <<
"_transform_group_" << groupIndex;
159 int animationStart = info.transform.animationGroup(groupIndex);
160 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
161 ? info.transform.animationGroup(groupIndex + 1)
162 : info.transform.animationCount();
164 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
165 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
166 if (replace && earliestOverrideGroup < 0)
167 earliestOverrideGroup = groupIndex;
169 for (
int i = nextAnimationStart - 1; i >= animationStart; --i) {
170 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
171 if (animation.frames.isEmpty())
174 const QVariantList ¶meters = animation.frames.first().value<QVariantList>();
175 switch (animation.subtype) {
176 case QTransform::TxTranslate:
177 if (animation.isConstant()) {
178 const QPointF translation = parameters.value(0).value<QPointF>();
179 if (!translation.isNull())
180 stream() <<
"Translate { x: " << translation.x() <<
"; y: " << translation.y() <<
" }";
182 hasNonConstantTransform =
true;
183 stream() <<
"Translate { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
186 case QTransform::TxScale:
187 if (animation.isConstant()) {
188 const QPointF scale = parameters.value(0).value<QPointF>();
189 if (scale != QPointF(1, 1))
190 stream() <<
"Scale { xScale: " << scale.x() <<
"; yScale: " << scale.y() <<
" }";
192 hasNonConstantTransform =
true;
193 stream() <<
"Scale { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
"}";
196 case QTransform::TxRotate:
197 if (animation.isConstant()) {
198 const QPointF center = parameters.value(0).value<QPointF>();
199 const qreal angle = parameters.value(1).toReal();
200 if (!qFuzzyIsNull(angle))
201 stream() <<
"Rotation { angle: " << angle <<
"; origin.x: " << center.x() <<
"; origin.y: " << center.y() <<
" }";
203 hasNonConstantTransform =
true;
204 stream() <<
"Rotation { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
207 case QTransform::TxShear:
208 if (animation.isConstant()) {
209 const QPointF skew = parameters.value(0).value<QPointF>();
211 stream() <<
"Shear { xAngle: " << skew.x() <<
"; yAngle: " << skew.y() <<
" }";
213 hasNonConstantTransform =
true;
214 stream() <<
"Shear { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
228 if (!info.isDefaultTransform) {
229 QTransform xf = info.transform.defaultValue().value<QTransform>();
230 if (xf.type() <= QTransform::TxTranslate) {
231 stream() <<
"Translate { x: " << xf.dx() <<
"; y: " << xf.dy() <<
"}";
233 stream() <<
"Matrix4x4 { matrix: ";
234 generateTransform(xf);
235 stream(SameLine) <<
"}";
239 if (!info.transformReferenceId.isEmpty())
240 stream() <<
"Matrix4x4 { matrix: " << info.transformReferenceId <<
".transformMatrix }";
245 if (hasNonConstantTransform) {
246 generateAnimateTransform(idString, info);
247 }
else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
250 stream() <<
"Component.onCompleted: {";
253 stream() << idString <<
"_transform_base_group.activateOverride("
254 << idString <<
"_transform_group_" << earliestOverrideGroup <<
")";
261 generatePropertyAnimation(info.opacity, idString, QStringLiteral(
"opacity"));
262 generatePropertyAnimation(info.visibility, idString, QStringLiteral(
"visible"));
265void QQuickQmlGenerator::generateMaskUse(
const NodeInfo &info)
267 Q_ASSERT(!info.maskId.isEmpty());
270 stream() <<
"ShaderEffectSource {";
273 const QString maskId = info.maskId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_mask");
274 stream() <<
"id: " << maskId;
275 stream() <<
"sourceItem: " << info.maskId;
276 stream() <<
"visible: false";
278 stream() <<
"ItemSpy {";
280 stream() <<
"id: " << maskId <<
"_itemspy";
281 stream() <<
"anchors.fill: parent";
284 stream() <<
"textureSize: " << maskId <<
"_itemspy.requiredTextureSize";
286 stream() <<
"sourceRect: " << info.maskId <<
".maskRect(" << info.id <<
")";
287 stream() <<
"width: sourceRect.width";
288 stream() <<
"height: sourceRect.height";
293 stream() <<
"ShaderEffect {";
296 const QString maskShaderId = maskId + QStringLiteral(
"_se");
297 stream() <<
"id:" << maskShaderId;
299 stream() <<
"property real sourceX: " << maskId <<
".sourceRect.x";
300 stream() <<
"property real sourceY: " << maskId <<
".sourceRect.y";
301 stream() <<
"width: " << maskId <<
".sourceRect.width";
302 stream() <<
"height: " << maskId <<
".sourceRect.height";
304 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
305 stream() <<
"property var source: " << info.id;
306 stream() <<
"property var maskSource: " << maskId;
307 stream() <<
"property bool isAlpha: " << (info.isMaskAlpha ?
"true" :
"false");
308 stream() <<
"property bool isInverted: " << (info.isMaskInverted ?
"true" :
"false");
310 generateItemAnimations(maskShaderId, info);
316bool QQuickQmlGenerator::generateDefsNode(
const NodeInfo &info)
323void QQuickQmlGenerator::generateImageNode(
const ImageNodeInfo &info)
325 if (!isNodeVisible(info))
328 const QFileInfo outputFileInfo(outputFileName);
329 const QDir outputDir(outputFileInfo.absolutePath());
333 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
334 filePath = m_assetFileDirectory;
335 if (filePath.isEmpty())
336 filePath = outputDir.absolutePath();
338 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
341 QDir fileDir(filePath);
342 if (!fileDir.exists()) {
343 if (!fileDir.mkpath(QStringLiteral(
".")))
344 qCWarning(lcQuickVectorImage) <<
"Failed to create image resource directory:" << filePath;
347 filePath += QStringLiteral(
"%1%2.png").arg(m_assetFilePrefix.isEmpty()
348 ? QStringLiteral(
"svg_asset_")
350 .arg(info.image.cacheKey());
352 if (!info.image.save(filePath))
353 qCWarning(lcQuickVectorImage) <<
"Unabled to save image resource" << filePath;
354 qCDebug(lcQuickVectorImage) <<
"Saving copy of IMAGE" << filePath;
356 filePath = info.externalFileReference;
359 const QFileInfo assetFileInfo(filePath);
361 stream() <<
"Image {";
364 generateNodeBase(info);
365 stream() <<
"x: " << info.rect.x();
366 stream() <<
"y: " << info.rect.y();
367 stream() <<
"width: " << info.rect.width();
368 stream() <<
"height: " << info.rect.height();
369 stream() <<
"source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<
"\"";
370 generateNodeEnd(info);
373void QQuickQmlGenerator::generatePath(
const PathNodeInfo &info,
const QRectF &overrideBoundingRect)
375 if (!isNodeVisible(info))
378 if (m_inShapeItemLevel > 0) {
379 if (!info.isDefaultTransform)
380 qWarning() <<
"Skipped transform for node" << info.nodeId <<
"type" << info.typeName <<
"(this is not supposed to happen)";
381 optimizePaths(info, overrideBoundingRect);
383 m_inShapeItemLevel++;
384 stream() << shapeName() <<
" {";
387 generateNodeBase(info);
389 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
390 stream() <<
"preferredRendererType: Shape.CurveRenderer";
391 optimizePaths(info, overrideBoundingRect);
393 generateNodeEnd(info);
394 m_inShapeItemLevel--;
398void QQuickQmlGenerator::generateGradient(
const QGradient *grad)
400 if (grad->type() == QGradient::LinearGradient) {
401 auto *linGrad =
static_cast<
const QLinearGradient *>(grad);
402 stream() <<
"fillGradient: LinearGradient {";
405 QRectF gradRect(linGrad->start(), linGrad->finalStop());
407 stream() <<
"x1: " << gradRect.left();
408 stream() <<
"y1: " << gradRect.top();
409 stream() <<
"x2: " << gradRect.right();
410 stream() <<
"y2: " << gradRect.bottom();
411 for (
auto &stop : linGrad->stops())
412 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
413 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
416 }
else if (grad->type() == QGradient::RadialGradient) {
417 auto *radGrad =
static_cast<
const QRadialGradient*>(grad);
418 stream() <<
"fillGradient: RadialGradient {";
421 stream() <<
"centerX: " << radGrad->center().x();
422 stream() <<
"centerY: " << radGrad->center().y();
423 stream() <<
"centerRadius: " << radGrad->radius();
424 stream() <<
"focalX:" << radGrad->focalPoint().x();
425 stream() <<
"focalY:" << radGrad->focalPoint().y();
426 for (
auto &stop : radGrad->stops())
427 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
428 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
434void QQuickQmlGenerator::generateAnimationBindings()
437 if (Q_UNLIKELY(!isRuntimeGenerator()))
438 prefix = QStringLiteral(
".animations");
440 stream() <<
"loops: " << m_topLevelIdString << prefix <<
".loops";
441 stream() <<
"paused: " << m_topLevelIdString << prefix <<
".paused";
442 stream() <<
"running: true";
445 stream() <<
"onLoopsChanged: { if (running) { restart() } }";
448void QQuickQmlGenerator::generateEasing(
const QQuickAnimatedProperty::PropertyAnimation &animation,
int time)
450 if (animation.easingPerFrame.contains(time)) {
451 QBezier bezier = animation.easingPerFrame.value(time);
452 QPointF c1 = bezier.pt2();
453 QPointF c2 = bezier.pt3();
455 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
457 int nextIdx = m_easings.size();
458 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
460 id = QString(QLatin1String(
"easing_%1")).arg(nextIdx, 2, 10, QLatin1Char(
'0'));
461 stream() <<
"easing: " << m_topLevelIdString <<
"." << id;
466void QQuickQmlGenerator::generatePropertyAnimation(
const QQuickAnimatedProperty &property,
467 const QString &targetName,
468 const QString &propertyName,
469 AnimationType animationType)
471 if (!property.isAnimated())
474 QString mainAnimationId = targetName
475 + QStringLiteral(
"_")
477 + QStringLiteral(
"_animation");
478 mainAnimationId.replace(QLatin1Char(
'.'), QLatin1Char(
'_'));
481 if (Q_UNLIKELY(!isRuntimeGenerator()))
482 prefix = QStringLiteral(
".animations");
484 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
486 stream() <<
"ParallelAnimation {";
489 stream() <<
"id: " << mainAnimationId;
491 generateAnimationBindings();
493 for (
int i = 0; i < property.animationCount(); ++i) {
494 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
496 stream() <<
"SequentialAnimation {";
499 const int startOffset = animation.startOffset;
501 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
503 stream() <<
"SequentialAnimation {";
506 const int repeatCount = animation.repeatCount;
508 stream() <<
"loops: Animation.Infinite";
510 stream() <<
"loops: " << repeatCount;
512 int previousTime = 0;
513 QVariant previousValue;
514 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
515 const int time = it.key();
516 const int frameTime = time - previousTime;
517 const QVariant &value = it.value();
519 if (previousValue.isValid() && previousValue == value) {
521 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
522 }
else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
525 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
526 stream() <<
"ScriptAction {";
529 stream() <<
"script:" << targetName <<
"." << propertyName <<
" = " << value.toString();
534 generateAnimatedPropertySetter(targetName,
544 previousValue = value;
547 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
548 stream() <<
"ScriptAction {";
550 stream() <<
"script: ";
552 switch (animationType) {
553 case AnimationType::Auto:
554 stream(SameLine) << targetName <<
"." << propertyName <<
" = ";
556 case AnimationType::ColorOpacity:
557 stream(SameLine) << targetName <<
"." << propertyName <<
".a = ";
561 QVariant value = property.defaultValue();
562 if (value.typeId() == QMetaType::QColor)
563 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
565 stream(SameLine) << value.toReal();
582void QQuickQmlGenerator::generateTransform(
const QTransform &xf)
585 stream(SameLine) <<
"PlanarTransform.fromAffineMatrix("
586 << xf.m11() <<
", " << xf.m12() <<
", "
587 << xf.m21() <<
", " << xf.m22() <<
", "
588 << xf.dx() <<
", " << xf.dy() <<
")";
591 stream(SameLine) <<
"Qt.matrix4x4(";
593 const auto *data = m.data();
594 for (
int i = 0; i < 4; i++) {
595 stream() << data[i] <<
", " << data[i+4] <<
", " << data[i+8] <<
", " << data[i+12];
597 stream(SameLine) <<
", ";
599 stream(SameLine) <<
")";
604void QQuickQmlGenerator::outputShapePath(
const PathNodeInfo &info,
const QPainterPath *painterPath,
const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector,
const QRectF &boundingRect)
606 Q_UNUSED(pathSelector)
607 Q_ASSERT(painterPath || quadPath);
609 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
610 const bool noPen = strokeColor == QColorConstants::Transparent
611 && !info.strokeStyle.color.isAnimated()
612 && !info.strokeStyle.opacity.isAnimated();
613 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
616 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
617 const bool noFill = info.grad.type() == QGradient::NoGradient
618 && fillColor == QColorConstants::Transparent
619 && !info.fillColor.isAnimated()
620 && !info.fillOpacity.isAnimated();
621 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
627 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
628 stream() <<
"ShapePath {";
631 QString shapePathId = info.id;
632 if (pathSelector & QQuickVectorImageGenerator::FillPath)
633 shapePathId += QStringLiteral(
"_fill");
634 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
635 shapePathId += QStringLiteral(
"_stroke");
637 stream() <<
"id: " << shapePathId;
639 if (!info.nodeId.isEmpty()) {
640 switch (pathSelector) {
641 case QQuickVectorImageGenerator::FillPath:
642 stream() <<
"objectName: \"svg_fill_path:" << info.nodeId <<
"\"";
644 case QQuickVectorImageGenerator::StrokePath:
645 stream() <<
"objectName: \"svg_stroke_path:" << info.nodeId <<
"\"";
647 case QQuickVectorImageGenerator::FillAndStroke:
648 stream() <<
"objectName: \"svg_path:" << info.nodeId <<
"\"";
653 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
654 stream() <<
"strokeColor: \"transparent\"";
656 stream() <<
"strokeColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
657 stream() <<
"strokeWidth: " << info.strokeStyle.width;
658 stream() <<
"capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
659 stream() <<
"joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
660 stream() <<
"miterLimit: " << info.strokeStyle.miterLimit;
661 if (info.strokeStyle.dashArray.length() != 0) {
662 stream() <<
"strokeStyle: " <<
"ShapePath.DashLine";
663 stream() <<
"dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
664 stream() <<
"dashOffset: " << info.strokeStyle.dashOffset;
668 QTransform fillTransform = info.fillTransform;
669 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
670 stream() <<
"fillColor: \"transparent\"";
671 }
else if (info.grad.type() != QGradient::NoGradient) {
672 generateGradient(&info.grad);
673 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
674 QTransform objectToUserSpace;
675 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
676 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
677 fillTransform *= objectToUserSpace;
680 stream() <<
"fillColor: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
683 if (!fillTransform.isIdentity()) {
684 const QTransform &xf = fillTransform;
685 stream() <<
"fillTransform: ";
686 if (info.fillTransform.type() == QTransform::TxTranslate)
687 stream(SameLine) <<
"PlanarTransform.fromTranslate(" << xf.dx() <<
", " << xf.dy() <<
")";
688 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
689 stream(SameLine) <<
"PlanarTransform.fromScale(" << xf.m11() <<
", " << xf.m22() <<
")";
691 generateTransform(xf);
694 if (info.trim.enabled) {
695 stream() <<
"trim.start: " << info.trim.start.defaultValue().toReal();
696 stream() <<
"trim.end: " << info.trim.end.defaultValue().toReal();
697 stream() <<
"trim.offset: " << info.trim.offset.defaultValue().toReal();
701 if (fillRule == QQuickShapePath::WindingFill)
702 stream() <<
"fillRule: ShapePath.WindingFill";
704 stream() <<
"fillRule: ShapePath.OddEvenFill";
708 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
709 if (!hintStr.isEmpty())
712 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
713 stream() <<
"PathSvg { path: \"" << svgPathString <<
"\" }";
718 if (info.trim.enabled) {
719 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"start"));
720 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"end"));
721 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"offset"));
724 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeColor"));
725 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral(
"strokeColor"), AnimationType::ColorOpacity);
726 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillColor"));
727 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral(
"fillColor"), AnimationType::ColorOpacity);
730void QQuickQmlGenerator::generateNode(
const NodeInfo &info)
732 if (!isNodeVisible(info))
735 stream() <<
"// Missing Implementation for SVG Node: " << info.typeName;
736 stream() <<
"// Adding an empty Item and skipping";
737 stream() <<
"Item {";
739 generateNodeBase(info);
740 generateNodeEnd(info);
743void QQuickQmlGenerator::generateTextNode(
const TextNodeInfo &info)
745 if (!isNodeVisible(info))
748 static int counter = 0;
749 stream() <<
"Item {";
751 generateNodeBase(info);
753 if (!info.isTextArea)
754 stream() <<
"Item { id: textAlignItem_" << counter <<
"; x: " << info.position.x() <<
"; y: " << info.position.y() <<
"}";
756 stream() <<
"Text {";
760 const QString textItemId = QStringLiteral(
"_qt_textItem_%1").arg(counter);
761 stream() <<
"id: " << textItemId;
763 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral(
"color"));
764 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral(
"color"), AnimationType::ColorOpacity);
765 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral(
"styleColor"));
766 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral(
"styleColor"), AnimationType::ColorOpacity);
768 if (info.isTextArea) {
769 stream() <<
"x: " << info.position.x();
770 stream() <<
"y: " << info.position.y();
771 if (info.size.width() > 0)
772 stream() <<
"width: " << info.size.width();
773 if (info.size.height() > 0)
774 stream() <<
"height: " << info.size.height();
775 stream() <<
"wrapMode: Text.Wrap";
776 stream() <<
"clip: true";
778 QString hAlign = QStringLiteral(
"left");
779 stream() <<
"anchors.baseline: textAlignItem_" << counter <<
".top";
780 switch (info.alignment) {
781 case Qt::AlignHCenter:
782 hAlign = QStringLiteral(
"horizontalCenter");
785 hAlign = QStringLiteral(
"right");
788 qCDebug(lcQuickVectorImage) <<
"Unexpected text alignment" << info.alignment;
793 stream() <<
"anchors." << hAlign <<
": textAlignItem_" << counter <<
".left";
797 stream() <<
"color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) <<
"\"";
798 stream() <<
"textFormat:" << (info.needsRichText ?
"Text.RichText" :
"Text.StyledText");
800 QString s = info.text;
801 s.replace(QLatin1Char(
'"'), QLatin1String(
"\\\""));
802 stream() <<
"text: \"" << s <<
"\"";
803 stream() <<
"font.family: \"" << info.font.family() <<
"\"";
804 if (info.font.pixelSize() > 0)
805 stream() <<
"font.pixelSize:" << info.font.pixelSize();
806 else if (info.font.pointSize() > 0)
807 stream() <<
"font.pixelSize:" << info.font.pointSizeF();
808 if (info.font.underline())
809 stream() <<
"font.underline: true";
810 if (info.font.weight() != QFont::Normal)
811 stream() <<
"font.weight: " <<
int(info.font.weight());
812 if (info.font.italic())
813 stream() <<
"font.italic: true";
814 switch (info.font.hintingPreference()) {
815 case QFont::PreferFullHinting:
816 stream() <<
"font.hintingPreference: Font.PreferFullHinting";
818 case QFont::PreferVerticalHinting:
819 stream() <<
"font.hintingPreference: Font.PreferVerticalHinting";
821 case QFont::PreferNoHinting:
822 stream() <<
"font.hintingPreference: Font.PreferNoHinting";
824 case QFont::PreferDefaultHinting:
825 stream() <<
"font.hintingPreference: Font.PreferDefaultHinting";
829 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
830 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
831 stream() <<
"styleColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
832 stream() <<
"style: Text.Outline";
838 generateNodeEnd(info);
841void QQuickQmlGenerator::generateUseNode(
const UseNodeInfo &info)
843 if (!isNodeVisible(info))
846 if (info.stage == StructureNodeStage::Start) {
847 stream() <<
"Item {";
849 generateNodeBase(info);
851 generateNodeEnd(info);
855void QQuickQmlGenerator::generatePathContainer(
const StructureNodeInfo &info)
858 stream() << shapeName() <<
" {";
860 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
861 stream() <<
"preferredRendererType: Shape.CurveRenderer";
864 m_inShapeItemLevel++;
867void QQuickQmlGenerator::generateAnimatedPropertySetter(
const QString &targetName,
868 const QString &propertyName,
869 const QVariant &value,
870 const QQuickAnimatedProperty::PropertyAnimation &animation,
873 AnimationType animationType)
876 switch (animationType) {
877 case AnimationType::Auto:
878 if (value.typeId() == QMetaType::QColor)
879 stream() <<
"ColorAnimation {";
881 stream() <<
"PropertyAnimation {";
883 case AnimationType::ColorOpacity:
884 stream() <<
"ColorOpacityAnimation {";
889 stream() <<
"duration: " << frameTime;
890 stream() <<
"target: " << targetName;
891 stream() <<
"property: \"" << propertyName <<
"\"";
893 if (value.typeId() == QMetaType::QVector3D) {
894 const QVector3D &v = value.value<QVector3D>();
895 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
896 }
else if (value.typeId() == QMetaType::QColor) {
897 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
899 stream(SameLine) << value.toReal();
901 generateEasing(animation, time);
905 stream() <<
"ScriptAction {";
907 stream() <<
"script:" << targetName <<
"." << propertyName;
908 if (animationType == AnimationType::ColorOpacity)
909 stream(SameLine) <<
".a";
911 stream(SameLine) <<
" = ";
912 if (value.typeId() == QMetaType::QVector3D) {
913 const QVector3D &v = value.value<QVector3D>();
914 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
915 }
else if (value.typeId() == QMetaType::QColor) {
916 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
918 stream(SameLine) << value.toReal();
925void QQuickQmlGenerator::generateAnimateTransform(
const QString &targetName,
const NodeInfo &info)
927 if (!info.transform.isAnimated())
930 const QString mainAnimationId = targetName
931 + QStringLiteral(
"_transform_animation");
934 if (Q_UNLIKELY(!isRuntimeGenerator()))
935 prefix = QStringLiteral(
".animations");
936 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
938 stream() <<
"ParallelAnimation {";
941 stream() <<
"id:" << mainAnimationId;
943 generateAnimationBindings();
944 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
945 int animationStart = info.transform.animationGroup(groupIndex);
946 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
947 ? info.transform.animationGroup(groupIndex + 1)
948 : info.transform.animationCount();
951 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
952 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
953 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
955 stream() <<
"SequentialAnimation {";
958 const int startOffset = firstAnimation.startOffset;
960 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
962 const int repeatCount = firstAnimation.repeatCount;
964 stream() <<
"loops: Animation.Infinite";
966 stream() <<
"loops: " << repeatCount;
969 stream() <<
"ScriptAction {";
972 stream() <<
"script: " << targetName <<
"_transform_base_group"
973 <<
".activateOverride(" << targetName <<
"_transform_group_" << groupIndex <<
")";
979 stream() <<
"ParallelAnimation {";
982 for (
int i = animationStart; i < nextAnimationStart; ++i) {
983 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
984 if (animation.isConstant())
986 bool hasRotationCenter =
false;
987 if (animation.subtype == QTransform::TxRotate) {
988 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
989 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
990 if (!center.isNull()) {
991 hasRotationCenter =
true;
997 stream() <<
"SequentialAnimation {";
1000 int previousTime = 0;
1001 QVariantList previousParameters;
1002 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1003 const int time = it.key();
1004 const int frameTime = time - previousTime;
1005 const QVariantList ¶meters = it.value().value<QVariantList>();
1006 if (parameters.isEmpty())
1009 if (parameters == previousParameters) {
1011 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
1013 stream() <<
"ParallelAnimation {";
1016 const QString propertyTargetName = targetName
1017 + QStringLiteral(
"_transform_")
1018 + QString::number(groupIndex)
1019 + QStringLiteral(
"_")
1020 + QString::number(i);
1022 switch (animation.subtype) {
1023 case QTransform::TxTranslate:
1025 const QPointF translation = parameters.first().value<QPointF>();
1027 generateAnimatedPropertySetter(propertyTargetName,
1028 QStringLiteral(
"x"),
1033 generateAnimatedPropertySetter(propertyTargetName,
1034 QStringLiteral(
"y"),
1041 case QTransform::TxScale:
1043 const QPointF scale = parameters.first().value<QPointF>();
1044 generateAnimatedPropertySetter(propertyTargetName,
1045 QStringLiteral(
"xScale"),
1050 generateAnimatedPropertySetter(propertyTargetName,
1051 QStringLiteral(
"yScale"),
1058 case QTransform::TxRotate:
1060 Q_ASSERT(parameters.size() == 2);
1061 const qreal angle = parameters.value(1).toReal();
1062 if (hasRotationCenter) {
1063 const QPointF center = parameters.value(0).value<QPointF>();
1064 generateAnimatedPropertySetter(propertyTargetName,
1065 QStringLiteral(
"origin"),
1066 QVector3D(center.x(), center.y(), 0.0),
1071 generateAnimatedPropertySetter(propertyTargetName,
1072 QStringLiteral(
"angle"),
1079 case QTransform::TxShear:
1081 const QPointF skew = parameters.first().value<QPointF>();
1083 generateAnimatedPropertySetter(propertyTargetName,
1084 QStringLiteral(
"xAngle"),
1090 generateAnimatedPropertySetter(propertyTargetName,
1091 QStringLiteral(
"yAngle"),
1106 previousTime = time;
1107 previousParameters = parameters;
1119 if (firstAnimation.repeatCount >= 0) {
1120 stream() <<
"ScriptAction {";
1123 stream() <<
"script: {";
1127 stream() << targetName <<
"_transform_base_group.deactivate("
1128 << targetName <<
"_transform_group_" << groupIndex <<
")";
1129 }
else if (!replace) {
1130 stream() << targetName <<
"_transform_base_group.deactivateOverride("
1131 << targetName <<
"_transform_group_" << groupIndex <<
")";
1149bool QQuickQmlGenerator::generateStructureNode(
const StructureNodeInfo &info)
1151 if (!isNodeVisible(info))
1154 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1155 if (info.stage == StructureNodeStage::Start) {
1156 if (!info.clipBox.isEmpty()) {
1157 stream() <<
"Item { // Clip";
1160 stream() <<
"width: " << info.clipBox.width();
1161 stream() <<
"height: " << info.clipBox.height();
1162 stream() <<
"clip: true";
1165 if (isPathContainer) {
1166 generatePathContainer(info);
1167 }
else if (!info.customItemType.isEmpty()) {
1168 stream() << info.customItemType <<
" {";
1170 stream() <<
"Item { // Structure node";
1174 if (!info.viewBox.isEmpty()) {
1175 stream() <<
"transform: [";
1177 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1179 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1180 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1185 generateNodeBase(info);
1187 generateNodeEnd(info);
1188 if (isPathContainer)
1189 m_inShapeItemLevel--;
1191 if (!info.clipBox.isEmpty()) {
1200bool QQuickQmlGenerator::generateMaskNode(
const MaskNodeInfo &info)
1203 if (info.stage == StructureNodeStage::Start) {
1204 stream() <<
"Item {";
1207 generateNodeBase(info);
1209 stream() <<
"visible: false";
1210 stream() <<
"width: originalBounds.width";
1211 stream() <<
"height: originalBounds.height";
1213 stream() <<
"property real maskX: " << info.maskRect.left();
1214 stream() <<
"property real maskY: " << info.maskRect.top();
1215 stream() <<
"property real maskWidth: " << info.maskRect.width();
1216 stream() <<
"property real maskHeight: " << info.maskRect.height();
1218 stream() <<
"function maskRect(other) {";
1221 stream() <<
"return ";
1222 if (info.isMaskRectRelativeCoordinates) {
1225 << info.id <<
".maskX * other.originalBounds.width + other.originalBounds.x,"
1226 << info.id <<
".maskY * other.originalBounds.height + other.originalBounds.y,"
1227 << info.id <<
".maskWidth * other.originalBounds.width,"
1228 << info.id <<
".maskHeight * other.originalBounds.height)";
1232 << info.id <<
".maskX, "
1233 << info.id <<
".maskY, "
1234 << info.id <<
".maskWidth, "
1235 << info.id <<
".maskHeight)";
1242 generateNodeEnd(info);
1248bool QQuickQmlGenerator::generateRootNode(
const StructureNodeInfo &info)
1250 const QStringList comments = m_commentString.split(u'\n');
1252 if (!isNodeVisible(info)) {
1255 if (comments.isEmpty()) {
1256 stream() <<
"// Generated from SVG";
1258 for (
const auto &comment : comments)
1259 stream() <<
"// " << comment;
1262 stream() <<
"import QtQuick";
1263 stream() <<
"import QtQuick.Shapes" << Qt::endl;
1264 stream() <<
"Item {";
1267 double w = info.size.width();
1268 double h = info.size.height();
1270 stream() <<
"implicitWidth: " << w;
1272 stream() <<
"implicitHeight: " << h;
1280 if (info.stage == StructureNodeStage::Start) {
1283 if (comments.isEmpty())
1284 stream() <<
"// Generated from SVG";
1286 for (
const auto &comment : comments)
1287 stream() <<
"// " << comment;
1289 stream() <<
"import QtQuick";
1290 stream() <<
"import QtQuick.VectorImage";
1291 stream() <<
"import QtQuick.VectorImage.Helpers";
1292 stream() <<
"import QtQuick.Shapes";
1293 for (
const auto &import : std::as_const(m_extraImports))
1294 stream() <<
"import " << import;
1296 stream() << Qt::endl <<
"Item {";
1299 double w = info.size.width();
1300 double h = info.size.height();
1302 stream() <<
"implicitWidth: " << w;
1304 stream() <<
"implicitHeight: " << h;
1306 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1307 stream() <<
"component AnimationsInfo : QtObject";
1312 stream() <<
"property bool paused: false";
1313 stream() <<
"property int loops: 1";
1314 stream() <<
"signal restart()";
1316 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1319 stream() <<
"property AnimationsInfo animations : AnimationsInfo {}";
1322 if (!info.viewBox.isEmpty()) {
1323 stream() <<
"transform: [";
1325 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1327 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1328 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1333 if (!info.forceSeparatePaths && info.isPathContainer) {
1334 m_topLevelIdString = QStringLiteral(
"__qt_toplevel");
1335 stream() <<
"id: " << m_topLevelIdString;
1337 generatePathContainer(info);
1340 generateNodeBase(info);
1342 m_topLevelIdString = generateNodeBase(info);
1343 if (m_topLevelIdString.isEmpty())
1344 qCWarning(lcQuickVectorImage) <<
"No ID specified for top level item";
1347 if (m_inShapeItemLevel > 0) {
1348 m_inShapeItemLevel--;
1353 for (
const auto [coords, id] : m_easings.asKeyValueRange()) {
1354 stream() <<
"property easingCurve " << id <<
": { type: Easing.BezierSpline; bezierCurve: [ ";
1355 for (
auto coord : coords)
1356 stream(SameLine) << coord <<
", ";
1357 stream(SameLine) <<
"1, 1 ] }";
1360 generateNodeEnd(info);
1367QStringView QQuickQmlGenerator::indent()
1369 static QString indentString;
1370 int indentWidth = m_indentLevel * 4;
1371 if (indentWidth > indentString.size())
1372 indentString.fill(QLatin1Char(
' '), indentWidth * 2);
1373 return QStringView(indentString).first(indentWidth);
1376QTextStream &QQuickQmlGenerator::stream(
int flags)
1378 if (m_stream.device() ==
nullptr)
1379 m_stream.setDevice(&m_result);
1380 else if (!(flags & StreamFlags::SameLine))
1381 m_stream << Qt::endl << indent();
1385const char *QQuickQmlGenerator::shapeName()
const
1387 return m_shapeTypeName.isEmpty() ?
"Shape" : m_shapeTypeName.constData();
Combined button and popup list for selecting options.