aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/quickvectorgraphics/CMakeLists.txt24
-rw-r--r--src/quickvectorgraphics/generator/qquickgenerator.cpp72
-rw-r--r--src/quickvectorgraphics/generator/qquickgenerator_p.h73
-rw-r--r--src/quickvectorgraphics/generator/qquickitemgenerator.cpp331
-rw-r--r--src/quickvectorgraphics/generator/qquickitemgenerator_p.h56
-rw-r--r--src/quickvectorgraphics/generator/qquicknodeinfo_p.h85
-rw-r--r--src/quickvectorgraphics/generator/qquickqmlgenerator.cpp481
-rw-r--r--src/quickvectorgraphics/generator/qquickqmlgenerator_p.h67
-rw-r--r--src/quickvectorgraphics/generator/qsvgvisitorimpl.cpp455
-rw-r--r--src/quickvectorgraphics/generator/qsvgvisitorimpl_p.h66
-rw-r--r--src/quickvectorgraphics/generator/utils_p.h200
-rw-r--r--src/quickvectorgraphics/qquickvectorgraphicsglobal_p.h43
-rw-r--r--tools/svgtoqml/CMakeLists.txt6
-rw-r--r--tools/svgtoqml/main.cpp56
-rw-r--r--tools/svgtoqml/qsvgloader.cpp1119
-rw-r--r--tools/svgtoqml/qsvgloader_p.h32
17 files changed, 1994 insertions, 1176 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ca812db713..57da69667b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -56,6 +56,10 @@ if(TARGET Qt::Gui AND TARGET Qt::qsb AND QT_FEATURE_qml_animation)
add_subdirectory(quickshapes)
endif()
+ if(TARGET Qt::Svg)
+ add_subdirectory(quickvectorgraphics)
+ endif()
+
if(TARGET Qt::Widgets)
add_subdirectory(quickwidgets)
endif()
diff --git a/src/quickvectorgraphics/CMakeLists.txt b/src/quickvectorgraphics/CMakeLists.txt
new file mode 100644
index 0000000000..287a6c1b9d
--- /dev/null
+++ b/src/quickvectorgraphics/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## QuickVectorGraphics Module:
+#####################################################################
+
+qt_internal_add_module(QuickVectorGraphicsGeneratorPrivate
+ INTERNAL_MODULE
+ SOURCES
+ generator/qsvgvisitorimpl_p.h generator/qsvgvisitorimpl.cpp
+ generator/qquickgenerator_p.h generator/qquickgenerator.cpp
+ generator/qquickitemgenerator_p.h generator/qquickitemgenerator.cpp
+ generator/qquickqmlgenerator_p.h generator/qquickqmlgenerator.cpp
+ generator/qquicknodeinfo_p.h
+ generator/utils_p.h
+ qquickvectorgraphicsglobal_p.h
+ LIBRARIES
+ Qt::Core
+ Qt::QuickPrivate
+ Qt::QuickShapesPrivate
+ Qt::SvgPrivate
+ GENERATE_CPP_EXPORTS
+)
diff --git a/src/quickvectorgraphics/generator/qquickgenerator.cpp b/src/quickvectorgraphics/generator/qquickgenerator.cpp
new file mode 100644
index 0000000000..4d9600dbe5
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickgenerator.cpp
@@ -0,0 +1,72 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickgenerator_p.h"
+#include "qsvgvisitorimpl_p.h"
+#include "qquicknodeinfo_p.h"
+
+#include <private/qsgcurveprocessor_p.h>
+#include <private/qquickshape_p.h>
+#include <private/qquadpath_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qquickimagebase_p_p.h>
+
+#include<QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQuickVectorGraphics, "qt.quick.vectorgraphics", QtWarningMsg)
+
+QQuickGenerator::QQuickGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags)
+ : m_flags(flags)
+ , m_fileName(fileName)
+ , m_loader(nullptr)
+{
+}
+
+QQuickGenerator::~QQuickGenerator()
+{
+ delete m_loader;
+}
+
+void QQuickGenerator::setGeneratorFlags(QQuickVectorGraphics::GeneratorFlags flags)
+{
+ m_flags = flags;
+}
+
+QQuickVectorGraphics::GeneratorFlags QQuickGenerator::generatorFlags()
+{
+ return m_flags;
+}
+
+void QQuickGenerator::generate()
+{
+ m_loader = new QSvgVisitorImpl(m_fileName, this);
+ m_loader->traverse();
+}
+
+void QQuickGenerator::optimizePaths(const PathNodeInfo &info)
+{
+ QPainterPath pathCopy = info.painterPath;
+ pathCopy.setFillRule(info.fillRule);
+
+ if (m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::OptimizePaths)) {
+ QQuadPath strokePath = QQuadPath::fromPainterPath(pathCopy);
+ bool fillPathNeededClose;
+ QQuadPath fillPath = strokePath.subPathsClosed(&fillPathNeededClose);
+ const bool intersectionsFound = QSGCurveProcessor::solveIntersections(fillPath, false);
+ fillPath.addCurvatureData();
+ QSGCurveProcessor::solveOverlaps(fillPath);
+ const bool compatibleStrokeAndFill = !fillPathNeededClose && !intersectionsFound;
+ if (compatibleStrokeAndFill || m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::OutlineStrokeMode)) {
+ outputShapePath(info, nullptr, &fillPath, QQuickVectorGraphics::FillAndStroke, pathCopy.boundingRect());
+ } else {
+ outputShapePath(info, nullptr, &fillPath, QQuickVectorGraphics::FillPath, pathCopy.boundingRect());
+ outputShapePath(info, nullptr, &strokePath, QQuickVectorGraphics::StrokePath, pathCopy.boundingRect());
+ }
+ } else {
+ outputShapePath(info, &pathCopy, nullptr, QQuickVectorGraphics::FillAndStroke, pathCopy.boundingRect());
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorgraphics/generator/qquickgenerator_p.h b/src/quickvectorgraphics/generator/qquickgenerator_p.h
new file mode 100644
index 0000000000..6bd8a934f8
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickgenerator_p.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QQUICKGENERATOR_P_H
+#define QQUICKGENERATOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qquickvectorgraphicsglobal_p.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSvgVisitorImpl;
+class QPainterPath;
+class QGradient;
+class QQuickShapePath;
+class QQuadPath;
+class QQuickItem;
+class QQuickShape;
+class QRectF;
+
+struct NodeInfo;
+struct ImageNodeInfo;
+struct PathNodeInfo;
+struct TextNodeInfo;
+struct StructureNodeInfo;
+
+class Q_QUICKVECTORGRAPHICSGENERATOR_EXPORT QQuickGenerator
+{
+public:
+ QQuickGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags);
+ virtual ~QQuickGenerator();
+
+ void setGeneratorFlags(QQuickVectorGraphics::GeneratorFlags flags);
+ QQuickVectorGraphics::GeneratorFlags generatorFlags();
+
+ void generate();
+
+protected:
+ virtual void generateNodeBase(NodeInfo &info) = 0;
+ virtual bool generateDefsNode(NodeInfo &info) = 0;
+ virtual void generateImageNode(ImageNodeInfo &info) = 0;
+ virtual void generatePath(PathNodeInfo &info) = 0;
+ virtual void generateNode(NodeInfo &info) = 0;
+ virtual void generateTextNode(TextNodeInfo &info) = 0;
+ virtual void generateStructureNode(StructureNodeInfo &info) = 0;
+ virtual void generateRootNode(StructureNodeInfo &info) = 0;
+ virtual void generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect) = 0;
+ virtual void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorGraphics::PathSelector pathSelector, const QRectF &boundingRect) = 0;
+ void optimizePaths(const PathNodeInfo &info);
+
+protected:
+ QQuickVectorGraphics::GeneratorFlags m_flags;
+
+private:
+ QString m_fileName;
+ QSvgVisitorImpl *m_loader;
+ friend class QSvgVisitorImpl;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKGENERATOR_P_H
diff --git a/src/quickvectorgraphics/generator/qquickitemgenerator.cpp b/src/quickvectorgraphics/generator/qquickitemgenerator.cpp
new file mode 100644
index 0000000000..cbae63c703
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickitemgenerator.cpp
@@ -0,0 +1,331 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickitemgenerator_p.h"
+#include "utils_p.h"
+#include "qquicknodeinfo_p.h"
+
+#include <private/qsgcurveprocessor_p.h>
+#include <private/qquickshape_p.h>
+#include <private/qquadpath_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qquickimagebase_p_p.h>
+
+#include<QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorGraphics)
+
+QQuickItemGenerator::QQuickItemGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags, QQuickItem *parentItem)
+ :QQuickGenerator(fileName, flags)
+{
+ Q_ASSERT(parentItem);
+ m_items.push(parentItem);
+ m_parentItem = parentItem;
+}
+
+QQuickItemGenerator::~QQuickItemGenerator()
+{
+
+}
+
+void QQuickItemGenerator::generateNodeBase(NodeInfo &info)
+{
+ if (!info.isDefaultTransform) {
+ auto sx = info.transform.m11();
+ auto sy = info.transform.m22();
+ auto x = info.transform.m31();
+ auto y = info.transform.m32();
+
+ auto xformProp = currentItem()->transform();
+ if (info.transform.type() == QTransform::TxTranslate) {
+ auto *translate = new QQuickTranslate;
+ translate->setX(x);
+ translate->setY(y);
+ xformProp.append(&xformProp, translate);
+ } else if (info.transform.type() == QTransform::TxScale && !x && !y) {
+ auto scale = new QQuickScale;
+ scale->setParent(currentItem());
+ scale->setXScale(sx);
+ scale->setYScale(sy);
+ xformProp.append(&xformProp, scale);
+ } else {
+ const QMatrix4x4 m(info.transform);
+ auto xform = new QQuickMatrix4x4;
+ xform->setMatrix(m);
+ xformProp.append(&xformProp, xform);
+ }
+ }
+ if (!info.isDefaultOpacity) {
+ currentItem()->setOpacity(info.opacity);
+ }
+}
+
+bool QQuickItemGenerator::generateDefsNode(NodeInfo &info)
+{
+ Q_UNUSED(info)
+
+ return false;
+}
+
+void QQuickItemGenerator::generateImageNode(ImageNodeInfo &info)
+{
+ auto *imageItem = new QQuickImage;
+ auto *imagePriv = static_cast<QQuickImageBasePrivate*>(QQuickItemPrivate::get(imageItem));
+ imagePriv->pix.setImage(info.image);
+
+ imageItem->setX(info.rect.x());
+ imageItem->setY(info.rect.y());
+ imageItem->setWidth(info.rect.width());
+ imageItem->setHeight(info.rect.height());
+
+ generateNodeBase(info);
+
+ addCurrentItem(imageItem, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+ m_items.pop();
+}
+
+void QQuickItemGenerator::generatePath(PathNodeInfo &info)
+{
+ if (m_inShapeItem) {
+ if (!info.isDefaultTransform)
+ qCWarning(lcQuickVectorGraphics) << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
+ optimizePaths(info);
+ } else {
+ auto *shapeItem = new QQuickShape;
+ if (m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::CurveRenderer))
+ shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
+ shapeItem->setContainsMode(QQuickShape::ContainsMode::FillContains); // TODO: configurable?
+ addCurrentItem(shapeItem, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+ m_parentShapeItem = shapeItem;
+ m_inShapeItem = true;
+
+ // Check ??
+ generateNodeBase(info);
+
+ optimizePaths(info);
+ //qCDebug(lcQuickVectorGraphics) << *node->qpath();
+ m_items.pop();
+ m_inShapeItem = false;
+ m_parentShapeItem = nullptr;
+ }
+}
+
+void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorGraphics::PathSelector pathSelector, const QRectF &boundingRect)
+{
+ Q_UNUSED(pathSelector)
+ Q_ASSERT(painterPath || quadPath);
+
+ QString penName = info.strokeColor;
+ const bool noPen = penName.isEmpty() || penName == u"transparent";
+ if (pathSelector == QQuickVectorGraphics::StrokePath && noPen)
+ return;
+
+ const bool noFill = !info.grad && info.fillColor == u"transparent";
+ if (pathSelector == QQuickVectorGraphics::FillPath && noFill)
+ return;
+
+ QQuickShapePath::FillRule fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
+
+ QQuickShapePath *shapePath = new QQuickShapePath;
+ Q_ASSERT(shapePath);
+
+ if (!info.nodeId.isEmpty()) {
+
+ shapePath->setObjectName(QStringLiteral("svg_path:") + info.nodeId);
+ }
+
+ if (noPen || !(pathSelector & QQuickVectorGraphics::StrokePath)) {
+ shapePath->setStrokeColor(Qt::transparent);
+ } else {
+ shapePath->setStrokeColor(QColor::fromString(penName));
+ shapePath->setStrokeWidth(info.strokeWidth);
+ }
+
+ shapePath->setCapStyle(QQuickShapePath::CapStyle(info.capStyle));
+
+ if (!(pathSelector & QQuickVectorGraphics::FillPath))
+ shapePath->setFillColor(Qt::transparent);
+ else if (auto *grad = info.grad)
+ generateGradient(grad, shapePath, boundingRect);
+ else
+ shapePath->setFillColor(QColor::fromString(info.fillColor));
+
+ shapePath->setFillRule(fillRule);
+
+ QString svgPathString = painterPath ? QQuickVectorGraphics::Utils::toSvgString(*painterPath) : QQuickVectorGraphics::Utils::toSvgString(*quadPath);
+
+ auto *pathSvg = new QQuickPathSvg;
+ pathSvg->setPath(svgPathString);
+ pathSvg->setParent(shapePath);
+
+ auto pathElementProp = shapePath->pathElements();
+ pathElementProp.append(&pathElementProp, pathSvg);
+
+ shapePath->setParent(currentItem());
+ auto shapeDataProp = m_parentShapeItem->data();
+ shapeDataProp.append(&shapeDataProp, shapePath);
+}
+
+void QQuickItemGenerator::generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect)
+{
+ auto setStops = [](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
+ auto stopsProp = quickGrad->stops();
+ for (auto &stop : stops) {
+ auto *stopObj = new QQuickGradientStop(quickGrad);
+ stopObj->setPosition(stop.first);
+ stopObj->setColor(stop.second);
+ stopsProp.append(&stopsProp, stopObj);
+ }
+ };
+
+ if (grad->type() == QGradient::LinearGradient) {
+ auto *linGrad = static_cast<const QLinearGradient *>(grad);
+
+ QRectF gradRect(linGrad->start(), linGrad->finalStop());
+ QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : QQuickVectorGraphics::Utils::mapToQtLogicalMode(gradRect, boundingRect);
+
+ if (shapePath) {
+ auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
+
+ quickGrad->setX1(logRect.left());
+ quickGrad->setY1(logRect.top());
+ quickGrad->setX2(logRect.right());
+ quickGrad->setY2(logRect.bottom());
+ setStops(quickGrad, linGrad->stops());
+
+ shapePath->setFillGradient(quickGrad);
+ }
+ } else if (grad->type() == QGradient::RadialGradient) {
+ auto *radGrad = static_cast<const QRadialGradient*>(grad);
+
+ if (shapePath) {
+ auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
+ quickGrad->setCenterX(radGrad->center().x());
+ quickGrad->setCenterY(radGrad->center().y());
+ quickGrad->setCenterRadius(radGrad->radius());
+ quickGrad->setFocalX(radGrad->center().x());
+ quickGrad->setFocalY(radGrad->center().y());
+ setStops(quickGrad, radGrad->stops());
+
+ shapePath->setFillGradient(quickGrad);
+ }
+ }
+}
+
+void QQuickItemGenerator::generateNode(NodeInfo &info)
+{
+ qCWarning(lcQuickVectorGraphics) << "//### SVG NODE NOT IMPLEMENTED: " << info.nodeId << " type: " << info.typeName;
+}
+
+void QQuickItemGenerator::generateTextNode(TextNodeInfo &info)
+{
+ QQuickItem *alignItem = nullptr;
+ QQuickText *textItem = nullptr;
+
+ if (!info.isTextArea) {
+ alignItem = new QQuickItem(currentItem());
+ alignItem->setX(info.position.x());
+ alignItem->setY(info.position.y());
+ }
+
+ textItem = new QQuickText;
+ addCurrentItem(textItem, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+
+ if (info.isTextArea) {
+ textItem->setX(info.position.x());
+ textItem->setY(info.position.y());
+ textItem->setWidth(info.size.width());
+ textItem->setHeight(info.size.height());
+ textItem->setWrapMode(QQuickText::Wrap);
+ textItem->setClip(true);
+ } else {
+ auto *anchors = QQuickItemPrivate::get(textItem)->anchors();
+ auto *alignPrivate = QQuickItemPrivate::get(alignItem);
+ anchors->setBaseline(alignPrivate->top());
+
+ QString hAlign = QStringLiteral("left");
+ switch (info.alignment) {
+ case Qt::AlignHCenter:
+ hAlign = QStringLiteral("horizontalCenter");
+ anchors->setHorizontalCenter(alignPrivate->left());
+ break;
+ case Qt::AlignRight:
+ hAlign = QStringLiteral("right");
+ anchors->setRight(alignPrivate->left());
+ break;
+ default:
+ qCDebug(lcQuickVectorGraphics) << "Unexpected text alignment" << info.alignment;
+ Q_FALLTHROUGH();
+ case Qt::AlignLeft:
+ anchors->setLeft(alignPrivate->left());
+ break;
+ }
+ }
+
+ textItem->setColor(QColor::fromString(info.color));
+ textItem->setTextFormat(QQuickText::StyledText);
+ textItem->setText(info.text);
+ textItem->setFont(info.font);
+
+ m_items.pop();
+}
+
+void QQuickItemGenerator::generateStructureNode(StructureNodeInfo &info)
+{
+ if (info.stage == StructureNodeInfo::StructureNodeStage::Start) {
+ if (!info.forceSeparatePaths && info.isPathContainer) {
+ m_inShapeItem = true;
+ auto *shapeItem = new QQuickShape;
+ if (m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::CurveRenderer))
+ shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
+ m_parentShapeItem = shapeItem;
+ addCurrentItem(shapeItem, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+ } else {
+ QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorGraphics::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
+ addCurrentItem(item, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+ }
+
+ generateNodeBase(info);
+ } else {
+ m_inShapeItem = false;
+ m_parentShapeItem = nullptr;
+ m_items.pop();
+ }
+}
+
+void QQuickItemGenerator::generateRootNode(StructureNodeInfo &info)
+{
+ if (info.stage == StructureNodeInfo::StructureNodeStage::Start) {
+ QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorGraphics::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
+ addCurrentItem(item, !info.nodeId.isEmpty() ? info.nodeId : info.typeName);
+ if (info.size.width() > 0)
+ m_parentItem->setImplicitWidth(info.size.width());
+
+ if (info.size.height() > 0)
+ m_parentItem->setImplicitHeight(info.size.height());
+ m_loadedItem = item;
+ m_loadedItem->setWidth(m_parentItem->implicitWidth());
+ m_loadedItem->setHeight(m_parentItem->implicitHeight());
+ generateNodeBase(info);
+ } else {
+ m_inShapeItem = false;
+ m_parentShapeItem = nullptr;
+ m_items.pop();
+ }
+}
+
+QQuickItem *QQuickItemGenerator::currentItem()
+{
+ return m_items.top();
+}
+
+void QQuickItemGenerator::addCurrentItem(QQuickItem *item, const QString &name)
+{
+ item->setParentItem(currentItem());
+ m_items.push(item);
+ item->setObjectName(name);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorgraphics/generator/qquickitemgenerator_p.h b/src/quickvectorgraphics/generator/qquickitemgenerator_p.h
new file mode 100644
index 0000000000..2224d5646f
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickitemgenerator_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QQUICKITEMGENERATOR_P_H
+#define QQUICKITEMGENERATOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qquickgenerator_p.h"
+#include <QStack>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICKVECTORGRAPHICSGENERATOR_EXPORT QQuickItemGenerator : public QQuickGenerator
+{
+public:
+ QQuickItemGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags, QQuickItem *parentItem);
+ ~QQuickItemGenerator();
+
+protected:
+ void generateNodeBase(NodeInfo &info) override;
+ bool generateDefsNode(NodeInfo &info) override;
+ void generateImageNode(ImageNodeInfo &info) override;
+ void generatePath(PathNodeInfo &info) override;
+ void generateNode(NodeInfo &info) override;
+ void generateTextNode(TextNodeInfo &info) override;
+ void generateStructureNode(StructureNodeInfo &info) override;
+ void generateRootNode(StructureNodeInfo &info) override;
+ void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorGraphics::PathSelector pathSelector, const QRectF &boundingRect) override;
+ void generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect) override;
+
+private:
+ QQuickItem *currentItem();
+ void addCurrentItem(QQuickItem *item, const QString &name);
+
+ bool m_inShapeItem = false;
+ QQuickShape *m_parentShapeItem = nullptr;
+
+ QStack<QQuickItem *> m_items;
+
+ QQuickItem *m_loadedItem = nullptr;
+ QQuickItem *m_parentItem = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKITEMGENERATOR_P_H
diff --git a/src/quickvectorgraphics/generator/qquicknodeinfo_p.h b/src/quickvectorgraphics/generator/qquicknodeinfo_p.h
new file mode 100644
index 0000000000..e22ef10498
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquicknodeinfo_p.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QQUICKNODEINFO_P_H
+#define QQUICKNODEINFO_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QString>
+#include <QPainter>
+#include <QPainterPath>
+#include <QMatrix4x4>
+#include <QQuickItem>
+#include <private/qquicktext_p.h>
+#include <private/qquicktranslate_p.h>
+#include <private/qquickimage_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct NodeInfo
+{
+ QString nodeId;
+ QString typeName;
+ QTransform transform;
+ qreal opacity;
+ bool isDefaultTransform;
+ bool isDefaultOpacity;
+};
+
+struct ImageNodeInfo : NodeInfo
+{
+ QImage image;
+ QRectF rect;
+};
+
+struct PathNodeInfo : NodeInfo
+{
+ QPainterPath painterPath;
+ Qt::FillRule fillRule = Qt::FillRule::OddEvenFill;
+ Qt::PenCapStyle capStyle = Qt::SquareCap;
+ QString strokeColor;
+ qreal strokeWidth;
+ QString fillColor;
+ const QGradient *grad;
+};
+
+struct TextNodeInfo : NodeInfo
+{
+ bool isTextArea;
+ QPointF position;
+ QSizeF size;
+ QString text;
+ QFont font;
+ Qt::Alignment alignment;
+ QString color;
+};
+
+struct StructureNodeInfo : NodeInfo
+{
+ enum StructureNodeStage
+ {
+ Start,
+ End
+ };
+
+ StructureNodeStage stage;
+ bool forceSeparatePaths;
+ QRectF viewBox;
+ QSize size;
+ bool isPathContainer;
+};
+
+
+QT_END_NAMESPACE
+
+#endif //QQUICKNODEINFO_P_H
diff --git a/src/quickvectorgraphics/generator/qquickqmlgenerator.cpp b/src/quickvectorgraphics/generator/qquickqmlgenerator.cpp
new file mode 100644
index 0000000000..85bd6aa339
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickqmlgenerator.cpp
@@ -0,0 +1,481 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickqmlgenerator_p.h"
+#include "qquicknodeinfo_p.h"
+#include "utils_p.h"
+
+#include <private/qsgcurveprocessor_p.h>
+#include <private/qquickshape_p.h>
+#include <private/qquadpath_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qquickimagebase_p_p.h>
+
+#include<QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorGraphics)
+
+class GeneratorStream : public QTextStream
+{
+public:
+ GeneratorStream() = default;
+ explicit GeneratorStream(QTextStream *stream): QTextStream(&m_output, QIODevice::ReadWrite), m_stream(stream) {}
+ ~GeneratorStream() {
+ flush();
+ if (m_stream && !m_output.isEmpty())
+ *m_stream << m_output << Qt::endl;
+ }
+
+ GeneratorStream(GeneratorStream &other) = delete;
+ GeneratorStream &operator=(const GeneratorStream &other) = delete;
+ GeneratorStream(GeneratorStream &&other) : m_stream(std::exchange(other.m_stream, nullptr)), m_output(std::move(other.m_output)) {}
+ GeneratorStream &operator=(GeneratorStream &&other) {
+ std::swap(m_stream, other.m_stream);
+ std::swap(m_output, other.m_output);
+ return *this;
+ }
+
+private:
+ QTextStream *m_stream = nullptr;
+ QByteArray m_output;
+};
+
+QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags, const QString &outFileName)
+ : QQuickGenerator(fileName, flags)
+ , outputFileName(outFileName)
+{
+ m_stream = new QTextStream(&result);
+}
+
+QQuickQmlGenerator::~QQuickQmlGenerator()
+{
+ if (!outputFileName.isEmpty()) {
+ QFile outFile(outputFileName);
+ outFile.open(QIODevice::WriteOnly);
+ outFile.write(result);
+ outFile.close();
+ }
+
+ if (lcQuickVectorGraphics().isDebugEnabled()) {
+ result.truncate(300);
+ qCDebug(lcQuickVectorGraphics).noquote() << result;
+ }
+}
+
+void QQuickQmlGenerator::setShapeTypeName(const QString &name)
+{
+ m_shapeTypeName = name.toLatin1();
+}
+
+QString QQuickQmlGenerator::shapeTypeName() const
+{
+ return QString::fromLatin1(m_shapeTypeName);
+}
+
+void QQuickQmlGenerator::setCommentString(const QString commentString)
+{
+ m_commentString = commentString;
+}
+
+QString QQuickQmlGenerator::commentString() const
+{
+ return m_commentString;
+}
+
+void QQuickQmlGenerator::generateNodeBase(NodeInfo &info)
+{
+ m_indentLevel++;
+ if (!info.nodeId.isEmpty())
+ stream() << "objectName: \"" << info.nodeId << "\"";
+ if (!info.isDefaultTransform) {
+ auto sx = info.transform.m11();
+ auto sy = info.transform.m22();
+ auto x = info.transform.m31();
+ auto y = info.transform.m32();
+ if (info.transform.type() == QTransform::TxTranslate) {
+ stream() << "transform: Translate { " << "x: " << x << "; y: " << y << " }";
+ } else if (info.transform.type() == QTransform::TxScale && !x && !y) {
+ stream() << "transform: Scale { xScale: " << sx << "; yScale: " << sy << " }";
+ } else {
+ const QMatrix4x4 m(info.transform);
+ auto xform = new QQuickMatrix4x4;
+ xform->setMatrix(m);
+ {
+ stream() << "transform: [ Matrix4x4 { matrix: Qt.matrix4x4 (";
+ m_indentLevel += 3;
+ const auto *data = m.data();
+ for (int i = 0; i < 4; i++) {
+ stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12] << ", ";
+ }
+ stream() << ") } ]";
+ m_indentLevel -= 3;
+ }
+ }
+ }
+ if (!info.isDefaultOpacity) {
+ stream() << "opacity: " << info.opacity;
+ }
+ m_indentLevel--;
+}
+
+bool QQuickQmlGenerator::generateDefsNode(NodeInfo &info)
+{
+ stream() << "// skipping DEFS \"" << info.nodeId << "\"";
+ return false;
+}
+
+void QQuickQmlGenerator::generateImageNode(ImageNodeInfo &info)
+{
+ QString fn = info.image.hasAlphaChannel() ? QStringLiteral("svg_asset_%1.png").arg(info.image.cacheKey())
+ : QStringLiteral("svg_asset_%1.jpg").arg(info.image.cacheKey());
+ // For now we just create a copy of the image in the current directory
+ info.image.save(fn);
+ qCDebug(lcQuickVectorGraphics) << "Saving copy of IMAGE" << fn;
+
+ // TODO: this requires proper asset management.
+ stream() << "Image {";
+ m_indentLevel++;
+
+ generateNodeBase(info);
+ stream() << "x: " << info.rect.x();
+ stream() << "y: " << info.rect.y();
+ stream() << "width: " << info.rect.width();
+ stream() << "height: " << info.rect.height();
+ stream() << "source: \"" << fn <<"\"";
+
+ m_indentLevel--;
+
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generatePath(PathNodeInfo &info)
+{
+ if (m_inShapeItem) {
+ if (!info.isDefaultTransform)
+ qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
+ optimizePaths(info);
+ } else {
+ m_inShapeItem = true;
+ stream() << shapeName() << " {";
+
+ // Check ??
+ generateNodeBase(info);
+
+ m_indentLevel++;
+ if (m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::CurveRenderer))
+ stream() << "preferredRendererType: Shape.CurveRenderer";
+ optimizePaths(info);
+ //qCDebug(lcQuickVectorGraphics) << *node->qpath();
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+}
+
+void QQuickQmlGenerator::generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect)
+{
+ auto setStops = [](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
+ auto stopsProp = quickGrad->stops();
+ for (auto &stop : stops) {
+ auto *stopObj = new QQuickGradientStop(quickGrad);
+ stopObj->setPosition(stop.first);
+ stopObj->setColor(stop.second);
+ stopsProp.append(&stopsProp, stopObj);
+ }
+ };
+
+ if (grad->type() == QGradient::LinearGradient) {
+ auto *linGrad = static_cast<const QLinearGradient *>(grad);
+ stream() << "fillGradient: LinearGradient {";
+ m_indentLevel++;
+
+ QRectF gradRect(linGrad->start(), linGrad->finalStop());
+ QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : QQuickVectorGraphics::Utils::mapToQtLogicalMode(gradRect, boundingRect);
+
+ stream() << "x1: " << logRect.left();
+ stream() << "y1: " << logRect.top();
+ stream() << "x2: " << logRect.right();
+ stream() << "y2: " << logRect.bottom();
+ for (auto &stop : linGrad->stops()) {
+ stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
+ }
+ m_indentLevel--;
+ stream() << "}";
+
+ if (shapePath) {
+ auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
+
+ quickGrad->setX1(logRect.left());
+ quickGrad->setY1(logRect.top());
+ quickGrad->setX2(logRect.right());
+ quickGrad->setY2(logRect.bottom());
+ setStops(quickGrad, linGrad->stops());
+
+ shapePath->setFillGradient(quickGrad);
+ }
+ } else if (grad->type() == QGradient::RadialGradient) {
+ auto *radGrad = static_cast<const QRadialGradient*>(grad);
+ stream() << "fillGradient: RadialGradient {";
+ m_indentLevel++;
+
+ stream() << "centerX: " << radGrad->center().x();
+ stream() << "centerY: " << radGrad->center().y();
+ stream() << "centerRadius: " << radGrad->radius();
+ stream() << "focalX: centerX; focalY: centerY";
+ for (auto &stop : radGrad->stops()) {
+ stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
+ }
+ m_indentLevel--;
+ stream() << "}";
+
+ if (shapePath) {
+ auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
+ quickGrad->setCenterX(radGrad->center().x());
+ quickGrad->setCenterY(radGrad->center().y());
+ quickGrad->setCenterRadius(radGrad->radius());
+ quickGrad->setFocalX(radGrad->center().x());
+ quickGrad->setFocalY(radGrad->center().y());
+ setStops(quickGrad, radGrad->stops());
+
+ shapePath->setFillGradient(quickGrad);
+ }
+ }
+}
+
+void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorGraphics::PathSelector pathSelector, const QRectF &boundingRect)
+{
+ Q_UNUSED(pathSelector)
+ Q_ASSERT(painterPath || quadPath);
+
+ QString penName = info.strokeColor;
+ const bool noPen = penName.isEmpty() || penName == u"transparent";
+ if (pathSelector == QQuickVectorGraphics::StrokePath && noPen)
+ return;
+
+ const bool noFill = !info.grad && info.fillColor == u"transparent";
+ if (pathSelector == QQuickVectorGraphics::FillPath && noFill)
+ return;
+
+ auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
+ stream() << "ShapePath {";
+ m_indentLevel++;
+ if (!info.nodeId.isEmpty()) {
+ switch (pathSelector) {
+ case QQuickVectorGraphics::FillPath:
+ stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorGraphics::StrokePath:
+ stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorGraphics::FillAndStroke:
+ stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
+ break;
+ }
+ }
+
+ if (noPen || !(pathSelector & QQuickVectorGraphics::StrokePath)) {
+ stream() << "strokeColor: \"transparent\"";
+ } else {
+ stream() << "strokeColor: \"" << penName << "\"";
+ stream() << "strokeWidth: " << info.strokeWidth;
+ }
+ if (info.capStyle == Qt::FlatCap)
+ stream() << "capStyle: ShapePath.FlatCap"; //### TODO Add the rest of the styles, as well as join styles etc.
+
+ if (!(pathSelector & QQuickVectorGraphics::FillPath)) {
+ stream() << "fillColor: \"transparent\"";
+ } else if (auto *grad = info.grad) {
+ generateGradient(grad, nullptr, boundingRect);
+ } else {
+ stream() << "fillColor: \"" << info.fillColor << "\"";
+
+ }
+ if (fillRule == QQuickShapePath::WindingFill)
+ stream() << "fillRule: ShapePath.WindingFill";
+ else
+ stream() << "fillRule: ShapePath.OddEvenFill";
+
+ QString hintStr;
+ if (quadPath)
+ hintStr = QQuickVectorGraphics::Utils::pathHintString(*quadPath);
+ if (!hintStr.isEmpty())
+ stream() << hintStr;
+
+
+ QString svgPathString = painterPath ? QQuickVectorGraphics::Utils::toSvgString(*painterPath) : QQuickVectorGraphics::Utils::toSvgString(*quadPath);
+ stream() << "PathSvg { path: \"" << svgPathString << "\" }";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateNode(NodeInfo &info)
+{
+ stream() << "// Missing Implementation for SVG Node: " << info.typeName;
+ stream() << "// Adding an empty Item and skipping";
+ stream() << "Item {";
+ generateNodeBase(info);
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateTextNode(TextNodeInfo &info)
+{
+ static int counter = 0;
+
+ if (!info.isTextArea) {
+ stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
+ }
+
+ stream() << "Text {";
+
+ m_indentLevel++;
+
+ if (!info.isTextArea) {
+ stream() << "x: " << info.position.x();
+ stream() << "y: " << info.position.y();
+ stream() << "width: " << info.size.width();
+ stream() << "height: " << info.size.height();
+ stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
+ stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
+ } else {
+ QString hAlign = QStringLiteral("left");
+ stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
+ switch (info.alignment) {
+ case Qt::AlignHCenter:
+ hAlign = QStringLiteral("horizontalCenter");
+ break;
+ case Qt::AlignRight:
+ hAlign = QStringLiteral("right");
+ break;
+ default:
+ qCDebug(lcQuickVectorGraphics) << "Unexpected text alignment" << info.alignment;
+ Q_FALLTHROUGH();
+ case Qt::AlignLeft:
+ break;
+ }
+ stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
+ }
+ counter++;
+
+ stream() << "color: \"" << info.color << "\"";
+ stream() << "textFormat: Text.StyledText";
+
+ stream() << "text: \"" << info.text << "\"";
+ stream() << "font.family: \"" << info.font.family() << "\"";
+ if (info.font.pixelSize() > 0)
+ stream() << "font.pixelSize:" << info.font.pixelSize();
+ else if (info.font.pointSize() > 0)
+ stream() << "font.pixelSize:" << info.font.pointSizeF();
+ if (info.font.underline())
+ stream() << "font.underline: true";
+ if (info.font.weight() != QFont::Normal)
+ stream() << "font.weight: " << int(info.font.weight());
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateStructureNode(StructureNodeInfo &info)
+{
+ if (info.stage == StructureNodeInfo::StructureNodeStage::Start) {
+ if (!info.forceSeparatePaths && info.isPathContainer) {
+ stream() << shapeName() <<" {";
+ m_indentLevel++;
+ if (m_flags.testFlag(QQuickVectorGraphics::GeneratorFlag::CurveRenderer))
+ stream() << "preferredRendererType: Shape.CurveRenderer";
+ m_indentLevel--;
+
+ m_inShapeItem = true;
+ } else {
+ stream() << "Item {";
+ }
+
+ if (!info.viewBox.isEmpty()) {
+ m_indentLevel++;
+ stream() << "transform: [";
+ m_indentLevel++;
+ bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
+ if (translate)
+ stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
+ stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
+ m_indentLevel--;
+ stream() << "]";;
+ m_indentLevel--;
+ }
+
+ generateNodeBase(info);
+ m_indentLevel++;
+ } else {
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+}
+
+void QQuickQmlGenerator::generateRootNode(StructureNodeInfo &info)
+{
+ if (info.stage == StructureNodeInfo::StructureNodeStage::Start) {
+ m_indentLevel = 0;
+
+ const QStringList comments = m_commentString.split(u'\n');
+ if (comments.isEmpty())
+ stream() << "// Generated from SVG";
+ else
+ for (const auto &comment : comments)
+ stream() << "// " << comment;
+
+ stream() << "import QtQuick";
+ stream() << "import QtQuick.Shapes";
+
+ stream() << "Item {";
+ m_indentLevel++;
+
+ double w = info.size.width();
+ double h = info.size.height();
+ if (w > 0)
+ stream() << "implicitWidth: " << w;
+ if (h > 0)
+ stream() << "implicitHeight: " << h;
+
+ if (!info.viewBox.isEmpty()) {
+ m_indentLevel++;
+ stream() << "transform: [";
+ m_indentLevel++;
+ bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
+ if (translate)
+ stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
+ stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
+ m_indentLevel--;
+ stream() << "]";;
+ m_indentLevel--;
+ }
+
+ generateNodeBase(info);
+ m_indentLevel++;
+ } else {
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+}
+
+QString QQuickQmlGenerator::indent()
+{
+ return QString().fill(QLatin1Char(' '), m_indentLevel * 4);
+}
+
+GeneratorStream QQuickQmlGenerator::stream()
+{
+ GeneratorStream strm(m_stream);
+ strm << indent();
+ return strm;
+}
+
+const char *QQuickQmlGenerator::shapeName() const
+{
+ return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData();
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorgraphics/generator/qquickqmlgenerator_p.h b/src/quickvectorgraphics/generator/qquickqmlgenerator_p.h
new file mode 100644
index 0000000000..aa69cd9123
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qquickqmlgenerator_p.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QQUICKQMLGENERATOR_P_H
+#define QQUICKQMLGENERATOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qquickgenerator_p.h"
+
+#include <QtCore/qtextstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class GeneratorStream;
+
+class Q_QUICKVECTORGRAPHICSGENERATOR_EXPORT QQuickQmlGenerator : public QQuickGenerator
+{
+public:
+ QQuickQmlGenerator(const QString fileName, QQuickVectorGraphics::GeneratorFlags flags, const QString &outFileName);
+ ~QQuickQmlGenerator();
+
+ void setShapeTypeName(const QString &name);
+ QString shapeTypeName() const;
+
+ void setCommentString(const QString commentString);
+ QString commentString() const;
+
+protected:
+ void generateNodeBase(NodeInfo &info) override;
+ bool generateDefsNode(NodeInfo &info) override;
+ void generateImageNode(ImageNodeInfo &info) override;
+ void generatePath(PathNodeInfo &info) override;
+ void generateNode(NodeInfo &info) override;
+ void generateTextNode(TextNodeInfo &info) override;
+ void generateStructureNode(StructureNodeInfo &info) override;
+ void generateRootNode(StructureNodeInfo &info) override;
+ void generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect) override;
+ void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorGraphics::PathSelector pathSelector, const QRectF &boundingRect) override;
+
+private:
+ QString indent();
+ GeneratorStream stream();
+ const char *shapeName() const;
+
+private:
+ int m_indentLevel = 0;
+ QTextStream *m_stream;
+ QByteArray result;
+ QString outputFileName;
+ bool m_inShapeItem = false;
+ QByteArray m_shapeTypeName;
+ QString m_commentString;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKQMLGENERATOR_P_H
diff --git a/src/quickvectorgraphics/generator/qsvgvisitorimpl.cpp b/src/quickvectorgraphics/generator/qsvgvisitorimpl.cpp
new file mode 100644
index 0000000000..64d3565da0
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qsvgvisitorimpl.cpp
@@ -0,0 +1,455 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qsvgvisitorimpl_p.h"
+#include "qquickgenerator_p.h"
+#include "qquicknodeinfo_p.h"
+
+#include <private/qsvgvisitor_p.h>
+
+#include <QString>
+#include <QPainter>
+#include <QMatrix4x4>
+#include <QQuickItem>
+
+#include <private/qquickshape_p.h>
+#include <private/qquicktext_p.h>
+#include <private/qquicktranslate_p.h>
+#include <private/qquickitem_p.h>
+
+#include <private/qquickimagebase_p_p.h>
+#include <private/qquickimage_p.h>
+#include <private/qsgcurveprocessor_p.h>
+
+#include <private/qquadpath_p.h>
+
+#include "utils_p.h"
+#include<QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorGraphics)
+
+static inline bool isPathContainer(const QSvgStructureNode *node)
+{
+ bool foundPath = false;
+ for (const auto *child : node->renderers()) {
+ switch (child->type()) {
+ // nodes that shouldn't go inside Shape{}
+ case QSvgNode::Switch:
+ case QSvgNode::Doc:
+ case QSvgNode::Group:
+ case QSvgNode::Animation:
+ case QSvgNode::Use:
+ case QSvgNode::Video:
+ //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
+ return false;
+
+ // nodes that could go inside Shape{}
+ case QSvgNode::Defs:
+ case QSvgNode::Image:
+ case QSvgNode::Textarea:
+ case QSvgNode::Text:
+ case QSvgNode::Tspan:
+ break;
+
+ // nodes that are done as pure ShapePath{}
+ case QSvgNode::Rect:
+ case QSvgNode::Circle:
+ case QSvgNode::Ellipse:
+ case QSvgNode::Line:
+ case QSvgNode::Path:
+ case QSvgNode::Polygon:
+ case QSvgNode::Polyline:
+ if (!child->style().transform.isDefault()) {
+ //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
+ return false;
+ }
+ foundPath = true;
+ break;
+ default:
+ qCDebug(lcQuickVectorGraphics) << "Unhandled type in switch" << child->type();
+ break;
+ }
+ }
+ //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
+ return foundPath;
+}
+
+class QSvgStyleResolver
+{
+public:
+ QSvgStyleResolver()
+ {
+ m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
+ m_dummyPainter.begin(&m_dummyImage);
+ QPen noPen(Qt::NoPen);
+ noPen.setBrush(Qt::NoBrush);
+ m_dummyPainter.setPen(noPen);
+ m_dummyPainter.setBrush(Qt::black);
+ }
+
+ ~QSvgStyleResolver()
+ {
+ m_dummyPainter.end();
+ }
+
+ QPainter& painter() { return m_dummyPainter; }
+ QSvgExtraStates& states() { return m_svgState; }
+
+ QString currentFillColor() const
+ {
+ if (m_dummyPainter.brush().style() != Qt::NoBrush) {
+ QColor c(m_dummyPainter.brush().color());
+ c.setAlphaF(m_svgState.fillOpacity);
+ //qCDebug(lcQuickVectorGraphics) << "FILL" << c << m_svgState.fillOpacity << c.name();
+ return c.name(QColor::HexArgb);
+ } else {
+ return QStringLiteral("transparent");
+ }
+ }
+
+ const QGradient *currentFillGradient() const
+ {
+ if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
+ return m_dummyPainter.brush().gradient();
+ return nullptr;
+ }
+
+ QString currentStrokeColor() const
+ {
+ if (m_dummyPainter.pen().style() != Qt::NoPen)
+ return m_dummyPainter.pen().color().name();
+ else if (m_dummyPainter.pen().brush().style() == Qt::SolidPattern)
+ return m_dummyPainter.pen().brush().color().name();
+ return {};
+ }
+
+ float currentStrokeWidth() const
+ {
+ float penWidth = m_dummyPainter.pen().widthF();
+ return penWidth ? penWidth : 1;
+ }
+
+protected:
+ QPainter m_dummyPainter;
+ QImage m_dummyImage;
+ QSvgExtraStates m_svgState;
+
+};
+
+Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
+
+QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator)
+ : m_svgFileName(svgFileName)
+ , m_generator(generator)
+{
+
+}
+
+void QSvgVisitorImpl::traverse()
+{
+ if (!m_generator) {
+ qCDebug(lcQuickVectorGraphics) << "No valid QQuickGenerator is set. Genration will stop";
+ return;
+ }
+
+ auto *doc = QSvgTinyDocument::load(m_svgFileName);
+ if (!doc) {
+ qCDebug(lcQuickVectorGraphics) << "Not a valid Svg File : " << m_svgFileName;
+ return;
+ }
+
+ QSvgVisitor::traverse(doc);
+}
+
+void QSvgVisitorImpl::visitNode(const QSvgNode *node)
+{
+ handleBaseNodeSetup(node);
+
+ ImageNodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ m_generator->generateNode(info);
+
+ handleBaseNodeEnd(node);
+}
+
+void QSvgVisitorImpl::visitImageNode(const QSvgImage *node)
+{
+ // TODO: this requires proper asset management.
+ handleBaseNodeSetup(node);
+
+ ImageNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ info.image = node->image();
+ info.rect = node->rect();
+
+ m_generator->generateImageNode(info);
+
+ handleBaseNodeEnd(node);
+}
+
+void QSvgVisitorImpl::visitRectNode(const QSvgRect *node)
+{
+ QRectF rect = node->rect();
+ QPointF rads = node->radius();
+ // This is using Qt::RelativeSize semantics: percentage of half rect size
+ qreal x1 = rect.left();
+ qreal x2 = rect.right();
+ qreal y1 = rect.top();
+ qreal y2 = rect.bottom();
+
+ qreal rx = rads.x() * rect.width() / 200;
+ qreal ry = rads.y() * rect.height() / 200;
+ QPainterPath p;
+
+ p.moveTo(x1 + rx, y1);
+ p.lineTo(x2 - rx, y1);
+ // qCDebug(lcQuickVectorGraphics) << "Line1" << x2 - rx << y1;
+ p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90); // ARC to x2, y1 + ry
+ // qCDebug(lcQuickVectorGraphics) << "p1" << p;
+
+ p.lineTo(x2, y2 - ry);
+ p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90); // ARC to x2 - rx, y2
+
+ p.lineTo(x1 + rx, y2);
+ p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90); // ARC to x1, y2 - ry
+
+ p.lineTo(x1, y1 + ry);
+ p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90); // ARC to x1 + rx, y1
+
+
+ handlePathNode(node, p);
+
+ return;
+}
+
+void QSvgVisitorImpl::visitEllipseNode(const QSvgEllipse *node)
+{
+ QRectF rect = node->rect();
+
+ QPainterPath p;
+ p.addEllipse(rect);
+
+ handlePathNode(node, p);
+}
+
+void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
+{
+ handlePathNode(node, node->path());
+}
+
+void QSvgVisitorImpl::visitLineNode(const QSvgLine *node)
+{
+ // TODO: proper end caps (should be flat by default?)
+ QPainterPath p;
+ p.moveTo(node->line().p1());
+ p.lineTo(node->line().p2());
+ handlePathNode(node, p, Qt::FlatCap);
+}
+
+void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
+{
+ QPainterPath p = QQuickVectorGraphics::Utils::polygonToPath(node->polygon(), true);
+ handlePathNode(node, p);
+}
+
+void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
+{
+ QPainterPath p = QQuickVectorGraphics::Utils::polygonToPath(node->polygon(), false);
+ handlePathNode(node, p, Qt::FlatCap);
+}
+
+
+void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
+{
+ // TODO: font/size
+ // TODO: fallback to path for gradient fill
+
+ handleBaseNodeSetup(node);
+ const bool isTextArea = node->type() == QSvgNode::Textarea;
+
+ QString text;
+ for (const auto *tspan : node->tspans()) {
+ if (!tspan) {
+ text += QStringLiteral("<br>");
+ continue;
+ }
+
+ if (!tspan->style().font.isDefault()) // TODO: switch to rich text when we have more complex spans with fonts?
+ qCDebug(lcQuickVectorGraphics) << "Not implemented Tspan with font:" << tspan->style().font->qfont();
+ QString spanColor;
+ if (!tspan->style().fill.isDefault()) {
+ auto &b = tspan->style().fill->qbrush();
+ qCDebug(lcQuickVectorGraphics) << "tspan FILL:" << b;
+ if (b.style() != Qt::NoBrush)
+ spanColor = b.color().name();
+ }
+ bool fontTag = !spanColor.isEmpty();
+ if (fontTag)
+ text += QStringLiteral("<font color=\"%1\">").arg(spanColor); // TODO: size="1-7" ???
+ text += tspan->text().toHtmlEscaped();
+ if (fontTag)
+ text += QStringLiteral("</font>");
+ }
+
+ QFont font = styleResolver->painter().font();
+
+ if (font.pixelSize() <= 0 && font.pointSize() > 0)
+ font.setPixelSize(font.pointSize()); // ### TODO: this makes no sense ###
+
+ TextNodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ info.position = node->position();
+ info.size = node->size();
+ info.font = font;
+ info.text = text;
+ info.isTextArea = isTextArea;
+ info.color = styleResolver->currentFillColor();
+ info.alignment = styleResolver->states().textAnchor;
+
+ m_generator->generateTextNode(info);
+
+ handleBaseNodeEnd(node);
+}
+
+bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node)
+{
+ Q_UNUSED(node);
+ NodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ m_generator->generateDefsNode(info);
+
+ // TODO CHECK WHAT IS THIS
+ return false;
+}
+
+bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node)
+{
+ constexpr bool forceSeparatePaths = false;
+ handleBaseNodeSetup(node);
+
+ StructureNodeInfo info;
+
+ fillCommonNodeInfo(node, info);
+ info.forceSeparatePaths = forceSeparatePaths;
+ info.isPathContainer = isPathContainer(node);
+ info.stage = StructureNodeInfo::StructureNodeStage::Start;
+
+ m_generator->generateStructureNode(info);
+
+ return true;
+}
+
+void QSvgVisitorImpl::visitStructureNodeEnd(const QSvgStructureNode *node)
+{
+ handleBaseNodeEnd(node);
+ // qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen) << m_styleResolver->painter().pen().color().name()
+ // << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush) << m_styleResolver->painter().pen().brush().color().name();
+
+ StructureNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ info.stage = StructureNodeInfo::StructureNodeStage::End;
+
+ m_generator->generateStructureNode(info);
+}
+
+bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgTinyDocument *node)
+{
+ handleBaseNodeSetup(node);
+
+ StructureNodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ const QSvgTinyDocument *doc = static_cast<const QSvgTinyDocument *>(node);
+ info.size = doc->size();
+ info.viewBox = doc->viewBox();
+ info.isPathContainer = isPathContainer(node);
+ info.stage = StructureNodeInfo::StructureNodeStage::Start;
+
+ m_generator->generateRootNode(info);
+
+ return true;
+}
+
+void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgTinyDocument *node)
+{
+ handleBaseNodeEnd(node);
+ qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen)
+ << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush)
+ << styleResolver->painter().pen().brush().color().name();
+
+ StructureNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ info.stage = StructureNodeInfo::StructureNodeStage::End;
+
+ m_generator->generateRootNode(info);
+}
+
+void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
+{
+ info.nodeId = node->nodeId();
+ info.typeName = node->typeName();
+ info.isDefaultTransform = node->style().transform.isDefault();
+ info.transform = !info.isDefaultTransform ? node->style().transform->qtransform() : QTransform();
+ info.isDefaultOpacity = node->style().opacity.isDefault();
+ info.opacity = !info.isDefaultOpacity ? node->style().opacity->opacity() : 1.0;
+}
+
+void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
+{
+ qCDebug(lcQuickVectorGraphics) << "Before SETUP" << node << "fill" << styleResolver->currentFillColor()
+ << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
+ << node->nodeId() << " type: " << node->typeName() << " " << node->type();
+
+ node->applyStyle(&styleResolver->painter(), styleResolver->states());
+
+ qCDebug(lcQuickVectorGraphics) << "After SETUP" << node << "fill" << styleResolver->currentFillColor()
+ << "stroke" << styleResolver->currentStrokeColor()
+ << styleResolver->currentStrokeWidth() << node->nodeId();
+}
+
+void QSvgVisitorImpl::handleBaseNode(const QSvgNode *node)
+{
+ NodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ m_generator->generateNodeBase(info);
+}
+
+void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
+{
+ node->revertStyle(&styleResolver->painter(), styleResolver->states());
+
+ qCDebug(lcQuickVectorGraphics) << "After END" << node << "fill" << styleResolver->currentFillColor()
+ << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
+ << node->nodeId();
+
+}
+
+void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
+{
+ handleBaseNodeSetup(node);
+
+ PathNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ auto fillStyle = node->style().fill;
+ if (fillStyle)
+ info.fillRule = fillStyle->fillRule();
+
+ info.painterPath = path;
+ info.capStyle = capStyle;
+ info.fillColor = styleResolver->currentFillColor();
+ info.strokeColor = styleResolver->currentStrokeColor();
+ info.strokeWidth = styleResolver->currentStrokeWidth();
+ info.grad = styleResolver->currentFillGradient();
+
+ m_generator->generatePath(info);
+
+ handleBaseNodeEnd(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorgraphics/generator/qsvgvisitorimpl_p.h b/src/quickvectorgraphics/generator/qsvgvisitorimpl_p.h
new file mode 100644
index 0000000000..a025cead1d
--- /dev/null
+++ b/src/quickvectorgraphics/generator/qsvgvisitorimpl_p.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSVGVISITORIMPL_P_H
+#define QSVGVISITORIMPL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSvg/private/qsvgvisitor_p.h>
+#include "qquickgenerator_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QTextStream;
+class QSvgTinyDocument;
+class QString;
+class QQuickItem;
+
+class QSvgVisitorImpl : public QSvgVisitor
+{
+public:
+ QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator);
+ void traverse();
+
+protected:
+ void visitNode(const QSvgNode *node) override;
+ void visitImageNode(const QSvgImage *node) override;
+ void visitRectNode(const QSvgRect *node) override;
+ void visitEllipseNode(const QSvgEllipse *node) override;
+ void visitPathNode(const QSvgPath *node) override;
+ void visitLineNode(const QSvgLine *node) override;
+ void visitPolygonNode(const QSvgPolygon *node) override;
+ void visitPolylineNode(const QSvgPolyline *node) override;
+ void visitTextNode(const QSvgText *node) override;
+ bool visitDefsNodeStart(const QSvgDefs *node) override;
+ bool visitStructureNodeStart(const QSvgStructureNode *node) override;
+ void visitStructureNodeEnd(const QSvgStructureNode *node) override;
+
+ bool visitDocumentNodeStart(const QSvgTinyDocument *node) override;
+ void visitDocumentNodeEnd(const QSvgTinyDocument *node) override;
+
+private:
+ void fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info);
+ void handleBaseNodeSetup(const QSvgNode *node);
+ void handleBaseNode(const QSvgNode *node);
+ void handleBaseNodeEnd(const QSvgNode *node);
+ void handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle = Qt::SquareCap);
+ void outputShapePath(QPainterPath pathCopy, const PathNodeInfo &info);
+
+private:
+ QString m_svgFileName;
+ QQuickGenerator *m_generator;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSVGVISITORIMPL_P_H
diff --git a/src/quickvectorgraphics/generator/utils_p.h b/src/quickvectorgraphics/generator/utils_p.h
new file mode 100644
index 0000000000..60f8e6f252
--- /dev/null
+++ b/src/quickvectorgraphics/generator/utils_p.h
@@ -0,0 +1,200 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef UTILS_P_H
+#define UTILS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qquicktranslate_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qsvgnode_p.h>
+
+#include <private/qquadpath_p.h>
+#include <private/qsvgvisitor_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQuickVectorGraphics::Utils
+{
+
+class ViewBoxItem : public QQuickItem
+{
+public:
+ ViewBoxItem(const QRectF viewBox, QQuickItem *parent = nullptr) : QQuickItem(parent), m_viewBox(viewBox) { setXForm(); }
+
+protected:
+ void geometryChange(const QRectF &/*newGeometry*/, const QRectF &/*oldGeometry*/) override
+ {
+ setXForm();
+ }
+
+private:
+ void setXForm()
+ {
+ auto xformProp = transform();
+ xformProp.clear(&xformProp);
+ bool translate = !qFuzzyIsNull(m_viewBox.x()) || !qFuzzyIsNull(m_viewBox.y());
+ if (translate) {
+ auto *tr = new QQuickTranslate(this);
+ tr->setX(-m_viewBox.x());
+ tr->setY(-m_viewBox.y());
+ xformProp.append(&xformProp, tr);
+ }
+ if (!m_viewBox.isEmpty() && width() && height()) {
+ auto *scale = new QQuickScale(this);
+ qreal sx = width() / m_viewBox.width();
+ qreal sy = height() / m_viewBox.height();
+
+ scale->setXScale(sx);
+ scale->setYScale(sy);
+ xformProp.append(&xformProp, scale);
+ }
+ }
+ QRectF m_viewBox;
+};
+
+inline QPainterPath polygonToPath(const QPolygonF &poly, bool closed)
+{
+ QPainterPath path;
+ if (poly.isEmpty())
+ return path;
+ bool first = true;
+ for (const auto &p : poly) {
+ if (first)
+ path.moveTo(p);
+ else
+ path.lineTo(p);
+ first = false;
+ }
+ if (closed)
+ path.closeSubpath();
+ return path;
+}
+
+inline QString pathHintString(const QQuadPath &qp)
+{
+ QString res;
+ QTextStream str(&res);
+ auto flags = qp.pathHints();
+ if (!flags)
+ return res;
+ str << "pathHints:";
+ bool first = true;
+
+#define CHECK_PATH_HINT(flagName) \
+ if (flags.testFlag(QQuadPath::flagName)) { \
+ if (!first) \
+ str << " |"; \
+ first = false; \
+ str << " ShapePath." #flagName; \
+ }
+
+ CHECK_PATH_HINT(PathLinear)
+ CHECK_PATH_HINT(PathQuadratic)
+ CHECK_PATH_HINT(PathConvex)
+ CHECK_PATH_HINT(PathFillOnRight)
+ CHECK_PATH_HINT(PathSolid)
+ CHECK_PATH_HINT(PathNonIntersecting)
+ CHECK_PATH_HINT(PathNonOverlappingControlPointTriangles)
+
+ return res;
+}
+
+// Find the square that gives the same gradient in QGradient::LogicalMode as
+// objModeRect does in QGradient::ObjectMode
+
+// When the object's bounding box is not square, the stripes that are conceptually
+// perpendicular to the gradient vector within object bounding box space shall render
+// non-perpendicular relative to the gradient vector in user space due to application
+// of the non-uniform scaling transformation from bounding box space to user space.
+inline QRectF mapToQtLogicalMode(const QRectF &objModeRect, const QRectF &boundingRect)
+{
+
+ QRect pixelRect(objModeRect.x() * boundingRect.width() + boundingRect.left(),
+ objModeRect.y() * boundingRect.height() + boundingRect.top(),
+ objModeRect.width() * boundingRect.width(),
+ objModeRect.height() * boundingRect.height());
+
+ if (pixelRect.isEmpty()) // pure horizontal/vertical gradient
+ return pixelRect;
+
+ double w = boundingRect.width();
+ double h = boundingRect.height();
+ double objModeSlope = objModeRect.height() / objModeRect.width();
+ double a = objModeSlope * w / h;
+
+ // do calculation with origin == pixelRect.topLeft
+ double x2 = pixelRect.width();
+ double y2 = pixelRect.height();
+ double x = (x2 + a * y2) / (1 + a * a);
+ double y = y2 - (x - x2)/a;
+
+ return QRectF(pixelRect.topLeft(), QSizeF(x,y));
+}
+
+inline QString toSvgString(const QPainterPath &path)
+{
+ QString svgPathString;
+ QTextStream strm(&svgPathString);
+
+ for (int i = 0; i < path.elementCount(); ++i) {
+ QPainterPath::Element element = path.elementAt(i);
+ if (element.isMoveTo()) {
+ strm << "M " << element.x << " " << element.y << " ";
+ } else if (element.isLineTo()) {
+ strm << "L " << element.x << " " << element.y << " ";
+ } else if (element.isCurveTo()) {
+ QPointF c1(element.x, element.y);
+ ++i;
+ element = path.elementAt(i);
+
+ QPointF c2(element.x, element.y);
+ ++i;
+ element = path.elementAt(i);
+ QPointF ep(element.x, element.y);
+
+ strm << "C "
+ << c1.x() << " "
+ << c1.y() << " "
+ << c2.x() << " "
+ << c2.y() << " "
+ << ep.x() << " "
+ << ep.y() << " ";
+ }
+ }
+
+ return svgPathString;
+}
+
+inline QString toSvgString(const QQuadPath &path)
+{
+ QString svgPathString;
+ QTextStream strm(&svgPathString);
+ path.iterateElements([&](const QQuadPath::Element &e){
+ if (e.isSubpathStart())
+ strm << "M " << e.startPoint().x() << " " << e.startPoint().y() << " ";
+ if (e.isLine())
+ strm << "L " << e.endPoint().x() << " " << e.endPoint().y() << " ";
+ else
+ strm << "Q " << e.controlPoint().x() << " " << e.controlPoint().y() << " "
+ << e.endPoint().x() << " " << e.endPoint().y() << " ";
+ });
+
+ return svgPathString;
+}
+
+}
+
+QT_END_NAMESPACE
+
+#endif // UTILS_P_H
diff --git a/src/quickvectorgraphics/qquickvectorgraphicsglobal_p.h b/src/quickvectorgraphics/qquickvectorgraphicsglobal_p.h
new file mode 100644
index 0000000000..a9fdc6e026
--- /dev/null
+++ b/src/quickvectorgraphics/qquickvectorgraphicsglobal_p.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QTQUICKVECTORGRAPHICSGLOBAL_P_H
+#define QTQUICKVECTORGRAPHICSGLOBAL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtQuickVectorGraphicsGenerator/qtquickvectorgraphicsgeneratorexports.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQuickVectorGraphics
+{
+ enum PathSelector {
+ FillPath = 0x1,
+ StrokePath = 0x2,
+ FillAndStroke = 0x3
+ };
+
+ enum GeneratorFlag {
+ OptimizePaths = 0x01,
+ CurveRenderer = 0x02,
+ OutlineStrokeMode = 0x04
+ };
+
+ Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag);
+ Q_DECLARE_OPERATORS_FOR_FLAGS(GeneratorFlags);
+}
+
+QT_END_NAMESPACE
+
+#endif //QTQUICKVECTORGRAPHICSGLOBAL_P_H
diff --git a/tools/svgtoqml/CMakeLists.txt b/tools/svgtoqml/CMakeLists.txt
index 008a1641f5..9511212627 100644
--- a/tools/svgtoqml/CMakeLists.txt
+++ b/tools/svgtoqml/CMakeLists.txt
@@ -11,16 +11,12 @@ qt_internal_add_tool(${target_name}
TOOLS_TARGET Quick
SOURCES
main.cpp
- qsvgloader.cpp qsvgloader_p.h
LIBRARIES
Qt::Core
Qt::Gui
Qt::Qml
Qt::Quick
- Qt::QuickPrivate
- Qt::Svg
- Qt::SvgPrivate
- Qt::QuickShapesPrivate
+ Qt::QuickVectorGraphicsGeneratorPrivate
)
qt_internal_return_unless_building_tools()
diff --git a/tools/svgtoqml/main.cpp b/tools/svgtoqml/main.cpp
index 816f4808c0..91d55b73eb 100644
--- a/tools/svgtoqml/main.cpp
+++ b/tools/svgtoqml/main.cpp
@@ -5,18 +5,17 @@
#include <QQmlApplicationEngine>
#include <QCommandLineParser>
#include <QFile>
-#include <private/qquickrectangle_p.h>
-#include <private/qsvgtinydocument_p.h>
#include <QQuickWindow>
-
-#include "qsvgloader_p.h"
+#include <QQuickItem>
+#include <QtQuickVectorGraphicsGenerator/private/qquickitemgenerator_p.h>
+#include <QtQuickVectorGraphicsGenerator/private/qquickqmlgenerator_p.h>
+#include <QtQuickVectorGraphicsGenerator/private/qquickvectorgraphicsglobal_p.h>
#define ENABLE_GUI
int main(int argc, char *argv[])
{
#ifdef ENABLE_GUI
- qputenv("QT_QUICKSHAPES_BACKEND", "curverenderer");
QGuiApplication app(argc, argv);
#else
QCoreApplication app(argc, argv);
@@ -41,6 +40,16 @@ int main(int argc, char *argv[])
QCoreApplication::translate("main", "typename"));
parser.addOption(typeNameOption);
+ QCommandLineOption copyrightOption("copyright-statement",
+ QCoreApplication::translate("main", "Add <string> as a comment at the start of the generated file."),
+ QCoreApplication::translate("main", "string"));
+ parser.addOption(copyrightOption);
+
+ QCommandLineOption outlineModeOption("outline-stroke-mode",
+ QCoreApplication::translate("main", "Stroke the outside of the filled shape instead of "
+ "the original path. Also sets optimize-paths."));
+ parser.addOption(outlineModeOption);
+
#ifdef ENABLE_GUI
QCommandLineOption guiOption(QStringList() << "v" << "view",
QCoreApplication::translate("main", "Display the SVG in a window."));
@@ -54,22 +63,30 @@ int main(int argc, char *argv[])
const QString inFileName = args.at(0);
- auto *doc = QSvgTinyDocument::load(inFileName);
- if (!doc) {
- fprintf(stderr, "%s is not a valid SVG\n", qPrintable(inFileName));
- return 2;
- }
-
- const QString commentString = QLatin1String("Generated from SVG file %1").arg(inFileName);
+ QString commentString = QLatin1String("Generated from SVG file %1").arg(inFileName);
const auto outFileName = args.size() > 1 ? args.at(1) : QString{};
const auto typeName = parser.value(typeNameOption);
+ auto copyrightString = parser.value(copyrightOption);
- QSvgQmlWriter::GeneratorFlags flags;
+ if (!copyrightString.isEmpty()) {
+ copyrightString = copyrightString.replace("\\n", "\n");
+ commentString = copyrightString + u"\n" + commentString;
+ }
+
+ QQuickVectorGraphics::GeneratorFlags flags;
if (parser.isSet(curveRendererOption))
- flags |= QSvgQmlWriter::CurveRenderer;
+ flags |= QQuickVectorGraphics::GeneratorFlag::CurveRenderer;
if (parser.isSet(optimizeOption))
- flags |= QSvgQmlWriter::OptimizePaths;
+ flags |= QQuickVectorGraphics::GeneratorFlag::OptimizePaths;
+ if (parser.isSet(outlineModeOption))
+ flags |= (QQuickVectorGraphics::GeneratorFlag::OutlineStrokeMode
+ | QQuickVectorGraphics::GeneratorFlag::OptimizePaths);
+
+ QQuickQmlGenerator generator(inFileName, flags, outFileName);
+ generator.setShapeTypeName(typeName);
+ generator.setCommentString(commentString);
+ generator.generate();
#ifdef ENABLE_GUI
if (parser.isSet(guiOption)) {
@@ -82,16 +99,15 @@ int main(int argc, char *argv[])
QCoreApplication::exit(-1);
if (obj) {
auto *containerItem = obj->findChild<QQuickItem*>(QStringLiteral("svg_item"));
- auto *contents = QSvgQmlWriter::loadSVG(doc, outFileName, flags, typeName, containerItem, commentString);
- contents->setWidth(containerItem->implicitWidth()); // Workaround for runtime loader viewbox size logic. TODO: fix
- contents->setHeight(containerItem->implicitHeight());
+ QQuickItemGenerator generator(inFileName, flags, containerItem);
+ generator.generate();
}
});
engine.load(url);
return app.exec();
}
+#else
+ return 0;
#endif
- QSvgQmlWriter::loadSVG(doc, outFileName, flags, typeName, nullptr, commentString);
- return 0;
}
diff --git a/tools/svgtoqml/qsvgloader.cpp b/tools/svgtoqml/qsvgloader.cpp
deleted file mode 100644
index fc72fb7454..0000000000
--- a/tools/svgtoqml/qsvgloader.cpp
+++ /dev/null
@@ -1,1119 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include "qsvgloader_p.h"
-#include <private/qsvgvisitor_p.h>
-
-#include <QString>
-#include <QPainter>
-#include <QMatrix4x4>
-#include <QQuickItem>
-
-#include <private/qquickshape_p.h>
-#include <private/qquicktext_p.h>
-#include <private/qquicktranslate_p.h>
-#include <private/qquickitem_p.h>
-
-#include <private/qquickimagebase_p_p.h>
-#include <private/qquickimage_p.h>
-#include <private/qsgcurveprocessor_p.h>
-
-#include <private/qquadpath_p.h>
-
-QT_BEGIN_NAMESPACE
-
-class RaiiStream : public QTextStream
-{
-public:
- RaiiStream() = default;
- explicit RaiiStream(QTextStream *stream): QTextStream(&output, QIODevice::ReadWrite), m_stream(stream) {}
- ~RaiiStream() {
- flush();
- if (m_stream && !output.isEmpty())
- *m_stream << output << Qt::endl;
- }
-
- RaiiStream(RaiiStream &other) = delete;
- RaiiStream &operator=(const RaiiStream &other) = delete;
- RaiiStream(RaiiStream &&other) : m_stream(std::exchange(other.m_stream, nullptr)), output(std::move(other.output)) {}
- RaiiStream &operator=(RaiiStream &&other) {
- std::swap(m_stream, other.m_stream);
- std::swap(output, other.output);
- return *this;
- }
-
-private:
- QTextStream *m_stream = nullptr;
- QByteArray output;
-};
-
-class SvgLoaderVisitor : public QSvgVisitor
-{
-public:
- SvgLoaderVisitor();
- ~SvgLoaderVisitor();
- void setShapeTypeName(const QString &name) { m_shapeTypeName = name.toLatin1(); }
- void setFlags(QSvgQmlWriter::GeneratorFlags flags) { m_flags = flags; }
- QQuickItem *loadQML(QTextStream *stream, const QSvgTinyDocument *doc, QQuickItem *svgItem);
-
-protected:
- void visitNode(const QSvgNode *) override;
-
- bool visitStructureNodeStart(const QSvgStructureNode *node) override;
- void visitStructureNodeEnd(const QSvgStructureNode *node) override;
- bool visitDefsNodeStart(const QSvgDefs *node) override;
-
- void visitTextNode(const QSvgText *node) override;
- void visitPathNode(const QSvgPath *node) override;
- void visitRectNode(const QSvgRect *node) override;
- void visitEllipseNode(const QSvgEllipse *node) override;
-
- void visitLineNode(const QSvgLine *node) override;
- void visitPolygonNode(const QSvgPolygon *node) override;
- void visitPolylineNode(const QSvgPolyline *node) override;
-
- void visitImageNode(const QSvgImage *node) override;
-
-private:
- QString indent() { return QString().fill(' ', m_indentLevel * 4);}
- RaiiStream stream() {
- RaiiStream strm(m_stream);
- strm << indent();
- return strm;
- }
-
- const char *shapeName() const { return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData(); }
-
- QString currentFillColor() const;
- const QGradient *currentFillGradient() const;
- QString currentStrokeColor() const;
- float currentStrokeWidth() const;
-
- void handleBaseNodeSetup(const QSvgNode *node);
- void handleBaseNode(const QSvgNode *node);
- void handleBaseNodeEnd(const QSvgNode *node);
- void handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle = Qt::SquareCap);
- void outputShapePath(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle);
- void outputGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect);
-
- enum PathSelector { FillPath = 0x1, StrokePath = 0x2, FillAndStroke = 0x3 };
- void outputShapePath(const QSvgNode *node, const QPainterPath *path, const QQuadPath *quadPath, Qt::PenCapStyle capStyle,
- PathSelector pathSelector, const QRectF &boundingRect);
-
- QQuickItem *currentItem() { return m_items.top(); }
- void addCurrentItem(QQuickItem *item, const QSvgNode *node = nullptr) {
- item->setParentItem(currentItem());
- m_items.push(item);
- if (node) {
- if (!node->nodeId().isEmpty())
- item->setObjectName(node->nodeId());
- else
- item->setObjectName(node->typeName());
- }
- }
-
- int m_indentLevel = 0;
- QTextStream *m_stream = nullptr;
- QPainter m_dummyPainter;
- QImage m_dummyImage;
- QSvgExtraStates m_svgState;
- bool m_inShapeItem = false;
- QQuickShape *m_parentShapeItem = nullptr;
- QByteArray m_shapeTypeName;
-
- QStack<QQuickItem *> m_items;
-
- QQuickItem *m_loadedItem = nullptr;
- bool m_generateQML = true;
- bool m_generateItems = false;
- QSvgQmlWriter::GeneratorFlags m_flags;
-};
-
-SvgLoaderVisitor::SvgLoaderVisitor()
-{
- m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
- m_dummyPainter.begin(&m_dummyImage);
- QPen noPen(Qt::NoPen);
- noPen.setBrush(Qt::NoBrush);
- m_dummyPainter.setPen(noPen);
- m_dummyPainter.setBrush(Qt::black);
-}
-
-SvgLoaderVisitor::~SvgLoaderVisitor()
-{
- m_dummyPainter.end();
-}
-
-void SvgLoaderVisitor::handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
-{
- handleBaseNodeSetup(node);
-
- QPainterPath pathCopy = path;
- auto fillStyle = node->style().fill;
- if (fillStyle)
- pathCopy.setFillRule(fillStyle->fillRule());
- if (m_inShapeItem) {
- if (!node->style().transform.isDefault())
- qWarning() << "Skipped transform for node" << node->nodeId() << "type" << node->typeName() << "(this is not supposed to happen)";
- outputShapePath(node, pathCopy, capStyle);
- } else {
- if (m_generateItems) {
- auto *shapeItem = new QQuickShape;
- shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
- shapeItem->setContainsMode(QQuickShape::ContainsMode::FillContains); // TODO: configurable?
- addCurrentItem(shapeItem, node);
- m_parentShapeItem = shapeItem;
- }
- m_inShapeItem = true;
- stream() << shapeName() << " {";
- handleBaseNode(node);
- m_indentLevel++;
- if (m_flags & QSvgQmlWriter::CurveRenderer)
- stream() << "preferredRendererType: Shape.CurveRenderer";
- outputShapePath(node, pathCopy, capStyle);
- //qDebug() << *node->qpath();
- m_indentLevel--;
- stream() << "}";
- if (m_generateItems)
- m_items.pop();
- m_inShapeItem = false;
- m_parentShapeItem = nullptr;
- }
- handleBaseNodeEnd(node);
-}
-
-void SvgLoaderVisitor::visitPathNode(const QSvgPath *node)
-{
- stream() << "// PATH visit " << node->nodeId() << " count: " << node->path().elementCount();
-
- handlePathNode(node, node->path());
-}
-
-void SvgLoaderVisitor::visitLineNode(const QSvgLine *node)
-{
- // TODO: proper end caps (should be flat by default?)
- QPainterPath p;
- p.moveTo(node->line().p1());
- p.lineTo(node->line().p2());
- handlePathNode(node, p, Qt::FlatCap);
-}
-
-static QPainterPath polygonToPath(const QPolygonF &poly, bool closed)
-{
- QPainterPath path;
- if (poly.isEmpty())
- return path;
- bool first = true;
- for (const auto &p : poly) {
- if (first)
- path.moveTo(p);
- else
- path.lineTo(p);
- first = false;
- }
- if (closed)
- path.closeSubpath();
- return path;
-}
-
-void SvgLoaderVisitor::visitPolygonNode(const QSvgPolygon *node)
-{
- stream() << "// POLYGON visit " << node->nodeId() << " count: " << node->polygon().count();
- QPainterPath p = polygonToPath(node->polygon(), true);
- handlePathNode(node, p);
-}
-
-void SvgLoaderVisitor::visitPolylineNode(const QSvgPolyline *node)
-{
- stream() << "// POLYLINE visit " << node->nodeId() << " count: " << node->polygon().count();
- QPainterPath p = polygonToPath(node->polygon(), false);
- handlePathNode(node, p, Qt::FlatCap);
-}
-
-void SvgLoaderVisitor::visitImageNode(const QSvgImage *node)
-{
- // TODO: this requires proper asset management.
- stream() << "// IMAGE visit " << node->nodeId() << " type: " << node->typeName() << " " << node->type();
-
- handleBaseNodeSetup(node);
- const auto &img = node->image();
- QRectF rect = node->rect();
-
- if (m_generateItems) {
- auto *imageItem = new QQuickImage;
- addCurrentItem(imageItem, node);
- auto *imagePriv = static_cast<QQuickImageBasePrivate*>(QQuickItemPrivate::get(imageItem));
- imagePriv->pix.setImage(img);
-
- imageItem->setX(rect.x());
- imageItem->setY(rect.y());
- imageItem->setWidth(rect.width());
- imageItem->setHeight(rect.height());
- }
-
- QString fn = img.hasAlphaChannel() ? QStringLiteral("svg_asset_%1.png").arg(img.cacheKey()) : QStringLiteral("svg_asset_%1.jpg").arg(img.cacheKey());
- if (m_generateQML) {
- // For now we just create a copy of the image in the current directory
- img.save(fn);
- qDebug() << "Saving copy of IMAGE" << fn;
- }
- stream() << "Image {";
- handleBaseNode(node);
- m_indentLevel++;
- stream() << "x: " << rect.x();
- stream() << "y: " << rect.y();
- stream() << "width: " << rect.width();
- stream() << "height: " << rect.height();
- stream() << "source: \"" << fn <<"\"";
-
- m_indentLevel--;
- stream() << "}";
- handleBaseNodeEnd(node);
- if (m_generateItems)
- m_items.pop();
-}
-
-void SvgLoaderVisitor::visitTextNode(const QSvgText *node)
-{
- // TODO: font/size
- // TODO: fallback to path for gradient fill
- stream() << "// TEXT visit " << node->nodeId() << " type: " << node->typeName() << " " << node->type();
- QPointF pos = node->position();
- static int counter = 0;
-
- const bool isTextArea = node->type() == QSvgNode::Textarea;
- QQuickItem *alignItem = nullptr;
- QQuickText *textItem = nullptr;
- if (!isTextArea) {
- stream() << "Item { id: textAlignItem_" << counter << "; x: " << pos.x() << "; y: " << pos.y() << "}";
- if (m_generateItems) {
- alignItem = new QQuickItem(currentItem());
- alignItem->setX(pos.x());
- alignItem->setY(pos.y());
- }
- }
- stream() << "Text {";
-
- if (m_generateItems) {
- textItem = new QQuickText;
- addCurrentItem(textItem, node);
- }
- handleBaseNodeSetup(node);
- m_indentLevel++;
-
- if (isTextArea) {
- stream() << "x: " << pos.x();
- stream() << "y: " << pos.y();
- stream() << "width: " << node->size().width();
- stream() << "height: " << node->size().height();
- stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
- stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
- if (textItem) {
- textItem->setX(pos.x());
- textItem->setY(pos.y());
- textItem->setWidth(node->size().width());
- textItem->setHeight(node->size().height());
- textItem->setWrapMode(QQuickText::Wrap);
- textItem->setClip(true);
- }
- } else {
- auto *anchors = m_generateItems ? QQuickItemPrivate::get(textItem)->anchors() : nullptr;
- auto *alignPrivate = m_generateItems ? QQuickItemPrivate::get(alignItem) : nullptr;
- if (m_generateItems)
- anchors->setBaseline(alignPrivate->top());
- QString hAlign = QStringLiteral("left");
- stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
- switch (m_svgState.textAnchor) {
- case Qt::AlignHCenter:
- hAlign = "horizontalCenter";
- if (m_generateItems)
- anchors->setHorizontalCenter(alignPrivate->left());
- break;
- case Qt::AlignRight:
- hAlign = "right";
- if (m_generateItems)
- anchors->setRight(alignPrivate->left());
- break;
- default:
- qDebug() << "Unexpected text alignment" << m_svgState.textAnchor;
- Q_FALLTHROUGH();
- case Qt::AlignLeft:
- if (m_generateItems)
- anchors->setLeft(alignPrivate->left());
- break;
- }
- stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
- }
- counter++;
- QString text;
- for (const auto *tspan : node->tspans()) {
- if (!tspan) {
- text += "<br>";
- continue;
- }
-
- if (!tspan->style().font.isDefault()) // TODO: switch to rich text when we have more complex spans with fonts?
- qDebug() << "Not implemented Tspan with font:" << tspan->style().font->qfont();
- QString spanColor;
- if (!tspan->style().fill.isDefault()) {
- auto &b = tspan->style().fill->qbrush();
- qDebug() << "tspan FILL:" << b;
- if (b.style() != Qt::NoBrush)
- spanColor = b.color().name();
- }
- bool fontTag = !spanColor.isEmpty();
- if (fontTag)
- text += QStringLiteral("<font color=\"%1\">").arg(spanColor); // TODO: size="1-7" ???
- text += tspan->text().toHtmlEscaped();
- if (fontTag)
- text += QStringLiteral("</font>");
- }
- stream() << "color: \"" << currentFillColor() << "\"";
- stream() << "textFormat: Text.StyledText";
-
- QFont font = m_dummyPainter.font();
-
- stream() << "text: \"" << text << "\""; // TODO: how about adding template<T> TestVisitor::streamProperty(const QString &name, const T &value)
- stream() << "font.family: \"" << font.family() << "\"";
- if (font.pixelSize() > 0)
- stream() << "font.pixelSize:" << font.pixelSize();
- else if (font.pointSize() > 0)
- stream() << "font.pixelSize:" << font.pointSizeF();
- if (font.underline())
- stream() << "font.underline: true";
- if (font.weight() != QFont::Normal)
- stream() << "font.weight: " << int(font.weight());
-
- if (font.pixelSize() <= 0 && font.pointSize() > 0)
- font.setPixelSize(font.pointSize()); // ### TODO: this makes no sense ###
-
- if (m_generateItems) {
- textItem->setColor(QColor::fromString(currentFillColor()));
- textItem->setTextFormat(QQuickText::StyledText);
- textItem->setText(text);
- textItem->setFont(font);
- }
-
- m_indentLevel--;
- stream() << "}";
- if (m_generateItems)
- m_items.pop();
- handleBaseNodeEnd(node);
-}
-
-//#define EXTRA_DEBUG
-#ifdef EXTRA_DEBUG
-static int nodeSetupLevel = 0;
-#endif
-
-void SvgLoaderVisitor::handleBaseNodeSetup(const QSvgNode *node)
-{
-#ifdef EXTRA_DEBUG
- qDebug() << QByteArray().fill(' ', m_indentLevel * 2).constData() << "before SETUP" << node << "fill" << currentFillColor()
- << "stroke" << currentStrokeColor() << currentStrokeWidth() << node->nodeId() << " type: " << node->typeName() << " " << node->type() << "level" << nodeSetupLevel;
-#endif
- node->applyStyle(&m_dummyPainter, m_svgState);
-
-#ifdef EXTRA_DEBUG
- nodeSetupLevel++;
- qDebug() << QByteArray().fill(' ', m_indentLevel * 2).constData() << "after SETUP" << node << "fill" << currentFillColor()
- << "stroke" << currentStrokeColor() << currentStrokeWidth() << node->nodeId();
-#endif
-}
-
-void SvgLoaderVisitor::handleBaseNode(const QSvgNode *node)
-{
- m_indentLevel++;
- if (!node->nodeId().isEmpty())
- stream() << "objectName: \"" << node->nodeId() << "\""; // or maybe "objectName: \"svg_node:" << node->nodeId()
- if (!node->style().transform.isDefault()) {
- QTransform tr = node->style().transform->qtransform();
- auto sx = tr.m11();
- auto sy = tr.m22();
- auto x = tr.m31();
- auto y = tr.m32();
- if (tr.type() == QTransform::TxTranslate) {
- stream() << "// Translate " << tr.m31() << ", " << tr.m32();
- stream() << "transform: Translate { " << "x: " << x << "; y: " << y << " }";
- } else if (tr.type() == QTransform::TxScale && !x && !y) {
- stream() << "// Scale " << tr.m11() << ", " << tr.m22();
- stream() << "transform: Scale { xScale: " << sx << "; yScale: " << sy << " }";
- } else {
- stream() << "// Complex xform " << tr.type();
- const QMatrix4x4 m(tr);
- auto xform = new QQuickMatrix4x4;
- xform->setMatrix(m);
- {
- stream() << "transform: [ Matrix4x4 { matrix: Qt.matrix4x4 (";
- m_indentLevel += 3;
- const auto *data = m.data();
- for (int i = 0; i < 4; i++) {
- stream() << data[i] << ", " << data[i+4] << ", " << data[i+8] << ", " << data[i+12] << ", ";
- }
- stream() << ") } ]";
- m_indentLevel -= 3;
- }
- }
- if (m_generateItems) {
- auto xformProp = currentItem()->transform();
- if (tr.type() == QTransform::TxTranslate) {
- auto *translate = new QQuickTranslate;
- translate->setX(x);
- translate->setY(y);
- xformProp.append(&xformProp, translate);
- } else if (tr.type() == QTransform::TxScale && !x && !y) {
- auto scale = new QQuickScale;
- scale->setParent(currentItem());
- scale->setXScale(sx);
- scale->setYScale(sy);
- xformProp.append(&xformProp, scale);
- } else {
- const QMatrix4x4 m(tr);
- auto xform = new QQuickMatrix4x4;
- xform->setMatrix(m);
- xformProp.append(&xformProp, xform);
- }
- }
- }
- if (!node->style().opacity.isDefault()) {
- stream() << "opacity: " << node->style().opacity->opacity();
- if (m_generateItems)
- currentItem()->setOpacity(node->style().opacity->opacity());
- }
- m_indentLevel--;
-}
-
-void SvgLoaderVisitor::handleBaseNodeEnd(const QSvgNode *node)
-{
- node->revertStyle(&m_dummyPainter, m_svgState);
-#ifdef EXTRA_DEBUG
- nodeSetupLevel--;
- qDebug() << QByteArray().fill(' ', m_indentLevel * 2).constData() << "AFTER" << node << "fill" << currentFillColor()
- << "stroke" << currentStrokeColor() << currentStrokeWidth() << node->nodeId() << "level" << nodeSetupLevel;
-#endif
-}
-
-QString SvgLoaderVisitor::currentFillColor() const
-{
- if (m_dummyPainter.brush().style() != Qt::NoBrush) {
- QColor c(m_dummyPainter.brush().color());
- c.setAlphaF(m_svgState.fillOpacity);
- //qDebug() << "FILL" << c << m_svgState.fillOpacity << c.name();
- return c.name(QColor::HexArgb);
- } else {
- return QStringLiteral("transparent");
- }
-}
-
-const QGradient *SvgLoaderVisitor::currentFillGradient() const
-{
-
- if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern )
- return m_dummyPainter.brush().gradient();
- return nullptr;
-}
-
-QString SvgLoaderVisitor::currentStrokeColor() const
-{
- if (m_dummyPainter.pen().style() != Qt::NoPen)
- return m_dummyPainter.pen().color().name();
- else if (m_dummyPainter.pen().brush().style() == Qt::SolidPattern)
- return m_dummyPainter.pen().brush().color().name();
- return {};
-}
-
-float SvgLoaderVisitor::currentStrokeWidth() const
-{
- float penWidth = m_dummyPainter.pen().widthF();
- return penWidth ? penWidth : 1;
-}
-
-// Find the square that gives the same gradient in QGradient::LogicalMode as
-// objModeRect does in QGradient::ObjectMode
-
-// When the object's bounding box is not square, the stripes that are conceptually
-// perpendicular to the gradient vector within object bounding box space shall render
-// non-perpendicular relative to the gradient vector in user space due to application
-// of the non-uniform scaling transformation from bounding box space to user space.
-static QRectF mapToQtLogicalMode(const QRectF &objModeRect, const QRectF &boundingRect)
-{
-
- QRect pixelRect(objModeRect.x() * boundingRect.width() + boundingRect.left(),
- objModeRect.y() * boundingRect.height() + boundingRect.top(),
- objModeRect.width() * boundingRect.width(),
- objModeRect.height() * boundingRect.height());
-
- if (pixelRect.isEmpty()) // pure horizontal/vertical gradient
- return pixelRect;
-
- double w = boundingRect.width();
- double h = boundingRect.height();
- double objModeSlope = objModeRect.height() / objModeRect.width();
- double a = objModeSlope * w / h;
-
- // do calculation with origin == pixelRect.topLeft
- double x2 = pixelRect.width();
- double y2 = pixelRect.height();
- double x = (x2 + a * y2) / (1 + a * a);
- double y = y2 - (x - x2)/a;
-
- return QRectF(pixelRect.topLeft(), QSizeF(x,y));
-}
-
-static QString toSvgString(const QPainterPath &path)
-{
- QString svgPathString;
- QTextStream strm(&svgPathString);
-
- for (int i = 0; i < path.elementCount(); ++i) {
- QPainterPath::Element element = path.elementAt(i);
- if (element.isMoveTo()) {
- strm << "M " << element.x << " " << element.y << " ";
- } else if (element.isLineTo()) {
- strm << "L " << element.x << " " << element.y << " ";
- } else if (element.isCurveTo()) {
- QPointF c1(element.x, element.y);
- ++i;
- element = path.elementAt(i);
-
- QPointF c2(element.x, element.y);
- ++i;
- element = path.elementAt(i);
- QPointF ep(element.x, element.y);
-
- strm << "C "
- << c1.x() << " "
- << c1.y() << " "
- << c2.x() << " "
- << c2.y() << " "
- << ep.x() << " "
- << ep.y() << " ";
- }
- }
-
- return svgPathString;
-}
-
-static QString toSvgString(const QQuadPath &path)
-{
- QString svgPathString;
- QTextStream strm(&svgPathString);
- path.iterateElements([&](const QQuadPath::Element &e){
- if (e.isSubpathStart())
- strm << "M " << e.startPoint().x() << " " << e.startPoint().y() << " ";
-
- if (e.isLine()) {
- strm << "L " << e.endPoint().x() << " " << e.endPoint().y() << " ";
- } else {
- strm << "Q " << e.controlPoint().x() << " " << e.controlPoint().y() << " "
- << e.endPoint().x() << " " << e.endPoint().y() << " ";
- }
- });
-
- return svgPathString;
-}
-
-void SvgLoaderVisitor::outputGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect)
-{
- auto setStops = [](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
- auto stopsProp = quickGrad->stops();
- for (auto &stop : stops) {
- auto *stopObj = new QQuickGradientStop(quickGrad);
- stopObj->setPosition(stop.first);
- stopObj->setColor(stop.second);
- stopsProp.append(&stopsProp, stopObj);
- }
- };
-
- if (grad->type() == QGradient::LinearGradient) {
- auto *linGrad = static_cast<const QLinearGradient *>(grad);
- stream() << "fillGradient: LinearGradient {";
- m_indentLevel++;
-
- QRectF gradRect(linGrad->start(), linGrad->finalStop());
- QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : mapToQtLogicalMode(gradRect, boundingRect);
-
- stream() << "x1: " << logRect.left();
- stream() << "y1: " << logRect.top();
- stream() << "x2: " << logRect.right();
- stream() << "y2: " << logRect.bottom();
- for (auto &stop : linGrad->stops()) {
- stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
- }
- m_indentLevel--;
- stream() << "}";
-
- if (shapePath) {
- auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
-
- quickGrad->setX1(logRect.left());
- quickGrad->setY1(logRect.top());
- quickGrad->setX2(logRect.right());
- quickGrad->setY2(logRect.bottom());
- setStops(quickGrad, linGrad->stops());
-
- shapePath->setFillGradient(quickGrad);
- }
- } else if (grad->type() == QGradient::RadialGradient) {
- auto *radGrad = static_cast<const QRadialGradient*>(grad);
- stream() << "fillGradient: RadialGradient {";
- m_indentLevel++;
-
- stream() << "centerX: " << radGrad->center().x();
- stream() << "centerY: " << radGrad->center().y();
- stream() << "centerRadius: " << radGrad->radius();
- stream() << "focalX: centerX; focalY: centerY";
- for (auto &stop : radGrad->stops()) {
- stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
- }
- m_indentLevel--;
- stream() << "}";
-
- if (shapePath) {
- auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
- quickGrad->setCenterX(radGrad->center().x());
- quickGrad->setCenterY(radGrad->center().y());
- quickGrad->setCenterRadius(radGrad->radius());
- quickGrad->setFocalX(radGrad->center().x());
- quickGrad->setFocalY(radGrad->center().y());
- setStops(quickGrad, radGrad->stops());
-
- shapePath->setFillGradient(quickGrad);
- }
- }
-}
-
-void SvgLoaderVisitor::outputShapePath(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
-{
- const bool optimize = m_flags.testFlag(QSvgQmlWriter::OptimizePaths);
- QRectF boundingRect = path.boundingRect();
- if (optimize) {
- const bool outlineMode = m_flags.testFlag(QSvgQmlWriter::OutlineStrokeMode);
- QQuadPath strokePath = QQuadPath::fromPainterPath(path);
- bool fillPathNeededClose;
- QQuadPath fillPath = strokePath.subPathsClosed(&fillPathNeededClose);
- const bool intersectionsFound = QSGCurveProcessor::solveIntersections(fillPath, false);
- fillPath.addCurvatureData();
- QSGCurveProcessor::solveOverlaps(fillPath);
-
- const bool compatibleStrokeAndFill = !fillPathNeededClose && !intersectionsFound;
-
- if (compatibleStrokeAndFill || outlineMode) {
- outputShapePath(node, nullptr, &fillPath, capStyle, FillAndStroke, boundingRect);
- } else {
- outputShapePath(node, nullptr, &fillPath, capStyle, FillPath, boundingRect);
- outputShapePath(node, nullptr, &strokePath, capStyle, StrokePath, boundingRect);
- }
- } else {
- outputShapePath(node, &path, nullptr, capStyle, FillAndStroke, boundingRect);
- }
-}
-
-static QString pathHintString(const QQuadPath &qp)
-{
- QString res;
- QTextStream str(&res);
- auto flags = qp.pathHints();
- if (!flags)
- return res;
- str << "pathHints:";
- bool first = true;
-
-#define CHECK_PATH_HINT(flagName) \
- if (flags.testFlag(QQuadPath::flagName)) { \
- if (!first) \
- str << " |"; \
- first = false; \
- str << " ShapePath." #flagName; \
- }
-
- CHECK_PATH_HINT(PathLinear)
- CHECK_PATH_HINT(PathQuadratic)
- CHECK_PATH_HINT(PathConvex)
- CHECK_PATH_HINT(PathFillOnRight)
- CHECK_PATH_HINT(PathSolid)
- CHECK_PATH_HINT(PathNonIntersecting)
- CHECK_PATH_HINT(PathNonOverlappingControlPointTriangles)
-
- return res;
-}
-
-void SvgLoaderVisitor::outputShapePath(const QSvgNode *node, const QPainterPath *painterPath, const QQuadPath *quadPath, Qt::PenCapStyle capStyle, PathSelector pathSelector, const QRectF &boundingRect)
-{
- Q_UNUSED(pathSelector)
- Q_ASSERT(painterPath || quadPath);
-
- QString penName = currentStrokeColor();
- const bool noPen = penName.isEmpty() || penName == u"transparent";
- if (pathSelector == StrokePath && noPen)
- return;
-
- const bool noFill = !currentFillGradient() && currentFillColor() == u"transparent";
- if (pathSelector == FillPath && noFill)
- return;
-
- auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
- stream() << "ShapePath {";
- m_indentLevel++;
- auto *shapePath = m_generateItems ? new QQuickShapePath : nullptr;
- if (!node->nodeId().isEmpty()) {
- switch (pathSelector) {
- case FillPath:
- stream() << "objectName: \"svg_fill_path:" << node->nodeId() << "\"";
- break;
- case StrokePath:
- stream() << "objectName: \"svg_stroke_path:" << node->nodeId() << "\"";
- break;
- case FillAndStroke:
- stream() << "objectName: \"svg_path:" << node->nodeId() << "\"";
- break;
- }
- if (shapePath)
- shapePath->setObjectName(QStringLiteral("svg_path:") + node->nodeId());
- }
- stream() << "// boundingRect: " << boundingRect.x() << ", " << boundingRect.y() << " " << boundingRect.width() << "x" << boundingRect.height();
-
- if (noPen || !(pathSelector & StrokePath)) {
- stream() << "strokeColor: \"transparent\"";
- if (shapePath)
- shapePath->setStrokeColor(Qt::transparent);
- } else {
- stream() << "strokeColor: \"" << penName << "\"";
- stream() << "strokeWidth: " << currentStrokeWidth();
- if (shapePath) {
- shapePath->setStrokeColor(QColor::fromString(penName));
- shapePath->setStrokeWidth(currentStrokeWidth());
- }
- }
- if (capStyle == Qt::FlatCap)
- stream() << "capStyle: ShapePath.FlatCap"; //### TODO Add the rest of the styles, as well as join styles etc.
-
- if (shapePath)
- shapePath->setCapStyle(QQuickShapePath::CapStyle(capStyle));
-
- if (!(pathSelector & FillPath)) {
- stream() << "fillColor: \"transparent\"";
- if (shapePath)
- shapePath->setFillColor(Qt::transparent);
- } else if (auto *grad = currentFillGradient()) {
- outputGradient(grad, shapePath, boundingRect);
- } else {
- stream() << "fillColor: \"" << currentFillColor() << "\"";
- if (shapePath)
- shapePath->setFillColor(QColor::fromString(currentFillColor()));
- }
- if (fillRule == QQuickShapePath::WindingFill)
- stream() << "fillRule: ShapePath.WindingFill";
- else
- stream() << "fillRule: ShapePath.OddEvenFill";
- if (shapePath)
- shapePath->setFillRule(fillRule);
-
- if (quadPath) {
- QString hintStr = pathHintString(*quadPath);
- if (!hintStr.isEmpty())
- stream() << hintStr;
- }
-
- QString svgPathString = painterPath ? toSvgString(*painterPath) : toSvgString(*quadPath);
- stream() << "PathSvg { path: \"" << svgPathString << "\" }";
-
- if (shapePath) {
- auto *pathSvg = new QQuickPathSvg;
- pathSvg->setPath(svgPathString);
- pathSvg->setParent(shapePath);
-
- auto pathElementProp = shapePath->pathElements();
- pathElementProp.append(&pathElementProp, pathSvg);
-
- shapePath->setParent(currentItem());
- auto shapeDataProp = m_parentShapeItem->data();
- shapeDataProp.append(&shapeDataProp, shapePath);
- }
- m_indentLevel--;
- stream() << "}";
-}
-
-static bool isPathContainer(const QSvgStructureNode *node)
-{
- bool foundPath = false;
- for (const auto *child : node->renderers()) {
- switch (child->type()) {
- // nodes that shouldn't go inside Shape{}
- case QSvgNode::Switch:
- case QSvgNode::Doc:
- case QSvgNode::Group:
- case QSvgNode::Animation:
- case QSvgNode::Use:
- case QSvgNode::Video:
- //qDebug() << "NOT path container because" << node->typeName() ;
- return false;
-
- // nodes that could go inside Shape{}
- case QSvgNode::Defs:
- case QSvgNode::Image:
- case QSvgNode::Textarea:
- case QSvgNode::Text:
- case QSvgNode::Tspan:
- break;
-
- // nodes that are done as pure ShapePath{}
- case QSvgNode::Rect:
- case QSvgNode::Circle:
- case QSvgNode::Ellipse:
- case QSvgNode::Line:
- case QSvgNode::Path:
- case QSvgNode::Polygon:
- case QSvgNode::Polyline:
- if (!child->style().transform.isDefault()) {
- //qDebug() << "NOT path container because local transform";
- return false;
- }
- foundPath = true;
- break;
- default:
- qDebug() << "Unhandled type in switch" << child->type();
- break;
- }
- }
- //qDebug() << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
- return foundPath;
-}
-
-namespace {
-class ViewBoxItem : public QQuickItem
-{
-public:
- ViewBoxItem(const QRectF viewBox, QQuickItem *parent = nullptr) : QQuickItem(parent), m_viewBox(viewBox) { setXForm(); }
-
-protected:
- void geometryChange(const QRectF &/*newGeometry*/, const QRectF &/*oldGeometry*/) override
- {
- setXForm();
- }
-
-private:
- void setXForm()
- {
- auto xformProp = transform();
- xformProp.clear(&xformProp);
- bool translate = !qFuzzyIsNull(m_viewBox.x()) || !qFuzzyIsNull(m_viewBox.y());
- if (translate) {
- auto *tr = new QQuickTranslate(this);
- tr->setX(-m_viewBox.x());
- tr->setY(-m_viewBox.y());
- xformProp.append(&xformProp, tr);
- }
- if (!m_viewBox.isEmpty() && width() && height()) {
- auto *scale = new QQuickScale(this);
- qreal sx = width() / m_viewBox.width();
- qreal sy = height() / m_viewBox.height();
-
- scale->setXScale(sx);
- scale->setYScale(sy);
- xformProp.append(&xformProp, scale);
- }
- }
- QRectF m_viewBox;
-};
-}
-
-bool SvgLoaderVisitor::visitDefsNodeStart(const QSvgDefs *node)
-{
- stream() << "// skipping DEFS \"" << node->nodeId() << "\"";
- return false; // skip to end; TODO: implement defs
-}
-
-bool SvgLoaderVisitor::visitStructureNodeStart(const QSvgStructureNode *node)
-{
- constexpr bool forceSeparatePaths = false;
- handleBaseNodeSetup(node);
- stream() << "// START " << node->nodeId() << " type: " << node->typeName() << " " << node->type();
-
- bool isTopLevel = node->type() == QSvgNode::Doc;
- bool hasViewBox = false;
- QRectF viewBox;
-
- if (isTopLevel) {
- auto *doc = static_cast<const QSvgTinyDocument *>(node);
- viewBox = doc->viewBox();
- hasViewBox = !viewBox.isEmpty();
- }
-
- if (!forceSeparatePaths && !isTopLevel && isPathContainer(node)) {
- stream() << shapeName() <<" { //combined path container";
- m_indentLevel++;
- if (m_flags & QSvgQmlWriter::CurveRenderer)
- stream() << "preferredRendererType: Shape.CurveRenderer";
- m_indentLevel--;
-
- m_inShapeItem = true;
- if (m_generateItems) {
- auto *shapeItem = new QQuickShape;
- shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer); // TODO: settable
- m_parentShapeItem = shapeItem;
- addCurrentItem(shapeItem, node);
- }
- } else {
- stream() << "Item { // structure node";
- if (m_generateItems) {
- auto *item = hasViewBox ? new ViewBoxItem(viewBox) : new QQuickItem;
- addCurrentItem(item, node );
- if (isTopLevel)
- m_loadedItem = item;
- }
- }
- if (hasViewBox) {
- m_indentLevel++;
- stream() << "transform: [";
- m_indentLevel++;
- bool translate = !qFuzzyIsNull(viewBox.x()) || !qFuzzyIsNull(viewBox.y());
- if (translate)
- stream() << "Translate { x: " << -viewBox.x() << "; y: " << -viewBox.y() << " },";
- stream() << "Scale { xScale: width / " << viewBox.width() << "; yScale: height / " << viewBox.height() << " }";
- m_indentLevel--;
- stream() << "]";;
- m_indentLevel--;
- }
- handleBaseNode(node);
- m_indentLevel++;
- return true;
-}
-
-void SvgLoaderVisitor::visitStructureNodeEnd(const QSvgStructureNode *node)
-{
- m_indentLevel--;
- stream() << "} // END " << node->nodeId() << " type: " << node->typeName() << " " << node->type();
- handleBaseNodeEnd(node);
-// qDebug() << "REVERT" << node->nodeId() << node->type() << (m_dummyPainter.pen().style() != Qt::NoPen) << m_dummyPainter.pen().color().name()
-// << (m_dummyPainter.pen().brush().style() != Qt::NoBrush) << m_dummyPainter.pen().brush().color().name();
- m_inShapeItem = false;
- m_parentShapeItem = nullptr;
- if (m_generateItems)
- m_items.pop();
-}
-
-void SvgLoaderVisitor::visitRectNode(const QSvgRect *node)
-{
- QRectF rect = node->rect();
- QPointF rads = node->radius();
- // This is using Qt::RelativeSize semantics: percentage of half rect size
- qreal x1 = rect.left();
- qreal x2 = rect.right();
- qreal y1 = rect.top();
- qreal y2 = rect.bottom();
-
- qreal rx = rads.x() * rect.width() / 200;
- qreal ry = rads.y() * rect.height() / 200;
- QPainterPath p;
-
- p.moveTo(x1 + rx, y1);
- p.lineTo(x2 - rx, y1);
- // qDebug() << "Line1" << x2 - rx << y1;
- p.arcTo(x2 - rx * 2, y1, rx * 2, ry * 2, 90, -90); // ARC to x2, y1 + ry
- // qDebug() << "p1" << p;
-
- p.lineTo(x2, y2 - ry);
- p.arcTo(x2 - rx * 2, y2 - ry * 2, rx * 2, ry * 2, 0, -90); // ARC to x2 - rx, y2
-
- p.lineTo(x1 + rx, y2);
- p.arcTo(x1, y2 - ry * 2, rx * 2, ry * 2, 270, -90); // ARC to x1, y2 - ry
-
- p.lineTo(x1, y1 + ry);
- p.arcTo(x1, y1, rx * 2, ry * 2, 180, -90); // ARC to x1 + rx, y1
-
-
- stream() << "// Path from rect " << node->nodeId() << " r " << rect.x() << ", " << rect.y()
- << " " << rect.width() << "x" << rect.height() << " R: " << rads.x() << ", " << rads.y();
-
- handlePathNode(node, p);
-
- return;
-
-}
-
-void SvgLoaderVisitor::visitEllipseNode(const QSvgEllipse *node)
-{
- QRectF rect = node->rect();
- stream() << "// Ellipse" << node->nodeId() << " rect: " << rect.x() << ", " << rect.y()
- << " " << rect.width() << "x" << rect.height();
- QPainterPath p;
- p.addEllipse(rect);
-
- handlePathNode(node, p);
-}
-
-QQuickItem *SvgLoaderVisitor::loadQML(QTextStream *outStream, const QSvgTinyDocument *doc, QQuickItem *parentItem)
-{
- Q_ASSERT(outStream);
- m_stream = outStream;
- m_indentLevel = 0;
-
- stream() << "import QtQuick";
- stream() << "import QtQuick.Shapes" << Qt::endl;
-
- QRectF viewBox = doc->viewBox();
-
- m_generateItems = parentItem != nullptr;
- m_items.push(parentItem);
-
- stream() << "Item {";
- m_indentLevel++;
- stream() << "// viewBox " << viewBox.x() << ", " << viewBox.y()
- << " " << viewBox.width() << "x" << viewBox.height();
- stream() << "// size " << doc->width() << "x" << doc->height();
- double w = doc->width();
- double h = doc->height();
- if (w > 0)
- stream() << "implicitWidth: " << w;
- if (h > 0)
- stream() << "implicitHeight: " << h;
-
- if (parentItem) {
- m_generateItems = true;
- parentItem->setImplicitWidth(w);
- parentItem->setImplicitHeight(h);
- }
- traverse(doc);
- m_indentLevel--;
- stream() << "}";
- return m_loadedItem;
-}
-
-void SvgLoaderVisitor::visitNode(const QSvgNode *node)
-{
- handleBaseNodeSetup(node);
- stream() << "//### SVG NODE NOT IMPLEMENTED: " << node->nodeId() << " type: " << node->typeName() << " " << node->type();
- stream() << "Item {";
- handleBaseNode(node);
- stream() << "}";
- handleBaseNodeEnd(node);
-}
-
-QQuickItem *QSvgQmlWriter::loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, GeneratorFlags flags, const QString &typeName, QQuickItem *parentItem, const QString &commentString)
-{
- SvgLoaderVisitor visitor;
- if (!typeName.isEmpty())
- visitor.setShapeTypeName(typeName);
- visitor.setFlags(flags);
- QByteArray result;
- QTextStream str(&result);
- if (commentString.isEmpty())
- str << "// Generated from SVG" << Qt::endl;
- else
- str << "// " << commentString << Qt::endl;
- auto *loadedItem = visitor.loadQML(&str, doc, parentItem);
- if (!outFileName.isEmpty()) {
- QFile outFile(outFileName);
- outFile.open(QIODevice::WriteOnly);
- outFile.write(result);
- outFile.close();
- }
-#if 0
- result.truncate(300);
- qDebug().noquote() << result;
-#endif
- return loadedItem;
-}
-
-QT_END_NAMESPACE
diff --git a/tools/svgtoqml/qsvgloader_p.h b/tools/svgtoqml/qsvgloader_p.h
deleted file mode 100644
index 1804a7d358..0000000000
--- a/tools/svgtoqml/qsvgloader_p.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#ifndef QSVGQMLWRITER_P_H
-#define QSVGQMLWRITER_P_H
-
-#include <QtCore/qtconfigmacros.h>
-#include <QtCore/qflags.h>
-
-QT_BEGIN_NAMESPACE
-
-class QTextStream;
-class QSvgTinyDocument;
-class QString;
-class QQuickItem;
-
-class QSvgQmlWriter
-{
-public:
- enum GeneratorFlag {
- OptimizePaths = 0x01,
- CurveRenderer = 0x02,
- OutlineStrokeMode = 0x04
- };
- Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag);
- static QQuickItem *loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, GeneratorFlags flags, const QString &typeName, QQuickItem *parentItem, const QString &commentString);
-};
-Q_DECLARE_OPERATORS_FOR_FLAGS(QSvgQmlWriter::GeneratorFlags);
-
-QT_END_NAMESPACE
-
-#endif // QSVGQMLWRITER_P_H