aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickvectorimage/generator/qsvgvisitorimpl.cpp')
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp455
1 files changed, 455 insertions, 0 deletions
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
new file mode 100644
index 0000000000..ccc597f141
--- /dev/null
+++ b/src/quickvectorimage/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 = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), true);
+ handlePathNode(node, p);
+}
+
+void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
+{
+ QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), false);
+ handlePathNode(node, p, 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