diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/tests/test_inkex_elements.py | |
parent | Initial commit. (diff) | |
download | inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.tar.xz inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/tests/test_inkex_elements.py')
-rw-r--r-- | share/extensions/tests/test_inkex_elements.py | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/share/extensions/tests/test_inkex_elements.py b/share/extensions/tests/test_inkex_elements.py new file mode 100644 index 0000000..4844bb4 --- /dev/null +++ b/share/extensions/tests/test_inkex_elements.py @@ -0,0 +1,816 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test specific elements API from svg xml lxml custom classes. +""" + +import pytest +import math +import inkex + +from inkex import ( + Group, + Layer, + Pattern, + Guide, + Page, + Polyline, + Use, + Defs, + TextElement, + TextPath, + Tspan, + FlowPara, + FlowRoot, + FlowRegion, + FlowSpan, + PathElement, + Rectangle, + Circle, + Ellipse, + Anchor, + Line as LineElement, + Transform, + Style, + LinearGradient, + RadialGradient, + Stop, +) +from inkex import paths +from inkex.colors import Color +from inkex.paths import Move, Line +from inkex.utils import FragmentError +from inkex.units import parse_unit + +from .test_inkex_elements_base import SvgTestCase +from inkex.tester.svg import svg + + +class ElementTestCase(SvgTestCase): + """Base element testing""" + + tag = "svg" + + def setUp(self): + super().setUp() + self.elem = self.svg.getElement("//svg:{}".format(self.tag)) + + def test_print(self): + """Print element as string""" + self.assertEqual(str(self.elem), self.tag) + + def assertElement(self, elem, compare): # pylint: disable=invalid-name + """Assert an element""" + self.assertEqual(elem.tostring(), compare) + + +class PathElementTestCase(ElementTestCase): + """Test PathElements""" + + source_file = "with-lpe.svg" + tag = "path" + + def test_new_path(self): + """Test new path element""" + path = PathElement.new(path=[Move(10, 10), Line(20, 20)]) + self.assertEqual(path.get("d"), "M 10 10 L 20 20") + + def test_original_path(self): + """LPE paths can return their original paths""" + lpe = self.svg.getElementById("lpe") + nolpe = self.svg.getElementById("nolpe") + self.assertEqual(str(lpe.path), "M 30 30 L -10 -10 Z") + self.assertEqual(str(lpe.original_path), "M 20 20 L 10 10 Z") + self.assertEqual(str(nolpe.path), "M 30 30 L -10 -10 Z") + self.assertEqual(str(nolpe.original_path), "M 30 30 L -10 -10 Z") + + lpe.original_path = "M 60 60 L 5 5" + self.assertEqual(lpe.get("inkscape:original-d"), "M 60 60 L 5 5") + self.assertEqual(lpe.get("d"), "M 30 30 L -10 -10 Z") + + lpe.path = "M 60 60 L 15 15 Z" + self.assertEqual(lpe.get("d"), "M 60 60 L 15 15 Z") + + nolpe.original_path = "M 60 60 L 5 5" + self.assertEqual(nolpe.get("inkscape:original-d", None), None) + self.assertEqual(nolpe.get("d"), "M 60 60 L 5 5") + + def _compare_paths(self, result, reference, precision=4): + reference = inkex.Path(reference) + result = inkex.Path(result) + self.assertEqual(len(reference), len(result)) + for c1, c2 in zip(reference, result): + self.assertEqual(c1.letter, c2.letter) + self.assertAlmostTuple(c1.args, c2.args, precision=precision, msg=result) + + def test_arc(self): + """Test arc generation""" + + def compare_arc(cx, cy, rx, ry, start, end, reference, type="arc"): + arc = PathElement.arc((cx, cy), rx, ry, start=start, end=end, arctype=type) + self.assertEqual(arc.get("sodipodi:arc-type"), type) + self._compare_paths(arc.get("d"), reference) + + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 6 / 4, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 + 5,5 0 0 1 -2.3296291,-5.624222 5,5 0 0 1 4.8296291,-3.705905""", + ) + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 6 / 4, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 + 5,5 0 0 1 -2.3296291,-5.624222 5,5 0 0 1 4.8296291,-3.705905 z""", + type="chord", + ) + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 28 / 18, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.2830789,0.641905 5,5 0 0 1 -1.8991927,-6.02347 + 5,5 0 0 1 5.5149786,-3.078008 l -0.868241,4.924039 z""", + type="slice", + ) + + def test_stars(self): + def compare_star( + cx, + cy, + sides, + r1, + r2, + arg1, + arg2, + flatsided, + rounded, + reference, + precision=4, + ): + star = PathElement.star( + (cx, cy), (r1, r2), sides, rounded, (arg1, arg2), flatsided + ) + self.assertEqual(star.get("inkscape:flatsided"), str(flatsided).lower()) + self._compare_paths(star.get("d"), reference, precision=precision) + + # Test a simple polygon + compare_star( + 10, + 5, + 6, + 5.5, + 10, + 7 / 8 * math.pi, + 42, + True, + 0, + """m 4.9186625,7.1047587 0.7178942,-5.4529467 5.0813373,-2.10475872 + 4.363443,3.34818802 -0.717894,5.4529467 -5.0813372,2.104759 z""", + ) + # Test a star + compare_star( + 5, + 10, + 7, + 35, + 17, + 0.95545678, + 1.4790556, + False, + 0, + """m 25.203254,38.580212 -18.6458484,-11.651701 -11.3057927,16.6865 + -2.5158293,-21.842628 -20.0950776,1.564637 15.508661,-15.5856099 + -13.752359,-14.7354284 21.8548124,2.4076898 2.94616632,-19.9394165 + 11.74384528,18.5879506 17.426168,-10.1286176 -7.210477,20.7711057 + 18.783909,7.3092373 -20.735163,7.313194 z""", + ) + # Test a rounded polygon + compare_star( + 10, + 5, + 6, + 5.5, + 10, + 7 / 8 * math.pi, + 42, + True, + 0.1, + """m 4.9186625,7.1047587 c -0.2104759,-0.5081337 0.3830754,-5.0166024 0.7178942,-5.4529467 + 0.3348188,-0.4363443 4.5360433,-2.17654814 5.0813373,-2.10475872 + 0.545295,0.0717894 4.152968,2.84005422 4.363443,3.34818802 + 0.210476,0.5081337 -0.383075,5.0166024 -0.717894,5.4529467 + -0.334819,0.4363443 -4.5360425,2.176548 -5.0813372,2.104759 + -0.5452947,-0.07179 -4.1529674,-2.8400545 -4.3634433,-3.3481883 z""", + ) + compare_star( + 5, + 10, + 7, + 35, + 17, + 0.95545678, + 1.4790556, + False, + 1, + """m 25.203254,38.580212 c -18.8526653,11.31401 3.036933,-15.296807 -18.6458484,-11.651701 + -19.8769816,3.341532 7.5786704,23.731873 -11.3057927,16.6865 + -20.6000929,-7.685438 13.8530224,-7.163034 -2.5158293,-21.842628 + -15.0056106,-13.4570388 -13.8291026,20.721824 -20.0950776,1.564637 + -6.835231,-20.8975929 14.237504,6.364651 15.508661,-15.5856099 + 1.165291,-20.1221851 -24.823279,2.1078182 -13.752359,-14.7354284 + 12.076699,-18.3734357 3.900854,15.0996232 21.8548124,2.4076898 + 16.4587046,-11.6349155 -17.1250194,-18.0934175 2.94616632,-19.9394165 + 21.89462928,-2.013706 -9.37321772,12.464272 11.74384528,18.5879506 + 19.358377,5.61368249 3.468728,-24.6699406 17.426168,-10.1286176 + 15.225456,15.86238583 -15.589066,0.44307 -7.210477,20.7711057 + 7.680797,18.6350633 21.450453,-12.6694955 18.783909,7.3092373 + -2.908795,21.793777 -10.066029,-11.91177319 -20.735163,7.313194 + -9.7805797,17.623861 23.27955,8.871339 5.996985,19.243087 z""", + precision=2, + ) + + +class PolylineElementTestCase(ElementTestCase): + """Test the polyline elements support""" + + tag = "polyline" + + def test_type(self): + """Polyline have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Polyline)) + + def test_polyline_points(self): + """Basic tests for points attribute as a path""" + pol = Polyline(points="10,10 50,50 10,15 15,10") + self.assertEqual(str(pol.path), "M 10 10 L 50 50 L 10 15 L 15 10") + pol.path = "M 10 10 L 30 9 L 1 2 C 10 45 3 4 45 60 M 35 35" + self.assertEqual(pol.get("points"), "10,10 30,9 1,2 45,60 35,35") + + +class PolygonElementTestCase(ElementTestCase): + """Test Polygon Elements""" + + tag = "polygon" + + def test_type(self): + """Polygons have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Polygon)) + + def test_conversion(self): + """Polygones are converted to paths""" + pol = inkex.Polygon(points="10,10 50,50 10,15 15,10") + self.assertEqual(str(pol.path), "M 10 10 L 50 50 L 10 15 L 15 10 Z") + + +class LineElementTestCase(ElementTestCase): + """Test Line Elements""" + + tag = "line" + + def test_new_line(self): + """Line creation""" + line = LineElement.new((10, 10), (20, 20)) + self.assertElement(line, b'<line x1="10.0" y1="10.0" x2="20.0" y2="20.0"/>') + + def test_type(self): + """Lines have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Line)) + + def test_conversion(self): + """Lines are converted to paths""" + pol = inkex.elements.Line(x1="2", y1="3", x2="4", y2="5") + self.assertEqual(str(pol.path), "M 2 3 L 4 5") + + +class PatternTestCase(ElementTestCase): + """Test Pattern elements""" + + tag = "pattern" + + def test_pattern_transform(self): + """Patterns have a transformation of their own""" + pattern = Pattern() + self.assertEqual(pattern.patternTransform, Transform()) + pattern.patternTransform.add_translate(10, 10) + self.assertEqual(pattern.get("patternTransform"), "translate(10, 10)") + + +class GroupTest(ElementTestCase): + """Test extra functionality on a group element""" + + tag = "g" + + def test_new_group(self): + """Test creating groups""" + svg = Layer.new("layerA", Group.new("groupA", Rectangle())) + self.assertElement( + svg, + b'<g inkscape:groupmode="layer" inkscape:label="layerA">' + b'<g inkscape:label="groupA"><rect/></g></g>', + ) + + def test_transform_property(self): + """Test getting and setting a transform""" + self.assertEqual( + str(self.elem.transform), "matrix(1.44985 0 0 1.36417 -107.03 -167.362)" + ) + self.elem.transform = "translate(12, 14)" + self.assertEqual(self.elem.transform, Transform("translate(12, 14)")) + self.assertEqual(str(self.elem.transform), "translate(12, 14)") + + def test_groupmode(self): + """Get groupmode is layer""" + self.assertEqual(self.svg.getElementById("A").groupmode, "layer") + self.assertEqual(self.svg.getElementById("C").groupmode, "group") + + def test_get_path(self): + """Group path is combined children""" + print(str(self.svg.getElementById("A").get_path())) + self.assertEqual( + str(self.svg.getElementById("A").get_path()), + "M -108.539 517.61 L -87.6093 496.117 L -98.3066 492.768 L -69.9353 492.301 L -55.5172" + " 506.163 L -66.2146 502.814 L -87.1446 524.307 M 60.0914 498.694 L 156.784 439.145 L" + " 240.218 491.183 L 143.526 550.731 z M -176.909 458.816 a 64.2385 38.9175 -7.86457 1" + " 0 88.3701 -19.0784 a 64.2385 38.9175 -7.86457 0 0 -88.3701 19.0784 z M -300.162" + " 513.715 L -282.488 509.9 Z M -214.583 540.504 L -209.001 448.77 M -193.189 547.201 " + "L -238.536 486.266 L -185.049 503.008 L -230.396 442.073 M -193.189 547.201 L -238.536" + " 486.266 L -185.049 503.008 L -230.396 442.073 Z M 15 15 L 15.5 20", + ) + + def test_bounding_box(self): + """A group returns a bounding box""" + empty = self.svg.add(Group(Group())) + self.assertEqual(empty.bounding_box(), None) + self.assertEqual(int(self.svg.getElementById("A").bounding_box().width), 783) + self.assertEqual(int(self.svg.getElementById("B").bounding_box().height), 114) + + +class RectTest(ElementTestCase): + """Test extra functionality on a rectangle element""" + + tag = "rect" + + def test_parse(self): + """Test Rectangle parsed from XML""" + rect = Rectangle( + attrib={ + "x": "10px", + "y": "20px", + "width": "100px", + "height": "200px", + "rx": "15px", + "ry": "30px", + } + ) + self.assertEqual(rect.left, 10) + self.assertEqual(rect.top, 20) + self.assertEqual(rect.right, 10 + 100) + self.assertEqual(rect.bottom, 20 + 200) + self.assertEqual(rect.width, 100) + self.assertEqual(rect.height, 200) + self.assertEqual(rect.rx, 15) + self.assertEqual(rect.ry, 30) + + def test_compose_transform(self): + """Composed transformation""" + self.assertEqual(self.elem.transform, Transform("rotate(16.097889)")) + self.assertEqual( + str(self.elem.composed_transform()), + "matrix(1.4019 -0.812338 1.20967 0.709877 -542.221 533.431)", + ) + + def test_effetive_stylesheet(self): + """Test the non-parent combination of styles""" + self.assertEqual( + str(self.elem.effective_style()), "fill:#0000ff;stroke-width:1px" + ) + self.assertEqual( + str(self.elem.getparent().effective_style()), + "fill:#0000ff;stroke-width:1px;stroke:#f00", + ) + + def test_compose_stylesheet(self): + """Test finding the composed stylesheet for the shape""" + self.assertEqual(str(self.elem.style), "fill:#0000ff;stroke-width:1px") + self.assertEqual( + str(self.elem.specified_style()), + "fill:#0000ff;stroke-width:1px;stroke:#d88", + ) + + def test_path(self): + """Rectangle path""" + self.assertEqual(self.elem.get_path(), "M 200.0,200.0 h100.0v100.0h-100.0 z") + self.assertEqual(str(self.elem.path), "M 200 200 h 100 v 100 h -100 z") + + +class PathTest(ElementTestCase): + """Test path extra functionality""" + + tag = "path" + + def test_apply_transform(self): + """Transformation can be applied to path""" + path = self.svg.getElementById("D") + path.transform = Transform(translate=(10, 10)) + self.assertEqual( + path.get("d"), "M30,130 L60,130 L60,120 L70,140 L60,160 L60,150 L30,150" + ) + path.apply_transform() + self.assertEqual( + path.get("d"), + "M 40 140 L 70 140 L 70 130 L 80 150 " "L 70 170 L 70 160 L 40 160", + ) + self.assertFalse(path.transform) + + +class CircleTest(ElementTestCase): + """Test extra functionality on a circle element""" + + tag = "circle" + + def test_parse(self): + """Test Circle parsed from XML""" + circle = Circle(attrib={"cx": "10px", "cy": "20px", "r": "30px"}) + self.assertEqual(circle.center.x, 10) + self.assertEqual(circle.center.y, 20) + self.assertEqual(circle.radius, 30) + ellipse = Ellipse( + attrib={"cx": "10px", "cy": "20px", "rx": "30px", "ry": "40px"} + ) + self.assertEqual(ellipse.center.x, 10) + self.assertEqual(ellipse.center.y, 20) + self.assertEqual(ellipse.radius.x, 30) + self.assertEqual(ellipse.radius.y, 40) + + def test_new(self): + """Test new circles""" + elem = Circle.new((10, 10), 50) + self.assertElement(elem, b'<circle cx="10.0" cy="10.0" r="50.0"/>') + elem = Ellipse.new((10, 10), (15, 10)) + self.assertElement(elem, b'<ellipse cx="10.0" cy="10.0" rx="15.0" ry="10.0"/>') + + def test_path(self): + """Circle path""" + self.assertEqual( + self.elem.get_path(), + "M 100.0,50.0 a 50.0,50.0 0 1 0 50.0, " + "50.0 a 50.0,50.0 0 0 0 -50.0, -50.0 z", + ) + + +class AnchorTest(ElementTestCase): + """Test anchor tags""" + + def test_new(self): + """Anchor tag creation""" + link = Anchor.new("https://inkscape.org", Rectangle()) + self.assertElement(link, b'<a xlink:href="https://inkscape.org"><rect/></a>') + + +class NamedViewTest(ElementTestCase): + """Test the sodipodi namedview tag""" + + def test_guides(self): + """Create a guide and see a list of them""" + self.svg.namedview.add(Guide().move_to(0, 0, 0)) + self.svg.namedview.add(Guide().move_to(0, 0, "90")) + self.assertEqual(len(self.svg.namedview.get_guides()), 2) + + def test_guides_coincident(self): + """Test the detection of coincident guides""" + + def coincidence_test(data, result): + guide1 = Guide().move_to(*data[0], angle=data[1]) + guide2 = Guide().move_to(*data[2], angle=data[3]) + self.assertEqual(Guide.guides_coincident(guide1, guide2), result) + + # both are good + coincidence_test(((0, 0), (0, 1), (2, 0), (0, 3)), True) + # antiparallel + coincidence_test(((0, 0), (0, 1), (2, 0), (0, -3)), True) + # point not on guide + coincidence_test(((0, 0), (0, 1), (0, 1), (0, 3)), False) + # different orientation + coincidence_test(((0, 0), (0, 1), (2, 0), (1, 0)), False) + # try the same at an angle + coincidence_test(((1, 1), (2, -1), (3, 5), (-2, 1)), True) + coincidence_test(((1, 1), (2, -1), (3, 6), (-2, 1)), False) + # and vertical + coincidence_test(((0, 1), (1, 0), (0, 2), (3, 0)), True) + coincidence_test(((0, 1), (1, 0), (0, 2), (-3, 0)), True) + coincidence_test(((1, 1), (1, 0), (0, 2), (-3, 0)), False) + + def test_pages(self): + """Create some extra pages and see a list of them""" + self.assertEqual(len(self.svg.namedview.get_pages()), 0) + self.svg.namedview.add(Page(width="210", height="297", x="0", y="0")) + self.svg.namedview.new_page( + x="220", y="0", width="147.5", height="210", label="TEST" + ) + self.assertEqual(len(self.svg.namedview.get_pages()), 2) + self.assertEqual(self.svg.namedview.get_pages()[0].attrib["x"], "0") + self.assertEqual( + self.svg.namedview.get_pages()[1].get("inkscape:label"), "TEST" + ) + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["width"], "147.5") + self.assertEqual(self.svg.namedview.get_pages()[0].attrib["height"], "297") + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["x"], "220") + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["y"], "0") + + def test_center(self): + """Test that the center in mm based documents is correctly computed""" + mmbased = svg(f'width="210mm" viewBox="0 0 210 297"') + mmbased.namedview.set( + "inkscape:cx", 396.57881 + ) # Values of a freshly opened mm document + mmbased.namedview.set("inkscape:cy", 561.81998) + + self.assertAlmostTuple( + mmbased.namedview.center, + [mmbased.viewbox_width / 2, mmbased.viewport_height / 2], + precision=0, + ) + + +class TextTest(ElementTestCase): + """Test all text functions""" + + def test_append_superscript(self): + """Test adding superscript""" + tap = TextPath() + tap.append(Tspan.superscript("th")) + self.assertEqual(len(tap), 1) + + def test_path(self): + """Test getting paths""" + self.assertFalse(TextPath().get_path()) + self.assertFalse(TextElement().get_path()) + self.assertFalse(FlowRegion().get_path()) + self.assertFalse(FlowRoot().get_path()) + self.assertFalse(FlowPara().get_path()) + self.assertFalse(FlowSpan().get_path()) + self.assertFalse(Tspan().get_path()) + + +class UseTest(ElementTestCase): + """Test extra functionality on a use element""" + + tag = "use" + + def test_path(self): + """Use path follows ref""" + self.assertEqual(str(self.elem.path), "M 0 0 L 10 10 Z") + + def test_empty_ref(self): + """An empty ref or None ref doesn't cause an error""" + use = Use() + use.set("xlink:href", "something") + self.assertRaises(FragmentError, getattr, use, "href") + elem = self.svg.add(Use()) + self.assertEqual(elem.href, None) + elem.set("xlink:href", "") + self.assertEqual(elem.href, None) + elem.set("xlink:href", "#badref") + self.assertEqual(elem.href, None) + elem.set("xlink:href", self.elem.get("xlink:href")) + self.assertEqual(elem.href.get("id"), "path1") + + def test_unlink(self): + """Test use tag unlinking""" + elem = self.elem.unlink() + self.assertEqual(str(elem.path), "M 0 0 L 10 10 Z") + self.assertEqual(elem.tag_name, "path") + self.assertEqual(elem.getparent().get("id"), "C") + + def test_unlink_xy(self): + """Check that unlink works if both transform and x, y are set on a clone""" + elem = self.svg.add(Use()) + elem.set("xlink:href", "path1") + elem.set("x", "100") + elem.set("y", "20") + elem.set("transform", "rotate(20,-100,100)") + elem2 = elem.unlink() + self.assertEqual(str(elem2.path), "M 0 0 L 10 10 Z") + self.assertEqual(elem2.tag_name, "path") + self.assertAlmostTuple( + tuple(elem2.transform.to_hexad()), + tuple(inkex.Transform("rotate(20,-206.71282,373.56409)").to_hexad()), + 3, + ) + + +class StopTests(ElementTestCase): + """Color stop tests""" + + black = Color("#000000") + grey50 = Color("#080808") + white = Color("#111111") + + def test_interpolate(self): + """Interpolate colours""" + stl1 = Style({"stop-color": self.black, "stop-opacity": 0.0}) + stop1 = Stop(offset="0.0", style=str(stl1)) + stl2 = Style({"stop-color": self.white, "stop-opacity": 1.0}) + stop2 = Stop(offset="1.0", style=str(stl2)) + stop3 = stop1.interpolate(stop2, 0.5) + assert stop3.style["stop-color"] == str(self.grey50) + assert float(stop3.style["stop-opacity"]) == pytest.approx(0.5, 1e-3) + + +class GradientTests(ElementTestCase): + """Gradient testing""" + + black = Color("#000000") + grey50 = Color("#080808") + white = Color("#111111") + + whiteop1 = Style({"stop-color": white, "stop-opacity": 1.0}) + blackop1 = Style({"stop-color": black, "stop-opacity": 1.0}) + whiteop0 = Style({"stop-color": white, "stop-opacity": 0.0}) + blackop0 = Style({"stop-color": black, "stop-opacity": 0.0}) + + translate11 = Transform("translate(1.0, 1.0)") + translate22 = Transform("translate(2.0, 2.0)") + + def test_parse(self): + """Gradients parsed from XML""" + values = [ + ( + LinearGradient, + {"x1": "0px", "y1": "1px", "x2": "2px", "y2": "3px"}, + {"x1": 0.0, "y1": 1.0, "x2": 2.0, "y2": 3.0}, + ), + ( + RadialGradient, + {"cx": "0px", "cy": "1px", "fx": "2px", "fy": "3px", "r": "4px"}, + {"cx": 0.0, "cy": 1.0, "fx": 2.0, "fy": 3.0}, + ), + ] + for classname, attributes, expected in values: + grad = classname(attrib=attributes) + grad.apply_transform() # identity transform + for key, value in expected.items(): + assert float(grad.get(key)) == pytest.approx(value, 1e-3) + grad = classname(attrib=attributes) + + grad = grad.interpolate(grad, 0.0) + for key, value in expected.items(): + assert float(parse_unit(grad.get(key))[0]) == pytest.approx(value, 1e-3) + + def test_apply_transform(self): + """Transform gradients""" + values = [ + ( + LinearGradient, + {"x1": 0.0, "y1": 0.0, "x2": 1.0, "y2": 1.0}, + {"x1": 1.0, "y1": 1.0, "x2": 2.0, "y2": 2.0}, + ), + ( + RadialGradient, + {"cx": 0.0, "cy": 0.0, "fx": 1.0, "fy": 1.0, "r": 1.0}, + {"cx": 1.0, "cy": 1.0, "fx": 2.0, "fy": 2.0, "r": 1.0}, + ), + ] + for classname, orientation, expected in values: + grad = classname().update(**orientation) + grad.gradientTransform = self.translate11 + grad.apply_transform() + val = grad.get("gradientTransform") + assert val is None + for key, value in expected.items(): + assert float(grad.get(key)) == pytest.approx(value, 1e-3) + + def test_stops(self): + """Gradients have stops""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1), + ] + grad.add(*stops) + assert [s1.tostring() == s2.tostring() for s1, s2 in zip(grad.stops, stops)] + + def test_stop_styles(self): + """Gradients have styles""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1), + ] + grad.add(*stops) + assert [str(s1) == str(s2.style) for s1, s2 in zip(grad.stop_styles, stops)] + + def test_get_stop_offsets(self): + """Gradients stop offsets""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1), + ] + grad.add(*stops) + for stop1, stop2 in zip(grad.stop_offsets, stops): + self.assertEqual(float(stop1), pytest.approx(float(stop2.offset), 1e-3)) + + def test_interpolate(self): + """Gradients can be interpolated""" + values = [ + ( + LinearGradient, + {"x1": 0, "y1": 0, "x2": 1, "y2": 1}, + {"x1": 2, "y1": 2, "x2": 1, "y2": 1}, + {"x1": 1.0, "y1": 1.0, "x2": 1.0, "y2": 1.0}, + ), + ( + RadialGradient, + {"cx": 0, "cy": 0, "fx": 1, "fy": 1, "r": 0}, + {"cx": 2, "cy": 2, "fx": 1, "fy": 1, "r": 1}, + {"cx": 1.0, "cy": 1.0, "fx": 1.0, "fy": 1.0, "r": 0.5}, + ), + ] + for classname, orientation1, orientation2, expected in values: + # gradient 1 + grad1 = classname() + stops1 = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1), + ] + grad1.add(*stops1) + grad1.update(gradientTransform=self.translate11) + grad1.update(**orientation1) + + # gradient 2 + grad2 = classname() + stops2 = [ + Stop().update(offset=0.0, style=self.blackop1), + Stop().update(offset=1.0, style=self.whiteop0), + ] + grad2.add(*stops2) + grad2.update(gradientTransform=self.translate22) + grad2.update(**orientation2) + grad = grad1.interpolate(grad2, 0.5) + comp = Style({"stop-color": self.grey50, "stop-opacity": 0.5}) + self.assertEqual(str(grad.stops[0].style), str(Style(comp))) + self.assertEqual(str(grad.stops[1].style), str(Style(comp))) + self.assertEqual(str(grad.gradientTransform), "translate(1.5, 1.5)") + for key, value in expected.items(): + self.assertEqual( + float(parse_unit(grad.get(key))[0]), pytest.approx(value, 1e-3) + ) + + +class SymbolTest(ElementTestCase): + """Test Symbol elements""" + + source_file = "symbol.svg" + tag = "symbol" + + def test_unlink_symbol(self): + """Test unlink symbols""" + use = self.svg.getElementById("plane01") + self.assertEqual(use.tag_name, "use") + self.assertEqual(use.href.tag_name, "symbol") + # Unlinking should replace symbol with group + elem = use.unlink() + self.assertEqual(elem.tag_name, "g") + self.assertEqual(str(elem.transform), "translate(18, 16)") + self.assertEqual(elem[0].tag_name, "title") + self.assertEqual(elem[1].tag_name, "rect") + + +class DefsTest(ElementTestCase): + """Test the definitions tag""" + + source_file = "shapes.svg" + tag = "defs" + + def test_defs(self): + """Make sure defs can be seen in the nodes of an svg""" + self.assertTrue(isinstance(self.svg.defs, Defs)) + defs = self.svg.getElementById("defs33") + self.assertTrue(isinstance(defs, Defs)) + + +class StyleTest(ElementTestCase): + """Test a style tag""" + + source_file = "css.svg" + tag = "style" + + def test_style(self): + """Make sure style tags can be loaded and saved""" + css = self.svg.stylesheet + self.assertTrue(css) |