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>
22using namespace Qt::StringLiterals;
27 s.replace(QLatin1Char(
'"'), QLatin1String(
"\\\""));
32QQuickAnimatedProperty::PropertyAnimation QQuickAnimatedProperty::PropertyAnimation::simplified()
const
34 QQuickAnimatedProperty::PropertyAnimation res = *
this;
35 int consecutiveEquals = 0;
36 int prevTimePoint = -1;
38 for (
const auto &[timePoint, value] : frames.asKeyValueRange()) {
39 if (value != prevValue) {
40 consecutiveEquals = 1;
42 }
else if (consecutiveEquals < 2) {
46 res.frames.remove(prevTimePoint);
47 res.easingPerFrame.remove(prevTimePoint);
49 prevTimePoint = timePoint;
55QQuickQmlGenerator::QQuickQmlGenerator(
const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags,
const QString &outFileName)
56 : QQuickGenerator(fileName, flags)
57 , outputFileName(outFileName)
59 m_result.open(QIODevice::ReadWrite);
60 m_oldIndentLevels.push(0);
63QQuickQmlGenerator::~QQuickQmlGenerator()
67bool QQuickQmlGenerator::save()
69 if (Q_UNLIKELY(errorState()))
73 if (!outputFileName.isEmpty()) {
74 QFileInfo fileInfo(outputFileName);
75 QDir dir(fileInfo.absolutePath());
76 if (!dir.exists() && !dir.mkpath(QStringLiteral(
"."))) {
77 qCWarning(lcQuickVectorImage) <<
"Failed to create path" << dir.absolutePath();
80 QFile outFile(outputFileName);
81 if (outFile.open(QIODevice::WriteOnly)) {
82 outFile.write(m_result.data());
85 qCWarning(lcQuickVectorImage) <<
"Failed to write to file" << outFile.fileName();
91 if (lcQuickVectorImage().isDebugEnabled())
92 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
97void QQuickQmlGenerator::setShapeTypeName(
const QString &name)
99 m_shapeTypeName = name.toLatin1();
102QString QQuickQmlGenerator::shapeTypeName()
const
104 return QString::fromLatin1(m_shapeTypeName);
107void QQuickQmlGenerator::setCommentString(
const QString commentString)
109 m_commentString = commentString;
112QString QQuickQmlGenerator::commentString()
const
114 return m_commentString;
117QString QQuickQmlGenerator::generateNodeBase(
const NodeInfo &info,
const QString &idSuffix)
119 static qint64 maxNodes = qEnvironmentVariableIntegerValue(
"QT_QUICKVECTORIMAGE_MAX_NODES").value_or(10000);
120 if (Q_UNLIKELY(!checkSanityLimit(++m_nodeCounter, maxNodes,
"nodes"_L1)))
123 if (!info.nodeId.isEmpty())
124 stream() <<
"objectName: \"" << info.nodeId <<
"\"";
126 if (!info.id.isEmpty())
127 stream() <<
"id: " << info.id << idSuffix;
129 if (!info.bounds.isNull() || !info.boundsReferenceId.isEmpty()) {
130 stream() <<
"property var originalBounds: ";
131 if (!info.bounds.isNull()) {
132 stream(SameLine) <<
"Qt.rect(" << info.bounds.x() <<
", " << info.bounds.y() <<
", "
133 << info.bounds.width() <<
", " << info.bounds.height() <<
")";
135 stream(SameLine) << info.boundsReferenceId <<
".originalBounds";
137 stream() <<
"width: originalBounds.width";
138 stream() <<
"height: originalBounds.height";
141 stream() <<
"transformOrigin: Item.TopLeft";
143 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
144 if (!info.isDefaultOpacity)
145 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
146 generateItemAnimations(info.id, info);
152void QQuickQmlGenerator::generateNodeEnd(
const NodeInfo &info)
154 if (Q_UNLIKELY(errorState()))
158 generateShaderUse(info);
161void QQuickQmlGenerator::generateItemAnimations(
const QString &idString,
const NodeInfo &info)
163 const bool hasTransform = info.transform.isAnimated()
164 || !info.maskId.isEmpty()
165 || !info.filterId.isEmpty()
166 || !info.isDefaultTransform
167 || !info.transformReferenceId.isEmpty()
168 || info.motionPath.isAnimated();
171 stream() <<
"transform: TransformGroup {";
174 bool hasNonConstantTransform =
false;
175 int earliestOverrideGroup = -1;
177 if (!idString.isEmpty()) {
178 stream() <<
"id: " << idString <<
"_transform_base_group";
180 if (!info.maskId.isEmpty() || !info.filterId.isEmpty())
181 stream() <<
"Translate { x: " << idString <<
".sourceX; y: " << idString <<
".sourceY }";
183 if (info.transform.isAnimated()) {
184 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
185 stream() <<
"TransformGroup {";
188 if (!idString.isEmpty())
189 stream() <<
"id: " << idString <<
"_transform_group_" << groupIndex;
191 int animationStart = info.transform.animationGroup(groupIndex);
192 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
193 ? info.transform.animationGroup(groupIndex + 1)
194 : info.transform.animationCount();
196 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
197 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
198 if (replace && earliestOverrideGroup < 0)
199 earliestOverrideGroup = groupIndex;
201 for (
int i = nextAnimationStart - 1; i >= animationStart; --i) {
202 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
203 if (animation.frames.isEmpty())
206 const QVariantList ¶meters = animation.frames.first().value<QVariantList>();
207 switch (animation.subtype) {
208 case QTransform::TxTranslate:
209 if (animation.isConstant()) {
210 const QPointF translation = parameters.value(0).value<QPointF>();
211 if (!translation.isNull())
212 stream() <<
"Translate { x: " << translation.x() <<
"; y: " << translation.y() <<
" }";
214 hasNonConstantTransform =
true;
215 stream() <<
"Translate { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
218 case QTransform::TxScale:
219 if (animation.isConstant()) {
220 const QPointF scale = parameters.value(0).value<QPointF>();
221 if (scale != QPointF(1, 1))
222 stream() <<
"Scale { xScale: " << scale.x() <<
"; yScale: " << scale.y() <<
" }";
224 hasNonConstantTransform =
true;
225 stream() <<
"Scale { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
"}";
228 case QTransform::TxRotate:
229 if (animation.isConstant()) {
230 const QPointF center = parameters.value(0).value<QPointF>();
231 const qreal angle = parameters.value(1).toReal();
232 if (!qFuzzyIsNull(angle))
233 stream() <<
"Rotation { angle: " << angle <<
"; origin.x: " << center.x() <<
"; origin.y: " << center.y() <<
" }";
235 hasNonConstantTransform =
true;
236 stream() <<
"Rotation { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
239 case QTransform::TxShear:
240 if (animation.isConstant()) {
241 const QPointF skew = parameters.value(0).value<QPointF>();
243 stream() <<
"Shear { xAngle: " << skew.x() <<
"; yAngle: " << skew.y() <<
" }";
245 hasNonConstantTransform =
true;
246 stream() <<
"Shear { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
259 if (info.motionPath.isAnimated()) {
260 QVariantList defaultProps = info.motionPath.defaultValue().value<QVariantList>();
261 const bool adaptAngle = defaultProps.value(1).toBool();
262 const qreal baseRotation = defaultProps.value(2).toReal();
263 QString interpolatorId = idString + QStringLiteral(
"_motion_interpolator");
264 if (adaptAngle || !qFuzzyIsNull(baseRotation)) {
265 stream() <<
"Rotation {";
269 stream() <<
"angle: " << interpolatorId <<
".angle";
270 if (!qFuzzyIsNull(baseRotation))
271 stream(SameLine) <<
" + " << baseRotation;
273 stream() <<
"angle: " << baseRotation;
280 stream() <<
"Translate {";
283 stream() <<
"x: " << interpolatorId <<
".x";
284 stream() <<
"y: " << interpolatorId <<
".y";
291 if (!info.isDefaultTransform) {
292 QTransform xf = info.transform.defaultValue().value<QTransform>();
293 if (xf.type() <= QTransform::TxTranslate) {
294 stream() <<
"Translate { x: " << xf.dx() <<
"; y: " << xf.dy() <<
"}";
296 stream() <<
"Matrix4x4 { matrix: ";
297 generateTransform(xf);
298 stream(SameLine) <<
"}";
302 if (!info.transformReferenceId.isEmpty())
303 stream() <<
"Matrix4x4 { matrix: " << info.transformReferenceId <<
".transformMatrix }";
308 if (hasNonConstantTransform) {
309 generateAnimateTransform(idString, info);
310 }
else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
313 stream() <<
"Component.onCompleted: {";
316 stream() << idString <<
"_transform_base_group.activateOverride("
317 << idString <<
"_transform_group_" << earliestOverrideGroup <<
")";
324 generateAnimateMotionPath(idString, info.motionPath);
326 generatePropertyAnimation(info.opacity, idString, QStringLiteral(
"opacity"));
329void QQuickQmlGenerator::generateShaderUse(
const NodeInfo &info)
331 const bool hasMask = !info.maskId.isEmpty();
332 const bool hasFilters = !info.filterId.isEmpty();
333 if (!hasMask && !hasFilters)
336 const QString effectId = hasFilters
337 ? info.filterId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_effect")
340 QString animatedItemId;
342 stream() <<
"ShaderEffectSource {";
345 const QString seId = info.id + QStringLiteral(
"_se");
346 stream() <<
"id: " << seId;
348 stream() <<
"ItemSpy {";
350 stream() <<
"id: " << info.id <<
"_itemspy";
351 stream() <<
"anchors.fill: parent";
355 stream() <<
"hideSource: true";
356 stream() <<
"wrapMode: " << info.filterId <<
"_filterParameters.wrapMode";
357 stream() <<
"sourceItem: " << info.id;
358 stream() <<
"sourceRect: " << info.filterId
359 <<
"_filterParameters.adaptToFilterRect("
360 << info.id <<
".originalBounds.x, "
361 << info.id <<
".originalBounds.y, "
362 << info.id <<
".originalBounds.width, "
363 << info.id <<
".originalBounds.height)";
364 stream() <<
"textureSize: " << info.id <<
"_itemspy.requiredTextureSize";
365 stream() <<
"width: sourceRect.width";
366 stream() <<
"height: sourceRect.height";
367 stream() <<
"visible: false";
372 stream() <<
"Loader {";
375 animatedItemId = effectId;
376 stream() <<
"id: " << effectId;
378 stream() <<
"property var filterSourceItem: " << seId;
379 stream() <<
"sourceComponent: " << info.filterId <<
"_container";
380 stream() <<
"property real sourceX: " << info.id <<
".originalBounds.x";
381 stream() <<
"property real sourceY: " << info.id <<
".originalBounds.y";
382 stream() <<
"width: " << info.id <<
".originalBounds.width";
383 stream() <<
"height: " << info.id <<
".originalBounds.height";
393 stream() <<
"ShaderEffectSource {";
396 const QString maskId = info.maskId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_mask");
397 stream() <<
"id: " << maskId;
398 stream() <<
"sourceItem: " << info.maskId;
399 stream() <<
"visible: false";
400 stream() <<
"hideSource: true";
402 stream() <<
"ItemSpy {";
404 stream() <<
"id: " << maskId <<
"_itemspy";
405 stream() <<
"anchors.fill: parent";
408 stream() <<
"textureSize: " << maskId <<
"_itemspy.requiredTextureSize";
410 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
411 << info.id <<
".originalBounds.x,"
412 << info.id <<
".originalBounds.y,"
413 << info.id <<
".originalBounds.width,"
414 << info.id <<
".originalBounds.height)";
416 stream() <<
"width: sourceRect.width";
417 stream() <<
"height: sourceRect.height";
423 stream() <<
"ShaderEffectSource {";
426 const QString seId = info.id + QStringLiteral(
"_masked_se");
427 stream() <<
"id: " << seId;
429 stream() <<
"ItemSpy {";
431 stream() <<
"id: " << info.id <<
"_masked_se_itemspy";
432 stream() <<
"anchors.fill: parent";
436 stream() <<
"hideSource: true";
438 stream() <<
"sourceItem: " << effectId;
440 stream() <<
"sourceItem: " << info.id;
441 stream() <<
"textureSize: " << info.id <<
"_masked_se_itemspy.requiredTextureSize";
443 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
444 << info.id <<
".originalBounds.x,"
445 << info.id <<
".originalBounds.y,"
446 << info.id <<
".originalBounds.width,"
447 << info.id <<
".originalBounds.height)";
449 stream() <<
"sourceRect: " << info.maskId <<
".maskRect(0, 0,"
450 << info.id <<
".originalBounds.width,"
451 << info.id <<
".originalBounds.height)";
453 stream() <<
"width: sourceRect.width";
454 stream() <<
"height: sourceRect.height";
455 stream() <<
"smooth: false";
456 stream() <<
"visible: false";
461 stream() <<
"ShaderEffect {";
464 const QString maskShaderId = maskId + QStringLiteral(
"_se");
465 animatedItemId = maskShaderId;
467 stream() <<
"id:" << maskShaderId;
469 stream() <<
"property real sourceX: " << maskId <<
".sourceRect.x";
470 stream() <<
"property real sourceY: " << maskId <<
".sourceRect.y";
471 stream() <<
"width: " << maskId <<
".sourceRect.width";
472 stream() <<
"height: " << maskId <<
".sourceRect.height";
474 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
475 stream() <<
"property var source: " << seId;
476 stream() <<
"property var maskSource: " << maskId;
477 stream() <<
"property bool isAlpha: " << (info.isMaskAlpha ?
"true" :
"false");
478 stream() <<
"property bool isInverted: " << (info.isMaskInverted ?
"true" :
"false");
481 if (!info.isDefaultOpacity)
482 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
484 generateItemAnimations(animatedItemId, info);
490bool QQuickQmlGenerator::generateDefsNode(
const StructureNodeInfo &info)
492 if (Q_UNLIKELY(errorState()))
495 if (info.stage == StructureNodeStage::Start) {
496 m_oldIndentLevels.push(m_indentLevel);
498 stream() <<
"Component {";
501 stream() <<
"id: " << info.id <<
"_container";
503 stream() <<
"Item {";
506 generateTimelineFields(info);
507 if (!info.transformReferenceChildId.isEmpty()) {
508 stream() <<
"property alias transformMatrix: "
509 << info.transformReferenceChildId <<
".transformMatrix";
512 generateNodeBase(info, QStringLiteral(
"_defs"));
514 generateNodeEnd(info);
519 stream() << m_defsSuffix;
520 m_defsSuffix.clear();
522 m_indentLevel = m_oldIndentLevels.pop();
528void QQuickQmlGenerator::generateImageNode(
const ImageNodeInfo &info)
530 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
533 const QFileInfo outputFileInfo(outputFileName);
534 const QDir outputDir(outputFileInfo.absolutePath());
538 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
539 filePath = m_assetFileDirectory;
540 if (filePath.isEmpty())
541 filePath = outputDir.absolutePath();
543 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
546 QDir fileDir(filePath);
547 if (!fileDir.exists()) {
548 if (!fileDir.mkpath(QStringLiteral(
".")))
549 qCWarning(lcQuickVectorImage) <<
"Failed to create image resource directory:" << filePath;
552 filePath += QStringLiteral(
"%1%2.png").arg(m_assetFilePrefix.isEmpty()
553 ? QStringLiteral(
"svg_asset_")
555 .arg(info.image.cacheKey());
557 if (!info.image.save(filePath))
558 qCWarning(lcQuickVectorImage) <<
"Unabled to save image resource" << filePath;
559 qCDebug(lcQuickVectorImage) <<
"Saving copy of IMAGE" << filePath;
561 filePath = info.externalFileReference;
564 const QFileInfo assetFileInfo(filePath);
566 stream() <<
"Image {";
569 generateNodeBase(info);
570 stream() <<
"x: " << info.rect.x();
571 stream() <<
"y: " << info.rect.y();
572 stream() <<
"width: " << info.rect.width();
573 stream() <<
"height: " << info.rect.height();
574 stream() <<
"source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<
"\"";
575 generateNodeEnd(info);
578void QQuickQmlGenerator::generateMarkers(
const PathNodeInfo &info)
580 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
581 for (
int i = 0; i < path.elementCount(); ++i) {
582 const QPainterPath::Element element = path.elementAt(i);
587 auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
588 QPointF t1 = p1 - p0;
589 QPointF t2 = p2 - p1;
590 qreal hyp1 = hypot(t1.x(), t1.y());
595 qreal hyp2 = hypot(t2.x(), t2.y());
600 QPointF tangent = t1 + t2;
601 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
605 markerId = info.markerStartId;
606 angle = path.angleAtPercent(0.0);
607 }
else if (i == path.elementCount() - 1) {
608 markerId = info.markerEndId;
609 angle = path.angleAtPercent(1.0);
610 }
else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
611 markerId = info.markerMidId;
613 const QPainterPath::Element prevElement = path.elementAt(i - 1);
614 const QPainterPath::Element nextElement = path.elementAt(i + 1);
616 QPointF p1(prevElement.x, prevElement.y);
617 QPointF p2(element.x, element.y);
618 QPointF p3(nextElement.x, nextElement.y);
620 angle = getMeanAngle(p1, p2, p3);
623 if (!markerId.isEmpty()) {
624 stream() <<
"Loader {";
628 stream() <<
"sourceComponent: " << markerId <<
"_container";
629 stream() <<
"property real strokeWidth: " << info.strokeStyle.width;
630 stream() <<
"transform: [";
633 stream() <<
"Scale { "
634 <<
"xScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1; "
635 <<
"yScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1 },";
637 stream() <<
"Rotation { angle: " << markerId <<
"_markerParameters.autoAngle(" << -angle <<
") },";
638 stream() <<
"Translate { x: " << element.x <<
"; y: " << element.y <<
"}";
649void QQuickQmlGenerator::generatePath(
const PathNodeInfo &info,
const QRectF &overrideBoundingRect)
651 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
654 if (m_inShapeItemLevel > 0) {
655 if (!info.isDefaultTransform)
656 qWarning() <<
"Skipped transform for node" << info.nodeId <<
"type" << info.typeName <<
"(this is not supposed to happen)";
657 optimizePaths(info, overrideBoundingRect);
659 m_inShapeItemLevel++;
660 stream() << shapeName() <<
" {";
663 generateNodeBase(info);
665 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
666 stream() <<
"preferredRendererType: Shape.CurveRenderer";
667 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
668 stream() <<
"asynchronous: true";
669 optimizePaths(info, overrideBoundingRect);
672 if (!info.markerStartId.isEmpty()
673 || !info.markerMidId.isEmpty()
674 || !info.markerEndId.isEmpty()) {
675 generateMarkers(info);
678 generateNodeEnd(info);
679 m_inShapeItemLevel--;
683void QQuickQmlGenerator::generateGradient(
const QGradient *grad,
684 const QString &propertyName,
685 const QRectF &coordinateConversion)
687 const QSizeF &scale = coordinateConversion.size();
688 const QPointF &translation = coordinateConversion.topLeft();
690 if (grad->type() == QGradient::LinearGradient) {
691 auto *linGrad =
static_cast<
const QLinearGradient *>(grad);
692 stream() << propertyName <<
": LinearGradient {";
695 QRectF gradRect(linGrad->start(), linGrad->finalStop());
697 stream() <<
"x1: " << (gradRect.left() * scale.width()) + translation.x();
698 stream() <<
"y1: " << (gradRect.top() * scale.height()) + translation.y();
699 stream() <<
"x2: " << (gradRect.right() * scale.width()) + translation.x();
700 stream() <<
"y2: " << (gradRect.bottom() * scale.height()) + translation.y();
701 for (
auto &stop : linGrad->stops())
702 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
703 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
704 }
else if (grad->type() == QGradient::RadialGradient) {
705 auto *radGrad =
static_cast<
const QRadialGradient*>(grad);
706 stream() << propertyName <<
": RadialGradient {";
709 stream() <<
"centerX: " << (radGrad->center().x() * scale.width()) + translation.x();
710 stream() <<
"centerY: " << (radGrad->center().y() * scale.height()) + translation.y();
711 stream() <<
"centerRadius: " << (radGrad->radius() * scale.width());
712 stream() <<
"focalX:" << (radGrad->focalPoint().x() * scale.width()) + translation.x();
713 stream() <<
"focalY:" << (radGrad->focalPoint().y() * scale.height()) + translation.y();
714 for (
auto &stop : radGrad->stops())
715 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
716 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
719 stream() <<
"spread: ShapeGradient.";
720 switch (grad->spread()) {
721 case QGradient::PadSpread:
722 stream(SameLine) <<
"PadSpread";
724 case QGradient::ReflectSpread:
725 stream(SameLine) <<
"ReflectSpread";
727 case QGradient::RepeatSpread:
728 stream(SameLine) <<
"RepeatSpread";
736void QQuickQmlGenerator::generateAnimationBindings()
739 if (Q_UNLIKELY(!isRuntimeGenerator()))
740 prefix = QStringLiteral(
".animations");
742 stream() <<
"loops: " << m_topLevelIdString << prefix <<
".loops";
743 stream() <<
"paused: " << m_topLevelIdString << prefix <<
".paused";
744 stream() <<
"running: true";
747 stream() <<
"onLoopsChanged: { if (running) { restart() } }";
750void QQuickQmlGenerator::generateEasing(
const QQuickAnimatedProperty::PropertyAnimation &animation,
751 int time,
int streamFlags)
753 if (animation.easingPerFrame.contains(time)) {
754 QBezier bezier = animation.easingPerFrame.value(time);
755 QPointF c1 = bezier.pt2();
756 QPointF c2 = bezier.pt3();
758 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
760 int nextIdx = m_easings.size();
761 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
763 id = QString(QLatin1String(
"easing_%1")).arg(nextIdx, 2, 10, QLatin1Char(
'0'));
764 if (streamFlags & SameLine)
765 stream(streamFlags) <<
"; ";
766 stream(streamFlags) <<
"easing: " << m_topLevelIdString <<
"." << id;
773 static qreal multiplier = qreal(qEnvironmentVariable(
"QT_QUICKVECTORIMAGE_TIME_DILATION", QStringLiteral(
"1.0"))
775 return std::round(multiplier * time);
778void QQuickQmlGenerator::generatePropertyAnimation(
const QQuickAnimatedProperty &property,
779 const QString &targetName,
780 const QString &propertyName,
781 AnimationType animationType)
783 if (!property.isAnimated())
786 if (usingTimelineAnimation())
787 return generatePropertyTimeline(property, targetName, propertyName, animationType);
789 QString mainAnimationId = targetName
790 + QStringLiteral(
"_")
792 + QStringLiteral(
"_animation");
793 mainAnimationId.replace(QLatin1Char(
'.'), QLatin1Char(
'_'));
796 if (Q_UNLIKELY(!isRuntimeGenerator()))
797 prefix = QStringLiteral(
".animations");
799 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
801 stream() <<
"ParallelAnimation {";
804 stream() <<
"id: " << mainAnimationId;
806 generateAnimationBindings();
808 for (
int i = 0; i < property.animationCount(); ++i) {
809 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
811 stream() <<
"SequentialAnimation {";
814 const int startOffset = processAnimationTime(animation.startOffset);
816 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
818 stream() <<
"SequentialAnimation {";
821 const int repeatCount = animation.repeatCount;
823 stream() <<
"loops: Animation.Infinite";
825 stream() <<
"loops: " << repeatCount;
827 int previousTime = 0;
828 QVariant previousValue;
829 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
830 const int time = it.key();
831 const int frameTime = processAnimationTime(time - previousTime);
832 const QVariant &value = it.value();
834 if (previousValue.isValid() && previousValue == value) {
836 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
837 }
else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
840 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
841 stream() <<
"ScriptAction {";
844 stream() <<
"script:" << targetName <<
"." << propertyName <<
" = " << value.toString();
849 generateAnimatedPropertySetter(targetName,
859 previousValue = value;
862 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
863 stream() <<
"ScriptAction {";
865 stream() <<
"script: ";
867 switch (animationType) {
868 case AnimationType::Auto:
869 stream(SameLine) << targetName <<
"." << propertyName <<
" = ";
871 case AnimationType::ColorOpacity:
872 stream(SameLine) << targetName <<
"." << propertyName <<
".a = ";
876 QVariant value = property.defaultValue();
877 if (value.typeId() == QMetaType::QColor)
878 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
880 stream(SameLine) << value.toReal();
897void QQuickQmlGenerator::generateTimelinePropertySetter(
898 const QString &targetName,
899 const QString &propertyName,
900 const QQuickAnimatedProperty::PropertyAnimation &animation,
901 std::function<QVariant(
const QVariant &)>
const& extractValue,
904 if (animation.repeatCount != 1 || animation.startOffset
905 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
906 qCWarning(lcQuickVectorImage) <<
"Animation feature not implemented in timeline mode, for"
907 << targetName << propertyName;
910 stream() <<
"KeyframeGroup {";
912 stream() <<
"target: " << targetName;
913 stream() <<
"property: \"" << propertyName <<
"\"";
915 for (
const auto &[frame, rawValue] : animation.frames.asKeyValueRange()) {
917 if (rawValue.typeId() == QMetaType::QVariantList)
918 value = extractValue(rawValue.toList().value(valueIndex));
920 value = extractValue(rawValue);
922 stream() <<
"Keyframe { frame: " << frame <<
"; value: ";
923 if (value.typeId() == QMetaType::QVector3D) {
924 const QVector3D &v = value.value<QVector3D>();
925 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
926 }
else if (value.typeId() == QMetaType::QColor) {
927 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
929 stream(SameLine) << value.toReal();
931 generateEasing(animation, frame, SameLine);
932 stream(SameLine) <<
" }";
939void QQuickQmlGenerator::generatePropertyTimeline(
const QQuickAnimatedProperty &property,
940 const QString &targetName,
941 const QString &propertyName,
942 AnimationType animationType)
944 if (animationType == QQuickQmlGenerator::AnimationType::ColorOpacity) {
945 qCWarning(lcQuickVectorImage) <<
"ColorOpacity animation not available in timeline mode";
949 if (property.animationGroupCount() > 1 || property.animationCount() > 1) {
950 qCWarning(lcQuickVectorImage) <<
"Property feature not implemented in timeline mode, for"
951 << targetName << propertyName;
954 stream() <<
"Timeline {";
956 stream() <<
"currentFrame: " << property.timelineReferenceId() <<
".frameCounter";
957 stream() <<
"enabled: true";
959 auto extractor = [](
const QVariant &value) {
return value; };
960 generateTimelinePropertySetter(targetName, propertyName, property.animation(0), extractor);
966void QQuickQmlGenerator::generateTransform(
const QTransform &xf)
969 stream(SameLine) <<
"PlanarTransform.fromAffineMatrix("
970 << xf.m11() <<
", " << xf.m12() <<
", "
971 << xf.m21() <<
", " << xf.m22() <<
", "
972 << xf.dx() <<
", " << xf.dy() <<
")";
975 stream(SameLine) <<
"Qt.matrix4x4(";
977 const auto *data = m.data();
978 for (
int i = 0; i < 4; i++) {
979 stream() << data[i] <<
", " << data[i+4] <<
", " << data[i+8] <<
", " << data[i+12];
981 stream(SameLine) <<
", ";
983 stream(SameLine) <<
")";
988void QQuickQmlGenerator::outputShapePath(
const PathNodeInfo &info,
const QPainterPath *painterPath,
const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector,
const QRectF &boundingRect)
990 Q_UNUSED(pathSelector)
991 Q_ASSERT(painterPath || quadPath);
993 if (Q_UNLIKELY(errorState()))
996 const bool invalidGradientBounds = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
997 && (qFuzzyIsNull(boundingRect.width()) ||
998 qFuzzyIsNull(boundingRect.height()));
1000 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
1001 const bool noPen = (strokeColor == QColorConstants::Transparent || !strokeColor.isValid())
1002 && !info.strokeStyle.color.isAnimated()
1003 && !info.strokeStyle.opacity.isAnimated()
1004 && (info.strokeGrad.type() == QGradient::NoGradient
1005 || invalidGradientBounds);
1006 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
1009 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
1010 const bool noFill = info.grad.type() == QGradient::NoGradient
1011 && fillColor == QColorConstants::Transparent
1012 && !info.fillColor.isAnimated()
1013 && !info.fillOpacity.isAnimated();
1014 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
1017 if (noPen && noFill)
1019 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
1020 stream() <<
"ShapePath {";
1023 QString shapePathId = info.id;
1024 if (pathSelector & QQuickVectorImageGenerator::FillPath)
1025 shapePathId += QStringLiteral(
"_fill");
1026 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
1027 shapePathId += QStringLiteral(
"_stroke");
1029 stream() <<
"id: " << shapePathId;
1031 if (!info.nodeId.isEmpty()) {
1032 switch (pathSelector) {
1033 case QQuickVectorImageGenerator::FillPath:
1034 stream() <<
"objectName: \"svg_fill_path:" << info.nodeId <<
"\"";
1036 case QQuickVectorImageGenerator::StrokePath:
1037 stream() <<
"objectName: \"svg_stroke_path:" << info.nodeId <<
"\"";
1039 case QQuickVectorImageGenerator::FillAndStroke:
1040 stream() <<
"objectName: \"svg_path:" << info.nodeId <<
"\"";
1045 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
1046 stream() <<
"strokeColor: \"transparent\"";
1048 if (info.strokeGrad.type() != QGradient::NoGradient && !invalidGradientBounds) {
1049 QRectF coordinateSys = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
1051 : QRectF(0.0, 0.0, 1.0, 1.0);
1052 generateGradient(&info.strokeGrad, QStringLiteral(
"strokeGradient"), coordinateSys);
1053 }
else if (info.strokeStyle.opacity.isAnimated()) {
1054 stream() <<
"property color strokeBase: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1055 stream() <<
"property real strokeOpacity: " << info.strokeStyle.opacity.defaultValue().toReal();
1056 stream() <<
"strokeColor: Qt.rgba(strokeBase.r, strokeBase.g, strokeBase.b, strokeOpacity)";
1058 stream() <<
"strokeColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1060 stream() <<
"strokeWidth: " << info.strokeStyle.width;
1061 stream() <<
"capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
1062 stream() <<
"joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
1063 stream() <<
"miterLimit: " << info.strokeStyle.miterLimit;
1064 if (info.strokeStyle.cosmetic)
1065 stream() <<
"cosmeticStroke: true";
1066 if (info.strokeStyle.dashArray.length() != 0) {
1067 stream() <<
"strokeStyle: " <<
"ShapePath.DashLine";
1068 stream() <<
"dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
1069 stream() <<
"dashOffset: " << info.strokeStyle.dashOffset;
1073 QTransform fillTransform = info.fillTransform;
1074 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
1075 stream() <<
"fillColor: \"transparent\"";
1076 }
else if (info.grad.type() != QGradient::NoGradient) {
1077 generateGradient(&info.grad, QStringLiteral(
"fillGradient"));
1080 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
1081 QTransform objectToUserSpace;
1082 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
1083 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
1084 fillTransform *= objectToUserSpace;
1087 if (info.fillOpacity.isAnimated()) {
1088 stream() <<
"property color fillBase: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
1089 stream() <<
"property real fillOpacity:" << info.fillOpacity.defaultValue().toReal();
1090 stream() <<
"fillColor: Qt.rgba(fillBase.r, fillBase.g, fillBase.b, fillOpacity)";
1092 stream() <<
"fillColor: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
1096 if (!info.patternId.isEmpty()) {
1097 stream() <<
"fillItem: ShaderEffectSource {";
1100 stream() <<
"parent: " << info.id;
1101 stream() <<
"sourceItem: " << info.patternId;
1102 stream() <<
"hideSource: true";
1103 stream() <<
"visible: false";
1104 stream() <<
"width: " << info.patternId <<
".width";
1105 stream() <<
"height: " << info.patternId <<
".height";
1106 stream() <<
"wrapMode: ShaderEffectSource.Repeat";
1107 stream() <<
"textureSize: Qt.size(width * __qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1108 <<
"height * __qt_toplevel_scale_itemspy.requiredTextureSize.height)";;
1109 stream() <<
"sourceRect: " << info.patternId <<
".sourceRect("
1110 << info.id <<
".width, "
1111 << info.id <<
".height)";
1118 stream() <<
"function calculateFillTransform(xScale, yScale) {";
1121 stream() <<
"var m = ";
1122 generateTransform(fillTransform);
1124 stream() <<
"m.translate(" << info.patternId <<
".sourceOffset("
1125 << info.id <<
".width, "
1126 << info.id <<
".height))";
1128 stream() <<
"m.scale(1.0 / xScale, 1.0 / yScale, 1.0)";
1129 stream() <<
"return m";
1134 stream() <<
"fillTransform: calculateFillTransform(__qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1135 <<
"__qt_toplevel_scale_itemspy.requiredTextureSize.height)";
1137 }
else if (!fillTransform.isIdentity()) {
1138 const QTransform &xf = fillTransform;
1139 stream() <<
"fillTransform: ";
1140 if (info.fillTransform.type() == QTransform::TxTranslate)
1141 stream(SameLine) <<
"PlanarTransform.fromTranslate(" << xf.dx() <<
", " << xf.dy() <<
")";
1142 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
1143 stream(SameLine) <<
"PlanarTransform.fromScale(" << xf.m11() <<
", " << xf.m22() <<
")";
1145 generateTransform(xf);
1148 if (info.trim.enabled) {
1149 stream() <<
"trim.start: " << info.trim.start.defaultValue().toReal();
1150 stream() <<
"trim.end: " << info.trim.end.defaultValue().toReal();
1151 stream() <<
"trim.offset: " << info.trim.offset.defaultValue().toReal();
1155 if (fillRule == QQuickShapePath::WindingFill)
1156 stream() <<
"fillRule: ShapePath.WindingFill";
1158 stream() <<
"fillRule: ShapePath.OddEvenFill";
1162 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
1163 if (!hintStr.isEmpty())
1164 stream() << hintStr;
1166 QQuickAnimatedProperty pathFactor(QVariant::fromValue(0));
1167 pathFactor.setTimelineReferenceId(info.path.timelineReferenceId());
1168 QString pathId = shapePathId +
"_ip"_L1;
1169 if (!info.path.isAnimated() || (info.path.animation(0).startOffset == 0 && info.path.animation(0).isConstant())) {
1170 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
1171 stream() <<
"PathSvg { path: \"" << svgPathString <<
"\" }";
1173 stream() <<
"PathInterpolated {";
1175 stream() <<
"id: " << pathId;
1176 stream() <<
"svgPaths: [";
1178 QQuickAnimatedProperty::PropertyAnimation pathFactorAnim = info.path.animation(0);
1179 auto &frames = pathFactorAnim.frames;
1182 for (
auto it = frames.begin(); it != frames.end(); ++it) {
1183 QString svg = QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
1184 if (svg != lastSvg) {
1186 stream(SameLine) <<
",";
1187 stream() <<
"\"" << svg <<
"\"";
1191 *it = QVariant::fromValue(pathIdx);
1193 pathFactor.addAnimation(pathFactorAnim);
1203 if (pathFactor.isAnimated())
1204 generatePropertyAnimation(pathFactor, pathId,
"factor"_L1);
1206 if (info.trim.enabled) {
1207 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"start"));
1208 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"end"));
1209 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"offset"));
1212 if (info.strokeStyle.opacity.isAnimated()) {
1213 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeBase"));
1214 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral(
"strokeOpacity"));
1216 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeColor"));
1218 if (info.fillOpacity.isAnimated()) {
1219 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillBase"));
1220 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral(
"fillOpacity"));
1222 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillColor"));
1226void QQuickQmlGenerator::generateNode(
const NodeInfo &info)
1228 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1231 stream() <<
"// Missing Implementation for SVG Node: " << info.typeName;
1232 stream() <<
"// Adding an empty Item and skipping";
1233 stream() <<
"Item {";
1235 generateNodeBase(info);
1236 generateNodeEnd(info);
1239void QQuickQmlGenerator::generateTextNode(
const TextNodeInfo &info)
1241 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1244 static int counter = 0;
1245 stream() <<
"Item {";
1247 generateNodeBase(info);
1249 if (!info.isTextArea)
1250 stream() <<
"Item { id: textAlignItem_" << counter <<
"; x: " << info.position.x() <<
"; y: " << info.position.y() <<
"}";
1252 stream() <<
"Text {";
1256 const QString textItemId = QStringLiteral(
"_qt_textItem_%1").arg(counter);
1257 stream() <<
"id: " << textItemId;
1259 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral(
"color"));
1260 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral(
"color"), AnimationType::ColorOpacity);
1261 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral(
"styleColor"));
1262 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral(
"styleColor"), AnimationType::ColorOpacity);
1264 if (info.isTextArea) {
1265 stream() <<
"x: " << info.position.x();
1266 stream() <<
"y: " << info.position.y();
1267 if (info.size.width() > 0)
1268 stream() <<
"width: " << info.size.width();
1269 if (info.size.height() > 0)
1270 stream() <<
"height: " << info.size.height();
1271 stream() <<
"wrapMode: Text.Wrap";
1272 stream() <<
"clip: true";
1274 QString hAlign = QStringLiteral(
"left");
1275 stream() <<
"anchors.baseline: textAlignItem_" << counter <<
".top";
1276 switch (info.alignment) {
1277 case Qt::AlignHCenter:
1278 hAlign = QStringLiteral(
"horizontalCenter");
1280 case Qt::AlignRight:
1281 hAlign = QStringLiteral(
"right");
1284 qCDebug(lcQuickVectorImage) <<
"Unexpected text alignment" << info.alignment;
1289 stream() <<
"anchors." << hAlign <<
": textAlignItem_" << counter <<
".left";
1293 stream() <<
"color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) <<
"\"";
1294 stream() <<
"textFormat:" << (info.needsRichText ?
"Text.RichText" :
"Text.StyledText");
1296 stream() <<
"text: \"" << sanitizeString(info.text) <<
"\"";
1297 stream() <<
"font.family: \"" << sanitizeString(info.font.family()) <<
"\"";
1298 if (info.font.pixelSize() > 0)
1299 stream() <<
"font.pixelSize:" << info.font.pixelSize();
1300 else if (info.font.pointSize() > 0)
1301 stream() <<
"font.pixelSize:" << info.font.pointSizeF();
1302 if (info.font.underline())
1303 stream() <<
"font.underline: true";
1304 if (info.font.weight() != QFont::Normal)
1305 stream() <<
"font.weight: " <<
int(info.font.weight());
1306 if (info.font.italic())
1307 stream() <<
"font.italic: true";
1308 switch (info.font.hintingPreference()) {
1309 case QFont::PreferFullHinting:
1310 stream() <<
"font.hintingPreference: Font.PreferFullHinting";
1312 case QFont::PreferVerticalHinting:
1313 stream() <<
"font.hintingPreference: Font.PreferVerticalHinting";
1315 case QFont::PreferNoHinting:
1316 stream() <<
"font.hintingPreference: Font.PreferNoHinting";
1318 case QFont::PreferDefaultHinting:
1319 stream() <<
"font.hintingPreference: Font.PreferDefaultHinting";
1323 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
1324 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
1325 stream() <<
"styleColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1326 stream() <<
"style: Text.Outline";
1332 generateNodeEnd(info);
1335void QQuickQmlGenerator::generateUseNode(
const UseNodeInfo &info)
1337 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1340 if (info.stage == StructureNodeStage::Start) {
1341 stream() <<
"Item {";
1343 generateNodeBase(info);
1345 generateNodeEnd(info);
1349void QQuickQmlGenerator::generatePathContainer(
const StructureNodeInfo &info)
1352 stream() << shapeName() <<
" {";
1354 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
1355 stream() <<
"preferredRendererType: Shape.CurveRenderer";
1356 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
1357 stream() <<
"asynchronous: true";
1360 m_inShapeItemLevel++;
1363void QQuickQmlGenerator::generateAnimateMotionPath(
const QString &targetName,
1364 const QQuickAnimatedProperty &property)
1366 if (!property.isAnimated())
1369 QPainterPath path = property.defaultValue().value<QVariantList>().value(0).value<QPainterPath>();
1370 const QString mainAnimationId = targetName + QStringLiteral(
"_motion_interpolator");
1371 stream() <<
"PathInterpolator {";
1373 stream() <<
"id: " << mainAnimationId;
1374 const QString svgPathString = QQuickVectorImageGenerator::Utils::toSvgString(path);
1375 stream() <<
"path: Path { PathSvg { path: \"" << svgPathString <<
"\" } }";
1379 generatePropertyAnimation(property, mainAnimationId, QStringLiteral(
"progress"));
1382void QQuickQmlGenerator::generateAnimatedPropertySetter(
const QString &targetName,
1383 const QString &propertyName,
1384 const QVariant &value,
1385 const QQuickAnimatedProperty::PropertyAnimation &animation,
1388 AnimationType animationType)
1390 if (frameTime > 0) {
1391 switch (animationType) {
1392 case AnimationType::Auto:
1393 if (value.typeId() == QMetaType::QColor)
1394 stream() <<
"ColorAnimation {";
1396 stream() <<
"PropertyAnimation {";
1398 case AnimationType::ColorOpacity:
1399 stream() <<
"ColorOpacityAnimation {";
1404 stream() <<
"duration: " << frameTime;
1405 stream() <<
"target: " << targetName;
1406 stream() <<
"property: \"" << propertyName <<
"\"";
1408 if (value.typeId() == QMetaType::QVector3D) {
1409 const QVector3D &v = value.value<QVector3D>();
1410 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1411 }
else if (value.typeId() == QMetaType::QColor) {
1412 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1414 stream(SameLine) << value.toReal();
1416 generateEasing(animation, time);
1420 stream() <<
"ScriptAction {";
1422 stream() <<
"script:" << targetName <<
"." << propertyName;
1423 if (animationType == AnimationType::ColorOpacity)
1424 stream(SameLine) <<
".a";
1426 stream(SameLine) <<
" = ";
1427 if (value.typeId() == QMetaType::QVector3D) {
1428 const QVector3D &v = value.value<QVector3D>();
1429 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1430 }
else if (value.typeId() == QMetaType::QColor) {
1431 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1433 stream(SameLine) << value.toReal();
1440void QQuickQmlGenerator::generateAnimateTransform(
const QString &targetName,
const NodeInfo &info)
1442 if (!info.transform.isAnimated())
1445 if (usingTimelineAnimation())
1446 return generateTransformTimeline(targetName, info);
1448 const QString mainAnimationId = targetName
1449 + QStringLiteral(
"_transform_animation");
1452 if (Q_UNLIKELY(!isRuntimeGenerator()))
1453 prefix = QStringLiteral(
".animations");
1454 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
1456 stream() <<
"ParallelAnimation {";
1459 stream() <<
"id:" << mainAnimationId;
1461 generateAnimationBindings();
1462 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
1463 int animationStart = info.transform.animationGroup(groupIndex);
1464 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
1465 ? info.transform.animationGroup(groupIndex + 1)
1466 : info.transform.animationCount();
1469 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
1470 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1471 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1473 stream() <<
"SequentialAnimation {";
1476 const int startOffset = processAnimationTime(firstAnimation.startOffset);
1477 if (startOffset > 0)
1478 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
1480 const int repeatCount = firstAnimation.repeatCount;
1481 if (repeatCount < 0)
1482 stream() <<
"loops: Animation.Infinite";
1484 stream() <<
"loops: " << repeatCount;
1487 stream() <<
"ScriptAction {";
1490 stream() <<
"script: " << targetName <<
"_transform_base_group"
1491 <<
".activateOverride(" << targetName <<
"_transform_group_" << groupIndex <<
")";
1497 stream() <<
"ParallelAnimation {";
1500 for (
int i = animationStart; i < nextAnimationStart; ++i) {
1501 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1502 if (animation.isConstant())
1504 bool hasRotationCenter =
false;
1505 if (animation.subtype == QTransform::TxRotate) {
1506 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1507 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1508 if (!center.isNull()) {
1509 hasRotationCenter =
true;
1515 stream() <<
"SequentialAnimation {";
1518 int previousTime = 0;
1519 QVariantList previousParameters;
1520 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1521 const int time = it.key();
1522 const int frameTime = processAnimationTime(time - previousTime);
1523 const QVariantList ¶meters = it.value().value<QVariantList>();
1524 if (parameters.isEmpty())
1527 if (parameters == previousParameters) {
1529 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
1531 stream() <<
"ParallelAnimation {";
1534 const QString propertyTargetName = targetName
1535 + QStringLiteral(
"_transform_")
1536 + QString::number(groupIndex)
1537 + QStringLiteral(
"_")
1538 + QString::number(i);
1540 switch (animation.subtype) {
1541 case QTransform::TxTranslate:
1543 const QPointF translation = parameters.first().value<QPointF>();
1545 generateAnimatedPropertySetter(propertyTargetName,
1546 QStringLiteral(
"x"),
1551 generateAnimatedPropertySetter(propertyTargetName,
1552 QStringLiteral(
"y"),
1559 case QTransform::TxScale:
1561 const QPointF scale = parameters.first().value<QPointF>();
1562 generateAnimatedPropertySetter(propertyTargetName,
1563 QStringLiteral(
"xScale"),
1568 generateAnimatedPropertySetter(propertyTargetName,
1569 QStringLiteral(
"yScale"),
1576 case QTransform::TxRotate:
1578 Q_ASSERT(parameters.size() == 2);
1579 const qreal angle = parameters.value(1).toReal();
1580 if (hasRotationCenter) {
1581 const QPointF center = parameters.value(0).value<QPointF>();
1582 generateAnimatedPropertySetter(propertyTargetName,
1583 QStringLiteral(
"origin"),
1584 QVector3D(center.x(), center.y(), 0.0),
1589 generateAnimatedPropertySetter(propertyTargetName,
1590 QStringLiteral(
"angle"),
1597 case QTransform::TxShear:
1599 const QPointF skew = parameters.first().value<QPointF>();
1601 generateAnimatedPropertySetter(propertyTargetName,
1602 QStringLiteral(
"xAngle"),
1608 generateAnimatedPropertySetter(propertyTargetName,
1609 QStringLiteral(
"yAngle"),
1624 previousTime = time;
1625 previousParameters = parameters;
1637 if (firstAnimation.repeatCount >= 0) {
1638 stream() <<
"ScriptAction {";
1641 stream() <<
"script: {";
1645 stream() << targetName <<
"_transform_base_group.deactivate("
1646 << targetName <<
"_transform_group_" << groupIndex <<
")";
1647 }
else if (!replace) {
1648 stream() << targetName <<
"_transform_base_group.deactivateOverride("
1649 << targetName <<
"_transform_group_" << groupIndex <<
")";
1667void QQuickQmlGenerator::generateTransformTimeline(
const QString &targetName,
const NodeInfo &info)
1669 stream() <<
"Timeline {";
1671 stream() <<
"currentFrame: " << info.transform.timelineReferenceId() <<
".frameCounter";
1672 stream() <<
"enabled: true";
1674 const int groupIndex = 0;
1675 for (
int i = 0; i < info.transform.animationCount(); ++i) {
1676 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1677 if (animation.isConstant())
1679 if (info.transform.animationGroupCount() > 1
1680 || animation.repeatCount != 1 || animation.startOffset
1681 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
1682 qCWarning(lcQuickVectorImage) <<
"Feature not implemented in timeline xf animation mode, for"
1683 << targetName <<
"subtype" << animation.subtype;
1686 bool hasRotationCenter =
false;
1687 if (animation.subtype == QTransform::TxRotate) {
1688 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1689 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1690 if (!center.isNull()) {
1691 hasRotationCenter =
true;
1697 auto pointFxExtractor = [](
const QVariant &value) {
return value.toPointF().x(); };
1698 auto pointFyExtractor = [](
const QVariant &value) {
return value.toPointF().y(); };
1699 auto realExtractor = [](
const QVariant &value) {
return value.toReal(); };
1700 auto pointFtoVector3dExtractor = [](
const QVariant &v) {
return QVector3D(v.toPointF()); };
1702 const QString propertyTargetName = targetName
1703 + QStringLiteral(
"_transform_")
1704 + QString::number(groupIndex)
1705 + QStringLiteral(
"_")
1706 + QString::number(i);
1708 switch (animation.subtype) {
1709 case QTransform::TxTranslate:
1710 generateTimelinePropertySetter(propertyTargetName,
"x"_L1, animation, pointFxExtractor);
1711 generateTimelinePropertySetter(propertyTargetName,
"y"_L1, animation, pointFyExtractor);
1713 case QTransform::TxScale:
1714 generateTimelinePropertySetter(propertyTargetName,
"xScale"_L1, animation, pointFxExtractor);
1715 generateTimelinePropertySetter(propertyTargetName,
"yScale"_L1, animation, pointFyExtractor);
1717 case QTransform::TxRotate:
1718 if (hasRotationCenter)
1719 generateTimelinePropertySetter(propertyTargetName,
"origin"_L1, animation, pointFtoVector3dExtractor);
1720 generateTimelinePropertySetter(propertyTargetName,
"angle"_L1, animation, realExtractor, 1);
1722 case QTransform::TxShear:
1723 generateTimelinePropertySetter(propertyTargetName,
"xAngle"_L1, animation, pointFxExtractor);
1724 generateTimelinePropertySetter(propertyTargetName,
"yAngle"_L1, animation, pointFyExtractor);
1733bool QQuickQmlGenerator::generateStructureNode(
const StructureNodeInfo &info)
1735 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1738 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1739 if (info.stage == StructureNodeStage::Start) {
1740 if (!info.clipBox.isEmpty()) {
1741 stream() <<
"Item { // Clip";
1744 stream() <<
"width: " << info.clipBox.width();
1745 stream() <<
"height: " << info.clipBox.height();
1746 stream() <<
"clip: true";
1749 if (isPathContainer) {
1750 generatePathContainer(info);
1751 }
else if (!info.customItemType.isEmpty()) {
1752 stream() << info.customItemType <<
" {";
1754 stream() <<
"Item { // Structure node";
1758 generateTimelineFields(info);
1760 if (!info.viewBox.isEmpty()) {
1761 stream() <<
"transform: [";
1763 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1765 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1766 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1771 generateNodeBase(info);
1773 generateNodeEnd(info);
1774 if (isPathContainer)
1775 m_inShapeItemLevel--;
1777 if (!info.clipBox.isEmpty()) {
1786bool QQuickQmlGenerator::generateMaskNode(
const MaskNodeInfo &info)
1788 if (Q_UNLIKELY(errorState()))
1792 if (info.stage == StructureNodeStage::End) {
1794 startDefsSuffixBlock();
1795 stream() <<
"Loader {";
1798 stream() <<
"id: " << info.id;
1799 stream() <<
"sourceComponent: " << info.id <<
"_container";
1800 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
1801 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
1803 if (info.boundsReferenceId.isEmpty()) {
1804 stream() <<
"property real maskX: " << info.maskRect.left();
1805 stream() <<
"property real maskY: " << info.maskRect.top();
1806 stream() <<
"property real maskWidth: " << info.maskRect.width();
1807 stream() <<
"property real maskHeight: " << info.maskRect.height();
1810 stream() <<
"function maskRect(otherX, otherY, otherWidth, otherHeight) {";
1813 stream() <<
"return ";
1814 if (!info.boundsReferenceId.isEmpty()) {
1815 stream(SameLine) << info.boundsReferenceId <<
".originalBounds";
1816 }
else if (info.isMaskRectRelativeCoordinates) {
1819 << info.id <<
".maskX * otherWidth + otherX,"
1820 << info.id <<
".maskY * otherHeight + otherY,"
1821 << info.id <<
".maskWidth * otherWidth,"
1822 << info.id <<
".maskHeight * otherHeight)";
1826 << info.id <<
".maskX, "
1827 << info.id <<
".maskY, "
1828 << info.id <<
".maskWidth, "
1829 << info.id <<
".maskHeight)";
1838 endDefsSuffixBlock();
1844void QQuickQmlGenerator::generateFilterNode(
const FilterNodeInfo &info)
1846 if (Q_UNLIKELY(errorState()))
1849 stream() <<
"Item {";
1852 generateNodeBase(info);
1854 stream() <<
"property real originalWidth: filterSourceItem.sourceItem.originalBounds.width";
1855 stream() <<
"property real originalHeight: filterSourceItem.sourceItem.originalBounds.height";
1856 stream() <<
"property rect filterRect: " << info.id <<
"_filterParameters"
1857 <<
".adaptToFilterRect(0, 0, originalWidth, originalHeight)";
1859 for (qsizetype i = 0; i < info.steps.size();)
1860 i = generateFilterStep(info, i);
1863 startDefsSuffixBlock();
1864 stream() <<
"QtObject {";
1867 stream() <<
"id: " << info.id <<
"_filterParameters";
1868 stream() <<
"property int wrapMode: ";
1869 if (info.wrapMode == QSGTexture::Repeat)
1870 stream(SameLine) <<
"ShaderEffectSource.Repeat";
1872 stream(SameLine) <<
"ShaderEffectSource.ClampToEdge";
1874 stream() <<
"property rect filterRect: Qt.rect("
1875 << info.filterRect.x() <<
", "
1876 << info.filterRect.y() <<
", "
1877 << info.filterRect.width() <<
", "
1878 << info.filterRect.height() <<
")";
1880 stream() <<
"function adaptToFilterRect(sx, sy, sw, sh) {";
1883 if (info.csFilterRect == FilterNodeInfo::CoordinateSystem::Absolute) {
1884 stream() <<
"return Qt.rect(filterRect.x, filterRect.y, filterRect.width, filterRect.height)";
1886 stream() <<
"return Qt.rect(sx + sw * filterRect.x, sy + sh * filterRect.y, sw * filterRect.width, sh * filterRect.height)";
1894 endDefsSuffixBlock();
1896 generateNodeEnd(info);
1899qsizetype QQuickQmlGenerator::generateFilterStep(
const FilterNodeInfo &info,
1900 qsizetype stepIndex)
1902 const FilterNodeInfo::FilterStep &step = info.steps.at(stepIndex);
1903 const QString primitiveId = info.id + QStringLiteral(
"_primitive") + QString::number(stepIndex);
1907 QString inputId = step.input1 != FilterNodeInfo::FilterInput::SourceColor
1909 : QStringLiteral(
"filterSourceItem");
1911 bool isComposite =
false;
1912 switch (step.filterType) {
1913 case FilterNodeInfo::Type::Merge:
1915 const int maxNodeCount = 8;
1918 QList<std::pair<FilterNodeInfo::FilterInput, QString> > inputs;
1919 for (; stepIndex < info.steps.size(); ++stepIndex) {
1920 const FilterNodeInfo::FilterStep &nodeStep = info.steps.at(stepIndex);
1921 if (nodeStep.filterType != FilterNodeInfo::Type::MergeNode)
1924 inputs.emplace_back(nodeStep.input1, nodeStep.namedInput1);
1927 if (inputs.size() > maxNodeCount) {
1928 qCWarning(lcQuickVectorImage) <<
"Maximum of" << maxNodeCount
1929 <<
"nodes exceeded in merge effect.";
1932 if (inputs.isEmpty()) {
1933 qCWarning(lcQuickVectorImage) <<
"Merge effect requires at least one node.";
1937 stream() <<
"ShaderEffect {";
1940 stream() <<
"id: " << primitiveId;
1941 stream() <<
"visible: false";
1943 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb\"";
1944 stream() <<
"width: source1.width";
1945 stream() <<
"height: source1.height";
1946 stream() <<
"property int sourceCount: " << std::min(qsizetype(8), inputs.size());
1948 for (
int i = 0; i < maxNodeCount; ++i) {
1949 auto input = i < inputs.size()
1951 : std::pair(FilterNodeInfo::FilterInput::None, u"null"_s);
1953 QString inputId = input.first != FilterNodeInfo::FilterInput::SourceColor
1955 : QStringLiteral(
"filterSourceItem");
1957 stream() <<
"property var source" << (i + 1) <<
": " << inputId;
1965 case FilterNodeInfo::Type::CompositeOver:
1966 case FilterNodeInfo::Type::CompositeOut:
1967 case FilterNodeInfo::Type::CompositeIn:
1968 case FilterNodeInfo::Type::CompositeXor:
1969 case FilterNodeInfo::Type::CompositeAtop:
1970 case FilterNodeInfo::Type::CompositeArithmetic:
1971 case FilterNodeInfo::Type::CompositeLighter:
1975 case FilterNodeInfo::Type::BlendNormal:
1976 case FilterNodeInfo::Type::BlendMultiply:
1977 case FilterNodeInfo::Type::BlendScreen:
1978 case FilterNodeInfo::Type::BlendDarken:
1979 case FilterNodeInfo::Type::BlendLighten:
1981 stream() <<
"ShaderEffect {";
1984 QString input2Id = step.input2 != FilterNodeInfo::FilterInput::SourceColor
1986 : QStringLiteral(
"filterSourceItem");
1988 stream() <<
"id: " << primitiveId;
1989 stream() <<
"visible: false";
1992 switch (step.filterType) {
1993 case FilterNodeInfo::Type::CompositeOver:
1994 shader = QStringLiteral(
"fecompositeover");
1996 case FilterNodeInfo::Type::CompositeOut:
1997 shader = QStringLiteral(
"fecompositeout");
1999 case FilterNodeInfo::Type::CompositeIn:
2000 shader = QStringLiteral(
"fecompositein");
2002 case FilterNodeInfo::Type::CompositeXor:
2003 shader = QStringLiteral(
"fecompositexor");
2005 case FilterNodeInfo::Type::CompositeAtop:
2006 shader = QStringLiteral(
"fecompositeatop");
2008 case FilterNodeInfo::Type::CompositeArithmetic:
2009 shader = QStringLiteral(
"fecompositearithmetic");
2011 case FilterNodeInfo::Type::CompositeLighter:
2012 shader = QStringLiteral(
"fecompositelighter");
2014 case FilterNodeInfo::Type::BlendNormal:
2015 shader = QStringLiteral(
"feblendnormal");
2017 case FilterNodeInfo::Type::BlendMultiply:
2018 shader = QStringLiteral(
"feblendmultiply");
2020 case FilterNodeInfo::Type::BlendScreen:
2021 shader = QStringLiteral(
"feblendscreen");
2023 case FilterNodeInfo::Type::BlendDarken:
2024 shader = QStringLiteral(
"feblenddarken");
2026 case FilterNodeInfo::Type::BlendLighten:
2027 shader = QStringLiteral(
"feblendlighten");
2033 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"
2034 << shader <<
".frag.qsb\"";
2035 stream() <<
"property var source: " << inputId;
2036 stream() <<
"property var source2: " << input2Id;
2037 stream() <<
"width: source.width";
2038 stream() <<
"height: source.height";
2041 QVector4D k = step.filterParameter.value<QVector4D>();
2042 stream() <<
"property var k: Qt.vector4d("
2055 case FilterNodeInfo::Type::Flood:
2057 stream() <<
"Rectangle {";
2060 stream() <<
"id: " << primitiveId;
2061 stream() <<
"visible: false";
2063 stream() <<
"width: " << inputId <<
".width";
2064 stream() <<
"height: " << inputId <<
".height";
2066 QColor floodColor = step.filterParameter.value<QColor>();
2067 stream() <<
"color: \"" << floodColor.name(QColor::HexArgb) <<
"\"";
2074 case FilterNodeInfo::Type::ColorMatrix:
2076 stream() <<
"ShaderEffect {";
2079 stream() <<
"id: " << primitiveId;
2080 stream() <<
"visible: false";
2082 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb\"";
2083 stream() <<
"property var source: " << inputId;
2084 stream() <<
"width: source.width";
2085 stream() <<
"height: source.height";
2087 QGenericMatrix<5, 5, qreal> matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
2088 for (
int row = 0; row < 4; ++row) {
2091 for (
int col = 0; col < 5; ++col)
2092 stream() <<
"property real m_" << row <<
"_" << col <<
": " << matrix(col, row);
2101 case FilterNodeInfo::Type::Offset:
2103 stream() <<
"ShaderEffectSource {";
2106 stream() <<
"id: " << primitiveId;
2107 stream() <<
"visible: false";
2108 stream() <<
"sourceItem: " << inputId;
2109 stream() <<
"width: sourceItem.width + offset.x";
2110 stream() <<
"height: sourceItem.height + offset.y";
2112 QVector2D offset = step.filterParameter.value<QVector2D>();
2113 stream() <<
"property vector2d offset: Qt.vector2d(";
2114 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute)
2115 stream(SameLine) << offset.x() <<
" / width, " << offset.y() <<
" / height)";
2117 stream(SameLine) << offset.x() <<
", " << offset.y() <<
")";
2119 stream() <<
"sourceRect: Qt.rect(-offset.x, -offset.y, width, height)";
2121 stream() <<
"ItemSpy {";
2123 stream() <<
"id: " << primitiveId <<
"_offset_itemspy";
2124 stream() <<
"anchors.fill: parent";
2128 stream() <<
"textureSize: " << primitiveId <<
"_offset_itemspy.requiredTextureSize";
2137 case FilterNodeInfo::Type::GaussianBlur:
2140 stream() <<
"MultiEffect {";
2143 stream() <<
"id: " << primitiveId;
2144 stream() <<
"visible: false";
2146 stream() <<
"source: " << inputId;
2147 stream() <<
"blurEnabled: true";
2148 stream() <<
"width: source.width";
2149 stream() <<
"height: source.height";
2151 const qreal maxDeviation(12.0);
2152 const qreal deviation = step.filterParameter.toReal();
2153 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
2154 stream() <<
"blur: Math.min(1.0, " << deviation <<
" * filterSourceItem.width / " << maxDeviation <<
")";
2156 stream() <<
"blur: " << std::min(qreal(1.0), deviation / maxDeviation);
2157 stream() <<
"blurMax: 64";
2165 qCWarning(lcQuickVectorImage) <<
"Unhandled filter type: " <<
int(step.filterType);
2167 stream() <<
"Item { id: " << primitiveId <<
" }";
2172 stream() <<
"ShaderEffectSource {";
2175 stream() <<
"id: " << step.outputName;
2176 if (stepIndex < info.steps.size())
2177 stream() <<
"visible: false";
2179 qreal x1, x2, y1, y2;
2180 step.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
2181 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
2182 stream() <<
"property real fpx1: " << x1;
2183 stream() <<
"property real fpy1: " << y1;
2184 stream() <<
"property real fpx2: " << x2;
2185 stream() <<
"property real fpy2: " << y2;
2186 }
else if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
2190 stream() <<
"property real fpx1: " << x1 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2191 stream() <<
"property real fpy1: " << y1 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2192 stream() <<
"property real fpx2: " << x2 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2193 stream() <<
"property real fpy2: " << y2 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2195 stream() <<
"property real fpx1: parent.filterRect.x";
2196 stream() <<
"property real fpy1: parent.filterRect.y";
2197 stream() <<
"property real fpx2: parent.filterRect.x + parent.filterRect.width";
2198 stream() <<
"property real fpy2: parent.filterRect.y + parent.filterRect.height";
2201 stream() <<
"sourceItem: " << primitiveId;
2202 stream() <<
"sourceRect: Qt.rect(fpx1 - parent.filterRect.x, fpy1 - parent.filterRect.y, width, height)";
2204 stream() <<
"x: fpx1";
2205 stream() <<
"y: fpy1";
2206 stream() <<
"width: " <<
"fpx2 - fpx1";
2207 stream() <<
"height: " <<
"fpy2 - fpy1";
2209 stream() <<
"ItemSpy {";
2211 stream() <<
"id: " << primitiveId <<
"_itemspy";
2212 stream() <<
"anchors.fill: parent";
2216 stream() <<
"textureSize: " << primitiveId <<
"_itemspy.requiredTextureSize";
2224void QQuickQmlGenerator::generateTimelineFields(
const StructureNodeInfo &info)
2226 if (usingTimelineAnimation() && info.timelineInfo) {
2227 QString frameCounterRef = info.timelineInfo->frameCounterReference
2228 + QStringLiteral(
".frameCounter");
2230 if (info.timelineInfo->generateVisibility) {
2231 stream() <<
"visible: " << frameCounterRef <<
" >= " << info.timelineInfo->startFrame
2232 <<
" && " << frameCounterRef <<
" < " << info.timelineInfo->endFrame;
2235 if (info.timelineInfo->generateFrameCounter) {
2236 stream() <<
"property real frameCounter: ";
2237 if (info.timelineInfo->frameCounterMapper.isAnimated()) {
2239 stream(SameLine) <<
"0";
2240 generatePropertyTimeline(info.timelineInfo->frameCounterMapper, info.id,
"frameCounter"_L1);
2242 const auto offset = info.timelineInfo->frameCounterOffset;
2243 const auto multiplier = info.timelineInfo->frameCounterMultiplier;
2244 const bool needsParens = (offset && multiplier);
2246 stream(SameLine) <<
"(";
2247 stream(SameLine) << frameCounterRef;
2249 stream(SameLine) << (offset > 0 ?
" + " :
" - ") << qAbs(offset);
2251 stream(SameLine) <<
")";
2253 stream(SameLine) <<
" * " << multiplier;
2259bool QQuickQmlGenerator::generatePatternNode(
const PatternNodeInfo &info)
2261 if (info.stage == StructureNodeStage::Start) {
2264 startDefsSuffixBlock();
2265 stream() <<
"Loader {";
2268 stream() <<
"id: " << info.id;
2269 stream() <<
"sourceComponent: " << info.id <<
"_container";
2270 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
2271 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
2272 stream() <<
"visible: false";
2273 stream() <<
"function sourceRect(targetWidth, targetHeight) {";
2276 stream() <<
"return Qt.rect(0, 0, ";
2277 if (!info.isPatternRectRelativeCoordinates) {
2278 stream(SameLine) << info.patternRect.width() <<
", "
2279 << info.patternRect.height();
2281 stream(SameLine) << info.patternRect.width() <<
" * targetWidth, "
2282 << info.patternRect.height() <<
" * targetHeight";
2284 stream(SameLine) <<
")";
2288 stream() <<
"function sourceOffset(targetWidth, targetHeight) {";
2291 stream() <<
"return Qt.vector3d(";
2292 if (!info.isPatternRectRelativeCoordinates) {
2293 stream(SameLine) << info.patternRect.x() <<
", "
2294 << info.patternRect.y() <<
", ";
2296 stream(SameLine) << info.patternRect.x() <<
" * targetWidth, "
2297 << info.patternRect.y() <<
" * targetHeight, ";
2299 stream(SameLine) <<
"0.0)";
2307 endDefsSuffixBlock();
2313void QQuickQmlGenerator::generateDefsInstantiationNode(
const StructureNodeInfo &info)
2315 if (Q_UNLIKELY(errorState()))
2318 if (info.stage == StructureNodeStage::Start) {
2319 stream() <<
"Loader {";
2322 stream() <<
"sourceComponent: " << info.defsId <<
"_container";
2323 generateNodeBase(info);
2324 generateTimelineFields(info);
2331bool QQuickQmlGenerator::generateMarkerNode(
const MarkerNodeInfo &info)
2333 if (info.stage == StructureNodeStage::Start) {
2334 startDefsSuffixBlock();
2335 stream() <<
"QtObject {";
2338 stream() <<
"id: " << info.id <<
"_markerParameters";
2340 stream() <<
"property bool startReversed: ";
2341 if (info.orientation == MarkerNodeInfo::Orientation::AutoStartReverse)
2342 stream(SameLine) <<
"true";
2344 stream(SameLine) <<
"false";
2346 stream() <<
"function autoAngle(adaptedAngle) {";
2348 if (info.orientation == MarkerNodeInfo::Orientation::Value)
2349 stream() <<
"return " << info.angle;
2351 stream() <<
"return adaptedAngle";
2357 endDefsSuffixBlock();
2359 if (!info.clipBox.isEmpty()) {
2360 stream() <<
"Item {";
2363 stream() <<
"x: " << info.clipBox.x();
2364 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2365 stream(SameLine) <<
" * strokeWidth";
2366 stream() <<
"y: " << info.clipBox.y();
2367 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2368 stream(SameLine) <<
" * strokeWidth";
2369 stream() <<
"width: " << info.clipBox.width();
2370 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2371 stream(SameLine) <<
" * strokeWidth";
2372 stream() <<
"height: " << info.clipBox.height();
2373 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2374 stream(SameLine) <<
" * strokeWidth";
2375 stream() <<
"clip: true";
2378 stream() <<
"Item {";
2381 if (!info.clipBox.isEmpty()) {
2382 stream() <<
"x: " << -info.clipBox.x();
2383 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2384 stream(SameLine) <<
" * strokeWidth";
2385 stream() <<
"y: " << -info.clipBox.y();
2386 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2387 stream(SameLine) <<
" * strokeWidth";
2390 stream() <<
"id: " << info.id;
2392 stream() <<
"property real markerWidth: " << info.markerSize.width();
2393 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2394 stream(SameLine) <<
" * strokeWidth";
2396 stream() <<
"property real markerHeight: " << info.markerSize.height();
2397 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2398 stream(SameLine) <<
" * strokeWidth";
2400 stream() <<
"function calculateMarkerScale(w, h) {";
2403 stream() <<
"var scaleX = 1.0";
2404 stream() <<
"var scaleY = 1.0";
2405 stream() <<
"var offsetX = 0.0";
2406 stream() <<
"var offsetY = 0.0";
2407 if (info.viewBox.width() > 0)
2408 stream() <<
"if (w > 0) scaleX = w / " << info.viewBox.width();
2409 if (info.viewBox.height() > 0)
2410 stream() <<
"if (h > 0) scaleY = h / " << info.viewBox.height();
2412 if (info.preserveAspectRatio & MarkerNodeInfo::xyMask) {
2413 stream() <<
"if (scaleX != scaleY) {";
2416 if (info.preserveAspectRatio & MarkerNodeInfo::meet)
2417 stream() <<
"scaleX = scaleY = Math.min(scaleX, scaleY)";
2419 stream() <<
"scaleX = scaleY = Math.max(scaleX, scaleY)";
2421 QString overflowX = QStringLiteral(
"scaleX * %1 - w").arg(info.viewBox.width());
2422 QString overflowY = QStringLiteral(
"scaleY * %1 - h").arg(info.viewBox.height());
2424 const quint8 xRatio = info.preserveAspectRatio & MarkerNodeInfo::xMask;
2425 if (xRatio == MarkerNodeInfo::xMid)
2426 stream() <<
"offsetX -= " << overflowX <<
" / 2";
2427 else if (xRatio == MarkerNodeInfo::xMax)
2428 stream() <<
"offsetX -= " << overflowX;
2430 const quint8 yRatio = info.preserveAspectRatio & MarkerNodeInfo::yMask;
2431 if (yRatio == MarkerNodeInfo::yMid)
2432 stream() <<
"offsetY -= " << overflowY <<
" / 2";
2433 else if (yRatio == MarkerNodeInfo::yMax)
2434 stream() <<
"offsetY -= " << overflowY;
2440 stream() <<
"return Qt.vector4d("
2441 <<
"offsetX - " << info.anchorPoint.x() <<
" * scaleX, "
2442 <<
"offsetY - " << info.anchorPoint.y() <<
" * scaleY, "
2449 stream() <<
"property vector4d markerScale: calculateMarkerScale(markerWidth, markerHeight)";
2451 stream() <<
"transform: [";
2454 stream() <<
"Scale { xScale: " << info.id <<
".markerScale.z; yScale: " << info.id <<
".markerScale.w },";
2455 stream() <<
"Translate { x: " << info.id <<
".markerScale.x; y: " << info.id <<
".markerScale.y }";
2461 generateNodeEnd(info);
2463 if (!info.clipBox.isEmpty()) {
2472bool QQuickQmlGenerator::generateRootNode(
const StructureNodeInfo &info)
2474 if (Q_UNLIKELY(errorState()))
2477 const QStringList comments = m_commentString.split(u'\n');
2479 if (!isNodeVisible(info)) {
2482 if (comments.isEmpty()) {
2483 stream() <<
"// Generated from SVG";
2485 for (
const auto &comment : comments)
2486 stream() <<
"// " << comment;
2489 stream() <<
"import QtQuick";
2490 stream() <<
"import QtQuick.Shapes" << Qt::endl;
2491 stream() <<
"Item {";
2494 double w = info.size.width();
2495 double h = info.size.height();
2497 stream() <<
"implicitWidth: " << w;
2499 stream() <<
"implicitHeight: " << h;
2507 if (info.stage == StructureNodeStage::Start) {
2510 if (comments.isEmpty())
2511 stream() <<
"// Generated from SVG";
2513 for (
const auto &comment : comments)
2514 stream() <<
"// " << comment;
2516 stream() <<
"import QtQuick";
2517 stream() <<
"import QtQuick.VectorImage";
2518 stream() <<
"import QtQuick.VectorImage.Helpers";
2519 stream() <<
"import QtQuick.Shapes";
2520 stream() <<
"import QtQuick.Effects";
2521 if (usingTimelineAnimation())
2522 stream() <<
"import QtQuick.Timeline";
2524 for (
const auto &import : std::as_const(m_extraImports))
2525 stream() <<
"import " << import;
2527 stream() << Qt::endl <<
"Item {";
2530 double w = info.size.width();
2531 double h = info.size.height();
2533 stream() <<
"implicitWidth: " << w;
2535 stream() <<
"implicitHeight: " << h;
2537 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2538 stream() <<
"component AnimationsInfo : QtObject";
2543 stream() <<
"property bool paused: false";
2544 stream() <<
"property int loops: 1";
2545 stream() <<
"signal restart()";
2547 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2550 stream() <<
"property AnimationsInfo animations : AnimationsInfo {}";
2553 stream() <<
"Item {";
2555 stream() <<
"width: 1";
2556 stream() <<
"height: 1";
2558 stream() <<
"ItemSpy { id: __qt_toplevel_scale_itemspy; anchors.fill: parent }";
2563 if (!info.viewBox.isEmpty()) {
2564 stream() <<
"transform: [";
2566 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
2568 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
2569 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
2574 if (!info.forceSeparatePaths && info.isPathContainer) {
2575 m_topLevelIdString = QStringLiteral(
"__qt_toplevel");
2576 stream() <<
"id: " << m_topLevelIdString;
2578 generatePathContainer(info);
2581 generateNodeBase(info);
2583 m_topLevelIdString = generateNodeBase(info);
2584 if (m_topLevelIdString.isEmpty())
2585 qCWarning(lcQuickVectorImage) <<
"No ID specified for top level item";
2588 if (usingTimelineAnimation() && info.timelineInfo) {
2589 stream() <<
"property real frameCounter: " << info.timelineInfo->startFrame;
2590 stream() <<
"NumberAnimation on frameCounter {";
2592 stream() <<
"from: " << info.timelineInfo->startFrame;
2593 stream() <<
"to: " << info.timelineInfo->endFrame - 0.01;
2594 stream() <<
"duration: " << processAnimationTime(info.timelineInfo->duration);
2595 generateAnimationBindings();
2598 stream() <<
"visible: frameCounter >= " << info.timelineInfo->startFrame
2599 <<
" && frameCounter < " << info.timelineInfo->endFrame;
2602 if (m_inShapeItemLevel > 0) {
2603 m_inShapeItemLevel--;
2608 for (
const auto [coords, id] : m_easings.asKeyValueRange()) {
2609 stream() <<
"readonly property easingCurve " << id <<
": ({ type: Easing.BezierSpline, bezierCurve: [ ";
2610 for (
auto coord : coords)
2611 stream(SameLine) << coord <<
", ";
2612 stream(SameLine) <<
"1, 1 ] })";
2615 generateNodeEnd(info);
2622void QQuickQmlGenerator::startDefsSuffixBlock()
2624 int tmp = m_oldIndentLevels.top();
2625 m_oldIndentLevels.push(m_indentLevel);
2626 m_indentLevel = tmp;
2627 m_stream.setString(&m_defsSuffix);
2630void QQuickQmlGenerator::endDefsSuffixBlock()
2632 m_indentLevel = m_oldIndentLevels.pop();
2633 m_stream.setDevice(&m_result);
2636QStringView QQuickQmlGenerator::indent()
2638 static QString indentString;
2639 int indentWidth = m_indentLevel * 4;
2640 if (indentWidth > indentString.size())
2641 indentString.fill(QLatin1Char(
' '), indentWidth * 2);
2642 return QStringView(indentString).first(indentWidth);
2645QTextStream &QQuickQmlGenerator::stream(
int flags)
2647 if (m_stream.device() ==
nullptr && m_stream.string() ==
nullptr)
2648 m_stream.setDevice(&m_result);
2649 else if (!(flags & StreamFlags::SameLine))
2650 m_stream << Qt::endl << indent();
2652 static qint64 maxBufferSize = qEnvironmentVariableIntegerValue(
"QT_QUICKVECTORIMAGE_MAX_BUFFER").value_or(64 << 20);
2653 if (m_stream.device()) {
2654 if (Q_UNLIKELY(!checkSanityLimit(m_stream.device()->size(), maxBufferSize,
"buffer size"_L1)))
2655 m_stream.device()->reset();
2657 if (Q_UNLIKELY(!checkSanityLimit(m_stream.string()->size(), maxBufferSize,
"buffer string size"_L1)))
2658 m_stream.string()->clear();
2664const char *QQuickQmlGenerator::shapeName()
const
2666 return m_shapeTypeName.isEmpty() ?
"Shape" : m_shapeTypeName.constData();
Combined button and popup list for selecting options.
static QString sanitizeString(const QString &input)
static int processAnimationTime(int time)