[Bf-extensions-cvs] [7f97ceb0] master: SVG: Refactor, move utilities to module

Sergey Sharybin noreply at git.blender.org
Thu Sep 19 15:34:21 CEST 2019


Commit: 7f97ceb05061fd45e49ae53e71ba9b7656cc8aab
Author: Sergey Sharybin
Date:   Thu Sep 19 12:50:09 2019 +0200
Branches: master
https://developer.blender.org/rBA7f97ceb05061fd45e49ae53e71ba9b7656cc8aab

SVG: Refactor, move utilities to module

Also cover with unit test.

===================================================================

M	io_curve_svg/import_svg.py
M	io_curve_svg/svg_util.py
M	io_curve_svg/svg_util_test.py

===================================================================

diff --git a/io_curve_svg/import_svg.py b/io_curve_svg/import_svg.py
index c4949013..0742bc4e 100644
--- a/io_curve_svg/import_svg.py
+++ b/io_curve_svg/import_svg.py
@@ -26,93 +26,17 @@ import bpy
 from mathutils import Vector, Matrix
 
 from . import svg_colors
-from .svg_util import (srgb_to_linearrgb,
+from .svg_util import (units,
+                       srgb_to_linearrgb,
                        check_points_equal,
-                       parse_array_of_floats)
+                       parse_array_of_floats,
+                       read_float)
 
 #### Common utilities ####
 
-# TODO: "em" and "ex" aren't actually supported
-SVGUnits = {"": 1.0,
-            "px": 1.0,
-            "in": 90.0,
-            "mm": 90.0 / 25.4,
-            "cm": 90.0 / 2.54,
-            "pt": 1.25,
-            "pc": 15.0,
-            "em": 1.0,
-            "ex": 1.0,
-            "INVALID": 1.0,  # some DocBook files contain this
-            }
-
 SVGEmptyStyles = {'useFill': None,
                   'fill': None}
 
-def SVGParseFloat(s, i=0):
-    """
-    Parse first float value from string
-
-    Returns value as string
-    """
-
-    start = i
-    n = len(s)
-    token = ''
-
-    # Skip leading whitespace characters
-    while i < n and (s[i].isspace() or s[i] == ','):
-        i += 1
-
-    if i == n:
-        return None, i
-
-    # Read sign
-    if s[i] == '-':
-        token += '-'
-        i += 1
-    elif s[i] == '+':
-        i += 1
-
-    # Read integer part
-    if s[i].isdigit():
-        while i < n and s[i].isdigit():
-            token += s[i]
-            i += 1
-
-    # Fractional part
-    if i < n and s[i] == '.':
-        token += '.'
-        i += 1
-
-        if s[i].isdigit():
-            while i < n and s[i].isdigit():
-                token += s[i]
-                i += 1
-        elif s[i].isspace() or s[i] == ',':
-            # Inkscape sometimes uses weird float format with missed
-            # fractional part after dot. Suppose zero fractional part
-            # for this case
-            pass
-        else:
-            raise Exception('Invalid float value near ' + s[start:start + 10])
-
-    # Degree
-    if i < n and (s[i] == 'e' or s[i] == 'E'):
-        token += s[i]
-        i += 1
-        if s[i] == '+' or s[i] == '-':
-            token += s[i]
-            i += 1
-
-        if s[i].isdigit():
-            while i < n and s[i].isdigit():
-                token += s[i]
-                i += 1
-        else:
-            raise Exception('Invalid float value near ' + s[start:start + 10])
-
-    return token, i
-
 
 def SVGCreateCurve(context):
     """
@@ -153,14 +77,14 @@ def SVGParseCoord(coord, size):
     Needed to handle coordinates set in cm, mm, inches.
     """
 
-    token, last_char = SVGParseFloat(coord)
+    token, last_char = read_float(coord)
     val = float(token)
     unit = coord[last_char:].strip()  # strip() in case there is a space
 
     if unit == '%':
         return float(size) / 100.0 * val
     else:
-        return val * SVGUnits[unit]
+        return val * units[unit]
 
     return val
 
@@ -493,7 +417,7 @@ class SVGPathData:
             elif c.lower() in commands:
                 tokens.append(c)
             elif c in ['-', '.'] or c.isdigit():
-                token, last_char = SVGParseFloat(d, i)
+                token, last_char = read_float(d, i)
                 tokens.append(token)
 
                 # in most cases len(token) and (last_char - i) are the same
@@ -1824,7 +1748,7 @@ class SVGGeometrySVG(SVGGeometryContainer):
          
         if self._node.getAttribute('height'):
             raw_height = self._node.getAttribute('height')
-            token, last_char = SVGParseFloat(raw_height)
+            token, last_char = read_float(raw_height)
             document_height = float(token)
             unit = raw_height[last_char:].strip() 
        
@@ -1837,7 +1761,7 @@ class SVGGeometrySVG(SVGGeometryContainer):
             unitscale = document_height / (viewbox[3] - viewbox[1])
             
             #convert units to BU: 
-            unitscale = unitscale * SVGUnits[unit] / 90 * 1000 / 39.3701 
+            unitscale = unitscale * units[unit] / 90 * 1000 / 39.3701 
             
             #apply blender unit scale: 
             unitscale = unitscale / bpy.context.scene.unit_settings.scale_length
diff --git a/io_curve_svg/svg_util.py b/io_curve_svg/svg_util.py
index 0aeb2018..42e900b4 100644
--- a/io_curve_svg/svg_util.py
+++ b/io_curve_svg/svg_util.py
@@ -20,6 +20,20 @@
 
 import re
 
+
+units = {"": 1.0,
+         "px": 1.0,
+         "in": 90.0,
+         "mm": 90.0 / 25.4,
+         "cm": 90.0 / 2.54,
+         "pt": 1.25,
+         "pc": 15.0,
+         "em": 1.0,
+         "ex": 1.0,
+         "INVALID": 1.0,  # some DocBook files contain this
+         }
+
+
 def srgb_to_linearrgb(c):
     if c < 0.04045:
         return 0.0 if c < 0.0 else c * (1.0 / 12.92)
@@ -48,6 +62,90 @@ def parse_array_of_floats(text):
     return [value_to_float(v[0]) for v in elements]
 
 
+def read_float(s: str, i: int = 0):
+    """
+    Reads floating point value from a string. Parsing starts at the given index.
+
+    Returns the value itself (as a string) and index of first character after the value.
+    """
+    start = i
+    n = len(s)
+    token = ''
+
+    # Skip leading whitespace characters
+    while i < n and (s[i].isspace() or s[i] == ','):
+        i += 1
+
+    if i == n:
+        return "0", i
+
+    # Read sign
+    if s[i] == '-':
+        token += '-'
+        i += 1
+    elif s[i] == '+':
+        i += 1
+
+    # Read integer part
+    if s[i].isdigit():
+        while i < n and s[i].isdigit():
+            token += s[i]
+            i += 1
+
+    # Fractional part
+    if i < n and s[i] == '.':
+        token += '.'
+        i += 1
+
+        if i < n and s[i].isdigit():
+            while i < n and s[i].isdigit():
+                token += s[i]
+                i += 1
+        elif i == n or s[i].isspace() or s[i] == ',':
+            # Inkscape sometimes uses weird float format with missed
+            # fractional part after dot. Suppose zero fractional part
+            # for this case
+            pass
+        else:
+            raise Exception('Invalid float value near ' + s[start:start + 10])
+
+    # Degree
+    if i < n and (s[i] == 'e' or s[i] == 'E'):
+        token += s[i]
+        i += 1
+        if s[i] == '+' or s[i] == '-':
+            token += s[i]
+            i += 1
+
+        if s[i].isdigit():
+            while i < n and s[i].isdigit():
+                token += s[i]
+                i += 1
+        else:
+            raise Exception('Invalid float value near ' + s[start:start + 10])
+
+    return token, i
+
+
+def parse_coord(coord, size):
+    """
+    Parse coordinate component to common basis
+
+    Needed to handle coordinates set in cm, mm, inches.
+    """
+
+    token, last_char = read_float(coord)
+    val = float(token)
+    unit = coord[last_char:].strip()  # strip() in case there is a space
+
+    if unit == '%':
+        return float(size) / 100.0 * val
+    else:
+        return val * units[unit]
+
+    return val
+
+
 def value_to_float(value_encoded: str):
     """
     A simple wrapper around float() which supports empty strings (which are converted to 0).
@@ -55,4 +153,3 @@ def value_to_float(value_encoded: str):
     if len(value_encoded) == 0:
         return 0
     return float(value_encoded)
-
diff --git a/io_curve_svg/svg_util_test.py b/io_curve_svg/svg_util_test.py
index 68f3a8b0..6f54d5f3 100755
--- a/io_curve_svg/svg_util_test.py
+++ b/io_curve_svg/svg_util_test.py
@@ -24,9 +24,9 @@
 # XXX Not really nice, but that hack is needed to allow execution of that test
 #     from both automated CTest and by directly running the file manually...
 if __name__ == '__main__':
-    from svg_util import parse_array_of_floats
+    from svg_util import (parse_array_of_floats, read_float, parse_coord,)
 else:
-    from .svg_util import parse_array_of_floats
+    from .svg_util import (parse_array_of_floats, read_float, parse_coord,)
 import unittest
 
 
@@ -79,5 +79,73 @@ class ParseArrayOfFloatsTest(unittest.TestCase):
         self.assertEqual(parse_array_of_floats("2.75,8.5"), [2.75, 8.5])
 
 
+class ReadFloatTest(unittest.TestCase):
+    def test_empty(self):
+        value, endptr = read_float("", 0)
+        self.assertEqual(value, "0")
+        self.assertEqual(endptr, 0)
+
+    def test_empty_spaces(self):
+        value, endptr = read_float("    ", 0)
+        self.assertEqual(value, "0")
+        self.assertEqual(endptr, 4)
+
+    def test_single_value(self):
+        value, endptr = read_float("1.2", 0)
+        self.assertEqual(value, "1.2")
+        self.assertEqual(endptr, 3)
+
+    def test_scientific_value(self):
+        value, endptr = read_float("1.2e+3", 0)
+        self.assertEqual(value, "1.2e+3")
+        self.assertEqual(endptr, 6)
+
+    def test_scientific_value_no_sign(self):
+        value, endptr = read_float("1.2e3", 0)
+        self.assertEqual(value, "1.2e3")
+        self.assertEqual(endptr, 5)
+
+    def test_middle(self):
+        value, endptr = read_float("1.2  3.4  5.6", 3)
+        self.assertEqual(value, "3.4")
+        self.assertEqual(endptr, 8)
+
+    def test_comma(self):
+        value, endptr = read_float("1.2  ,,3.4  5.6", 3)
+        self.assertEqual(value, "3.4")
+        self.assertEqual(endptr, 10)
+
+    def test_not_a_number(self):
+        # TODO(sergey): Make this more concrete.
+        with self.assertRaises(Exception):
+            value, endptr = read_float("1.2eV", 3)
+
+    def test_missing_fractional(self):
+        value, endptr = read_float("1.", 0)
+        self.assertEqual(value, "1.")
+        self.assertEqual(endptr, 2)
+
+        value, endptr = read_float("2. 3", 0)
+        self.assertEqual(value, "2.")
+        self.assertEqual(endptr, 2)
+
+
+class ParseCoordTest(unittest.TestCase):
+    def test_empty(self):
+        self.assertEqual(parse_coord("", 200), 0)
+
+    def test_empty_spaces(self):
+        self.assertEqual(parse_coord("    ", 200), 0)
+
+    def test_no_units(self):
+        self.assertEqual(parse_coord("1.2", 200), 1.2)
+
+    de

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list