From b00b374e648cb95a4fb0abd8f593499907444598 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sat, 18 Feb 2017 18:06:09 +0100 Subject: [PATCH 1/9] Fix generation of non-scientific number representation. Before numbers often were already in scientific notation due to the str() implementation of Decimal leading to strange optimization results. --- scour/scour.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scour/scour.py b/scour/scour.py index a6dede0..24bfaa6 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -2634,10 +2634,8 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of else: length = length.normalize() - # gather the non-scientific notation version of the coordinate. - # this may actually be in scientific notation if the value is - # sufficiently large or small, so this is a misnomer. - nonsci = six.text_type(length).lower().replace("e+", "e") + # Gather the non-scientific notation version of the coordinate. + nonsci = '{0:f}'.format(length) if not needsRendererWorkaround: if len(nonsci) > 2 and nonsci[:2] == '0.': nonsci = nonsci[1:] # remove the 0, leave the dot From 8f87118725a70329801b3f42b8eed2cc8c8e0dc1 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sat, 18 Feb 2017 19:01:26 +0100 Subject: [PATCH 2/9] Only use number representation with reduced precision if it is shorter than the initial representation. Before it could happen that "123" was replaced with "1e3" if precision was set to 1 which is obviously not desirable. --- scour/scour.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scour/scour.py b/scour/scour.py index 24bfaa6..7d2e320 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -2623,6 +2623,16 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of if not isinstance(length, Decimal): length = getcontext().create_decimal(str(length)) + # remove trailing zeroes as we do not care for significance + intLength = length.to_integral_value() + if length == intLength: + length = Decimal(intLength) + else: + length = length.normalize() + + # Gather the initial non-scientific notation version of the coordinate (we want to compare with it later) + initial_value = '{0:f}'.format(length) + # reduce numeric precision # plus() corresponds to the unary prefix plus operator and applies context precision and rounding length = scouringContext.plus(length) @@ -2641,6 +2651,7 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of nonsci = nonsci[1:] # remove the 0, leave the dot elif len(nonsci) > 3 and nonsci[:3] == '-0.': nonsci = '-' + nonsci[2:] # remove the 0, leave the minus and dot + return_value = nonsci # Gather the scientific notation version of the coordinate which # can only be shorter if the length of the number is at least 4 characters (e.g. 1000 = 1e3). @@ -2653,11 +2664,13 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of sci = six.text_type(length) + 'e' + six.text_type(exponent) if len(sci) < len(nonsci): - return sci - else: - return nonsci + return_value = sci + + # Return the shortest representation (if they are equal prefer the original as it still has the full precision) + if len(return_value) < len(initial_value): + return return_value else: - return nonsci + return initial_value def reducePrecision(element): From a69efb3a558b91603facaf18912403b658ad1451 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sat, 18 Feb 2017 19:36:19 +0100 Subject: [PATCH 3/9] Add unittests for b00b374e648cb95a4fb0abd8f593499907444598 and 8f87118725a70329801b3f42b8eed2cc8c8e0dc1 --- testscour.py | 34 ++++++++++++++++++++++++++++++++++ unittests/path-precision.svg | 11 ++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/testscour.py b/testscour.py index 560f79b..879b7e8 100755 --- a/testscour.py +++ b/testscour.py @@ -956,6 +956,40 @@ class LimitPrecisionInPathData(unittest.TestCase): 'Not correctly limiting precision on path data') +class KeepPrecisionInPathDataIfSameLength(unittest.TestCase): + + def runTest(self): + doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=1'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths[1:3]: + self.assertEqual(path.getAttribute('d'), "m1 12 123 1e3 1e4 1e5", + 'Precision not correctly reduced with "--set-precision=1" ' + 'for path with ID ' + path.getAttribute('id')) + self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1e3 -1e4 -1e5", + 'Precision not correctly reduced with "--set-precision=1" ' + 'for path with ID ' + paths[4].getAttribute('id')) + + doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=2'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths[1:3]: + self.assertEqual(path.getAttribute('d'), "m1 12 123 1234 12345 1.2e5", + 'Precision not correctly reduced with "--set-precision=2" ' + 'for path with ID ' + path.getAttribute('id')) + self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1234-12345-1.2e5", + 'Precision not correctly reduced with "--set-precision=2" ' + 'for path with ID ' + paths[4].getAttribute('id')) + + doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=3'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths[1:3]: + self.assertEqual(path.getAttribute('d'), "m1 12 123 1234 12345 123456", + 'Precision not correctly reduced with "--set-precision=3" ' + 'for path with ID ' + path.getAttribute('id')) + self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1234-12345-123456", + 'Precision not correctly reduced with "--set-precision=3" ' + 'for path with ID ' + paths[4].getAttribute('id')) + + class RemoveEmptyLineSegmentsFromPath(unittest.TestCase): def runTest(self): diff --git a/unittests/path-precision.svg b/unittests/path-precision.svg index 8e1e267..1d644e4 100644 --- a/unittests/path-precision.svg +++ b/unittests/path-precision.svg @@ -1,4 +1,9 @@ - - - + + + + + + + + From f5a61eeeb338932191a57264d96527c5d6fbb52e Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 00:39:36 +0100 Subject: [PATCH 4/9] Even better fix for 8f87118725a70329801b3f42b8eed2cc8c8e0dc1 (previous solution still did not work for numbers like 123.4 with precision < 3) --- scour/scour.py | 20 +++++--------------- testscour.py | 22 ++++++++++++++++++++++ unittests/path-precision.svg | 2 ++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/scour/scour.py b/scour/scour.py index 7d2e320..b4e848b 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -2622,16 +2622,7 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of """ if not isinstance(length, Decimal): length = getcontext().create_decimal(str(length)) - - # remove trailing zeroes as we do not care for significance - intLength = length.to_integral_value() - if length == intLength: - length = Decimal(intLength) - else: - length = length.normalize() - - # Gather the initial non-scientific notation version of the coordinate (we want to compare with it later) - initial_value = '{0:f}'.format(length) + initial_length = length # reduce numeric precision # plus() corresponds to the unary prefix plus operator and applies context precision and rounding @@ -2645,7 +2636,10 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of length = length.normalize() # Gather the non-scientific notation version of the coordinate. + # Re-quantize from the initial value to prevent unnecessary loss of precision + # (e.g. 123.4 should become 123, not 120 or even 100) nonsci = '{0:f}'.format(length) + nonsci = '{0:f}'.format(initial_length.quantize(Decimal(nonsci))) if not needsRendererWorkaround: if len(nonsci) > 2 and nonsci[:2] == '0.': nonsci = nonsci[1:] # remove the 0, leave the dot @@ -2666,11 +2660,7 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of if len(sci) < len(nonsci): return_value = sci - # Return the shortest representation (if they are equal prefer the original as it still has the full precision) - if len(return_value) < len(initial_value): - return return_value - else: - return initial_value + return return_value def reducePrecision(element): diff --git a/testscour.py b/testscour.py index 879b7e8..43eeb03 100755 --- a/testscour.py +++ b/testscour.py @@ -968,6 +968,9 @@ class KeepPrecisionInPathDataIfSameLength(unittest.TestCase): self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1e3 -1e4 -1e5", 'Precision not correctly reduced with "--set-precision=1" ' 'for path with ID ' + paths[4].getAttribute('id')) + self.assertEqual(paths[5].getAttribute('d'), "m123 101-123-101", + 'Precision not correctly reduced with "--set-precision=1" ' + 'for path with ID ' + paths[5].getAttribute('id')) doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=2'])) paths = doc.getElementsByTagNameNS(SVGNS, 'path') @@ -978,6 +981,9 @@ class KeepPrecisionInPathDataIfSameLength(unittest.TestCase): self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1234-12345-1.2e5", 'Precision not correctly reduced with "--set-precision=2" ' 'for path with ID ' + paths[4].getAttribute('id')) + self.assertEqual(paths[5].getAttribute('d'), "m123 101-123-101", + 'Precision not correctly reduced with "--set-precision=2" ' + 'for path with ID ' + paths[5].getAttribute('id')) doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=3'])) paths = doc.getElementsByTagNameNS(SVGNS, 'path') @@ -988,6 +994,22 @@ class KeepPrecisionInPathDataIfSameLength(unittest.TestCase): self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1234-12345-123456", 'Precision not correctly reduced with "--set-precision=3" ' 'for path with ID ' + paths[4].getAttribute('id')) + self.assertEqual(paths[5].getAttribute('d'), "m123 101-123-101", + 'Precision not correctly reduced with "--set-precision=3" ' + 'for path with ID ' + paths[5].getAttribute('id')) + + doc = scourXmlFile('unittests/path-precision.svg', parse_args(['--set-precision=4'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths[1:3]: + self.assertEqual(path.getAttribute('d'), "m1 12 123 1234 12345 123456", + 'Precision not correctly reduced with "--set-precision=4" ' + 'for path with ID ' + path.getAttribute('id')) + self.assertEqual(paths[4].getAttribute('d'), "m-1-12-123-1234-12345-123456", + 'Precision not correctly reduced with "--set-precision=4" ' + 'for path with ID ' + paths[4].getAttribute('id')) + self.assertEqual(paths[5].getAttribute('d'), "m123.5 101-123.5-101", + 'Precision not correctly reduced with "--set-precision=4" ' + 'for path with ID ' + paths[5].getAttribute('id')) class RemoveEmptyLineSegmentsFromPath(unittest.TestCase): diff --git a/unittests/path-precision.svg b/unittests/path-precision.svg index 1d644e4..e075ec7 100644 --- a/unittests/path-precision.svg +++ b/unittests/path-precision.svg @@ -6,4 +6,6 @@ + + From 3e4c8d793fd5d3eb3f0323a1e56d16c3d5ad9bf5 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 15:19:53 +0100 Subject: [PATCH 5/9] Typo in unittest svg file --- unittests/path-precision.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/path-precision.svg b/unittests/path-precision.svg index e075ec7..9f2bc38 100644 --- a/unittests/path-precision.svg +++ b/unittests/path-precision.svg @@ -1,5 +1,5 @@ - + From 01cb120d718d200e28cbde7e28adbf85eaa6831b Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 15:22:50 +0100 Subject: [PATCH 6/9] Reduce precision of lengths in viewBox This fixes #127. Also simplify splitting of viewBox lengths and avoiding a "FutureWarning: split() requires a non-empty pattern match" at the same time --- scour/scour.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scour/scour.py b/scour/scour.py index b4e848b..d3a3d71 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -3037,7 +3037,7 @@ def properlySizeDoc(docElement, options): # else we have a statically sized image and we should try to remedy that # parse viewBox attribute - vbSep = re.split("\\s*\\,?\\s*", docElement.getAttribute('viewBox'), 3) + vbSep = re.split('[, ]+', docElement.getAttribute('viewBox')) # if we have a valid viewBox we need to check it vbWidth, vbHeight = 0, 0 if len(vbSep) == 4: @@ -3471,6 +3471,11 @@ def scourString(in_string, options=None): 'x1', 'y1', 'x2', 'y2', 'fx', 'fy', 'offset']: if elem.getAttribute(attr) != '': elem.setAttribute(attr, scourLength(elem.getAttribute(attr))) + viewBox = doc.documentElement.getAttribute('viewBox') + if viewBox: + lengths = re.split('[, ]+', viewBox) + lengths = [scourUnitlessLength(lenght) for lenght in lengths] + doc.documentElement.setAttribute('viewBox', ' '.join(lengths)) # more length scouring in this function _num_bytes_saved_in_lengths = reducePrecision(doc.documentElement) From 0ffefcd8bb6ad02aefff1063e0a7a859cad75893 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 15:39:53 +0100 Subject: [PATCH 7/9] Unittests for `--enable-viewboxing` --- testscour.py | 16 +++++++++++++++- unittests/viewbox-create.svg | 3 +++ unittests/viewbox-remove.svg | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 unittests/viewbox-create.svg create mode 100644 unittests/viewbox-remove.svg diff --git a/testscour.py b/testscour.py index 43eeb03..6daec27 100755 --- a/testscour.py +++ b/testscour.py @@ -2505,7 +2505,21 @@ class EmbedRasters(unittest.TestCase): "Raster image from remote path '" + href + "' not embedded.") -# TODO: write tests for --enable-viewboxing +class ViewBox(unittest.TestCase): + + def test_viewbox_create(self): + doc = scourXmlFile('unittests/viewbox-create.svg', parse_args(['--enable-viewboxing'])) + viewBox = doc.documentElement.getAttribute('viewBox') + self.assertEqual(viewBox, '0 0 123.46 654.32', "viewBox not properly created with '--enable-viewboxing'.") + + def test_viewbox_remove_width_and_height(self): + doc = scourXmlFile('unittests/viewbox-remove.svg', parse_args(['--enable-viewboxing'])) + width = doc.documentElement.getAttribute('width') + height = doc.documentElement.getAttribute('height') + self.assertEqual(width, '', "width not removed with '--enable-viewboxing'.") + self.assertEqual(height, '', "height not removed with '--enable-viewboxing'.") + + # TODO: write tests for --keep-editor-data if __name__ == '__main__': diff --git a/unittests/viewbox-create.svg b/unittests/viewbox-create.svg new file mode 100644 index 0000000..0d250db --- /dev/null +++ b/unittests/viewbox-create.svg @@ -0,0 +1,3 @@ + + + diff --git a/unittests/viewbox-remove.svg b/unittests/viewbox-remove.svg new file mode 100644 index 0000000..8fa8307 --- /dev/null +++ b/unittests/viewbox-remove.svg @@ -0,0 +1,3 @@ + + + From 3b41d3a5470f43149c590425442f165870bb58c1 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 18:04:36 +0100 Subject: [PATCH 8/9] Add Python 3.6 to tests and simplify .travis.yml by using 'tox-travis' --- .travis.yml | 26 ++++++++++---------------- tox.ini | 1 + 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 48a15d5..a853a19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,25 @@ language: python +python: + - pypy + - 2.7 + - 3.3 + - 3.4 + - 3.5 + - 3.6 install: - - pip install tox codecov - -env: - - TOX_ENV=pypy - - TOX_ENV=py27 - - TOX_ENV=py33 - - TOX_ENV=py34 -# - TOX_ENV=py35 -# - TOX_ENV=flake8 + - pip install tox-travis codecov script: - - tox -e $TOX_ENV + - tox matrix: fast_finish: true include: - # https://github.com/travis-ci/travis-ci/issues/4794#issuecomment-143758799 - python: 3.5 env: - - TOX_ENV=py35 - - - python: 3.5 - env: - - TOX_ENV=flake8 + - TOXENV=flake8 after_success: - coverage combine && codecov \ No newline at end of file diff --git a/tox.ini b/tox.ini index 30dacf8..df04c05 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py33 py34 py35 + py36 flake8 From 0f6d9be4e241ea2e53ea3cc8576789ac6ab341a2 Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sun, 19 Feb 2017 18:06:57 +0100 Subject: [PATCH 9/9] Add `sudo: false` to .travis.yml for faster execution of jobs --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a853a19..5200f17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: false + language: python python: