# 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) 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.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) 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)) 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)) 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) 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): """Quickly add 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)') def test_is_unity(self): unity = Transform() self.assertTrue(unity.is_rotate()) self.assertTrue(unity.is_scale()) self.assertTrue(unity.is_translate()) def test_is_rotation(self): 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): 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): 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): 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_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) 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)