Remove trailing zeros from path coordinates. Use scientific notation in path coords if shorter. Scour polygon coordinates just like path coordinates. Added tests
|
|
@ -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>
|
||||||
|
|
|
||||||
86
scour.py
|
|
@ -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) :
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
33
testscour.py
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 |
4
unittests/path-truncate-zeros-calc.svg
Normal 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 |
|
Before Width: | Height: | Size: 108 B After Width: | Height: | Size: 108 B |
4
unittests/path-use-scientific-notation.svg
Normal 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 |
4
unittests/polygon-coord.svg
Normal 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 |
|
|
@ -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 |