From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- share/extensions/tests/test_inkex_transforms.py | 801 ++++++++++++++++++++++++ 1 file changed, 801 insertions(+) create mode 100644 share/extensions/tests/test_inkex_transforms.py (limited to 'share/extensions/tests/test_inkex_transforms.py') diff --git a/share/extensions/tests/test_inkex_transforms.py b/share/extensions/tests/test_inkex_transforms.py new file mode 100644 index 0000000..6068631 --- /dev/null +++ b/share/extensions/tests/test_inkex_transforms.py @@ -0,0 +1,801 @@ +# coding=utf-8 +""" +Test Inkex transformational logic. +""" +from math import sqrt, pi +from inkex.transforms import ( + Vector2d, + ImmutableVector2d, + BoundingBox, + BoundingInterval, + Transform, + DirectedLineSegment, +) +from inkex.utils import PY3 +from inkex.tester import TestCase +import pytest + + +class ImmutableVector2dTest(TestCase): + """Test the ImmutableVector2d object""" + + def test_vector_creation(self): + """Test ImmutableVector2d creation""" + vec0 = ImmutableVector2d(15, 22) + self.assertEqual(vec0.x, 15) + self.assertEqual(vec0.y, 22) + + vec1 = ImmutableVector2d() + self.assertEqual(vec1.x, 0) + self.assertEqual(vec1.y, 0) + + vec2 = ImmutableVector2d((17, 32)) + self.assertEqual(vec2.x, 17) + self.assertEqual(vec2.y, 32) + + vec3 = ImmutableVector2d(vec0) + self.assertEqual(vec3.x, 15) + self.assertEqual(vec3.y, 22) + + vec4 = ImmutableVector2d("-5,8") + self.assertEqual(vec4.x, -5) + self.assertEqual(vec4.y, 8) + + self.assertRaises(ValueError, ImmutableVector2d, (1)) + self.assertRaises(ValueError, ImmutableVector2d, (1, 2, 3)) + + def test_binary_operators(self): + """Test binary operators for vector2d""" + vec1 = ImmutableVector2d(15, 22) + vec2 = ImmutableVector2d(5, 3) + + self.assertTrue((vec1 - vec2).is_close((10, 19))) + self.assertTrue((vec1 - (5, 3)).is_close((10, 19))) + self.assertTrue(((15, 22) - vec2).is_close((10, 19))) + self.assertTrue((vec1 + vec2).is_close((20, 25))) + self.assertTrue((vec1 + (5, 3)).is_close((20, 25))) + self.assertTrue(((15, 22) + vec2).is_close((20, 25))) + self.assertTrue((vec1 * 2).is_close((30, 44))) + self.assertTrue((2 * vec1).is_close((30, 44))) + self.assertTrue((vec1 / 2).is_close((7.5, 11))) + self.assertTrue((vec1.__div__(2)).is_close((7.5, 11))) + self.assertTrue((vec1 // 2).is_close((7.5, 11))) + + def test_ioperators(self): + """Test operators for vector2d""" + vec0 = vec = ImmutableVector2d(15, 22) + vec += (1, 1) + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((16, 23))) + vec -= (10, 20) + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((6, 3))) + vec *= 5 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((30, 15))) + vec /= 90 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((1.0 / 3, 1.0 / 6))) + vec //= 1.0 / 3 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((1, 0.5))) + self.assertTrue(vec0.is_close((15, 22))) + self.assertFalse(vec0.is_close(vec)) + + def test_unary_operators(self): + """Test unary operators""" + vec = ImmutableVector2d(1, 2) + self.assertTrue((-vec).is_close((-1, -2))) + self.assertTrue((+vec).is_close(vec)) + self.assertAlmostEqual(abs(vec), sqrt(5)) + self.assertTrue(+vec is not vec) # returned value is a copy + + def test_representations(self): + """Test ImmutableVector2d Repr""" + self.assertEqual(str(ImmutableVector2d(1, 2)), "1, 2") + self.assertEqual(repr(ImmutableVector2d(1, 2)), "Vector2d(1, 2)") + self.assertEqual(ImmutableVector2d(1, 2).to_tuple(), (1, 2)) + + def test_assign(self): + """Test ImmutableVector2d assignement""" + vec = ImmutableVector2d(10, 20) + with pytest.raises(AttributeError): + vec.assign(5, 10) + + def test_getitem(self): + """Test getitem for ImmutableVector2d""" + vec = ImmutableVector2d(10, 20) + self.assertEqual(len(vec), 2) + self.assertEqual(vec[0], 10) + self.assertEqual(vec[1], 20) + + def test_cross(self): + """Test cross product for ImmutableVector2d""" + vec1 = ImmutableVector2d(0, 2) + vec2 = ImmutableVector2d(0, 3) + vec3 = ImmutableVector2d(0, -3) + vec4 = ImmutableVector2d(3, 0) + vec5 = ImmutableVector2d(-3, 0) + vec6 = ImmutableVector2d(1, 1) + self.assertAlmostEqual(vec1.cross(vec2), 0) + self.assertAlmostEqual(vec2.cross(vec3), 0) + self.assertAlmostEqual(vec1.cross(vec4), -6) + self.assertAlmostEqual(vec1.cross(vec5), 6) + self.assertAlmostEqual(vec1.cross(vec6), -2.0) + self.assertAlmostEqual(vec6.cross(vec1), 2.0) + + +class Vector2dTest(TestCase): + """Test the Vector2d object""" + + def test_vector_creation(self): + """Test Vector2D creation""" + vec0 = Vector2d(15, 22) + self.assertEqual(vec0.x, 15) + self.assertEqual(vec0.y, 22) + + vec1 = Vector2d() + self.assertEqual(vec1.x, 0) + self.assertEqual(vec1.y, 0) + + vec2 = Vector2d((17, 32)) + self.assertEqual(vec2.x, 17) + self.assertEqual(vec2.y, 32) + + vec3 = Vector2d(vec0) + self.assertEqual(vec3.x, 15) + self.assertEqual(vec3.y, 22) + + self.assertRaises(ValueError, Vector2d, (1)) + self.assertRaises(ValueError, Vector2d, (1, 2, 3)) + self.assertRaises(ValueError, Vector2d, 1, 2, 3) + + def test_vector_default_creation(self): + """Test fallback for vectors""" + + # no fallback + vec0 = Vector2d("1,2", fallback=None) + self.assertEqual(vec0.x, 1) + self.assertEqual(vec0.y, 2) + + self.assertRaises(ValueError, Vector2d, "a,2") + self.assertRaises(ValueError, Vector2d, 1) + # invalid fallback + self.assertRaises(ValueError, Vector2d, 1, fallback="a") + + # fallback + vec0 = Vector2d("a,3", fallback=(1, 2)) + self.assertEqual(vec0.x, 1) + self.assertEqual(vec0.y, 2) + + vec0 = Vector2d(("a", "b"), fallback=(1, 2)) + self.assertEqual(vec0.x, 1) + self.assertEqual(vec0.y, 2) + + vec0 = Vector2d((3, 4, 5), fallback=(1, 2)) + self.assertEqual(vec0.x, 1) + self.assertEqual(vec0.y, 2) + + def test_binary_operators(self): + """Test binary operators for vector2d""" + vec1 = Vector2d(15, 22) + vec2 = Vector2d(5, 3) + + self.assertTrue((vec1 - vec2).is_close((10, 19))) + self.assertTrue((vec1 - (5, 3)).is_close((10, 19))) + self.assertTrue(((15, 22) - vec2).is_close((10, 19))) + self.assertTrue((vec1 + vec2).is_close((20, 25))) + self.assertTrue((vec1 + (5, 3)).is_close((20, 25))) + self.assertTrue(((15, 22) + vec2).is_close((20, 25))) + self.assertTrue((vec1 * 2).is_close((30, 44))) + self.assertTrue((2 * vec1).is_close((30, 44))) + self.assertTrue((vec1 / 2).is_close((7.5, 11))) + self.assertTrue((vec1.__div__(2)).is_close((7.5, 11))) + self.assertTrue((vec1 // 2).is_close((7.5, 11))) + + def test_ioperators(self): + """Test operators for vector2d""" + vec0 = vec = Vector2d(15, 22) + vec += (1, 1) + self.assertTrue(vec.is_close((16, 23))) + vec -= (10, 20) + self.assertTrue(vec.is_close((6, 3))) + vec *= 5 + self.assertTrue(vec.is_close((30, 15))) + vec /= 90 + self.assertTrue(vec.is_close((1.0 / 3, 1.0 / 6))) + vec //= 1.0 / 3 + self.assertTrue(vec.is_close((1, 0.5))) + self.assertFalse(vec0.is_close((15, 22))) + self.assertTrue(vec0.is_close(vec)) + + def test_unary_operators(self): + """Test unary operators""" + vec = Vector2d(1, 2) + self.assertTrue((-vec).is_close((-1, -2))) + self.assertTrue((+vec).is_close(vec)) + self.assertTrue(+vec is not vec) # returned value is a copy + + def test_representations(self): + """Test Vector2D Repr""" + self.assertEqual(str(Vector2d(1, 2)), "1, 2") + self.assertEqual(repr(Vector2d(1, 2)), "Vector2d(1, 2)") + self.assertEqual(Vector2d(1, 2).to_tuple(), (1, 2)) + + def test_assign(self): + """Test vector2d assignement""" + vec = Vector2d(10, 20) + vec.assign(5, 10) + self.assertAlmostTuple(vec, (5, 10)) + vec.assign((7, 11)) + self.assertAlmostTuple(vec, (7, 11)) + self.assertAlmostTuple(vec.assign(10, 20).assign(96, 11), (96, 11)) + self.assertAlmostTuple(vec.assign(10, 20).assign(45, 22), (45, 22)) + self.assertTrue(vec.assign(0, 0) is vec) + + def test_getitem(self): + """Test getitem for Vector2D""" + vec = Vector2d(10, 20) + self.assertEqual(len(vec), 2) + self.assertEqual(vec[0], 10) + self.assertEqual(vec[1], 20) + + def test_polar_operations(self): + """Test polar coordinates operations""" + # x y r pi + equivilents = [ + (0, 0, 0, 0), + (0, 0, 0, 1), + (0, 0, 0, -1), + (0, 0, 0, 0.5), + (1, 0, 1, 0), + (0, 1, 1, 0.5), + (0, -1, 1, -0.5), + (3, 0, 3, 0), + (0, 3, 3, 0.5), + (0, -3, 3, -0.5), + (sqrt(2), sqrt(2), 2, 0.25), + (-sqrt(2), sqrt(2), 2, 0.75), + (sqrt(2), -sqrt(2), 2, -0.25), + (-sqrt(2), -sqrt(2), 2, -0.75), + ] + for x, y, r, t in equivilents: + theta = t * pi if r != 0 else None + for ts in [0, 2, -2]: + ctx_msg = "Test values are x: {} y: {} r: {} θ: {} * pi".format( + x, y, r, t + ts + ) + polar = Vector2d.from_polar(r, (t + ts) * pi) + cart = Vector2d(x, y) + self.assertEqual(cart.length, r, msg=ctx_msg) + self.assertEqual(polar.length, r, msg=ctx_msg) + self.assertAlmostEqual(cart.angle, theta, msg=ctx_msg, delta=1e-12) + self.assertAlmostEqual(polar.angle, theta, msg=ctx_msg, delta=1e-12) + self.assertEqual(cart.to_polar_tuple(), (r, cart.angle), msg=ctx_msg) + self.assertEqual(polar.to_polar_tuple(), (r, polar.angle), msg=ctx_msg) + self.assertEqual(cart.to_tuple(), (x, y), msg=ctx_msg) + self.assertAlmostEqual(polar.to_tuple()[0], x, msg=ctx_msg, delta=1e-12) + self.assertAlmostEqual(polar.to_tuple()[1], y, msg=ctx_msg, delta=1e-12) + # Test special handling of from_polar with None theta + self.assertEqual(Vector2d.from_polar(0, None).to_tuple(), (0.0, 0.0)) + self.assertIsNone(Vector2d.from_polar(4, None)) + + +class TransformTest(TestCase): + """Test transformation API and calculations""" + + def test_new_empty(self): + """Create a transformation from two triplets matrix""" + self.assertEqual(Transform(), ((1, 0, 0), (0, 1, 0))) + + def test_new_from_triples(self): + """Create a transformation from two triplets matrix""" + self.assertEqual(Transform(((1, 2, 3), (4, 5, 6))), ((1, 2, 3), (4, 5, 6))) + + def test_new_from_sextlet(self): + """Create a transformation from a list of six numbers""" + self.assertEqual(Transform((1, 2, 3, 4, 5, 6)), ((1, 3, 5), (2, 4, 6))) + + def test_new_from_matrix_str(self): + """Create a transformation from a list of six numbers""" + self.assertEqual(Transform("matrix(1, 2, 3, 4, 5, 6)"), ((1, 3, 5), (2, 4, 6))) + + def test_new_from_scale(self): + """Create a scale based transformation""" + self.assertEqual(Transform("scale(10)"), ((10, 0, 0), (0, 10, 0))) + self.assertEqual(Transform("scale(10, 3.3)"), ((10, 0, 0), (0, 3.3, 0))) + + def test_new_from_translate(self): + """Create a translate transformation""" + self.assertEqual(Transform("translate(12)"), ((1, 0, 12), (0, 1, 0))) + self.assertEqual(Transform("translate(12, 14)"), ((1, 0, 12), (0, 1, 14))) + + def test_new_from_rotate(self): + """Create a rotational transformation""" + self.assertEqual(str(Transform("rotate(90)")), "rotate(90)") + self.assertEqual( + str(Transform("rotate(90 10 12)")), + "matrix(6.12323e-17 1 -1 6.12323e-17 22 2)", + ) + + def test_new_from_skew(self): + """Create skew x/y transformations""" + self.assertEqual(str(Transform("skewX(10)")), "matrix(1 0 0.176327 1 0 0)") + self.assertEqual(str(Transform("skewY(10)")), "matrix(1 0.176327 0 1 0 0)") + + def test_invalid_creation_string(self): + """Test creating invalid transforms""" + self.assertEqual(Transform("boo(4)"), ((1, 0, 0), (0, 1, 0))) + + def test_invalid_creation_matrix(self): + """Test creating invalid transforms""" + self.assertRaises(ValueError, Transform, 0.0) + self.assertRaises(ValueError, Transform, (0.0,)) + self.assertRaises(ValueError, Transform, (0.0, 0.0, 0.0)) + + def test_repr(self): + """Test repr string""" + self.assertEqual(repr(Transform()), "Transform(((1, 0, 0), (0, 1, 0)))") + + def test_matrix_inversion(self): + """Test the negative of a transformation""" + self.assertEqual(-Transform("rotate(45)"), Transform("rotate(-45)")) + self.assertEqual( + -Transform("translate(12, 10)"), Transform("translate(-12, -10)") + ) + self.assertEqual(-Transform("scale(4)"), Transform("scale(0.25)")) + + def test_apply_to_point(self): + """Test applying the transformation to a point""" + trans = Transform("translate(10, 10)") + self.assertEqual(trans.apply_to_point((10, 10)).to_tuple(), (20, 20)) + self.assertRaises(ValueError, trans.apply_to_point, "") + + def test_translate(self): + """Test making translate specific items""" + self.assertEqual( + str(Transform(translate=(10.6, 99.9))), "translate(10.6, 99.9)" + ) + + def test_scale(self): + """Test making scale specific items""" + self.assertEqual(str(Transform(scale=(1.0, 2.2))), "scale(1, 2.2)") + + def test_rotate(self): + """Test making rotate specific items""" + self.assertEqual(str(Transform(rotate=45)), "rotate(45)") + self.assertEqual( + str(Transform(rotate=(45, 10, 10))), + "matrix(0.707107 0.707107 -0.707107 0.707107 10 -4.14214)", + ) + + def test_add_transform(self): + """Test add_TRANSFORM syntax for quickly composing known transforms""" + tr1 = Transform() + tr1.add_scale(5.0, 1.0) + self.assertEqual(str(tr1), "scale(5, 1)") + tr1.add_translate(10, 10) + self.assertEqual(str(tr1), "matrix(5 0 0 1 50 10)") + self.assertEqual(str(Transform().add_scale(5.0, 1.0)), "scale(5, 1)") + self.assertEqual( + str(Transform().add_scale(5.0, 1.0).add_translate(10, 10)), + "matrix(5 0 0 1 50 10)", + ) + tr2 = Transform() + self.assertTrue( + tr2.add_scale(1, 1) + .add_translate(0, 0) + .add_skewy(0) + .add_skewx(0) + .add_rotate(0) + is tr2 + ) + self.assertEqual( + str(tr2.add_kwargs(translate=(10, 10), scale=(5.0, 1.0))), + "matrix(5 0 0 1 50 10)", + ) + + def test_is_unity(self): + """Test that unix matrix looks like rotate, scale, and translate""" + unity = Transform() + self.assertTrue(unity.is_rotate()) + self.assertTrue(unity.is_scale()) + self.assertTrue(unity.is_translate()) + + def test_is_rotation(self): + """Test that rotations about origin are correctly identified""" + rot1 = Transform(rotate=21) + rot2 = Transform(rotate=35) + rot3 = Transform(rotate=53) + + self.assertFalse(Transform(translate=1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(scale=1 + 1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_rotate(exactly=True)) + + self.assertTrue(Transform(translate=1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(scale=1 + 1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_rotate(exactly=False)) + + self.assertTrue(rot1.is_rotate()) + self.assertTrue(rot2.is_rotate()) + self.assertTrue(rot3.is_rotate()) + + self.assertFalse(rot1.is_translate()) + self.assertFalse(rot2.is_translate()) + self.assertFalse(rot3.is_translate()) + + self.assertFalse(rot1.is_scale()) + self.assertFalse(rot2.is_scale()) + self.assertFalse(rot3.is_scale()) + + self.assertTrue((rot1 @ rot1).is_rotate()) + self.assertTrue((rot1 @ rot2).is_rotate()) + self.assertTrue((rot1 @ rot2 @ rot3 @ rot2 @ rot1).is_rotate()) + + def test_is_translate(self): + """Test that translations are correctly identified""" + tr1 = Transform(translate=(1.1,)) + tr2 = Transform(translate=(1.3, 2.7)) + tr3 = Transform(translate=(sqrt(2) / 2, pi)) + + self.assertFalse(Transform(rotate=1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(scale=1 + 1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_translate(exactly=True)) + + self.assertTrue(Transform(rotate=1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(scale=1 + 1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_translate(exactly=False)) + + self.assertTrue(tr1.is_translate()) + self.assertTrue(tr2.is_translate()) + self.assertTrue(tr3.is_translate()) + self.assertFalse(tr1.is_rotate()) + self.assertFalse(tr2.is_rotate()) + self.assertFalse(tr3.is_rotate()) + self.assertFalse(tr1.is_scale()) + self.assertFalse(tr2.is_scale()) + self.assertFalse(tr3.is_scale()) + + self.assertTrue((tr1 @ tr1).is_translate()) + self.assertTrue((tr1 @ tr2).is_translate()) + self.assertTrue((tr1 @ tr2 @ tr3 @ tr2 @ tr1).is_translate()) + self.assertFalse(tr1 @ tr2 @ tr3 @ -tr1 @ -tr2 @ -tr3) # is almost unity + + def test_is_scale(self): + """Test that scale transformations are correctly identified""" + s1 = Transform(scale=(1.1,)) + s2 = Transform(scale=(1.3, 2.7)) + s3 = Transform(scale=(sqrt(2) / 2, pi)) + + self.assertFalse(Transform(translate=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(rotate=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_scale(exactly=True)) + + self.assertTrue(Transform(translate=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(rotate=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_scale(exactly=False)) + + self.assertFalse(s1.is_translate()) + self.assertFalse(s2.is_translate()) + self.assertFalse(s3.is_translate()) + self.assertFalse(s1.is_rotate()) + self.assertFalse(s2.is_rotate()) + self.assertFalse(s3.is_rotate()) + self.assertTrue(s1.is_scale()) + self.assertTrue(s2.is_scale()) + self.assertTrue(s3.is_scale()) + + def test_rotation_degrees(self): + """Test parsing and composition of different rotations""" + self.assertAlmostEqual(Transform(rotate=30).rotation_degrees(), 30) + self.assertAlmostEqual(Transform(translate=(10, 20)).rotation_degrees(), 0) + self.assertAlmostEqual(Transform(scale=(1, 1)).rotation_degrees(), 0) + + self.assertAlmostEqual( + Transform(rotate=35, translate=(10, 20)).rotation_degrees(), 35 + ) + self.assertAlmostEqual( + Transform(rotate=35, translate=(10, 20), scale=5).rotation_degrees(), 35 + ) + self.assertAlmostEqual( + Transform(rotate=35, translate=(10, 20), scale=(5, 5)).rotation_degrees(), + 35, + ) + + def rotation_degrees(**kwargs): + return Transform(**kwargs).rotation_degrees() + + self.assertRaises(ValueError, rotation_degrees, rotate=35, skewx=1) + self.assertRaises(ValueError, rotation_degrees, rotate=35, skewy=1) + self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11)) + self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11)) + + def test_construction_order(self): + """Test transform kwargs construction order""" + if not PY3: + self.skipTest("Construction order is known to fail on python2 (by design).") + return + + self.assertEqual( + str(Transform(scale=2.0, translate=(5, 6))), "matrix(2 0 0 2 5 6)" + ) + self.assertEqual( + str(Transform(scale=2.0, rotate=45)), + "matrix(1.41421 1.41421 -1.41421 1.41421 0 0)", + ) + + x, y, angle = 5, 7, 31 + rotation = Transform(rotate=angle) + translation = Transform(translate=(x, y)) + + rotation_then_translation = translation @ rotation + translation_then_rotation = rotation @ translation + + tr1 = Transform(rotate=angle, translate=(x, y)) + tr2 = Transform(translate=(x, y), rotate=angle) + + self.assertNotEqual(tr1, tr2) + self.assertDeepAlmostEqual(tr1.matrix, rotation_then_translation.matrix) + self.assertDeepAlmostEqual(tr2.matrix, translation_then_rotation.matrix) + + def test_interpolate(self): + """Test interpolate with other transform""" + t1 = Transform((0, 0, 0, 0, 0, 0)) + t2 = Transform((1, 1, 1, 1, 1, 1)) + val = t1.interpolate(t2, 0.5) + assert all(getattr(val, a) == pytest.approx(0.5, 1e-3) for a in "abcdef") + + +class ScaleTest(TestCase): + """Test scale class""" + + def test_creation(self): + """Creating scales""" + self.assertEqual(BoundingInterval(0, 0), (0, 0)) + self.assertEqual(BoundingInterval(1), (1, 1)) + self.assertEqual(BoundingInterval(10), (10, 10)) + self.assertEqual(BoundingInterval(10, 20), (10, 20)) + self.assertEqual(BoundingInterval((2, 50)), (2, 50)) + self.assertEqual(repr(BoundingInterval((5, 10))), "BoundingInterval(5, 10)") + + def test_center(self): + """Center of a scale""" + self.assertEqual(BoundingInterval(0, 0).center, 0) + self.assertEqual(BoundingInterval(0, 10).center, 5) + self.assertEqual(BoundingInterval(-10, 10).center, 0) + + def test_neg(self): + """-Span(...)""" + self.assertEqual(tuple(-BoundingInterval(-10, 10)), (-10, 10)) + self.assertEqual(tuple(-BoundingInterval(-15, 2)), (-2, 15)) + self.assertEqual(tuple(-BoundingInterval(100, 110)), (-110, -100)) + self.assertEqual(tuple(-BoundingInterval(-110, -100)), (100, 110)) + + def test_size(self): + """Size of the scale""" + self.assertEqual(BoundingInterval(0, 0).size, 0) + self.assertEqual(BoundingInterval(10, 30).size, 20) + self.assertEqual(BoundingInterval(-10, 10).size, 20) + self.assertEqual(BoundingInterval(-30, -10).size, 20) + + def test_combine(self): + """Combine scales together""" + self.assertEqual(BoundingInterval(9, 10) + BoundingInterval(4, 5), (4, 10)) + self.assertEqual( + sum([BoundingInterval(4), BoundingInterval(3), BoundingInterval(10)], None), + (3, 10), + ) + self.assertEqual(BoundingInterval(2, 2) * 2, (4, 4)) + + def test_errors(self): + """Expected errors""" + self.assertRaises(ValueError, BoundingInterval, "foo") + + +class BoundingBoxTest(TestCase): + """Test bounding box calculations""" + + def test_bbox(self): + """Creating bounding boxes""" + self.assertEqual(tuple(BoundingBox(1, 3)), ((1, 1), (3, 3))) + self.assertEqual(tuple(BoundingBox((1, 2), 3)), ((1, 2), (3, 3))) + self.assertEqual(tuple(BoundingBox(1, (3, 4))), ((1, 1), (3, 4))) + self.assertEqual(tuple(BoundingBox((1, 2), (3, 4))), ((1, 2), (3, 4))) + self.assertEqual( + repr(BoundingBox((1, 2), (3, 4))), "BoundingBox((1, 2),(3, 4))" + ) + + def test_bbox_sum(self): + """Test adding bboxes together""" + self.assertEqual( + tuple(BoundingBox((0, 10), (0, 10)) + BoundingBox((-10, 0), (-10, 0))), + ((-10, 10), (-10, 10)), + ) + ret = sum( + [ + BoundingBox((-5, 0), (0, 0)), + BoundingBox((0, 5), (0, 0)), + BoundingBox((0, 0), (-5, 0)), + BoundingBox((0, 0), (0, 5)), + ], + None, + ) + self.assertEqual(tuple(ret), ((-5, 5), (-5, 5))) + self.assertEqual(tuple(BoundingBox(-10, 2) + ret), ((-10, 5), (-5, 5))) + self.assertEqual(tuple(ret + BoundingBox(1, -10)), ((-5, 5), (-10, 5))) + + def test_bbox_neg(self): + self.assertEqual(tuple(-BoundingBox(-10, 2)), ((10, 10), (-2, -2))) + self.assertEqual( + tuple(-BoundingBox((-10, 15), (2, 10))), ((-15, 10), (-10, -2)) + ) + + def test_bbox_scale(self): + """Bounding Boxes can be scaled""" + self.assertEqual(tuple(BoundingBox(1, 3) * 2), ((2, 2), (6, 6))) + + def test_bbox_area(self): + self.assertEqual(BoundingBox((-3, 7), (-5, 5)).area, 100) + + def test_bbox_anchor_left_right(self): + """Bunding box anchoring (left to right)""" + bbox = BoundingBox((-1, 1), (10, 20)) + self.assertEqual( + [ + bbox.get_anchor("l", "t", "lr"), + bbox.get_anchor("m", "t", "lr"), + bbox.get_anchor("r", "t", "lr"), + bbox.get_anchor("l", "t", "rl"), + bbox.get_anchor("m", "t", "rl"), + bbox.get_anchor("r", "t", "rl"), + ], + [-1, 0.0, 1, 1, -0.0, -1], + ) + + def test_bbox_anchor_top_bottom(self): + """Bunding box anchoring (top to bottom)""" + bbox = BoundingBox((10, 20), (-1, 1)) + self.assertEqual( + [ + bbox.get_anchor("l", "t", "tb"), + bbox.get_anchor("l", "m", "tb"), + bbox.get_anchor("l", "b", "tb"), + bbox.get_anchor("l", "t", "bt"), + bbox.get_anchor("l", "m", "bt"), + bbox.get_anchor("l", "b", "bt"), + ], + [-1, 0.0, 1, 1, -0.0, -1], + ) + + def test_bbox_anchor_custom(self): + """Bounding box anchoring custom angle""" + bbox = BoundingBox((10, 10), (5, 5)) + self.assertEqual( + [ + bbox.get_anchor("l", "t", 0), + bbox.get_anchor("l", "t", 90), + bbox.get_anchor("l", "t", 180), + bbox.get_anchor("l", "t", 270), + bbox.get_anchor("l", "t", 45), + ], + [10, -5, -10, 5, 3.5355339059327378], + ) + + def test_bbox_anchor_radial(self): + """Bounding box anchoring radial in/out""" + bbox = BoundingBox((10, 10), (5, 5)) + self.assertRaises(ValueError, bbox.get_anchor, "m", "m", "ro") + selbox = BoundingBox((100, 100), (100, 100)) + self.assertEqual(int(bbox.get_anchor("m", "m", "ro", selbox)), 130) + + +class SegmentTest(TestCase): + """Test special Segments""" + + def test_segment_creation(self): + """Test segments""" + self.assertEqual(DirectedLineSegment((1, 2), (3, 4)), (1, 3, 2, 4)) + self.assertEqual( + repr(DirectedLineSegment((1, 2), (3, 4))), + "DirectedLineSegment((1, 2), (3, 4))", + ) + + def test_segment_maths(self): + """Segments have calculations""" + self.assertEqual(DirectedLineSegment((0, 0), (10, 0)).angle, 0) + self.assertAlmostEqual( + DirectedLineSegment((0, 0), (0.5 * sqrt(3), 0.5)).angle, pi / 6, delta=1e-6 + ) + + def test_segment_dx(self): + """Test segment dx calculation""" + self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dx, 0) + self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dx, 0) + self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dx, 3) + self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).dx, -3) + self.assertEqual(DirectedLineSegment((5, 0), (1, 0)).dx, -4) + self.assertEqual(DirectedLineSegment((-3, 0), (1, 0)).dx, 4) + + def test_segment_dy(self): + """Test segment dy calculation""" + self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dy, 0) + self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dy, 0) + self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dy, 3) + self.assertEqual(DirectedLineSegment((0, 0), (0, -3)).dy, -3) + self.assertEqual(DirectedLineSegment((0, 5), (0, 1)).dy, -4) + self.assertEqual(DirectedLineSegment((0, -3), (0, 1)).dy, 4) + + def test_segment_vector(self): + """Test segment delta vector""" + self.assertEqual(DirectedLineSegment((0, 0), (2, 3)).vector.to_tuple(), (2, 3)) + self.assertEqual( + DirectedLineSegment((-2, -3), (2, 3)).vector.to_tuple(), (4, 6) + ) + + def test_segment_length(self): + """Test segment length calculation""" + self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).length, 0) + self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).length, 3) + self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).length, 3) + self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).length, 5) + self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).length, 5) + self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).length, 2) + self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).length, 2) + self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).length, 4) + self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).length, 4) + self.assertEqual(DirectedLineSegment((0, 0), (3, 4)).length, 5) + self.assertEqual(DirectedLineSegment((-3, -4), (0, 0)).length, 5) + + def test_segment_angle(self): + """Test segment angle calculation""" + self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).angle, 0) + self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).angle, pi) + self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).angle, pi / 2) + self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).angle, -pi / 2) + self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).angle, pi) + self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).angle, 0) + self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).angle, -pi / 2) + self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).angle, pi / 2) + self.assertEqual(DirectedLineSegment((0, 0), (1, 1)).angle, pi / 4) + self.assertEqual(DirectedLineSegment((0, 0), (-1, 1)).angle, 3 * pi / 4) + self.assertEqual(DirectedLineSegment((0, 0), (-1, -1)).angle, -3 * pi / 4) + self.assertEqual(DirectedLineSegment((0, 0), (1, -1)).angle, -pi / 4) + + +class ExtremaTest(TestCase): + """Test school formula implementation""" + + def test_cubic_extrema_1(self): + from inkex.transforms import cubic_extrema + + a, b, c, d = ( + 14.644651000000003194, + -4.881549508464541276, + -4.8815495084645448287, + 14.644651000000003194, + ) + cmin, cmax = cubic_extrema(a, b, c, d) + self.assertAlmostEqual(cmin, 0, delta=1e-6) + self.assertAlmostEqual(cmax, a, delta=1e-6) + + def test_quadratic_extrema_1(self): + from inkex.transforms import quadratic_extrema + + a, b = 5.0, 12.0 + cmin, cmax = quadratic_extrema(a, b, a) + self.assertAlmostEqual(cmin, 5, delta=1e-6) + self.assertAlmostEqual(cmax, 8.5, delta=1e-6) + + def test_quadratic_extrema_2(self): + from inkex.transforms import quadratic_extrema + + a = 5.0 + cmin, cmax = quadratic_extrema(a, a, a) + self.assertAlmostEqual(cmin, a, delta=1e-6) + self.assertAlmostEqual(cmax, a, delta=1e-6) -- cgit v1.2.3