2020-07-06 18:13:47 +00:00
|
|
|
"""Tests related to SVG groups.
|
|
|
|
|
|
|
|
To run these tests, you can use (from root svgpathtools directory):
|
|
|
|
$ python -m unittest test.test_groups.TestGroups.test_group_flatten
|
|
|
|
"""
|
2018-08-22 01:00:29 +00:00
|
|
|
from __future__ import division, absolute_import, print_function
|
|
|
|
import unittest
|
2023-05-05 06:18:36 +00:00
|
|
|
from svgpathtools import Document, SVG_NAMESPACE, parse_path, Line, Arc
|
2018-08-22 01:00:29 +00:00
|
|
|
from os.path import join, dirname
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
2023-05-05 06:18:36 +00:00
|
|
|
# When an assert fails, show the full error message, don't truncate it.
|
|
|
|
unittest.util._MAX_LENGTH = 999999999
|
|
|
|
|
|
|
|
|
2018-08-22 01:00:29 +00:00
|
|
|
def get_desired_path(name, paths):
|
2018-08-22 03:54:02 +00:00
|
|
|
return next(p for p in paths
|
|
|
|
if p.element.get('{some://testuri}name') == name)
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestGroups(unittest.TestCase):
|
|
|
|
|
|
|
|
def check_values(self, v, z):
|
2018-08-22 03:54:02 +00:00
|
|
|
# Check that the components of 2D vector v match the components
|
|
|
|
# of complex number z
|
2018-08-22 01:00:29 +00:00
|
|
|
self.assertAlmostEqual(v[0], z.real)
|
|
|
|
self.assertAlmostEqual(v[1], z.imag)
|
|
|
|
|
|
|
|
def check_line(self, tf, v_s_vals, v_e_relative_vals, name, paths):
|
|
|
|
# Check that the endpoints of the line have been correctly transformed.
|
|
|
|
# * tf is the transform that should have been applied.
|
|
|
|
# * v_s_vals is a 2D list of the values of the line's start point
|
2018-08-22 03:54:02 +00:00
|
|
|
# * v_e_relative_vals is a 2D list of the values of the line's
|
|
|
|
# end point relative to the start point
|
|
|
|
# * name is the path name (value of the test:name attribute in
|
|
|
|
# the SVG document)
|
2020-06-24 04:54:58 +00:00
|
|
|
# * paths is the output of doc.paths()
|
2018-08-22 01:00:29 +00:00
|
|
|
v_s_vals.append(1.0)
|
|
|
|
v_e_relative_vals.append(0.0)
|
2018-08-22 03:05:59 +00:00
|
|
|
v_s = np.array(v_s_vals)
|
|
|
|
v_e = v_s + v_e_relative_vals
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
actual = get_desired_path(name, paths)
|
|
|
|
|
2020-07-06 18:13:47 +00:00
|
|
|
self.check_values(tf.dot(v_s), actual.start)
|
|
|
|
self.check_values(tf.dot(v_e), actual.end)
|
2018-08-22 01:00:29 +00:00
|
|
|
|
2023-05-05 06:18:36 +00:00
|
|
|
def test_group_transform(self):
|
|
|
|
# The input svg has a group transform of "scale(1,-1)", which
|
|
|
|
# can mess with Arc sweeps.
|
|
|
|
doc = Document(join(dirname(__file__), 'negative-scale.svg'))
|
|
|
|
path = doc.paths()[0]
|
|
|
|
self.assertEqual(path[0], Line(start=-10j, end=-80j))
|
|
|
|
self.assertEqual(path[1], Arc(start=-80j, radius=(30+30j), rotation=0.0, large_arc=True, sweep=True, end=-140j))
|
|
|
|
self.assertEqual(path[2], Arc(start=-140j, radius=(20+20j), rotation=0.0, large_arc=False, sweep=False, end=-100j))
|
|
|
|
self.assertEqual(path[3], Line(start=-100j, end=(100-100j)))
|
|
|
|
self.assertEqual(path[4], Arc(start=(100-100j), radius=(20+20j), rotation=0.0, large_arc=True, sweep=False, end=(100-140j)))
|
|
|
|
self.assertEqual(path[5], Arc(start=(100-140j), radius=(30+30j), rotation=0.0, large_arc=False, sweep=True, end=(100-80j)))
|
|
|
|
self.assertEqual(path[6], Line(start=(100-80j), end=(100-10j)))
|
|
|
|
self.assertEqual(path[7], Arc(start=(100-10j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=(90+0j)))
|
|
|
|
self.assertEqual(path[8], Line(start=(90+0j), end=(10+0j)))
|
|
|
|
self.assertEqual(path[9], Arc(start=(10+0j), radius=(10+10j), rotation=0.0, large_arc=False, sweep=True, end=-10j))
|
|
|
|
|
2018-08-22 01:00:29 +00:00
|
|
|
def test_group_flatten(self):
|
2020-06-24 04:54:58 +00:00
|
|
|
# Test the Document.paths() function against the
|
2018-08-22 03:54:02 +00:00
|
|
|
# groups.svg test file.
|
|
|
|
# There are 12 paths in that file, with various levels of being
|
|
|
|
# nested inside of group transforms.
|
|
|
|
# The check_line function is used to reduce the boilerplate,
|
|
|
|
# since all the tests are very similar.
|
|
|
|
# This test covers each of the different types of transforms
|
|
|
|
# that are specified by the SVG standard.
|
2018-08-22 01:00:29 +00:00
|
|
|
doc = Document(join(dirname(__file__), 'groups.svg'))
|
|
|
|
|
2020-06-24 04:54:58 +00:00
|
|
|
result = doc.paths()
|
2018-08-22 01:00:29 +00:00
|
|
|
self.assertEqual(12, len(result))
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_matrix_group = np.array([[1.5, 0.0, -40.0],
|
|
|
|
[0.0, 0.5, 20.0],
|
|
|
|
[0.0, 0.0, 1.0]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group,
|
|
|
|
[183, 183], [0.0, -50],
|
|
|
|
'path00', result)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_scale_group = np.array([[1.25, 0.0, 0.0],
|
|
|
|
[0.0, 1.25, 0.0],
|
|
|
|
[0.0, 0.0, 1.0]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_group),
|
|
|
|
[122, 320], [-50.0, 0.0],
|
|
|
|
'path01', result)
|
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_group),
|
|
|
|
[150, 200], [-50, 25],
|
|
|
|
'path02', result)
|
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_group),
|
|
|
|
[150, 200], [-50, 25],
|
|
|
|
'path03', result)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_nested_translate_group = np.array([[1, 0, 20],
|
|
|
|
[0, 1, 0],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_group
|
|
|
|
).dot(tf_nested_translate_group),
|
2018-08-22 01:00:29 +00:00
|
|
|
[150, 200], [-50, 25],
|
|
|
|
'path04', result)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_nested_translate_xy_group = np.array([[1, 0, 20],
|
|
|
|
[0, 1, 30],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_group
|
|
|
|
).dot(tf_nested_translate_xy_group),
|
2018-08-22 01:00:29 +00:00
|
|
|
[150, 200], [-50, 25],
|
|
|
|
'path05', result)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_scale_xy_group = np.array([[0.5, 0, 0],
|
|
|
|
[0, 1.5, 0.0],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_scale_xy_group),
|
|
|
|
[122, 320], [-50, 0],
|
|
|
|
'path06', result)
|
|
|
|
|
|
|
|
a_07 = 20.0*np.pi/180.0
|
2018-08-22 03:05:59 +00:00
|
|
|
tf_rotate_group = np.array([[np.cos(a_07), -np.sin(a_07), 0],
|
2018-08-22 01:00:29 +00:00
|
|
|
[np.sin(a_07), np.cos(a_07), 0],
|
|
|
|
[0, 0, 1]])
|
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_rotate_group),
|
|
|
|
[183, 183], [0, 30],
|
|
|
|
'path07', result)
|
|
|
|
|
|
|
|
a_08 = 45.0*np.pi/180.0
|
2018-08-22 03:05:59 +00:00
|
|
|
tf_rotate_xy_group_R = np.array([[np.cos(a_08), -np.sin(a_08), 0],
|
2018-08-22 03:54:02 +00:00
|
|
|
[np.sin(a_08), np.cos(a_08), 0],
|
|
|
|
[0, 0, 1]])
|
|
|
|
tf_rotate_xy_group_T = np.array([[1, 0, 183],
|
|
|
|
[0, 1, 183],
|
|
|
|
[0, 0, 1]])
|
|
|
|
tf_rotate_xy_group = tf_rotate_xy_group_T.dot(
|
|
|
|
tf_rotate_xy_group_R).dot(
|
|
|
|
np.linalg.inv(tf_rotate_xy_group_T))
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_rotate_xy_group),
|
|
|
|
[183, 183], [0, 30],
|
|
|
|
'path08', result)
|
|
|
|
|
|
|
|
a_09 = 5.0*np.pi/180.0
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_skew_x_group = np.array([[1, np.tan(a_09), 0],
|
|
|
|
[0, 1, 0],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_skew_x_group),
|
|
|
|
[183, 183], [40, 40],
|
|
|
|
'path09', result)
|
|
|
|
|
|
|
|
a_10 = 5.0*np.pi/180.0
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_skew_y_group = np.array([[1, 0, 0],
|
|
|
|
[np.tan(a_10), 1, 0],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_skew_y_group),
|
|
|
|
[183, 183], [40, 40],
|
|
|
|
'path10', result)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
# This last test is for handling transforms that are defined as
|
|
|
|
# attributes of a <path> element.
|
2018-08-22 01:00:29 +00:00
|
|
|
a_11 = -40*np.pi/180.0
|
2018-08-22 03:05:59 +00:00
|
|
|
tf_path11_R = np.array([[np.cos(a_11), -np.sin(a_11), 0],
|
2018-08-22 01:00:29 +00:00
|
|
|
[np.sin(a_11), np.cos(a_11), 0],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 03:54:02 +00:00
|
|
|
tf_path11_T = np.array([[1, 0, 100],
|
|
|
|
[0, 1, 100],
|
|
|
|
[0, 0, 1]])
|
2018-08-22 01:00:29 +00:00
|
|
|
tf_path11 = tf_path11_T.dot(tf_path11_R).dot(np.linalg.inv(tf_path11_T))
|
|
|
|
|
|
|
|
self.check_line(tf_matrix_group.dot(tf_skew_y_group).dot(tf_path11),
|
|
|
|
[180, 20], [-70, 80],
|
|
|
|
'path11', result)
|
|
|
|
|
|
|
|
def check_group_count(self, doc, expected_count):
|
|
|
|
count = 0
|
2018-08-22 03:54:02 +00:00
|
|
|
for _ in doc.tree.getroot().iter('{{{0}}}g'.format(SVG_NAMESPACE['svg'])):
|
2018-08-22 01:00:29 +00:00
|
|
|
count += 1
|
|
|
|
|
|
|
|
self.assertEqual(expected_count, count)
|
|
|
|
|
2020-06-20 01:59:47 +00:00
|
|
|
def test_nested_group(self):
|
2020-06-24 04:54:58 +00:00
|
|
|
# A bug in the flattened_paths_from_group() implementation made it so that only top-level
|
2020-06-20 01:59:47 +00:00
|
|
|
# groups could have their paths flattened. This is a regression test to make
|
|
|
|
# sure that when a nested group is requested, its paths can also be flattened.
|
|
|
|
doc = Document(join(dirname(__file__), 'groups.svg'))
|
2020-06-24 04:54:58 +00:00
|
|
|
result = doc.paths_from_group(['matrix group', 'scale group'])
|
2020-06-20 01:59:47 +00:00
|
|
|
self.assertEqual(len(result), 5)
|
|
|
|
|
2018-08-22 01:00:29 +00:00
|
|
|
def test_add_group(self):
|
2018-08-22 03:54:02 +00:00
|
|
|
# Test `Document.add_group()` function and related Document functions.
|
2018-08-22 01:00:29 +00:00
|
|
|
doc = Document(None)
|
|
|
|
self.check_group_count(doc, 0)
|
|
|
|
|
|
|
|
base_group = doc.add_group()
|
|
|
|
base_group.set('id', 'base_group')
|
|
|
|
self.assertTrue(doc.contains_group(base_group))
|
|
|
|
self.check_group_count(doc, 1)
|
|
|
|
|
|
|
|
child_group = doc.add_group(parent=base_group)
|
|
|
|
child_group.set('id', 'child_group')
|
|
|
|
self.assertTrue(doc.contains_group(child_group))
|
|
|
|
self.check_group_count(doc, 2)
|
|
|
|
|
|
|
|
grandchild_group = doc.add_group(parent=child_group)
|
|
|
|
grandchild_group.set('id', 'grandchild_group')
|
|
|
|
self.assertTrue(doc.contains_group(grandchild_group))
|
|
|
|
self.check_group_count(doc, 3)
|
|
|
|
|
|
|
|
sibling_group = doc.add_group(parent=base_group)
|
|
|
|
sibling_group.set('id', 'sibling_group')
|
|
|
|
self.assertTrue(doc.contains_group(sibling_group))
|
|
|
|
self.check_group_count(doc, 4)
|
|
|
|
|
|
|
|
# Test that we can retrieve each new group from the document
|
|
|
|
self.assertEqual(base_group, doc.get_or_add_group(['base_group']))
|
2018-08-22 03:54:02 +00:00
|
|
|
self.assertEqual(child_group, doc.get_or_add_group(
|
|
|
|
['base_group', 'child_group']))
|
|
|
|
self.assertEqual(grandchild_group, doc.get_or_add_group(
|
|
|
|
['base_group', 'child_group', 'grandchild_group']))
|
|
|
|
self.assertEqual(sibling_group, doc.get_or_add_group(
|
|
|
|
['base_group', 'sibling_group']))
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
# Create a new nested group
|
2018-08-22 03:54:02 +00:00
|
|
|
new_child = doc.get_or_add_group(
|
|
|
|
['base_group', 'new_parent', 'new_child'])
|
2018-08-22 01:00:29 +00:00
|
|
|
self.check_group_count(doc, 6)
|
2018-08-22 03:54:02 +00:00
|
|
|
self.assertEqual(new_child, doc.get_or_add_group(
|
|
|
|
['base_group', 'new_parent', 'new_child']))
|
2018-08-22 01:00:29 +00:00
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
new_leaf = doc.get_or_add_group(
|
|
|
|
['base_group', 'new_parent', 'new_child', 'new_leaf'])
|
|
|
|
self.assertEqual(new_leaf, doc.get_or_add_group([
|
|
|
|
'base_group', 'new_parent', 'new_child', 'new_leaf']))
|
2018-08-22 01:00:29 +00:00
|
|
|
self.check_group_count(doc, 7)
|
|
|
|
|
2018-08-22 03:54:02 +00:00
|
|
|
path_d = ('M 206.07112,858.41289 L 206.07112,-2.02031 '
|
|
|
|
'C -50.738,-81.14814 -20.36402,-105.87055 52.52793,-101.01525 '
|
|
|
|
'L 103.03556,0.0 '
|
|
|
|
'L 0.0,111.11678')
|
2018-08-22 01:00:29 +00:00
|
|
|
|
|
|
|
svg_path = doc.add_path(path_d, group=new_leaf)
|
|
|
|
self.assertEqual(path_d, svg_path.get('d'))
|
|
|
|
|
|
|
|
path = parse_path(path_d)
|
|
|
|
svg_path = doc.add_path(path, group=new_leaf)
|
2022-02-28 02:48:50 +00:00
|
|
|
self.assertEqual(path_d, svg_path.get('d'))
|
|
|
|
|
|
|
|
# Test that paths are added to the correct group
|
|
|
|
new_sibling = doc.get_or_add_group(
|
|
|
|
['base_group', 'new_parent', 'new_sibling'])
|
|
|
|
doc.add_path(path, group=new_sibling)
|
|
|
|
self.assertEqual(len(new_sibling), 1)
|
|
|
|
self.assertEqual(path_d, new_sibling[0].get('d'))
|