Skip to content
Snippets Groups Projects
Commit 7e77c6ef authored by Sergey Sharybin's avatar Sergey Sharybin
Browse files

SVG: Properly handle values in exponential notation

Some SVG exporters outputs small values in an exponential
notation. There is no big reason to reject those files.

This change makes it so any notation of the value is accepted.
Only do it in the path point parsing, since other areas are
already dealing with this correct.

Also covered the array parsing covered with a unit test which
can be run as a stand-alone application.

The parsing code is from Jacques Lucke, thanks!

Differential Revision: https://developer.blender.org/D4234
parent 812d1c1e
No related branches found
No related tags found
No related merge requests found
......@@ -26,6 +26,9 @@ import bpy
from mathutils import Vector, Matrix
from . import svg_colors
from .svg_util import (srgb_to_linearrgb,
check_points_equal,
parse_array_of_floats)
#### Common utilities ####
......@@ -45,17 +48,6 @@ SVGUnits = {"": 1.0,
SVGEmptyStyles = {'useFill': None,
'fill': None}
def srgb_to_linearrgb(c):
if c < 0.04045:
return 0.0 if c < 0.0 else c * (1.0 / 12.92)
else:
return pow((c + 0.055) * (1.0 / 1.055), 2.4)
def check_points_equal(point_a, point_b):
return (abs(point_a[0] - point_b[0]) < 1e-6 and
abs(point_a[1] - point_b[1]) < 1e-6)
def SVGParseFloat(s, i=0):
"""
Parse first float value from string
......@@ -1729,9 +1721,7 @@ class SVGGeometryPOLY(SVGGeometry):
self._styles = SVGParseStyles(self._node, self._context)
points = self._node.getAttribute('points')
points = points.replace(',', ' ').replace('-', ' -')
points = points.split()
points = parse_array_of_floats(self._node.getAttribute('points'))
prev = None
self._points = []
......@@ -1740,7 +1730,7 @@ class SVGGeometryPOLY(SVGGeometry):
if prev is None:
prev = p
else:
self._points.append((float(prev), float(p)))
self._points.append((prev, p))
prev = None
def _doCreateGeom(self, instancing):
......
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import re
def srgb_to_linearrgb(c):
if c < 0.04045:
return 0.0 if c < 0.0 else c * (1.0 / 12.92)
else:
return pow((c + 0.055) * (1.0 / 1.055), 2.4)
def check_points_equal(point_a, point_b):
return (abs(point_a[0] - point_b[0]) < 1e-6 and
abs(point_a[1] - point_b[1]) < 1e-6)
match_number = r"-?\d+([eE][-+]?\d+)?"
match_first_comma = r"^\s*(?=,)"
match_comma_pair = r",\s*(?=,)"
match_last_comma = r",\s*$"
pattern = f"({match_number})|{match_first_comma}|{match_comma_pair}|{match_last_comma}"
re_pattern = re.compile(pattern)
def parse_array_of_floats(text):
elements = re_pattern.findall(text)
return [value_to_float(v[0]) for v in elements]
def value_to_float(value_encoded: str):
if len(value_encoded) == 0:
return 0
return float(value_encoded)
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
from svg_util import parse_array_of_floats
import unittest
class ParseArrayOfFloatsTest(unittest.TestCase):
def test_empty(self):
self.assertEqual(parse_array_of_floats(""), [])
self.assertEqual(parse_array_of_floats(" "), [])
def test_single_value(self):
self.assertEqual(parse_array_of_floats("123"), [123])
self.assertEqual(parse_array_of_floats(" \t 123 \t"), [123])
def test_single_value_exponent(self):
self.assertEqual(parse_array_of_floats("12e+3"), [12000])
self.assertEqual(parse_array_of_floats("12e-3"), [0.012])
def test_space_separated_values(self):
self.assertEqual(parse_array_of_floats("123 45 6 89"),
[123, 45, 6, 89])
self.assertEqual(parse_array_of_floats(" 123 45 6 89 "),
[123, 45, 6, 89])
def test_comma_separated_values(self):
self.assertEqual(parse_array_of_floats("123,45,6,89"),
[123, 45, 6, 89])
self.assertEqual(parse_array_of_floats(" 123,45,6,89 "),
[123, 45, 6, 89])
def test_mixed_separated_values(self):
self.assertEqual(parse_array_of_floats("123,45 6,89"),
[123, 45, 6, 89])
self.assertEqual(parse_array_of_floats(" 123 45,6,89 "),
[123, 45, 6, 89])
def test_omitted_value_with_comma(self):
self.assertEqual(parse_array_of_floats("1,,3"), [1, 0, 3])
self.assertEqual(parse_array_of_floats(",,3"), [0, 0, 3])
def test_sign_as_separator(self):
self.assertEqual(parse_array_of_floats("1-3"), [1, -3])
self.assertEqual(parse_array_of_floats("1+3"), [1, 3])
def test_all_commas(self):
self.assertEqual(parse_array_of_floats(",,,"), [0, 0, 0, 0])
if __name__ == '__main__':
unittest.main(verbosity=2)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment