Handle stop offsets in percentages. Convert stop offsets into floating point (or integer if possible)
This commit is contained in:
parent
efbdd3a027
commit
321f16c46d
3 changed files with 117 additions and 86 deletions
185
scour.py
185
scour.py
|
|
@ -45,6 +45,7 @@
|
||||||
# * 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)
|
||||||
|
|
||||||
# Next Up:
|
# Next Up:
|
||||||
|
# - deal with gradient stops with offsets in percentages
|
||||||
# - implement command-line option to output svgz
|
# - implement command-line option to output svgz
|
||||||
# - Remove unnecessary units of precision on attributes (use decimal: http://docs.python.org/library/decimal.html)
|
# - Remove unnecessary units of precision on attributes (use decimal: http://docs.python.org/library/decimal.html)
|
||||||
# - Convert all colors to #RRGGBB format
|
# - Convert all colors to #RRGGBB format
|
||||||
|
|
@ -123,6 +124,92 @@ svgAttributes = [
|
||||||
'visibility'
|
'visibility'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
coord = re.compile("\\-?\\d+\\.?\\d*")
|
||||||
|
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
|
||||||
|
number = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+")
|
||||||
|
sciExponent = re.compile("[eE]([\\-\\+]?\\d+)")
|
||||||
|
unit = re.compile("(em|ex|px|pt|pc|cm|mm|in|\\%){1,1}$")
|
||||||
|
|
||||||
|
class Unit:
|
||||||
|
INVALID = -1
|
||||||
|
NONE = 0
|
||||||
|
PCT = 1
|
||||||
|
PX = 2
|
||||||
|
PT = 3
|
||||||
|
PC = 4
|
||||||
|
EM = 5
|
||||||
|
EX = 6
|
||||||
|
CM = 7
|
||||||
|
MM = 8
|
||||||
|
IN = 9
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(str):
|
||||||
|
if str == None or str == '': return Unit.NONE
|
||||||
|
elif str == '%': return Unit.PCT
|
||||||
|
elif str == 'px': return Unit.PX
|
||||||
|
elif str == 'pt': return Unit.PT
|
||||||
|
elif str == 'pc': return Unit.PC
|
||||||
|
elif str == 'em': return Unit.EM
|
||||||
|
elif str == 'ex': return Unit.EX
|
||||||
|
elif str == 'cm': return Unit.CM
|
||||||
|
elif str == 'mm': return Unit.MM
|
||||||
|
elif str == 'in': return Unit.IN
|
||||||
|
return Unit.INVALID
|
||||||
|
|
||||||
|
class SVGLength:
|
||||||
|
def __init__(self, str):
|
||||||
|
# print "Parsing '%s'" % str
|
||||||
|
try: # simple unitless and no scientific notation
|
||||||
|
self.value = string.atof(str)
|
||||||
|
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
|
||||||
|
|
||||||
|
# TODO: parse out number, exponent and unit
|
||||||
|
unitBegin = 0
|
||||||
|
scinum = scinumber.match(str)
|
||||||
|
if scinum != None:
|
||||||
|
# this will always match, no need to check it
|
||||||
|
numMatch = number.match(str)
|
||||||
|
expMatch = sciExponent.search(str, numMatch.start(0))
|
||||||
|
self.value = string.atof(numMatch.group(0)) * math.pow(10, string.atof(expMatch.group(1)))
|
||||||
|
unitBegin = expMatch.end(1)
|
||||||
|
else:
|
||||||
|
# unit or invalid
|
||||||
|
numMatch = number.match(str)
|
||||||
|
if numMatch != None:
|
||||||
|
self.value = string.atof(numMatch.group(0))
|
||||||
|
unitBegin = numMatch.end(0)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# returns the length of a property
|
||||||
|
# TODO: eventually use the above class once it is complete
|
||||||
|
def getSVGLength(value):
|
||||||
|
try:
|
||||||
|
v = string.atof(value)
|
||||||
|
except ValueError:
|
||||||
|
coordMatch = coord.match(value)
|
||||||
|
if coordMatch != None:
|
||||||
|
unitMatch = unit.search(value, coordMatch.start(0))
|
||||||
|
v = value
|
||||||
|
return v
|
||||||
|
|
||||||
def findElementById(node, id):
|
def findElementById(node, id):
|
||||||
if node == None or node.nodeType != 1: return None
|
if node == None or node.nodeType != 1: return None
|
||||||
if node.getAttribute('id') == id: return node
|
if node.getAttribute('id') == id: return node
|
||||||
|
|
@ -330,7 +417,18 @@ def removeDuplicateGradientStops(doc):
|
||||||
stops = {}
|
stops = {}
|
||||||
stopsToRemove = []
|
stopsToRemove = []
|
||||||
for stop in grad.getElementsByTagNameNS(NS['SVG'], 'stop'):
|
for stop in grad.getElementsByTagNameNS(NS['SVG'], 'stop'):
|
||||||
offset = string.atof(stop.getAttribute('offset'))
|
# convert percentages into a floating point number
|
||||||
|
offsetU = SVGLength(stop.getAttribute('offset'))
|
||||||
|
if offsetU.units == Unit.PCT:
|
||||||
|
offset = offsetU.value / 100.0
|
||||||
|
elif offsetU.units == Unit.NONE:
|
||||||
|
offset = offsetU.value
|
||||||
|
else:
|
||||||
|
offset = 0
|
||||||
|
# set the stop offset value to the integer or floating point equivalent
|
||||||
|
if int(offset) == offset: stop.setAttribute('offset', str(int(offset)))
|
||||||
|
else: stop.setAttribute('offset', str(offset))
|
||||||
|
|
||||||
color = stop.getAttribute('stop-color')
|
color = stop.getAttribute('stop-color')
|
||||||
opacity = stop.getAttribute('stop-opacity')
|
opacity = stop.getAttribute('stop-opacity')
|
||||||
if stops.has_key(offset) :
|
if stops.has_key(offset) :
|
||||||
|
|
@ -401,91 +499,6 @@ def collapseSinglyReferencedGradients(doc):
|
||||||
num += 1
|
num += 1
|
||||||
|
|
||||||
return num
|
return num
|
||||||
|
|
||||||
coord = re.compile("\\-?\\d+\\.?\\d*")
|
|
||||||
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
|
|
||||||
number = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+")
|
|
||||||
sciExponent = re.compile("[eE]([\\-\\+]?\\d+)")
|
|
||||||
unit = re.compile("(em|ex|px|pt|pc|cm|mm|in|\\%){1,1}$")
|
|
||||||
|
|
||||||
class Unit:
|
|
||||||
INVALID = -1
|
|
||||||
NONE = 0
|
|
||||||
PCT = 1
|
|
||||||
PX = 2
|
|
||||||
PT = 3
|
|
||||||
PC = 4
|
|
||||||
EM = 5
|
|
||||||
EX = 6
|
|
||||||
CM = 7
|
|
||||||
MM = 8
|
|
||||||
IN = 9
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(str):
|
|
||||||
if str == None or str == '': return Unit.NONE
|
|
||||||
elif str == '%': return Unit.PCT
|
|
||||||
elif str == 'px': return Unit.PX
|
|
||||||
elif str == 'pt': return Unit.PT
|
|
||||||
elif str == 'pc': return Unit.PC
|
|
||||||
elif str == 'em': return Unit.EM
|
|
||||||
elif str == 'ex': return Unit.EX
|
|
||||||
elif str == 'cm': return Unit.CM
|
|
||||||
elif str == 'mm': return Unit.MM
|
|
||||||
elif str == 'in': return Unit.IN
|
|
||||||
return Unit.INVALID
|
|
||||||
|
|
||||||
class SVGLength:
|
|
||||||
def __init__(self, str):
|
|
||||||
# print "Parsing '%s'" % str
|
|
||||||
try: # simple unitless and no scientific notation
|
|
||||||
self.value = string.atof(str)
|
|
||||||
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
|
|
||||||
|
|
||||||
# TODO: parse out number, exponent and unit
|
|
||||||
unitBegin = 0
|
|
||||||
scinum = scinumber.match(str)
|
|
||||||
if scinum != None:
|
|
||||||
# this will always match, no need to check it
|
|
||||||
numMatch = number.match(str)
|
|
||||||
expMatch = sciExponent.search(str, numMatch.start(0))
|
|
||||||
self.value = string.atof(numMatch.group(0)) * math.pow(10, string.atof(expMatch.group(1)))
|
|
||||||
unitBegin = expMatch.end(1)
|
|
||||||
else:
|
|
||||||
# unit or invalid
|
|
||||||
numMatch = number.match(str)
|
|
||||||
if numMatch != None:
|
|
||||||
self.value = string.atof(numMatch.group(0))
|
|
||||||
unitBegin = numMatch.end(0)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# returns the length of a property
|
|
||||||
# TODO: eventually use the above class once it is complete
|
|
||||||
def getSVGLength(value):
|
|
||||||
try:
|
|
||||||
v = string.atof(value)
|
|
||||||
except ValueError:
|
|
||||||
coordMatch = coord.match(value)
|
|
||||||
if coordMatch != None:
|
|
||||||
unitMatch = unit.search(value, coordMatch.start(0))
|
|
||||||
v = value
|
|
||||||
return v
|
|
||||||
|
|
||||||
def repairStyle(node):
|
def repairStyle(node):
|
||||||
num = 0
|
num = 0
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,13 @@ class RemoveDuplicateLinearGradientStops(unittest.TestCase):
|
||||||
self.assertEquals(len(grad[0].getElementsByTagNameNS(SVGNS, 'stop')), 3,
|
self.assertEquals(len(grad[0].getElementsByTagNameNS(SVGNS, 'stop')), 3,
|
||||||
'Duplicate linear gradient stops not removed' )
|
'Duplicate linear gradient stops not removed' )
|
||||||
|
|
||||||
|
class RemoveDuplicateLinearGradientStopsPct(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/duplicate-gradient-stops-pct.svg')
|
||||||
|
grad = doc.getElementsByTagNameNS(SVGNS, 'linearGradient')
|
||||||
|
self.assertEquals(len(grad[0].getElementsByTagNameNS(SVGNS, 'stop')), 3,
|
||||||
|
'Duplicate linear gradient stops with percentages not removed' )
|
||||||
|
|
||||||
class RemoveDuplicateRadialGradientStops(unittest.TestCase):
|
class RemoveDuplicateRadialGradientStops(unittest.TestCase):
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
doc = scour.scourXmlFile('unittests/duplicate-gradient-stops.svg')
|
doc = scour.scourXmlFile('unittests/duplicate-gradient-stops.svg')
|
||||||
|
|
|
||||||
11
unittests/duplicate-gradient-stops-pct.svg
Normal file
11
unittests/duplicate-gradient-stops-pct.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="sea-gradient" x1="0" x2="0" y1="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="lightgrey"/>
|
||||||
|
<stop offset="0.5" stop-color="darkgrey"/>
|
||||||
|
<stop offset="50%" stop-color="darkgrey"/>
|
||||||
|
<stop offset="100%" stop-color="white"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect style="fill: url(#sea-gradient) rgb(0, 0, 0);" y="0" x="0" height="100" width="100"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 441 B |
Loading…
Add table
Add a link
Reference in a new issue