diff --git a/scour/scour.py b/scour/scour.py index a1c11a0..753a162 100644 --- a/scour/scour.py +++ b/scour/scour.py @@ -2580,7 +2580,7 @@ def cleanPolyline(elem, options): def controlPoints(cmd, data): """ - Checks if there are control points in the path + Checks if there are control points in the path data Returns False if there aren't any Returns a list of bools set to True for coordinates in the path data which are control points @@ -2596,13 +2596,28 @@ def controlPoints(cmd, data): return False +def flags(cmd, data): + """ + Checks if there are flags in the path data + + Returns the indices of all values in the path data which are flags + """ + if cmd.lower() == 'a': # a: (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ + indices = range(len(data)) + return [index for index in indices if (index % 7) in [3, 4]] + + return [] + + def serializePath(pathObj, options): """ Reserializes the path data with some cleanups. """ # elliptical arc commands must have comma/wsp separating the coordinates # this fixes an issue outlined in Fix https://bugs.launchpad.net/scour/+bug/412754 - return ''.join([cmd + scourCoordinates(data, options, reduce_precision=controlPoints(cmd, data)) + return ''.join([cmd + scourCoordinates(data, options, + reduce_precision=controlPoints(cmd, data), + flags=flags(cmd, data)) for cmd, data in pathObj]) @@ -2614,7 +2629,7 @@ def serializeTransform(transformObj): for command, numbers in transformObj]) -def scourCoordinates(data, options, force_whitespace=False, reduce_precision=False): +def scourCoordinates(data, options, force_whitespace=False, reduce_precision=False, flags=[]): """ Serializes coordinate data with some cleanups: - removes all trailing zeros after the decimal @@ -2636,10 +2651,12 @@ def scourCoordinates(data, options, force_whitespace=False, reduce_precision=Fal # - this number starts with a dot but the previous number had *no* dot or exponent # i.e. '1.3 0.5' -> '1.3.5' or '1e3 0.5' -> '1e3.5' is fine but '123 0.5' -> '123.5' is obviously not # - 'force_whitespace' is explicitly set to 'True' - if c > 0 and (force_whitespace - or scouredCoord[0].isdigit() - or (scouredCoord[0] == '.' and not ('.' in previousCoord or 'e' in previousCoord)) - ): + # we never need a space after flags (occuring in elliptical arcs), but librsvg struggles without it + if (c > 0 + and (force_whitespace + or scouredCoord[0].isdigit() + or (scouredCoord[0] == '.' and not ('.' in previousCoord or 'e' in previousCoord))) + and ((c-1 not in flags) or options.renderer_workaround)): newData.append(' ') # add the scoured coordinate to the path string diff --git a/scour/svg_regex.py b/scour/svg_regex.py index d4dfe3a..c62ba2a 100644 --- a/scour/svg_regex.py +++ b/scour/svg_regex.py @@ -247,16 +247,24 @@ class SVGPathParser(object): axis_rotation = Decimal(token[1]) * 1 token = next_val_fn() - if token[1] not in ('0', '1'): + if token[1][0] not in ('0', '1'): raise SyntaxError("expecting a boolean flag; got %r" % (token,)) - large_arc_flag = Decimal(token[1]) * 1 + large_arc_flag = Decimal(token[1][0]) * 1 - token = next_val_fn() - if token[1] not in ('0', '1'): + if len(token[1]) > 1: + token = list(token) + token[1] = token[1][1:] + else: + token = next_val_fn() + if token[1][0] not in ('0', '1'): raise SyntaxError("expecting a boolean flag; got %r" % (token,)) - sweep_flag = Decimal(token[1]) * 1 + sweep_flag = Decimal(token[1][0]) * 1 - token = next_val_fn() + if len(token[1]) > 1: + token = list(token) + token[1] = token[1][1:] + else: + token = next_val_fn() if token[0] not in self.number_tokens: raise SyntaxError("expecting a number; got %r" % (token,)) x = Decimal(token[1]) * 1 diff --git a/testscour.py b/testscour.py index 060b095..b52d98f 100755 --- a/testscour.py +++ b/testscour.py @@ -1101,6 +1101,24 @@ class ChangeQuadToShorthandInPath(unittest.TestCase): 'Did not change quadratic curves into shorthand curve segments in path') +class BooleanFlagsInEllipticalPath(unittest.TestCase): + + def test_omit_spaces(self): + doc = scourXmlFile('unittests/path-elliptical-flags.svg', parse_args(['--no-renderer-workaround'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths: + self.assertEqual(path.getAttribute('d'), 'm0 0a100 50 0 00100 50', + 'Did not ommit spaces after boolean flags in elliptical arg path command') + + def test_output_spaces_with_renderer_workaround(self): + doc = scourXmlFile('unittests/path-elliptical-flags.svg', parse_args(['--renderer-workaround'])) + paths = doc.getElementsByTagNameNS(SVGNS, 'path') + for path in paths: + self.assertEqual(path.getAttribute('d'), 'm0 0a100 50 0 0 0 100 50', + 'Did not output spaces after boolean flags in elliptical arg path command ' + 'with renderer workaround') + + class DoNotOptimzePathIfLarger(unittest.TestCase): def runTest(self): diff --git a/unittests/path-elliptical-flags.svg b/unittests/path-elliptical-flags.svg new file mode 100644 index 0000000..cdf13ba --- /dev/null +++ b/unittests/path-elliptical-flags.svg @@ -0,0 +1,7 @@ + + + + + + +