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>
25 s.replace(QLatin1Char(
'"'), QLatin1String(
"\\\""));
30QQuickAnimatedProperty::PropertyAnimation QQuickAnimatedProperty::PropertyAnimation::simplified()
const
32 QQuickAnimatedProperty::PropertyAnimation res = *
this;
33 int consecutiveEquals = 0;
34 int prevTimePoint = -1;
36 for (
const auto &[timePoint, value] : frames.asKeyValueRange()) {
37 if (value != prevValue) {
38 consecutiveEquals = 1;
40 }
else if (consecutiveEquals < 2) {
44 res.frames.remove(prevTimePoint);
45 res.easingPerFrame.remove(prevTimePoint);
47 prevTimePoint = timePoint;
53QQuickQmlGenerator::QQuickQmlGenerator(
const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags,
const QString &outFileName)
54 : QQuickGenerator(fileName, flags)
55 , outputFileName(outFileName)
57 m_result.open(QIODevice::ReadWrite);
60QQuickQmlGenerator::~QQuickQmlGenerator()
64bool QQuickQmlGenerator::save()
67 if (!outputFileName.isEmpty()) {
68 QFileInfo fileInfo(outputFileName);
69 QDir dir(fileInfo.absolutePath());
70 if (!dir.exists() && !dir.mkpath(QStringLiteral(
"."))) {
71 qCWarning(lcQuickVectorImage) <<
"Failed to create path" << dir.absolutePath();
74 QFile outFile(outputFileName);
75 if (outFile.open(QIODevice::WriteOnly)) {
76 outFile.write(m_result.data());
79 qCWarning(lcQuickVectorImage) <<
"Failed to write to file" << outFile.fileName();
85 if (lcQuickVectorImage().isDebugEnabled())
86 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
91void QQuickQmlGenerator::setShapeTypeName(
const QString &name)
93 m_shapeTypeName = name.toLatin1();
96QString QQuickQmlGenerator::shapeTypeName()
const
98 return QString::fromLatin1(m_shapeTypeName);
101void QQuickQmlGenerator::setCommentString(
const QString commentString)
103 m_commentString = commentString;
106QString QQuickQmlGenerator::commentString()
const
108 return m_commentString;
111QString QQuickQmlGenerator::generateNodeBase(
const NodeInfo &info,
const QString &idSuffix)
113 if (!info.nodeId.isEmpty())
114 stream() <<
"objectName: \"" << info.nodeId <<
"\"";
116 if (!info.id.isEmpty())
117 stream() <<
"id: " << info.id << idSuffix;
119 if (!info.bounds.isNull()) {
120 stream() <<
"property var originalBounds: Qt.rect("
121 << info.bounds.x() <<
", "
122 << info.bounds.y() <<
", "
123 << info.bounds.width() <<
", "
124 << info.bounds.height() <<
")";
125 stream() <<
"width: originalBounds.width";
126 stream() <<
"height: originalBounds.height";
129 stream() <<
"transformOrigin: Item.TopLeft";
131 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
132 if (!info.isDefaultOpacity)
133 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
134 generateItemAnimations(info.id, info);
140void QQuickQmlGenerator::generateNodeEnd(
const NodeInfo &info)
144 generateShaderUse(info);
147void QQuickQmlGenerator::generateItemAnimations(
const QString &idString,
const NodeInfo &info)
149 const bool hasTransform = info.transform.isAnimated()
150 || !info.maskId.isEmpty()
151 || !info.filterId.isEmpty()
152 || !info.isDefaultTransform
153 || !info.transformReferenceId.isEmpty()
154 || info.motionPath.isAnimated();
157 stream() <<
"transform: TransformGroup {";
160 bool hasNonConstantTransform =
false;
161 int earliestOverrideGroup = -1;
163 if (!idString.isEmpty()) {
164 stream() <<
"id: " << idString <<
"_transform_base_group";
166 if (!info.maskId.isEmpty() || !info.filterId.isEmpty())
167 stream() <<
"Translate { x: " << idString <<
".sourceX; y: " << idString <<
".sourceY }";
169 if (info.transform.isAnimated()) {
170 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
171 stream() <<
"TransformGroup {";
174 if (!idString.isEmpty())
175 stream() <<
"id: " << idString <<
"_transform_group_" << groupIndex;
177 int animationStart = info.transform.animationGroup(groupIndex);
178 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
179 ? info.transform.animationGroup(groupIndex + 1)
180 : info.transform.animationCount();
182 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
183 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
184 if (replace && earliestOverrideGroup < 0)
185 earliestOverrideGroup = groupIndex;
187 for (
int i = nextAnimationStart - 1; i >= animationStart; --i) {
188 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
189 if (animation.frames.isEmpty())
192 const QVariantList ¶meters = animation.frames.first().value<QVariantList>();
193 switch (animation.subtype) {
194 case QTransform::TxTranslate:
195 if (animation.isConstant()) {
196 const QPointF translation = parameters.value(0).value<QPointF>();
197 if (!translation.isNull())
198 stream() <<
"Translate { x: " << translation.x() <<
"; y: " << translation.y() <<
" }";
200 hasNonConstantTransform =
true;
201 stream() <<
"Translate { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
204 case QTransform::TxScale:
205 if (animation.isConstant()) {
206 const QPointF scale = parameters.value(0).value<QPointF>();
207 if (scale != QPointF(1, 1))
208 stream() <<
"Scale { xScale: " << scale.x() <<
"; yScale: " << scale.y() <<
" }";
210 hasNonConstantTransform =
true;
211 stream() <<
"Scale { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
"}";
214 case QTransform::TxRotate:
215 if (animation.isConstant()) {
216 const QPointF center = parameters.value(0).value<QPointF>();
217 const qreal angle = parameters.value(1).toReal();
218 if (!qFuzzyIsNull(angle))
219 stream() <<
"Rotation { angle: " << angle <<
"; origin.x: " << center.x() <<
"; origin.y: " << center.y() <<
" }";
221 hasNonConstantTransform =
true;
222 stream() <<
"Rotation { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
225 case QTransform::TxShear:
226 if (animation.isConstant()) {
227 const QPointF skew = parameters.value(0).value<QPointF>();
229 stream() <<
"Shear { xAngle: " << skew.x() <<
"; yAngle: " << skew.y() <<
" }";
231 hasNonConstantTransform =
true;
232 stream() <<
"Shear { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
245 if (info.motionPath.isAnimated()) {
246 QVariantPair defaultProps = info.motionPath.defaultValue().value<QVariantPair>();
247 const bool adaptAngle = defaultProps.first.toBool();
248 const qreal baseRotation = defaultProps.second.toReal();
249 if (adaptAngle || !qFuzzyIsNull(baseRotation)) {
250 stream() <<
"Rotation {";
254 stream() <<
"angle: " << idString
255 <<
"_motion_animation.currentInterpolator.angle";
256 if (!qFuzzyIsNull(baseRotation))
257 stream(SameLine) <<
" + " << baseRotation;
259 stream() <<
"angle: " << baseRotation;
266 stream() <<
"Translate {";
269 stream() <<
"x: " << idString <<
"_motion_animation.currentInterpolator.x";
270 stream() <<
"y: " << idString <<
"_motion_animation.currentInterpolator.y";
277 if (!info.isDefaultTransform) {
278 QTransform xf = info.transform.defaultValue().value<QTransform>();
279 if (xf.type() <= QTransform::TxTranslate) {
280 stream() <<
"Translate { x: " << xf.dx() <<
"; y: " << xf.dy() <<
"}";
282 stream() <<
"Matrix4x4 { matrix: ";
283 generateTransform(xf);
284 stream(SameLine) <<
"}";
288 if (!info.transformReferenceId.isEmpty())
289 stream() <<
"Matrix4x4 { matrix: " << info.transformReferenceId <<
".transformMatrix }";
294 if (hasNonConstantTransform) {
295 generateAnimateTransform(idString, info);
296 }
else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
299 stream() <<
"Component.onCompleted: {";
302 stream() << idString <<
"_transform_base_group.activateOverride("
303 << idString <<
"_transform_group_" << earliestOverrideGroup <<
")";
310 generateAnimateMotionPath(idString, info.motionPath);
312 generatePropertyAnimation(info.opacity, idString, QStringLiteral(
"opacity"));
313 generatePropertyAnimation(info.visibility, idString, QStringLiteral(
"visible"));
316void QQuickQmlGenerator::generateShaderUse(
const NodeInfo &info)
318 const bool hasMask = !info.maskId.isEmpty();
319 const bool hasFilters = !info.filterId.isEmpty();
320 if (!hasMask && !hasFilters)
323 const QString effectId = hasFilters
324 ? info.filterId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_effect")
327 QString animatedItemId;
329 stream() <<
"ShaderEffectSource {";
332 const QString seId = info.id + QStringLiteral(
"_se");
333 stream() <<
"id: " << seId;
335 stream() <<
"ItemSpy {";
337 stream() <<
"id: " << info.id <<
"_itemspy";
338 stream() <<
"anchors.fill: parent";
342 stream() <<
"hideSource: true";
343 stream() <<
"wrapMode: " << info.filterId <<
"_filterParameters.wrapMode";
344 stream() <<
"sourceItem: " << info.id;
345 stream() <<
"sourceRect: " << info.filterId
346 <<
"_filterParameters.adaptToFilterRect("
347 << info.id <<
".originalBounds.x, "
348 << info.id <<
".originalBounds.y, "
349 << info.id <<
".originalBounds.width, "
350 << info.id <<
".originalBounds.height)";
351 stream() <<
"textureSize: " << info.id <<
"_itemspy.requiredTextureSize";
352 stream() <<
"width: sourceRect.width";
353 stream() <<
"height: sourceRect.height";
354 stream() <<
"visible: false";
359 stream() <<
"Loader {";
362 animatedItemId = effectId;
363 stream() <<
"id: " << effectId;
365 stream() <<
"property var filterSourceItem: " << seId;
366 stream() <<
"sourceComponent: " << info.filterId <<
"_container";
367 stream() <<
"property real sourceX: " << info.id <<
".originalBounds.x";
368 stream() <<
"property real sourceY: " << info.id <<
".originalBounds.y";
369 stream() <<
"width: " << info.id <<
".originalBounds.width";
370 stream() <<
"height: " << info.id <<
".originalBounds.height";
380 stream() <<
"ShaderEffectSource {";
383 const QString maskId = info.maskId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_mask");
384 stream() <<
"id: " << maskId;
385 stream() <<
"sourceItem: " << info.maskId;
386 stream() <<
"visible: false";
387 stream() <<
"hideSource: true";
389 stream() <<
"ItemSpy {";
391 stream() <<
"id: " << maskId <<
"_itemspy";
392 stream() <<
"anchors.fill: parent";
395 stream() <<
"textureSize: " << maskId <<
"_itemspy.requiredTextureSize";
397 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
398 << info.id <<
".originalBounds.x,"
399 << info.id <<
".originalBounds.y,"
400 << info.id <<
".originalBounds.width,"
401 << info.id <<
".originalBounds.height)";
403 stream() <<
"width: sourceRect.width";
404 stream() <<
"height: sourceRect.height";
410 stream() <<
"ShaderEffectSource {";
413 const QString seId = info.id + QStringLiteral(
"_masked_se");
414 stream() <<
"id: " << seId;
416 stream() <<
"ItemSpy {";
418 stream() <<
"id: " << info.id <<
"_masked_se_itemspy";
419 stream() <<
"anchors.fill: parent";
423 stream() <<
"hideSource: true";
425 stream() <<
"sourceItem: " << effectId;
427 stream() <<
"sourceItem: " << info.id;
428 stream() <<
"textureSize: " << info.id <<
"_masked_se_itemspy.requiredTextureSize";
430 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
431 << info.id <<
".originalBounds.x,"
432 << info.id <<
".originalBounds.y,"
433 << info.id <<
".originalBounds.width,"
434 << info.id <<
".originalBounds.height)";
436 stream() <<
"sourceRect: " << info.maskId <<
".maskRect(0, 0,"
437 << info.id <<
".originalBounds.width,"
438 << info.id <<
".originalBounds.height)";
440 stream() <<
"width: sourceRect.width";
441 stream() <<
"height: sourceRect.height";
442 stream() <<
"smooth: false";
443 stream() <<
"visible: false";
448 stream() <<
"ShaderEffect {";
451 const QString maskShaderId = maskId + QStringLiteral(
"_se");
452 animatedItemId = maskShaderId;
454 stream() <<
"id:" << maskShaderId;
456 stream() <<
"property real sourceX: " << maskId <<
".sourceRect.x";
457 stream() <<
"property real sourceY: " << maskId <<
".sourceRect.y";
458 stream() <<
"width: " << maskId <<
".sourceRect.width";
459 stream() <<
"height: " << maskId <<
".sourceRect.height";
461 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
462 stream() <<
"property var source: " << seId;
463 stream() <<
"property var maskSource: " << maskId;
464 stream() <<
"property bool isAlpha: " << (info.isMaskAlpha ?
"true" :
"false");
465 stream() <<
"property bool isInverted: " << (info.isMaskInverted ?
"true" :
"false");
468 if (!info.isDefaultOpacity)
469 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
471 generateItemAnimations(animatedItemId, info);
477bool QQuickQmlGenerator::generateDefsNode(
const StructureNodeInfo &info)
479 if (info.stage == StructureNodeStage::Start) {
480 m_oldIndentLevel = m_indentLevel;
482 stream() <<
"Component {";
485 stream() <<
"id: " << info.id <<
"_container";
487 stream() <<
"Item {";
490 if (!info.transformReferenceChildId.isEmpty()) {
491 stream() <<
"property alias transformMatrix: "
492 << info.transformReferenceChildId <<
".transformMatrix";
495 generateNodeBase(info, QStringLiteral(
"_defs"));
497 generateNodeEnd(info);
502 stream() << m_defsSuffix;
503 m_defsSuffix.clear();
505 m_indentLevel = m_oldIndentLevel;
511void QQuickQmlGenerator::generateImageNode(
const ImageNodeInfo &info)
513 if (!isNodeVisible(info))
516 const QFileInfo outputFileInfo(outputFileName);
517 const QDir outputDir(outputFileInfo.absolutePath());
521 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
522 filePath = m_assetFileDirectory;
523 if (filePath.isEmpty())
524 filePath = outputDir.absolutePath();
526 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
529 QDir fileDir(filePath);
530 if (!fileDir.exists()) {
531 if (!fileDir.mkpath(QStringLiteral(
".")))
532 qCWarning(lcQuickVectorImage) <<
"Failed to create image resource directory:" << filePath;
535 filePath += QStringLiteral(
"%1%2.png").arg(m_assetFilePrefix.isEmpty()
536 ? QStringLiteral(
"svg_asset_")
538 .arg(info.image.cacheKey());
540 if (!info.image.save(filePath))
541 qCWarning(lcQuickVectorImage) <<
"Unabled to save image resource" << filePath;
542 qCDebug(lcQuickVectorImage) <<
"Saving copy of IMAGE" << filePath;
544 filePath = info.externalFileReference;
547 const QFileInfo assetFileInfo(filePath);
549 stream() <<
"Image {";
552 generateNodeBase(info);
553 stream() <<
"x: " << info.rect.x();
554 stream() <<
"y: " << info.rect.y();
555 stream() <<
"width: " << info.rect.width();
556 stream() <<
"height: " << info.rect.height();
557 stream() <<
"source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<
"\"";
558 generateNodeEnd(info);
561void QQuickQmlGenerator::generateMarkers(
const PathNodeInfo &info)
563 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
564 for (
int i = 0; i < path.elementCount(); ++i) {
565 const QPainterPath::Element element = path.elementAt(i);
570 auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
571 QPointF t1 = p1 - p0;
572 QPointF t2 = p2 - p1;
573 qreal hyp1 = hypot(t1.x(), t1.y());
578 qreal hyp2 = hypot(t2.x(), t2.y());
583 QPointF tangent = t1 + t2;
584 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
588 markerId = info.markerStartId;
589 angle = path.angleAtPercent(0.0);
590 }
else if (i == path.elementCount() - 1) {
591 markerId = info.markerEndId;
592 angle = path.angleAtPercent(1.0);
593 }
else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
594 markerId = info.markerMidId;
596 const QPainterPath::Element prevElement = path.elementAt(i - 1);
597 const QPainterPath::Element nextElement = path.elementAt(i + 1);
599 QPointF p1(prevElement.x, prevElement.y);
600 QPointF p2(element.x, element.y);
601 QPointF p3(nextElement.x, nextElement.y);
603 angle = getMeanAngle(p1, p2, p3);
606 if (!markerId.isEmpty()) {
607 stream() <<
"Loader {";
611 stream() <<
"sourceComponent: " << markerId <<
"_container";
612 stream() <<
"property real strokeWidth: " << info.strokeStyle.width;
613 stream() <<
"transform: [";
616 stream() <<
"Scale { "
617 <<
"xScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1; "
618 <<
"yScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1 },";
620 stream() <<
"Rotation { angle: " << markerId <<
"_markerParameters.autoAngle(" << -angle <<
") },";
621 stream() <<
"Translate { x: " << element.x <<
"; y: " << element.y <<
"}";
632void QQuickQmlGenerator::generatePath(
const PathNodeInfo &info,
const QRectF &overrideBoundingRect)
634 if (!isNodeVisible(info))
637 if (m_inShapeItemLevel > 0) {
638 if (!info.isDefaultTransform)
639 qWarning() <<
"Skipped transform for node" << info.nodeId <<
"type" << info.typeName <<
"(this is not supposed to happen)";
640 optimizePaths(info, overrideBoundingRect);
642 m_inShapeItemLevel++;
643 stream() << shapeName() <<
" {";
646 generateNodeBase(info);
648 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
649 stream() <<
"preferredRendererType: Shape.CurveRenderer";
650 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
651 stream() <<
"asynchronous: true";
652 optimizePaths(info, overrideBoundingRect);
655 if (!info.markerStartId.isEmpty()
656 || !info.markerMidId.isEmpty()
657 || !info.markerEndId.isEmpty()) {
658 generateMarkers(info);
661 generateNodeEnd(info);
662 m_inShapeItemLevel--;
666void QQuickQmlGenerator::generateGradient(
const QGradient *grad)
668 if (grad->type() == QGradient::LinearGradient) {
669 auto *linGrad =
static_cast<
const QLinearGradient *>(grad);
670 stream() <<
"fillGradient: LinearGradient {";
673 QRectF gradRect(linGrad->start(), linGrad->finalStop());
675 stream() <<
"x1: " << gradRect.left();
676 stream() <<
"y1: " << gradRect.top();
677 stream() <<
"x2: " << gradRect.right();
678 stream() <<
"y2: " << gradRect.bottom();
679 for (
auto &stop : linGrad->stops())
680 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
681 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
684 }
else if (grad->type() == QGradient::RadialGradient) {
685 auto *radGrad =
static_cast<
const QRadialGradient*>(grad);
686 stream() <<
"fillGradient: RadialGradient {";
689 stream() <<
"centerX: " << radGrad->center().x();
690 stream() <<
"centerY: " << radGrad->center().y();
691 stream() <<
"centerRadius: " << radGrad->radius();
692 stream() <<
"focalX:" << radGrad->focalPoint().x();
693 stream() <<
"focalY:" << radGrad->focalPoint().y();
694 for (
auto &stop : radGrad->stops())
695 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
696 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
702void QQuickQmlGenerator::generateAnimationBindings()
705 if (Q_UNLIKELY(!isRuntimeGenerator()))
706 prefix = QStringLiteral(
".animations");
708 stream() <<
"loops: " << m_topLevelIdString << prefix <<
".loops";
709 stream() <<
"paused: " << m_topLevelIdString << prefix <<
".paused";
710 stream() <<
"running: true";
713 stream() <<
"onLoopsChanged: { if (running) { restart() } }";
716void QQuickQmlGenerator::generateEasing(
const QQuickAnimatedProperty::PropertyAnimation &animation,
int time)
718 if (animation.easingPerFrame.contains(time)) {
719 QBezier bezier = animation.easingPerFrame.value(time);
720 QPointF c1 = bezier.pt2();
721 QPointF c2 = bezier.pt3();
723 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
725 int nextIdx = m_easings.size();
726 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
728 id = QString(QLatin1String(
"easing_%1")).arg(nextIdx, 2, 10, QLatin1Char(
'0'));
729 stream() <<
"easing: " << m_topLevelIdString <<
"." << id;
736 static qreal multiplier = qreal(qEnvironmentVariable(
"QT_QUICKVECTORIMAGE_TIME_DILATION", QStringLiteral(
"1.0"))
738 return std::round(multiplier * time);
741void QQuickQmlGenerator::generatePropertyAnimation(
const QQuickAnimatedProperty &property,
742 const QString &targetName,
743 const QString &propertyName,
744 AnimationType animationType)
746 if (!property.isAnimated())
749 QString mainAnimationId = targetName
750 + QStringLiteral(
"_")
752 + QStringLiteral(
"_animation");
753 mainAnimationId.replace(QLatin1Char(
'.'), QLatin1Char(
'_'));
756 if (Q_UNLIKELY(!isRuntimeGenerator()))
757 prefix = QStringLiteral(
".animations");
759 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
761 stream() <<
"ParallelAnimation {";
764 stream() <<
"id: " << mainAnimationId;
766 generateAnimationBindings();
768 for (
int i = 0; i < property.animationCount(); ++i) {
769 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
771 stream() <<
"SequentialAnimation {";
774 const int startOffset = processAnimationTime(animation.startOffset);
776 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
778 stream() <<
"SequentialAnimation {";
781 const int repeatCount = animation.repeatCount;
783 stream() <<
"loops: Animation.Infinite";
785 stream() <<
"loops: " << repeatCount;
787 int previousTime = 0;
788 QVariant previousValue;
789 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
790 const int time = it.key();
791 const int frameTime = processAnimationTime(time - previousTime);
792 const QVariant &value = it.value();
794 if (previousValue.isValid() && previousValue == value) {
796 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
797 }
else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
800 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
801 stream() <<
"ScriptAction {";
804 stream() <<
"script:" << targetName <<
"." << propertyName <<
" = " << value.toString();
809 generateAnimatedPropertySetter(targetName,
819 previousValue = value;
822 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
823 stream() <<
"ScriptAction {";
825 stream() <<
"script: ";
827 switch (animationType) {
828 case AnimationType::Auto:
829 stream(SameLine) << targetName <<
"." << propertyName <<
" = ";
831 case AnimationType::ColorOpacity:
832 stream(SameLine) << targetName <<
"." << propertyName <<
".a = ";
836 QVariant value = property.defaultValue();
837 if (value.typeId() == QMetaType::QColor)
838 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
840 stream(SameLine) << value.toReal();
857void QQuickQmlGenerator::generateTransform(
const QTransform &xf)
860 stream(SameLine) <<
"PlanarTransform.fromAffineMatrix("
861 << xf.m11() <<
", " << xf.m12() <<
", "
862 << xf.m21() <<
", " << xf.m22() <<
", "
863 << xf.dx() <<
", " << xf.dy() <<
")";
866 stream(SameLine) <<
"Qt.matrix4x4(";
868 const auto *data = m.data();
869 for (
int i = 0; i < 4; i++) {
870 stream() << data[i] <<
", " << data[i+4] <<
", " << data[i+8] <<
", " << data[i+12];
872 stream(SameLine) <<
", ";
874 stream(SameLine) <<
")";
879void QQuickQmlGenerator::outputShapePath(
const PathNodeInfo &info,
const QPainterPath *painterPath,
const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector,
const QRectF &boundingRect)
881 Q_UNUSED(pathSelector)
882 Q_ASSERT(painterPath || quadPath);
884 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
885 const bool noPen = strokeColor == QColorConstants::Transparent
886 && !info.strokeStyle.color.isAnimated()
887 && !info.strokeStyle.opacity.isAnimated();
888 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
891 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
892 const bool noFill = info.grad.type() == QGradient::NoGradient
893 && fillColor == QColorConstants::Transparent
894 && !info.fillColor.isAnimated()
895 && !info.fillOpacity.isAnimated();
896 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
902 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
903 stream() <<
"ShapePath {";
906 QString shapePathId = info.id;
907 if (pathSelector & QQuickVectorImageGenerator::FillPath)
908 shapePathId += QStringLiteral(
"_fill");
909 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
910 shapePathId += QStringLiteral(
"_stroke");
912 stream() <<
"id: " << shapePathId;
914 if (!info.nodeId.isEmpty()) {
915 switch (pathSelector) {
916 case QQuickVectorImageGenerator::FillPath:
917 stream() <<
"objectName: \"svg_fill_path:" << info.nodeId <<
"\"";
919 case QQuickVectorImageGenerator::StrokePath:
920 stream() <<
"objectName: \"svg_stroke_path:" << info.nodeId <<
"\"";
922 case QQuickVectorImageGenerator::FillAndStroke:
923 stream() <<
"objectName: \"svg_path:" << info.nodeId <<
"\"";
928 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
929 stream() <<
"strokeColor: \"transparent\"";
931 stream() <<
"strokeColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
932 stream() <<
"strokeWidth: " << info.strokeStyle.width;
933 stream() <<
"capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
934 stream() <<
"joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
935 stream() <<
"miterLimit: " << info.strokeStyle.miterLimit;
936 if (info.strokeStyle.dashArray.length() != 0) {
937 stream() <<
"strokeStyle: " <<
"ShapePath.DashLine";
938 stream() <<
"dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
939 stream() <<
"dashOffset: " << info.strokeStyle.dashOffset;
943 QTransform fillTransform = info.fillTransform;
944 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
945 stream() <<
"fillColor: \"transparent\"";
946 }
else if (info.grad.type() != QGradient::NoGradient) {
947 generateGradient(&info.grad);
948 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
949 QTransform objectToUserSpace;
950 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
951 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
952 fillTransform *= objectToUserSpace;
955 stream() <<
"fillColor: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
958 if (!info.patternId.isEmpty()) {
959 stream() <<
"fillItem: ShaderEffectSource {";
962 stream() <<
"parent: " << info.id;
963 stream() <<
"sourceItem: " << info.patternId;
964 stream() <<
"hideSource: true";
965 stream() <<
"visible: false";
966 stream() <<
"width: " << info.patternId <<
".width";
967 stream() <<
"height: " << info.patternId <<
".height";
968 stream() <<
"wrapMode: ShaderEffectSource.Repeat";
969 stream() <<
"textureSize: Qt.size(width * __qt_toplevel_scale_itemspy.requiredTextureSize.width, "
970 <<
"height * __qt_toplevel_scale_itemspy.requiredTextureSize.height)";;
971 stream() <<
"sourceRect: " << info.patternId <<
".sourceRect("
972 << info.id <<
".width, "
973 << info.id <<
".height)";
980 stream() <<
"function calculateFillTransform(xScale, yScale) {";
983 stream() <<
"var m = ";
984 generateTransform(fillTransform);
986 stream() <<
"m.translate(" << info.patternId <<
".sourceOffset("
987 << info.id <<
".width, "
988 << info.id <<
".height))";
990 stream() <<
"m.scale(1.0 / xScale, 1.0 / yScale, 1.0)";
991 stream() <<
"return m";
996 stream() <<
"fillTransform: calculateFillTransform(__qt_toplevel_scale_itemspy.requiredTextureSize.width, "
997 <<
"__qt_toplevel_scale_itemspy.requiredTextureSize.height)";
999 }
else if (!fillTransform.isIdentity()) {
1000 const QTransform &xf = fillTransform;
1001 stream() <<
"fillTransform: ";
1002 if (info.fillTransform.type() == QTransform::TxTranslate)
1003 stream(SameLine) <<
"PlanarTransform.fromTranslate(" << xf.dx() <<
", " << xf.dy() <<
")";
1004 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
1005 stream(SameLine) <<
"PlanarTransform.fromScale(" << xf.m11() <<
", " << xf.m22() <<
")";
1007 generateTransform(xf);
1010 if (info.trim.enabled) {
1011 stream() <<
"trim.start: " << info.trim.start.defaultValue().toReal();
1012 stream() <<
"trim.end: " << info.trim.end.defaultValue().toReal();
1013 stream() <<
"trim.offset: " << info.trim.offset.defaultValue().toReal();
1017 if (fillRule == QQuickShapePath::WindingFill)
1018 stream() <<
"fillRule: ShapePath.WindingFill";
1020 stream() <<
"fillRule: ShapePath.OddEvenFill";
1024 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
1025 if (!hintStr.isEmpty())
1026 stream() << hintStr;
1028 QQuickAnimatedProperty pathFactor(QVariant::fromValue(0));
1029 QString pathId = shapePathId +
"_ip"_L1;
1030 if (!info.path.isAnimated() || (info.path.animation(0).startOffset == 0 && info.path.animation(0).isConstant())) {
1031 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
1032 stream() <<
"PathSvg { path: \"" << svgPathString <<
"\" }";
1034 stream() <<
"PathInterpolated {";
1036 stream() <<
"id: " << pathId;
1037 stream() <<
"svgPaths: [";
1039 QQuickAnimatedProperty::PropertyAnimation pathFactorAnim = info.path.animation(0);
1040 auto &frames = pathFactorAnim.frames;
1043 for (
auto it = frames.begin(); it != frames.end(); ++it) {
1044 QString svg = QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
1045 if (svg != lastSvg) {
1047 stream(SameLine) <<
",";
1048 stream() <<
"\"" << svg <<
"\"";
1052 *it = QVariant::fromValue(pathIdx);
1054 pathFactor.addAnimation(pathFactorAnim);
1064 if (pathFactor.isAnimated())
1065 generatePropertyAnimation(pathFactor, pathId,
"factor"_L1);
1067 if (info.trim.enabled) {
1068 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"start"));
1069 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"end"));
1070 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"offset"));
1073 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeColor"));
1074 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral(
"strokeColor"), AnimationType::ColorOpacity);
1075 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillColor"));
1076 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral(
"fillColor"), AnimationType::ColorOpacity);
1079void QQuickQmlGenerator::generateNode(
const NodeInfo &info)
1081 if (!isNodeVisible(info))
1084 stream() <<
"// Missing Implementation for SVG Node: " << info.typeName;
1085 stream() <<
"// Adding an empty Item and skipping";
1086 stream() <<
"Item {";
1088 generateNodeBase(info);
1089 generateNodeEnd(info);
1092void QQuickQmlGenerator::generateTextNode(
const TextNodeInfo &info)
1094 if (!isNodeVisible(info))
1097 static int counter = 0;
1098 stream() <<
"Item {";
1100 generateNodeBase(info);
1102 if (!info.isTextArea)
1103 stream() <<
"Item { id: textAlignItem_" << counter <<
"; x: " << info.position.x() <<
"; y: " << info.position.y() <<
"}";
1105 stream() <<
"Text {";
1109 const QString textItemId = QStringLiteral(
"_qt_textItem_%1").arg(counter);
1110 stream() <<
"id: " << textItemId;
1112 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral(
"color"));
1113 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral(
"color"), AnimationType::ColorOpacity);
1114 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral(
"styleColor"));
1115 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral(
"styleColor"), AnimationType::ColorOpacity);
1117 if (info.isTextArea) {
1118 stream() <<
"x: " << info.position.x();
1119 stream() <<
"y: " << info.position.y();
1120 if (info.size.width() > 0)
1121 stream() <<
"width: " << info.size.width();
1122 if (info.size.height() > 0)
1123 stream() <<
"height: " << info.size.height();
1124 stream() <<
"wrapMode: Text.Wrap";
1125 stream() <<
"clip: true";
1127 QString hAlign = QStringLiteral(
"left");
1128 stream() <<
"anchors.baseline: textAlignItem_" << counter <<
".top";
1129 switch (info.alignment) {
1130 case Qt::AlignHCenter:
1131 hAlign = QStringLiteral(
"horizontalCenter");
1133 case Qt::AlignRight:
1134 hAlign = QStringLiteral(
"right");
1137 qCDebug(lcQuickVectorImage) <<
"Unexpected text alignment" << info.alignment;
1142 stream() <<
"anchors." << hAlign <<
": textAlignItem_" << counter <<
".left";
1146 stream() <<
"color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) <<
"\"";
1147 stream() <<
"textFormat:" << (info.needsRichText ?
"Text.RichText" :
"Text.StyledText");
1149 stream() <<
"text: \"" << sanitizeString(info.text) <<
"\"";
1150 stream() <<
"font.family: \"" << sanitizeString(info.font.family()) <<
"\"";
1151 if (info.font.pixelSize() > 0)
1152 stream() <<
"font.pixelSize:" << info.font.pixelSize();
1153 else if (info.font.pointSize() > 0)
1154 stream() <<
"font.pixelSize:" << info.font.pointSizeF();
1155 if (info.font.underline())
1156 stream() <<
"font.underline: true";
1157 if (info.font.weight() != QFont::Normal)
1158 stream() <<
"font.weight: " <<
int(info.font.weight());
1159 if (info.font.italic())
1160 stream() <<
"font.italic: true";
1161 switch (info.font.hintingPreference()) {
1162 case QFont::PreferFullHinting:
1163 stream() <<
"font.hintingPreference: Font.PreferFullHinting";
1165 case QFont::PreferVerticalHinting:
1166 stream() <<
"font.hintingPreference: Font.PreferVerticalHinting";
1168 case QFont::PreferNoHinting:
1169 stream() <<
"font.hintingPreference: Font.PreferNoHinting";
1171 case QFont::PreferDefaultHinting:
1172 stream() <<
"font.hintingPreference: Font.PreferDefaultHinting";
1176 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
1177 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
1178 stream() <<
"styleColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1179 stream() <<
"style: Text.Outline";
1185 generateNodeEnd(info);
1188void QQuickQmlGenerator::generateUseNode(
const UseNodeInfo &info)
1190 if (!isNodeVisible(info))
1193 if (info.stage == StructureNodeStage::Start) {
1194 stream() <<
"Item {";
1196 generateNodeBase(info);
1198 generateNodeEnd(info);
1202void QQuickQmlGenerator::generatePathContainer(
const StructureNodeInfo &info)
1205 stream() << shapeName() <<
" {";
1207 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
1208 stream() <<
"preferredRendererType: Shape.CurveRenderer";
1209 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
1210 stream() <<
"asynchronous: true";
1213 m_inShapeItemLevel++;
1216void QQuickQmlGenerator::generateAnimateMotionPath(
const QString &targetName,
1217 const QQuickAnimatedProperty &property)
1219 if (!property.isAnimated())
1222 Q_ASSERT(property.animationCount() == 1);
1223 const auto &animation = property.animation(0);
1225 qsizetype count = 0;
1226 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it, ++count) {
1227 QPainterPath path = it.value().value<QPainterPath>();
1235 if (path.isEmpty() && it == animation.frames.constBegin()) {
1236 for (
auto jt = std::next(it); jt != animation.frames.constEnd(); ++jt) {
1237 const QPainterPath &nextPath = jt.value().value<QPainterPath>();
1238 if (!nextPath.isEmpty()) {
1239 position = nextPath.pointAtPercent(0.0);
1240 angle = -nextPath.angleAtPercent(0.0);
1245 stream() <<
"QtObject {";
1248 stream() <<
"id: " << targetName <<
"_pathInterpolator_" << count;
1249 stream() <<
"property real x: " << position.x();
1250 stream() <<
"property real y: " << position.y();
1251 stream() <<
"property real angle: " << angle;
1256 }
else if (!path.isEmpty()) {
1257 stream() <<
"PathInterpolator {";
1260 stream() <<
"id: " << targetName <<
"_pathInterpolator_" << count;
1261 const QString svgPathString = QQuickVectorImageGenerator::Utils::toSvgString(path);
1263 stream() <<
"path: Path { PathSvg { path: \"" << svgPathString <<
"\" } }";
1270 const QString mainAnimationId = targetName + QStringLiteral(
"_motion_animation");
1273 if (Q_UNLIKELY(!isRuntimeGenerator()))
1274 prefix = QStringLiteral(
".animations");
1275 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
1277 stream() <<
"SequentialAnimation {";
1280 stream() <<
"id: " << mainAnimationId;
1281 stream() <<
"property var currentInterpolator: " << targetName <<
"_pathInterpolator_0";
1283 generateAnimationBindings();
1285 Q_ASSERT(property.animationCount() == 1);
1287 int previousTime = 0;
1289 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it, ++count) {
1290 const int time = it.key();
1291 const int frameTime = processAnimationTime(time - previousTime);
1292 const QPainterPath path = it.value().value<QPainterPath>();
1293 if (frameTime > 0) {
1294 if (path.isEmpty()) {
1295 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
1297 stream() <<
"ScriptAction {";
1300 stream() <<
"script: {";
1303 stream() << mainAnimationId <<
".currentInterpolator = "
1304 << targetName <<
"_pathInterpolator_" << count;
1312 stream() <<
"PropertyAnimation {";
1315 stream() <<
"id: " << targetName <<
"_motionAnimation_" << count;
1317 stream() <<
"duration: " << frameTime;
1318 stream() <<
"target: " << targetName <<
"_pathInterpolator_" << count;
1319 stream() <<
"property: \"progress\"";
1320 stream() <<
"from: 0; to: 1";
1322 generateEasing(animation, time);
1329 previousTime = time;
1337void QQuickQmlGenerator::generateAnimatedPropertySetter(
const QString &targetName,
1338 const QString &propertyName,
1339 const QVariant &value,
1340 const QQuickAnimatedProperty::PropertyAnimation &animation,
1343 AnimationType animationType)
1345 if (frameTime > 0) {
1346 switch (animationType) {
1347 case AnimationType::Auto:
1348 if (value.typeId() == QMetaType::QColor)
1349 stream() <<
"ColorAnimation {";
1351 stream() <<
"PropertyAnimation {";
1353 case AnimationType::ColorOpacity:
1354 stream() <<
"ColorOpacityAnimation {";
1359 stream() <<
"duration: " << frameTime;
1360 stream() <<
"target: " << targetName;
1361 stream() <<
"property: \"" << propertyName <<
"\"";
1363 if (value.typeId() == QMetaType::QVector3D) {
1364 const QVector3D &v = value.value<QVector3D>();
1365 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1366 }
else if (value.typeId() == QMetaType::QColor) {
1367 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1369 stream(SameLine) << value.toReal();
1371 generateEasing(animation, time);
1375 stream() <<
"ScriptAction {";
1377 stream() <<
"script:" << targetName <<
"." << propertyName;
1378 if (animationType == AnimationType::ColorOpacity)
1379 stream(SameLine) <<
".a";
1381 stream(SameLine) <<
" = ";
1382 if (value.typeId() == QMetaType::QVector3D) {
1383 const QVector3D &v = value.value<QVector3D>();
1384 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1385 }
else if (value.typeId() == QMetaType::QColor) {
1386 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1388 stream(SameLine) << value.toReal();
1395void QQuickQmlGenerator::generateAnimateTransform(
const QString &targetName,
const NodeInfo &info)
1397 if (!info.transform.isAnimated())
1400 const QString mainAnimationId = targetName
1401 + QStringLiteral(
"_transform_animation");
1404 if (Q_UNLIKELY(!isRuntimeGenerator()))
1405 prefix = QStringLiteral(
".animations");
1406 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
1408 stream() <<
"ParallelAnimation {";
1411 stream() <<
"id:" << mainAnimationId;
1413 generateAnimationBindings();
1414 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
1415 int animationStart = info.transform.animationGroup(groupIndex);
1416 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
1417 ? info.transform.animationGroup(groupIndex + 1)
1418 : info.transform.animationCount();
1421 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
1422 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1423 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1425 stream() <<
"SequentialAnimation {";
1428 const int startOffset = processAnimationTime(firstAnimation.startOffset);
1429 if (startOffset > 0)
1430 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
1432 const int repeatCount = firstAnimation.repeatCount;
1433 if (repeatCount < 0)
1434 stream() <<
"loops: Animation.Infinite";
1436 stream() <<
"loops: " << repeatCount;
1439 stream() <<
"ScriptAction {";
1442 stream() <<
"script: " << targetName <<
"_transform_base_group"
1443 <<
".activateOverride(" << targetName <<
"_transform_group_" << groupIndex <<
")";
1449 stream() <<
"ParallelAnimation {";
1452 for (
int i = animationStart; i < nextAnimationStart; ++i) {
1453 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1454 if (animation.isConstant())
1456 bool hasRotationCenter =
false;
1457 if (animation.subtype == QTransform::TxRotate) {
1458 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1459 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1460 if (!center.isNull()) {
1461 hasRotationCenter =
true;
1467 stream() <<
"SequentialAnimation {";
1470 int previousTime = 0;
1471 QVariantList previousParameters;
1472 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1473 const int time = it.key();
1474 const int frameTime = processAnimationTime(time - previousTime);
1475 const QVariantList ¶meters = it.value().value<QVariantList>();
1476 if (parameters.isEmpty())
1479 if (parameters == previousParameters) {
1481 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
1483 stream() <<
"ParallelAnimation {";
1486 const QString propertyTargetName = targetName
1487 + QStringLiteral(
"_transform_")
1488 + QString::number(groupIndex)
1489 + QStringLiteral(
"_")
1490 + QString::number(i);
1492 switch (animation.subtype) {
1493 case QTransform::TxTranslate:
1495 const QPointF translation = parameters.first().value<QPointF>();
1497 generateAnimatedPropertySetter(propertyTargetName,
1498 QStringLiteral(
"x"),
1503 generateAnimatedPropertySetter(propertyTargetName,
1504 QStringLiteral(
"y"),
1511 case QTransform::TxScale:
1513 const QPointF scale = parameters.first().value<QPointF>();
1514 generateAnimatedPropertySetter(propertyTargetName,
1515 QStringLiteral(
"xScale"),
1520 generateAnimatedPropertySetter(propertyTargetName,
1521 QStringLiteral(
"yScale"),
1528 case QTransform::TxRotate:
1530 Q_ASSERT(parameters.size() == 2);
1531 const qreal angle = parameters.value(1).toReal();
1532 if (hasRotationCenter) {
1533 const QPointF center = parameters.value(0).value<QPointF>();
1534 generateAnimatedPropertySetter(propertyTargetName,
1535 QStringLiteral(
"origin"),
1536 QVector3D(center.x(), center.y(), 0.0),
1541 generateAnimatedPropertySetter(propertyTargetName,
1542 QStringLiteral(
"angle"),
1549 case QTransform::TxShear:
1551 const QPointF skew = parameters.first().value<QPointF>();
1553 generateAnimatedPropertySetter(propertyTargetName,
1554 QStringLiteral(
"xAngle"),
1560 generateAnimatedPropertySetter(propertyTargetName,
1561 QStringLiteral(
"yAngle"),
1576 previousTime = time;
1577 previousParameters = parameters;
1589 if (firstAnimation.repeatCount >= 0) {
1590 stream() <<
"ScriptAction {";
1593 stream() <<
"script: {";
1597 stream() << targetName <<
"_transform_base_group.deactivate("
1598 << targetName <<
"_transform_group_" << groupIndex <<
")";
1599 }
else if (!replace) {
1600 stream() << targetName <<
"_transform_base_group.deactivateOverride("
1601 << targetName <<
"_transform_group_" << groupIndex <<
")";
1619bool QQuickQmlGenerator::generateStructureNode(
const StructureNodeInfo &info)
1621 if (!isNodeVisible(info))
1624 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1625 if (info.stage == StructureNodeStage::Start) {
1626 if (!info.clipBox.isEmpty()) {
1627 stream() <<
"Item { // Clip";
1630 stream() <<
"width: " << info.clipBox.width();
1631 stream() <<
"height: " << info.clipBox.height();
1632 stream() <<
"clip: true";
1635 if (isPathContainer) {
1636 generatePathContainer(info);
1637 }
else if (!info.customItemType.isEmpty()) {
1638 stream() << info.customItemType <<
" {";
1640 stream() <<
"Item { // Structure node";
1644 if (!info.viewBox.isEmpty()) {
1645 stream() <<
"transform: [";
1647 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1649 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1650 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1655 generateNodeBase(info);
1657 generateNodeEnd(info);
1658 if (isPathContainer)
1659 m_inShapeItemLevel--;
1661 if (!info.clipBox.isEmpty()) {
1670bool QQuickQmlGenerator::generateMaskNode(
const MaskNodeInfo &info)
1673 if (info.stage == StructureNodeStage::End) {
1675 startDefsSuffixBlock();
1676 stream() <<
"Loader {";
1679 stream() <<
"id: " << info.id;
1680 stream() <<
"sourceComponent: " << info.id <<
"_container";
1681 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
1682 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
1684 stream() <<
"property real maskX: " << info.maskRect.left();
1685 stream() <<
"property real maskY: " << info.maskRect.top();
1686 stream() <<
"property real maskWidth: " << info.maskRect.width();
1687 stream() <<
"property real maskHeight: " << info.maskRect.height();
1689 stream() <<
"function maskRect(otherX, otherY, otherWidth, otherHeight) {";
1692 stream() <<
"return ";
1693 if (info.isMaskRectRelativeCoordinates) {
1696 << info.id <<
".maskX * otherWidth + otherX,"
1697 << info.id <<
".maskY * otherHeight + otherY,"
1698 << info.id <<
".maskWidth * otherWidth,"
1699 << info.id <<
".maskHeight * otherHeight)";
1703 << info.id <<
".maskX, "
1704 << info.id <<
".maskY, "
1705 << info.id <<
".maskWidth, "
1706 << info.id <<
".maskHeight)";
1715 endDefsSuffixBlock();
1721void QQuickQmlGenerator::generateFilterNode(
const FilterNodeInfo &info)
1723 stream() <<
"Item {";
1726 generateNodeBase(info);
1728 stream() <<
"property real originalWidth: filterSourceItem.sourceItem.originalBounds.width";
1729 stream() <<
"property real originalHeight: filterSourceItem.sourceItem.originalBounds.height";
1730 stream() <<
"property rect filterRect: " << info.id <<
"_filterParameters"
1731 <<
".adaptToFilterRect(0, 0, originalWidth, originalHeight)";
1733 for (qsizetype i = 0; i < info.steps.size();)
1734 i = generateFilterStep(info, i);
1737 startDefsSuffixBlock();
1738 stream() <<
"QtObject {";
1741 stream() <<
"id: " << info.id <<
"_filterParameters";
1742 stream() <<
"property int wrapMode: ";
1743 if (info.wrapMode == QSGTexture::Repeat)
1744 stream(SameLine) <<
"ShaderEffectSource.Repeat";
1746 stream(SameLine) <<
"ShaderEffectSource.ClampToEdge";
1748 stream() <<
"property rect filterRect: Qt.rect("
1749 << info.filterRect.x() <<
", "
1750 << info.filterRect.y() <<
", "
1751 << info.filterRect.width() <<
", "
1752 << info.filterRect.height() <<
")";
1754 stream() <<
"function adaptToFilterRect(sx, sy, sw, sh) {";
1757 if (info.csFilterRect == FilterNodeInfo::CoordinateSystem::Absolute) {
1758 stream() <<
"return Qt.rect(filterRect.x, filterRect.y, filterRect.width, filterRect.height)";
1760 stream() <<
"return Qt.rect(sx + sw * filterRect.x, sy + sh * filterRect.y, sw * filterRect.width, sh * filterRect.height)";
1768 endDefsSuffixBlock();
1770 generateNodeEnd(info);
1773qsizetype QQuickQmlGenerator::generateFilterStep(
const FilterNodeInfo &info,
1774 qsizetype stepIndex)
1776 const FilterNodeInfo::FilterStep &step = info.steps.at(stepIndex);
1777 const QString primitiveId = info.id + QStringLiteral(
"_primitive") + QString::number(stepIndex);
1781 QString inputId = step.input1 != FilterNodeInfo::FilterInput::SourceColor
1783 : QStringLiteral(
"filterSourceItem");
1785 bool isComposite =
false;
1786 switch (step.filterType) {
1787 case FilterNodeInfo::Type::Merge:
1789 const int maxNodeCount = 8;
1792 QList<QPair<FilterNodeInfo::FilterInput, QString> > inputs;
1793 for (; stepIndex < info.steps.size(); ++stepIndex) {
1794 const FilterNodeInfo::FilterStep &nodeStep = info.steps.at(stepIndex);
1795 if (nodeStep.filterType != FilterNodeInfo::Type::MergeNode)
1798 inputs.append(qMakePair(nodeStep.input1, nodeStep.namedInput1));
1801 if (inputs.size() > maxNodeCount) {
1802 qCWarning(lcQuickVectorImage) <<
"Maximum of" << maxNodeCount
1803 <<
"nodes exceeded in merge effect.";
1806 if (inputs.isEmpty()) {
1807 qCWarning(lcQuickVectorImage) <<
"Merge effect requires at least one node.";
1811 stream() <<
"ShaderEffect {";
1814 stream() <<
"id: " << primitiveId;
1815 stream() <<
"visible: false";
1817 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb\"";
1818 stream() <<
"width: source1.width";
1819 stream() <<
"height: source1.height";
1820 stream() <<
"property int sourceCount: " << std::min(qsizetype(8), inputs.size());
1822 for (
int i = 0; i < maxNodeCount; ++i) {
1823 auto input = i < inputs.size()
1825 : qMakePair(FilterNodeInfo::FilterInput::None, QStringLiteral(
"null"));
1827 QString inputId = input.first != FilterNodeInfo::FilterInput::SourceColor
1829 : QStringLiteral(
"filterSourceItem");
1831 stream() <<
"property var source" << (i + 1) <<
": " << inputId;
1839 case FilterNodeInfo::Type::CompositeOver:
1840 case FilterNodeInfo::Type::CompositeOut:
1841 case FilterNodeInfo::Type::CompositeIn:
1842 case FilterNodeInfo::Type::CompositeXor:
1843 case FilterNodeInfo::Type::CompositeAtop:
1844 case FilterNodeInfo::Type::CompositeArithmetic:
1845 case FilterNodeInfo::Type::CompositeLighter:
1849 case FilterNodeInfo::Type::BlendNormal:
1850 case FilterNodeInfo::Type::BlendMultiply:
1851 case FilterNodeInfo::Type::BlendScreen:
1852 case FilterNodeInfo::Type::BlendDarken:
1853 case FilterNodeInfo::Type::BlendLighten:
1855 stream() <<
"ShaderEffect {";
1858 QString input2Id = step.input2 != FilterNodeInfo::FilterInput::SourceColor
1860 : QStringLiteral(
"filterSourceItem");
1862 stream() <<
"id: " << primitiveId;
1863 stream() <<
"visible: false";
1866 switch (step.filterType) {
1867 case FilterNodeInfo::Type::CompositeOver:
1868 shader = QStringLiteral(
"fecompositeover");
1870 case FilterNodeInfo::Type::CompositeOut:
1871 shader = QStringLiteral(
"fecompositeout");
1873 case FilterNodeInfo::Type::CompositeIn:
1874 shader = QStringLiteral(
"fecompositein");
1876 case FilterNodeInfo::Type::CompositeXor:
1877 shader = QStringLiteral(
"fecompositexor");
1879 case FilterNodeInfo::Type::CompositeAtop:
1880 shader = QStringLiteral(
"fecompositeatop");
1882 case FilterNodeInfo::Type::CompositeArithmetic:
1883 shader = QStringLiteral(
"fecompositearithmetic");
1885 case FilterNodeInfo::Type::CompositeLighter:
1886 shader = QStringLiteral(
"fecompositelighter");
1888 case FilterNodeInfo::Type::BlendNormal:
1889 shader = QStringLiteral(
"feblendnormal");
1891 case FilterNodeInfo::Type::BlendMultiply:
1892 shader = QStringLiteral(
"feblendmultiply");
1894 case FilterNodeInfo::Type::BlendScreen:
1895 shader = QStringLiteral(
"feblendscreen");
1897 case FilterNodeInfo::Type::BlendDarken:
1898 shader = QStringLiteral(
"feblenddarken");
1900 case FilterNodeInfo::Type::BlendLighten:
1901 shader = QStringLiteral(
"feblendlighten");
1907 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"
1908 << shader <<
".frag.qsb\"";
1909 stream() <<
"property var source: " << inputId;
1910 stream() <<
"property var source2: " << input2Id;
1911 stream() <<
"width: source.width";
1912 stream() <<
"height: source.height";
1915 QVector4D k = step.filterParameter.value<QVector4D>();
1916 stream() <<
"property var k: Qt.vector4d("
1929 case FilterNodeInfo::Type::Flood:
1931 stream() <<
"Rectangle {";
1934 stream() <<
"id: " << primitiveId;
1935 stream() <<
"visible: false";
1937 stream() <<
"width: " << inputId <<
".width";
1938 stream() <<
"height: " << inputId <<
".height";
1940 QColor floodColor = step.filterParameter.value<QColor>();
1941 stream() <<
"color: \"" << floodColor.name(QColor::HexArgb) <<
"\"";
1948 case FilterNodeInfo::Type::ColorMatrix:
1950 stream() <<
"ShaderEffect {";
1953 stream() <<
"id: " << primitiveId;
1954 stream() <<
"visible: false";
1956 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb\"";
1957 stream() <<
"property var source: " << inputId;
1958 stream() <<
"width: source.width";
1959 stream() <<
"height: source.height";
1961 QGenericMatrix<5, 5, qreal> matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
1962 for (
int row = 0; row < 4; ++row) {
1965 for (
int col = 0; col < 5; ++col)
1966 stream() <<
"property real m_" << row <<
"_" << col <<
": " << matrix(col, row);
1975 case FilterNodeInfo::Type::Offset:
1977 stream() <<
"ShaderEffectSource {";
1980 stream() <<
"id: " << primitiveId;
1981 stream() <<
"visible: false";
1982 stream() <<
"sourceItem: " << inputId;
1983 stream() <<
"width: sourceItem.width + offset.x";
1984 stream() <<
"height: sourceItem.height + offset.y";
1986 QVector2D offset = step.filterParameter.value<QVector2D>();
1987 stream() <<
"property vector2d offset: Qt.vector2d(";
1988 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute)
1989 stream(SameLine) << offset.x() <<
" / width, " << offset.y() <<
" / height)";
1991 stream(SameLine) << offset.x() <<
", " << offset.y() <<
")";
1993 stream() <<
"sourceRect: Qt.rect(-offset.x, -offset.y, width, height)";
1995 stream() <<
"ItemSpy {";
1997 stream() <<
"id: " << primitiveId <<
"_offset_itemspy";
1998 stream() <<
"anchors.fill: parent";
2002 stream() <<
"textureSize: " << primitiveId <<
"_offset_itemspy.requiredTextureSize";
2011 case FilterNodeInfo::Type::GaussianBlur:
2014 stream() <<
"MultiEffect {";
2017 stream() <<
"id: " << primitiveId;
2018 stream() <<
"visible: false";
2020 stream() <<
"source: " << inputId;
2021 stream() <<
"blurEnabled: true";
2022 stream() <<
"width: source.width";
2023 stream() <<
"height: source.height";
2025 const qreal maxDeviation(12.0);
2026 const qreal deviation = step.filterParameter.toReal();
2027 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
2028 stream() <<
"blur: Math.min(1.0, " << deviation <<
" * filterSourceItem.width / " << maxDeviation <<
")";
2030 stream() <<
"blur: " << std::min(qreal(1.0), deviation / maxDeviation);
2031 stream() <<
"blurMax: 64";
2039 qCWarning(lcQuickVectorImage) <<
"Unhandled filter type: " <<
int(step.filterType);
2041 stream() <<
"Item { id: " << primitiveId <<
" }";
2046 stream() <<
"ShaderEffectSource {";
2049 stream() <<
"id: " << step.outputName;
2050 if (stepIndex < info.steps.size())
2051 stream() <<
"visible: false";
2053 qreal x1, x2, y1, y2;
2054 step.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
2055 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
2056 stream() <<
"property real fpx1: " << x1;
2057 stream() <<
"property real fpy1: " << y1;
2058 stream() <<
"property real fpx2: " << x2;
2059 stream() <<
"property real fpy2: " << y2;
2060 }
else if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
2064 stream() <<
"property real fpx1: " << x1 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2065 stream() <<
"property real fpy1: " << y1 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2066 stream() <<
"property real fpx2: " << x2 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2067 stream() <<
"property real fpy2: " << y2 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2069 stream() <<
"property real fpx1: parent.filterRect.x";
2070 stream() <<
"property real fpy1: parent.filterRect.y";
2071 stream() <<
"property real fpx2: parent.filterRect.x + parent.filterRect.width";
2072 stream() <<
"property real fpy2: parent.filterRect.y + parent.filterRect.height";
2075 stream() <<
"sourceItem: " << primitiveId;
2076 stream() <<
"sourceRect: Qt.rect(fpx1 - parent.filterRect.x, fpy1 - parent.filterRect.y, width, height)";
2078 stream() <<
"x: fpx1";
2079 stream() <<
"y: fpy1";
2080 stream() <<
"width: " <<
"fpx2 - fpx1";
2081 stream() <<
"height: " <<
"fpy2 - fpy1";
2083 stream() <<
"ItemSpy {";
2085 stream() <<
"id: " << primitiveId <<
"_itemspy";
2086 stream() <<
"anchors.fill: parent";
2090 stream() <<
"textureSize: " << primitiveId <<
"_itemspy.requiredTextureSize";
2098bool QQuickQmlGenerator::generatePatternNode(
const PatternNodeInfo &info)
2100 if (info.stage == StructureNodeStage::Start) {
2103 startDefsSuffixBlock();
2104 stream() <<
"Loader {";
2107 stream() <<
"id: " << info.id;
2108 stream() <<
"sourceComponent: " << info.id <<
"_container";
2109 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
2110 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
2111 stream() <<
"visible: false";
2112 stream() <<
"function sourceRect(targetWidth, targetHeight) {";
2115 stream() <<
"return Qt.rect(0, 0, ";
2116 if (!info.isPatternRectRelativeCoordinates) {
2117 stream(SameLine) << info.patternRect.width() <<
", "
2118 << info.patternRect.height();
2120 stream(SameLine) << info.patternRect.width() <<
" * targetWidth, "
2121 << info.patternRect.height() <<
" * targetHeight";
2123 stream(SameLine) <<
")";
2127 stream() <<
"function sourceOffset(targetWidth, targetHeight) {";
2130 stream() <<
"return Qt.vector3d(";
2131 if (!info.isPatternRectRelativeCoordinates) {
2132 stream(SameLine) << info.patternRect.x() <<
", "
2133 << info.patternRect.y() <<
", ";
2135 stream(SameLine) << info.patternRect.x() <<
" * targetWidth, "
2136 << info.patternRect.y() <<
" * targetHeight, ";
2138 stream(SameLine) <<
"0.0)";
2146 endDefsSuffixBlock();
2152bool QQuickQmlGenerator::generateMarkerNode(
const MarkerNodeInfo &info)
2154 if (info.stage == StructureNodeStage::Start) {
2155 startDefsSuffixBlock();
2156 stream() <<
"QtObject {";
2159 stream() <<
"id: " << info.id <<
"_markerParameters";
2161 stream() <<
"property bool startReversed: ";
2162 if (info.orientation == MarkerNodeInfo::Orientation::AutoStartReverse)
2163 stream(SameLine) <<
"true";
2165 stream(SameLine) <<
"false";
2167 stream() <<
"function autoAngle(adaptedAngle) {";
2169 if (info.orientation == MarkerNodeInfo::Orientation::Value)
2170 stream() <<
"return " << info.angle;
2172 stream() <<
"return adaptedAngle";
2178 endDefsSuffixBlock();
2180 if (!info.clipBox.isEmpty()) {
2181 stream() <<
"Item {";
2184 stream() <<
"x: " << info.clipBox.x();
2185 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2186 stream(SameLine) <<
" * strokeWidth";
2187 stream() <<
"y: " << info.clipBox.y();
2188 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2189 stream(SameLine) <<
" * strokeWidth";
2190 stream() <<
"width: " << info.clipBox.width();
2191 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2192 stream(SameLine) <<
" * strokeWidth";
2193 stream() <<
"height: " << info.clipBox.height();
2194 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2195 stream(SameLine) <<
" * strokeWidth";
2196 stream() <<
"clip: true";
2199 stream() <<
"Item {";
2202 if (!info.clipBox.isEmpty()) {
2203 stream() <<
"x: " << -info.clipBox.x();
2204 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2205 stream(SameLine) <<
" * strokeWidth";
2206 stream() <<
"y: " << -info.clipBox.y();
2207 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2208 stream(SameLine) <<
" * strokeWidth";
2211 stream() <<
"id: " << info.id;
2213 stream() <<
"property real markerWidth: " << info.markerSize.width();
2214 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2215 stream(SameLine) <<
" * strokeWidth";
2217 stream() <<
"property real markerHeight: " << info.markerSize.height();
2218 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2219 stream(SameLine) <<
" * strokeWidth";
2221 stream() <<
"function calculateMarkerScale(w, h) {";
2224 stream() <<
"var scaleX = 1.0";
2225 stream() <<
"var scaleY = 1.0";
2226 stream() <<
"var offsetX = 0.0";
2227 stream() <<
"var offsetY = 0.0";
2228 if (info.viewBox.width() > 0)
2229 stream() <<
"if (w > 0) scaleX = w / " << info.viewBox.width();
2230 if (info.viewBox.height() > 0)
2231 stream() <<
"if (h > 0) scaleY = h / " << info.viewBox.height();
2233 if (info.preserveAspectRatio & MarkerNodeInfo::xyMask) {
2234 stream() <<
"if (scaleX != scaleY) {";
2237 if (info.preserveAspectRatio & MarkerNodeInfo::meet)
2238 stream() <<
"scaleX = scaleY = Math.min(scaleX, scaleY)";
2240 stream() <<
"scaleX = scaleY = Math.max(scaleX, scaleY)";
2242 QString overflowX = QStringLiteral(
"scaleX * %1 - w").arg(info.viewBox.width());
2243 QString overflowY = QStringLiteral(
"scaleY * %1 - h").arg(info.viewBox.height());
2245 const quint8 xRatio = info.preserveAspectRatio & MarkerNodeInfo::xMask;
2246 if (xRatio == MarkerNodeInfo::xMid)
2247 stream() <<
"offsetX -= " << overflowX <<
" / 2";
2248 else if (xRatio == MarkerNodeInfo::xMax)
2249 stream() <<
"offsetX -= " << overflowX;
2251 const quint8 yRatio = info.preserveAspectRatio & MarkerNodeInfo::yMask;
2252 if (yRatio == MarkerNodeInfo::yMid)
2253 stream() <<
"offsetY -= " << overflowY <<
" / 2";
2254 else if (yRatio == MarkerNodeInfo::yMax)
2255 stream() <<
"offsetY -= " << overflowY;
2261 stream() <<
"return Qt.vector4d("
2262 <<
"offsetX - " << info.anchorPoint.x() <<
" * scaleX, "
2263 <<
"offsetY - " << info.anchorPoint.y() <<
" * scaleY, "
2270 stream() <<
"property vector4d markerScale: calculateMarkerScale(markerWidth, markerHeight)";
2272 stream() <<
"transform: [";
2275 stream() <<
"Scale { xScale: " << info.id <<
".markerScale.z; yScale: " << info.id <<
".markerScale.w },";
2276 stream() <<
"Translate { x: " << info.id <<
".markerScale.x; y: " << info.id <<
".markerScale.y }";
2282 generateNodeEnd(info);
2284 if (!info.clipBox.isEmpty()) {
2293bool QQuickQmlGenerator::generateRootNode(
const StructureNodeInfo &info)
2295 const QStringList comments = m_commentString.split(u'\n');
2297 if (!isNodeVisible(info)) {
2300 if (comments.isEmpty()) {
2301 stream() <<
"// Generated from SVG";
2303 for (
const auto &comment : comments)
2304 stream() <<
"// " << comment;
2307 stream() <<
"import QtQuick";
2308 stream() <<
"import QtQuick.Shapes" << Qt::endl;
2309 stream() <<
"Item {";
2312 double w = info.size.width();
2313 double h = info.size.height();
2315 stream() <<
"implicitWidth: " << w;
2317 stream() <<
"implicitHeight: " << h;
2325 if (info.stage == StructureNodeStage::Start) {
2328 if (comments.isEmpty())
2329 stream() <<
"// Generated from SVG";
2331 for (
const auto &comment : comments)
2332 stream() <<
"// " << comment;
2334 stream() <<
"import QtQuick";
2335 stream() <<
"import QtQuick.VectorImage";
2336 stream() <<
"import QtQuick.VectorImage.Helpers";
2337 stream() <<
"import QtQuick.Shapes";
2338 stream() <<
"import QtQuick.Effects";
2340 for (
const auto &import : std::as_const(m_extraImports))
2341 stream() <<
"import " << import;
2343 stream() << Qt::endl <<
"Item {";
2346 double w = info.size.width();
2347 double h = info.size.height();
2349 stream() <<
"implicitWidth: " << w;
2351 stream() <<
"implicitHeight: " << h;
2353 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2354 stream() <<
"component AnimationsInfo : QtObject";
2359 stream() <<
"property bool paused: false";
2360 stream() <<
"property int loops: 1";
2361 stream() <<
"signal restart()";
2363 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2366 stream() <<
"property AnimationsInfo animations : AnimationsInfo {}";
2369 stream() <<
"Item {";
2371 stream() <<
"width: 1";
2372 stream() <<
"height: 1";
2374 stream() <<
"ItemSpy { id: __qt_toplevel_scale_itemspy; anchors.fill: parent }";
2379 if (!info.viewBox.isEmpty()) {
2380 stream() <<
"transform: [";
2382 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
2384 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
2385 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
2390 if (!info.forceSeparatePaths && info.isPathContainer) {
2391 m_topLevelIdString = QStringLiteral(
"__qt_toplevel");
2392 stream() <<
"id: " << m_topLevelIdString;
2394 generatePathContainer(info);
2397 generateNodeBase(info);
2399 m_topLevelIdString = generateNodeBase(info);
2400 if (m_topLevelIdString.isEmpty())
2401 qCWarning(lcQuickVectorImage) <<
"No ID specified for top level item";
2404 if (m_inShapeItemLevel > 0) {
2405 m_inShapeItemLevel--;
2410 for (
const auto [coords, id] : m_easings.asKeyValueRange()) {
2411 stream() <<
"readonly property easingCurve " << id <<
": ({ type: Easing.BezierSpline, bezierCurve: [ ";
2412 for (
auto coord : coords)
2413 stream(SameLine) << coord <<
", ";
2414 stream(SameLine) <<
"1, 1 ] })";
2417 generateNodeEnd(info);
2424void QQuickQmlGenerator::startDefsSuffixBlock()
2426 std::swap(m_indentLevel, m_oldIndentLevel);
2427 m_stream.setString(&m_defsSuffix);
2430void QQuickQmlGenerator::endDefsSuffixBlock()
2432 std::swap(m_indentLevel, m_oldIndentLevel);
2433 m_stream.setDevice(&m_result);
2436QStringView QQuickQmlGenerator::indent()
2438 static QString indentString;
2439 int indentWidth = m_indentLevel * 4;
2440 if (indentWidth > indentString.size())
2441 indentString.fill(QLatin1Char(
' '), indentWidth * 2);
2442 return QStringView(indentString).first(indentWidth);
2445QTextStream &QQuickQmlGenerator::stream(
int flags)
2447 if (m_stream.device() ==
nullptr && m_stream.string() ==
nullptr)
2448 m_stream.setDevice(&m_result);
2449 else if (!(flags & StreamFlags::SameLine))
2450 m_stream << Qt::endl << indent();
2454const char *QQuickQmlGenerator::shapeName()
const
2456 return m_shapeTypeName.isEmpty() ?
"Shape" : m_shapeTypeName.constData();
Combined button and popup list for selecting options.
static QT_BEGIN_NAMESPACE QString sanitizeString(const QString &input)
static int processAnimationTime(int time)