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:
parent
ba7f4b5f18
commit
103dcc0a48
4 changed files with 63 additions and 13 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,))
|
||||||
|
|
|
||||||
18
testscour.py
18
testscour.py
|
|
@ -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):
|
||||||
|
|
|
||||||
7
unittests/path-elliptical-flags.svg
Normal file
7
unittests/path-elliptical-flags.svg
Normal 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 |
Loading…
Add table
Add a link
Reference in a new issue