Fix handling of boolean flags in elliptical path commands (#183)

* properly parse paths without space after boolean flags (fixes #161)
* omit space after boolean flag to shave off a few bytes when not using renderer workarounds
This commit is contained in:
Eduard Braun 2018-04-08 15:32:47 +02:00 committed by GitHub
parent ba7f4b5f18
commit 103dcc0a48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 13 deletions

View file

@ -2580,7 +2580,7 @@ def cleanPolyline(elem, options):
def controlPoints(cmd, data): 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 False if there aren't any
Returns a list of bools set to True for coordinates in the path data which are control points 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 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): def serializePath(pathObj, options):
""" """
Reserializes the path data with some cleanups. Reserializes the path data with some cleanups.
""" """
# elliptical arc commands must have comma/wsp separating the coordinates # elliptical arc commands must have comma/wsp separating the coordinates
# this fixes an issue outlined in Fix https://bugs.launchpad.net/scour/+bug/412754 # 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]) for cmd, data in pathObj])
@ -2614,7 +2629,7 @@ def serializeTransform(transformObj):
for command, numbers in 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: Serializes coordinate data with some cleanups:
- removes all trailing zeros after the decimal - 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 # - 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 # 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' # - 'force_whitespace' is explicitly set to 'True'
if c > 0 and (force_whitespace # 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].isdigit()
or (scouredCoord[0] == '.' and not ('.' in previousCoord or 'e' in previousCoord)) or (scouredCoord[0] == '.' and not ('.' in previousCoord or 'e' in previousCoord)))
): and ((c-1 not in flags) or options.renderer_workaround)):
newData.append(' ') newData.append(' ')
# add the scoured coordinate to the path string # add the scoured coordinate to the path string

View file

@ -247,15 +247,23 @@ class SVGPathParser(object):
axis_rotation = Decimal(token[1]) * 1 axis_rotation = Decimal(token[1]) * 1
token = next_val_fn() 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,)) raise SyntaxError("expecting a boolean flag; got %r" % (token,))
large_arc_flag = Decimal(token[1]) * 1 large_arc_flag = Decimal(token[1][0]) * 1
if len(token[1]) > 1:
token = list(token)
token[1] = token[1][1:]
else:
token = next_val_fn() 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,)) raise SyntaxError("expecting a boolean flag; got %r" % (token,))
sweep_flag = Decimal(token[1]) * 1 sweep_flag = Decimal(token[1][0]) * 1
if len(token[1]) > 1:
token = list(token)
token[1] = token[1][1:]
else:
token = next_val_fn() token = next_val_fn()
if token[0] not in self.number_tokens: if token[0] not in self.number_tokens:
raise SyntaxError("expecting a number; got %r" % (token,)) raise SyntaxError("expecting a number; got %r" % (token,))

View file

@ -1101,6 +1101,24 @@ class ChangeQuadToShorthandInPath(unittest.TestCase):
'Did not change quadratic curves into shorthand curve segments in path') '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): class DoNotOptimzePathIfLarger(unittest.TestCase):
def runTest(self): def runTest(self):

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="-100 -50 300 150" xmlns="http://www.w3.org/2000/svg">
<path d="m0 0a100 50 0 0 0 100 50" fill="none" stroke="#000"/>
<path d="m0 0a100 50 0 0 0100 50" fill="none" stroke="green"/>
<path d="m0 0a100 50 0 00 100 50" fill="none" stroke="blue"/>
<path d="m0 0a100 50 0 00100 50" fill="none" stroke="red"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B