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 | |
4 | #include "qquickqmlgenerator_p.h" |
5 | #include "qquicknodeinfo_p.h" |
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 | |
14 | #include <QtCore/qloggingcategory.h> |
15 | #include <QtCore/qdir.h> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage) |
20 | |
21 | QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName) |
22 | : QQuickGenerator(fileName, flags) |
23 | , outputFileName(outFileName) |
24 | { |
25 | m_result.open(openMode: QIODevice::ReadWrite); |
26 | } |
27 | |
28 | QQuickQmlGenerator::~QQuickQmlGenerator() |
29 | { |
30 | if (m_generationSucceeded && !outputFileName.isEmpty()) { |
31 | QFileInfo fileInfo(outputFileName); |
32 | QDir dir(fileInfo.absolutePath()); |
33 | if (!dir.exists() && !dir.mkpath(QStringLiteral("." ))) { |
34 | qCWarning(lcQuickVectorImage) << "Failed to create path" << dir.absolutePath(); |
35 | } else { |
36 | stream().flush(); // Add a final newline and flush the stream to m_result |
37 | QFile outFile(outputFileName); |
38 | outFile.open(flags: QIODevice::WriteOnly); |
39 | outFile.write(data: m_result.data()); |
40 | outFile.close(); |
41 | } |
42 | } |
43 | |
44 | if (lcQuickVectorImage().isDebugEnabled()) |
45 | qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(n: 300); |
46 | } |
47 | |
48 | void QQuickQmlGenerator::setShapeTypeName(const QString &name) |
49 | { |
50 | m_shapeTypeName = name.toLatin1(); |
51 | } |
52 | |
53 | QString QQuickQmlGenerator::shapeTypeName() const |
54 | { |
55 | return QString::fromLatin1(ba: m_shapeTypeName); |
56 | } |
57 | |
58 | void QQuickQmlGenerator::(const QString ) |
59 | { |
60 | m_commentString = commentString; |
61 | } |
62 | |
63 | QString QQuickQmlGenerator::() const |
64 | { |
65 | return m_commentString; |
66 | } |
67 | |
68 | void QQuickQmlGenerator::generateNodeBase(const NodeInfo &info) |
69 | { |
70 | m_indentLevel++; |
71 | if (!info.nodeId.isEmpty()) |
72 | stream() << "objectName: \"" << info.nodeId << "\"" ; |
73 | if (!info.isDefaultTransform) { |
74 | auto sx = info.transform.m11(); |
75 | auto sy = info.transform.m22(); |
76 | auto x = info.transform.m31(); |
77 | auto y = info.transform.m32(); |
78 | if (info.transform.type() == QTransform::TxTranslate) { |
79 | stream() << "transform: Translate { " << "x: " << x << "; y: " << y << " }" ; |
80 | } else if (info.transform.type() == QTransform::TxScale && !x && !y) { |
81 | stream() << "transform: Scale { xScale: " << sx << "; yScale: " << sy << " }" ; |
82 | } else { |
83 | stream() << "transform: Matrix4x4 { matrix: " ; |
84 | generateTransform(xf: info.transform); |
85 | stream(flags: SameLine) << " }" ; |
86 | } |
87 | } |
88 | if (!info.isDefaultOpacity) { |
89 | stream() << "opacity: " << info.opacity; |
90 | } |
91 | m_indentLevel--; |
92 | } |
93 | |
94 | bool QQuickQmlGenerator::generateDefsNode(const NodeInfo &info) |
95 | { |
96 | Q_UNUSED(info) |
97 | |
98 | return false; |
99 | } |
100 | |
101 | void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info) |
102 | { |
103 | if (!isNodeVisible(info)) |
104 | return; |
105 | |
106 | const QFileInfo outputFileInfo(outputFileName); |
107 | const QDir outputDir(outputFileInfo.absolutePath()); |
108 | |
109 | QString filePath; |
110 | |
111 | if (!m_retainFilePaths || info.externalFileReference.isEmpty()) { |
112 | filePath = m_assetFileDirectory; |
113 | if (filePath.isEmpty()) |
114 | filePath = outputDir.absolutePath(); |
115 | |
116 | if (!filePath.isEmpty() && !filePath.endsWith(c: u'/')) |
117 | filePath += u'/'; |
118 | |
119 | QDir fileDir(filePath); |
120 | if (!fileDir.exists()) { |
121 | if (!fileDir.mkpath(QStringLiteral("." ))) |
122 | qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath; |
123 | } |
124 | |
125 | filePath += QStringLiteral("%1%2.png" ).arg(a: m_assetFilePrefix.isEmpty() |
126 | ? QStringLiteral("svg_asset_" ) |
127 | : m_assetFilePrefix) |
128 | .arg(a: info.image.cacheKey()); |
129 | |
130 | if (!info.image.save(fileName: filePath)) |
131 | qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath; |
132 | qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath; |
133 | } else { |
134 | filePath = info.externalFileReference; |
135 | } |
136 | |
137 | const QFileInfo assetFileInfo(filePath); |
138 | |
139 | stream() << "Image {" ; |
140 | |
141 | generateNodeBase(info); |
142 | m_indentLevel++; |
143 | stream() << "x: " << info.rect.x(); |
144 | stream() << "y: " << info.rect.y(); |
145 | stream() << "width: " << info.rect.width(); |
146 | stream() << "height: " << info.rect.height(); |
147 | stream() << "source: \"" << outputDir.relativeFilePath(fileName: assetFileInfo.absoluteFilePath()) <<"\"" ; |
148 | |
149 | m_indentLevel--; |
150 | |
151 | stream() << "}" ; |
152 | } |
153 | |
154 | void QQuickQmlGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect) |
155 | { |
156 | if (!isNodeVisible(info)) |
157 | return; |
158 | |
159 | if (m_inShapeItem) { |
160 | if (!info.isDefaultTransform) |
161 | qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)" ; |
162 | optimizePaths(info, overrideBoundingRect); |
163 | } else { |
164 | m_inShapeItem = true; |
165 | stream() << shapeName() << " {" ; |
166 | |
167 | generateNodeBase(info); |
168 | |
169 | m_indentLevel++; |
170 | if (m_flags.testFlag(flag: QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer)) |
171 | stream() << "preferredRendererType: Shape.CurveRenderer" ; |
172 | optimizePaths(info, overrideBoundingRect); |
173 | //qCDebug(lcQuickVectorGraphics) << *node->qpath(); |
174 | m_indentLevel--; |
175 | stream() << "}" ; |
176 | m_inShapeItem = false; |
177 | } |
178 | } |
179 | |
180 | void QQuickQmlGenerator::generateGradient(const QGradient *grad) |
181 | { |
182 | if (grad->type() == QGradient::LinearGradient) { |
183 | auto *linGrad = static_cast<const QLinearGradient *>(grad); |
184 | stream() << "fillGradient: LinearGradient {" ; |
185 | m_indentLevel++; |
186 | |
187 | QRectF gradRect(linGrad->start(), linGrad->finalStop()); |
188 | |
189 | stream() << "x1: " << gradRect.left(); |
190 | stream() << "y1: " << gradRect.top(); |
191 | stream() << "x2: " << gradRect.right(); |
192 | stream() << "y2: " << gradRect.bottom(); |
193 | for (auto &stop : linGrad->stops()) |
194 | stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(format: QColor::HexArgb) << "\" }" ; |
195 | m_indentLevel--; |
196 | stream() << "}" ; |
197 | } else if (grad->type() == QGradient::RadialGradient) { |
198 | auto *radGrad = static_cast<const QRadialGradient*>(grad); |
199 | stream() << "fillGradient: RadialGradient {" ; |
200 | m_indentLevel++; |
201 | |
202 | stream() << "centerX: " << radGrad->center().x(); |
203 | stream() << "centerY: " << radGrad->center().y(); |
204 | stream() << "centerRadius: " << radGrad->radius(); |
205 | stream() << "focalX:" << radGrad->focalPoint().x(); |
206 | stream() << "focalY:" << radGrad->focalPoint().y(); |
207 | for (auto &stop : radGrad->stops()) |
208 | stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(format: QColor::HexArgb) << "\" }" ; |
209 | m_indentLevel--; |
210 | stream() << "}" ; |
211 | } |
212 | } |
213 | |
214 | void QQuickQmlGenerator::generateTransform(const QTransform &xf) |
215 | { |
216 | if (xf.isAffine()) { |
217 | stream(flags: SameLine) << "PlanarTransform.fromAffineMatrix(" |
218 | << xf.m11() << ", " << xf.m12() << ", " |
219 | << xf.m21() << ", " << xf.m22() << ", " |
220 | << xf.dx() << ", " << xf.dy() << ")" ; |
221 | } else { |
222 | QMatrix4x4 m(xf); |
223 | stream(flags: SameLine) << "Qt.matrix4x4(" ; |
224 | m_indentLevel += 3; |
225 | const auto *data = m.data(); |
226 | for (int i = 0; i < 4; i++) { |
227 | stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12]; |
228 | if (i < 3) |
229 | stream(flags: SameLine) << ", " ; |
230 | } |
231 | stream(flags: SameLine) << ")" ; |
232 | m_indentLevel -= 3; |
233 | } |
234 | } |
235 | |
236 | void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect) |
237 | { |
238 | Q_UNUSED(pathSelector) |
239 | Q_ASSERT(painterPath || quadPath); |
240 | |
241 | const bool noPen = info.strokeStyle.color == QColorConstants::Transparent; |
242 | if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen) |
243 | return; |
244 | |
245 | const bool noFill = info.grad.type() == QGradient::NoGradient && info.fillColor == QColorConstants::Transparent; |
246 | |
247 | if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill) |
248 | return; |
249 | |
250 | auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule()); |
251 | stream() << "ShapePath {" ; |
252 | m_indentLevel++; |
253 | if (!info.nodeId.isEmpty()) { |
254 | switch (pathSelector) { |
255 | case QQuickVectorImageGenerator::FillPath: |
256 | stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"" ; |
257 | break; |
258 | case QQuickVectorImageGenerator::StrokePath: |
259 | stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"" ; |
260 | break; |
261 | case QQuickVectorImageGenerator::FillAndStroke: |
262 | stream() << "objectName: \"svg_path:" << info.nodeId << "\"" ; |
263 | break; |
264 | } |
265 | } |
266 | |
267 | if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) { |
268 | stream() << "strokeColor: \"transparent\"" ; |
269 | } else { |
270 | stream() << "strokeColor: \"" << info.strokeStyle.color.name(format: QColor::HexArgb) << "\"" ; |
271 | stream() << "strokeWidth: " << info.strokeStyle.width; |
272 | stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(strokeCapStyle: info.strokeStyle.lineCapStyle); |
273 | stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(strokeJoinStyle: info.strokeStyle.lineJoinStyle); |
274 | stream() << "miterLimit: " << info.strokeStyle.miterLimit; |
275 | if (info.strokeStyle.dashArray.length() != 0) { |
276 | stream() << "strokeStyle: " << "ShapePath.DashLine" ; |
277 | stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(list: info.strokeStyle.dashArray); |
278 | stream() << "dashOffset: " << info.strokeStyle.dashOffset; |
279 | } |
280 | } |
281 | |
282 | QTransform fillTransform = info.fillTransform; |
283 | if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) { |
284 | stream() << "fillColor: \"transparent\"" ; |
285 | } else if (info.grad.type() != QGradient::NoGradient) { |
286 | generateGradient(grad: &info.grad); |
287 | if (info.grad.coordinateMode() == QGradient::ObjectMode) { |
288 | QTransform objectToUserSpace; |
289 | objectToUserSpace.translate(dx: boundingRect.x(), dy: boundingRect.y()); |
290 | objectToUserSpace.scale(sx: boundingRect.width(), sy: boundingRect.height()); |
291 | fillTransform *= objectToUserSpace; |
292 | } |
293 | } else { |
294 | stream() << "fillColor: \"" << info.fillColor.name(format: QColor::HexArgb) << "\"" ; |
295 | } |
296 | |
297 | if (!fillTransform.isIdentity()) { |
298 | const QTransform &xf = fillTransform; |
299 | stream() << "fillTransform: " ; |
300 | if (info.fillTransform.type() == QTransform::TxTranslate) |
301 | stream(flags: SameLine) << "PlanarTransform.fromTranslate(" << xf.dx() << ", " << xf.dy() << ")" ; |
302 | else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy()) |
303 | stream(flags: SameLine) << "PlanarTransform.fromScale(" << xf.m11() << ", " << xf.m22() << ")" ; |
304 | else |
305 | generateTransform(xf); |
306 | } |
307 | |
308 | if (fillRule == QQuickShapePath::WindingFill) |
309 | stream() << "fillRule: ShapePath.WindingFill" ; |
310 | else |
311 | stream() << "fillRule: ShapePath.OddEvenFill" ; |
312 | |
313 | QString hintStr; |
314 | if (quadPath) |
315 | hintStr = QQuickVectorImageGenerator::Utils::pathHintString(qp: *quadPath); |
316 | if (!hintStr.isEmpty()) |
317 | stream() << hintStr; |
318 | |
319 | |
320 | QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(path: *painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(path: *quadPath); |
321 | stream() << "PathSvg { path: \"" << svgPathString << "\" }" ; |
322 | |
323 | m_indentLevel--; |
324 | stream() << "}" ; |
325 | } |
326 | |
327 | void QQuickQmlGenerator::generateNode(const NodeInfo &info) |
328 | { |
329 | if (!isNodeVisible(info)) |
330 | return; |
331 | |
332 | stream() << "// Missing Implementation for SVG Node: " << info.typeName; |
333 | stream() << "// Adding an empty Item and skipping" ; |
334 | stream() << "Item {" ; |
335 | generateNodeBase(info); |
336 | stream() << "}" ; |
337 | } |
338 | |
339 | void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info) |
340 | { |
341 | if (!isNodeVisible(info)) |
342 | return; |
343 | |
344 | static int counter = 0; |
345 | stream() << "Item {" ; |
346 | generateNodeBase(info); |
347 | m_indentLevel++; |
348 | |
349 | if (!info.isTextArea) |
350 | stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}" ; |
351 | |
352 | stream() << "Text {" ; |
353 | |
354 | m_indentLevel++; |
355 | |
356 | if (info.isTextArea) { |
357 | stream() << "x: " << info.position.x(); |
358 | stream() << "y: " << info.position.y(); |
359 | if (info.size.width() > 0) |
360 | stream() << "width: " << info.size.width(); |
361 | if (info.size.height() > 0) |
362 | stream() << "height: " << info.size.height(); |
363 | stream() << "wrapMode: Text.Wrap" ; // ### WordWrap? verify with SVG standard |
364 | stream() << "clip: true" ; //### Not exactly correct: should clip on the text level, not the pixel level |
365 | } else { |
366 | QString hAlign = QStringLiteral("left" ); |
367 | stream() << "anchors.baseline: textAlignItem_" << counter << ".top" ; |
368 | switch (info.alignment) { |
369 | case Qt::AlignHCenter: |
370 | hAlign = QStringLiteral("horizontalCenter" ); |
371 | break; |
372 | case Qt::AlignRight: |
373 | hAlign = QStringLiteral("right" ); |
374 | break; |
375 | default: |
376 | qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment; |
377 | Q_FALLTHROUGH(); |
378 | case Qt::AlignLeft: |
379 | break; |
380 | } |
381 | stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left" ; |
382 | } |
383 | counter++; |
384 | |
385 | stream() << "color: \"" << info.fillColor.name(format: QColor::HexArgb) << "\"" ; |
386 | stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText" ); |
387 | |
388 | QString s = info.text; |
389 | s.replace(c: QLatin1Char('"'), after: QLatin1String("\\\"" )); |
390 | stream() << "text: \"" << s << "\"" ; |
391 | stream() << "font.family: \"" << info.font.family() << "\"" ; |
392 | if (info.font.pixelSize() > 0) |
393 | stream() << "font.pixelSize:" << info.font.pixelSize(); |
394 | else if (info.font.pointSize() > 0) |
395 | stream() << "font.pixelSize:" << info.font.pointSizeF(); |
396 | if (info.font.underline()) |
397 | stream() << "font.underline: true" ; |
398 | if (info.font.weight() != QFont::Normal) |
399 | stream() << "font.weight: " << int(info.font.weight()); |
400 | if (info.font.italic()) |
401 | stream() << "font.italic: true" ; |
402 | switch (info.font.hintingPreference()) { |
403 | case QFont::PreferFullHinting: |
404 | stream() << "font.hintingPreference: Font.PreferFullHinting" ; |
405 | break; |
406 | case QFont::PreferVerticalHinting: |
407 | stream() << "font.hintingPreference: Font.PreferVerticalHinting" ; |
408 | break; |
409 | case QFont::PreferNoHinting: |
410 | stream() << "font.hintingPreference: Font.PreferNoHinting" ; |
411 | break; |
412 | case QFont::PreferDefaultHinting: |
413 | stream() << "font.hintingPreference: Font.PreferDefaultHinting" ; |
414 | break; |
415 | }; |
416 | |
417 | if (info.strokeColor != QColorConstants::Transparent) { |
418 | stream() << "styleColor: \"" << info.strokeColor.name(format: QColor::HexArgb) << "\"" ; |
419 | stream() << "style: Text.Outline" ; |
420 | } |
421 | |
422 | m_indentLevel--; |
423 | stream() << "}" ; |
424 | |
425 | m_indentLevel--; |
426 | stream() << "}" ; |
427 | } |
428 | |
429 | void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info) |
430 | { |
431 | if (!isNodeVisible(info)) |
432 | return; |
433 | |
434 | if (info.stage == StructureNodeStage::Start) { |
435 | stream() << "Item {" ; |
436 | generateNodeBase(info); |
437 | m_indentLevel++; |
438 | stream() << "x: " << info.startPos.x(); |
439 | stream() << "y: " << info.startPos.y(); |
440 | } else { |
441 | m_indentLevel--; |
442 | stream() << "}" ; |
443 | } |
444 | } |
445 | |
446 | void QQuickQmlGenerator::generatePathContainer(const StructureNodeInfo &info) |
447 | { |
448 | Q_UNUSED(info); |
449 | stream() << shapeName() <<" {" ; |
450 | m_indentLevel++; |
451 | if (m_flags.testFlag(flag: QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer)) |
452 | stream() << "preferredRendererType: Shape.CurveRenderer" ; |
453 | m_indentLevel--; |
454 | |
455 | m_inShapeItem = true; |
456 | } |
457 | |
458 | bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info) |
459 | { |
460 | if (!isNodeVisible(info)) |
461 | return false; |
462 | |
463 | if (info.stage == StructureNodeStage::Start) { |
464 | if (!info.forceSeparatePaths && info.isPathContainer) { |
465 | generatePathContainer(info); |
466 | } else { |
467 | stream() << "Item {" ; |
468 | } |
469 | |
470 | if (!info.viewBox.isEmpty()) { |
471 | m_indentLevel++; |
472 | stream() << "transform: [" ; |
473 | m_indentLevel++; |
474 | bool translate = !qFuzzyIsNull(d: info.viewBox.x()) || !qFuzzyIsNull(d: info.viewBox.y()); |
475 | if (translate) |
476 | stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " }," ; |
477 | stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }" ; |
478 | m_indentLevel--; |
479 | stream() << "]" ; |
480 | m_indentLevel--; |
481 | } |
482 | |
483 | generateNodeBase(info); |
484 | m_indentLevel++; |
485 | } else { |
486 | m_indentLevel--; |
487 | stream() << "}" ; |
488 | m_inShapeItem = false; |
489 | } |
490 | |
491 | return true; |
492 | } |
493 | |
494 | bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info) |
495 | { |
496 | const QStringList = m_commentString.split(sep: u'\n'); |
497 | |
498 | if (!isNodeVisible(info)) { |
499 | m_indentLevel = 0; |
500 | |
501 | if (comments.isEmpty()) { |
502 | stream() << "// Generated from SVG" ; |
503 | } else { |
504 | for (const auto & : comments) |
505 | stream() << "// " << comment; |
506 | } |
507 | |
508 | stream() << "import QtQuick" ; |
509 | stream() << "import QtQuick.Shapes" << Qt::endl; |
510 | stream() << "Item {" ; |
511 | m_indentLevel++; |
512 | |
513 | double w = info.size.width(); |
514 | double h = info.size.height(); |
515 | if (w > 0) |
516 | stream() << "implicitWidth: " << w; |
517 | if (h > 0) |
518 | stream() << "implicitHeight: " << h; |
519 | |
520 | m_indentLevel--; |
521 | stream() << "}" ; |
522 | |
523 | return false; |
524 | } |
525 | |
526 | if (info.stage == StructureNodeStage::Start) { |
527 | m_indentLevel = 0; |
528 | |
529 | if (comments.isEmpty()) |
530 | stream() << "// Generated from SVG" ; |
531 | else |
532 | for (const auto & : comments) |
533 | stream() << "// " << comment; |
534 | |
535 | stream() << "import QtQuick" ; |
536 | stream() << "import QtQuick.Shapes" << Qt::endl; |
537 | stream() << "Item {" ; |
538 | m_indentLevel++; |
539 | |
540 | double w = info.size.width(); |
541 | double h = info.size.height(); |
542 | if (w > 0) |
543 | stream() << "implicitWidth: " << w; |
544 | if (h > 0) |
545 | stream() << "implicitHeight: " << h; |
546 | |
547 | if (!info.viewBox.isEmpty()) { |
548 | stream() << "transform: [" ; |
549 | m_indentLevel++; |
550 | bool translate = !qFuzzyIsNull(d: info.viewBox.x()) || !qFuzzyIsNull(d: info.viewBox.y()); |
551 | if (translate) |
552 | stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " }," ; |
553 | stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }" ; |
554 | m_indentLevel--; |
555 | stream() << "]" ; |
556 | } |
557 | |
558 | generateNodeBase(info); |
559 | |
560 | if (!info.forceSeparatePaths && info.isPathContainer) { |
561 | generatePathContainer(info); |
562 | m_indentLevel++; |
563 | } |
564 | } else { |
565 | if (m_inShapeItem) { |
566 | m_inShapeItem = false; |
567 | m_indentLevel--; |
568 | stream() << "}" ; |
569 | } |
570 | |
571 | m_indentLevel--; |
572 | stream() << "}" ; |
573 | } |
574 | |
575 | return true; |
576 | } |
577 | |
578 | QStringView QQuickQmlGenerator::indent() |
579 | { |
580 | static QString indentString; |
581 | int indentWidth = m_indentLevel * 4; |
582 | if (indentWidth > indentString.size()) |
583 | indentString.fill(c: QLatin1Char(' '), size: indentWidth * 2); |
584 | return QStringView(indentString).first(n: indentWidth); |
585 | } |
586 | |
587 | QTextStream &QQuickQmlGenerator::stream(int flags) |
588 | { |
589 | if (m_stream.device() == nullptr) |
590 | m_stream.setDevice(&m_result); |
591 | else if (!(flags & StreamFlags::SameLine)) |
592 | m_stream << Qt::endl << indent(); |
593 | return m_stream; |
594 | } |
595 | |
596 | const char *QQuickQmlGenerator::shapeName() const |
597 | { |
598 | return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData(); |
599 | } |
600 | |
601 | QT_END_NAMESPACE |
602 | |