diff --git a/svgelements/svgelements.py b/svgelements/svgelements.py index 0152683f..83d34870 100644 --- a/svgelements/svgelements.py +++ b/svgelements/svgelements.py @@ -43,7 +43,7 @@ and the Arc can do exact arc calculations if scipy is installed. """ -SVGELEMENTS_VERSION = "1.5.5" +SVGELEMENTS_VERSION = "1.5.6" MIN_DEPTH = 5 ERROR = 1e-12 @@ -543,14 +543,14 @@ class Length(object): """ SVGLength as used in SVG - Length are lazy solving values. Several conversion values are typically unknown by default and length simply + Length class is lazy when solving values. Several conversion values are unknown by default and length simply stores that ambiguity. So we can have a length of 50% and without calling .value(relative_length=3000) it will - simply store as 50%. Likewise you can have discrete values like 30cm or 20in which have knowable discrete values - but are not knowable in pixels unless a PPI value is supplied. We can say .value(relative_length=30cm, PPI=96) and - solve this for a value like 12%. We can also convert values between knowable lengths. So 30cm in 300mm regardless - whether we know how to convert this to pixels. 0% is 0 in any units or relative values. We can convert pixels to - pc and pt without issue. We can convert vh, vw, vmax, vmin values if we know viewbox values. We can convert em - values if we know the font_size. We can add values together if they are convertible units. Length("20in") + "3cm". + simply store as 50%. Likewise you can have absolute values like 30cm or 20in which are not knowable in pixels + unless a PPI value is supplied. We can say .value(relative_length=30cm, PPI=96) and solve this for a value like + 12%. We can also convert values between knowable lengths. So 30cm is 300mm regardless whether we know how to + convert this to pixels. 0% is 0 in any units or relative values. We can convert pixels to pc and pt without issue. + We can convert vh, vw, vmax, vmin values if we know viewbox values. We can convert em values if we know the font_size. + We can add values together if they are convertible units e.g. Length("20in") + Length("3cm"). If .value() cannot solve for the value with the given information then it will return a Length value. If it can be solved it will return a float. @@ -3030,24 +3030,35 @@ def matrix_multiply(m, s): class Viewbox: - def __init__(self, viewbox, preserve_aspect_ratio=None): + def __init__(self, *args, **kwargs): """ Viewbox controls the scaling between the drawing size view that is observing that drawing. :param viewbox: either values or viewbox attribute or a Viewbox object - :param preserve_aspect_ratio: preserveAspectRatio + :param preserveAspectRatio or preserve_aspect_ratio: preserveAspectRatio """ self.x = None self.y = None self.width = None self.height = None - self.preserve_aspect_ratio = preserve_aspect_ratio - if isinstance(viewbox, dict): - self.property_by_values(viewbox) - elif isinstance(viewbox, Viewbox): - self.property_by_object(viewbox) - else: - self.set_viewbox(viewbox) + self.preserve_aspect_ratio = None + if args and len(args) <= 2: + viewbox = args[0] + if isinstance(viewbox, dict): + self.property_by_values(viewbox) + elif isinstance(viewbox, Viewbox): + self.property_by_object(viewbox) + else: + self.set_viewbox(viewbox) + if len(args) == 2: + self.preserve_aspect_ratio = args[1] + elif len(args) == 4: + self.x = float(args[0]) + self.y = float(args[1]) + self.width = float(args[2]) + self.height = float(args[3]) + if kwargs: + self.property_by_values(dict(kwargs)) def __eq__(self, other): if not isinstance(other, Viewbox): @@ -3071,7 +3082,19 @@ def __str__(self): ) def __repr__(self): - return "%s('%s')" % (self.__class__.__name__, str(self)) + values = [] + if self.x is not None: + values.append("x=%s" % Length.str(self.x)) + if self.y is not None: + values.append("y=%s" % Length.str(self.y)) + if self.width is not None: + values.append("width=%s" % Length.str(self.width)) + if self.height is not None: + values.append("height=%s" % Length.str(self.height)) + if self.preserve_aspect_ratio is not None: + values.append("%s='%s'" % (SVG_ATTR_PRESERVEASPECTRATIO, self.preserve_aspect_ratio)) + params = ", ".join(values) + return "Viewbox(%s)" % params def property_by_object(self, obj): self.x = obj.x @@ -3081,9 +3104,18 @@ def property_by_object(self, obj): self.preserve_aspect_ratio = obj.preserve_aspect_ratio def property_by_values(self, values): - viewbox = values.get(SVG_ATTR_VIEWBOX) - if viewbox is not None: - self.set_viewbox(viewbox) + if SVG_ATTR_VIEWBOX in values: + self.set_viewbox(values[SVG_ATTR_VIEWBOX]) + if SVG_ATTR_X in values: + self.x = values[SVG_ATTR_X] + if SVG_ATTR_Y in values: + self.y = values[SVG_ATTR_Y] + if SVG_ATTR_WIDTH in values: + self.width = values[SVG_ATTR_WIDTH] + if SVG_ATTR_HEIGHT in values: + self.height = values[SVG_ATTR_HEIGHT] + if "preserve_aspect_ratio" in values: + self.preserve_aspect_ratio = values["preserve_aspect_ratio"] if SVG_ATTR_PRESERVEASPECTRATIO in values: self.preserve_aspect_ratio = values[SVG_ATTR_PRESERVEASPECTRATIO] @@ -6952,8 +6984,8 @@ def _init_points(self, points): def __repr__(self): values = [] if self.points is not None: - s = ", ".join(map(str, self.points)) - values.append("points=(%s)" % repr(s)) + s = " ".join(map(str, self.points)) + values.append("points='%s'" % s) self._repr_shape(values) params = ", ".join(values) return "%s(%s)" % (self.__class__.__name__, params) @@ -7703,8 +7735,6 @@ def __init__(self, *args, **kwargs): def __repr__(self): values = [] - if self.url is not None: - values.append("%s=%s" % (SVG_HREF, self.url)) if self.x != 0: values.append("%s=%s" % (SVG_ATTR_X, Length.str(self.x))) if self.y != 0: @@ -7719,6 +7749,8 @@ def __repr__(self): ) if self.viewbox is not None: values.append("%s=%s" % (SVG_ATTR_VIEWBOX, repr(self.viewbox))) + if self.url is not None: + values.append("%s='%s'" % (SVG_HREF, self.url)) params = ", ".join(values) return "SVGImage(%s)" % params @@ -7752,7 +7784,10 @@ def property_by_values(self, values): if viewbox is not None: self.viewbox = Viewbox(viewbox) if SVG_ATTR_PRESERVEASPECTRATIO in values: - self.preserve_aspect_ratio = values[SVG_ATTR_PRESERVEASPECTRATIO] + if values[SVG_ATTR_PRESERVEASPECTRATIO] == SVG_VALUE_NONE: + self.preserve_aspect_ratio = None + else: + self.preserve_aspect_ratio = values[SVG_ATTR_PRESERVEASPECTRATIO] self.x = Length(values.get(SVG_ATTR_X, 0)).value() self.y = Length(values.get(SVG_ATTR_Y, 0)).value() self.width = Length(values.get(SVG_ATTR_WIDTH, "100%")).value() diff --git a/test/test_copy.py b/test/test_copy.py index bfb5364f..88210d09 100644 --- a/test/test_copy.py +++ b/test/test_copy.py @@ -31,7 +31,7 @@ def test_copy_objects(self): self.assertEqual(matrix, matrix_copy) # SVG OBJECTS - viewbox = Viewbox('0 0 103 109', preserve_aspect_ratio="xMaxyMin slice") + viewbox = Viewbox('0 0 103 109', preserveAspectRatio="xMaxyMin slice") viewbox_copy = copy(viewbox) # self.assertEqual(viewbox, viewbox_copy) @@ -143,4 +143,4 @@ def test_copy_objects(self): svg = SVG.parse(q) svg_copy = copy(svg) self.assertEqual(svg, svg_copy) - self.assertIsNotNone(svg_copy.values) \ No newline at end of file + self.assertIsNotNone(svg_copy.values) diff --git a/test/test_group.py b/test/test_group.py index 10828312..50d89fab 100644 --- a/test/test_group.py +++ b/test/test_group.py @@ -48,8 +48,8 @@ def test_group_2rect(self): r1 = rects[1] self.assertEqual(r1.implicit_x, 0) self.assertEqual(r1.implicit_y, 0) - self.assertEqual(r1.implicit_width, 200) - self.assertEqual(r1.implicit_height, 200) + self.assertAlmostEqual(r1.implicit_width, 200) + self.assertAlmostEqual(r1.implicit_height, 200) print(r1.bbox()) def test_issue_107(self): @@ -68,4 +68,4 @@ def test_issue_107(self): m *= "translate(100,100)" # Test __imul__ n = m * 'scale(2)' # Test __mult__ self.assertEqual(n[0][0].transform, Matrix("matrix(2,0,0,2,200,200)")) - self.assertEqual(m[0][0].transform, Matrix("matrix(1,0,0,1,100,100)")) \ No newline at end of file + self.assertEqual(m[0][0].transform, Matrix("matrix(1,0,0,1,100,100)")) diff --git a/test/test_repr.py b/test/test_repr.py index cc248f2f..6e520a02 100644 --- a/test/test_repr.py +++ b/test/test_repr.py @@ -32,8 +32,7 @@ def test_repr_matrix(self): self.assertFalse(obj != eval(repr(obj))) def test_repr_viewbox(self): - obj = Viewbox("0 0 100 60") - print(repr(obj)) + obj = Viewbox("0 0 100 60", "xMid") self.assertTrue(obj == eval(repr(obj))) self.assertFalse(obj != eval(repr(obj))) @@ -146,4 +145,3 @@ def test_repr_title(self): obj = Title(title="SVG Description") self.assertTrue(obj == eval(repr(obj))) self.assertFalse(obj != eval(repr(obj))) - diff --git a/test/test_shape.py b/test/test_shape.py index 419b34e2..071ac0b5 100644 --- a/test/test_shape.py +++ b/test/test_shape.py @@ -297,9 +297,9 @@ def test_shapes_repr(self): s = SimpleLine(fill='red') self.assertEqual(repr(s), "SimpleLine(x1=0.0, y1=0.0, x2=0.0, y2=0.0, fill='#ff0000')") s = Polygon(fill='red') - self.assertEqual(repr(s), "Polygon(points=(''), fill='#ff0000')") + self.assertEqual(repr(s), "Polygon(points='', fill='#ff0000')") s = Polyline(fill='red') - self.assertEqual(repr(s), "Polyline(points=(''), fill='#ff0000')") + self.assertEqual(repr(s), "Polyline(points='', fill='#ff0000')") s = Path(fill='red') self.assertEqual(repr(s), "Path(fill='#ff0000')") diff --git a/test/test_viewbox.py b/test/test_viewbox.py index a04037a5..fe1d2db3 100644 --- a/test/test_viewbox.py +++ b/test/test_viewbox.py @@ -6,6 +6,24 @@ class TestElementViewbox(unittest.TestCase): + def test_viewbox_creation(self): + # Test various ways of creating a viewbox are equal. + v1 = Viewbox('0 0 100 100', 'xMid') + v2 = Viewbox(viewBox="0 0 100 100", preserve_aspect_ratio="xMid") + v3 = Viewbox(x=0, y=0, width=100, height=100, preserveAspectRatio="xMid") + v4 = Viewbox(v1) + v5 = Viewbox({"x":0, "y":0, "width":100, "height":100, "preserveAspectRatio":"xMid"}) + self.assertEqual(v1, v2) + self.assertEqual(v1, v3) + self.assertEqual(v1, v4) + self.assertEqual(v1, v5) + self.assertEqual(v2, v3) + self.assertEqual(v2, v4) + self.assertEqual(v2, v5) + self.assertEqual(v3, v4) + self.assertEqual(v3, v5) + self.assertEqual(v4, v5) + def test_viewbox_incomplete_transform(self): q = io.StringIO(u''' ''')