From 7f23e32441077ad51cd6df055894d10adea03a1f Mon Sep 17 00:00:00 2001 From: Danny Dawson Date: Mon, 20 Mar 2023 12:49:25 -0700 Subject: [PATCH] Add svg rotation transform support (#8170) * Add svg rotation transform support Fixes #8166 - ChatGPT wrote the matrix transformations. I merely fixed type and import errors and verified the code works. * Adding some simple screenshot-based Svg tests * Simplify rotated example svg * Clean up unnecessary lines --- examples/svg/rotated.svg | 23 +++++++++ kivy/graphics/svg.pyx | 22 +++++++-- kivy/tests/test_graphics_svg.py | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 examples/svg/rotated.svg create mode 100644 kivy/tests/test_graphics_svg.py diff --git a/examples/svg/rotated.svg b/examples/svg/rotated.svg new file mode 100644 index 000000000..8c795e368 --- /dev/null +++ b/examples/svg/rotated.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/kivy/graphics/svg.pyx b/kivy/graphics/svg.pyx index 7670087a4..9431ea18f 100644 --- a/kivy/graphics/svg.pyx +++ b/kivy/graphics/svg.pyx @@ -24,6 +24,7 @@ __all__ = ("Svg", ) include "common.pxi" +import math import re cimport cython from xml.etree.cElementTree import parse @@ -229,6 +230,21 @@ cdef class Matrix(object): print("SVG: unknown how to parse: {!r}".format(value)) self.mat[0] = float(a) self.mat[3] = float(b) + elif string.startswith('rotate('): + value = parse_list(string[7:-1]) + angle = float(value[0]) + if len(value) == 3: + cx, cy = map(float, value[1:]) + else: + cx = cy = 0 + cos_a = math.cos(math.radians(angle)) + sin_a = math.sin(math.radians(angle)) + self.mat[0] = cos_a + self.mat[1] = sin_a + self.mat[2] = -sin_a + self.mat[3] = cos_a + self.mat[4] = -cx * cos_a + cy * sin_a + cx + self.mat[5] = -cx * sin_a - cy * cos_a + cy elif string is not None: i = 0 for f in string: @@ -386,7 +402,7 @@ cdef class Svg(RenderContext): :param color the default color to use for Svg elements that specify "currentColor" .. note:: if you want to use SVGs from string, you can parse the source yourself - using `from xml.etree.cElementTree import fromstring` and pass the result to + using `from xml.etree.cElementTree import fromstring` and pass the result to Svg().set_tree(). This will trigger the rendering of the Svg - as an alternative to assigning a filepath to Svg.source. This is also viable to trigger reloading. @@ -526,7 +542,7 @@ cdef class Svg(RenderContext): def set_tree(self, tree): ''' sets the tree used to render the Svg and triggers reloading. - + :param xml.etree.cElementTree tree: the tree parsed from the SVG source .. versionadded:: 2.0.0 @@ -937,7 +953,7 @@ cdef class Svg(RenderContext): x_ = cp * dx + sp * dy y_ = -sp * dx + cp * dy r2 = (((rx * ry)**2 - (rx * y_)**2 - (ry * x_)**2)/ - ((rx * y_)**2 + (ry * x_)**2)) + ((rx * y_)**2 + (ry * x_)**2)) if r2 < 0: r2 = 0 r = sqrt(r2) if large_arc == sweep: diff --git a/kivy/tests/test_graphics_svg.py b/kivy/tests/test_graphics_svg.py new file mode 100644 index 000000000..74243560f --- /dev/null +++ b/kivy/tests/test_graphics_svg.py @@ -0,0 +1,84 @@ +''' +Svg tests +============== + +Testing Svg rendering. +''' + +from kivy.tests.common import GraphicUnitTest + + +SIMPLE_SVG = """ + + + +""" + +SCALE_SVG = """ + + + +""" + +ROTATE_SVG = """ + + + +""" + + +class SvgTest(GraphicUnitTest): + + def test_simple(self): + import xml.etree.ElementTree as ET + from kivy.uix.widget import Widget + from kivy.graphics.svg import Svg + + # create a root widget + wid = Widget() + + # put some graphics instruction on it + with wid.canvas: + svg = Svg() + svg.set_tree(ET.ElementTree(ET.fromstring(SIMPLE_SVG))) + + # render, and capture it directly + self.render(wid) + + def test_scale(self): + import xml.etree.ElementTree as ET + from kivy.uix.widget import Widget + from kivy.graphics.svg import Svg + + # create a root widget + wid = Widget() + + # put some graphics instruction on it + with wid.canvas: + svg = Svg() + svg.set_tree(ET.ElementTree(ET.fromstring(SCALE_SVG))) + + # render, and capture it directly + self.render(wid) + + def test_rotate(self): + import xml.etree.ElementTree as ET + from kivy.uix.widget import Widget + from kivy.graphics.svg import Svg + + # create a root widget + wid = Widget() + + # put some graphics instruction on it + with wid.canvas: + svg = Svg() + svg.set_tree(ET.ElementTree(ET.fromstring(ROTATE_SVG))) + + # render, and capture it directly + self.render(wid)