Remove all empty path segments. Scour polyline coordinates
This commit is contained in:
parent
46f86a0978
commit
a1d9afb12f
2 changed files with 83 additions and 14 deletions
85
scour.py
85
scour.py
|
|
@ -31,15 +31,38 @@
|
||||||
# * Process quadratic Bezier curves
|
# * Process quadratic Bezier curves
|
||||||
# * Collapse all group based transformations
|
# * Collapse all group based transformations
|
||||||
|
|
||||||
|
# Even more ideas here: http://esw.w3.org/topic/SvgTidy
|
||||||
|
# * namespace cleanup <svg:path xmlns:svg="..."/> -> <path />
|
||||||
|
# * removal of more default attribute values (gradientUnits, spreadMethod, x1, y1, etc)
|
||||||
|
# * analysis of path elements to see if rect can be used instead?
|
||||||
|
# * removal of unused attributes in groups:
|
||||||
|
# <g fill="blue" ...>
|
||||||
|
# <rect fill="red" ... />
|
||||||
|
# <rect fill="red" ... />
|
||||||
|
# <rect fill="red" ... />
|
||||||
|
# </g>
|
||||||
|
# in this case, fill="blue" should be removed
|
||||||
|
# * Move common attributes up to a parent group:
|
||||||
|
# <g>
|
||||||
|
# <rect fill="white"/>
|
||||||
|
# <rect fill="white"/>
|
||||||
|
# <rect fill="white"/>
|
||||||
|
# </g>
|
||||||
|
# becomes:
|
||||||
|
# <g fill="white">
|
||||||
|
# <rect />
|
||||||
|
# <rect />
|
||||||
|
# <rect />
|
||||||
|
# </g>
|
||||||
|
|
||||||
# Suggestion from Richard Hutch:
|
# Suggestion from Richard Hutch:
|
||||||
# * Put id attributes first in the serialization (or make the d attribute last)
|
# * Put id attributes first in the serialization (or make the d attribute last)
|
||||||
# This would require my own serialization of the DOM objects (not impossible)
|
# This would require my own serialization of the DOM objects (not impossible)
|
||||||
|
|
||||||
# Next Up:
|
# Next Up:
|
||||||
# + remove duplicate gradients
|
# + remove duplicate gradients
|
||||||
# - scour polyline coordinates just like path coordinates
|
# + remove all empty path segments
|
||||||
# - if after reducing precision we have duplicate path segments, then remove the duplicates and
|
# + scour polyline coordinates just like path coordinates
|
||||||
# leave it as a straight line segment
|
|
||||||
# - enable the precision argument to affect all numbers: polygon points, lengths, coordinates
|
# - enable the precision argument to affect all numbers: polygon points, lengths, coordinates
|
||||||
# - remove id if it matches the Inkscape-style of IDs (also provide a switch to disable this)
|
# - remove id if it matches the Inkscape-style of IDs (also provide a switch to disable this)
|
||||||
# - prevent elements from being stripped if they are referenced in a <style> element
|
# - prevent elements from being stripped if they are referenced in a <style> element
|
||||||
|
|
@ -312,13 +335,11 @@ class Unit(object):
|
||||||
|
|
||||||
class SVGLength(object):
|
class SVGLength(object):
|
||||||
def __init__(self, str):
|
def __init__(self, str):
|
||||||
# print "Parsing '%s'" % str
|
|
||||||
try: # simple unitless and no scientific notation
|
try: # simple unitless and no scientific notation
|
||||||
self.value = float(str)
|
self.value = float(str)
|
||||||
if int(self.value) == self.value:
|
if int(self.value) == self.value:
|
||||||
self.value = int(self.value)
|
self.value = int(self.value)
|
||||||
self.units = Unit.NONE
|
self.units = Unit.NONE
|
||||||
# print " Value =", self.value
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# we know that the length string has an exponent, a unit, both or is invalid
|
# we know that the length string has an exponent, a unit, both or is invalid
|
||||||
|
|
||||||
|
|
@ -344,16 +365,13 @@ class SVGLength(object):
|
||||||
self.value = int(self.value)
|
self.value = int(self.value)
|
||||||
|
|
||||||
if unitBegin != 0 :
|
if unitBegin != 0 :
|
||||||
# print " Value =", self.value
|
|
||||||
unitMatch = unit.search(str, unitBegin)
|
unitMatch = unit.search(str, unitBegin)
|
||||||
if unitMatch != None :
|
if unitMatch != None :
|
||||||
self.units = Unit.get(unitMatch.group(0))
|
self.units = Unit.get(unitMatch.group(0))
|
||||||
# print " Units =", self.units
|
|
||||||
|
|
||||||
# invalid
|
# invalid
|
||||||
else:
|
else:
|
||||||
# TODO: this needs to set the default for the given attribute (how?)
|
# TODO: this needs to set the default for the given attribute (how?)
|
||||||
# print " Invalid: ", str
|
|
||||||
self.value = 0
|
self.value = 0
|
||||||
self.units = Unit.INVALID
|
self.units = Unit.INVALID
|
||||||
|
|
||||||
|
|
@ -1232,7 +1250,6 @@ def cleanPath(element) :
|
||||||
path = newPath
|
path = newPath
|
||||||
|
|
||||||
# remove empty segments
|
# remove empty segments
|
||||||
# TODO: q, t, a
|
|
||||||
newPath = [path[0]]
|
newPath = [path[0]]
|
||||||
for (cmd,data) in path[1:]:
|
for (cmd,data) in path[1:]:
|
||||||
if cmd in ['m','l','t']:
|
if cmd in ['m','l','t']:
|
||||||
|
|
@ -1251,8 +1268,7 @@ def cleanPath(element) :
|
||||||
newData = []
|
newData = []
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(data):
|
while i < len(data):
|
||||||
if data[i] != 0 or data[i+1] != 0 or data[i+2] != 0 or \
|
if data[i+4] != 0 or data[i+5] != 0:
|
||||||
data[i+3] != 0 or data[i+4] != 0 or data[i+5] != 0:
|
|
||||||
newData.append(data[i])
|
newData.append(data[i])
|
||||||
newData.append(data[i+1])
|
newData.append(data[i+1])
|
||||||
newData.append(data[i+2])
|
newData.append(data[i+2])
|
||||||
|
|
@ -1264,6 +1280,37 @@ def cleanPath(element) :
|
||||||
i += 6
|
i += 6
|
||||||
if newData:
|
if newData:
|
||||||
newPath.append( (cmd,newData) )
|
newPath.append( (cmd,newData) )
|
||||||
|
elif cmd == 'a':
|
||||||
|
newData = []
|
||||||
|
i = 0
|
||||||
|
while i < len(data):
|
||||||
|
if data[i+5] != 0 or data[i+6] != 0:
|
||||||
|
newData.append(data[i])
|
||||||
|
newData.append(data[i+1])
|
||||||
|
newData.append(data[i+2])
|
||||||
|
newData.append(data[i+3])
|
||||||
|
newData.append(data[i+4])
|
||||||
|
newData.append(data[i+5])
|
||||||
|
newData.append(data[i+6])
|
||||||
|
else:
|
||||||
|
numPathSegmentsReduced += 1
|
||||||
|
i += 7
|
||||||
|
if newData:
|
||||||
|
newPath.append( (cmd,newData) )
|
||||||
|
elif cmd == 'q':
|
||||||
|
newData = []
|
||||||
|
i = 0
|
||||||
|
while i < len(data):
|
||||||
|
if data[i+2] != 0 or data[i+3] != 0:
|
||||||
|
newData.append(data[i])
|
||||||
|
newData.append(data[i+1])
|
||||||
|
newData.append(data[i+2])
|
||||||
|
newData.append(data[i+3])
|
||||||
|
else:
|
||||||
|
numPathSegmentsReduced += 1
|
||||||
|
i += 4
|
||||||
|
if newData:
|
||||||
|
newPath.append( (cmd,newData) )
|
||||||
elif cmd in ['h','v']:
|
elif cmd in ['h','v']:
|
||||||
newData = []
|
newData = []
|
||||||
i = 0
|
i = 0
|
||||||
|
|
@ -1450,8 +1497,14 @@ def cleanPolygon(elem):
|
||||||
(endx,endy) = (pts[len(pts)-2],pts[len(pts)-1])
|
(endx,endy) = (pts[len(pts)-2],pts[len(pts)-1])
|
||||||
if startx == endx and starty == endy:
|
if startx == endx and starty == endy:
|
||||||
pts = pts[:-2]
|
pts = pts[:-2]
|
||||||
numPointsRemovedFromPolygon += 1
|
numPointsRemovedFromPolygon += 1
|
||||||
|
elem.setAttribute('points', scourCoordinates(pts))
|
||||||
|
|
||||||
|
def cleanPolyline(elem):
|
||||||
|
"""
|
||||||
|
Scour the polyline points attribute
|
||||||
|
"""
|
||||||
|
pts = parseListOfPoints(elem.getAttribute('points'))
|
||||||
elem.setAttribute('points', scourCoordinates(pts))
|
elem.setAttribute('points', scourCoordinates(pts))
|
||||||
|
|
||||||
def serializePath(pathObj):
|
def serializePath(pathObj):
|
||||||
|
|
@ -1691,10 +1744,14 @@ def scourString(in_string, options=None):
|
||||||
else:
|
else:
|
||||||
cleanPath(elem)
|
cleanPath(elem)
|
||||||
|
|
||||||
# remove unnecessary closing point of polygons
|
# remove unnecessary closing point of polygons and scour points
|
||||||
for polygon in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'polygon') :
|
for polygon in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'polygon') :
|
||||||
cleanPolygon(polygon)
|
cleanPolygon(polygon)
|
||||||
|
|
||||||
|
# scour points of polyline
|
||||||
|
for polyline in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'polyline') :
|
||||||
|
cleanPolygon(polyline)
|
||||||
|
|
||||||
# convert rasters references to base64-encoded strings
|
# convert rasters references to base64-encoded strings
|
||||||
if options.embed_rasters:
|
if options.embed_rasters:
|
||||||
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'image') :
|
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'image') :
|
||||||
|
|
|
||||||
12
testscour.py
12
testscour.py
|
|
@ -652,6 +652,12 @@ class ScourPolygonCoordinates(unittest.TestCase):
|
||||||
self.assertEquals(p.getAttribute('points'), '1E+4-50',
|
self.assertEquals(p.getAttribute('points'), '1E+4-50',
|
||||||
'Polygon coordinates not scoured')
|
'Polygon coordinates not scoured')
|
||||||
|
|
||||||
|
class ScourPolylineCoordinates(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
p = scour.scourXmlFile('unittests/polyline-coord.svg').getElementsByTagNameNS(SVGNS, 'polyline')[0]
|
||||||
|
self.assertEquals(p.getAttribute('points'), '1E+4-50',
|
||||||
|
'Polyline coordinates not scoured')
|
||||||
|
|
||||||
class DoNotRemoveGroupsWithIDsInDefs(unittest.TestCase):
|
class DoNotRemoveGroupsWithIDsInDefs(unittest.TestCase):
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
f = scour.scourXmlFile('unittests/important-groups-in-defs.svg')
|
f = scour.scourXmlFile('unittests/important-groups-in-defs.svg')
|
||||||
|
|
@ -694,6 +700,12 @@ class RereferenceForRadialGradient(unittest.TestCase):
|
||||||
self.assertEquals(rects[2].getAttribute('stroke'), rects[3].getAttribute('fill'),
|
self.assertEquals(rects[2].getAttribute('stroke'), rects[3].getAttribute('fill'),
|
||||||
'Rect not changed after removing duplicate radial gradient')
|
'Rect not changed after removing duplicate radial gradient')
|
||||||
|
|
||||||
|
class CollapseSamePathPoints(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
p = scour.scourXmlFile('unittests/collapse-same-path-points.svg').getElementsByTagNameNS(SVGNS, 'path')[0];
|
||||||
|
self.assertEquals(p.getAttribute('d'), "M100,100l100.12,100.12z",
|
||||||
|
'Did not collapse same path points')
|
||||||
|
|
||||||
# TODO; write a test for embedding rasters
|
# TODO; write a test for embedding rasters
|
||||||
# TODO: write a test for --disable-embed-rasters
|
# TODO: write a test for --disable-embed-rasters
|
||||||
# TODO: write tests for --keep-editor-data
|
# TODO: write tests for --keep-editor-data
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue