| 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 |  |