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