Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Sharybin <sergey.vfx@gmail.com>2019-09-19 13:50:09 +0300
committerSergey Sharybin <sergey.vfx@gmail.com>2019-09-19 16:33:28 +0300
commit7f97ceb05061fd45e49ae53e71ba9b7656cc8aab (patch)
tree1b6cb19ba565c51976fe143e2b3688a18d8fc9da /io_curve_svg
parent9d54d44eb9a326e1f57be396a2825f0712a35fda (diff)
SVG: Refactor, move utilities to module
Also cover with unit test.
Diffstat (limited to 'io_curve_svg')
-rw-r--r--io_curve_svg/import_svg.py94
-rw-r--r--io_curve_svg/svg_util.py99
-rwxr-xr-xio_curve_svg/svg_util_test.py72
3 files changed, 177 insertions, 88 deletions
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)
+
+ def test_unit_cm(self):
+ self.assertAlmostEqual(parse_coord("1.2cm", 200), 42.51968503937008)
+
+ def test_unit_percentage(self):
+ self.assertEqual(parse_coord("1.2%", 200), 2.4)
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)