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
22QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName)
23 : QQuickGenerator(fileName, flags)
24 , outputFileName(outFileName)
25{
26 m_result.open(QIODevice::ReadWrite);
27}
28
29QQuickQmlGenerator::~QQuickQmlGenerator()
30{
31}
32
33bool QQuickQmlGenerator::save()
34{
35 bool res = true;
36 if (!outputFileName.isEmpty()) {
37 QFileInfo fileInfo(outputFileName);
38 QDir dir(fileInfo.absolutePath());
39 if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
40 qCWarning(lcQuickVectorImage) << "Failed to create path" << dir.absolutePath();
41 res = false;
42 } else {
43 QFile outFile(outputFileName);
44 if (outFile.open(QIODevice::WriteOnly)) {
45 outFile.write(m_result.data());
46 outFile.close();
47 } else {
48 qCWarning(lcQuickVectorImage) << "Failed to write to file" << outFile.fileName();
49 res = false;
50 }
51 }
52 }
53
54 if (lcQuickVectorImage().isDebugEnabled())
55 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
56
57 return res;
58}
59
60void QQuickQmlGenerator::setShapeTypeName(const QString &name)
61{
62 m_shapeTypeName = name.toLatin1();
63}
64
65QString QQuickQmlGenerator::shapeTypeName() const
66{
67 return QString::fromLatin1(m_shapeTypeName);
68}
69
70void QQuickQmlGenerator::setCommentString(const QString commentString)
71{
72 m_commentString = commentString;
73}
74
75QString QQuickQmlGenerator::commentString() const
76{
77 return m_commentString;
78}
79
80QString QQuickQmlGenerator::generateNodeBase(const NodeInfo &info)
81{
82 if (!info.nodeId.isEmpty())
83 stream() << "objectName: \"" << info.nodeId << "\"";
84
85 QString idString = info.id;
86 if (!idString.isEmpty()) {
87 // If input contains multiple items with the same id, then this is invalid. However we
88 // shouldn't generate invalid QML from it. So we add a suffix if this is detected.
89 if (m_generatedIds.contains(idString))
90 idString += QStringLiteral("_%1").arg(m_generatedIds.size());
91
92 m_generatedIds.insert(idString);
93
94 stream() << "id: " << idString;
95 }
96
97 if (!info.bounds.isNull()) {
98 stream() << "property var originalBounds: Qt.rect("
99 << info.bounds.x() << ", "
100 << info.bounds.y() << ", "
101 << info.bounds.width() << ", "
102 << info.bounds.height() << ")";
103 stream() << "implicitWidth: originalBounds.width";
104 stream() << "implicitHeight: originalBounds.height";
105 }
106
107 if (!info.isDefaultOpacity)
108 stream() << "opacity: " << info.opacity.defaultValue().toReal();
109
110 if (!info.maskId.isEmpty()) {
111 stream() << "layer.enabled: true";
112 stream() << "visible: false";
113 stream() << "layer.textureSize: " << info.maskId << "_" << info.id << "_mask.textureSize";
114 stream() << "layer.sourceRect: " << info.maskId << ".maskRect(" << info.id << ")";
115 } else {
116 generateItemAnimations(idString, info);
117 }
118
119 return idString;
120}
121
122void QQuickQmlGenerator::generateNodeEnd(const NodeInfo &info)
123{
124 m_indentLevel--;
125 stream() << "}";
126
127 if (!info.maskId.isEmpty())
128 generateMaskUse(info);
129}
130
131void QQuickQmlGenerator::generateItemAnimations(const QString &idString, const NodeInfo &info)
132{
133 const bool hasTransform = info.transform.isAnimated()
134 || !info.maskId.isEmpty()
135 || !info.isDefaultTransform
136 || !info.transformReferenceId.isEmpty();
137
138 if (hasTransform) {
139 stream() << "transform: TransformGroup {";
140 m_indentLevel++;
141
142 if (!idString.isEmpty()) {
143 stream() << "id: " << idString << "_transform_base_group";
144
145 if (!info.maskId.isEmpty())
146 stream() << "Translate { x: " << idString << ".sourceX; y: " << idString << ".sourceY }";
147
148 if (info.transform.isAnimated()) {
149 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
150 stream() << "TransformGroup {";
151 m_indentLevel++;
152
153 if (!idString.isEmpty())
154 stream() << "id: " << idString << "_transform_group_" << groupIndex;
155
156 int animationStart = info.transform.animationGroup(groupIndex);
157 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
158 ? info.transform.animationGroup(groupIndex + 1)
159 : info.transform.animationCount();
160
161 for (int i = nextAnimationStart - 1; i >= animationStart; --i) {
162 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
163 if (animation.frames.isEmpty())
164 continue;
165
166 const QVariantList &parameters = animation.frames.first().value<QVariantList>();
167 switch (animation.subtype) {
168 case QTransform::TxTranslate:
169 if (animation.isConstant()) {
170 const QPointF translation = parameters.value(0).value<QPointF>();
171 if (!translation.isNull())
172 stream() << "Translate { x: " << translation.x() << "; y: " << translation.y() << " }";
173 } else {
174 stream() << "Translate { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
175 }
176 break;
177 case QTransform::TxScale:
178 if (animation.isConstant()) {
179 const QPointF scale = parameters.value(0).value<QPointF>();
180 if (scale != QPointF(1, 1))
181 stream() << "Scale { xScale: " << scale.x() << "; yScale: " << scale.y() << " }";
182 } else {
183 stream() << "Scale { id: " << idString << "_transform_" << groupIndex << "_" << i << "}";
184 }
185 break;
186 case QTransform::TxRotate:
187 if (animation.isConstant()) {
188 const QPointF center = parameters.value(0).value<QPointF>();
189 const qreal angle = parameters.value(1).toReal();
190 if (!qFuzzyIsNull(angle))
191 stream() << "Rotation { angle: " << angle << "; origin.x: " << center.x() << "; origin.y: " << center.y() << " }"; //### center relative to what?
192 } else {
193 stream() << "Rotation { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
194 }
195 break;
196 case QTransform::TxShear:
197 if (animation.isConstant()) {
198 const QPointF skew = parameters.value(0).value<QPointF>();
199 if (!skew.isNull())
200 stream() << "Shear { xAngle: " << skew.x() << "; yAngle: " << skew.y() << " }";
201 } else {
202 stream() << "Shear { id: " << idString << "_transform_" << groupIndex << "_" << i << " }";
203 }
204 break;
205 default:
206 Q_UNREACHABLE();
207 }
208 }
209
210 m_indentLevel--;
211 stream() << "}";
212 }
213 }
214 }
215
216 if (!info.isDefaultTransform) {
217 QTransform xf = info.transform.defaultValue().value<QTransform>();
218 if (xf.type() <= QTransform::TxTranslate) {
219 stream() << "Translate { x: " << xf.dx() << "; y: " << xf.dy() << "}";
220 } else {
221 stream() << "Matrix4x4 { matrix: ";
222 generateTransform(xf);
223 stream(SameLine) << "}";
224 }
225 }
226
227 if (!info.transformReferenceId.isEmpty())
228 stream() << "Matrix4x4 { matrix: " << info.transformReferenceId << ".transformMatrix }";
229
230 m_indentLevel--;
231 stream() << "}";
232
233 generateAnimateTransform(idString, info);
234 }
235
236 generatePropertyAnimation(info.opacity, idString, QStringLiteral("opacity"));
237 generatePropertyAnimation(info.visibility, idString, QStringLiteral("visible"));
238}
239
240void QQuickQmlGenerator::generateMaskUse(const NodeInfo &info)
241{
242 Q_ASSERT(!info.maskId.isEmpty());
243
244 // Shader effect source for the mask itself
245 stream() << "ShaderEffectSource {";
246 m_indentLevel++;
247
248 const QString maskId = info.maskId + QStringLiteral("_") + info.id + QStringLiteral("_mask");
249 stream() << "id: " << maskId;
250 stream() << "sourceItem: " << info.maskId;
251 stream() << "visible: false";
252
253 stream() << "ItemSpy {";
254 m_indentLevel++;
255 stream() << "id: " << maskId << "_itemspy";
256 stream() << "anchors.fill: parent";
257 m_indentLevel--;
258 stream() << "}";
259 stream() << "textureSize: " << maskId << "_itemspy.requiredTextureSize";
260
261 stream() << "sourceRect: " << info.maskId << ".maskRect(" << info.id << ")";
262 stream() << "width: sourceRect.width";
263 stream() << "height: sourceRect.height";
264
265 m_indentLevel--;
266 stream() << "}";
267
268 stream() << "ShaderEffect {";
269 m_indentLevel++;
270
271 const QString maskShaderId = maskId + QStringLiteral("_se");
272 stream() << "id:" << maskShaderId;
273
274 stream() << "property real sourceX: " << maskId << ".sourceRect.x";
275 stream() << "property real sourceY: " << maskId << ".sourceRect.y";
276 stream() << "width: " << maskId << ".sourceRect.width";
277 stream() << "height: " << maskId << ".sourceRect.height";
278
279 stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/luminancemask.frag.qsb\"";
280 stream() << "property var source: " << info.id;
281 stream() << "property var maskSource: " << maskId;
282
283 generateItemAnimations(maskShaderId, info);
284
285 m_indentLevel--;
286 stream() << "}";
287}
288
289bool QQuickQmlGenerator::generateDefsNode(const NodeInfo &info)
290{
291 Q_UNUSED(info)
292
293 return false;
294}
295
296void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
297{
298 if (!isNodeVisible(info))
299 return;
300
301 const QFileInfo outputFileInfo(outputFileName);
302 const QDir outputDir(outputFileInfo.absolutePath());
303
304 QString filePath;
305
306 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
307 filePath = m_assetFileDirectory;
308 if (filePath.isEmpty())
309 filePath = outputDir.absolutePath();
310
311 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
312 filePath += u'/';
313
314 QDir fileDir(filePath);
315 if (!fileDir.exists()) {
316 if (!fileDir.mkpath(QStringLiteral(".")))
317 qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath;
318 }
319
320 filePath += QStringLiteral("%1%2.png").arg(m_assetFilePrefix.isEmpty()
321 ? QStringLiteral("svg_asset_")
322 : m_assetFilePrefix)
323 .arg(info.image.cacheKey());
324
325 if (!info.image.save(filePath))
326 qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath;
327 qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath;
328 } else {
329 filePath = info.externalFileReference;
330 }
331
332 const QFileInfo assetFileInfo(filePath);
333
334 stream() << "Image {";
335
336 m_indentLevel++;
337 generateNodeBase(info);
338 stream() << "x: " << info.rect.x();
339 stream() << "y: " << info.rect.y();
340 stream() << "width: " << info.rect.width();
341 stream() << "height: " << info.rect.height();
342 stream() << "source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<"\"";
343 generateNodeEnd(info);
344}
345
346void QQuickQmlGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
347{
348 if (!isNodeVisible(info))
349 return;
350
351 if (m_inShapeItemLevel > 0) {
352 if (!info.isDefaultTransform)
353 qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
354 optimizePaths(info, overrideBoundingRect);
355 } else {
356 m_inShapeItemLevel++;
357 stream() << shapeName() << " {";
358
359 m_indentLevel++;
360 generateNodeBase(info);
361
362 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
363 stream() << "preferredRendererType: Shape.CurveRenderer";
364 optimizePaths(info, overrideBoundingRect);
365 //qCDebug(lcQuickVectorGraphics) << *node->qpath();
366 generateNodeEnd(info);
367 m_inShapeItemLevel--;
368 }
369}
370
371void QQuickQmlGenerator::generateGradient(const QGradient *grad)
372{
373 if (grad->type() == QGradient::LinearGradient) {
374 auto *linGrad = static_cast<const QLinearGradient *>(grad);
375 stream() << "fillGradient: LinearGradient {";
376 m_indentLevel++;
377
378 QRectF gradRect(linGrad->start(), linGrad->finalStop());
379
380 stream() << "x1: " << gradRect.left();
381 stream() << "y1: " << gradRect.top();
382 stream() << "x2: " << gradRect.right();
383 stream() << "y2: " << gradRect.bottom();
384 for (auto &stop : linGrad->stops())
385 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
386 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
387 m_indentLevel--;
388 stream() << "}";
389 } else if (grad->type() == QGradient::RadialGradient) {
390 auto *radGrad = static_cast<const QRadialGradient*>(grad);
391 stream() << "fillGradient: RadialGradient {";
392 m_indentLevel++;
393
394 stream() << "centerX: " << radGrad->center().x();
395 stream() << "centerY: " << radGrad->center().y();
396 stream() << "centerRadius: " << radGrad->radius();
397 stream() << "focalX:" << radGrad->focalPoint().x();
398 stream() << "focalY:" << radGrad->focalPoint().y();
399 for (auto &stop : radGrad->stops())
400 stream() << "GradientStop { position: " << QString::number(stop.first, 'g', 7)
401 << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
402 m_indentLevel--;
403 stream() << "}";
404 }
405}
406
407void QQuickQmlGenerator::generateAnimationBindings()
408{
409 QString prefix;
410 if (Q_UNLIKELY(!isRuntimeGenerator()))
411 prefix = QStringLiteral(".animations");
412
413 stream() << "loops: " << m_topLevelIdString << prefix << ".loops";
414 stream() << "paused: " << m_topLevelIdString << prefix << ".paused";
415 stream() << "running: true";
416
417 // We need to reset the animation when the loop count changes
418 stream() << "onLoopsChanged: { if (running) { restart() } }";
419}
420
421void QQuickQmlGenerator::generateEasing(const QQuickAnimatedProperty::PropertyAnimation &animation, int time)
422{
423 if (animation.easingPerFrame.contains(time)) {
424 QBezier bezier = animation.easingPerFrame.value(time);
425 QPointF c1 = bezier.pt2();
426 QPointF c2 = bezier.pt3();
427
428 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
429 if (!isLinear) {
430 int nextIdx = m_easings.size();
431 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
432 if (id.isNull())
433 id = QString(QLatin1String("easing_%1")).arg(nextIdx, 2, 10, QLatin1Char('0'));
434 stream() << "easing: " << m_topLevelIdString << "." << id;
435 }
436 }
437}
438
439void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty &property,
440 const QString &targetName,
441 const QString &propertyName,
442 AnimationType animationType)
443{
444 if (!property.isAnimated())
445 return;
446
447 QString mainAnimationId = targetName
448 + QStringLiteral("_")
449 + propertyName
450 + QStringLiteral("_animation");
451 mainAnimationId.replace(QLatin1Char('.'), QLatin1Char('_'));
452
453 QString prefix;
454 if (Q_UNLIKELY(!isRuntimeGenerator()))
455 prefix = QStringLiteral(".animations");
456
457 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
458
459 stream() << "ParallelAnimation {";
460 m_indentLevel++;
461
462 stream() << "id: " << mainAnimationId;
463
464 generateAnimationBindings();
465
466 for (int i = 0; i < property.animationCount(); ++i) {
467 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
468
469 stream() << "SequentialAnimation {";
470 m_indentLevel++;
471
472 const int startOffset = animation.startOffset;
473 if (startOffset > 0)
474 stream() << "PauseAnimation { duration: " << startOffset << " }";
475
476 stream() << "SequentialAnimation {";
477 m_indentLevel++;
478
479 const int repeatCount = animation.repeatCount;
480 if (repeatCount < 0)
481 stream() << "loops: Animation.Infinite";
482 else
483 stream() << "loops: " << repeatCount;
484
485 int previousTime = 0;
486 QVariant previousValue;
487 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
488 const int time = it.key();
489 const int frameTime = time - previousTime;
490 const QVariant &value = it.value();
491
492 if (previousValue.isValid() && previousValue == value) {
493 if (frameTime > 0)
494 stream() << "PauseAnimation { duration: " << frameTime << " }";
495 } else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
496 // We special case bools, with PauseAnimation and then a setter at the end
497 if (frameTime > 0)
498 stream() << "PauseAnimation { duration: " << frameTime << " }";
499 stream() << "ScriptAction {";
500 m_indentLevel++;
501
502 stream() << "script:" << targetName << "." << propertyName << " = " << value.toString();
503
504 m_indentLevel--;
505 stream() << "}";
506 } else {
507 generateAnimatedPropertySetter(targetName,
508 propertyName,
509 value,
510 animation,
511 frameTime,
512 time,
513 animationType);
514 }
515
516 previousTime = time;
517 previousValue = value;
518 }
519
520 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
521 stream() << "ScriptAction {";
522 m_indentLevel++;
523 stream() << "script: ";
524
525 switch (animationType) {
526 case AnimationType::Auto:
527 stream(SameLine) << targetName << "." << propertyName << " = ";
528 break;
529 case AnimationType::ColorOpacity:
530 stream(SameLine) << targetName << "." << propertyName << ".a = ";
531 break;
532 };
533
534 QVariant value = property.defaultValue();
535 if (value.typeId() == QMetaType::QColor)
536 stream(SameLine) << "\"" << value.toString() << "\"";
537 else
538 stream(SameLine) << value.toReal();
539
540 m_indentLevel--;
541 stream() << "}";
542 }
543
544 m_indentLevel--;
545 stream() << "}";
546
547 m_indentLevel--;
548 stream() << "}";
549 }
550
551 m_indentLevel--;
552 stream() << "}";
553}
554
555void QQuickQmlGenerator::generateTransform(const QTransform &xf)
556{
557 if (xf.isAffine()) {
558 stream(SameLine) << "PlanarTransform.fromAffineMatrix("
559 << xf.m11() << ", " << xf.m12() << ", "
560 << xf.m21() << ", " << xf.m22() << ", "
561 << xf.dx() << ", " << xf.dy() << ")";
562 } else {
563 QMatrix4x4 m(xf);
564 stream(SameLine) << "Qt.matrix4x4(";
565 m_indentLevel += 3;
566 const auto *data = m.data();
567 for (int i = 0; i < 4; i++) {
568 stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12];
569 if (i < 3)
570 stream(SameLine) << ", ";
571 }
572 stream(SameLine) << ")";
573 m_indentLevel -= 3;
574 }
575}
576
577void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
578{
579 Q_UNUSED(pathSelector)
580 Q_ASSERT(painterPath || quadPath);
581
582 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
583 const bool noPen = strokeColor == QColorConstants::Transparent
584 && !info.strokeStyle.color.isAnimated()
585 && !info.strokeStyle.opacity.isAnimated();
586 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
587 return;
588
589 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
590 const bool noFill = info.grad.type() == QGradient::NoGradient
591 && fillColor == QColorConstants::Transparent
592 && !info.fillColor.isAnimated()
593 && !info.fillOpacity.isAnimated();
594 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
595 return;
596
597 if (noPen && noFill)
598 return;
599
600 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
601 stream() << "ShapePath {";
602 m_indentLevel++;
603
604 QString shapePathId = info.id;
605 if (pathSelector & QQuickVectorImageGenerator::FillPath)
606 shapePathId += QStringLiteral("_fill");
607 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
608 shapePathId += QStringLiteral("_stroke");
609
610 stream() << "id: " << shapePathId;
611
612 if (!info.nodeId.isEmpty()) {
613 switch (pathSelector) {
614 case QQuickVectorImageGenerator::FillPath:
615 stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
616 break;
617 case QQuickVectorImageGenerator::StrokePath:
618 stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
619 break;
620 case QQuickVectorImageGenerator::FillAndStroke:
621 stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
622 break;
623 }
624 }
625
626 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
627 stream() << "strokeColor: \"transparent\"";
628 } else {
629 stream() << "strokeColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
630 stream() << "strokeWidth: " << info.strokeStyle.width;
631 stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
632 stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
633 stream() << "miterLimit: " << info.strokeStyle.miterLimit;
634 if (info.strokeStyle.dashArray.length() != 0) {
635 stream() << "strokeStyle: " << "ShapePath.DashLine";
636 stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
637 stream() << "dashOffset: " << info.strokeStyle.dashOffset;
638 }
639 }
640
641 QTransform fillTransform = info.fillTransform;
642 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
643 stream() << "fillColor: \"transparent\"";
644 } else if (info.grad.type() != QGradient::NoGradient) {
645 generateGradient(&info.grad);
646 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
647 QTransform objectToUserSpace;
648 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
649 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
650 fillTransform *= objectToUserSpace;
651 }
652 } else {
653 stream() << "fillColor: \"" << fillColor.name(QColor::HexArgb) << "\"";
654 }
655
656 if (!fillTransform.isIdentity()) {
657 const QTransform &xf = fillTransform;
658 stream() << "fillTransform: ";
659 if (info.fillTransform.type() == QTransform::TxTranslate)
660 stream(SameLine) << "PlanarTransform.fromTranslate(" << xf.dx() << ", " << xf.dy() << ")";
661 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
662 stream(SameLine) << "PlanarTransform.fromScale(" << xf.m11() << ", " << xf.m22() << ")";
663 else
664 generateTransform(xf);
665 }
666
667 if (info.trim.enabled) {
668 stream() << "trim.start: " << info.trim.start.defaultValue().toReal();
669 stream() << "trim.end: " << info.trim.end.defaultValue().toReal();
670 stream() << "trim.offset: " << info.trim.offset.defaultValue().toReal();
671
672 }
673
674 if (fillRule == QQuickShapePath::WindingFill)
675 stream() << "fillRule: ShapePath.WindingFill";
676 else
677 stream() << "fillRule: ShapePath.OddEvenFill";
678
679 QString hintStr;
680 if (quadPath)
681 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
682 if (!hintStr.isEmpty())
683 stream() << hintStr;
684
685 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
686 stream() << "PathSvg { path: \"" << svgPathString << "\" }";
687
688 m_indentLevel--;
689 stream() << "}";
690
691 if (info.trim.enabled) {
692 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(".trim"), QStringLiteral("start"));
693 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(".trim"), QStringLiteral("end"));
694 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(".trim"), QStringLiteral("offset"));
695 }
696
697 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeColor"));
698 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral("strokeColor"), AnimationType::ColorOpacity);
699 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillColor"));
700 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral("fillColor"), AnimationType::ColorOpacity);
701}
702
703void QQuickQmlGenerator::generateNode(const NodeInfo &info)
704{
705 if (!isNodeVisible(info))
706 return;
707
708 stream() << "// Missing Implementation for SVG Node: " << info.typeName;
709 stream() << "// Adding an empty Item and skipping";
710 stream() << "Item {";
711 m_indentLevel++;
712 generateNodeBase(info);
713 generateNodeEnd(info);
714}
715
716void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
717{
718 if (!isNodeVisible(info))
719 return;
720
721 static int counter = 0;
722 stream() << "Item {";
723 m_indentLevel++;
724 generateNodeBase(info);
725
726 if (!info.isTextArea)
727 stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
728
729 stream() << "Text {";
730
731 m_indentLevel++;
732
733 const QString textItemId = QStringLiteral("_qt_textItem_%1").arg(counter);
734 stream() << "id: " << textItemId;
735
736 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral("color"));
737 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral("color"), AnimationType::ColorOpacity);
738 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral("styleColor"));
739 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral("styleColor"), AnimationType::ColorOpacity);
740
741 if (info.isTextArea) {
742 stream() << "x: " << info.position.x();
743 stream() << "y: " << info.position.y();
744 if (info.size.width() > 0)
745 stream() << "width: " << info.size.width();
746 if (info.size.height() > 0)
747 stream() << "height: " << info.size.height();
748 stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
749 stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
750 } else {
751 QString hAlign = QStringLiteral("left");
752 stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
753 switch (info.alignment) {
754 case Qt::AlignHCenter:
755 hAlign = QStringLiteral("horizontalCenter");
756 break;
757 case Qt::AlignRight:
758 hAlign = QStringLiteral("right");
759 break;
760 default:
761 qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
762 Q_FALLTHROUGH();
763 case Qt::AlignLeft:
764 break;
765 }
766 stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
767 }
768 counter++;
769
770 stream() << "color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) << "\"";
771 stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText");
772
773 QString s = info.text;
774 s.replace(QLatin1Char('"'), QLatin1String("\\\""));
775 stream() << "text: \"" << s << "\"";
776 stream() << "font.family: \"" << info.font.family() << "\"";
777 if (info.font.pixelSize() > 0)
778 stream() << "font.pixelSize:" << info.font.pixelSize();
779 else if (info.font.pointSize() > 0)
780 stream() << "font.pixelSize:" << info.font.pointSizeF();
781 if (info.font.underline())
782 stream() << "font.underline: true";
783 if (info.font.weight() != QFont::Normal)
784 stream() << "font.weight: " << int(info.font.weight());
785 if (info.font.italic())
786 stream() << "font.italic: true";
787 switch (info.font.hintingPreference()) {
788 case QFont::PreferFullHinting:
789 stream() << "font.hintingPreference: Font.PreferFullHinting";
790 break;
791 case QFont::PreferVerticalHinting:
792 stream() << "font.hintingPreference: Font.PreferVerticalHinting";
793 break;
794 case QFont::PreferNoHinting:
795 stream() << "font.hintingPreference: Font.PreferNoHinting";
796 break;
797 case QFont::PreferDefaultHinting:
798 stream() << "font.hintingPreference: Font.PreferDefaultHinting";
799 break;
800 };
801
802 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
803 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
804 stream() << "styleColor: \"" << strokeColor.name(QColor::HexArgb) << "\"";
805 stream() << "style: Text.Outline";
806 }
807
808 m_indentLevel--;
809 stream() << "}";
810
811 generateNodeEnd(info);
812}
813
814void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
815{
816 if (!isNodeVisible(info))
817 return;
818
819 if (info.stage == StructureNodeStage::Start) {
820 stream() << "Item {";
821 m_indentLevel++;
822 generateNodeBase(info);
823 stream() << "x: " << info.startPos.x();
824 stream() << "y: " << info.startPos.y();
825 } else {
826 generateNodeEnd(info);
827 }
828}
829
830void QQuickQmlGenerator::generatePathContainer(const StructureNodeInfo &info)
831{
832 Q_UNUSED(info);
833 stream() << shapeName() <<" {";
834 m_indentLevel++;
835 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
836 stream() << "preferredRendererType: Shape.CurveRenderer";
837 m_indentLevel--;
838
839 m_inShapeItemLevel++;
840}
841
842void QQuickQmlGenerator::generateAnimatedPropertySetter(const QString &targetName,
843 const QString &propertyName,
844 const QVariant &value,
845 const QQuickAnimatedProperty::PropertyAnimation &animation,
846 int frameTime,
847 int time,
848 AnimationType animationType)
849{
850 if (frameTime > 0) {
851 switch (animationType) {
852 case AnimationType::Auto:
853 if (value.typeId() == QMetaType::QColor)
854 stream() << "ColorAnimation {";
855 else
856 stream() << "PropertyAnimation {";
857 break;
858 case AnimationType::ColorOpacity:
859 stream() << "ColorOpacityAnimation {";
860 break;
861 };
862 m_indentLevel++;
863
864 stream() << "duration: " << frameTime;
865 stream() << "target: " << targetName;
866 stream() << "property: \"" << propertyName << "\"";
867 stream() << "to: ";
868 if (value.typeId() == QMetaType::QVector3D) {
869 const QVector3D &v = value.value<QVector3D>();
870 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
871 } else if (value.typeId() == QMetaType::QColor) {
872 stream(SameLine) << "\"" << value.toString() << "\"";
873 } else {
874 stream(SameLine) << value.toReal();
875 }
876 generateEasing(animation, time);
877 m_indentLevel--;
878 stream() << "}";
879 } else {
880 stream() << "ScriptAction {";
881 m_indentLevel++;
882 stream() << "script:" << targetName << "." << propertyName;
883 if (animationType == AnimationType::ColorOpacity)
884 stream(SameLine) << ".a";
885
886 stream(SameLine) << " = ";
887 if (value.typeId() == QMetaType::QVector3D) {
888 const QVector3D &v = value.value<QVector3D>();
889 stream(SameLine) << "Qt.vector3d(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
890 } else if (value.typeId() == QMetaType::QColor) {
891 stream(SameLine) << "\"" << value.toString() << "\"";
892 } else {
893 stream(SameLine) << value.toReal();
894 }
895 m_indentLevel--;
896 stream() << "}";
897 }
898}
899
900void QQuickQmlGenerator::generateAnimateTransform(const QString &targetName, const NodeInfo &info)
901{
902 if (!info.transform.isAnimated())
903 return;
904
905 const QString mainAnimationId = targetName
906 + QStringLiteral("_transform_animation");
907
908 QString prefix;
909 if (Q_UNLIKELY(!isRuntimeGenerator()))
910 prefix = QStringLiteral(".animations");
911 stream() << "Connections { target: " << m_topLevelIdString << prefix << "; function onRestart() {" << mainAnimationId << ".restart() } }";
912
913 stream() << "ParallelAnimation {";
914 m_indentLevel++;
915
916 stream() << "id:" << mainAnimationId;
917
918 generateAnimationBindings();
919 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
920 int animationStart = info.transform.animationGroup(groupIndex);
921 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
922 ? info.transform.animationGroup(groupIndex + 1)
923 : info.transform.animationCount();
924
925 // The first animation in the group holds the shared properties for the whole group
926 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
927 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
928 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
929
930 stream() << "SequentialAnimation {";
931 m_indentLevel++;
932
933 const int startOffset = firstAnimation.startOffset;
934 if (startOffset > 0)
935 stream() << "PauseAnimation { duration: " << startOffset << " }";
936
937 const int repeatCount = firstAnimation.repeatCount;
938 if (repeatCount < 0)
939 stream() << "loops: Animation.Infinite";
940 else
941 stream() << "loops: " << repeatCount;
942
943 if (replace) {
944 stream() << "ScriptAction {";
945 m_indentLevel++;
946
947 stream() << "script: " << targetName << "_transform_base_group"
948 << ".activateOverride(" << targetName << "_transform_group_" << groupIndex << ")";
949
950 m_indentLevel--;
951 stream() << "}";
952 }
953
954 stream() << "ParallelAnimation {";
955 m_indentLevel++;
956
957 for (int i = animationStart; i < nextAnimationStart; ++i) {
958 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
959 if (animation.isConstant())
960 continue;
961 bool hasRotationCenter = false;
962 if (animation.subtype == QTransform::TxRotate) {
963 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
964 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
965 if (!center.isNull()) {
966 hasRotationCenter = true;
967 break;
968 }
969 }
970 }
971
972 stream() << "SequentialAnimation {";
973 m_indentLevel++;
974
975 int previousTime = 0;
976 QVariantList previousParameters;
977 for (auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
978 const int time = it.key();
979 const int frameTime = time - previousTime;
980 const QVariantList &parameters = it.value().value<QVariantList>();
981 if (parameters.isEmpty())
982 continue;
983
984 if (parameters == previousParameters) {
985 if (frameTime > 0)
986 stream() << "PauseAnimation { duration: " << frameTime << " }";
987 } else {
988 stream() << "ParallelAnimation {";
989 m_indentLevel++;
990
991 const QString propertyTargetName = targetName
992 + QStringLiteral("_transform_")
993 + QString::number(groupIndex)
994 + QStringLiteral("_")
995 + QString::number(i);
996
997 switch (animation.subtype) {
998 case QTransform::TxTranslate:
999 {
1000 const QPointF translation = parameters.first().value<QPointF>();
1001
1002 generateAnimatedPropertySetter(propertyTargetName,
1003 QStringLiteral("x"),
1004 translation.x(),
1005 animation,
1006 frameTime,
1007 time);
1008 generateAnimatedPropertySetter(propertyTargetName,
1009 QStringLiteral("y"),
1010 translation.y(),
1011 animation,
1012 frameTime,
1013 time);
1014 break;
1015 }
1016 case QTransform::TxScale:
1017 {
1018 const QPointF scale = parameters.first().value<QPointF>();
1019 generateAnimatedPropertySetter(propertyTargetName,
1020 QStringLiteral("xScale"),
1021 scale.x(),
1022 animation,
1023 frameTime,
1024 time);
1025 generateAnimatedPropertySetter(propertyTargetName,
1026 QStringLiteral("yScale"),
1027 scale.y(),
1028 animation,
1029 frameTime,
1030 time);
1031 break;
1032 }
1033 case QTransform::TxRotate:
1034 {
1035 Q_ASSERT(parameters.size() == 2);
1036 const qreal angle = parameters.value(1).toReal();
1037 if (hasRotationCenter) {
1038 const QPointF center = parameters.value(0).value<QPointF>();
1039 generateAnimatedPropertySetter(propertyTargetName,
1040 QStringLiteral("origin"),
1041 QVector3D(center.x(), center.y(), 0.0),
1042 animation,
1043 frameTime,
1044 time);
1045 }
1046 generateAnimatedPropertySetter(propertyTargetName,
1047 QStringLiteral("angle"),
1048 angle,
1049 animation,
1050 frameTime,
1051 time);
1052 break;
1053 }
1054 case QTransform::TxShear:
1055 {
1056 const QPointF skew = parameters.first().value<QPointF>();
1057
1058 generateAnimatedPropertySetter(propertyTargetName,
1059 QStringLiteral("xAngle"),
1060 skew.x(),
1061 animation,
1062 frameTime,
1063 time);
1064
1065 generateAnimatedPropertySetter(propertyTargetName,
1066 QStringLiteral("yAngle"),
1067 skew.y(),
1068 animation,
1069 frameTime,
1070 time);
1071 break;
1072 }
1073 default:
1074 Q_UNREACHABLE();
1075 }
1076
1077 m_indentLevel--;
1078 stream() << "}"; // Parallel key frame animation
1079 }
1080
1081 previousTime = time;
1082 previousParameters = parameters;
1083 }
1084
1085 m_indentLevel--;
1086 stream() << "}"; // Parallel key frame animation
1087 }
1088
1089 m_indentLevel--;
1090 stream() << "}"; // Parallel key frame animation
1091
1092 // If the animation ever finishes, then we add an action on the end that handles itsr
1093 // freeze state
1094 if (firstAnimation.repeatCount >= 0) {
1095 stream() << "ScriptAction {";
1096 m_indentLevel++;
1097
1098 stream() << "script: {";
1099 m_indentLevel++;
1100
1101 if (!freeze) {
1102 stream() << targetName << "_transform_base_group.deactivate("
1103 << targetName << "_transform_group_" << groupIndex << ")";
1104 } else if (!replace) {
1105 stream() << targetName << "_transform_base_group.deactivateOverride("
1106 << targetName << "_transform_group_" << groupIndex << ")";
1107 }
1108
1109 m_indentLevel--;
1110 stream() << "}";
1111
1112 m_indentLevel--;
1113 stream() << "}";
1114 }
1115
1116 m_indentLevel--;
1117 stream() << "}";
1118 }
1119
1120 m_indentLevel--;
1121 stream() << "}";
1122}
1123
1124bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
1125{
1126 if (!isNodeVisible(info))
1127 return false;
1128
1129 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1130 if (info.stage == StructureNodeStage::Start) {
1131 if (isPathContainer) {
1132 generatePathContainer(info);
1133 } else if (!info.customItemType.isEmpty()) {
1134 stream() << info.customItemType << " {";
1135 } else {
1136 stream() << "Item { // Structure node";
1137 }
1138
1139 if (!info.viewBox.isEmpty()) {
1140 m_indentLevel++;
1141 stream() << "transform: [";
1142 m_indentLevel++;
1143 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1144 if (translate)
1145 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
1146 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
1147 m_indentLevel--;
1148 stream() << "]";
1149 m_indentLevel--;
1150 }
1151
1152 m_indentLevel++;
1153 generateNodeBase(info);
1154 } else {
1155 generateNodeEnd(info);
1156 if (isPathContainer)
1157 m_inShapeItemLevel--;
1158 }
1159
1160 return true;
1161}
1162
1163bool QQuickQmlGenerator::generateMaskNode(const MaskNodeInfo &info)
1164{
1165 // Generate an invisible item subtree which can be used in ShaderEffectSource
1166 if (info.stage == StructureNodeStage::Start) {
1167 stream() << "Item {";
1168 m_indentLevel++;
1169
1170 generateNodeBase(info);
1171
1172 stream() << "visible: false";
1173 stream() << "width: originalBounds.width";
1174 stream() << "height: originalBounds.height";
1175
1176 stream() << "property real maskX: " << info.maskRect.left();
1177 stream() << "property real maskY: " << info.maskRect.top();
1178 stream() << "property real maskWidth: " << info.maskRect.width();
1179 stream() << "property real maskHeight: " << info.maskRect.height();
1180
1181 stream() << "function maskRect(other) {";
1182 m_indentLevel++;
1183
1184 stream() << "return ";
1185 if (info.isMaskRectRelativeCoordinates) {
1186 stream(SameLine)
1187 << "Qt.rect("
1188 << info.id << ".maskX * other.originalBounds.width + other.originalBounds.x,"
1189 << info.id << ".maskY * other.originalBounds.height + other.originalBounds.y,"
1190 << info.id << ".maskWidth * other.originalBounds.width,"
1191 << info.id << ".maskHeight * other.originalBounds.height)";
1192 } else {
1193 stream(SameLine)
1194 << "Qt.rect("
1195 << info.id << ".maskX, "
1196 << info.id << ".maskY, "
1197 << info.id << ".maskWidth, "
1198 << info.id << ".maskHeight)";
1199 }
1200
1201 m_indentLevel--;
1202 stream() << "}";
1203
1204 } else {
1205 generateNodeEnd(info);
1206 }
1207
1208 return true;
1209}
1210
1211bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
1212{
1213 const QStringList comments = m_commentString.split(u'\n');
1214
1215 if (!isNodeVisible(info)) {
1216 m_indentLevel = 0;
1217
1218 if (comments.isEmpty()) {
1219 stream() << "// Generated from SVG";
1220 } else {
1221 for (const auto &comment : comments)
1222 stream() << "// " << comment;
1223 }
1224
1225 stream() << "import QtQuick";
1226 stream() << "import QtQuick.Shapes" << Qt::endl;
1227 stream() << "Item {";
1228 m_indentLevel++;
1229
1230 double w = info.size.width();
1231 double h = info.size.height();
1232 if (w > 0)
1233 stream() << "implicitWidth: " << w;
1234 if (h > 0)
1235 stream() << "implicitHeight: " << h;
1236
1237 m_indentLevel--;
1238 stream() << "}";
1239
1240 return false;
1241 }
1242
1243 if (info.stage == StructureNodeStage::Start) {
1244 m_indentLevel = 0;
1245
1246 if (comments.isEmpty())
1247 stream() << "// Generated from SVG";
1248 else
1249 for (const auto &comment : comments)
1250 stream() << "// " << comment;
1251
1252 stream() << "import QtQuick";
1253 stream() << "import QtQuick.VectorImage";
1254 stream() << "import QtQuick.VectorImage.Helpers";
1255 stream() << "import QtQuick.Shapes";
1256 for (const auto &import : std::as_const(m_extraImports))
1257 stream() << "import " << import;
1258
1259 stream() << Qt::endl << "Item {";
1260 m_indentLevel++;
1261
1262 double w = info.size.width();
1263 double h = info.size.height();
1264 if (w > 0)
1265 stream() << "implicitWidth: " << w;
1266 if (h > 0)
1267 stream() << "implicitHeight: " << h;
1268
1269 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1270 stream() << "component AnimationsInfo : QtObject";
1271 stream() << "{";
1272 m_indentLevel++;
1273 }
1274
1275 stream() << "property bool paused: false";
1276 stream() << "property int loops: 1";
1277 stream() << "signal restart()";
1278
1279 if (Q_UNLIKELY(!isRuntimeGenerator())) {
1280 m_indentLevel--;
1281 stream() << "}";
1282 stream() << "property AnimationsInfo animations : AnimationsInfo {}";
1283 }
1284
1285 if (!info.viewBox.isEmpty()) {
1286 stream() << "transform: [";
1287 m_indentLevel++;
1288 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1289 if (translate)
1290 stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
1291 stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
1292 m_indentLevel--;
1293 stream() << "]";
1294 }
1295
1296 if (!info.forceSeparatePaths && info.isPathContainer) {
1297 m_topLevelIdString = QStringLiteral("__qt_toplevel");
1298 stream() << "id: " << m_topLevelIdString;
1299
1300 generatePathContainer(info);
1301 m_indentLevel++;
1302
1303 generateNodeBase(info);
1304 } else {
1305 m_topLevelIdString = generateNodeBase(info);
1306 if (m_topLevelIdString.isEmpty())
1307 qCWarning(lcQuickVectorImage) << "No ID specified for top level item";
1308 }
1309 } else {
1310 if (m_inShapeItemLevel > 0) {
1311 m_inShapeItemLevel--;
1312 m_indentLevel--;
1313 stream() << "}";
1314 }
1315
1316 for (const auto [coords, id] : m_easings.asKeyValueRange()) {
1317 stream() << "property easingCurve " << id << ": { type: Easing.BezierSpline; bezierCurve: [ ";
1318 for (auto coord : coords)
1319 stream(SameLine) << coord << ", ";
1320 stream(SameLine) << "1, 1 ] }";
1321 }
1322
1323 generateNodeEnd(info);
1324 stream().flush();
1325 }
1326
1327 return true;
1328}
1329
1330QStringView QQuickQmlGenerator::indent()
1331{
1332 static QString indentString;
1333 int indentWidth = m_indentLevel * 4;
1334 if (indentWidth > indentString.size())
1335 indentString.fill(QLatin1Char(' '), indentWidth * 2);
1336 return QStringView(indentString).first(indentWidth);
1337}
1338
1339QTextStream &QQuickQmlGenerator::stream(int flags)
1340{
1341 if (m_stream.device() == nullptr)
1342 m_stream.setDevice(&m_result);
1343 else if (!(flags & StreamFlags::SameLine))
1344 m_stream << Qt::endl << indent();
1345 return m_stream;
1346}
1347
1348const char *QQuickQmlGenerator::shapeName() const
1349{
1350 return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData();
1351}
1352
1353QT_END_NAMESPACE