Repository: btel/svg_utils

Issue:
SVGFigure does not set width and height element if created directly
Width and height aren't correctly set in the XML if `transform.SVGFigure` is created directly:

```python
import svgutils
svgutils.transform.SVGFigure("10cm", "16cm").to_str()
```
prints 
```python
b'<?xml version=\'1.0\' encoding=\'ASCII\' standalone=\'yes\'?>\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"/>\n'
```
This code is used in the tutorial:

https://github.com/btel/svg_utils/blob/4abf7fb18cea0da04b6b2a0bcacbec5daded1662/docs/source/tutorials/scripts/fig_final.py#L5

Therefore I expect that this is not an intended behavior.

Comments:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 15831e0..73321ce 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/psf/black
-    rev: 20.8b1
+    rev: 22.8.0
     hooks:
       - id: black
         language_version: python3
diff --git a/src/svgutils/common.py b/src/svgutils/common.py
new file mode 100644
index 0000000..9928d72
--- /dev/null
+++ b/src/svgutils/common.py
@@ -0,0 +1,59 @@
+import re
+
+
+class Unit:
+    """Implementation of SVG units and conversions between them.
+
+    Parameters
+    ----------
+    measure : str
+        value with unit (for example, '2cm')
+    """
+
+    per_inch = {"px": 90, "cm": 2.54, "mm": 25.4, "pt": 72.0}
+
+    def __init__(self, measure):
+        try:
+            self.value = float(measure)
+            self.unit = "px"
+        except ValueError:
+            m = re.match("([0-9]+\.?[0-9]*)([a-z]+)", measure)
+            value, unit = m.groups()
+            self.value = float(value)
+            self.unit = unit
+
+    def to(self, unit):
+        """Convert to a given unit.
+
+        Parameters
+        ----------
+        unit : str
+           Name of the unit to convert to.
+
+        Returns
+        -------
+        u : Unit
+            new Unit object with the requested unit and computed value.
+        """
+        u = Unit("0cm")
+        u.value = self.value / self.per_inch[self.unit] * self.per_inch[unit]
+        u.unit = unit
+        return u
+
+    def __str__(self):
+        return "{}{}".format(self.value, self.unit)
+
+    def __repr__(self):
+        return "Unit({})".format(str(self))
+
+    def __mul__(self, number):
+        u = Unit("0cm")
+        u.value = self.value * number
+        u.unit = self.unit
+        return u
+
+    def __truediv__(self, number):
+        return self * (1.0 / number)
+
+    def __div__(self, number):
+        return self * (1.0 / number)
diff --git a/src/svgutils/compose.py b/src/svgutils/compose.py
index 8a30a3b..0932fe5 100644
--- a/src/svgutils/compose.py
+++ b/src/svgutils/compose.py
@@ -15,9 +15,9 @@ Features:
 """
 
 import os
-import re
 
 from svgutils import transform as _transform
+from svgutils.common import Unit
 
 CONFIG = {
     "svg.file_path": ".",
@@ -358,61 +358,3 @@ class Figure(Panel):
             if iy > nrows:
                 break
         return self
-
-
-class Unit:
-    """Implementation of SVG units and conversions between them.
-
-    Parameters
-    ----------
-    measure : str
-        value with unit (for example, '2cm')
-    """
-
-    per_inch = {"px": 90, "cm": 2.54, "mm": 25.4, "pt": 72.0}
-
-    def __init__(self, measure):
-        try:
-            self.value = float(measure)
-            self.unit = "px"
-        except ValueError:
-            m = re.match("([0-9]+\.?[0-9]*)([a-z]+)", measure)
-            value, unit = m.groups()
-            self.value = float(value)
-            self.unit = unit
-
-    def to(self, unit):
-        """Convert to a given unit.
-
-        Parameters
-        ----------
-        unit : str
-           Name of the unit to convert to.
-
-        Returns
-        -------
-        u : Unit
-            new Unit object with the requested unit and computed value.
-        """
-        u = Unit("0cm")
-        u.value = self.value / self.per_inch[self.unit] * self.per_inch[unit]
-        u.unit = unit
-        return u
-
-    def __str__(self):
-        return "{}{}".format(self.value, self.unit)
-
-    def __repr__(self):
-        return "Unit({})".format(str(self))
-
-    def __mul__(self, number):
-        u = Unit("0cm")
-        u.value = self.value * number
-        u.unit = self.unit
-        return u
-
-    def __truediv__(self, number):
-        return self * (1.0 / number)
-
-    def __div__(self, number):
-        return self * (1.0 / number)
diff --git a/src/svgutils/transform.py b/src/svgutils/transform.py
index ef15f9e..5cddfa3 100644
--- a/src/svgutils/transform.py
+++ b/src/svgutils/transform.py
@@ -7,6 +7,8 @@ try:
 except ImportError:
     from io import StringIO
 
+from svgutils.common import Unit
+
 SVG_NAMESPACE = "http://www.w3.org/2000/svg"
 XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"
 SVG = "{%s}" % SVG_NAMESPACE
@@ -239,17 +241,10 @@ class SVGFigure(object):
         self._height = 0
 
         if width:
-            try:
-                self.width = width  # this goes to @width.setter a few lines down
-            except AttributeError:
-                # int or str
-                self._width = width
+            self.width = width  # this goes to @width.setter a few lines down
 
         if height:
-            try:
-                self.height = height  # this goes to @height.setter a few lines down
-            except AttributeError:
-                self._height = height
+            self.height = height  # this goes to @height.setter a few lines down
 
     @property
     def width(self):
@@ -258,6 +253,8 @@ class SVGFigure(object):
 
     @width.setter
     def width(self, value):
+        if not isinstance(value, Unit):
+            value = Unit(value)
         self._width = value.value
         self.root.set("width", str(value))
         self.root.set("viewBox", "0 0 %s %s" % (self._width, self._height))
@@ -269,6 +266,8 @@ class SVGFigure(object):
 
     @height.setter
     def height(self, value):
+        if not isinstance(value, Unit):
+            value = Unit(value)
         self._height = value.value
         self.root.set("height", str(value))
         self.root.set("viewBox", "0 0 %s %s" % (self._width, self._height))


Test patch:
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 7074ea6..ca449da 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -75,3 +75,10 @@ def test_svg_figure_writes_width_height_and_view_box():
     assert 'width="400.0mm"' in written_content
     assert 'height="300.0mm"' in written_content
     assert 'viewBox="0 0 400.0 300.0"' in written_content
+
+
+def test_svg_figure__width_height_tostr():
+
+    svg_fig = transform.SVGFigure("400px", "300px")
+    assert b'height="300.0px"' in svg_fig.to_str()
+    assert b'width="400.0px"' in svg_fig.to_str()