aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHatem ElKharashy <hatem.elkharashy@qt.io>2024-03-25 13:24:51 +0200
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2024-04-25 10:04:21 +0300
commit645e1ee76b56fe351170bd76d54dd08d56bb917f (patch)
treecd3c2deab1232c41b799b379dca05197a05ab218
parentde436b852586ab03f93418ddf55f90082e93ae03 (diff)
VectorImage: support stroke styling for paths
SVG different stroke attibutes can be easily mapped to QQuickShapePath properties. Task-number: QTBUG-121650 Change-Id: Id52f3e7d99a81c84851b7a7645f75fdee1efbaeb Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r--src/quickvectorimage/generator/qquickitemgenerator.cpp16
-rw-r--r--src/quickvectorimage/generator/qquicknodeinfo_p.h15
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp16
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp148
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl_p.h2
-rw-r--r--src/quickvectorimage/generator/utils_p.h64
-rw-r--r--tests/manual/svg/data/styling/stroking_capStyle_shapes_1.svg11
-rw-r--r--tests/manual/svg/data/styling/stroking_capStyle_shapes_2.svg12
-rw-r--r--tests/manual/svg/data/styling/stroking_dash.svg10
-rw-r--r--tests/manual/svg/data/styling/stroking_joinStyle_shapes_1.svg12
10 files changed, 224 insertions, 82 deletions
diff --git a/src/quickvectorimage/generator/qquickitemgenerator.cpp b/src/quickvectorimage/generator/qquickitemgenerator.cpp
index cc46ba943c..121af73130 100644
--- a/src/quickvectorimage/generator/qquickitemgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickitemgenerator.cpp
@@ -123,7 +123,7 @@ void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPaint
Q_UNUSED(pathSelector)
Q_ASSERT(painterPath || quadPath);
- const bool noPen = info.strokeColor == QColorConstants::Transparent;
+ const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
return;
@@ -145,12 +145,18 @@ void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPaint
if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
shapePath->setStrokeColor(Qt::transparent);
} else {
- shapePath->setStrokeColor(info.strokeColor);
- shapePath->setStrokeWidth(info.strokeWidth);
+ shapePath->setStrokeColor(info.strokeStyle.color);
+ shapePath->setStrokeWidth(info.strokeStyle.width);
+ shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
+ shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
+ shapePath->setMiterLimit(info.strokeStyle.miterLimit);
+ if (info.strokeStyle.dashArray.length() != 0) {
+ shapePath->setStrokeStyle(QQuickShapePath::DashLine);
+ shapePath->setDashPattern(info.strokeStyle.dashArray.toVector());
+ shapePath->setDashOffset(info.strokeStyle.dashOffset);
+ }
}
- shapePath->setCapStyle(QQuickShapePath::CapStyle(info.capStyle));
-
if (!(pathSelector & QQuickVectorImageGenerator::FillPath))
shapePath->setFillColor(Qt::transparent);
else if (info.grad.type() != QGradient::NoGradient)
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h
index 5d2d92bc40..b7123f53e0 100644
--- a/src/quickvectorimage/generator/qquicknodeinfo_p.h
+++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h
@@ -44,14 +44,23 @@ struct ImageNodeInfo : NodeInfo
QRectF rect;
};
+struct StrokeStyle
+{
+ Qt::PenCapStyle lineCapStyle = Qt::SquareCap;
+ Qt::PenJoinStyle lineJoinStyle = Qt::MiterJoin;
+ qreal miterLimit = 4;
+ qreal dashOffset = 0;
+ QList<qreal> dashArray;
+ QColor color = QColorConstants::Transparent;
+ qreal width = 1.0;
+};
+
struct PathNodeInfo : NodeInfo
{
QPainterPath painterPath;
Qt::FillRule fillRule = Qt::FillRule::WindingFill;
- Qt::PenCapStyle capStyle = Qt::SquareCap;
- QColor strokeColor;
- qreal strokeWidth;
QColor fillColor;
+ StrokeStyle strokeStyle;
QGradient grad;
};
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
index e10e6166bd..7313109f0d 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -246,7 +246,7 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte
Q_UNUSED(pathSelector)
Q_ASSERT(painterPath || quadPath);
- const bool noPen = info.strokeColor == QColorConstants::Transparent;
+ const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
return;
@@ -275,11 +275,17 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte
if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
stream() << "strokeColor: \"transparent\"";
} else {
- stream() << "strokeColor: \"" << info.strokeColor.name(QColor::HexArgb) << "\"";
- stream() << "strokeWidth: " << info.strokeWidth;
+ 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 (info.capStyle == Qt::FlatCap)
- stream() << "capStyle: ShapePath.FlatCap"; //### TODO Add the rest of the styles, as well as join styles etc.
if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
stream() << "fillColor: \"transparent\"";
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
index c0fae484f7..603c68ea33 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
@@ -34,52 +34,6 @@ using namespace Qt::StringLiterals;
Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage)
-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(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
- break;
- }
- }
- //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
- return foundPath;
-}
-
class QSvgStyleResolver
{
public:
@@ -141,17 +95,6 @@ public:
return strokeColor;
}
- qreal currentStrokeOpacity() const
- {
- return m_svgState.strokeOpacity;
- }
-
- float currentStrokeWidth() const
- {
- float penWidth = m_dummyPainter.pen().widthF();
- return penWidth ? penWidth : 1;
- }
-
static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
{
QGradient grad = gradient;
@@ -166,6 +109,17 @@ public:
return grad;
}
+ float currentStrokeWidth() const
+ {
+ float penWidth = m_dummyPainter.pen().widthF();
+ return penWidth ? penWidth : 1;
+ }
+
+ QPen currentStroke() const
+ {
+ return m_dummyPainter.pen();
+ }
+
protected:
QPainter m_dummyPainter;
QImage m_dummyImage;
@@ -175,6 +129,67 @@ protected:
Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
+namespace {
+inline bool isPathContainer(const QSvgStructureNode *node)
+{
+ bool foundPath = false;
+ for (const auto *child : node->renderers()) {
+ switch (child->type()) {
+ // nodes that shouldn't go inside Shape{}
+ case QSvgNode::Switch:
+ case QSvgNode::Doc:
+ case QSvgNode::Group:
+ case QSvgNode::Animation:
+ case QSvgNode::Use:
+ case QSvgNode::Video:
+ //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
+ return false;
+
+ // nodes that could go inside Shape{}
+ case QSvgNode::Defs:
+ case QSvgNode::Image:
+ case QSvgNode::Textarea:
+ case QSvgNode::Text:
+ case QSvgNode::Tspan:
+ break;
+
+ // nodes that are done as pure ShapePath{}
+ case QSvgNode::Rect:
+ case QSvgNode::Circle:
+ case QSvgNode::Ellipse:
+ case QSvgNode::Line:
+ case QSvgNode::Path:
+ case QSvgNode::Polygon:
+ case QSvgNode::Polyline:
+ if (!child->style().transform.isDefault()) {
+ //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
+ return false;
+ }
+ foundPath = true;
+ break;
+ default:
+ qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
+ break;
+ }
+ }
+ //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
+ return foundPath;
+}
+
+void populateStrokeStyle(StrokeStyle &srokeStyle)
+{
+ QPen p = styleResolver->currentStroke();
+ srokeStyle.lineCapStyle = p.capStyle();
+ srokeStyle.lineJoinStyle = p.joinStyle() == Qt::SvgMiterJoin ? Qt::MiterJoin : p.joinStyle(); //TODO support SvgMiterJoin
+ srokeStyle.miterLimit = p.miterLimit();
+ srokeStyle.dashOffset = p.dashOffset();
+ srokeStyle.dashArray = p.dashPattern();
+ srokeStyle.color = styleResolver->currentStrokeColor();
+ srokeStyle.width = p.widthF();
+}
+
+};
+
QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator)
: m_svgFileName(svgFileName)
, m_generator(generator)
@@ -277,11 +292,10 @@ void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
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);
+ handlePathNode(node, p);
}
void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
@@ -293,7 +307,7 @@ void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
{
QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), false);
- handlePathNode(node, p, Qt::FlatCap);
+ handlePathNode(node, p);
}
QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
@@ -526,11 +540,11 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
info.painterPath = p;
if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
- info.strokeWidth = fmt.textOutline().widthF();
- info.strokeColor = fmt.textOutline().color();
+ info.strokeStyle.width = fmt.textOutline().widthF();
+ info.strokeStyle.color = fmt.textOutline().color();
} else {
- info.strokeColor = styleResolver->currentStrokeColor();
- info.strokeWidth = styleResolver->currentStrokeWidth();
+ info.strokeStyle.color = styleResolver->currentStrokeColor();
+ info.strokeStyle.width = styleResolver->currentStrokeWidth();
}
if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr)
@@ -738,7 +752,7 @@ void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
}
-void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
+void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
{
handleBaseNodeSetup(node);
@@ -749,10 +763,8 @@ void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &p
info.fillRule = fillStyle->fillRule();
info.painterPath = path;
- info.capStyle = capStyle;
info.fillColor = styleResolver->currentFillColor();
- info.strokeColor = styleResolver->currentStrokeColor();
- info.strokeWidth = styleResolver->currentStrokeWidth();
+ populateStrokeStyle(info.strokeStyle);
if (styleResolver->currentFillGradient() != nullptr)
info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
index af631f387d..af8a8d386e 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
@@ -54,7 +54,7 @@ private:
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 handlePathNode(const QSvgNode *node, const QPainterPath &path);
void outputShapePath(QPainterPath pathCopy, const PathNodeInfo &info);
static QString gradientCssDescription(const QGradient *gradient);
static QString colorCssDescription(QColor color);
diff --git a/src/quickvectorimage/generator/utils_p.h b/src/quickvectorimage/generator/utils_p.h
index 9673dbbe65..0c25f3d7d3 100644
--- a/src/quickvectorimage/generator/utils_p.h
+++ b/src/quickvectorimage/generator/utils_p.h
@@ -193,6 +193,70 @@ inline QString toSvgString(const QQuadPath &path)
return svgPathString;
}
+inline QString strokeCapStyleString(Qt::PenCapStyle strokeCapStyle)
+{
+ QString capStyle;
+ switch (strokeCapStyle) {
+ case Qt::FlatCap:
+ capStyle = QStringLiteral("ShapePath.FlatCap");
+ break;
+ case Qt::SquareCap:
+ capStyle = QStringLiteral("ShapePath.SquareCap");
+ break;
+ case Qt::RoundCap:
+ capStyle = QStringLiteral("ShapePath.RoundCap");
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ return capStyle;
+}
+
+inline QString strokeJoinStyleString(Qt::PenJoinStyle strokeJoinStyle)
+{
+ QString joinStyle;
+ switch (strokeJoinStyle) {
+ case Qt::MiterJoin:
+ joinStyle = QStringLiteral("ShapePath.MiterJoin");
+ break;
+ case Qt::BevelJoin:
+ joinStyle = QStringLiteral("ShapePath.BevelJoin");
+ break;
+ case Qt::RoundJoin:
+ joinStyle = QStringLiteral("ShapePath.RoundJoin");
+ break;
+ default:
+ //TODO: Add support for SvgMiter case
+ Q_UNREACHABLE();
+ break;
+ }
+
+ return joinStyle;
+}
+
+template<typename T>
+inline QString listString(QList<T> list)
+{
+ if (list.isEmpty())
+ return QStringLiteral("[]");
+
+ QString l;
+ QTextStream stream(&l);
+ stream << "[";
+
+ if (list.length() > 1) {
+ for (int i = 0; i < list.length() - 1; i++) {
+ T v = list[i];
+ stream << v << ", ";
+ }
+ }
+
+ stream << list.last() << "]";
+ return l;
+}
+
}
QT_END_NAMESPACE
diff --git a/tests/manual/svg/data/styling/stroking_capStyle_shapes_1.svg b/tests/manual/svg/data/styling/stroking_capStyle_shapes_1.svg
new file mode 100644
index 0000000000..9448fcb527
--- /dev/null
+++ b/tests/manual/svg/data/styling/stroking_capStyle_shapes_1.svg
@@ -0,0 +1,11 @@
+<svg width="900" height="600" viewBox="0 0 90 60" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <g id="capTest">
+ <polyline points="10,10 80,10" fill="none" stroke-width="8" stroke="black"/>
+ <polyline points="10,10 80,10" fill="none" stroke-width="0.5" stroke-linecap="butt" stroke="yellow"/>
+ </g>
+ </defs>
+ <use href="#capTest" stroke-linecap="butt"/>
+ <use href="#capTest" y="20" stroke-linecap="round"/>
+ <use href="#capTest" y=" 40" stroke-linecap="square"/>
+</svg>
diff --git a/tests/manual/svg/data/styling/stroking_capStyle_shapes_2.svg b/tests/manual/svg/data/styling/stroking_capStyle_shapes_2.svg
new file mode 100644
index 0000000000..1d52d2d247
--- /dev/null
+++ b/tests/manual/svg/data/styling/stroking_capStyle_shapes_2.svg
@@ -0,0 +1,12 @@
+<svg width="900" height="300" viewBox="0 0 130 40" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <polyline id="letterz" points="10,10 40,10 10,30 40,30" fill="none" stroke-width="2.5" stroke="black"/>
+ <polyline id="highlight" points="10,10 40,10 10,30 40,30" fill="none" stroke-width="0.1" stroke="yellow"/>
+ </defs>
+ <use href="#letterz" stroke-linecap="butt"/>
+ <use href="#highlight"/>
+ <use href="#letterz" x="40" stroke-linecap="round"/>
+ <use href="#highlight" x="40"/>
+ <use href="#letterz" x=" 80" stroke-linecap="square"/>
+ <use href="#highlight" x="80"/>
+</svg>
diff --git a/tests/manual/svg/data/styling/stroking_dash.svg b/tests/manual/svg/data/styling/stroking_dash.svg
new file mode 100644
index 0000000000..5fdefa4cce
--- /dev/null
+++ b/tests/manual/svg/data/styling/stroking_dash.svg
@@ -0,0 +1,10 @@
+<svg width="460" height="200" viewBox="0 0 30 13" xmlns="http://www.w3.org/2000/svg">
+ <line x1="0" y1="1" x2="30" y2="1" stroke="black" stroke-dashoffset="2"/>
+ <line x1="0" y1="3" x2="30" y2="3" stroke="black" stroke-dasharray="4 2"/>
+<!--positive dashoffset pulls the dashes-->
+ <line x1="0" y1="5" x2="30" y2="5" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="2"/>
+ <line x1="0" y1="7" x2="30" y2="7" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="4"/>
+ <!--negative dashoffset pushs the dashes-->
+ <line x1="0" y1="9" x2="30" y2="9" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="-2"/>
+ <line x1="0" y1="11" x2="30" y2="11" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="-4"/>
+</svg>
diff --git a/tests/manual/svg/data/styling/stroking_joinStyle_shapes_1.svg b/tests/manual/svg/data/styling/stroking_joinStyle_shapes_1.svg
new file mode 100644
index 0000000000..1da775a97e
--- /dev/null
+++ b/tests/manual/svg/data/styling/stroking_joinStyle_shapes_1.svg
@@ -0,0 +1,12 @@
+<svg width="600" height="200" viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <g id="capTest">
+ <polyline points="1,8 1,2 7,2" fill="none" stroke-width="1" stroke="black"/>
+ <polyline points="1,8 1,2 7,2" fill="none" stroke-width="0.03" stroke="yellow"/>
+ </g>
+ </defs>
+
+ <use href="#capTest" x="1" stroke-linejoin="bevel"/>
+ <use href="#capTest" x="11" stroke-linejoin="round"/>
+ <use href="#capTest" x="21" stroke-linejoin="miter"/>
+</svg>