Remove all empty path segments. Scour polyline coordinates

This commit is contained in:
JSCHILL1 2009-08-03 08:50:48 -05:00
parent 46f86a0978
commit a1d9afb12f
2 changed files with 83 additions and 14 deletions

View file

@ -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') :

View file

@ -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