aboutsummaryrefslogtreecommitdiff
path: root/svg-flatten
diff options
context:
space:
mode:
Diffstat (limited to 'svg-flatten')
-rw-r--r--svg-flatten/Makefile15
-rw-r--r--svg-flatten/src/test/nopencv_test.cpp (renamed from svg-flatten/src/nopencv_test.cpp)2
-rw-r--r--svg-flatten/src/test/svg_tests.py75
-rw-r--r--svg-flatten/testdata/svg/circles.svg78
-rw-r--r--svg-flatten/testdata/svg/compound_xform.svg102
-rw-r--r--svg-flatten/testdata/svg/empty.svg11
-rw-r--r--svg-flatten/testdata/svg/empty_inkscape.svg54
-rw-r--r--svg-flatten/testdata/svg/groups.svg96
-rw-r--r--svg-flatten/testdata/svg/pattern_fill.svg135
-rw-r--r--svg-flatten/testdata/svg/pattern_stroke.svg89
-rw-r--r--svg-flatten/testdata/svg/pattern_stroke_dashed.svg89
-rw-r--r--svg-flatten/testdata/svg/rect.svg65
-rw-r--r--svg-flatten/testdata/svg/rect_occlusion.svg79
-rw-r--r--svg-flatten/testdata/svg/rotation.svg66
-rw-r--r--svg-flatten/testdata/svg/rotation_90.svg66
-rw-r--r--svg-flatten/testdata/svg/scale.svg91
-rw-r--r--svg-flatten/testdata/svg/shear.svg59
-rw-r--r--svg-flatten/testdata/svg/stroke.svg67
-rw-r--r--svg-flatten/testdata/svg/stroke_caps.svg68
-rw-r--r--svg-flatten/testdata/svg/stroke_dashes.svg75
-rw-r--r--svg-flatten/testdata/svg/stroke_joins.svg68
-rw-r--r--svg-flatten/testdata/svg/text.svg108
22 files changed, 1548 insertions, 10 deletions
diff --git a/svg-flatten/Makefile b/svg-flatten/Makefile
index a980c05..2c9ff6c 100644
--- a/svg-flatten/Makefile
+++ b/svg-flatten/Makefile
@@ -1,5 +1,5 @@
-CXX := clang++
+CXX ?= clang++
LD ?= ld
INSTALL := install
PKG_CONFIG ?= pkg-config
@@ -45,7 +45,7 @@ SOURCES += $(CLIPPER_SOURCES)
INCLUDES := -Iinclude -Isrc $(CLIPPER_INCLUDES) $(VORONOI_INCLUDES) $(POISSON_INCLUDES) $(BASE64_INCLUDES) $(ARGAGG_INCLUDES) $(CAVC_INCLUDES) $(SUBPROCESS_INCLUDES) $(MINUNIT_INCLUDES) $(STB_INCLUDES)
CXXFLAGS := -std=c++2a -g -Wall -Wextra -O0
-LDFLAGS := -lm -lc -lstdc++
+LDFLAGS := -lm
PKG_CONFIG_DEPS :=
ifdef USE_SYSTEM_PUGIXML
@@ -71,19 +71,16 @@ $(BUILDDIR)/%.o: %.cpp
$(BUILDDIR)/$(TARGET): $(SOURCES:%.cpp=$(BUILDDIR)/%.o)
@mkdir -p $(dir $@)
- if [ $$(uname -s) = "Darwin" ]; then \
- $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^; \
- else \
- $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $^ -lstdc++fs -Wl,--end-group; \
- fi
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^
-$(BUILDDIR)/nopencv-tests: src/nopencv_test.cpp src/nopencv.cpp
+$(BUILDDIR)/nopencv-test: src/test/nopencv_test.cpp src/nopencv.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(INCLUDES) $(LDFLAGS) -o $@ $^
.PHONY: tests
-tests: $(BUILDDIR)/nopencv-tests
+tests: $(BUILDDIR)/nopencv-test
+ $(BUILDDIR)/nopencv-test
.PHONY: install
install:
diff --git a/svg-flatten/src/nopencv_test.cpp b/svg-flatten/src/test/nopencv_test.cpp
index 17ba71e..2f358b2 100644
--- a/svg-flatten/src/nopencv_test.cpp
+++ b/svg-flatten/src/test/nopencv_test.cpp
@@ -15,7 +15,7 @@
using namespace gerbolyze;
using namespace gerbolyze::nopencv;
-char msg[1024];
+char msg[512];
class TempfileHack {
public:
diff --git a/svg-flatten/src/test/svg_tests.py b/svg-flatten/src/test/svg_tests.py
new file mode 100644
index 0000000..a5139ec
--- /dev/null
+++ b/svg-flatten/src/test/svg_tests.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+
+import tempfile
+import unittest
+from pathlib import Path
+import subprocess
+import os
+
+from PIL import Image
+import numpy as np
+
+def run_svg_flatten(input_file, output_file, **kwargs):
+ if 'SVG_FLATTEN' in os.environ:
+ svg_flatten = os.environ.get('SVG_FLATTEN')
+ elif (Path(__file__) / '../../build/svg-flatten').is_file():
+ svg_flatten = '../../build/svg-flatten'
+ elif Path('./build/svg-flatten').is_file():
+ svg_flatten = './build/svg-flatten'
+ else:
+ svg_flatten = 'svg-flatten'
+
+ args = [ svg_flatten,
+ *(arg for (key, value) in kwargs.items() for arg in (f'--{key.replace("_", "-")}', value)),
+ str(input_file), str(output_file) ]
+
+ try:
+ proc = subprocess.run(args, capture_output=True, check=True)
+ except:
+ print('Subprocess stdout:')
+ print(proc.stdout)
+ print('Subprocess stderr:')
+ print(proc.stderr)
+ raise
+
+class SVGRoundTripTests(unittest.TestCase):
+
+ def compare_images(self, reference, output, test_name, mean=0.01):
+ ref = np.array(Image.open(reference))
+ out = np.array(Image.open(output))
+ delta = np.abs(out - ref).astype(float) / 255
+
+ #print(f'{test_name}: mean={delta.mean():.5g}')
+
+ self.assertTrue(delta.mean() < mean,
+ f'Expected mean pixel difference between images to be <{mean}, was {delta.mean():.5g}')
+
+ def run_svg_round_trip_test(self, test_in_svg):
+ with tempfile.NamedTemporaryFile(suffix='.svg') as tmp_out_svg,\
+ tempfile.NamedTemporaryFile(suffix='.png') as tmp_out_png,\
+ tempfile.NamedTemporaryFile(suffix='.png') as tmp_in_png:
+
+ run_svg_flatten(test_in_svg, tmp_out_svg.name, format='svg')
+
+ subprocess.run(['resvg', tmp_out_svg.name, tmp_out_png.name], check=True, stdout=subprocess.DEVNULL)
+ subprocess.run(['resvg', test_in_svg, tmp_in_png.name], check=True, stdout=subprocess.DEVNULL)
+
+ try:
+ self.compare_images(tmp_in_png, tmp_out_png, test_in_svg.stem)
+ except AssertionError as e:
+ import shutil
+ shutil.copyfile(tmp_in_png.name, f'/tmp/gerbolyze-fail-{test_in_svg.stem}-in.png')
+ shutil.copyfile(tmp_out_png.name, f'/tmp/gerbolyze-fail-{test_in_svg.stem}-out.png')
+ foo = list(e.args)
+ foo[0] += '\nFailing test renderings copied to:\n'
+ foo[0] += f' /tmp/gerbolyze-fail-{test_in_svg.stem}-{{in|out}}.png\n'
+ e.args = tuple(foo)
+ raise e
+
+for test_in_svg in Path('testdata/svg').glob('*.svg'):
+ # We need to make sure we capture the loop variable's current value here.
+ gen = lambda testcase: lambda self: self.run_svg_round_trip_test(testcase)
+ setattr(SVGRoundTripTests, f'test_{test_in_svg.stem}', gen(test_in_svg))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/svg-flatten/testdata/svg/circles.svg b/svg-flatten/testdata/svg/circles.svg
new file mode 100644
index 0000000..9be474e
--- /dev/null
+++ b/svg-flatten/testdata/svg/circles.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="circles.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.4553"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <circle
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path950"
+ cx="16.593897"
+ cy="20.344759"
+ r="8.5389299" />
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path952"
+ cx="30.070639"
+ cy="28.833015"
+ rx="4.5358982"
+ ry="8.5728388" />
+ <ellipse
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path954"
+ cx="26.473625"
+ cy="24.711111"
+ rx="11.056757"
+ ry="5.5283785" />
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path952-5"
+ cx="33.704639"
+ cy="15.709757"
+ rx="4.5358982"
+ ry="8.5728388" />
+</svg>
diff --git a/svg-flatten/testdata/svg/compound_xform.svg b/svg-flatten/testdata/svg/compound_xform.svg
new file mode 100644
index 0000000..229e05d
--- /dev/null
+++ b/svg-flatten/testdata/svg/compound_xform.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="compound_xform.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="83.428533"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <g
+ id="g1200">
+ <g
+ id="g1190"
+ transform="rotate(-6.212576,25,25)">
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1166"
+ width="36.952793"
+ height="36.952793"
+ x="6.5236034"
+ y="6.5236034" />
+ <rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1166-9"
+ width="32.644421"
+ height="32.644421"
+ x="8.6777897"
+ y="8.6777897" />
+ </g>
+ <g
+ id="g1164">
+ <g
+ id="g1154"
+ transform="matrix(-1,0,0,1,50,0)">
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0307622;stroke-linejoin:round;stop-color:#000000"
+ id="path1130"
+ cx="47.739796"
+ cy="33.794945"
+ rx="14.123164"
+ ry="19.091663"
+ transform="matrix(1,0,-0.67287566,0.7397556,0,0)" />
+ <rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1150"
+ width="22.3258"
+ height="20.708179"
+ x="13.8371"
+ y="14.64591" />
+ </g>
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1158"
+ width="7.5826759"
+ height="7.5826759"
+ x="31.564001"
+ y="-3.791338"
+ transform="rotate(45)" />
+ </g>
+ </g>
+</svg>
diff --git a/svg-flatten/testdata/svg/empty.svg b/svg-flatten/testdata/svg/empty.svg
new file mode 100644
index 0000000..d2cdc52
--- /dev/null
+++ b/svg-flatten/testdata/svg/empty.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8">
+</svg>
diff --git a/svg-flatten/testdata/svg/empty_inkscape.svg b/svg-flatten/testdata/svg/empty_inkscape.svg
new file mode 100644
index 0000000..716379f
--- /dev/null
+++ b/svg-flatten/testdata/svg/empty_inkscape.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
+ sodipodi:docname="empty_inkscape.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.7472574"
+ inkscape:cx="76.820344"
+ inkscape:cy="114.80779"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ inkscape:window-width="1530"
+ inkscape:window-height="669"
+ inkscape:window-x="390"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1" />
+</svg>
diff --git a/svg-flatten/testdata/svg/groups.svg b/svg-flatten/testdata/svg/groups.svg
new file mode 100644
index 0000000..d94dad0
--- /dev/null
+++ b/svg-flatten/testdata/svg/groups.svg
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="groups.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1002"
+ width="5.4755607"
+ height="36.937885"
+ x="22.069475"
+ y="5.679245" />
+ <g
+ id="g998"
+ transform="translate(0.10591836,3.2209219)">
+ <circle
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path950"
+ cx="16.593897"
+ cy="20.344759"
+ r="8.5389299" />
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path952"
+ cx="30.070639"
+ cy="28.833015"
+ rx="4.5358982"
+ ry="8.5728388" />
+ <ellipse
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path954"
+ cx="26.473625"
+ cy="24.711111"
+ rx="11.056757"
+ ry="5.5283785" />
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="path952-5"
+ cx="33.704639"
+ cy="15.709757"
+ rx="4.5358982"
+ ry="8.5728388" />
+ </g>
+ <rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1000"
+ width="31.061703"
+ height="4.7948809"
+ x="16.278164"
+ y="13.511433" />
+</svg>
diff --git a/svg-flatten/testdata/svg/pattern_fill.svg b/svg-flatten/testdata/svg/pattern_fill.svg
new file mode 100644
index 0000000..21789e6
--- /dev/null
+++ b/svg-flatten/testdata/svg/pattern_fill.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="pattern_fill.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <pattern
+ inkscape:collect="always"
+ xlink:href="#Strips2_1"
+ id="pattern3491"
+ patternTransform="matrix(1.774288,0.91540651,-2.3098808,4.4771298,0,1.6295182e-8)" />
+ <pattern
+ inkscape:collect="always"
+ patternUnits="userSpaceOnUse"
+ width="1.5"
+ height="1"
+ patternTransform="translate(0,0) scale(10,10)"
+ id="Strips2_1"
+ inkscape:stockid="Stripes 2:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect2399" />
+ </pattern>
+ <pattern
+ inkscape:collect="always"
+ xlink:href="#Packedcircles"
+ id="pattern3461"
+ patternTransform="matrix(5.9164411,-2.7358919,2.7358917,5.9164404,9.4471894,8.2115447)" />
+ <pattern
+ inkscape:collect="always"
+ patternUnits="userSpaceOnUse"
+ width="1"
+ height="1.73205080756"
+ patternTransform="translate(0,0) scale(10,10)"
+ id="Packedcircles"
+ inkscape:stockid="Packed circles">
+ <circle
+ style="fill:black;stroke:none"
+ cx="0"
+ cy="0.5"
+ r="0.5"
+ id="circle2421" />
+ <circle
+ style="fill:black;stroke:none"
+ cx="1"
+ cy="0.5"
+ r="0.5"
+ id="circle2423" />
+ <circle
+ style="fill:black;stroke:none"
+ cx="0.5"
+ cy="1.36602540378"
+ r="0.5"
+ id="circle2425" />
+ <circle
+ style="fill:black;stroke:none"
+ cx="0.5"
+ cy="-0.366025403784"
+ r="0.5"
+ id="circle2427" />
+ </pattern>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.440884"
+ inkscape:cx="168.05451"
+ inkscape:cy="88.906258"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <circle
+ style="opacity:0.99435;fill:url(#pattern3461);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4.00001, 4.00001;stroke-dashoffset:2.19001;stop-color:#000000"
+ id="path1374"
+ cx="31.476124"
+ cy="30.316647"
+ r="13.079776" />
+ <rect
+ style="opacity:0.99435;fill:url(#pattern3491);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:2.19;stop-color:#000000"
+ id="rect3463"
+ width="23.181004"
+ height="23.181004"
+ x="6.0242319"
+ y="6.351799" />
+ <rect
+ style="opacity:0.99435;fill:#ffffff;stroke:none;stroke-width:1.16072;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4.64288, 4.64288;stroke-dashoffset:2.54198;stop-color:#000000"
+ id="rect3493"
+ width="13.836466"
+ height="59.941151"
+ x="40.70414"
+ y="5.6588583"
+ transform="matrix(1,0,-0.67013266,0.74224134,0,0)" />
+</svg>
diff --git a/svg-flatten/testdata/svg/pattern_stroke.svg b/svg-flatten/testdata/svg/pattern_stroke.svg
new file mode 100644
index 0000000..5564df0
--- /dev/null
+++ b/svg-flatten/testdata/svg/pattern_stroke.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="pattern_stroke.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <pattern
+ inkscape:collect="always"
+ xlink:href="#Checkerboard"
+ id="pattern3520"
+ patternTransform="scale(1.9742409,1.8180095)" />
+ <pattern
+ inkscape:collect="always"
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="2"
+ patternTransform="translate(0,0) scale(10,10)"
+ id="Checkerboard"
+ inkscape:stockid="Checkerboard">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="0"
+ width="1"
+ height="1"
+ id="rect2411" />
+ <rect
+ style="fill:black;stroke:none"
+ x="1"
+ y="1"
+ width="1"
+ height="1"
+ id="rect2413" />
+ </pattern>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.440884"
+ inkscape:cx="168.05451"
+ inkscape:cy="88.906258"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <circle
+ style="opacity:0.99435;fill:none;stroke:url(#pattern3520);stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:10.95;stroke-opacity:1;stop-color:#000000"
+ id="path3513"
+ cx="25"
+ cy="25"
+ r="13.90474" />
+</svg>
diff --git a/svg-flatten/testdata/svg/pattern_stroke_dashed.svg b/svg-flatten/testdata/svg/pattern_stroke_dashed.svg
new file mode 100644
index 0000000..3d8145e
--- /dev/null
+++ b/svg-flatten/testdata/svg/pattern_stroke_dashed.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="pattern_stroke_dashed.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <pattern
+ inkscape:collect="always"
+ xlink:href="#Checkerboard"
+ id="pattern3520"
+ patternTransform="scale(1.9742409,1.8180095)" />
+ <pattern
+ inkscape:collect="always"
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="2"
+ patternTransform="translate(0,0) scale(10,10)"
+ id="Checkerboard"
+ inkscape:stockid="Checkerboard">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="0"
+ width="1"
+ height="1"
+ id="rect2411" />
+ <rect
+ style="fill:black;stroke:none"
+ x="1"
+ y="1"
+ width="1"
+ height="1"
+ id="rect2413" />
+ </pattern>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.440884"
+ inkscape:cx="168.05451"
+ inkscape:cy="88.906258"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <circle
+ style="opacity:0.99435;fill:#000000;stroke:url(#pattern3520);stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6,6;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
+ id="path3513"
+ cx="25"
+ cy="25"
+ r="13.90474" />
+</svg>
diff --git a/svg-flatten/testdata/svg/rect.svg b/svg-flatten/testdata/svg/rect.svg
new file mode 100644
index 0000000..297ff7e
--- /dev/null
+++ b/svg-flatten/testdata/svg/rect.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="rect.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="2.6961137"
+ inkscape:cx="84.54603"
+ inkscape:cy="109.58858"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832"
+ width="20"
+ height="15"
+ x="10"
+ y="15" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832-3"
+ width="20"
+ height="15"
+ x="19.318335"
+ y="22.307203" />
+</svg>
diff --git a/svg-flatten/testdata/svg/rect_occlusion.svg b/svg-flatten/testdata/svg/rect_occlusion.svg
new file mode 100644
index 0000000..ce0e531
--- /dev/null
+++ b/svg-flatten/testdata/svg/rect_occlusion.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="rect_occlusion.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="6.608567"
+ inkscape:cx="109.89929"
+ inkscape:cy="79.489935"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832"
+ width="20"
+ height="15"
+ x="10"
+ y="15" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832-3"
+ width="20"
+ height="15"
+ x="19.318335"
+ y="22.307203" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832-3-6"
+ width="20"
+ height="15"
+ x="19.318335"
+ y="22.307203" />
+ <rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect832-3-6-7"
+ width="20"
+ height="15"
+ x="23.297699"
+ y="13.616298" />
+</svg>
diff --git a/svg-flatten/testdata/svg/rotation.svg b/svg-flatten/testdata/svg/rotation.svg
new file mode 100644
index 0000000..961ef0f
--- /dev/null
+++ b/svg-flatten/testdata/svg/rotation.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="rotation.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022"
+ width="30"
+ height="10"
+ x="10"
+ y="10" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022-3"
+ width="30"
+ height="10"
+ x="-3.3479581"
+ y="-44.595863"
+ transform="rotate(120.85838)" />
+</svg>
diff --git a/svg-flatten/testdata/svg/rotation_90.svg b/svg-flatten/testdata/svg/rotation_90.svg
new file mode 100644
index 0000000..dcca867
--- /dev/null
+++ b/svg-flatten/testdata/svg/rotation_90.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="rotation_90.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022"
+ width="30"
+ height="10"
+ x="10"
+ y="10" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0264583;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022-3"
+ width="30"
+ height="10"
+ x="15"
+ y="-30"
+ transform="rotate(90)" />
+</svg>
diff --git a/svg-flatten/testdata/svg/scale.svg b/svg-flatten/testdata/svg/scale.svg
new file mode 100644
index 0000000..91b28d6
--- /dev/null
+++ b/svg-flatten/testdata/svg/scale.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="scale.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <g
+ id="g1082"
+ transform="matrix(0.81799278,0,0,0.81799278,12.474166,-0.98587389)"
+ style="stroke-width:1.2225">
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0323454;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022"
+ width="30"
+ height="10"
+ x="10"
+ y="10" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0323454;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022-3"
+ width="30"
+ height="10"
+ x="-3.3479581"
+ y="-44.595863"
+ transform="rotate(120.85838)" />
+ </g>
+ <g
+ id="g1082-5"
+ transform="matrix(0.61146823,0,0,0.61146823,-0.17038858,14.878729)"
+ style="stroke-width:1.63541">
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0432701;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022-6"
+ width="30"
+ height="10"
+ x="10"
+ y="10" />
+ <rect
+ style="fill:#000000;stroke:none;stroke-width:0.0432701;stroke-linejoin:round;stop-color:#000000"
+ id="rect1022-3-2"
+ width="30"
+ height="10"
+ x="-3.3479581"
+ y="-44.595863"
+ transform="rotate(120.85838)" />
+ </g>
+</svg>
diff --git a/svg-flatten/testdata/svg/shear.svg b/svg-flatten/testdata/svg/shear.svg
new file mode 100644
index 0000000..ec7875f
--- /dev/null
+++ b/svg-flatten/testdata/svg/shear.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="shear.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="97.954218"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8" />
+ <ellipse
+ style="fill:#000000;stroke:none;stroke-width:0.0307622;stroke-linejoin:round;stop-color:#000000"
+ id="path1130"
+ cx="47.739796"
+ cy="33.794945"
+ rx="14.123164"
+ ry="19.091663"
+ transform="matrix(1,0,-0.67287566,0.7397556,0,0)" />
+</svg>
diff --git a/svg-flatten/testdata/svg/stroke.svg b/svg-flatten/testdata/svg/stroke.svg
new file mode 100644
index 0000000..6a4b334
--- /dev/null
+++ b/svg-flatten/testdata/svg/stroke.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="stroke.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.2278448"
+ inkscape:cx="83.06317"
+ inkscape:cy="83.428533"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:9.99998;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stop-color:#000000"
+ id="rect1220"
+ width="30"
+ height="30.000029"
+ x="10"
+ y="10" />
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stop-color:#000000"
+ id="rect1220-1"
+ width="11"
+ height="11"
+ x="19.5"
+ y="19.5" />
+</svg>
diff --git a/svg-flatten/testdata/svg/stroke_caps.svg b/svg-flatten/testdata/svg/stroke_caps.svg
new file mode 100644
index 0000000..6b89a34
--- /dev/null
+++ b/svg-flatten/testdata/svg/stroke_caps.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="stroke_caps.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.4020648"
+ inkscape:cx="99.189726"
+ inkscape:cy="80.388351"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 8.1209509,7.3978548 20.159949,7.3803896 9.2562029,14.249268 c 19.4757851,0.912458 -2.156778,6.498823 6.5634261,12.04404"
+ id="path1278"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 28.369293,7.3978548 40.408291,7.3803896 29.504545,14.249268 c 19.475785,0.912458 -2.156778,6.498823 6.563426,12.04404"
+ id="path1278-2"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 17.843819,42.603963 12.038998,0.01746 -10.903746,-6.868878 c 19.475785,-0.912458 -2.156778,-6.498823 6.563426,-12.04404"
+ id="path1278-7"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/svg-flatten/testdata/svg/stroke_dashes.svg b/svg-flatten/testdata/svg/stroke_dashes.svg
new file mode 100644
index 0000000..c1d6f5c
--- /dev/null
+++ b/svg-flatten/testdata/svg/stroke_dashes.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="stroke_dashes.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.4020648"
+ inkscape:cx="99.189726"
+ inkscape:cy="80.388351"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:3.00000008;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:3.00000008,3.00000008;stroke-dashoffset:0"
+ d="M 8.1209509,7.3978548 20.159949,7.3803896 9.2562029,14.249268 c 19.4757851,0.912458 -2.156778,6.498823 6.5634261,12.04404"
+ id="path1278"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:2, 1, 0.50000000000000000, 1;stroke-opacity:1;stroke-dashoffset:0;opacity:0.99435036"
+ d="M 28.369293,7.3978548 40.408291,7.3803896 29.504545,14.249268 c 19.475785,0.912458 -2.156778,6.498823 6.563426,12.04404"
+ id="path1278-2"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:0.4,0.4;stroke-opacity:1;stroke-dashoffset:0"
+ d="m 17.843819,42.603963 12.038998,0.01746 -10.903746,-6.868878 c 19.475785,-0.912458 -2.156778,-6.498823 6.563426,-12.04404"
+ id="path1278-7"
+ sodipodi:nodetypes="cccc" />
+ <rect
+ style="opacity:0.99435;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:2.19;stop-color:#000000"
+ id="rect1354"
+ width="43.84502"
+ height="43.932682"
+ x="3.0774899"
+ y="3.033659" />
+</svg>
diff --git a/svg-flatten/testdata/svg/stroke_joins.svg b/svg-flatten/testdata/svg/stroke_joins.svg
new file mode 100644
index 0000000..ee3e1ac
--- /dev/null
+++ b/svg-flatten/testdata/svg/stroke_joins.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="stroke_joins.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.4020648"
+ inkscape:cx="99.189726"
+ inkscape:cy="80.388351"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 8.1209509,7.3978548 20.159949,7.3803896 9.2562029,14.249268 c 19.4757851,0.912458 -2.156778,6.498823 6.5634261,12.04404"
+ id="path1278"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 28.369293,7.3978548 40.408291,7.3803896 29.504545,14.249268 c 19.475785,0.912458 -2.156778,6.498823 6.563426,12.04404"
+ id="path1278-2"
+ sodipodi:nodetypes="cccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 17.843819,42.603963 12.038998,0.01746 -10.903746,-6.868878 c 19.475785,-0.912458 -2.156778,-6.498823 6.563426,-12.04404"
+ id="path1278-7"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/svg-flatten/testdata/svg/text.svg b/svg-flatten/testdata/svg/text.svg
new file mode 100644
index 0000000..bd8c390
--- /dev/null
+++ b/svg-flatten/testdata/svg/text.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50mm"
+ height="50mm"
+ viewBox="0 0 50 50"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="text.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <rect
+ x="3.0026957"
+ y="3.2239678"
+ width="65.46684"
+ height="67.27124"
+ id="rect3560" />
+ <rect
+ x="3.0026958"
+ y="3.2239678"
+ width="65.466843"
+ height="67.27124"
+ id="rect3560-2" />
+ <rect
+ x="3.0026958"
+ y="3.2239678"
+ width="65.466843"
+ height="67.27124"
+ id="rect3573" />
+ <rect
+ x="3.0026958"
+ y="3.2239678"
+ width="65.466843"
+ height="67.27124"
+ id="rect3560-2-4" />
+ <rect
+ x="3.0026958"
+ y="3.2239678"
+ width="65.466843"
+ height="67.27124"
+ id="rect3624" />
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1019"
+ id="namedview3"
+ showgrid="false"
+ inkscape:zoom="3.440884"
+ inkscape:cx="92.105039"
+ inkscape:cy="86.46986"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8"
+ inkscape:document-units="mm"
+ showguides="false" />
+ <text
+ xml:space="preserve"
+ id="text3558"
+ style="font-size:7.05556px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect3560);"
+ transform="translate(4.917252,17.244243)"><tspan
+ x="3.0019531"
+ y="9.5799363"><tspan
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Liberation Sans';-inkscape-font-specification:'Liberation Sans Bold'">Gerbolyze</tspan></tspan></text>
+ <text
+ xml:space="preserve"
+ id="text3558-0"
+ style="font-size:7.05556px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect3560-2);"
+ transform="matrix(1,-0.42912384,0,1,4.8672981,38.568218)"><tspan
+ x="3.0019531"
+ y="9.5799363"><tspan
+ style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Liberation Sans';-inkscape-font-specification:'Liberation Sans Bold'">Gerbolyze</tspan></tspan></text>
+ <text
+ xml:space="preserve"
+ id="text3558-0-1"
+ style="font-size:7.05556px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect3560-2-4);"
+ transform="matrix(1,-0.42912384,0,1,4.8672981,13.281795)"><tspan
+ x="3.0019531"
+ y="9.5799363"><tspan
+ style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Liberation Sans';-inkscape-font-specification:'Liberation Sans Bold'">Gerbolyze</tspan></tspan></text>
+</svg>