Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickqmlgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "utils_p.h"
7
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>
16
17#include <QtCore/qloggingcategory.h>
18#include <QtCore/qdir.h>
19
21
22static QString sanitizeString(const QString &input)
23{
24 QString s = input;
25 s.replace(QLatin1Char('"'), QLatin1String("\\\""));
26 return s;
27}
28
29
30QQuickAnimatedProperty::PropertyAnimation QQuickAnimatedProperty::PropertyAnimation::simplified() const
31{
32 QQuickAnimatedProperty::PropertyAnimation res = *this;
33 int consecutiveEquals = 0;
34 int prevTimePoint = -1;
35 QVariant prevValue;
36 for (const auto &[timePoint, value] : frames.asKeyValueRange()) {
37 if (value != prevValue) {
38 consecutiveEquals = 1;
39 prevValue = value;
40 } else if (consecutiveEquals < 2) {
41 consecutiveEquals++;
42 } else {
43 // Third consecutive equal value found, remove the redundant middle one
44 res.frames.remove(prevTimePoint);
45 res.easingPerFrame.remove(prevTimePoint);
46 }
47 prevTimePoint = timePoint;
48 }
49
50 return res;
51}
52
53QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName)
54 : QQuickGenerator(fileName, flags)
55 , outputFileName(outFileName)
56{
57 m_result.open(QIODevice::ReadWrite);
58 m_oldIndentLevels.push(0);
59}
60
61QQuickQmlGenerator::~QQuickQmlGenerator()
62{
63}
64
65bool QQuickQmlGenerator::save()
66{
67 if (Q_UNLIKELY(errorState()))
68 return false;
69
70 bool res = true;
71 if (!outputFileName.isEmpty()) {
72 QFileInfo fileInfo(outputFileName);
73 QDir dir(fileInfo.absolutePath());
74 if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
75 qCWarning(lcQuickVectorImage) << "Failed to create path" << dir.absolutePath();
76 res = false;
77 } else {
78 QFile outFile(outputFileName);
79 if (outFile.open(QIODevice::WriteOnly)) {
80 outFile.write(m_result.data());
81 outFile.close();
82 } else {
83 qCWarning(lcQuickVectorImage) << "Failed to write to file" << outFile.fileName();
84 res = false;
85 }
86 }
87 }
88
89 if (lcQuickVectorImage().isDebugEnabled())
90 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
91
92 return res;
93}
94
95void QQuickQmlGenerator::setShapeTypeName(const QString &name)
96{
97 m_shapeTypeName = name.toLatin1();
98}
99
100QString QQuickQmlGenerator::shapeTypeName() const
101{
102 return QString::fromLatin1(m_shapeTypeName);
103}
104
105void QQuickQmlGenerator::setCommentString(const QString commentString)
106{
107 m_commentString = commentString;
108}
109
110QString QQuickQmlGenerator::commentString() const
111{
112 return m_commentString;
113}
114
115QString QQuickQmlGenerator::generateNodeBase(const NodeInfo &info, const QString &idSuffix)
116{
117 static qint64 maxNodes = qEnvironmentVariableIntegerValue("QT_QUICKVECTORIMAGE_MAX_NODES").value_or(10000);
118 if (Q_UNLIKELY(!checkSanityLimit(++m_nodeCounter, maxNodes, "nodes"_L1)))
119 return {};
120
121 if (!info.nodeId.isEmpty())
122 stream() << "objectName: \"" << info.nodeId << "\"";
123
124 if (!info.id.isEmpty())
125 stream() << "id: " << info.id << idSuffix;
126
127 if (!info.bounds.isNull() || !info.boundsReferenceId.isEmpty()) {
128 stream() << "property var originalBounds: ";
129 if (!info.bounds.isNull()) {
130 stream(SameLine) << "Qt.rect(" << info.bounds.x() << ", " << info.bounds.y() << ", "
131 << info.bounds.width() << ", " << info.bounds.height() << ")";
132 } else {
133 stream(SameLine) << info.boundsReferenceId << ".originalBounds";
134 }
135 stream() << "width: originalBounds.width";
136 stream() << "height: originalBounds.height";
137 }
138
139 stream() << "transformOrigin: Item.TopLeft";
140
141 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
142 if (!info.isDefaultOpacity)
143 stream() << "opacity: " << info.opacity.defaultValue().toReal();
144 generateItemAnimations(info.id, info);
145 }
146
147 return info.id;
148}
149
150void QQuickQmlGenerator::generateNodeEnd(const NodeInfo &info)
151{
152 if (Q_UNLIKELY(errorState()))
153 return;
154 m_indentLevel--;
155 stream() << "}";
156 generateShaderUse(info);
157}
158
159void QQuickQmlGenerator::generateItemAnimations(const QString &idString, const NodeInfo &info)
160{
161 const bool hasTransform = info.transform.isAnimated()
162 || !info.maskId.isEmpty()
163 || !info.filterId.isEmpty()
164 || !info.isDefaultTransform
165 || !info.transformReferenceId.isEmpty()
166 || info.motionPath.isAnimated();
167
168 if (hasTransform) {
169 stream() << "transform: TransformGroup {";
170 m_indentLevel++;
171
172 bool hasNonConstantTransform = false;
173 int earliestOverrideGroup = -1;
174
175 if (!idString.isEmpty()) {
176 stream() << "id: " << idString << "_transform_base_group";
177
178 if (!info.maskId.isEmpty() || !info.filterId.isEmpty())
179 stream() << "Translate { x: " << idString << ".sourceX; y: " << idString << ".sourceY }";
180
181 if (info.transform.isAnimated()) {
182 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
183 stream() << "TransformGroup {";
184 m_indentLevel++;
185
186 if (!idString.isEmpty())
187 stream() << "id: " << idString << "_transform_group_" << groupIndex;
188
189 int animationStart = info.transform.animationGroup(groupIndex);
190 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
191 ? info.transform.animationGroup(groupIndex + 1)
192 : info.transform.animationCount();
193
194 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
195 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
196 if (replace && earliestOverrideGroup < 0)
197 earliestOverrideGroup = groupIndex;
198
199 for (int i = nextAnimationStart - 1; i >= animationStart; --i) {
200 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
201 if (animation.frames.isEmpty())
202 continue;
203
204 const QVariantList &parameters = animation.frames.first().value<QVariantList>();
205 switch (animation.subtype) {
206 case QTransform::TxTranslate:
207 if (animation.isConstant()) {
208 const QPointF translation = parameters.value(0).value<QPointF>();
209 if (!translation.isNull())
210 stream() << "Translate { x: " << translation.x() << "; y: " << translation.y() << " }";
211 } else {
212 hasNonConstantTransform = true;
213 stream() << "Translate { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
214 }
215 break;
216 case QTransform::TxScale:
217 if (animation.isConstant()) {
218 const QPointF scale = parameters.value(0).value<QPointF>();
219 if (scale != QPointF(1, 1))
220 stream() << "Scale { xScale: " << scale.x() << "; yScale: " << scale.y() << " }";
221 } else {
222 hasNonConstantTransform = true;
223 stream() << "Scale { id: " << idString << "_transform_" << groupIndex << "_" << i << "}";
224 }
225 break;
226 case QTransform::TxRotate:
227 if (animation.isConstant()) {
228 const QPointF center = parameters.value(0).value<QPointF>();
229 const qreal angle = parameters.value(1).toReal();
230 if (!qFuzzyIsNull(angle))
231 stream() << "Rotation { angle: " << angle << "; origin.x: " << center.x() << "; origin.y: " << center.y() << " }"; //### center relative to what?
232 } else {
233 hasNonConstantTransform = true;
234 stream() << "Rotation { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
235 }
236 break;
237 case QTransform::TxShear:
238 if (animation.isConstant()) {
239 const QPointF skew = parameters.value(0).value<QPointF>();
240 if (!skew.isNull())
241 stream() << "Shear { xAngle: " << skew.x() << "; yAngle: " << skew.y() << " }";
242 } else {
243 hasNonConstantTransform = true;
244 stream() << "Shear { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
245 }
246 break;
247 default:
248 Q_UNREACHABLE();
249 }
250 }
251
252 m_indentLevel--;
253 stream() << "}";
254 }
255 }
256
257 if (info.motionPath.isAnimated()) {
258 QVariantList defaultProps = info.motionPath.defaultValue().value<QVariantList>();
259 const bool adaptAngle = defaultProps.value(1).toBool();
260 const qreal baseRotation = defaultProps.value(2).toReal();
261 QString interpolatorId = idString + QStringLiteral("_motion_interpolator");
262 if (adaptAngle || !qFuzzyIsNull(baseRotation)) {
263 stream() << "Rotation {";
264 m_indentLevel++;
265
266 if (adaptAngle) {
267 stream() << "angle: " << interpolatorId << ".angle";
268 if (!qFuzzyIsNull(baseRotation))
269 stream(SameLine) << " + " << baseRotation;
270 } else {
271 stream() << "angle: " << baseRotation;
272 }
273
274 m_indentLevel--;
275 stream() << "}";
276 }
277
278 stream() << "Translate {";
279 m_indentLevel++;
280
281 stream() << "x: " << interpolatorId << ".x";
282 stream() << "y: " << interpolatorId << ".y";
283
284 m_indentLevel--;
285 stream() << "}";
286 }
287 }
288
289 if (!info.isDefaultTransform) {
290 QTransform xf = info.transform.defaultValue().value<QTransform>();
291 if (xf.type() <= QTransform::TxTranslate) {
292 stream() << "Translate { x: " << xf.dx() << "; y: " << xf.dy() << "}";
293 } else {
294 stream() << "Matrix4x4 { matrix: ";
295 generateTransform(xf);
296 stream(SameLine) << "}";
297 }
298 }
299
300 if (!info.transformReferenceId.isEmpty())
301 stream() << "Matrix4x4 { matrix: " << info.transformReferenceId << ".transformMatrix }";
302
303 m_indentLevel--;
304 stream() << "}";
305
306 if (hasNonConstantTransform) {
307 generateAnimateTransform(idString, info);
308 } else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
309 // We have animations, but they are all constant? Then we still need to respect the
310 // override flag of the animations
311 stream() << "Component.onCompleted: {";
312 m_indentLevel++;
313
314 stream() << idString << "_transform_base_group.activateOverride("
315 << idString << "_transform_group_" << earliestOverrideGroup << ")";
316
317 m_indentLevel--;
318 stream() << "}";
319 }
320 }
321
322 generateAnimateMotionPath(idString, info.motionPath);
323
324 generatePropertyAnimation(info.opacity, idString, QStringLiteral("opacity"));
325}
326
327void QQuickQmlGenerator::generateShaderUse(const NodeInfo &info)
328{
329 const bool hasMask = !info.maskId.isEmpty();
330 const bool hasFilters = !info.filterId.isEmpty();
331 if (!hasMask && !hasFilters)
332 return;
333
334 const QString effectId = hasFilters
335 ? info.filterId + QStringLiteral("_") + info.id + QStringLiteral("_effect")
336 : QString{};
337
338 QString animatedItemId;
339 if (hasFilters) {
340 stream() << "ShaderEffectSource {";
341 m_indentLevel++;
342
343 const QString seId = info.id + QStringLiteral("_se");
344 stream() << "id: " << seId;
345
346 stream() << "ItemSpy {";
347 m_indentLevel++;
348 stream() << "id: " << info.id << "_itemspy";
349 stream() << "anchors.fill: parent";
350 m_indentLevel--;
351 stream() << "}";
352
353 stream() << "hideSource: true";
354 stream() << "wrapMode: " << info.filterId << "_filterParameters.wrapMode";
355 stream() << "sourceItem: " << info.id;
356 stream() << "sourceRect: " << info.filterId
357 << "_filterParameters.adaptToFilterRect("
358 << info.id << ".originalBounds.x, "
359 << info.id << ".originalBounds.y, "
360 << info.id << ".originalBounds.width, "
361 << info.id << ".originalBounds.height)";
362 stream() << "textureSize: " << info.id << "_itemspy.requiredTextureSize";
363 stream() << "width: sourceRect.width";
364 stream() << "height: sourceRect.height";
365 stream() << "visible: false";
366
367 m_indentLevel--;
368 stream() << "}";
369
370 stream() << "Loader {";
371 m_indentLevel++;
372
373 animatedItemId = effectId;
374 stream() << "id: " << effectId;
375
376 stream() << "property var filterSourceItem: " << seId;
377 stream() << "sourceComponent: " << info.filterId << "_container";
378 stream() << "property real sourceX: " << info.id << ".originalBounds.x";
379 stream() << "property real sourceY: " << info.id << ".originalBounds.y";
380 stream() << "width: " << info.id << ".originalBounds.width";
381 stream() << "height: " << info.id << ".originalBounds.height";
382
383 if (hasMask) {
384 m_indentLevel--;
385 stream() << "}";
386 }
387 }
388
389 if (hasMask) {
390 // Shader effect source for the mask itself
391 stream() << "ShaderEffectSource {";
392 m_indentLevel++;
393
394 const QString maskId = info.maskId + QStringLiteral("_") + info.id + QStringLiteral("_mask");
395 stream() << "id: " << maskId;
396 stream() << "sourceItem: " << info.maskId;
397 stream() << "visible: false";
398 stream() << "hideSource: true";
399
400 stream() << "ItemSpy {";
401 m_indentLevel++;
402 stream() << "id: " << maskId << "_itemspy";
403 stream() << "anchors.fill: parent";
404 m_indentLevel--;
405 stream() << "}";
406 stream() << "textureSize: " << maskId << "_itemspy.requiredTextureSize";
407
408 stream() << "sourceRect: " << info.maskId << ".maskRect("
409 << info.id << ".originalBounds.x,"
410 << info.id << ".originalBounds.y,"
411 << info.id << ".originalBounds.width,"
412 << info.id << ".originalBounds.height)";
413
414 stream() << "width: sourceRect.width";
415 stream() << "height: sourceRect.height";
416
417 m_indentLevel--;
418 stream() << "}";
419
420 // Shader effect source of the masked item
421 stream() << "ShaderEffectSource {";
422 m_indentLevel++;
423
424 const QString seId = info.id + QStringLiteral("_masked_se");
425 stream() << "id: " << seId;
426
427 stream() << "ItemSpy {";
428 m_indentLevel++;
429 stream() << "id: " << info.id << "_masked_se_itemspy";
430 stream() << "anchors.fill: parent";
431 m_indentLevel--;
432 stream() << "}";
433
434 stream() << "hideSource: true";
435 if (hasFilters)
436 stream() << "sourceItem: " << effectId;
437 else
438 stream() << "sourceItem: " << info.id;
439 stream() << "textureSize: " << info.id << "_masked_se_itemspy.requiredTextureSize";
440 if (!hasFilters) {
441 stream() << "sourceRect: " << info.maskId << ".maskRect("
442 << info.id << ".originalBounds.x,"
443 << info.id << ".originalBounds.y,"
444 << info.id << ".originalBounds.width,"
445 << info.id << ".originalBounds.height)";
446 } else {
447 stream() << "sourceRect: " << info.maskId << ".maskRect(0, 0,"
448 << info.id << ".originalBounds.width,"
449 << info.id << ".originalBounds.height)";
450 }
451 stream() << "width: sourceRect.width";
452 stream() << "height: sourceRect.height";
453 stream() << "smooth: false";
454 stream() << "visible: false";
455
456 m_indentLevel--;
457 stream() << "}";
458
459 stream() << "ShaderEffect {";
460 m_indentLevel++;
461
462 const QString maskShaderId = maskId + QStringLiteral("_se");
463 animatedItemId = maskShaderId;
464
465 stream() << "id:" << maskShaderId;
466
467 stream() << "property real sourceX: " << maskId << ".sourceRect.x";
468 stream() << "property real sourceY: " << maskId << ".sourceRect.y";
469 stream() << "width: " << maskId << ".sourceRect.width";
470 stream() << "height: " << maskId << ".sourceRect.height";
471
472 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
473 stream() << "property var source: " << seId;
474 stream() << "property var maskSource: " << maskId;
475 stream() << "property bool isAlpha: " << (info.isMaskAlpha ? "true" : "false");
476 stream() << "property bool isInverted: " << (info.isMaskInverted ? "true" : "false");
477 }
478
479 if (!info.isDefaultOpacity)
480 stream() << "opacity: " << info.opacity.defaultValue().toReal();
481
482 generateItemAnimations(animatedItemId, info);
483
484 m_indentLevel--;
485 stream() << "}";
486}
487
488bool QQuickQmlGenerator::generateDefsNode(const StructureNodeInfo &info)
489{
490 if (Q_UNLIKELY(errorState()))
491 return false;
492
493 if (info.stage == StructureNodeStage::Start) {
494 m_oldIndentLevels.push(m_indentLevel);
495
496 stream() << "Component {";
497 m_indentLevel++;
498
499 stream() << "id: " << info.id << "_container";
500
501 stream() << "Item {";
502 m_indentLevel++;
503
504 generateTimelineFields(info);
505 if (!info.transformReferenceChildId.isEmpty()) {
506 stream() << "property alias transformMatrix: "
507 << info.transformReferenceChildId << ".transformMatrix";
508 }
509
510 generateNodeBase(info, QStringLiteral("_defs"));
511 } else {
512 generateNodeEnd(info);
513
514 m_indentLevel--;
515 stream() << "}"; // Component
516
517 stream() << m_defsSuffix;
518 m_defsSuffix.clear();
519
520 m_indentLevel = m_oldIndentLevels.pop();
521 }
522
523 return true;
524}
525
526void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
527{
528 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
529 return;
530
531 const QFileInfo outputFileInfo(outputFileName);
532 const QDir outputDir(outputFileInfo.absolutePath());
533
534 QString filePath;
535
536 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
537 filePath = m_assetFileDirectory;
538 if (filePath.isEmpty())
539 filePath = outputDir.absolutePath();
540
541 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
542 filePath += u'/';
543
544 QDir fileDir(filePath);
545 if (!fileDir.exists()) {
546 if (!fileDir.mkpath(QStringLiteral(".")))
547 qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath;
548 }
549
550 filePath += QStringLiteral("%1%2.png").arg(m_assetFilePrefix.isEmpty()
551 ? QStringLiteral("svg_asset_")
552 : m_assetFilePrefix)
553 .arg(info.image.cacheKey());
554
555 if (!info.image.save(filePath))
556 qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath;
557 qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath;
558 } else {
559 filePath = info.externalFileReference;
560 }
561
562 const QFileInfo assetFileInfo(filePath);
563
564 stream() << "Image {";
565
566 m_indentLevel++;
567 generateNodeBase(info);
568 stream() << "x: " << info.rect.x();
569 stream() << "y: " << info.rect.y();
570 stream() << "width: " << info.rect.width();
571 stream() << "height: " << info.rect.height();
572 stream() << "source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<"\"";
573 generateNodeEnd(info);
574}
575
576void QQuickQmlGenerator::generateMarkers(const PathNodeInfo &info)
577{
578 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
579 for (int i = 0; i < path.elementCount(); ++i) {
580 const QPainterPath::Element element = path.elementAt(i);
581 QString markerId;
582 qreal angle = 0;
583
584 // Copied from Qt SVG
585 auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
586 QPointF t1 = p1 - p0;
587 QPointF t2 = p2 - p1;
588 qreal hyp1 = hypot(t1.x(), t1.y());
589 if (hyp1 > 0)
590 t1 /= hyp1;
591 else
592 return 0.;
593 qreal hyp2 = hypot(t2.x(), t2.y());
594 if (hyp2 > 0)
595 t2 /= hyp2;
596 else
597 return 0.;
598 QPointF tangent = t1 + t2;
599 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
600 };
601
602 if (i == 0) {
603 markerId = info.markerStartId;
604 angle = path.angleAtPercent(0.0);
605 } else if (i == path.elementCount() - 1) {
606 markerId = info.markerEndId;
607 angle = path.angleAtPercent(1.0);
608 } else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
609 markerId = info.markerMidId;
610
611 const QPainterPath::Element prevElement = path.elementAt(i - 1);
612 const QPainterPath::Element nextElement = path.elementAt(i + 1);
613
614 QPointF p1(prevElement.x, prevElement.y);
615 QPointF p2(element.x, element.y);
616 QPointF p3(nextElement.x, nextElement.y);
617
618 angle = getMeanAngle(p1, p2, p3);
619 }
620
621 if (!markerId.isEmpty()) {
622 stream() << "Loader {";
623 m_indentLevel++;
624
625 //stream() << "clip: true";
626 stream() << "sourceComponent: " << markerId << "_container";
627 stream() << "property real strokeWidth: " << info.strokeStyle.width;
628 stream() << "transform: [";
629 m_indentLevel++;
630 if (i == 0) {
631 stream() << "Scale { "
632 << "xScale: " << markerId << "_markerParameters.startReversed ? -1 : 1; "
633 << "yScale: " << markerId << "_markerParameters.startReversed ? -1 : 1 },";
634 }
635 stream() << "Rotation { angle: " << markerId << "_markerParameters.autoAngle(" << -angle << ") },";
636 stream() << "Translate { x: " << element.x << "; y: " << element.y << "}";
637
638 m_indentLevel--;
639 stream() << "]";
640
641 m_indentLevel--;
642 stream() << "}";
643 }
644 }
645}
646
647void QQuickQmlGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
648{
649 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
650 return;
651
652 if (m_inShapeItemLevel > 0) {
653 if (!info.isDefaultTransform)
654 qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
655 optimizePaths(info, overrideBoundingRect);
656 } else {
657 m_inShapeItemLevel++;
658 stream() << shapeName() << " {";
659
660 m_indentLevel++;
661 generateNodeBase(info);
662
663 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
664 stream() << "preferredRendererType: Shape.CurveRenderer";
665 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
666 stream() << "asynchronous: true";
667 optimizePaths(info, overrideBoundingRect);
668 //qCDebug(lcQuickVectorGraphics) << *node->qpath();
669
670 if (!info.markerStartId.isEmpty()
671 || !info.markerMidId.isEmpty()
672 || !info.markerEndId.isEmpty()) {
673 generateMarkers(info);
674 }
675
676 generateNodeEnd(info);
677 m_inShapeItemLevel--;
678 }
679}
680
681void QQuickQmlGenerator::generateGradient(const QGradient *grad,
682 const QString &propertyName,
683 const QRectF &coordinateConversion)
684{
685 const QSizeF &scale = coordinateConversion.size();
686 const QPointF &translation = coordinateConversion.topLeft();
687
688 if (grad->type() == QGradient::LinearGradient) {
689 auto *linGrad = static_cast<const QLinearGradient *>(grad);
690 stream() << propertyName << ": LinearGradient {";
691 m_indentLevel++;
692
693 QRectF gradRect(linGrad->start(), linGrad->finalStop());
694
695 stream() << "x1: " << (gradRect.left() * scale.width()) + translation.x();
696 stream() << "y1: " << (gradRect.top() * scale.height()) + translation.y();
697 stream() << "x2: " << (gradRect.right() * scale.width()) + translation.x();
698 stream() << "y2: " << (gradRect.bottom() * scale.height()) + translation.y();
699 for (auto &stop : linGrad->stops())
700 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
701 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
702 } else if (grad->type() == QGradient::RadialGradient) {
703 auto *radGrad = static_cast<const QRadialGradient*>(grad);
704 stream() << propertyName << ": RadialGradient {";
705 m_indentLevel++;
706
707 stream() << "centerX: " << (radGrad->center().x() * scale.width()) + translation.x();
708 stream() << "centerY: " << (radGrad->center().y() * scale.height()) + translation.y();
709 stream() << "centerRadius: " << (radGrad->radius() * scale.width()); // ### ?
710 stream() << "focalX:" << (radGrad->focalPoint().x() * scale.width()) + translation.x();
711 stream() << "focalY:" << (radGrad->focalPoint().y() * scale.height()) + translation.y();
712 for (auto &stop : radGrad->stops())
713 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
714 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
715 }
716
717 stream() << "spread: ShapeGradient.";
718 switch (grad->spread()) {
719 case QGradient::PadSpread:
720 stream(SameLine) << "PadSpread";
721 break;
722 case QGradient::ReflectSpread:
723 stream(SameLine) << "ReflectSpread";
724 break;
725 case QGradient::RepeatSpread:
726 stream(SameLine) << "RepeatSpread";
727 break;
728 }
729
730 m_indentLevel--;
731 stream() << "}";
732}
733
734void QQuickQmlGenerator::generateAnimationBindings()
735{
736 QString prefix;
737 if (Q_UNLIKELY(!isRuntimeGenerator()))
738 prefix = QStringLiteral(".animations");
739
740 stream() << "loops: " << m_topLevelIdString << prefix << ".loops";
741 stream() << "paused: " << m_topLevelIdString << prefix << ".paused";
742 stream() << "running: true";
743
744 // We need to reset the animation when the loop count changes
745 stream() << "onLoopsChanged: { if (running) { restart() } }";
746}
747
748void QQuickQmlGenerator::generateEasing(const QQuickAnimatedProperty::PropertyAnimation &animation,
749 int time, int streamFlags)
750{
751 if (animation.easingPerFrame.contains(time)) {
752 QBezier bezier = animation.easingPerFrame.value(time);
753 QPointF c1 = bezier.pt2();
754 QPointF c2 = bezier.pt3();
755
756 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
757 if (!isLinear) {
758 int nextIdx = m_easings.size();
759 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
760 if (id.isNull())
761 id = QString(QLatin1String("easing_%1")).arg(nextIdx, 2, 10, QLatin1Char('0'));
762 if (streamFlags & SameLine)
763 stream(streamFlags) << "; ";
764 stream(streamFlags) << "easing: " << m_topLevelIdString << "." << id;
765 }
766 }
767}
768
769static int processAnimationTime(int time)
770{
771 static qreal multiplier = qreal(qEnvironmentVariable("QT_QUICKVECTORIMAGE_TIME_DILATION", QStringLiteral("1.0"))
772 .toDouble());
773 return std::round(multiplier * time);
774}
775
776void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty &property,
777 const QString &targetName,
778 const QString &propertyName,
779 AnimationType animationType)
780{
781 if (!property.isAnimated())
782 return;
783
784 if (usingTimelineAnimation())
785 return generatePropertyTimeline(property, targetName, propertyName, animationType);
786
787 QString mainAnimationId = targetName
788 + QStringLiteral("_")
789 + propertyName
790 + QStringLiteral("_animation");
791 mainAnimationId.replace(QLatin1Char('.'), QLatin1Char('_'));
792
793 QString prefix;
794 if (Q_UNLIKELY(!isRuntimeGenerator()))
795 prefix = QStringLiteral(".animations");
796
797 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
798
799 stream() << "ParallelAnimation {";
800 m_indentLevel++;
801
802 stream() << "id: " << mainAnimationId;
803
804 generateAnimationBindings();
805
806 for (int i = 0; i < property.animationCount(); ++i) {
807 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
808
809 stream() << "SequentialAnimation {";
810 m_indentLevel++;
811
812 const int startOffset = processAnimationTime(animation.startOffset);
813 if (startOffset > 0)
814 stream() << "PauseAnimation { duration: " << startOffset << " }";
815
816 stream() << "SequentialAnimation {";
817 m_indentLevel++;
818
819 const int repeatCount = animation.repeatCount;
820 if (repeatCount < 0)
821 stream() << "loops: Animation.Infinite";
822 else
823 stream() << "loops: " << repeatCount;
824
825 int previousTime = 0;
826 QVariant previousValue;
827 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
828 const int time = it.key();
829 const int frameTime = processAnimationTime(time - previousTime);
830 const QVariant &value = it.value();
831
832 if (previousValue.isValid() && previousValue == value) {
833 if (frameTime > 0)
834 stream() << "PauseAnimation { duration: " << frameTime << " }";
835 } else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
836 // We special case bools, with PauseAnimation and then a setter at the end
837 if (frameTime > 0)
838 stream() << "PauseAnimation { duration: " << frameTime << " }";
839 stream() << "ScriptAction {";
840 m_indentLevel++;
841
842 stream() << "script:" << targetName << "." << propertyName << " = " << value.toString();
843
844 m_indentLevel--;
845 stream() << "}";
846 } else {
847 generateAnimatedPropertySetter(targetName,
848 propertyName,
849 value,
850 animation,
851 frameTime,
852 time,
853 animationType);
854 }
855
856 previousTime = time;
857 previousValue = value;
858 }
859
860 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
861 stream() << "ScriptAction {";
862 m_indentLevel++;
863 stream() << "script: ";
864
865 switch (animationType) {
866 case AnimationType::Auto:
867 stream(SameLine) << targetName << "." << propertyName << " = ";
868 break;
869 case AnimationType::ColorOpacity:
870 stream(SameLine) << targetName << "." << propertyName << ".a = ";
871 break;
872 };
873
874 QVariant value = property.defaultValue();
875 if (value.typeId() == QMetaType::QColor)
876 stream(SameLine) << "\"" << value.toString() << "\"";
877 else
878 stream(SameLine) << value.toReal();
879
880 m_indentLevel--;
881 stream() << "}";
882 }
883
884 m_indentLevel--;
885 stream() << "}";
886
887 m_indentLevel--;
888 stream() << "}";
889 }
890
891 m_indentLevel--;
892 stream() << "}";
893}
894
895void QQuickQmlGenerator::generateTimelinePropertySetter(
896 const QString &targetName,
897 const QString &propertyName,
898 const QQuickAnimatedProperty::PropertyAnimation &animation,
899 std::function<QVariant(const QVariant &)> const& extractValue,
900 int valueIndex)
901{
902 if (animation.repeatCount != 1 || animation.startOffset
903 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
904 qCWarning(lcQuickVectorImage) << "Animation feature not implemented in timeline mode, for"
905 << targetName << propertyName;
906 }
907
908 stream() << "KeyframeGroup {";
909 m_indentLevel++;
910 stream() << "target: " << targetName;
911 stream() << "property: \"" << propertyName << "\"";
912
913 for (const auto &[frame, rawValue] : animation.frames.asKeyValueRange()) {
914 QVariant value;
915 if (rawValue.typeId() == QMetaType::QVariantList)
916 value = extractValue(rawValue.toList().value(valueIndex));
917 else
918 value = extractValue(rawValue);
919
920 stream() << "Keyframe { frame: " << frame << "; value: ";
921 if (value.typeId() == QMetaType::QVector3D) {
922 const QVector3D &v = value.value<QVector3D>();
923 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
924 } else if (value.typeId() == QMetaType::QColor) {
925 stream(SameLine) << "\"" << value.toString() << "\"";
926 } else {
927 stream(SameLine) << value.toReal();
928 }
929 generateEasing(animation, frame, SameLine);
930 stream(SameLine) << " }";
931 }
932
933 m_indentLevel--;
934 stream() << "}";
935}
936
937void QQuickQmlGenerator::generatePropertyTimeline(const QQuickAnimatedProperty &property,
938 const QString &targetName,
939 const QString &propertyName,
940 AnimationType animationType)
941{
942 if (animationType == QQuickQmlGenerator::AnimationType::ColorOpacity) {
943 qCWarning(lcQuickVectorImage) << "ColorOpacity animation not available in timeline mode";
944 return;
945 }
946
947 if (property.animationGroupCount() > 1 || property.animationCount() > 1) {
948 qCWarning(lcQuickVectorImage) << "Property feature not implemented in timeline mode, for"
949 << targetName << propertyName;
950 }
951
952 stream() << "Timeline {";
953 m_indentLevel++;
954 stream() << "currentFrame: " << property.timelineReferenceId() << ".frameCounter";
955 stream() << "enabled: true";
956
957 auto extractor = [](const QVariant &value) { return value; };
958 generateTimelinePropertySetter(targetName, propertyName, property.animation(0), extractor);
959
960 m_indentLevel--;
961 stream() << "}";
962}
963
964void QQuickQmlGenerator::generateTransform(const QTransform &xf)
965{
966 if (xf.isAffine()) {
967 stream(SameLine) << "PlanarTransform.fromAffineMatrix("
968 << xf.m11() << ", " << xf.m12() << ", "
969 << xf.m21() << ", " << xf.m22() << ", "
970 << xf.dx() << ", " << xf.dy() << ")";
971 } else {
972 QMatrix4x4 m(xf);
973 stream(SameLine) << "Qt.matrix4x4(";
974 m_indentLevel += 3;
975 const auto *data = m.data();
976 for (int i = 0; i < 4; i++) {
977 stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12];
978 if (i < 3)
979 stream(SameLine) << ", ";
980 }
981 stream(SameLine) << ")";
982 m_indentLevel -= 3;
983 }
984}
985
986void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
987{
988 Q_UNUSED(pathSelector)
989 Q_ASSERT(painterPath || quadPath);
990
991 if (Q_UNLIKELY(errorState()))
992 return;
993
994 const bool invalidGradientBounds = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
995 && (qFuzzyIsNull(boundingRect.width()) ||
996 qFuzzyIsNull(boundingRect.height()));
997
998 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
999 const bool noPen = (strokeColor == QColorConstants::Transparent || !strokeColor.isValid())
1000 && !info.strokeStyle.color.isAnimated()
1001 && !info.strokeStyle.opacity.isAnimated()
1002 && (info.strokeGrad.type() == QGradient::NoGradient
1003 || invalidGradientBounds);
1004 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
1005 return;
1006
1007 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
1008 const bool noFill = info.grad.type() == QGradient::NoGradient
1009 && fillColor == QColorConstants::Transparent
1010 && !info.fillColor.isAnimated()
1011 && !info.fillOpacity.isAnimated();
1012 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
1013 return;
1014
1015 if (noPen && noFill)
1016 return;
1017 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
1018 stream() << "ShapePath {";
1019 m_indentLevel++;
1020
1021 QString shapePathId = info.id;
1022 if (pathSelector & QQuickVectorImageGenerator::FillPath)
1023 shapePathId += QStringLiteral("_fill");
1024 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
1025 shapePathId += QStringLiteral("_stroke");
1026
1027 stream() << "id: " << shapePathId;
1028
1029 if (!info.nodeId.isEmpty()) {
1030 switch (pathSelector) {
1031 case QQuickVectorImageGenerator::FillPath:
1032 stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
1033 break;
1034 case QQuickVectorImageGenerator::StrokePath:
1035 stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
1036 break;
1037 case QQuickVectorImageGenerator::FillAndStroke:
1038 stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
1039 break;
1040 }
1041 }
1042
1043 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
1044 stream() << "strokeColor: \"transparent\"";
1045 } else {
1046 if (info.strokeGrad.type() != QGradient::NoGradient && !invalidGradientBounds) {
1047 QRectF coordinateSys = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
1048 ? boundingRect
1049 : QRectF(0.0, 0.0, 1.0, 1.0);
1050 generateGradient(&info.strokeGrad, QStringLiteral("strokeGradient"), coordinateSys);
1051 } else if (info.strokeStyle.opacity.isAnimated()) {
1052 stream() << "property color strokeBase: \"" << strokeColor.name(QColor::HexArgb) << "\"";
1053 stream() << "property real strokeOpacity: " << info.strokeStyle.opacity.defaultValue().toReal();
1054 stream() << "strokeColor: Qt.rgba(strokeBase.r, strokeBase.g, strokeBase.b, strokeOpacity)";
1055 } else {
1056 stream() << "strokeColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
1057 }
1058 stream() << "strokeWidth: " << info.strokeStyle.width;
1059 stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
1060 stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
1061 stream() << "miterLimit: " << info.strokeStyle.miterLimit;
1062 if (info.strokeStyle.cosmetic)
1063 stream() << "cosmeticStroke: true";
1064 if (info.strokeStyle.dashArray.length() != 0) {
1065 stream() << "strokeStyle: " << "ShapePath.DashLine";
1066 stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
1067 stream() << "dashOffset: " << info.strokeStyle.dashOffset;
1068 }
1069 }
1070
1071 QTransform fillTransform = info.fillTransform;
1072 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
1073 stream() << "fillColor: \"transparent\"";
1074 } else if (info.grad.type() != QGradient::NoGradient) {
1075 generateGradient(&info.grad, QStringLiteral("fillGradient"));
1076
1077 // Scaling done via fillTransform to get correct order of operations
1078 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
1079 QTransform objectToUserSpace;
1080 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
1081 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
1082 fillTransform *= objectToUserSpace;
1083 }
1084 } else {
1085 if (info.fillOpacity.isAnimated()) {
1086 stream() << "property color fillBase: \"" << fillColor.name(QColor::HexArgb) << "\"";
1087 stream() << "property real fillOpacity:" << info.fillOpacity.defaultValue().toReal();
1088 stream() << "fillColor: Qt.rgba(fillBase.r, fillBase.g, fillBase.b, fillOpacity)";
1089 } else {
1090 stream() << "fillColor: \"" << fillColor.name(QColor::HexArgb) << "\"";
1091 }
1092 }
1093
1094 if (!info.patternId.isEmpty()) {
1095 stream() << "fillItem: ShaderEffectSource {";
1096 m_indentLevel++;
1097
1098 stream() << "parent: " << info.id;
1099 stream() << "sourceItem: " << info.patternId;
1100 stream() << "hideSource: true";
1101 stream() << "visible: false";
1102 stream() << "width: " << info.patternId << ".width";
1103 stream() << "height: " << info.patternId << ".height";
1104 stream() << "wrapMode: ShaderEffectSource.Repeat";
1105 stream() << "textureSize: Qt.size(width * __qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1106 << "height * __qt_toplevel_scale_itemspy.requiredTextureSize.height)";;
1107 stream() << "sourceRect: " << info.patternId << ".sourceRect("
1108 << info.id << ".width, "
1109 << info.id << ".height)";
1110
1111 m_indentLevel--;
1112 stream() << "}";
1113
1114 // Fill transform has to include the inverse of the scene scale, since the texture size
1115 // is scaled by this amount
1116 stream() << "function calculateFillTransform(xScale, yScale) {";
1117 m_indentLevel++;
1118
1119 stream() << "var m = ";
1120 generateTransform(fillTransform);
1121
1122 stream() << "m.translate(" << info.patternId << ".sourceOffset("
1123 << info.id << ".width, "
1124 << info.id << ".height))";
1125
1126 stream() << "m.scale(1.0 / xScale, 1.0 / yScale, 1.0)";
1127 stream() << "return m";
1128
1129 m_indentLevel--;
1130 stream() << "}";
1131
1132 stream() << "fillTransform: calculateFillTransform(__qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1133 << "__qt_toplevel_scale_itemspy.requiredTextureSize.height)";
1134
1135 } else if (!fillTransform.isIdentity()) {
1136 const QTransform &xf = fillTransform;
1137 stream() << "fillTransform: ";
1138 if (info.fillTransform.type() == QTransform::TxTranslate)
1139 stream(SameLine) << "PlanarTransform.fromTranslate(" << xf.dx() << ", " << xf.dy() << ")";
1140 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
1141 stream(SameLine) << "PlanarTransform.fromScale(" << xf.m11() << ", " << xf.m22() << ")";
1142 else
1143 generateTransform(xf);
1144 }
1145
1146 if (info.trim.enabled) {
1147 stream() << "trim.start: " << info.trim.start.defaultValue().toReal();
1148 stream() << "trim.end: " << info.trim.end.defaultValue().toReal();
1149 stream() << "trim.offset: " << info.trim.offset.defaultValue().toReal();
1150
1151 }
1152
1153 if (fillRule == QQuickShapePath::WindingFill)
1154 stream() << "fillRule: ShapePath.WindingFill";
1155 else
1156 stream() << "fillRule: ShapePath.OddEvenFill";
1157
1158 QString hintStr;
1159 if (quadPath)
1160 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
1161 if (!hintStr.isEmpty())
1162 stream() << hintStr;
1163
1164 QQuickAnimatedProperty pathFactor(QVariant::fromValue(0));
1165 pathFactor.setTimelineReferenceId(info.path.timelineReferenceId());
1166 QString pathId = shapePathId + "_ip"_L1;
1167 if (!info.path.isAnimated() || (info.path.animation(0).startOffset == 0 && info.path.animation(0).isConstant())) {
1168 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
1169 stream() << "PathSvg { path: \"" << svgPathString << "\" }";
1170 } else {
1171 stream() << "PathInterpolated {";
1172 m_indentLevel++;
1173 stream() << "id: " << pathId;
1174 stream() << "svgPaths: [";
1175 m_indentLevel++;
1176 QQuickAnimatedProperty::PropertyAnimation pathFactorAnim = info.path.animation(0);
1177 auto &frames = pathFactorAnim.frames;
1178 int pathIdx = -1;
1179 QString lastSvg;
1180 for (auto it = frames.begin(); it != frames.end(); ++it) {
1181 QString svg = QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
1182 if (svg != lastSvg) {
1183 if (pathIdx >= 0)
1184 stream(SameLine) << ",";
1185 stream() << "\"" << svg << "\"";
1186 ++pathIdx;
1187 lastSvg = svg;
1188 }
1189 *it = QVariant::fromValue(pathIdx);
1190 }
1191 pathFactor.addAnimation(pathFactorAnim);
1192 m_indentLevel--;
1193 stream() << "]";
1194 m_indentLevel--;
1195 stream() << "}";
1196 }
1197
1198 m_indentLevel--;
1199 stream() << "}";
1200
1201 if (pathFactor.isAnimated())
1202 generatePropertyAnimation(pathFactor, pathId, "factor"_L1);
1203
1204 if (info.trim.enabled) {
1205 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(".trim"), QStringLiteral("start"));
1206 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(".trim"), QStringLiteral("end"));
1207 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(".trim"), QStringLiteral("offset"));
1208 }
1209
1210 if (info.strokeStyle.opacity.isAnimated()) {
1211 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeBase"));
1212 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral("strokeOpacity"));
1213 } else {
1214 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeColor"));
1215 }
1216 if (info.fillOpacity.isAnimated()) {
1217 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillBase"));
1218 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral("fillOpacity"));
1219 } else {
1220 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillColor"));
1221 }
1222}
1223
1224void QQuickQmlGenerator::generateNode(const NodeInfo &info)
1225{
1226 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1227 return;
1228
1229 stream() << "// Missing Implementation for SVG Node: " << info.typeName;
1230 stream() << "// Adding an empty Item and skipping";
1231 stream() << "Item {";
1232 m_indentLevel++;
1233 generateNodeBase(info);
1234 generateNodeEnd(info);
1235}
1236
1237void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
1238{
1239 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1240 return;
1241
1242 static int counter = 0;
1243 stream() << "Item {";
1244 m_indentLevel++;
1245 generateNodeBase(info);
1246
1247 if (!info.isTextArea)
1248 stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
1249
1250 stream() << "Text {";
1251
1252 m_indentLevel++;
1253
1254 const QString textItemId = QStringLiteral("_qt_textItem_%1").arg(counter);
1255 stream() << "id: " << textItemId;
1256
1257 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral("color"));
1258 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral("color"), AnimationType::ColorOpacity);
1259 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral("styleColor"));
1260 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral("styleColor"), AnimationType::ColorOpacity);
1261
1262 if (info.isTextArea) {
1263 stream() << "x: " << info.position.x();
1264 stream() << "y: " << info.position.y();
1265 if (info.size.width() > 0)
1266 stream() << "width: " << info.size.width();
1267 if (info.size.height() > 0)
1268 stream() << "height: " << info.size.height();
1269 stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
1270 stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
1271 } else {
1272 QString hAlign = QStringLiteral("left");
1273 stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
1274 switch (info.alignment) {
1275 case Qt::AlignHCenter:
1276 hAlign = QStringLiteral("horizontalCenter");
1277 break;
1278 case Qt::AlignRight:
1279 hAlign = QStringLiteral("right");
1280 break;
1281 default:
1282 qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
1283 Q_FALLTHROUGH();
1284 case Qt::AlignLeft:
1285 break;
1286 }
1287 stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
1288 }
1289 counter++;
1290
1291 stream() << "color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) << "\"";
1292 stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText");
1293
1294 stream() << "text: \"" << sanitizeString(info.text) << "\"";
1295 stream() << "font.family: \"" << sanitizeString(info.font.family()) << "\"";
1296 if (info.font.pixelSize() > 0)
1297 stream() << "font.pixelSize:" << info.font.pixelSize();
1298 else if (info.font.pointSize() > 0)
1299 stream() << "font.pixelSize:" << info.font.pointSizeF();
1300 if (info.font.underline())
1301 stream() << "font.underline: true";
1302 if (info.font.weight() != QFont::Normal)
1303 stream() << "font.weight: " << int(info.font.weight());
1304 if (info.font.italic())
1305 stream() << "font.italic: true";
1306 switch (info.font.hintingPreference()) {
1307 case QFont::PreferFullHinting:
1308 stream() << "font.hintingPreference: Font.PreferFullHinting";
1309 break;
1310 case QFont::PreferVerticalHinting:
1311 stream() << "font.hintingPreference: Font.PreferVerticalHinting";
1312 break;
1313 case QFont::PreferNoHinting:
1314 stream() << "font.hintingPreference: Font.PreferNoHinting";
1315 break;
1316 case QFont::PreferDefaultHinting:
1317 stream() << "font.hintingPreference: Font.PreferDefaultHinting";
1318 break;
1319 };
1320
1321 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
1322 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
1323 stream() << "styleColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
1324 stream() << "style: Text.Outline";
1325 }
1326
1327 m_indentLevel--;
1328 stream() << "}";
1329
1330 generateNodeEnd(info);
1331}
1332
1333void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
1334{
1335 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1336 return;
1337
1338 if (info.stage == StructureNodeStage::Start) {
1339 stream() << "Item {";
1340 m_indentLevel++;
1341 generateNodeBase(info);
1342 } else {
1343 generateNodeEnd(info);
1344 }
1345}
1346
1347void QQuickQmlGenerator::generatePathContainer(const StructureNodeInfo &info)
1348{
1349 Q_UNUSED(info);
1350 stream() << shapeName() <<" {";
1351 m_indentLevel++;
1352 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
1353 stream() << "preferredRendererType: Shape.CurveRenderer";
1354 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
1355 stream() << "asynchronous: true";
1356 m_indentLevel--;
1357
1358 m_inShapeItemLevel++;
1359}
1360
1361void QQuickQmlGenerator::generateAnimateMotionPath(const QString &targetName,
1362 const QQuickAnimatedProperty &property)
1363{
1364 if (!property.isAnimated())
1365 return;
1366
1367 QPainterPath path = property.defaultValue().value<QVariantList>().value(0).value<QPainterPath>();
1368 const QString mainAnimationId = targetName + QStringLiteral("_motion_interpolator");
1369 stream() << "PathInterpolator {";
1370 m_indentLevel++;
1371 stream() << "id: " << mainAnimationId;
1372 const QString svgPathString = QQuickVectorImageGenerator::Utils::toSvgString(path);
1373 stream() << "path: Path { PathSvg { path: \"" << svgPathString << "\" } }";
1374 m_indentLevel--;
1375 stream() << "}";
1376
1377 generatePropertyAnimation(property, mainAnimationId, QStringLiteral("progress"));
1378}
1379
1380void QQuickQmlGenerator::generateAnimatedPropertySetter(const QString &targetName,
1381 const QString &propertyName,
1382 const QVariant &value,
1383 const QQuickAnimatedProperty::PropertyAnimation &animation,
1384 int frameTime,
1385 int time,
1386 AnimationType animationType)
1387{
1388 if (frameTime > 0) {
1389 switch (animationType) {
1390 case AnimationType::Auto:
1391 if (value.typeId() == QMetaType::QColor)
1392 stream() << "ColorAnimation {";
1393 else
1394 stream() << "PropertyAnimation {";
1395 break;
1396 case AnimationType::ColorOpacity:
1397 stream() << "ColorOpacityAnimation {";
1398 break;
1399 };
1400 m_indentLevel++;
1401
1402 stream() << "duration: " << frameTime;
1403 stream() << "target: " << targetName;
1404 stream() << "property: \"" << propertyName << "\"";
1405 stream() << "to: ";
1406 if (value.typeId() == QMetaType::QVector3D) {
1407 const QVector3D &v = value.value<QVector3D>();
1408 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
1409 } else if (value.typeId() == QMetaType::QColor) {
1410 stream(SameLine) << "\"" << value.toString() << "\"";
1411 } else {
1412 stream(SameLine) << value.toReal();
1413 }
1414 generateEasing(animation, time);
1415 m_indentLevel--;
1416 stream() << "}";
1417 } else {
1418 stream() << "ScriptAction {";
1419 m_indentLevel++;
1420 stream() << "script:" << targetName << "." << propertyName;
1421 if (animationType == AnimationType::ColorOpacity)
1422 stream(SameLine) << ".a";
1423
1424 stream(SameLine) << " = ";
1425 if (value.typeId() == QMetaType::QVector3D) {
1426 const QVector3D &v = value.value<QVector3D>();
1427 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
1428 } else if (value.typeId() == QMetaType::QColor) {
1429 stream(SameLine) << "\"" << value.toString() << "\"";
1430 } else {
1431 stream(SameLine) << value.toReal();
1432 }
1433 m_indentLevel--;
1434 stream() << "}";
1435 }
1436}
1437
1438void QQuickQmlGenerator::generateAnimateTransform(const QString &targetName, const NodeInfo &info)
1439{
1440 if (!info.transform.isAnimated())
1441 return;
1442
1443 if (usingTimelineAnimation())
1444 return generateTransformTimeline(targetName, info);
1445
1446 const QString mainAnimationId = targetName
1447 + QStringLiteral("_transform_animation");
1448
1449 QString prefix;
1450 if (Q_UNLIKELY(!isRuntimeGenerator()))
1451 prefix = QStringLiteral(".animations");
1452 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
1453
1454 stream() << "ParallelAnimation {";
1455 m_indentLevel++;
1456
1457 stream() << "id:" << mainAnimationId;
1458
1459 generateAnimationBindings();
1460 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
1461 int animationStart = info.transform.animationGroup(groupIndex);
1462 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
1463 ? info.transform.animationGroup(groupIndex + 1)
1464 : info.transform.animationCount();
1465
1466 // The first animation in the group holds the shared properties for the whole group
1467 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
1468 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1469 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1470
1471 stream() << "SequentialAnimation {";
1472 m_indentLevel++;
1473
1474 const int startOffset = processAnimationTime(firstAnimation.startOffset);
1475 if (startOffset > 0)
1476 stream() << "PauseAnimation { duration: " << startOffset << " }";
1477
1478 const int repeatCount = firstAnimation.repeatCount;
1479 if (repeatCount < 0)
1480 stream() << "loops: Animation.Infinite";
1481 else
1482 stream() << "loops: " << repeatCount;
1483
1484 if (replace) {
1485 stream() << "ScriptAction {";
1486 m_indentLevel++;
1487
1488 stream() << "script: " << targetName << "_transform_base_group"
1489 << ".activateOverride(" << targetName << "_transform_group_" << groupIndex << ")";
1490
1491 m_indentLevel--;
1492 stream() << "}";
1493 }
1494
1495 stream() << "ParallelAnimation {";
1496 m_indentLevel++;
1497
1498 for (int i = animationStart; i < nextAnimationStart; ++i) {
1499 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1500 if (animation.isConstant())
1501 continue;
1502 bool hasRotationCenter = false;
1503 if (animation.subtype == QTransform::TxRotate) {
1504 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1505 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1506 if (!center.isNull()) {
1507 hasRotationCenter = true;
1508 break;
1509 }
1510 }
1511 }
1512
1513 stream() << "SequentialAnimation {";
1514 m_indentLevel++;
1515
1516 int previousTime = 0;
1517 QVariantList previousParameters;
1518 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1519 const int time = it.key();
1520 const int frameTime = processAnimationTime(time - previousTime);
1521 const QVariantList &parameters = it.value().value<QVariantList>();
1522 if (parameters.isEmpty())
1523 continue;
1524
1525 if (parameters == previousParameters) {
1526 if (frameTime > 0)
1527 stream() << "PauseAnimation { duration: " << frameTime << " }";
1528 } else {
1529 stream() << "ParallelAnimation {";
1530 m_indentLevel++;
1531
1532 const QString propertyTargetName = targetName
1533 + QStringLiteral("_transform_")
1534 + QString::number(groupIndex)
1535 + QStringLiteral("_")
1536 + QString::number(i);
1537
1538 switch (animation.subtype) {
1539 case QTransform::TxTranslate:
1540 {
1541 const QPointF translation = parameters.first().value<QPointF>();
1542
1543 generateAnimatedPropertySetter(propertyTargetName,
1544 QStringLiteral("x"),
1545 translation.x(),
1546 animation,
1547 frameTime,
1548 time);
1549 generateAnimatedPropertySetter(propertyTargetName,
1550 QStringLiteral("y"),
1551 translation.y(),
1552 animation,
1553 frameTime,
1554 time);
1555 break;
1556 }
1557 case QTransform::TxScale:
1558 {
1559 const QPointF scale = parameters.first().value<QPointF>();
1560 generateAnimatedPropertySetter(propertyTargetName,
1561 QStringLiteral("xScale"),
1562 scale.x(),
1563 animation,
1564 frameTime,
1565 time);
1566 generateAnimatedPropertySetter(propertyTargetName,
1567 QStringLiteral("yScale"),
1568 scale.y(),
1569 animation,
1570 frameTime,
1571 time);
1572 break;
1573 }
1574 case QTransform::TxRotate:
1575 {
1576 Q_ASSERT(parameters.size() == 2);
1577 const qreal angle = parameters.value(1).toReal();
1578 if (hasRotationCenter) {
1579 const QPointF center = parameters.value(0).value<QPointF>();
1580 generateAnimatedPropertySetter(propertyTargetName,
1581 QStringLiteral("origin"),
1582 QVector3D(center.x(), center.y(), 0.0),
1583 animation,
1584 frameTime,
1585 time);
1586 }
1587 generateAnimatedPropertySetter(propertyTargetName,
1588 QStringLiteral("angle"),
1589 angle,
1590 animation,
1591 frameTime,
1592 time);
1593 break;
1594 }
1595 case QTransform::TxShear:
1596 {
1597 const QPointF skew = parameters.first().value<QPointF>();
1598
1599 generateAnimatedPropertySetter(propertyTargetName,
1600 QStringLiteral("xAngle"),
1601 skew.x(),
1602 animation,
1603 frameTime,
1604 time);
1605
1606 generateAnimatedPropertySetter(propertyTargetName,
1607 QStringLiteral("yAngle"),
1608 skew.y(),
1609 animation,
1610 frameTime,
1611 time);
1612 break;
1613 }
1614 default:
1615 Q_UNREACHABLE();
1616 }
1617
1618 m_indentLevel--;
1619 stream() << "}"; // Parallel key frame animation
1620 }
1621
1622 previousTime = time;
1623 previousParameters = parameters;
1624 }
1625
1626 m_indentLevel--;
1627 stream() << "}"; // Parallel key frame animation
1628 }
1629
1630 m_indentLevel--;
1631 stream() << "}"; // Parallel key frame animation
1632
1633 // If the animation ever finishes, then we add an action on the end that handles itsr
1634 // freeze state
1635 if (firstAnimation.repeatCount >= 0) {
1636 stream() << "ScriptAction {";
1637 m_indentLevel++;
1638
1639 stream() << "script: {";
1640 m_indentLevel++;
1641
1642 if (!freeze) {
1643 stream() << targetName << "_transform_base_group.deactivate("
1644 << targetName << "_transform_group_" << groupIndex << ")";
1645 } else if (!replace) {
1646 stream() << targetName << "_transform_base_group.deactivateOverride("
1647 << targetName << "_transform_group_" << groupIndex << ")";
1648 }
1649
1650 m_indentLevel--;
1651 stream() << "}";
1652
1653 m_indentLevel--;
1654 stream() << "}";
1655 }
1656
1657 m_indentLevel--;
1658 stream() << "}";
1659 }
1660
1661 m_indentLevel--;
1662 stream() << "}";
1663}
1664
1665void QQuickQmlGenerator::generateTransformTimeline(const QString &targetName, const NodeInfo &info)
1666{
1667 stream() << "Timeline {";
1668 m_indentLevel++;
1669 stream() << "currentFrame: " << info.transform.timelineReferenceId() << ".frameCounter";
1670 stream() << "enabled: true";
1671
1672 const int groupIndex = 0;
1673 for (int i = 0; i < info.transform.animationCount(); ++i) {
1674 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1675 if (animation.isConstant())
1676 continue;
1677 if (info.transform.animationGroupCount() > 1
1678 || animation.repeatCount != 1 || animation.startOffset
1679 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
1680 qCWarning(lcQuickVectorImage) << "Feature not implemented in timeline xf animation mode, for"
1681 << targetName << "subtype" << animation.subtype;
1682 }
1683
1684 bool hasRotationCenter = false;
1685 if (animation.subtype == QTransform::TxRotate) {
1686 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1687 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1688 if (!center.isNull()) {
1689 hasRotationCenter = true;
1690 break;
1691 }
1692 }
1693 }
1694
1695 auto pointFxExtractor = [](const QVariant &value) { return value.toPointF().x(); };
1696 auto pointFyExtractor = [](const QVariant &value) { return value.toPointF().y(); };
1697 auto realExtractor = [](const QVariant &value) { return value.toReal(); };
1698 auto pointFtoVector3dExtractor = [](const QVariant &v) { return QVector3D(v.toPointF()); };
1699
1700 const QString propertyTargetName = targetName
1701 + QStringLiteral("_transform_")
1702 + QString::number(groupIndex)
1703 + QStringLiteral("_")
1704 + QString::number(i);
1705
1706 switch (animation.subtype) {
1707 case QTransform::TxTranslate:
1708 generateTimelinePropertySetter(propertyTargetName, "x"_L1, animation, pointFxExtractor);
1709 generateTimelinePropertySetter(propertyTargetName, "y"_L1, animation, pointFyExtractor);
1710 break;
1711 case QTransform::TxScale:
1712 generateTimelinePropertySetter(propertyTargetName, "xScale"_L1, animation, pointFxExtractor);
1713 generateTimelinePropertySetter(propertyTargetName, "yScale"_L1, animation, pointFyExtractor);
1714 break;
1715 case QTransform::TxRotate:
1716 if (hasRotationCenter)
1717 generateTimelinePropertySetter(propertyTargetName, "origin"_L1, animation, pointFtoVector3dExtractor);
1718 generateTimelinePropertySetter(propertyTargetName, "angle"_L1, animation, realExtractor, 1);
1719 break;
1720 case QTransform::TxShear:
1721 generateTimelinePropertySetter(propertyTargetName, "xAngle"_L1, animation, pointFxExtractor);
1722 generateTimelinePropertySetter(propertyTargetName, "yAngle"_L1, animation, pointFyExtractor);
1723 break;
1724 }
1725 }
1726
1727 m_indentLevel--;
1728 stream() << "}";
1729}
1730
1731bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
1732{
1733 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1734 return false;
1735
1736 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1737 if (info.stage == StructureNodeStage::Start) {
1738 if (!info.clipBox.isEmpty()) {
1739 stream() << "Item { // Clip";
1740
1741 m_indentLevel++;
1742 stream() << "width: " << info.clipBox.width();
1743 stream() << "height: " << info.clipBox.height();
1744 stream() << "clip: true";
1745 }
1746
1747 if (isPathContainer) {
1748 generatePathContainer(info);
1749 } else if (!info.customItemType.isEmpty()) {
1750 stream() << info.customItemType << " {";
1751 } else {
1752 stream() << "Item { // Structure node";
1753 }
1754 m_indentLevel++;
1755
1756 generateTimelineFields(info);
1757
1758 if (!info.viewBox.isEmpty()) {
1759 stream() << "transform: [";
1760 m_indentLevel++;
1761 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1762 if (translate)
1763 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
1764 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
1765 m_indentLevel--;
1766 stream() << "]";
1767 }
1768
1769 generateNodeBase(info);
1770 } else {
1771 generateNodeEnd(info);
1772 if (isPathContainer)
1773 m_inShapeItemLevel--;
1774
1775 if (!info.clipBox.isEmpty()) {
1776 m_indentLevel--;
1777 stream() << "}";
1778 }
1779 }
1780
1781 return true;
1782}
1783
1784bool QQuickQmlGenerator::generateMaskNode(const MaskNodeInfo &info)
1785{
1786 if (Q_UNLIKELY(errorState()))
1787 return false;
1788
1789 // Generate an invisible item subtree which can be used in ShaderEffectSource
1790 if (info.stage == StructureNodeStage::End) {
1791 // Generate code to add after defs block
1792 startDefsSuffixBlock();
1793 stream() << "Loader {";
1794 m_indentLevel++;
1795
1796 stream() << "id: " << info.id; // This is in a different scope, so we can reuse the ID
1797 stream() << "sourceComponent: " << info.id << "_container";
1798 stream() << "width: item !== null ? item.originalBounds.width : 0";
1799 stream() << "height: item !== null ? item.originalBounds.height : 0";
1800
1801 if (info.boundsReferenceId.isEmpty()) {
1802 stream() << "property real maskX: " << info.maskRect.left();
1803 stream() << "property real maskY: " << info.maskRect.top();
1804 stream() << "property real maskWidth: " << info.maskRect.width();
1805 stream() << "property real maskHeight: " << info.maskRect.height();
1806 }
1807
1808 stream() << "function maskRect(otherX, otherY, otherWidth, otherHeight) {";
1809 m_indentLevel++;
1810
1811 stream() << "return ";
1812 if (!info.boundsReferenceId.isEmpty()) {
1813 stream(SameLine) << info.boundsReferenceId << ".originalBounds";
1814 } else if (info.isMaskRectRelativeCoordinates) {
1815 stream(SameLine)
1816 << "Qt.rect("
1817 << info.id << ".maskX * otherWidth + otherX,"
1818 << info.id << ".maskY * otherHeight + otherY,"
1819 << info.id << ".maskWidth * otherWidth,"
1820 << info.id << ".maskHeight * otherHeight)";
1821 } else {
1822 stream(SameLine)
1823 << "Qt.rect("
1824 << info.id << ".maskX, "
1825 << info.id << ".maskY, "
1826 << info.id << ".maskWidth, "
1827 << info.id << ".maskHeight)";
1828 }
1829
1830 m_indentLevel--;
1831 stream() << "}";
1832
1833 m_indentLevel--;
1834 stream() << "}";
1835
1836 endDefsSuffixBlock();
1837 }
1838
1839 return true;
1840}
1841
1842void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
1843{
1844 if (Q_UNLIKELY(errorState()))
1845 return;
1846
1847 stream() << "Item {";
1848 m_indentLevel++;
1849
1850 generateNodeBase(info);
1851
1852 stream() << "property real originalWidth: filterSourceItem.sourceItem.originalBounds.width";
1853 stream() << "property real originalHeight: filterSourceItem.sourceItem.originalBounds.height";
1854 stream() << "property rect filterRect: " << info.id << "_filterParameters"
1855 << ".adaptToFilterRect(0, 0, originalWidth, originalHeight)";
1856
1857 for (qsizetype i = 0; i < info.steps.size();)
1858 i = generateFilterStep(info, i);
1859
1860 // Generate code to be added after defs block
1861 startDefsSuffixBlock();
1862 stream() << "QtObject {";
1863 m_indentLevel++;
1864
1865 stream() << "id: " << info.id << "_filterParameters";
1866 stream() << "property int wrapMode: ";
1867 if (info.wrapMode == QSGTexture::Repeat)
1868 stream(SameLine) << "ShaderEffectSource.Repeat";
1869 else
1870 stream(SameLine) << "ShaderEffectSource.ClampToEdge";
1871
1872 stream() << "property rect filterRect: Qt.rect("
1873 << info.filterRect.x() << ", "
1874 << info.filterRect.y() << ", "
1875 << info.filterRect.width() << ", "
1876 << info.filterRect.height() << ")";
1877
1878 stream() << "function adaptToFilterRect(sx, sy, sw, sh) {";
1879 m_indentLevel++;
1880
1881 if (info.csFilterRect == FilterNodeInfo::CoordinateSystem::Absolute) {
1882 stream() << "return Qt.rect(filterRect.x, filterRect.y, filterRect.width, filterRect.height)";
1883 } else {
1884 stream() << "return Qt.rect(sx + sw * filterRect.x, sy + sh * filterRect.y, sw * filterRect.width, sh * filterRect.height)";
1885 }
1886
1887 m_indentLevel--;
1888 stream() << "}";
1889
1890 m_indentLevel--;
1891 stream() << "}";
1892 endDefsSuffixBlock();
1893
1894 generateNodeEnd(info);
1895}
1896
1897qsizetype QQuickQmlGenerator::generateFilterStep(const FilterNodeInfo &info,
1898 qsizetype stepIndex)
1899{
1900 const FilterNodeInfo::FilterStep &step = info.steps.at(stepIndex);
1901 const QString primitiveId = info.id + QStringLiteral("_primitive") + QString::number(stepIndex);
1902
1903 stepIndex++;
1904
1905 QString inputId = step.input1 != FilterNodeInfo::FilterInput::SourceColor
1906 ? step.namedInput1
1907 : QStringLiteral("filterSourceItem");
1908
1909 bool isComposite = false;
1910 switch (step.filterType) {
1911 case FilterNodeInfo::Type::Merge:
1912 {
1913 const int maxNodeCount = 8;
1914
1915 // Find all nodes for this merge
1916 QList<QPair<FilterNodeInfo::FilterInput, QString> > inputs;
1917 for (; stepIndex < info.steps.size(); ++stepIndex) {
1918 const FilterNodeInfo::FilterStep &nodeStep = info.steps.at(stepIndex);
1919 if (nodeStep.filterType != FilterNodeInfo::Type::MergeNode)
1920 break;
1921
1922 inputs.append(qMakePair(nodeStep.input1, nodeStep.namedInput1));
1923 }
1924
1925 if (inputs.size() > maxNodeCount) {
1926 qCWarning(lcQuickVectorImage) << "Maximum of" << maxNodeCount
1927 << "nodes exceeded in merge effect.";
1928 }
1929
1930 if (inputs.isEmpty()) {
1931 qCWarning(lcQuickVectorImage) << "Merge effect requires at least one node.";
1932 break;
1933 }
1934
1935 stream() << "ShaderEffect {";
1936 m_indentLevel++;
1937
1938 stream() << "id: " << primitiveId;
1939 stream() << "visible: false";
1940
1941 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb\"";
1942 stream() << "width: source1.width";
1943 stream() << "height: source1.height";
1944 stream() << "property int sourceCount: " << std::min(qsizetype(8), inputs.size());
1945
1946 for (int i = 0; i < maxNodeCount; ++i) {
1947 auto input = i < inputs.size()
1948 ? inputs.at(i)
1949 : qMakePair(FilterNodeInfo::FilterInput::None, QStringLiteral("null"));
1950
1951 QString inputId = input.first != FilterNodeInfo::FilterInput::SourceColor
1952 ? input.second
1953 : QStringLiteral("filterSourceItem");
1954
1955 stream() << "property var source" << (i + 1) << ": " << inputId;
1956 }
1957
1958 m_indentLevel--;
1959 stream() << "}";
1960
1961 break;
1962 }
1963 case FilterNodeInfo::Type::CompositeOver:
1964 case FilterNodeInfo::Type::CompositeOut:
1965 case FilterNodeInfo::Type::CompositeIn:
1966 case FilterNodeInfo::Type::CompositeXor:
1967 case FilterNodeInfo::Type::CompositeAtop:
1968 case FilterNodeInfo::Type::CompositeArithmetic:
1969 case FilterNodeInfo::Type::CompositeLighter:
1970 isComposite = true;
1971 Q_FALLTHROUGH();
1972
1973 case FilterNodeInfo::Type::BlendNormal:
1974 case FilterNodeInfo::Type::BlendMultiply:
1975 case FilterNodeInfo::Type::BlendScreen:
1976 case FilterNodeInfo::Type::BlendDarken:
1977 case FilterNodeInfo::Type::BlendLighten:
1978 {
1979 stream() << "ShaderEffect {";
1980 m_indentLevel++;
1981
1982 QString input2Id = step.input2 != FilterNodeInfo::FilterInput::SourceColor
1983 ? step.namedInput2
1984 : QStringLiteral("filterSourceItem");
1985
1986 stream() << "id: " << primitiveId;
1987 stream() << "visible: false";
1988
1989 QString shader;
1990 switch (step.filterType) {
1991 case FilterNodeInfo::Type::CompositeOver:
1992 shader = QStringLiteral("fecompositeover");
1993 break;
1994 case FilterNodeInfo::Type::CompositeOut:
1995 shader = QStringLiteral("fecompositeout");
1996 break;
1997 case FilterNodeInfo::Type::CompositeIn:
1998 shader = QStringLiteral("fecompositein");
1999 break;
2000 case FilterNodeInfo::Type::CompositeXor:
2001 shader = QStringLiteral("fecompositexor");
2002 break;
2003 case FilterNodeInfo::Type::CompositeAtop:
2004 shader = QStringLiteral("fecompositeatop");
2005 break;
2006 case FilterNodeInfo::Type::CompositeArithmetic:
2007 shader = QStringLiteral("fecompositearithmetic");
2008 break;
2009 case FilterNodeInfo::Type::CompositeLighter:
2010 shader = QStringLiteral("fecompositelighter");
2011 break;
2012 case FilterNodeInfo::Type::BlendNormal:
2013 shader = QStringLiteral("feblendnormal");
2014 break;
2015 case FilterNodeInfo::Type::BlendMultiply:
2016 shader = QStringLiteral("feblendmultiply");
2017 break;
2018 case FilterNodeInfo::Type::BlendScreen:
2019 shader = QStringLiteral("feblendscreen");
2020 break;
2021 case FilterNodeInfo::Type::BlendDarken:
2022 shader = QStringLiteral("feblenddarken");
2023 break;
2024 case FilterNodeInfo::Type::BlendLighten:
2025 shader = QStringLiteral("feblendlighten");
2026 break;
2027 default:
2028 Q_UNREACHABLE();
2029 }
2030
2031 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"
2032 << shader << ".frag.qsb\"";
2033 stream() << "property var source: " << inputId;
2034 stream() << "property var source2: " << input2Id;
2035 stream() << "width: source.width";
2036 stream() << "height: source.height";
2037
2038 if (isComposite) {
2039 QVector4D k = step.filterParameter.value<QVector4D>();
2040 stream() << "property var k: Qt.vector4d("
2041 << k.x() << ", "
2042 << k.y() << ", "
2043 << k.z() << ", "
2044 << k.w() << ")";
2045 }
2046
2047 m_indentLevel--;
2048 stream() << "}";
2049
2050 break;
2051
2052 }
2053 case FilterNodeInfo::Type::Flood:
2054 {
2055 stream() << "Rectangle {";
2056 m_indentLevel++;
2057
2058 stream() << "id: " << primitiveId;
2059 stream() << "visible: false";
2060
2061 stream() << "width: " << inputId << ".width";
2062 stream() << "height: " << inputId << ".height";
2063
2064 QColor floodColor = step.filterParameter.value<QColor>();
2065 stream() << "color: \"" << floodColor.name(QColor::HexArgb) << "\"";
2066
2067 m_indentLevel--;
2068 stream() << "}";
2069
2070 break;
2071 }
2072 case FilterNodeInfo::Type::ColorMatrix:
2073 {
2074 stream() << "ShaderEffect {";
2075 m_indentLevel++;
2076
2077 stream() << "id: " << primitiveId;
2078 stream() << "visible: false";
2079
2080 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb\"";
2081 stream() << "property var source: " << inputId;
2082 stream() << "width: source.width";
2083 stream() << "height: source.height";
2084
2085 QGenericMatrix<5, 5, qreal> matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
2086 for (int row = 0; row < 4; ++row) { // Last row is ignored
2087
2088 // Qt SVG stores rows as columns, so we flip the coordinates
2089 for (int col = 0; col < 5; ++col)
2090 stream() << "property real m_" << row << "_" << col << ": " << matrix(col, row);
2091 }
2092
2093 m_indentLevel--;
2094 stream() << "}";
2095
2096 break;
2097 }
2098
2099 case FilterNodeInfo::Type::Offset:
2100 {
2101 stream() << "ShaderEffectSource {";
2102 m_indentLevel++;
2103
2104 stream() << "id: " << primitiveId;
2105 stream() << "visible: false";
2106 stream() << "sourceItem: " << inputId;
2107 stream() << "width: sourceItem.width + offset.x";
2108 stream() << "height: sourceItem.height + offset.y";
2109
2110 QVector2D offset = step.filterParameter.value<QVector2D>();
2111 stream() << "property vector2d offset: Qt.vector2d(";
2112 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute)
2113 stream(SameLine) << offset.x() << " / width, " << offset.y() << " / height)";
2114 else
2115 stream(SameLine) << offset.x() << ", " << offset.y() << ")";
2116
2117 stream() << "sourceRect: Qt.rect(-offset.x, -offset.y, width, height)";
2118
2119 stream() << "ItemSpy {";
2120 m_indentLevel++;
2121 stream() << "id: " << primitiveId << "_offset_itemspy";
2122 stream() << "anchors.fill: parent";
2123
2124 m_indentLevel--;
2125 stream() << "}";
2126 stream() << "textureSize: " << primitiveId << "_offset_itemspy.requiredTextureSize";
2127
2128
2129 m_indentLevel--;
2130 stream() << "}";
2131
2132 break;
2133 }
2134
2135 case FilterNodeInfo::Type::GaussianBlur:
2136 {
2137 // Approximate blur effect with fast blur
2138 stream() << "MultiEffect {";
2139 m_indentLevel++;
2140
2141 stream() << "id: " << primitiveId;
2142 stream() << "visible: false";
2143
2144 stream() << "source: " << inputId;
2145 stream() << "blurEnabled: true";
2146 stream() << "width: source.width";
2147 stream() << "height: source.height";
2148
2149 const qreal maxDeviation(12.0); // Decided experimentally
2150 const qreal deviation = step.filterParameter.toReal();
2151 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
2152 stream() << "blur: Math.min(1.0, " << deviation << " * filterSourceItem.width / " << maxDeviation << ")";
2153 else
2154 stream() << "blur: " << std::min(qreal(1.0), deviation / maxDeviation);
2155 stream() << "blurMax: 64";
2156
2157 m_indentLevel--;
2158 stream() << "}";
2159
2160 break;
2161 }
2162 default:
2163 qCWarning(lcQuickVectorImage) << "Unhandled filter type: " << int(step.filterType);
2164 // Dummy item to avoid empty component
2165 stream() << "Item { id: " << primitiveId << " }";
2166 break;
2167 }
2168
2169 // Sample correct part of primitive
2170 stream() << "ShaderEffectSource {";
2171 m_indentLevel++;
2172
2173 stream() << "id: " << step.outputName;
2174 if (stepIndex < info.steps.size())
2175 stream() << "visible: false";
2176
2177 qreal x1, x2, y1, y2;
2178 step.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
2179 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
2180 stream() << "property real fpx1: " << x1;
2181 stream() << "property real fpy1: " << y1;
2182 stream() << "property real fpx2: " << x2;
2183 stream() << "property real fpy2: " << y2;
2184 } else if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
2185 // If they are relative, they are actually in the coordinate system
2186 // of the original bounds of the filtered item. This means we first have to convert
2187 // them to the filter's coordinate system first.
2188 stream() << "property real fpx1: " << x1 << " * filterSourceItem.sourceItem.originalBounds.width";
2189 stream() << "property real fpy1: " << y1 << " * filterSourceItem.sourceItem.originalBounds.height";
2190 stream() << "property real fpx2: " << x2 << " * filterSourceItem.sourceItem.originalBounds.width";
2191 stream() << "property real fpy2: " << y2 << " * filterSourceItem.sourceItem.originalBounds.height";
2192 } else { // Just match filter rect
2193 stream() << "property real fpx1: parent.filterRect.x";
2194 stream() << "property real fpy1: parent.filterRect.y";
2195 stream() << "property real fpx2: parent.filterRect.x + parent.filterRect.width";
2196 stream() << "property real fpy2: parent.filterRect.y + parent.filterRect.height";
2197 }
2198
2199 stream() << "sourceItem: " << primitiveId;
2200 stream() << "sourceRect: Qt.rect(fpx1 - parent.filterRect.x, fpy1 - parent.filterRect.y, width, height)";
2201
2202 stream() << "x: fpx1";
2203 stream() << "y: fpy1";
2204 stream() << "width: " << "fpx2 - fpx1";
2205 stream() << "height: " << "fpy2 - fpy1";
2206
2207 stream() << "ItemSpy {";
2208 m_indentLevel++;
2209 stream() << "id: " << primitiveId << "_itemspy";
2210 stream() << "anchors.fill: parent";
2211
2212 m_indentLevel--;
2213 stream() << "}";
2214 stream() << "textureSize: " << primitiveId << "_itemspy.requiredTextureSize";
2215
2216 m_indentLevel--;
2217 stream() << "}";
2218
2219 return stepIndex;
2220}
2221
2222void QQuickQmlGenerator::generateTimelineFields(const StructureNodeInfo &info)
2223{
2224 if (usingTimelineAnimation() && info.timelineInfo) {
2225 QString frameCounterRef = info.timelineInfo->frameCounterReference
2226 + QStringLiteral(".frameCounter");
2227
2228 if (info.timelineInfo->generateVisibility) {
2229 stream() << "visible: " << frameCounterRef << " >= " << info.timelineInfo->startFrame
2230 << " && " << frameCounterRef << " < " << info.timelineInfo->endFrame;
2231 }
2232
2233 if (info.timelineInfo->generateFrameCounter) {
2234 stream() << "property real frameCounter: ";
2235 if (info.timelineInfo->frameCounterMapper.isAnimated()) {
2236 // Animated frame counter remapping; overrides offset / multiplier
2237 stream(SameLine) << "0";
2238 generatePropertyTimeline(info.timelineInfo->frameCounterMapper, info.id, "frameCounter"_L1);
2239 } else {
2240 const auto offset = info.timelineInfo->frameCounterOffset;
2241 const auto multiplier = info.timelineInfo->frameCounterMultiplier;
2242 const bool needsParens = (offset && multiplier);
2243 if (needsParens)
2244 stream(SameLine) << "(";
2245 stream(SameLine) << frameCounterRef;
2246 if (offset)
2247 stream(SameLine) << (offset > 0 ? " + " : " - ") << qAbs(offset);
2248 if (needsParens)
2249 stream(SameLine) << ")";
2250 if (multiplier)
2251 stream(SameLine) << " * " << multiplier;
2252 }
2253 }
2254 }
2255}
2256
2257bool QQuickQmlGenerator::generatePatternNode(const PatternNodeInfo &info)
2258{
2259 if (info.stage == StructureNodeStage::Start) {
2260 return true;
2261 } else {
2262 startDefsSuffixBlock();
2263 stream() << "Loader {";
2264 m_indentLevel++;
2265
2266 stream() << "id: " << info.id; // This is in a different scope, so we can reuse the ID
2267 stream() << "sourceComponent: " << info.id << "_container";
2268 stream() << "width: item !== null ? item.originalBounds.width : 0";
2269 stream() << "height: item !== null ? item.originalBounds.height : 0";
2270 stream() << "visible: false";
2271 stream() << "function sourceRect(targetWidth, targetHeight) {";
2272 m_indentLevel++;
2273
2274 stream() << "return Qt.rect(0, 0, ";
2275 if (!info.isPatternRectRelativeCoordinates) {
2276 stream(SameLine) << info.patternRect.width() << ", "
2277 << info.patternRect.height();
2278 } else {
2279 stream(SameLine) << info.patternRect.width() << " * targetWidth, "
2280 << info.patternRect.height() << " * targetHeight";
2281 }
2282 stream(SameLine) << ")";
2283 m_indentLevel--;
2284 stream() << "}";
2285
2286 stream() << "function sourceOffset(targetWidth, targetHeight) {";
2287 m_indentLevel++;
2288
2289 stream() << "return Qt.vector3d(";
2290 if (!info.isPatternRectRelativeCoordinates) {
2291 stream(SameLine) << info.patternRect.x() << ", "
2292 << info.patternRect.y() << ", ";
2293 } else {
2294 stream(SameLine) << info.patternRect.x() << " * targetWidth, "
2295 << info.patternRect.y() << " * targetHeight, ";
2296 }
2297 stream(SameLine) << "0.0)";
2298 m_indentLevel--;
2299 stream() << "}";
2300
2301
2302 m_indentLevel--;
2303 stream() << "}";
2304
2305 endDefsSuffixBlock();
2306
2307 return true;
2308 }
2309}
2310
2311void QQuickQmlGenerator::generateDefsInstantiationNode(const StructureNodeInfo &info)
2312{
2313 if (Q_UNLIKELY(errorState()))
2314 return;
2315
2316 if (info.stage == StructureNodeStage::Start) {
2317 stream() << "Loader {";
2318 m_indentLevel++;
2319
2320 stream() << "sourceComponent: " << info.defsId << "_container";
2321 generateNodeBase(info);
2322 generateTimelineFields(info);
2323 } else {
2324 m_indentLevel--;
2325 stream() << "}";
2326 }
2327}
2328
2329bool QQuickQmlGenerator::generateMarkerNode(const MarkerNodeInfo &info)
2330{
2331 if (info.stage == StructureNodeStage::Start) {
2332 startDefsSuffixBlock();
2333 stream() << "QtObject {";
2334 m_indentLevel++;
2335
2336 stream() << "id: " << info.id << "_markerParameters";
2337
2338 stream() << "property bool startReversed: ";
2339 if (info.orientation == MarkerNodeInfo::Orientation::AutoStartReverse)
2340 stream(SameLine) << "true";
2341 else
2342 stream(SameLine) << "false";
2343
2344 stream() << "function autoAngle(adaptedAngle) {";
2345 m_indentLevel++;
2346 if (info.orientation == MarkerNodeInfo::Orientation::Value)
2347 stream() << "return " << info.angle;
2348 else
2349 stream() << "return adaptedAngle";
2350 m_indentLevel--;
2351 stream() << "}";
2352
2353 m_indentLevel--;
2354 stream() << "}";
2355 endDefsSuffixBlock();
2356
2357 if (!info.clipBox.isEmpty()) {
2358 stream() << "Item {";
2359 m_indentLevel++;
2360
2361 stream() << "x: " << info.clipBox.x();
2362 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2363 stream(SameLine) << " * strokeWidth";
2364 stream() << "y: " << info.clipBox.y();
2365 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2366 stream(SameLine) << " * strokeWidth";
2367 stream() << "width: " << info.clipBox.width();
2368 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2369 stream(SameLine) << " * strokeWidth";
2370 stream() << "height: " << info.clipBox.height();
2371 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2372 stream(SameLine) << " * strokeWidth";
2373 stream() << "clip: true";
2374 }
2375
2376 stream() << "Item {";
2377 m_indentLevel++;
2378
2379 if (!info.clipBox.isEmpty()) {
2380 stream() << "x: " << -info.clipBox.x();
2381 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2382 stream(SameLine) << " * strokeWidth";
2383 stream() << "y: " << -info.clipBox.y();
2384 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2385 stream(SameLine) << " * strokeWidth";
2386 }
2387
2388 stream() << "id: " << info.id;
2389
2390 stream() << "property real markerWidth: " << info.markerSize.width();
2391 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2392 stream(SameLine) << " * strokeWidth";
2393
2394 stream() << "property real markerHeight: " << info.markerSize.height();
2395 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2396 stream(SameLine) << " * strokeWidth";
2397
2398 stream() << "function calculateMarkerScale(w, h) {";
2399 m_indentLevel++;
2400
2401 stream() << "var scaleX = 1.0";
2402 stream() << "var scaleY = 1.0";
2403 stream() << "var offsetX = 0.0";
2404 stream() << "var offsetY = 0.0";
2405 if (info.viewBox.width() > 0)
2406 stream() << "if (w > 0) scaleX = w / " << info.viewBox.width();
2407 if (info.viewBox.height() > 0)
2408 stream() << "if (h > 0) scaleY = h / " << info.viewBox.height();
2409
2410 if (info.preserveAspectRatio & MarkerNodeInfo::xyMask) {
2411 stream() << "if (scaleX != scaleY) {";
2412 m_indentLevel++;
2413
2414 if (info.preserveAspectRatio & MarkerNodeInfo::meet)
2415 stream() << "scaleX = scaleY = Math.min(scaleX, scaleY)";
2416 else
2417 stream() << "scaleX = scaleY = Math.max(scaleX, scaleY)";
2418
2419 QString overflowX = QStringLiteral("scaleX * %1 - w").arg(info.viewBox.width());
2420 QString overflowY = QStringLiteral("scaleY * %1 - h").arg(info.viewBox.height());
2421
2422 const quint8 xRatio = info.preserveAspectRatio & MarkerNodeInfo::xMask;
2423 if (xRatio == MarkerNodeInfo::xMid)
2424 stream() << "offsetX -= " << overflowX << " / 2";
2425 else if (xRatio == MarkerNodeInfo::xMax)
2426 stream() << "offsetX -= " << overflowX;
2427
2428 const quint8 yRatio = info.preserveAspectRatio & MarkerNodeInfo::yMask;
2429 if (yRatio == MarkerNodeInfo::yMid)
2430 stream() << "offsetY -= " << overflowY << " / 2";
2431 else if (yRatio == MarkerNodeInfo::yMax)
2432 stream() << "offsetY -= " << overflowY;
2433
2434 m_indentLevel--;
2435 stream() << "}";
2436 }
2437
2438 stream() << "return Qt.vector4d("
2439 << "offsetX - " << info.anchorPoint.x() << " * scaleX, "
2440 << "offsetY - " << info.anchorPoint.y() << " * scaleY, "
2441 << "scaleX, "
2442 << "scaleY)";
2443
2444 m_indentLevel--;
2445 stream() << "}";
2446
2447 stream() << "property vector4d markerScale: calculateMarkerScale(markerWidth, markerHeight)";
2448
2449 stream() << "transform: [";
2450 m_indentLevel++;
2451
2452 stream() << "Scale { xScale: " << info.id << ".markerScale.z; yScale: " << info.id << ".markerScale.w },";
2453 stream() << "Translate { x: " << info.id << ".markerScale.x; y: " << info.id << ".markerScale.y }";
2454
2455 m_indentLevel--;
2456 stream() << "]";
2457
2458 } else {
2459 generateNodeEnd(info);
2460
2461 if (!info.clipBox.isEmpty()) {
2462 m_indentLevel--;
2463 stream() << "}";
2464 }
2465 }
2466
2467 return true;
2468}
2469
2470bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
2471{
2472 if (Q_UNLIKELY(errorState()))
2473 return false;
2474
2475 const QStringList comments = m_commentString.split(u'\n');
2476
2477 if (!isNodeVisible(info)) {
2478 m_indentLevel = 0;
2479
2480 if (comments.isEmpty()) {
2481 stream() << "// Generated from SVG";
2482 } else {
2483 for (const auto &comment : comments)
2484 stream() << "// " << comment;
2485 }
2486
2487 stream() << "import QtQuick";
2488 stream() << "import QtQuick.Shapes" << Qt::endl;
2489 stream() << "Item {";
2490 m_indentLevel++;
2491
2492 double w = info.size.width();
2493 double h = info.size.height();
2494 if (w > 0)
2495 stream() << "implicitWidth: " << w;
2496 if (h > 0)
2497 stream() << "implicitHeight: " << h;
2498
2499 m_indentLevel--;
2500 stream() << "}";
2501
2502 return false;
2503 }
2504
2505 if (info.stage == StructureNodeStage::Start) {
2506 m_indentLevel = 0;
2507
2508 if (comments.isEmpty())
2509 stream() << "// Generated from SVG";
2510 else
2511 for (const auto &comment : comments)
2512 stream() << "// " << comment;
2513
2514 stream() << "import QtQuick";
2515 stream() << "import QtQuick.VectorImage";
2516 stream() << "import QtQuick.VectorImage.Helpers";
2517 stream() << "import QtQuick.Shapes";
2518 stream() << "import QtQuick.Effects";
2519 if (usingTimelineAnimation())
2520 stream() << "import QtQuick.Timeline";
2521
2522 for (const auto &import : std::as_const(m_extraImports))
2523 stream() << "import " << import;
2524
2525 stream() << Qt::endl << "Item {";
2526 m_indentLevel++;
2527
2528 double w = info.size.width();
2529 double h = info.size.height();
2530 if (w > 0)
2531 stream() << "implicitWidth: " << w;
2532 if (h > 0)
2533 stream() << "implicitHeight: " << h;
2534
2535 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2536 stream() << "component AnimationsInfo : QtObject";
2537 stream() << "{";
2538 m_indentLevel++;
2539 }
2540
2541 stream() << "property bool paused: false";
2542 stream() << "property int loops: 1";
2543 stream() << "signal restart()";
2544
2545 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2546 m_indentLevel--;
2547 stream() << "}";
2548 stream() << "property AnimationsInfo animations : AnimationsInfo {}";
2549 }
2550
2551 stream() << "Item {";
2552 m_indentLevel++;
2553 stream() << "width: 1";
2554 stream() << "height: 1";
2555
2556 stream() << "ItemSpy { id: __qt_toplevel_scale_itemspy; anchors.fill: parent }";
2557
2558 m_indentLevel--;
2559 stream() << "}";
2560
2561 if (!info.viewBox.isEmpty()) {
2562 stream() << "transform: [";
2563 m_indentLevel++;
2564 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
2565 if (translate)
2566 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
2567 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
2568 m_indentLevel--;
2569 stream() << "]";
2570 }
2571
2572 if (!info.forceSeparatePaths && info.isPathContainer) {
2573 m_topLevelIdString = QStringLiteral("__qt_toplevel");
2574 stream() << "id: " << m_topLevelIdString;
2575
2576 generatePathContainer(info);
2577 m_indentLevel++;
2578
2579 generateNodeBase(info);
2580 } else {
2581 m_topLevelIdString = generateNodeBase(info);
2582 if (m_topLevelIdString.isEmpty())
2583 qCWarning(lcQuickVectorImage) << "No ID specified for top level item";
2584 }
2585
2586 if (usingTimelineAnimation() && info.timelineInfo) {
2587 stream() << "property real frameCounter: " << info.timelineInfo->startFrame;
2588 stream() << "NumberAnimation on frameCounter {";
2589 m_indentLevel++;
2590 stream() << "from: " << info.timelineInfo->startFrame;
2591 stream() << "to: " << info.timelineInfo->endFrame - 0.01;
2592 stream() << "duration: " << processAnimationTime(info.timelineInfo->duration);
2593 generateAnimationBindings();
2594 m_indentLevel--;
2595 stream() << "}";
2596 stream() << "visible: frameCounter >= " << info.timelineInfo->startFrame
2597 << " && frameCounter < " << info.timelineInfo->endFrame;
2598 }
2599 } else {
2600 if (m_inShapeItemLevel > 0) {
2601 m_inShapeItemLevel--;
2602 m_indentLevel--;
2603 stream() << "}";
2604 }
2605
2606 for (const auto [coords, id] : m_easings.asKeyValueRange()) {
2607 stream() << "readonly property easingCurve " << id << ": ({ type: Easing.BezierSpline, bezierCurve: [ ";
2608 for (auto coord : coords)
2609 stream(SameLine) << coord << ", ";
2610 stream(SameLine) << "1, 1 ] })";
2611 }
2612
2613 generateNodeEnd(info);
2614 stream().flush();
2615 }
2616
2617 return true;
2618}
2619
2620void QQuickQmlGenerator::startDefsSuffixBlock()
2621{
2622 int tmp = m_oldIndentLevels.top();
2623 m_oldIndentLevels.push(m_indentLevel);
2624 m_indentLevel = tmp;
2625 m_stream.setString(&m_defsSuffix);
2626}
2627
2628void QQuickQmlGenerator::endDefsSuffixBlock()
2629{
2630 m_indentLevel = m_oldIndentLevels.pop();
2631 m_stream.setDevice(&m_result);
2632}
2633
2634QStringView QQuickQmlGenerator::indent()
2635{
2636 static QString indentString;
2637 int indentWidth = m_indentLevel * 4;
2638 if (indentWidth > indentString.size())
2639 indentString.fill(QLatin1Char(' '), indentWidth * 2);
2640 return QStringView(indentString).first(indentWidth);
2641}
2642
2643QTextStream &QQuickQmlGenerator::stream(int flags)
2644{
2645 if (m_stream.device() == nullptr && m_stream.string() == nullptr)
2646 m_stream.setDevice(&m_result);
2647 else if (!(flags & StreamFlags::SameLine))
2648 m_stream << Qt::endl << indent();
2649
2650 static qint64 maxBufferSize = qEnvironmentVariableIntegerValue("QT_QUICKVECTORIMAGE_MAX_BUFFER").value_or(64 << 20); // 64MB
2651 if (m_stream.device()) {
2652 if (Q_UNLIKELY(!checkSanityLimit(m_stream.device()->size(), maxBufferSize, "buffer size"_L1)))
2653 m_stream.device()->reset();
2654 } else {
2655 if (Q_UNLIKELY(!checkSanityLimit(m_stream.string()->size(), maxBufferSize, "buffer string size"_L1)))
2656 m_stream.string()->clear();
2657 }
2658
2659 return m_stream;
2660}
2661
2662const char *QQuickQmlGenerator::shapeName() const
2663{
2664 return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData();
2665}
2666
2667QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QT_BEGIN_NAMESPACE QString sanitizeString(const QString &input)
static int processAnimationTime(int time)