summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2022-11-07 14:41:45 +0100
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2022-11-29 10:10:47 +0100
commit77ad7934057b056bdb0c15d15a01ef83190dafaa (patch)
treed7b6e5c609c3eaf52445a8c0e08e47bc9483a867 /tests
parent5019037adf38f7b7fd299949af3f8f46c07e574d (diff)
downloadqtsvg-77ad7934057b056bdb0c15d15a01ef83190dafaa.tar.gz
QSvgGenerator: add support for clip paths
SVG 1.1 allows to specify clipping paths. Before they were silently discarded, but now we can support them. The SVG generator code is very simple at its core -- at *any* state change of the painter, a new <g> tag is emitted with the new state (brush, pen, transform, ...). Clipping is slightly more complicated because: 1) it needs its own element (<clipPath>), which needs to be referenced by a shape/group by using a clip-path attribute (specifying a url). 2) in QPainter clipping happens in the logical coordinates when the clip was set. Then the coordinates may get transformed again, but the drawn shapes still have to honor the original clipping. In SVG, if one specifies both the clip-path and the transform attributes on a shape, the transformation also affects the clip-path (!). This is the 'clipPathUnits' attribute [1], that however doesn't match QPainter semantics. As a workaround: a) store clip paths already transformed (using the transform existing when the clip path got set) b) when clipping is active, emit an untransformed group, clip that group, then open another inner group with the current painter transformation. This ensures that the clip path is unaffected by any further modification of the painter's transform. Add a manual test. [1] https://www.w3.org/TR/SVG11/masking.html#EstablishingANewClippingPath Change-Id: I78161091925dc09c86e35ed042e31cece2618b9d Reviewed-by: Albert Astals Cid <aacid@kde.org> Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/manual/CMakeLists.txt2
-rw-r--r--tests/manual/cliptests/CMakeLists.txt9
-rw-r--r--tests/manual/cliptests/svgcliptest.cpp132
3 files changed, 143 insertions, 0 deletions
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
new file mode 100644
index 0000000..2a050a0
--- /dev/null
+++ b/tests/manual/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(cliptests)
+
diff --git a/tests/manual/cliptests/CMakeLists.txt b/tests/manual/cliptests/CMakeLists.txt
new file mode 100644
index 0000000..0d568d0
--- /dev/null
+++ b/tests/manual/cliptests/CMakeLists.txt
@@ -0,0 +1,9 @@
+qt_internal_add_manual_test(svgcliptest
+ SOURCES
+ svgcliptest.cpp
+ INCLUDE_DIRECTORIES
+ .
+ LIBRARIES
+ Qt::Svg
+)
+
diff --git a/tests/manual/cliptests/svgcliptest.cpp b/tests/manual/cliptests/svgcliptest.cpp
new file mode 100644
index 0000000..b150a18
--- /dev/null
+++ b/tests/manual/cliptests/svgcliptest.cpp
@@ -0,0 +1,132 @@
+// Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QGuiApplication>
+
+#include <QFile>
+
+#include <QSvgGenerator>
+#include <QPainter>
+#include <QBrush>
+#include <QPen>
+#include <QPainterPath>
+#include <QRect>
+#include <QSize>
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ const QStringList arguments = app.arguments();
+ if (arguments.size() < 2) {
+ qWarning("Missing file name");
+ return 0;
+ }
+
+ QFile output(arguments[1]);
+ if (!output.open(QIODevice::WriteOnly))
+ qFatal("Cannot open output file name");
+
+ QSvgGenerator generator(QSvgGenerator::SvgVersion::Svg11);
+ generator.setOutputDevice(&output);
+ generator.setSize(QSize(1000, 500));
+ generator.setViewBox(QRect(0, 0, 1000, 500));
+
+ {
+ QPainter painter(&generator);
+ QFont f = painter.font();
+ f.setPointSize(48);
+ painter.setFont(f);
+
+ {
+ painter.save();
+ // clipped rectangle
+ painter.setClipRect(QRect(100, 100, 250, 200));
+
+ painter.setBrush(QColorConstants::Blue);
+ painter.drawEllipse(QRect(0, 100, 400, 200));
+
+ {
+ painter.save();
+
+ // transformed element within clip
+ painter.setBrush(QColorConstants::Green);
+ painter.translate(300, 150);
+ painter.rotate(45);
+ painter.drawEllipse(QPointF(0, 0), 100, 50);
+
+ painter.restore();
+ }
+
+ painter.drawText(200, 200, "A very long clipped text");
+
+ painter.restore();
+ }
+ {
+ // unclipped
+ painter.setBrush(QColorConstants::Red);
+ painter.drawEllipse(0, 0, 200, 150);
+ }
+ {
+ painter.save();
+
+ // transformed clip (by transforming the painter before setting the clip);
+ painter.translate(500, 0);
+ painter.rotate(45);
+
+ painter.setClipRect(QRect(50, 50, 150, 200));
+
+ painter.setBrush(QColorConstants::Green);
+ QPainterPath path;
+ path.addRect(50, 50, 100, 100);
+ path.moveTo(0, 0);
+ path.cubicTo(300, 0, 150, 150, 300, 300);
+ path.cubicTo(0, 300, 150, 150, 0, 0);
+ painter.drawPath(path);
+
+ painter.setBrush(QColorConstants::Blue);
+ painter.drawEllipse(QPointF(125, 125), 100, 50);
+
+ painter.restore();
+ }
+
+ {
+ painter.save();
+
+ // transformed clip
+ painter.translate(700, 50);
+
+ // clip by path
+ QPainterPath path;
+ path.moveTo(0, 0);
+ path.cubicTo(300, 0, 150, 150, 300, 300);
+ path.cubicTo(0, 300, 150, 150, 0, 0);
+
+ painter.setBrush(QColorConstants::Svg::red);
+ painter.drawPath(path);
+
+ painter.setClipPath(path);
+
+ painter.setBrush(QColorConstants::Svg::purple);
+ painter.drawEllipse(QPointF(150, 50), 150, 50);
+
+ // transform and remove clipping
+ painter.translate(0, 100);
+ painter.setClipping(false);
+ painter.setBrush(QColorConstants::Svg::darkblue);
+ painter.drawEllipse(QPointF(150, 50), 150, 50);
+
+ // transform and clip again
+ painter.translate(0, 100);
+ painter.setClipping(true);
+ painter.setBrush(QColorConstants::Svg::green);
+ painter.drawEllipse(QPointF(150, 50), 150, 50);
+
+
+ painter.restore();
+ }
+
+ }
+
+ output.close();
+}