aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickvectorimage/generator/qquickqmlgenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickvectorimage/generator/qquickqmlgenerator.cpp')
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp578
1 files changed, 578 insertions, 0 deletions
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
new file mode 100644
index 0000000000..fdb59c7ef4
--- /dev/null
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -0,0 +1,578 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickqmlgenerator_p.h"
+#include "qquicknodeinfo_p.h"
+#include "utils_p.h"
+
+#include <private/qsgcurveprocessor_p.h>
+#include <private/qquickshape_p.h>
+#include <private/qquadpath_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qquickimagebase_p_p.h>
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qdir.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage)
+
+class GeneratorStream
+{
+public:
+ explicit GeneratorStream(QTextStream *result)
+ : m_array(new QByteArray())
+ , m_stream(new QTextStream(m_array, QIODeviceBase::ReadWrite))
+ , m_resultStream(result)
+ {}
+
+ ~GeneratorStream()
+ {
+ if (m_stream) {
+ m_stream->flush();
+ delete m_stream;
+ }
+
+ if (m_resultStream && m_array && !m_array->isEmpty())
+ *m_resultStream << *m_array << Qt::endl;
+
+ delete m_array;
+ }
+
+ GeneratorStream(GeneratorStream &&other) noexcept
+ : m_array(std::exchange(other.m_array, nullptr))
+ , m_stream(std::exchange(other.m_stream, nullptr))
+ , m_resultStream(std::exchange(other.m_resultStream, nullptr))
+ {}
+ GeneratorStream &operator=(GeneratorStream &&other) noexcept
+ {
+ std::swap(m_resultStream, other.m_resultStream);
+ std::swap(m_stream, other.m_stream);
+ std::swap(m_array, other.m_array);
+
+ return *this;
+ }
+
+ Q_DISABLE_COPY(GeneratorStream)
+private:
+ template<typename T>
+ friend const GeneratorStream &operator<<(const GeneratorStream& str, T val);
+ QByteArray *m_array = nullptr;
+ QTextStream *m_stream = nullptr;
+ QTextStream *m_resultStream = nullptr;
+};
+
+template<typename T>
+const GeneratorStream &operator<<(const GeneratorStream& str, T val)
+{
+ *str.m_stream << val;
+ return str;
+}
+
+QQuickQmlGenerator::QQuickQmlGenerator(const QString fileName, QQuickVectorImageGenerator::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 (lcQuickVectorImage().isDebugEnabled()) {
+ result.truncate(300);
+ qCDebug(lcQuickVectorImage).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(const NodeInfo &info)
+{
+ m_indentLevel++;
+ if (!info.nodeId.isEmpty())
+ stream() << "objectName: \"" << info.nodeId << "\"";
+ if (!info.isDefaultTransform) {
+ auto sx = info.transform.m11();
+ auto sy = info.transform.m22();
+ auto x = info.transform.m31();
+ auto y = info.transform.m32();
+ if (info.transform.type() == QTransform::TxTranslate) {
+ stream() << "transform: Translate { " << "x: " << x << "; y: " << y << " }";
+ } else if (info.transform.type() == QTransform::TxScale && !x && !y) {
+ stream() << "transform: Scale { xScale: " << sx << "; yScale: " << sy << " }";
+ } else {
+ const QMatrix4x4 m(info.transform);
+ {
+ 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(const NodeInfo &info)
+{
+ Q_UNUSED(info)
+
+ return false;
+}
+
+void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ const QFileInfo outputFileInfo(outputFileName);
+ const QDir outputDir(outputFileInfo.absolutePath());
+
+ QString filePath;
+
+ if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
+ filePath = m_assetFileDirectory;
+ if (filePath.isEmpty())
+ filePath = outputDir.absolutePath();
+
+ if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
+ filePath += u'/';
+
+ QDir fileDir(filePath);
+ if (!fileDir.exists()) {
+ if (!fileDir.mkpath(QStringLiteral(".")))
+ qCWarning(lcQuickVectorImage) << "Failed to create image resource directory:" << filePath;
+ }
+
+ filePath += QStringLiteral("%1%2.png").arg(m_assetFilePrefix.isEmpty()
+ ? QStringLiteral("svg_asset_")
+ : m_assetFilePrefix)
+ .arg(info.image.cacheKey());
+
+ if (!info.image.save(filePath))
+ qCWarning(lcQuickVectorImage) << "Unabled to save image resource" << filePath;
+ qCDebug(lcQuickVectorImage) << "Saving copy of IMAGE" << filePath;
+ } else {
+ filePath = info.externalFileReference;
+ }
+
+ const QFileInfo assetFileInfo(filePath);
+
+ // TODO: this requires proper asset management.
+ stream() << "Image {";
+ m_indentLevel++;
+
+ generateNodeBase(info);
+ stream() << "x: " << info.rect.x();
+ stream() << "y: " << info.rect.y();
+ stream() << "width: " << info.rect.width();
+ stream() << "height: " << info.rect.height();
+ stream() << "source: \"" << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<"\"";
+
+ m_indentLevel--;
+
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generatePath(const PathNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ if (m_inShapeItem) {
+ if (!info.isDefaultTransform)
+ qWarning() << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
+ optimizePaths(info);
+ } else {
+ m_inShapeItem = true;
+ stream() << shapeName() << " {";
+
+ // Check ??
+ generateNodeBase(info);
+
+ m_indentLevel++;
+ if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
+ stream() << "preferredRendererType: Shape.CurveRenderer";
+ optimizePaths(info);
+ //qCDebug(lcQuickVectorGraphics) << *node->qpath();
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+}
+
+void QQuickQmlGenerator::generateGradient(const QGradient *grad, const QRectF &boundingRect)
+{
+ if (grad->type() == QGradient::LinearGradient) {
+ auto *linGrad = static_cast<const QLinearGradient *>(grad);
+ stream() << "fillGradient: LinearGradient {";
+ m_indentLevel++;
+
+ QRectF gradRect(linGrad->start(), linGrad->finalStop());
+ QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : QQuickVectorImageGenerator::Utils::mapToQtLogicalMode(gradRect, boundingRect);
+
+ stream() << "x1: " << logRect.left();
+ stream() << "y1: " << logRect.top();
+ stream() << "x2: " << logRect.right();
+ stream() << "y2: " << logRect.bottom();
+ for (auto &stop : linGrad->stops())
+ stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
+ m_indentLevel--;
+ stream() << "}";
+ } else if (grad->type() == QGradient::RadialGradient) {
+ auto *radGrad = static_cast<const QRadialGradient*>(grad);
+ stream() << "fillGradient: RadialGradient {";
+ m_indentLevel++;
+
+ stream() << "centerX: " << radGrad->center().x();
+ stream() << "centerY: " << radGrad->center().y();
+ stream() << "centerRadius: " << radGrad->radius();
+ stream() << "focalX:" << radGrad->focalPoint().x();
+ stream() << "focalY:" << radGrad->focalPoint().y();
+ for (auto &stop : radGrad->stops())
+ stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
+ m_indentLevel--;
+ stream() << "}";
+ }
+}
+
+void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
+{
+ Q_UNUSED(pathSelector)
+ Q_ASSERT(painterPath || quadPath);
+
+ const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
+ if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
+ return;
+
+ const bool noFill = info.grad.type() == QGradient::NoGradient && info.fillColor == QColorConstants::Transparent;
+
+ if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
+ return;
+
+ auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
+ stream() << "ShapePath {";
+ m_indentLevel++;
+ if (!info.nodeId.isEmpty()) {
+ switch (pathSelector) {
+ case QQuickVectorImageGenerator::FillPath:
+ stream() << "objectName: \"svg_fill_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorImageGenerator::StrokePath:
+ stream() << "objectName: \"svg_stroke_path:" << info.nodeId << "\"";
+ break;
+ case QQuickVectorImageGenerator::FillAndStroke:
+ stream() << "objectName: \"svg_path:" << info.nodeId << "\"";
+ break;
+ }
+ }
+
+ if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
+ stream() << "strokeColor: \"transparent\"";
+ } else {
+ stream() << "strokeColor: \"" << info.strokeStyle.color.name(QColor::HexArgb) << "\"";
+ stream() << "strokeWidth: " << info.strokeStyle.width;
+ stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
+ stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
+ stream() << "miterLimit: " << info.strokeStyle.miterLimit;
+ if (info.strokeStyle.dashArray.length() != 0) {
+ stream() << "strokeStyle: " << "ShapePath.DashLine";
+ stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
+ stream() << "dashOffset: " << info.strokeStyle.dashOffset;
+ }
+ }
+
+ if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
+ stream() << "fillColor: \"transparent\"";
+ } else if (info.grad.type() != QGradient::NoGradient) {
+ generateGradient(&info.grad, boundingRect);
+ } else {
+ stream() << "fillColor: \"" << info.fillColor.name(QColor::HexArgb) << "\"";
+ }
+ if (fillRule == QQuickShapePath::WindingFill)
+ stream() << "fillRule: ShapePath.WindingFill";
+ else
+ stream() << "fillRule: ShapePath.OddEvenFill";
+
+ QString hintStr;
+ if (quadPath)
+ hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
+ if (!hintStr.isEmpty())
+ stream() << hintStr;
+
+
+ QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
+ stream() << "PathSvg { path: \"" << svgPathString << "\" }";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateNode(const NodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ stream() << "// Missing Implementation for SVG Node: " << info.typeName;
+ stream() << "// Adding an empty Item and skipping";
+ stream() << "Item {";
+ generateNodeBase(info);
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ static int counter = 0;
+ stream() << "Item {";
+ generateNodeBase(info);
+ m_indentLevel++;
+
+ if (!info.isTextArea)
+ stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
+
+ stream() << "Text {";
+
+ m_indentLevel++;
+
+ if (info.isTextArea) {
+ stream() << "x: " << info.position.x();
+ stream() << "y: " << info.position.y();
+ if (info.size.width() > 0)
+ stream() << "width: " << info.size.width();
+ if (info.size.height() > 0)
+ stream() << "height: " << info.size.height();
+ stream() << "wrapMode: Text.Wrap"; // ### WordWrap? verify with SVG standard
+ stream() << "clip: true"; //### Not exactly correct: should clip on the text level, not the pixel level
+ } else {
+ QString hAlign = QStringLiteral("left");
+ stream() << "anchors.baseline: textAlignItem_" << counter << ".top";
+ switch (info.alignment) {
+ case Qt::AlignHCenter:
+ hAlign = QStringLiteral("horizontalCenter");
+ break;
+ case Qt::AlignRight:
+ hAlign = QStringLiteral("right");
+ break;
+ default:
+ qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
+ Q_FALLTHROUGH();
+ case Qt::AlignLeft:
+ break;
+ }
+ stream() << "anchors." << hAlign << ": textAlignItem_" << counter << ".left";
+ }
+ counter++;
+
+ stream() << "color: \"" << info.fillColor.name(QColor::HexArgb) << "\"";
+ stream() << "textFormat:" << (info.needsRichText ? "Text.RichText" : "Text.StyledText");
+
+ QString s = info.text;
+ s.replace(QLatin1Char('"'), QLatin1String("\\\""));
+ stream() << "text: \"" << s << "\"";
+ stream() << "font.family: \"" << info.font.family() << "\"";
+ if (info.font.pixelSize() > 0)
+ stream() << "font.pixelSize:" << info.font.pixelSize();
+ else if (info.font.pointSize() > 0)
+ stream() << "font.pixelSize:" << info.font.pointSizeF();
+ if (info.font.underline())
+ stream() << "font.underline: true";
+ if (info.font.weight() != QFont::Normal)
+ stream() << "font.weight: " << int(info.font.weight());
+ if (info.font.italic())
+ stream() << "font.italic: true";
+
+ if (info.strokeColor != QColorConstants::Transparent) {
+ stream() << "styleColor: \"" << info.strokeColor.name(QColor::HexArgb) << "\"";
+ stream() << "style: Text.Outline";
+ }
+
+ m_indentLevel--;
+ stream() << "}";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
+void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return;
+
+ if (info.stage == StructureNodeStage::Start) {
+ stream() << "Item {";
+ generateNodeBase(info);
+ m_indentLevel++;
+ stream() << "x: " << info.startPos.x();
+ stream() << "y: " << info.startPos.y();
+ } else {
+ m_indentLevel--;
+ stream() << "}";
+ }
+}
+
+bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
+{
+ if (!isNodeVisible(info))
+ return false;
+
+ if (info.stage == StructureNodeStage::Start) {
+ if (!info.forceSeparatePaths && info.isPathContainer) {
+ stream() << shapeName() <<" {";
+ m_indentLevel++;
+ if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
+ stream() << "preferredRendererType: Shape.CurveRenderer";
+ m_indentLevel--;
+
+ m_inShapeItem = true;
+ } else {
+ stream() << "Item {";
+ }
+
+ if (!info.viewBox.isEmpty()) {
+ m_indentLevel++;
+ stream() << "transform: [";
+ m_indentLevel++;
+ bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
+ if (translate)
+ stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
+ stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
+ m_indentLevel--;
+ stream() << "]";
+ m_indentLevel--;
+ }
+
+ generateNodeBase(info);
+ m_indentLevel++;
+ } else {
+ m_indentLevel--;
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+
+ return true;
+}
+
+bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
+{
+ m_indentLevel = 0;
+ const QStringList comments = m_commentString.split(u'\n');
+
+ if (!isNodeVisible(info)) {
+ if (comments.isEmpty()) {
+ stream() << "// Generated from SVG";
+ } else {
+ for (const auto &comment : comments)
+ stream() << "// " << comment;
+ }
+
+ stream() << "import QtQuick";
+ stream() << "import QtQuick.Shapes" << Qt::endl;
+ stream() << "Item {";
+ m_indentLevel++;
+
+ double w = info.size.width();
+ double h = info.size.height();
+ if (w > 0)
+ stream() << "implicitWidth: " << w;
+ if (h > 0)
+ stream() << "implicitHeight: " << h;
+
+ m_indentLevel--;
+ stream() << "}";
+
+ return false;
+ }
+
+ if (info.stage == StructureNodeStage::Start) {
+ if (comments.isEmpty())
+ stream() << "// Generated from SVG";
+ else
+ for (const auto &comment : comments)
+ stream() << "// " << comment;
+
+ stream() << "import QtQuick";
+ stream() << "import QtQuick.Shapes" << Qt::endl;
+ stream() << "Item {";
+ m_indentLevel++;
+
+ double w = info.size.width();
+ double h = info.size.height();
+ if (w > 0)
+ stream() << "implicitWidth: " << w;
+ if (h > 0)
+ stream() << "implicitHeight: " << h;
+
+ if (!info.viewBox.isEmpty()) {
+ stream() << "transform: [";
+ m_indentLevel++;
+ bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
+ if (translate)
+ stream() << "Translate { x: " << -info.viewBox.x() << "; y: " << -info.viewBox.y() << " },";
+ stream() << "Scale { xScale: width / " << info.viewBox.width() << "; yScale: height / " << info.viewBox.height() << " }";
+ m_indentLevel--;
+ stream() << "]";;
+ }
+
+ generateNodeBase(info);
+ } else {
+ stream() << "}";
+ m_inShapeItem = false;
+ }
+
+ return true;
+}
+
+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