aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickvectorimage/generator
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickvectorimage/generator')
-rw-r--r--src/quickvectorimage/generator/qquickgenerator.cpp81
-rw-r--r--src/quickvectorimage/generator/qquickgenerator_p.h79
-rw-r--r--src/quickvectorimage/generator/qquickitemgenerator.cpp393
-rw-r--r--src/quickvectorimage/generator/qquickitemgenerator_p.h56
-rw-r--r--src/quickvectorimage/generator/qquicknodeinfo_p.h119
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp561
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator_p.h103
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp1094
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl_p.h69
-rw-r--r--src/quickvectorimage/generator/utils_p.h264
10 files changed, 2819 insertions, 0 deletions
diff --git a/src/quickvectorimage/generator/qquickgenerator.cpp b/src/quickvectorimage/generator/qquickgenerator.cpp
new file mode 100644
index 0000000000..e31b6ae99f
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickgenerator.cpp
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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(lcQuickVectorImage, "qt.quick.vectorimage", QtWarningMsg)
+
+QQuickGenerator::QQuickGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags)
+ : m_flags(flags)
+ , m_fileName(fileName)
+ , m_loader(nullptr)
+{
+}
+
+QQuickGenerator::~QQuickGenerator()
+{
+ delete m_loader;
+}
+
+void QQuickGenerator::setGeneratorFlags(QQuickVectorImageGenerator::GeneratorFlags flags)
+{
+ m_flags = flags;
+}
+
+QQuickVectorImageGenerator::GeneratorFlags QQuickGenerator::generatorFlags()
+{
+ return m_flags;
+}
+
+bool QQuickGenerator::generate()
+{
+ m_loader = new QSvgVisitorImpl(m_fileName, this);
+ m_generationSucceeded = m_loader->traverse();
+ return m_generationSucceeded;
+}
+
+void QQuickGenerator::optimizePaths(const PathNodeInfo &info)
+{
+ QPainterPath pathCopy = info.painterPath;
+ pathCopy.setFillRule(info.fillRule);
+
+ if (m_flags.testFlag(QQuickVectorImageGenerator::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(QQuickVectorImageGenerator::GeneratorFlag::OutlineStrokeMode)) {
+ outputShapePath(info, nullptr, &fillPath, QQuickVectorImageGenerator::FillAndStroke, pathCopy.boundingRect());
+ } else {
+ outputShapePath(info, nullptr, &fillPath, QQuickVectorImageGenerator::FillPath, pathCopy.boundingRect());
+ outputShapePath(info, nullptr, &strokePath, QQuickVectorImageGenerator::StrokePath, pathCopy.boundingRect());
+ }
+ } else {
+ outputShapePath(info, &pathCopy, nullptr, QQuickVectorImageGenerator::FillAndStroke, pathCopy.boundingRect());
+ }
+}
+
+bool QQuickGenerator::isNodeVisible(const NodeInfo &info)
+{
+ if (!info.isVisible || !info.isDisplayed)
+ return false;
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorimage/generator/qquickgenerator_p.h b/src/quickvectorimage/generator/qquickgenerator_p.h
new file mode 100644
index 0000000000..d8ef5c2819
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickgenerator_p.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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/qquickvectorimageglobal_p.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcQuickVectorImage, Q_QUICKVECTORIMAGEGENERATOR_EXPORT)
+
+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 UseNodeInfo;
+struct StructureNodeInfo;
+
+class Q_QUICKVECTORIMAGEGENERATOR_EXPORT QQuickGenerator
+{
+public:
+ QQuickGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags);
+ virtual ~QQuickGenerator();
+
+ void setGeneratorFlags(QQuickVectorImageGenerator::GeneratorFlags flags);
+ QQuickVectorImageGenerator::GeneratorFlags generatorFlags();
+
+ bool generate();
+
+protected:
+ virtual void generateNodeBase(const NodeInfo &info) = 0;
+ virtual bool generateDefsNode(const NodeInfo &info) = 0;
+ virtual void generateImageNode(const ImageNodeInfo &info) = 0;
+ virtual void generatePath(const PathNodeInfo &info) = 0;
+ virtual void generateNode(const NodeInfo &info) = 0;
+ virtual void generateTextNode(const TextNodeInfo &info) = 0;
+ virtual void generateUseNode(const UseNodeInfo &info) = 0;
+ virtual bool generateStructureNode(const StructureNodeInfo &info) = 0;
+ virtual bool generateRootNode(const StructureNodeInfo &info) = 0;
+ virtual void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect) = 0;
+ void optimizePaths(const PathNodeInfo &info);
+ bool isNodeVisible(const NodeInfo &info);
+
+protected:
+ bool m_generationSucceeded = false;
+ QQuickVectorImageGenerator::GeneratorFlags m_flags;
+
+private:
+ QString m_fileName;
+ QSvgVisitorImpl *m_loader;
+ friend class QSvgVisitorImpl;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKGENERATOR_P_H
diff --git a/src/quickvectorimage/generator/qquickitemgenerator.cpp b/src/quickvectorimage/generator/qquickitemgenerator.cpp
new file mode 100644
index 0000000000..ad85bd3685
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickitemgenerator.cpp
@@ -0,0 +1,393 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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
+
+QQuickItemGenerator::QQuickItemGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, QQuickItem *parentItem)
+ :QQuickGenerator(fileName, flags)
+{
+ Q_ASSERT(parentItem);
+ m_items.push(parentItem);
+ m_parentItem = parentItem;
+}
+
+QQuickItemGenerator::~QQuickItemGenerator()
+{
+}
+
+void QQuickItemGenerator::generateNodeBase(const 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(const NodeInfo &info)
+{
+ Q_UNUSED(info)
+
+ return false;
+}
+
+void QQuickItemGenerator::generateImageNode(const ImageNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ auto *imageItem = new QQuickImage;
+ auto *imagePriv = static_cast<QQuickImageBasePrivate*>(QQuickItemPrivate::get(imageItem));
+ imagePriv->currentPix->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);
+ m_items.pop();
+}
+
+void QQuickItemGenerator::generatePath(const PathNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ if (m_inShapeItem) {
+ if (!info.isDefaultTransform)
+ qCWarning(lcQuickVectorImage) << "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(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
+ shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
+ shapeItem->setContainsMode(QQuickShape::ContainsMode::FillContains); // TODO: configurable?
+ addCurrentItem(shapeItem, info);
+ m_parentShapeItem = shapeItem;
+ m_inShapeItem = true;
+
+ 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, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
+{
+ Q_UNUSED(pathSelector)
+ Q_ASSERT(painterPath || quadPath);
+
+ const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
+ if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
+ return;
+
+ const bool noFill = info.grad.type() == QGradient::NoGradient && info.fillColor == QColorConstants::Transparent;
+
+ if (pathSelector == QQuickVectorImageGenerator::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 & QQuickVectorImageGenerator::StrokePath)) {
+ shapePath->setStrokeColor(Qt::transparent);
+ } else {
+ shapePath->setStrokeColor(info.strokeStyle.color);
+ shapePath->setStrokeWidth(info.strokeStyle.width);
+ shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
+ shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
+ shapePath->setMiterLimit(info.strokeStyle.miterLimit);
+ if (info.strokeStyle.dashArray.length() != 0) {
+ shapePath->setStrokeStyle(QQuickShapePath::DashLine);
+ shapePath->setDashPattern(info.strokeStyle.dashArray.toVector());
+ shapePath->setDashOffset(info.strokeStyle.dashOffset);
+ }
+ }
+
+ if (!(pathSelector & QQuickVectorImageGenerator::FillPath))
+ shapePath->setFillColor(Qt::transparent);
+ else if (info.grad.type() != QGradient::NoGradient)
+ generateGradient(&info.grad, shapePath, boundingRect);
+ else
+ shapePath->setFillColor(info.fillColor);
+
+ shapePath->setFillRule(fillRule);
+ if (!info.fillTransform.isIdentity())
+ shapePath->setFillTransform(info.fillTransform);
+
+ QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::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)
+{
+ if (!shapePath)
+ return;
+
+ 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 : QQuickVectorImageGenerator::Utils::mapToQtLogicalMode(gradRect, boundingRect);
+
+ 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);
+ auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
+ quickGrad->setCenterX(radGrad->center().x());
+ quickGrad->setCenterY(radGrad->center().y());
+ quickGrad->setCenterRadius(radGrad->radius());
+ quickGrad->setFocalX(radGrad->focalPoint().x());
+ quickGrad->setFocalY(radGrad->focalPoint().y());
+ setStops(quickGrad, radGrad->stops());
+
+ shapePath->setFillGradient(quickGrad);
+ }
+}
+
+void QQuickItemGenerator::generateNode(const NodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ qCWarning(lcQuickVectorImage) << "SVG NODE NOT IMPLEMENTED: "
+ << info.nodeId
+ << " type: " << info.typeName;
+}
+
+void QQuickItemGenerator::generateTextNode(const TextNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ QQuickItem *alignItem = nullptr;
+ QQuickText *textItem = nullptr;
+
+ QQuickItem *containerItem = new QQuickItem(currentItem());
+ addCurrentItem(containerItem, info);
+
+ generateNodeBase(info);
+
+ if (!info.isTextArea) {
+ alignItem = new QQuickItem(currentItem());
+ alignItem->setX(info.position.x());
+ alignItem->setY(info.position.y());
+ }
+
+ textItem = new QQuickText(containerItem);
+ addCurrentItem(textItem, info);
+
+ if (info.isTextArea) {
+ textItem->setX(info.position.x());
+ textItem->setY(info.position.y());
+ if (info.size.width() > 0)
+ textItem->setWidth(info.size.width());
+ if (info.size.height() > 0)
+ 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());
+
+ switch (info.alignment) {
+ case Qt::AlignHCenter:
+ anchors->setHorizontalCenter(alignPrivate->left());
+ break;
+ case Qt::AlignRight:
+ anchors->setRight(alignPrivate->left());
+ break;
+ default:
+ qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
+ Q_FALLTHROUGH();
+ case Qt::AlignLeft:
+ anchors->setLeft(alignPrivate->left());
+ break;
+ }
+ }
+
+ textItem->setColor(info.fillColor);
+ textItem->setTextFormat(info.needsRichText ? QQuickText::RichText : QQuickText::StyledText);
+ textItem->setText(info.text);
+ textItem->setFont(info.font);
+
+ if (info.strokeColor != QColorConstants::Transparent) {
+ textItem->setStyleColor(info.strokeColor);
+ textItem->setStyle(QQuickText::Outline);
+ }
+
+ m_items.pop(); m_items.pop();
+}
+
+void QQuickItemGenerator::generateUseNode(const UseNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ if (info.stage == StructureNodeStage::Start) {
+ QQuickItem *item = new QQuickItem();
+ item->setPosition(info.startPos);
+ addCurrentItem(item, info);
+ generateNodeBase(info);
+ } else {
+ m_items.pop();
+ }
+
+}
+
+bool QQuickItemGenerator::generateStructureNode(const StructureNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return false;
+
+ if (info.stage == StructureNodeStage::Start) {
+ if (!info.forceSeparatePaths && info.isPathContainer) {
+ m_inShapeItem = true;
+ auto *shapeItem = new QQuickShape;
+ if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
+ shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
+ m_parentShapeItem = shapeItem;
+ addCurrentItem(shapeItem, info);
+ } else {
+ QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorImageGenerator::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
+ addCurrentItem(item, info);
+ }
+
+ generateNodeBase(info);
+ } else {
+ m_inShapeItem = false;
+ m_parentShapeItem = nullptr;
+ m_items.pop();
+ }
+
+ return true;
+}
+
+bool QQuickItemGenerator::generateRootNode(const StructureNodeInfo &info)
+{
+ if (!isNodeVisible(info)) {
+ QQuickItem *item = new QQuickItem();
+ item->setParentItem(m_parentItem);
+
+ if (info.size.width() > 0)
+ m_parentItem->setImplicitWidth(info.size.width());
+
+ if (info.size.height() > 0)
+ m_parentItem->setImplicitHeight(info.size.height());
+
+ item->setWidth(m_parentItem->implicitWidth());
+ item->setHeight(m_parentItem->implicitHeight());
+
+ return false;
+ }
+
+ if (info.stage == StructureNodeStage::Start) {
+ QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorImageGenerator::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
+ addCurrentItem(item, info);
+ if (info.size.width() > 0)
+ m_parentItem->setImplicitWidth(info.size.width());
+
+ if (info.size.height() > 0)
+ m_parentItem->setImplicitHeight(info.size.height());
+
+ item->setWidth(m_parentItem->implicitWidth());
+ item->setHeight(m_parentItem->implicitHeight());
+ generateNodeBase(info);
+ } else {
+ m_inShapeItem = false;
+ m_parentShapeItem = nullptr;
+ m_items.pop();
+ }
+
+ return true;
+}
+
+QQuickItem *QQuickItemGenerator::currentItem()
+{
+ return m_items.top();
+}
+
+void QQuickItemGenerator::addCurrentItem(QQuickItem *item, const NodeInfo &info)
+{
+ item->setParentItem(currentItem());
+ m_items.push(item);
+ QStringView name = !info.nodeId.isEmpty() ? info.nodeId : info.typeName;
+ item->setObjectName(name);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorimage/generator/qquickitemgenerator_p.h b/src/quickvectorimage/generator/qquickitemgenerator_p.h
new file mode 100644
index 0000000000..e96812ec30
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickitemgenerator_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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_QUICKVECTORIMAGEGENERATOR_EXPORT QQuickItemGenerator : public QQuickGenerator
+{
+public:
+ QQuickItemGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, QQuickItem *parentItem);
+ ~QQuickItemGenerator();
+
+protected:
+ void generateNodeBase(const NodeInfo &info) override;
+ bool generateDefsNode(const NodeInfo &info) override;
+ void generateImageNode(const ImageNodeInfo &info) override;
+ void generatePath(const PathNodeInfo &info) override;
+ void generateNode(const NodeInfo &info) override;
+ void generateTextNode(const TextNodeInfo &info) override;
+ void generateUseNode(const UseNodeInfo &info) override;
+ bool generateStructureNode(const StructureNodeInfo &info) override;
+ bool generateRootNode(const StructureNodeInfo &info) override;
+ void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect) override;
+
+private:
+ void generateGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect);
+ QQuickItem *currentItem();
+ void addCurrentItem(QQuickItem *item, const NodeInfo &info);
+
+ bool m_inShapeItem = false;
+ QQuickShape *m_parentShapeItem = nullptr;
+
+ QStack<QQuickItem *> m_items;
+
+ QQuickItem *m_parentItem = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKITEMGENERATOR_P_H
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h
new file mode 100644
index 0000000000..b36f4a59ab
--- /dev/null
+++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h
@@ -0,0 +1,119 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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;
+ bool isVisible;
+ bool isDisplayed; // TODO: Map to display enum in QtSvg
+};
+
+struct ImageNodeInfo : NodeInfo
+{
+ QImage image;
+ QRectF rect;
+ QString externalFileReference;
+};
+
+struct StrokeStyle
+{
+ Qt::PenCapStyle lineCapStyle = Qt::SquareCap;
+ Qt::PenJoinStyle lineJoinStyle = Qt::MiterJoin;
+ qreal miterLimit = 4;
+ qreal dashOffset = 0;
+ QList<qreal> dashArray;
+ QColor color = QColorConstants::Transparent;
+ qreal width = 1.0;
+
+ static StrokeStyle fromPen(const QPen &p)
+ {
+ StrokeStyle style;
+ style.lineCapStyle = p.capStyle();
+ style.lineJoinStyle = p.joinStyle() == Qt::SvgMiterJoin ? Qt::MiterJoin : p.joinStyle(); //TODO support SvgMiterJoin
+ style.miterLimit = p.miterLimit();
+ style.dashOffset = p.dashOffset();
+ style.dashArray = p.dashPattern();
+ style.width = p.widthF();
+
+ return style;
+ }
+};
+
+struct PathNodeInfo : NodeInfo
+{
+ QPainterPath painterPath;
+ Qt::FillRule fillRule = Qt::FillRule::WindingFill;
+ QColor fillColor;
+ StrokeStyle strokeStyle;
+ QGradient grad;
+ QTransform fillTransform;
+};
+
+struct TextNodeInfo : NodeInfo
+{
+ bool isTextArea;
+ bool needsRichText;
+ QPointF position;
+ QSizeF size;
+ QString text;
+ QFont font;
+ Qt::Alignment alignment;
+ QColor fillColor;
+ QColor strokeColor;
+};
+
+enum class StructureNodeStage
+{
+ Start,
+ End
+};
+
+struct UseNodeInfo : NodeInfo
+{
+ QPointF startPos;
+ StructureNodeStage stage;
+};
+
+struct StructureNodeInfo : NodeInfo
+{
+ StructureNodeStage stage;
+ bool forceSeparatePaths;
+ QRectF viewBox;
+ QSize size;
+ bool isPathContainer;
+};
+
+
+QT_END_NAMESPACE
+
+#endif //QQUICKNODEINFO_P_H
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
new file mode 100644
index 0000000000..bd3e781c32
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -0,0 +1,561 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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>
+#include <QtCore/qdir.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName)
+ : QQuickGenerator(fileName, flags)
+ , outputFileName(outFileName)
+{
+ m_result.open(QIODevice::ReadWrite);
+}
+
+QQuickQmlGenerator::~QQuickQmlGenerator()
+{
+ if (m_generationSucceeded && !outputFileName.isEmpty()) {
+ QFileInfo fileInfo(outputFileName);
+ QDir dir(fileInfo.absolutePath());
+ if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
+ qCWarning(lcQuickVectorImage) << "Failed to create path" << dir.absolutePath();
+ } else {
+ stream().flush(); // Add a final newline and flush the stream to m_result
+ QFile outFile(outputFileName);
+ outFile.open(QIODevice::WriteOnly);
+ outFile.write(m_result.data());
+ outFile.close();
+ }
+ }
+
+ if (lcQuickVectorImage().isDebugEnabled())
+ qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
+}
+
+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(const 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 {
+ stream() << "transform: Matrix4x4 { matrix: ";
+ generateTransform(info.transform);
+ stream(SameLine) << " }";
+ }
+ }
+ if (!info.isDefaultOpacity) {
+ stream() << "opacity: " << info.opacity;
+ }
+ m_indentLevel--;
+}
+
+bool QQuickQmlGenerator::generateDefsNode(const NodeInfo &info)
+{
+ Q_UNUSED(info)
+
+ return false;
+}
+
+void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ const QFileInfo outputFileInfo(outputFileName);
+ const QDir outputDir(outputFileInfo.absolutePath());
+
+ QString filePath;
+
+ if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
+ filePath = m_assetFileDirectory;
+ if (filePath.isEmpty())
+ filePath = outputDir.absolutePath();
+
+ if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
+ filePath += u'/';
+
+ QDir fileDir(filePath);
+ if (!fileDir.exists()) {
+ if (!fileDir.mkpath(QStringLiteral(".")))
+ qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath;
+ }
+
+ filePath += QStringLiteral("%1%2.png").arg(m_assetFilePrefix.isEmpty()
+ ? QStringLiteral("svg_asset_")
+ : m_assetFilePrefix)
+ .arg(info.image.cacheKey());
+
+ if (!info.image.save(filePath))
+ qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath;
+ qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath;
+ } else {
+ filePath = info.externalFileReference;
+ }
+
+ const QFileInfo assetFileInfo(filePath);
+
+ // 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: \"" << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<"\"";
+
+ m_indentLevel--;
+
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generatePath(const PathNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ 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(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
+ stream() << "preferredRendererType: Shape.CurveRenderer";
+ optimizePaths(info);
+ //qCDebug(lcQuickVectorGraphics) << *node->qpath();
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+}
+
+void QQuickQmlGenerator::generateGradient(const QGradient *grad, const QRectF &boundingRect)
+{
+ 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 : QQuickVectorImageGenerator::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() << "}";
+ } 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:" << radGrad->focalPoint().x();
+ stream() << "focalY:" << radGrad->focalPoint().y();
+ for (auto &stop : radGrad->stops())
+ stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
+ m_indentLevel--;
+ stream() << "}";
+ }
+}
+
+void QQuickQmlGenerator::generateTransform(const QTransform &xf)
+{
+ if (xf.isAffine()) {
+ stream(SameLine) << "PlanarTransform.fromAffineMatrix("
+ << xf.m11() << ", " << xf.m12() << ", "
+ << xf.m21() << ", " << xf.m22() << ", "
+ << xf.dx() << ", " << xf.dy() << ")";
+ } else {
+ QMatrix4x4 m(xf);
+ stream(SameLine) << "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];
+ if (i < 3)
+ stream(SameLine) << ", ";
+ }
+ stream(SameLine) << ")";
+ m_indentLevel -= 3;
+ }
+}
+
+void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
+{
+ Q_UNUSED(pathSelector)
+ Q_ASSERT(painterPath || quadPath);
+
+ const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
+ if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
+ return;
+
+ const bool noFill = info.grad.type() == QGradient::NoGradient && info.fillColor == QColorConstants::Transparent;
+
+ if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
+ return;
+
+ auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
+ stream() << "ShapePath {";
+ m_indentLevel++;
+ if (!info.nodeId.isEmpty()) {
+ switch (pathSelector) {
+ case QQuickVectorImageGenerator::FillPath:
+ stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorImageGenerator::StrokePath:
+ stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorImageGenerator::FillAndStroke:
+ stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
+ break;
+ }
+ }
+
+ if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
+ stream() << "strokeColor: \"transparent\"";
+ } else {
+ stream() << "strokeColor: \"" << info.strokeStyle.color.name(QColor::HexArgb) << "\"";
+ stream() << "strokeWidth: " << info.strokeStyle.width;
+ stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
+ stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
+ stream() << "miterLimit: " << info.strokeStyle.miterLimit;
+ if (info.strokeStyle.dashArray.length() != 0) {
+ stream() << "strokeStyle: " << "ShapePath.DashLine";
+ stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
+ stream() << "dashOffset: " << info.strokeStyle.dashOffset;
+ }
+ }
+
+ if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
+ stream() << "fillColor: \"transparent\"";
+ } else if (info.grad.type() != QGradient::NoGradient) {
+ generateGradient(&info.grad, boundingRect);
+ } else {
+ stream() << "fillColor: \"" << info.fillColor.name(QColor::HexArgb) << "\"";
+ }
+
+ if (!info.fillTransform.isIdentity()) {
+ const QTransform &xf = info.fillTransform;
+ stream() << "fillTransform: ";
+ if (info.fillTransform.type() == QTransform::TxTranslate)
+ stream(SameLine) << "PlanarTransform.fromTranslate(" << xf.dx() << ", " << xf.dy() << ")";
+ else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
+ stream(SameLine) << "PlanarTransform.fromScale(" << xf.m11() << ", " << xf.m22() << ")";
+ else
+ generateTransform(xf);
+ }
+
+ if (fillRule == QQuickShapePath::WindingFill)
+ stream() << "fillRule: ShapePath.WindingFill";
+ else
+ stream() << "fillRule: ShapePath.OddEvenFill";
+
+ QString hintStr;
+ if (quadPath)
+ hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
+ if (!hintStr.isEmpty())
+ stream() << hintStr;
+
+
+ QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
+ stream() << "PathSvg { path: \"" << svgPathString << "\" }";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateNode(const NodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ stream() << "// Missing Implementation for SVG Node: " << info.typeName;
+ stream() << "// Adding an empty Item and skipping";
+ stream() << "Item {";
+ generateNodeBase(info);
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ static int counter = 0;
+ stream() << "Item {";
+ generateNodeBase(info);
+ m_indentLevel++;
+
+ 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();
+ if (info.size.width() > 0)
+ stream() << "width: " << info.size.width();
+ if (info.size.height() > 0)
+ 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(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
+ Q_FALLTHROUGH();
+ case Qt::AlignLeft:
+ break;
+ }
+ stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
+ }
+ counter++;
+
+ stream() << "color: \"" << info.fillColor.name(QColor::HexArgb) << "\"";
+ stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText");
+
+ QString s = info.text;
+ s.replace(QLatin1Char('"'), QLatin1String("\\\""));
+ stream() << "text: \"" << s << "\"";
+ 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());
+ if (info.font.italic())
+ stream() << "font.italic: true";
+
+ if (info.strokeColor != QColorConstants::Transparent) {
+ stream() << "styleColor: \"" << info.strokeColor.name(QColor::HexArgb) << "\"";
+ stream() << "style: Text.Outline";
+ }
+
+ m_indentLevel--;
+ stream() << "}";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ if (info.stage == StructureNodeStage::Start) {
+ stream() << "Item {";
+ generateNodeBase(info);
+ m_indentLevel++;
+ stream() << "x: " << info.startPos.x();
+ stream() << "y: " << info.startPos.y();
+ } else {
+ m_indentLevel--;
+ stream() << "}";
+ }
+}
+
+bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return false;
+
+ if (info.stage == StructureNodeStage::Start) {
+ if (!info.forceSeparatePaths && info.isPathContainer) {
+ stream() << shapeName() <<" {";
+ m_indentLevel++;
+ if (m_flags.testFlag(QQuickVectorImageGenerator::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;
+ }
+
+ return true;
+}
+
+bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
+{
+ m_indentLevel = 0;
+ const QStringList comments = m_commentString.split(u'\n');
+
+ if (!isNodeVisible(info)) {
+ if (comments.isEmpty()) {
+ stream() << "// Generated from SVG";
+ } else {
+ for (const auto &comment : comments)
+ stream() << "// " << comment;
+ }
+
+ stream() << "import QtQuick";
+ stream() << "import QtQuick.Shapes" << Qt::endl;
+ 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;
+
+ m_indentLevel--;
+ stream() << "}";
+
+ return false;
+ }
+
+ if (info.stage == StructureNodeStage::Start) {
+ if (comments.isEmpty())
+ stream() << "// Generated from SVG";
+ else
+ for (const auto &comment : comments)
+ stream() << "// " << comment;
+
+ stream() << "import QtQuick";
+ stream() << "import QtQuick.Shapes" << Qt::endl;
+ 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()) {
+ 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() << "]";;
+ }
+
+ generateNodeBase(info);
+ } else {
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+
+ return true;
+}
+
+QStringView QQuickQmlGenerator::indent()
+{
+ static QString indentString;
+ int indentWidth = m_indentLevel * 4;
+ if (indentWidth > indentString.size())
+ indentString.fill(QLatin1Char(' '), indentWidth * 2);
+ return QStringView(indentString).first(indentWidth);
+}
+
+QTextStream &QQuickQmlGenerator::stream(int flags)
+{
+ if (m_stream.device() == nullptr)
+ m_stream.setDevice(&m_result);
+ else if (!(flags & StreamFlags::SameLine))
+ m_stream << Qt::endl << indent();
+ return m_stream;
+}
+
+const char *QQuickQmlGenerator::shapeName() const
+{
+ return m_shapeTypeName.isEmpty() ? "Shape" : m_shapeTypeName.constData();
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator_p.h b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
new file mode 100644
index 0000000000..dae34757b0
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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>
+#include <QtCore/qbuffer.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICKVECTORIMAGEGENERATOR_EXPORT QQuickQmlGenerator : public QQuickGenerator
+{
+public:
+ QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, const QString &outFileName);
+ ~QQuickQmlGenerator();
+
+ void setShapeTypeName(const QString &name);
+ QString shapeTypeName() const;
+
+ void setCommentString(const QString commentString);
+ QString commentString() const;
+
+ void setRetainFilePaths(bool retainFilePaths)
+ {
+ m_retainFilePaths = retainFilePaths;
+ }
+
+ bool retainFilePaths() const
+ {
+ return m_retainFilePaths;
+ }
+
+ void setAssetFileDirectory(const QString &assetFileDirectory)
+ {
+ m_assetFileDirectory = assetFileDirectory;
+ }
+
+ QString assetFileDirectory() const
+ {
+ return m_assetFileDirectory;
+ }
+
+ void setAssetFilePrefix(const QString &assetFilePrefix)
+ {
+ m_assetFilePrefix = assetFilePrefix;
+ }
+
+ QString assetFilePrefix() const
+ {
+ return m_assetFilePrefix;
+ }
+
+protected:
+ void generateNodeBase(const NodeInfo &info) override;
+ bool generateDefsNode(const NodeInfo &info) override;
+ void generateImageNode(const ImageNodeInfo &info) override;
+ void generatePath(const PathNodeInfo &info) override;
+ void generateNode(const NodeInfo &info) override;
+ void generateTextNode(const TextNodeInfo &info) override;
+ void generateUseNode(const UseNodeInfo &info) override;
+ bool generateStructureNode(const StructureNodeInfo &info) override;
+ bool generateRootNode(const StructureNodeInfo &info) override;
+ void outputShapePath(const PathNodeInfo &info, const QPainterPath *path, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect) override;
+
+private:
+ void generateGradient(const QGradient *grad, const QRectF &boundingRect);
+ void generateTransform(const QTransform &xf);
+
+ QStringView indent();
+ enum StreamFlags { NoFlags = 0x0, SameLine = 0x1 };
+ QTextStream &stream(int flags = NoFlags);
+ const char *shapeName() const;
+
+private:
+ int m_indentLevel = 0;
+ QBuffer m_result;
+ QTextStream m_stream;
+ QString outputFileName;
+ bool m_inShapeItem = false;
+ QByteArray m_shapeTypeName;
+ QString m_commentString;
+ bool m_retainFilePaths = false;
+ QString m_assetFileDirectory;
+ QString m_assetFilePrefix;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKQMLGENERATOR_P_H
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
new file mode 100644
index 0000000000..0717aa21e9
--- /dev/null
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
@@ -0,0 +1,1094 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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 <QTextDocument>
+#include <QTextLayout>
+#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 <QtCore/private/qstringiterator_p.h>
+
+#include "utils_p.h"
+#include <QtCore/qloggingcategory.h>
+
+#include <QtSvg/private/qsvgstyle_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+class QSvgStyleResolver
+{
+public:
+ QSvgStyleResolver()
+ {
+ m_dummyImage = QImage(1, 1, QImage::Format_RGB32);
+ m_dummyPainter.begin(&m_dummyImage);
+ QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin);
+ defaultPen.setMiterLimit(4);
+ m_dummyPainter.setPen(defaultPen);
+ m_dummyPainter.setBrush(Qt::black);
+ }
+
+ ~QSvgStyleResolver()
+ {
+ m_dummyPainter.end();
+ }
+
+ QPainter& painter() { return m_dummyPainter; }
+ QSvgExtraStates& states() { return m_svgState; }
+
+ QColor currentFillColor() const
+ {
+ if (m_dummyPainter.brush().style() == Qt::NoBrush ||
+ m_dummyPainter.brush().color() == QColorConstants::Transparent) {
+ return QColor(QColorConstants::Transparent);
+ }
+
+ QColor fillColor;
+ fillColor = m_dummyPainter.brush().color();
+ fillColor.setAlphaF(m_svgState.fillOpacity);
+
+ return fillColor;
+ }
+
+ qreal currentFillOpacity() const
+ {
+ return m_svgState.fillOpacity;
+ }
+
+ const QGradient *currentStrokeGradient() const
+ {
+ QBrush brush = m_dummyPainter.pen().brush();
+ if (brush.style() == Qt::LinearGradientPattern
+ || brush.style() == Qt::RadialGradientPattern
+ || brush.style() == Qt::ConicalGradientPattern) {
+ return brush.gradient();
+ }
+ return nullptr;
+ }
+
+ 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;
+ }
+
+ QTransform currentFillTransform() const
+ {
+ return m_dummyPainter.brush().transform();
+ }
+
+ QColor currentStrokeColor() const
+ {
+ if (m_dummyPainter.pen().brush().style() == Qt::NoBrush ||
+ m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) {
+ return QColor(QColorConstants::Transparent);
+ }
+
+ QColor strokeColor;
+ strokeColor = m_dummyPainter.pen().brush().color();
+ strokeColor.setAlphaF(m_svgState.strokeOpacity);
+
+ return strokeColor;
+ }
+
+ static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
+ {
+ QGradient grad = gradient;
+ QGradientStops stops;
+ for (auto &stop : grad.stops()) {
+ stop.second.setAlphaF(stop.second.alphaF() * opacity);
+ stops.append(stop);
+ }
+
+ grad.setStops(stops);
+
+ return grad;
+ }
+
+ float currentStrokeWidth() const
+ {
+ float penWidth = m_dummyPainter.pen().widthF();
+ return penWidth ? penWidth : 1;
+ }
+
+ QPen currentStroke() const
+ {
+ return m_dummyPainter.pen();
+ }
+
+protected:
+ QPainter m_dummyPainter;
+ QImage m_dummyImage;
+ QSvgExtraStates m_svgState;
+};
+
+Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
+
+namespace {
+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(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
+ break;
+ }
+ }
+ //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
+ return foundPath;
+}
+
+static QString capStyleName(Qt::PenCapStyle style)
+{
+ QString styleName;
+
+ switch (style) {
+ case Qt::SquareCap:
+ styleName = QStringLiteral("squarecap");
+ break;
+ case Qt::FlatCap:
+ styleName = QStringLiteral("flatcap");
+ break;
+ case Qt::RoundCap:
+ styleName = QStringLiteral("roundcap");
+ break;
+ default:
+ break;
+ }
+
+ return styleName;
+}
+
+static QString joinStyleName(Qt::PenJoinStyle style)
+{
+ QString styleName;
+
+ switch (style) {
+ case Qt::MiterJoin:
+ styleName = QStringLiteral("miterjoin");
+ break;
+ case Qt::BevelJoin:
+ styleName = QStringLiteral("beveljoin");
+ break;
+ case Qt::RoundJoin:
+ styleName = QStringLiteral("roundjoin");
+ break;
+ case Qt::SvgMiterJoin:
+ styleName = QStringLiteral("svgmiterjoin");
+ break;
+ default:
+ break;
+ }
+
+ return styleName;
+}
+
+static QString dashArrayString(QList<qreal> dashArray)
+{
+ if (dashArray.isEmpty())
+ return QString();
+
+ QString dashArrayString;
+ QTextStream stream(&dashArrayString);
+
+ for (int i = 0; i < dashArray.length() - 1; i++) {
+ qreal value = dashArray[i];
+ stream << value << ", ";
+ }
+
+ stream << dashArray.last();
+
+ return dashArrayString;
+}
+};
+
+QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator)
+ : m_svgFileName(svgFileName)
+ , m_generator(generator)
+{
+}
+
+bool QSvgVisitorImpl::traverse()
+{
+ if (!m_generator) {
+ qCDebug(lcQuickVectorImage) << "No valid QQuickGenerator is set. Genration will stop";
+ return false;
+ }
+
+ auto *doc = QSvgTinyDocument::load(m_svgFileName);
+ if (!doc) {
+ qCDebug(lcQuickVectorImage) << "Not a valid Svg File : " << m_svgFileName;
+ return false;
+ }
+
+ QSvgVisitor::traverse(doc);
+ return true;
+}
+
+void QSvgVisitorImpl::visitNode(const QSvgNode *node)
+{
+ handleBaseNodeSetup(node);
+
+ NodeInfo 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();
+ info.externalFileReference = node->filename();
+
+ 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);
+}
+
+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)
+{
+ QPainterPath p;
+ p.moveTo(node->line().p1());
+ p.lineTo(node->line().p2());
+ handlePathNode(node, p);
+}
+
+void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
+{
+ QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), true);
+ handlePathNode(node, p);
+}
+
+void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
+{
+ QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), false);
+ handlePathNode(node, p);
+}
+
+QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
+{
+ QString cssDescription;
+ if (gradient->type() == QGradient::LinearGradient) {
+ const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(gradient);
+
+ cssDescription += " -qt-foreground: qlineargradient("_L1;
+ cssDescription += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
+ cssDescription += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
+ cssDescription += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
+ cssDescription += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
+ } else if (gradient->type() == QGradient::RadialGradient) {
+ const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(gradient);
+
+ cssDescription += " -qt-foreground: qradialgradient("_L1;
+ cssDescription += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
+ cssDescription += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
+ cssDescription += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
+ cssDescription += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
+ cssDescription += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
+ } else {
+ const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(gradient);
+
+ cssDescription += " -qt-foreground: qconicalgradient("_L1;
+ cssDescription += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
+ cssDescription += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
+ cssDescription += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
+ }
+
+ const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
+ cssDescription += "coordinatemode:"_L1;
+ cssDescription += coordinateModes.at(int(gradient->coordinateMode()));
+ cssDescription += u',';
+
+ const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
+ cssDescription += "spread:"_L1;
+ cssDescription += spreads.at(int(gradient->spread()));
+
+ for (const QGradientStop &stop : gradient->stops()) {
+ cssDescription += ",stop:"_L1;
+ cssDescription += QString::number(stop.first);
+ cssDescription += u' ';
+ cssDescription += stop.second.name(QColor::HexArgb);
+ }
+
+ cssDescription += ");"_L1;
+
+ return cssDescription;
+}
+
+QString QSvgVisitorImpl::colorCssDescription(QColor color)
+{
+ QString cssDescription;
+ cssDescription += QStringLiteral("rgba(");
+ cssDescription += QString::number(color.red()) + QStringLiteral(",");
+ cssDescription += QString::number(color.green()) + QStringLiteral(",");
+ cssDescription += QString::number(color.blue()) + QStringLiteral(",");
+ cssDescription += QString::number(color.alphaF()) + QStringLiteral(")");
+
+ return cssDescription;
+}
+
+namespace {
+
+ // Simple class for representing the SVG font as a font engine
+ // We use the Proxy font engine type, which is currently unused and does not map to
+ // any specific font engine
+ // (The QSvgFont object must outlive the engine.)
+ class QSvgFontEngine : public QFontEngine
+ {
+ public:
+ QSvgFontEngine(const QSvgFont *font, qreal size);
+
+ QFontEngine *cloneWithSize(qreal size) const override;
+
+ glyph_t glyphIndex(uint ucs4) const override;
+ int stringToCMap(const QChar *str,
+ int len,
+ QGlyphLayout *glyphs,
+ int *nglyphs,
+ ShaperFlags flags) const override;
+
+ void addGlyphsToPath(glyph_t *glyphs,
+ QFixedPoint *positions,
+ int nGlyphs,
+ QPainterPath *path,
+ QTextItem::RenderFlags flags) override;
+
+ glyph_metrics_t boundingBox(glyph_t glyph) override;
+
+ void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
+ QFixed ascent() const override;
+ QFixed capHeight() const override;
+ QFixed descent() const override;
+ QFixed leading() const override;
+ qreal maxCharWidth() const override;
+ qreal minLeftBearing() const override;
+ qreal minRightBearing() const override;
+
+ QFixed emSquareSize() const override;
+
+ private:
+ const QSvgFont *m_font;
+ };
+
+ QSvgFontEngine::QSvgFontEngine(const QSvgFont *font, qreal size)
+ : QFontEngine(Proxy)
+ , m_font(font)
+ {
+ fontDef.pixelSize = size;
+ fontDef.families = QStringList(m_font->m_familyName);
+ }
+
+ QFixed QSvgFontEngine::emSquareSize() const
+ {
+ return QFixed::fromReal(m_font->m_unitsPerEm);
+ }
+
+ glyph_t QSvgFontEngine::glyphIndex(uint ucs4) const
+ {
+ if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(QChar(ushort(ucs4))))
+ return glyph_t(ucs4);
+
+ return 0;
+ }
+
+ int QSvgFontEngine::stringToCMap(const QChar *str,
+ int len,
+ QGlyphLayout *glyphs,
+ int *nglyphs,
+ ShaperFlags flags) const
+ {
+ Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
+ if (*nglyphs < len) {
+ *nglyphs = len;
+ return -1;
+ }
+
+ int ucs4Length = 0;
+ QStringIterator it(str, str + len);
+ while (it.hasNext()) {
+ char32_t ucs4 = it.next();
+ glyph_t index = glyphIndex(ucs4);
+ glyphs->glyphs[ucs4Length++] = index;
+ }
+
+ *nglyphs = ucs4Length;
+ glyphs->numGlyphs = ucs4Length;
+
+ if (!(flags & GlyphIndicesOnly))
+ recalcAdvances(glyphs, flags);
+
+ return *nglyphs;
+ }
+
+ void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs,
+ QFixedPoint *positions,
+ int nGlyphs,
+ QPainterPath *path,
+ QTextItem::RenderFlags flags)
+ {
+ Q_UNUSED(flags);
+ const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
+ for (int i = 0; i < nGlyphs; ++i) {
+ glyph_t index = glyphs[i];
+ if (index > 0) {
+ QPointF position = positions[i].toPointF();
+ QPainterPath glyphPath = m_font->m_glyphs.value(QChar(ushort(index))).m_path;
+
+ QTransform xform;
+ xform.translate(position.x(), position.y());
+ xform.scale(scale, -scale);
+ glyphPath = xform.map(glyphPath);
+ path->addPath(glyphPath);
+ }
+ }
+ }
+
+ glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph)
+ {
+ glyph_metrics_t ret;
+ ret.x = 0; // left bearing
+ ret.y = -ascent();
+ const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
+ const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
+ ret.width = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
+ ret.height = ascent() + descent();
+ return ret;
+ }
+
+ QFontEngine *QSvgFontEngine::cloneWithSize(qreal size) const
+ {
+ QSvgFontEngine *otherEngine = new QSvgFontEngine(m_font, size);
+ return otherEngine;
+ }
+
+ void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags) const
+ {
+ const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
+ for (int i = 0; i < glyphLayout->numGlyphs; i++) {
+ glyph_t glyph = glyphLayout->glyphs[i];
+ const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(QChar(ushort(glyph)));
+ glyphLayout->advances[i] = QFixed::fromReal(svgGlyph.m_horizAdvX * scale);
+ }
+ }
+
+ QFixed QSvgFontEngine::ascent() const
+ {
+ return QFixed::fromReal(fontDef.pixelSize);
+ }
+
+ QFixed QSvgFontEngine::capHeight() const
+ {
+ return ascent();
+ }
+ QFixed QSvgFontEngine::descent() const
+ {
+ return QFixed{};
+ }
+
+ QFixed QSvgFontEngine::leading() const
+ {
+ return QFixed{};
+ }
+
+ qreal QSvgFontEngine::maxCharWidth() const
+ {
+ const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm;
+ return m_font->m_horizAdvX * scale;
+ }
+
+ qreal QSvgFontEngine::minLeftBearing() const
+ {
+ return 0.0;
+ }
+
+ qreal QSvgFontEngine::minRightBearing() const
+ {
+ return 0.0;
+ }
+}
+
+void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
+{
+ handleBaseNodeSetup(node);
+ const bool isTextArea = node->type() == QSvgNode::Textarea;
+
+ QString text;
+ const QSvgFont *svgFont = styleResolver->states().svgFont;
+ bool needsRichText = false;
+ bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve;
+ const QGradient *mainGradient = styleResolver->currentFillGradient();
+
+ QFontEngine *fontEngine = nullptr;
+ if (svgFont != nullptr) {
+ fontEngine = new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize());
+ fontEngine->ref.ref();
+ }
+
+#if QT_CONFIG(texthtmlparser)
+ bool needsPathNode = mainGradient != nullptr
+ || svgFont != nullptr
+ || styleResolver->currentStrokeGradient() != nullptr;
+#endif
+ for (const auto *tspan : node->tspans()) {
+ if (!tspan) {
+ text += QStringLiteral("<br>");
+ continue;
+ }
+
+ // Note: We cannot get the font directly from the style, since this does
+ // not apply the weight, since this is relative and depends on current state.
+ handleBaseNodeSetup(tspan);
+ QFont font = styleResolver->painter().font();
+
+ QString styleTagContent;
+
+ if ((font.resolveMask() & QFont::FamilyResolved)
+ || (font.resolveMask() & QFont::FamiliesResolved)) {
+ styleTagContent += QStringLiteral("font-family: %1;").arg(font.family());
+ }
+
+ if (font.resolveMask() & QFont::WeightResolved
+ && font.weight() != QFont::Normal
+ && font.weight() != QFont::Bold) {
+ styleTagContent += QStringLiteral("font-weight: %1;").arg(int(font.weight()));
+ }
+
+ if (font.resolveMask() & QFont::SizeResolved) {
+ // Pixel size stored as point size in SVG parser
+ styleTagContent += QStringLiteral("font-size: %1px;").arg(int(font.pointSizeF()));
+ }
+
+ if (font.resolveMask() & QFont::CapitalizationResolved
+ && font.capitalization() == QFont::SmallCaps) {
+ styleTagContent += QStringLiteral("font-variant: small-caps;");
+ }
+
+ if (styleResolver->currentFillGradient() != nullptr
+ && styleResolver->currentFillGradient() != mainGradient) {
+ const QGradient grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
+ styleTagContent += gradientCssDescription(&grad) + u';';
+#if QT_CONFIG(texthtmlparser)
+ needsPathNode = true;
+#endif
+ }
+
+ const QColor currentStrokeColor = styleResolver->currentStrokeColor();
+ if (currentStrokeColor.alpha() > 0) {
+ QString strokeColor = colorCssDescription(currentStrokeColor);
+ styleTagContent += QStringLiteral("-qt-stroke-color:%1;").arg(strokeColor);
+ styleTagContent += QStringLiteral("-qt-stroke-width:%1px;").arg(styleResolver->currentStrokeWidth());
+ styleTagContent += QStringLiteral("-qt-stroke-dasharray:%1;").arg(dashArrayString(styleResolver->currentStroke().dashPattern()));
+ styleTagContent += QStringLiteral("-qt-stroke-dashoffset:%1;").arg(styleResolver->currentStroke().dashOffset());
+ styleTagContent += QStringLiteral("-qt-stroke-lineCap:%1;").arg(capStyleName(styleResolver->currentStroke().capStyle()));
+ styleTagContent += QStringLiteral("-qt-stroke-lineJoin:%1;").arg(joinStyleName(styleResolver->currentStroke().joinStyle()));
+ if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin)
+ styleTagContent += QStringLiteral("-qt-stroke-miterlimit:%1;").arg(styleResolver->currentStroke().miterLimit());
+#if QT_CONFIG(texthtmlparser)
+ needsPathNode = true;
+#endif
+ }
+
+ if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace)
+ styleTagContent += QStringLiteral("white-space: pre-wrap;");
+
+ QString content = tspan->text().toHtmlEscaped();
+ content.replace(QLatin1Char('\t'), QLatin1Char(' '));
+ content.replace(QLatin1Char('\n'), QLatin1Char(' '));
+
+ bool fontTag = false;
+ if (!tspan->style().fill.isDefault()) {
+ auto &b = tspan->style().fill->qbrush();
+ qCDebug(lcQuickVectorImage) << "tspan FILL:" << b;
+ if (b.style() != Qt::NoBrush)
+ {
+ if (qFuzzyCompare(b.color().alphaF() + 1.0, 2.0))
+ {
+ QString spanColor = b.color().name();
+ fontTag = !spanColor.isEmpty();
+ if (fontTag)
+ text += QStringLiteral("<font color=\"%1\">").arg(spanColor);
+ } else {
+ QString spanColor = colorCssDescription(b.color());
+ styleTagContent += QStringLiteral("color:%1").arg(spanColor);
+ }
+ }
+ }
+
+ needsRichText = needsRichText || !styleTagContent.isEmpty();
+ if (!styleTagContent.isEmpty())
+ text += QStringLiteral("<span style=\"%1\">").arg(styleTagContent);
+
+ if (font.resolveMask() & QFont::WeightResolved && font.bold())
+ text += QStringLiteral("<b>");
+
+ if (font.resolveMask() & QFont::StyleResolved && font.italic())
+ text += QStringLiteral("<i>");
+
+ if (font.resolveMask() & QFont::CapitalizationResolved) {
+ switch (font.capitalization()) {
+ case QFont::AllLowercase:
+ content = content.toLower();
+ break;
+ case QFont::AllUppercase:
+ content = content.toUpper();
+ break;
+ case QFont::Capitalize:
+ // ### We need to iterate over the string and do the title case conversion,
+ // since this is not part of QString.
+ qCWarning(lcQuickVectorImage) << "Title case not implemented for tspan";
+ break;
+ default:
+ break;
+ }
+ }
+ text += content;
+ if (fontTag)
+ text += QStringLiteral("</font>");
+
+ if (font.resolveMask() & QFont::StyleResolved && font.italic())
+ text += QStringLiteral("</i>");
+
+ if (font.resolveMask() & QFont::WeightResolved && font.bold())
+ text += QStringLiteral("</b>");
+
+ if (!styleTagContent.isEmpty())
+ text += QStringLiteral("</span>");
+
+ handleBaseNodeEnd(tspan);
+ }
+
+ if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() != nullptr))
+ text = QStringLiteral("<span style=\"white-space: pre-wrap\">") + text + QStringLiteral("</span>");
+
+ QFont font = styleResolver->painter().font();
+ if (font.pixelSize() <= 0 && font.pointSize() > 0)
+ font.setPixelSize(font.pointSize()); // Pixel size stored as point size by SVG parser
+
+#if QT_CONFIG(texthtmlparser)
+ if (needsPathNode) {
+ QTextDocument document;
+ document.setHtml(text);
+ if (isTextArea && node->size().width() > 0)
+ document.setTextWidth(node->size().width());
+ document.setDefaultFont(font);
+ document.pageCount(); // Force layout
+
+ QTextBlock block = document.firstBlock();
+ while (block.isValid()) {
+ QTextLayout *lout = block.layout();
+
+ if (lout != nullptr) {
+ // If this block has requested the current SVG font, we override it
+ // (note that this limits the text to one svg font, but this is also the case
+ // in the QPainter at the moment, and needs a more centralized solution in Qt Svg
+ // first)
+ QFont blockFont = block.charFormat().font();
+ if (svgFont != nullptr
+ && blockFont.family() == svgFont->m_familyName) {
+ QRawFont rawFont;
+ QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
+ rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize()));
+
+ lout->setRawFont(rawFont);
+ }
+
+ auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt) {
+ PathNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ auto fillStyle = node->style().fill;
+ if (fillStyle)
+ info.fillRule = fillStyle->fillRule();
+
+ if (fmt.hasProperty(QTextCharFormat::ForegroundBrush)) {
+ info.fillColor = fmt.foreground().color();
+ if (fmt.foreground().gradient() != nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient)
+ info.grad = *fmt.foreground().gradient();
+ } else {
+ info.fillColor = styleResolver->currentFillColor();
+ }
+
+ info.painterPath = p;
+
+ const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
+ QPen pen;
+ if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
+ pen = fmt.textOutline();
+ if (strokeGradient == nullptr) {
+ info.strokeStyle = StrokeStyle::fromPen(pen);
+ info.strokeStyle.color = pen.color();
+ }
+ } else {
+ pen = styleResolver->currentStroke();
+ if (strokeGradient == nullptr) {
+ info.strokeStyle = StrokeStyle::fromPen(pen);
+ info.strokeStyle.color = styleResolver->currentStrokeColor();
+ }
+ }
+
+ if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr)
+ info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
+
+ info.fillTransform = styleResolver->currentFillTransform();
+
+ m_generator->generatePath(info);
+
+ if (strokeGradient != nullptr) {
+ PathNodeInfo strokeInfo;
+ fillCommonNodeInfo(node, strokeInfo);
+
+ strokeInfo.grad = *strokeGradient;
+
+ QPainterPathStroker stroker(pen);
+ strokeInfo.painterPath = stroker.createStroke(p);
+ m_generator->generatePath(strokeInfo);
+ }
+ };
+
+ qreal baselineOffset = -QFontMetricsF(font).ascent();
+ if (lout->lineCount() > 0 && lout->lineAt(0).isValid())
+ baselineOffset = -lout->lineAt(0).ascent();
+
+ const QPointF baselineTranslation(0.0, baselineOffset);
+ auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) {
+ QList<QPainterPath> paths;
+ for (const QGlyphRun &glyphRun : glyphRuns) {
+ QRawFont font = glyphRun.rawFont();
+ QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
+ QList<QPointF> positions = glyphRun.positions();
+
+ for (qsizetype j = 0; j < glyphIndexes.size(); ++j) {
+ quint32 glyphIndex = glyphIndexes.at(j);
+ const QPointF &pos = positions.at(j);
+
+ QPainterPath p = font.pathForGlyph(glyphIndex);
+ p.translate(pos + node->position() + baselineTranslation);
+ if (styleResolver->states().textAnchor == Qt::AlignHCenter)
+ p.translate(QPointF(-0.5 * width, 0));
+ else if (styleResolver->states().textAnchor == Qt::AlignRight)
+ p.translate(QPointF(-width, 0));
+ paths.append(p);
+ }
+ }
+
+ return paths;
+ };
+
+ QList<QTextLayout::FormatRange> formats = block.textFormats();
+ for (int i = 0; i < formats.size(); ++i) {
+ QTextLayout::FormatRange range = formats.at(i);
+
+ QList<QGlyphRun> glyphRuns = lout->glyphRuns(range.start, range.length);
+ QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth());
+ for (const QPainterPath &path : paths)
+ addPathForFormat(path, range.format);
+ }
+ }
+
+ block = block.next();
+ }
+ } else
+#endif
+ {
+ TextNodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ info.position = node->position();
+ info.size = node->size();
+ info.font = font;
+ info.text = text;
+ info.isTextArea = isTextArea;
+ info.needsRichText = needsRichText;
+ info.fillColor = styleResolver->currentFillColor();
+ info.alignment = styleResolver->states().textAnchor;
+ info.strokeColor = styleResolver->currentStrokeColor();
+
+ m_generator->generateTextNode(info);
+ }
+
+ handleBaseNodeEnd(node);
+
+ if (fontEngine != nullptr) {
+ fontEngine->ref.deref();
+ Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
+ delete fontEngine;
+ }
+}
+
+void QSvgVisitorImpl::visitUseNode(const QSvgUse *node)
+{
+ QSvgNode *link = node->link();
+ if (!link)
+ return;
+
+ handleBaseNodeSetup(node);
+ UseNodeInfo info;
+ fillCommonNodeInfo(node, info);
+
+ info.stage = StructureNodeStage::Start;
+ info.startPos = node->start();
+
+ m_generator->generateUseNode(info);
+
+ QSvgVisitor::traverse(link);
+
+ info.stage = StructureNodeStage::End;
+ m_generator->generateUseNode(info);
+ handleBaseNodeEnd(node);
+}
+
+bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node)
+{
+ Q_UNUSED(node)
+
+ return m_generator->generateDefsNode(NodeInfo{});
+}
+
+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 = StructureNodeStage::Start;
+
+ return m_generator->generateStructureNode(info);
+}
+
+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 = 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 = StructureNodeStage::Start;
+
+ return m_generator->generateRootNode(info);
+}
+
+void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgTinyDocument *node)
+{
+ handleBaseNodeEnd(node);
+ qCDebug(lcQuickVectorImage) << "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 = 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;
+ info.isVisible = node->isVisible();
+ info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
+}
+
+void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
+{
+ qCDebug(lcQuickVectorImage) << "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(lcQuickVectorImage) << "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(lcQuickVectorImage) << "After END" << node << "fill" << styleResolver->currentFillColor()
+ << "stroke" << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth()
+ << node->nodeId();
+}
+
+void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
+{
+ handleBaseNodeSetup(node);
+
+ PathNodeInfo info;
+ fillCommonNodeInfo(node, info);
+ auto fillStyle = node->style().fill;
+ if (fillStyle)
+ info.fillRule = fillStyle->fillRule();
+
+ const QGradient *strokeGradient = styleResolver->currentStrokeGradient();
+
+ info.painterPath = path;
+ info.fillColor = styleResolver->currentFillColor();
+ if (strokeGradient == nullptr) {
+ info.strokeStyle = StrokeStyle::fromPen(styleResolver->currentStroke());
+ info.strokeStyle.color = styleResolver->currentStrokeColor();
+ }
+ if (styleResolver->currentFillGradient() != nullptr)
+ info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
+ info.fillTransform = styleResolver->currentFillTransform();
+
+ m_generator->generatePath(info);
+
+ if (strokeGradient != nullptr) {
+ PathNodeInfo strokeInfo;
+ fillCommonNodeInfo(node, strokeInfo);
+
+ strokeInfo.grad = *strokeGradient;
+
+ QPainterPathStroker stroker(styleResolver->currentStroke());
+ strokeInfo.painterPath = stroker.createStroke(path);
+ m_generator->generatePath(strokeInfo);
+ }
+
+ handleBaseNodeEnd(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
new file mode 100644
index 0000000000..2113510d9b
--- /dev/null
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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);
+ bool 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;
+ void visitUseNode(const QSvgUse *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);
+ void outputShapePath(QPainterPath pathCopy, const PathNodeInfo &info);
+ static QString gradientCssDescription(const QGradient *gradient);
+ static QString colorCssDescription(QColor color);
+
+private:
+ QString m_svgFileName;
+ QQuickGenerator *m_generator;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSVGVISITORIMPL_P_H
diff --git a/src/quickvectorimage/generator/utils_p.h b/src/quickvectorimage/generator/utils_p.h
new file mode 100644
index 0000000000..bb65ee2b69
--- /dev/null
+++ b/src/quickvectorimage/generator/utils_p.h
@@ -0,0 +1,264 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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 QQuickVectorImageGenerator::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, int) {
+ 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;
+}
+
+inline QString strokeCapStyleString(Qt::PenCapStyle strokeCapStyle)
+{
+ QString capStyle;
+ switch (strokeCapStyle) {
+ case Qt::FlatCap:
+ capStyle = QStringLiteral("ShapePath.FlatCap");
+ break;
+ case Qt::SquareCap:
+ capStyle = QStringLiteral("ShapePath.SquareCap");
+ break;
+ case Qt::RoundCap:
+ capStyle = QStringLiteral("ShapePath.RoundCap");
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ return capStyle;
+}
+
+inline QString strokeJoinStyleString(Qt::PenJoinStyle strokeJoinStyle)
+{
+ QString joinStyle;
+ switch (strokeJoinStyle) {
+ case Qt::MiterJoin:
+ joinStyle = QStringLiteral("ShapePath.MiterJoin");
+ break;
+ case Qt::BevelJoin:
+ joinStyle = QStringLiteral("ShapePath.BevelJoin");
+ break;
+ case Qt::RoundJoin:
+ joinStyle = QStringLiteral("ShapePath.RoundJoin");
+ break;
+ default:
+ //TODO: Add support for SvgMiter case
+ Q_UNREACHABLE();
+ break;
+ }
+
+ return joinStyle;
+}
+
+template<typename T>
+inline QString listString(QList<T> list)
+{
+ if (list.isEmpty())
+ return QStringLiteral("[]");
+
+ QString listString;
+ QTextStream stream(&listString);
+ stream << "[";
+
+ if (list.length() > 1) {
+ for (int i = 0; i < list.length() - 1; i++) {
+ T v = list[i];
+ stream << v << ", ";
+ }
+ }
+
+ stream << list.last() << "]";
+ return listString;
+}
+
+}
+
+QT_END_NAMESPACE
+
+#endif // UTILS_P_H