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
81
scour.py
81
scour.py
|
|
@ -31,15 +31,38 @@
|
|||
# * Process quadratic Bezier curves
|
||||
# * 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:
|
||||
# * 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)
|
||||
|
||||
# Next Up:
|
||||
# + remove duplicate gradients
|
||||
# - scour polyline coordinates just like path coordinates
|
||||
# - if after reducing precision we have duplicate path segments, then remove the duplicates and
|
||||
# leave it as a straight line segment
|
||||
# + remove all empty path segments
|
||||
# + scour polyline coordinates just like path 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)
|
||||
# - prevent elements from being stripped if they are referenced in a <style> element
|
||||
|
|
@ -312,13 +335,11 @@ class Unit(object):
|
|||
|
||||
class SVGLength(object):
|
||||
def __init__(self, str):
|
||||
# print "Parsing '%s'" % str
|
||||
try: # simple unitless and no scientific notation
|
||||
self.value = float(str)
|
||||
if int(self.value) == self.value:
|
||||
self.value = int(self.value)
|
||||
self.units = Unit.NONE
|
||||
# print " Value =", self.value
|
||||
except ValueError:
|
||||
# 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)
|
||||
|
||||
if unitBegin != 0 :
|
||||
# print " Value =", self.value
|
||||
unitMatch = unit.search(str, unitBegin)
|
||||
if unitMatch != None :
|
||||
self.units = Unit.get(unitMatch.group(0))
|
||||
# print " Units =", self.units
|
||||
|
||||
# invalid
|
||||
else:
|
||||
# TODO: this needs to set the default for the given attribute (how?)
|
||||
# print " Invalid: ", str
|
||||
self.value = 0
|
||||
self.units = Unit.INVALID
|
||||
|
||||
|
|
@ -1232,7 +1250,6 @@ def cleanPath(element) :
|
|||
path = newPath
|
||||
|
||||
# remove empty segments
|
||||
# TODO: q, t, a
|
||||
newPath = [path[0]]
|
||||
for (cmd,data) in path[1:]:
|
||||
if cmd in ['m','l','t']:
|
||||
|
|
@ -1251,8 +1268,7 @@ def cleanPath(element) :
|
|||
newData = []
|
||||
i = 0
|
||||
while i < len(data):
|
||||
if data[i] != 0 or data[i+1] != 0 or data[i+2] != 0 or \
|
||||
data[i+3] != 0 or data[i+4] != 0 or data[i+5] != 0:
|
||||
if data[i+4] != 0 or data[i+5] != 0:
|
||||
newData.append(data[i])
|
||||
newData.append(data[i+1])
|
||||
newData.append(data[i+2])
|
||||
|
|
@ -1264,6 +1280,37 @@ def cleanPath(element) :
|
|||
i += 6
|
||||
if 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']:
|
||||
newData = []
|
||||
i = 0
|
||||
|
|
@ -1451,7 +1498,13 @@ def cleanPolygon(elem):
|
|||
if startx == endx and starty == endy:
|
||||
pts = pts[:-2]
|
||||
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))
|
||||
|
||||
def serializePath(pathObj):
|
||||
|
|
@ -1691,10 +1744,14 @@ def scourString(in_string, options=None):
|
|||
else:
|
||||
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') :
|
||||
cleanPolygon(polygon)
|
||||
|
||||
# scour points of polyline
|
||||
for polyline in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'polyline') :
|
||||
cleanPolygon(polyline)
|
||||
|
||||
# convert rasters references to base64-encoded strings
|
||||
if options.embed_rasters:
|
||||
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',
|
||||
'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):
|
||||
def runTest(self):
|
||||
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'),
|
||||
'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 --disable-embed-rasters
|
||||
# TODO: write tests for --keep-editor-data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue