diff options
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/quickvectorgraphics/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickgenerator.cpp | 72 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickgenerator_p.h | 73 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickitemgenerator.cpp | 331 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickitemgenerator_p.h | 56 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquicknodeinfo_p.h | 85 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickqmlgenerator.cpp | 481 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qquickqmlgenerator_p.h | 67 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qsvgvisitorimpl.cpp | 455 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/qsvgvisitorimpl_p.h | 66 | ||||
-rw-r--r-- | src/quickvectorgraphics/generator/utils_p.h | 200 | ||||
-rw-r--r-- | src/quickvectorgraphics/qquickvectorgraphicsglobal_p.h | 43 | ||||
-rw-r--r-- | tools/svgtoqml/CMakeLists.txt | 6 | ||||
-rw-r--r-- | tools/svgtoqml/main.cpp | 56 | ||||
-rw-r--r-- | tools/svgtoqml/qsvgloader.cpp | 1119 | ||||
-rw-r--r-- | tools/svgtoqml/qsvgloader_p.h | 32 |
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 |