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>
|
||||
|
||||
<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">
|
||||
<header>
|
||||
<h2><a href="#0.15">Version 0.15</a></h2>
|
||||
|
|
|
|||
96
scour.py
|
|
@ -39,10 +39,14 @@
|
|||
|
||||
# Next Up:
|
||||
# + 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
|
||||
# - 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
|
||||
# (for instance, filter, marker, pattern) - need a crude CSS parser
|
||||
# - Remove any unused glyphs from font elements?
|
||||
|
|
@ -69,7 +73,7 @@ except ImportError:
|
|||
Decimal = FixedPoint
|
||||
|
||||
APP = 'scour'
|
||||
VER = '0.15'
|
||||
VER = '0.16'
|
||||
COPYRIGHT = 'Copyright Jeff Schiller, 2009'
|
||||
|
||||
NS = { 'SVG': 'http://www.w3.org/2000/svg',
|
||||
|
|
@ -956,9 +960,9 @@ def cleanPath(element) :
|
|||
# one or more tuples, each containing two numbers
|
||||
nums = []
|
||||
for t in dataset:
|
||||
# convert to a Decimal and ensure precision
|
||||
nums.append(Decimal(str(t[0])) * Decimal(1))
|
||||
nums.append(Decimal(str(t[1])) * Decimal(1))
|
||||
# convert to a Decimal
|
||||
nums.append(Decimal(str(t[0])))
|
||||
nums.append(Decimal(str(t[1])))
|
||||
|
||||
# only create this segment if it is not empty
|
||||
if nums:
|
||||
|
|
@ -1339,7 +1343,7 @@ def parseListOfPoints(s):
|
|||
"""
|
||||
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)?
|
||||
|
|
@ -1356,7 +1360,8 @@ def parseListOfPoints(s):
|
|||
|
||||
# if the coordinates were not unitless, return empty
|
||||
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
|
||||
|
||||
return points
|
||||
|
|
@ -1368,41 +1373,70 @@ def cleanPolygon(elem):
|
|||
global numPointsRemovedFromPolygon
|
||||
|
||||
pts = parseListOfPoints(elem.getAttribute('points'))
|
||||
N = len(pts)
|
||||
N = len(pts)/2
|
||||
if N >= 2:
|
||||
(startx,starty) = (pts[0][0],pts[0][1])
|
||||
(endx,endy) = (pts[N-1][0],pts[N-1][1])
|
||||
(startx,starty) = (pts[0],pts[0])
|
||||
(endx,endy) = (pts[len(pts)-2],pts[len(pts)-1])
|
||||
if startx == endx and starty == endy:
|
||||
str = ''
|
||||
for pt in pts[:-1]:
|
||||
str += (pt[0] + ',' + pt[1] + ' ')
|
||||
elem.setAttribute('points', str[:-1])
|
||||
pts = pts[:-2]
|
||||
numPointsRemovedFromPolygon += 1
|
||||
|
||||
elem.setAttribute('points', scourCoordinates(pts))
|
||||
|
||||
def serializePath(pathObj):
|
||||
"""
|
||||
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
|
||||
Reserializes the path data with some cleanups.
|
||||
"""
|
||||
pathStr = ""
|
||||
for (cmd,data) in pathObj:
|
||||
pathStr += cmd
|
||||
if data != None:
|
||||
c = 0
|
||||
for coord in data:
|
||||
# if coord can be an integer without loss of precision, go for it
|
||||
if int(coord) == coord: pathStr += str(int(coord))
|
||||
else: pathStr += str(coord)
|
||||
|
||||
# only need the comma if the next number is non-negative
|
||||
if c < len(data)-1 and data[c+1] >= 0:
|
||||
pathStr += ','
|
||||
c += 1
|
||||
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:
|
||||
c = 0
|
||||
for coord in data:
|
||||
if int(coord) == coord: coord = Decimal(str(int(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
|
||||
if c < len(data)-1 and Decimal(data[c+1]) >= 0:
|
||||
coordsStr += ','
|
||||
c += 1
|
||||
return coordsStr
|
||||
|
||||
def embedRasters(element, options) :
|
||||
"""
|
||||
Converts raster references to inline images.
|
||||
|
|
|
|||
33
testscour.py
|
|
@ -470,20 +470,34 @@ class DoNotCollapseMultiplyReferencedGradients(unittest.TestCase):
|
|||
self.assertNotEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0,
|
||||
'Multiply-referenced linear gradient collapsed' )
|
||||
|
||||
class RemoveTrailingZeroesFromPath(unittest.TestCase):
|
||||
class RemoveTrailingZerosFromPath(unittest.TestCase):
|
||||
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')
|
||||
self.assertEquals(path[:4] == 'M300' and path[4] != '.', True,
|
||||
'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):
|
||||
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')
|
||||
self.assertEquals(path[4], '-',
|
||||
'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):
|
||||
def runTest(self):
|
||||
doc = scour.scourXmlFile('unittests/path-abs-to-rel.svg')
|
||||
|
|
@ -506,7 +520,7 @@ class LimitPrecisionInPathData(unittest.TestCase):
|
|||
def runTest(self):
|
||||
doc = scour.scourXmlFile('unittests/path-precision.svg')
|
||||
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' )
|
||||
|
||||
class RemoveEmptyLineSegmentsFromPath(unittest.TestCase):
|
||||
|
|
@ -616,15 +630,21 @@ class ConvertStraightCurvesToLines(unittest.TestCase):
|
|||
class RemoveUnnecessaryPolgonEndPoint(unittest.TestCase):
|
||||
def runTest(self):
|
||||
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' )
|
||||
|
||||
class DoNotRemovePolgonLastPoint(unittest.TestCase):
|
||||
def runTest(self):
|
||||
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' )
|
||||
|
||||
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):
|
||||
def runTest(self):
|
||||
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',
|
||||
'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 --disable-embed-rasters
|
||||
# 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">
|
||||
<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>
|
||||
|
|
|
|||
|
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">
|
||||
<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" />
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 244 B |