Remove trailing zeros from path coordinates. Use scientific notation in path coords if shorter. Scour polygon coordinates just like path coordinates. Added tests

This commit is contained in:
JSCHILL1 2009-07-29 12:27:51 -05:00
parent 8f50f0d509
commit b28ae6ec8d
9 changed files with 119 additions and 39 deletions

View file

@ -9,6 +9,19 @@
<p>Copyright 2009, Jeff Schiller</p> <p>Copyright 2009, Jeff Schiller</p>
<section id="0.16">
<header>
<h2><a href="#0.16">Version 0.16</a></h2>
</header>
<p>July 29th, 2009</p>
<ul>
<li>Fix <a href="https://bugs.launchpad.net/scour/+bug/401628">Bug 401628</a>: Keep namespace declarations when using --keep-editor-data (Thanks YoNoSoyTu!)</li>
<li>Remove trailing zeros after decimal places for all path coordinates</li>
<li>Use scientific notation in path coordinates if that representation is shorter</li>
<li>Scour polygon coordinates just like path coordinates</li>
</ul>
</section>
<section id="0.15"> <section id="0.15">
<header> <header>
<h2><a href="#0.15">Version 0.15</a></h2> <h2><a href="#0.15">Version 0.15</a></h2>

View file

@ -39,10 +39,14 @@
# Next Up: # Next Up:
# + add option to keep inkscape, adobe, sodipodi elements and attributes # + add option to keep inkscape, adobe, sodipodi elements and attributes
# - ensure a really good understanding of prec vs. quantize and what I want --set-precision to do # + if any path coordinate has decimal places, remove any trailing zeros
# + use scientific notation is shorter in path coordinates
# + scour polygon coordinates just like path coordinates
# - 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
# - 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)
# - convert polygons/polylines to path? (actually the change in semantics may not be worth the marginal savings)
# - 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
# (for instance, filter, marker, pattern) - need a crude CSS parser # (for instance, filter, marker, pattern) - need a crude CSS parser
# - Remove any unused glyphs from font elements? # - Remove any unused glyphs from font elements?
@ -69,7 +73,7 @@ except ImportError:
Decimal = FixedPoint Decimal = FixedPoint
APP = 'scour' APP = 'scour'
VER = '0.15' VER = '0.16'
COPYRIGHT = 'Copyright Jeff Schiller, 2009' COPYRIGHT = 'Copyright Jeff Schiller, 2009'
NS = { 'SVG': 'http://www.w3.org/2000/svg', NS = { 'SVG': 'http://www.w3.org/2000/svg',
@ -956,9 +960,9 @@ def cleanPath(element) :
# one or more tuples, each containing two numbers # one or more tuples, each containing two numbers
nums = [] nums = []
for t in dataset: for t in dataset:
# convert to a Decimal and ensure precision # convert to a Decimal
nums.append(Decimal(str(t[0])) * Decimal(1)) nums.append(Decimal(str(t[0])))
nums.append(Decimal(str(t[1])) * Decimal(1)) nums.append(Decimal(str(t[1])))
# only create this segment if it is not empty # only create this segment if it is not empty
if nums: if nums:
@ -1339,7 +1343,7 @@ def parseListOfPoints(s):
""" """
Parse string into a list of points. Parse string into a list of points.
Returns a list of (x,y) tuples where x and y are strings Returns a list of containing an even number of coordinate strings
""" """
# (wsp)? comma-or-wsp-separated coordinate pairs (wsp)? # (wsp)? comma-or-wsp-separated coordinate pairs (wsp)?
@ -1356,7 +1360,8 @@ def parseListOfPoints(s):
# if the coordinates were not unitless, return empty # if the coordinates were not unitless, return empty
if x.units != Unit.NONE or y.units != Unit.NONE: return [] if x.units != Unit.NONE or y.units != Unit.NONE: return []
points.append( (str(x.value),str(y.value)) ) points.append( str(x.value) )
points.append( str(y.value) )
i += 2 i += 2
return points return points
@ -1368,40 +1373,69 @@ def cleanPolygon(elem):
global numPointsRemovedFromPolygon global numPointsRemovedFromPolygon
pts = parseListOfPoints(elem.getAttribute('points')) pts = parseListOfPoints(elem.getAttribute('points'))
N = len(pts) N = len(pts)/2
if N >= 2: if N >= 2:
(startx,starty) = (pts[0][0],pts[0][1]) (startx,starty) = (pts[0],pts[0])
(endx,endy) = (pts[N-1][0],pts[N-1][1]) (endx,endy) = (pts[len(pts)-2],pts[len(pts)-1])
if startx == endx and starty == endy: if startx == endx and starty == endy:
str = '' pts = pts[:-2]
for pt in pts[:-1]:
str += (pt[0] + ',' + pt[1] + ' ')
elem.setAttribute('points', str[:-1])
numPointsRemovedFromPolygon += 1 numPointsRemovedFromPolygon += 1
elem.setAttribute('points', scourCoordinates(pts))
def serializePath(pathObj): def serializePath(pathObj):
""" """
Reserializes the path data with some cleanups: Reserializes the path data with some cleanups.
- removes scientific notation (exponents)
- removes all trailing zeros after the decimal
- removes extraneous whitespace
- adds commas between values in a subcommand if required
""" """
pathStr = "" pathStr = ""
for (cmd,data) in pathObj: for (cmd,data) in pathObj:
pathStr += cmd pathStr += cmd
pathStr += scourCoordinates(data)
return pathStr
def scourCoordinates(data):
"""
Serializes coordinate data with some cleanups:
- removes all trailing zeros after the decimal
- integerize coordinates if possible
- removes extraneous whitespace
- adds commas between values in a subcommand if required
"""
coordsStr = ""
if data != None: if data != None:
c = 0 c = 0
for coord in data: for coord in data:
# if coord can be an integer without loss of precision, go for it if int(coord) == coord: coord = Decimal(str(int(coord)))
if int(coord) == coord: pathStr += str(int(coord))
else: pathStr += str(coord) # Decimal.trim() is available in Python 2.6+ to trim trailing zeros
try:
coord = coord.trim()
except AttributeError:
# trim it ourselves
s = str(coord)
dec = s.find('.')
if dec != -1:
while s[-1] == '0':
s = s[:-1]
coord = Decimal(s)
# reduce to the proper number of digits
coord = coord * Decimal(1)
# Decimal.normalize() will uses scientific notation - if that
# string is smaller, then use it
normd = coord.normalize()
if len(str(normd)) < len(str(coord)):
coord = normd
# finally add the coordinate to the path string
coordsStr += str(coord)
# only need the comma if the next number is non-negative # only need the comma if the next number is non-negative
if c < len(data)-1 and data[c+1] >= 0: if c < len(data)-1 and Decimal(data[c+1]) >= 0:
pathStr += ',' coordsStr += ','
c += 1 c += 1
return pathStr return coordsStr
def embedRasters(element, options) : def embedRasters(element, options) :
""" """

View file

@ -470,20 +470,34 @@ class DoNotCollapseMultiplyReferencedGradients(unittest.TestCase):
self.assertNotEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0, self.assertNotEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0,
'Multiply-referenced linear gradient collapsed' ) 'Multiply-referenced linear gradient collapsed' )
class RemoveTrailingZeroesFromPath(unittest.TestCase): class RemoveTrailingZerosFromPath(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-truncate-zeroes.svg') doc = scour.scourXmlFile('unittests/path-truncate-zeros.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d') path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path[:4] == 'M300' and path[4] != '.', True, self.assertEquals(path[:4] == 'M300' and path[4] != '.', True,
'Trailing zeros not removed from path data' ) 'Trailing zeros not removed from path data' )
class RemoveTrailingZerosFromPathAfterCalculation(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/path-truncate-zeros-calc.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path, 'M5.81,0h0.1',
'Trailing zeros not removed from path data after calculation' )
class RemoveDelimiterBeforeNegativeCoordsInPath(unittest.TestCase): class RemoveDelimiterBeforeNegativeCoordsInPath(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-truncate-zeroes.svg') doc = scour.scourXmlFile('unittests/path-truncate-zeros.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d') path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path[4], '-', self.assertEquals(path[4], '-',
'Delimiters not removed before negative coordinates in path data' ) 'Delimiters not removed before negative coordinates in path data' )
class UseScientificNotationToShortenCoordsInPath(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/path-use-scientific-notation.svg')
path = doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')
self.assertEquals(path, 'M1E+4,0',
'Not using scientific notation for path coord when representation is shorter')
class ConvertAbsoluteToRelativePathCommands(unittest.TestCase): class ConvertAbsoluteToRelativePathCommands(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-abs-to-rel.svg') doc = scour.scourXmlFile('unittests/path-abs-to-rel.svg')
@ -506,7 +520,7 @@ class LimitPrecisionInPathData(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/path-precision.svg') doc = scour.scourXmlFile('unittests/path-precision.svg')
path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d')) path = svg_parser.parse(doc.getElementsByTagNameNS(SVGNS, 'path')[0].getAttribute('d'))
self.assertEquals(path[1][1][0], 100.001, self.assertEquals(path[1][1][0], 100.01,
'Not correctly limiting precision on path data' ) 'Not correctly limiting precision on path data' )
class RemoveEmptyLineSegmentsFromPath(unittest.TestCase): class RemoveEmptyLineSegmentsFromPath(unittest.TestCase):
@ -616,15 +630,21 @@ class ConvertStraightCurvesToLines(unittest.TestCase):
class RemoveUnnecessaryPolgonEndPoint(unittest.TestCase): class RemoveUnnecessaryPolgonEndPoint(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0] p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0]
self.assertEquals(p.getAttribute('points'), '50,50 150,50 150,150 50,150', self.assertEquals(p.getAttribute('points'), '50,50,150,50,150,150,50,150',
'Unnecessary polygon end point not removed' ) 'Unnecessary polygon end point not removed' )
class DoNotRemovePolgonLastPoint(unittest.TestCase): class DoNotRemovePolgonLastPoint(unittest.TestCase):
def runTest(self): def runTest(self):
p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[1] p = scour.scourXmlFile('unittests/polygon.svg').getElementsByTagNameNS(SVGNS, 'polygon')[1]
self.assertEquals(p.getAttribute('points'), '200,50 300,50 300,150 200,150', self.assertEquals(p.getAttribute('points'), '200,50,300,50,300,150,200,150',
'Last point of polygon removed' ) 'Last point of polygon removed' )
class ScourPolygonCoordinates(unittest.TestCase):
def runTest(self):
p = scour.scourXmlFile('unittests/polygon-coord.svg').getElementsByTagNameNS(SVGNS, 'polygon')[0]
self.assertEquals(p.getAttribute('points'), '1E+4-50',
'Polygon 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')
@ -637,7 +657,6 @@ class AlwaysKeepClosePathSegments(unittest.TestCase):
self.assertEquals(p.getAttribute('d'), 'M10,10h100v100h-100z', self.assertEquals(p.getAttribute('d'), 'M10,10h100v100h-100z',
'Path with closepath not preserved') 'Path with closepath not preserved')
# TODO: write tests for --set-precision for path data, for polygon data, for attributes
# 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

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1"> <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1">
<path d="M 100.0000001 99.9999999 h100.001 v123456789.123456789 h-100 z" fill="red" /> <path d="M 100.0000001 99.9999999 h100.01 v123456789.123456789 h-100 z" fill="red" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 227 B

Before After
Before After

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="5 0 3 3">
<path stroke="blue" stroke-width="0.01" fill="none" d="M5.81,0 H5.91000" />
</svg>

After

Width:  |  Height:  |  Size: 199 B

View file

Before

Width:  |  Height:  |  Size: 108 B

After

Width:  |  Height:  |  Size: 108 B

Before After
Before After

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="5 0 3 3">
<path d="M10000,0" />
</svg>

After

Width:  |  Height:  |  Size: 145 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg">
<polygon fill="blue" points="10000,-50" />
</svg>

After

Width:  |  Height:  |  Size: 146 B

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<polygon fill="blue" points="50,50 150,50 150,150 50,150 +5e1,500.00e-1" /> <polygon fill="blue" points="50,50 150,50 150,150 50,150 +5e1,500.00e-1" />
<polygon fill="green" points="200,50 300,50 300,150 200,150" /> <polygon fill="green" points="200,50 300,50 300,150 200,150" />

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 244 B

Before After
Before After