Remove gradients singly referenced from another gradient
This commit is contained in:
parent
f6f98580c7
commit
8e9683f648
5 changed files with 132 additions and 7 deletions
78
scour.py
78
scour.py
|
|
@ -49,6 +49,9 @@
|
||||||
|
|
||||||
# Next Up:
|
# Next Up:
|
||||||
# + fix bug when removing stroke styles
|
# + fix bug when removing stroke styles
|
||||||
|
# + Remove gradients that are only referenced by one other gradient
|
||||||
|
# - Remove unnecessary units of precision on attributes
|
||||||
|
# - Remove unnecessary units of precision on path coordinates
|
||||||
# - Convert all colors to #RRGGBB format
|
# - Convert all colors to #RRGGBB format
|
||||||
# - Reduce #RRGGBB format to #RGB format when possible
|
# - Reduce #RRGGBB format to #RGB format when possible
|
||||||
# https://bugs.edge.launchpad.net/ubuntu/+source/human-icon-theme/+bug/361667/
|
# https://bugs.edge.launchpad.net/ubuntu/+source/human-icon-theme/+bug/361667/
|
||||||
|
|
@ -147,8 +150,8 @@ def findElementsWithId(node,elems={}):
|
||||||
findElementsWithId(child, elems)
|
findElementsWithId(child, elems)
|
||||||
return elems
|
return elems
|
||||||
|
|
||||||
# returns the number of times an id is referenced
|
# returns the number of times an id is referenced as well as all elements that reference it
|
||||||
# currently looks at fill, stroke and xlink:href attributes
|
# currently looks at fill, stroke, clip-path, mask, marker and xlink:href attributes
|
||||||
def findReferencedElements(node,ids={}):
|
def findReferencedElements(node,ids={}):
|
||||||
# TODO: error here (ids is not cleared upon next invocation), the
|
# TODO: error here (ids is not cleared upon next invocation), the
|
||||||
# input argument ids is clunky here (see below how it is called)
|
# input argument ids is clunky here (see below how it is called)
|
||||||
|
|
@ -159,9 +162,10 @@ def findReferencedElements(node,ids={}):
|
||||||
# we remove the hash mark from the beginning of the id
|
# we remove the hash mark from the beginning of the id
|
||||||
id = href[1:]
|
id = href[1:]
|
||||||
if ids.has_key(id) :
|
if ids.has_key(id) :
|
||||||
ids[id] += 1
|
ids[id][0] += 1
|
||||||
|
ids[id][1].append(node)
|
||||||
else:
|
else:
|
||||||
ids[id] = 1
|
ids[id] = [1,[node]]
|
||||||
|
|
||||||
# now get all style properties and the fill, stroke, filter attributes
|
# now get all style properties and the fill, stroke, filter attributes
|
||||||
styles = string.split(node.getAttribute('style'),';')
|
styles = string.split(node.getAttribute('style'),';')
|
||||||
|
|
@ -178,9 +182,10 @@ def findReferencedElements(node,ids={}):
|
||||||
if prop in referencingProps and val != '' and val[0:5] == 'url(#' :
|
if prop in referencingProps and val != '' and val[0:5] == 'url(#' :
|
||||||
id = val[5:val.find(')')]
|
id = val[5:val.find(')')]
|
||||||
if ids.has_key(id) :
|
if ids.has_key(id) :
|
||||||
ids[id] += 1
|
ids[id][0] += 1
|
||||||
|
ids[id][1].append(node)
|
||||||
else:
|
else:
|
||||||
ids[id] = 1
|
ids[id] = [1,[node]]
|
||||||
|
|
||||||
if node.hasChildNodes() :
|
if node.hasChildNodes() :
|
||||||
for child in node.childNodes:
|
for child in node.childNodes:
|
||||||
|
|
@ -340,6 +345,61 @@ def removeDuplicateGradientStops(doc):
|
||||||
# linear gradients
|
# linear gradients
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
def collapseSinglyReferencedGradients(doc):
|
||||||
|
global numElemsRemoved
|
||||||
|
num = 0
|
||||||
|
|
||||||
|
# make sure to reset the ref'ed ids for when we are running this in testscour
|
||||||
|
for rid,nodeCount in findReferencedElements(doc.documentElement, {}).iteritems():
|
||||||
|
count = nodeCount[0]
|
||||||
|
nodes = nodeCount[1]
|
||||||
|
if count == 1:
|
||||||
|
elem = findElementById(doc.documentElement,rid)
|
||||||
|
if elem != None and elem.nodeType == 1 and elem.nodeName in ['linearGradient', 'radialGradient'] \
|
||||||
|
and elem.namespaceURI == NS['SVG']:
|
||||||
|
# found a gradient that is referenced by only 1 other element
|
||||||
|
refElem = nodes[0]
|
||||||
|
if refElem.nodeType == 1 and refElem.nodeName in ['linearGradient', 'radialGradient'] \
|
||||||
|
and refElem.namespaceURI == NS['SVG']:
|
||||||
|
# elem is a gradient referenced by only one other gradient (refElem)
|
||||||
|
# TODO: update elem with properties and stops from refElem
|
||||||
|
|
||||||
|
# add the stops to the referencing gradient (this removes them from elem)
|
||||||
|
if len(refElem.getElementsByTagNameNS(NS['SVG'], 'stop')) == 0:
|
||||||
|
stopsToAdd = elem.getElementsByTagNameNS(NS['SVG'], 'stop')
|
||||||
|
for stop in stopsToAdd:
|
||||||
|
refElem.appendChild(stop)
|
||||||
|
|
||||||
|
# adopt the gradientUnits, spreadMethod, gradientTransform attributess if
|
||||||
|
# they are unspecified on refElem
|
||||||
|
for attr in ['gradientUnits','spreadMethod','gradientTransform']:
|
||||||
|
if refElem.getAttribute(attr) == '' and not elem.getAttribute(attr) == '':
|
||||||
|
refElem.setAttributeNS(None, attr, elem.getAttribute(attr))
|
||||||
|
|
||||||
|
# if both are radialGradients, adopt elem's fx,fy,cx,cy,r attributes if
|
||||||
|
# they are unspecified on refElem
|
||||||
|
if elem.nodeName == 'radialGradient' and refElem.nodeName == 'radialGradient':
|
||||||
|
for attr in ['fx','fy','cx','cy','r']:
|
||||||
|
if refElem.getAttribute(attr) == '' and not elem.getAttribute(attr) == '':
|
||||||
|
refElem.setAttributeNS(None, attr, elem.getAttribute(attr))
|
||||||
|
|
||||||
|
# if both are linearGradients, adopt elem's x1,y1,x2,y2 attributes if
|
||||||
|
# they are unspecified on refElem
|
||||||
|
if elem.nodeName == 'linearGradient' and refElem.nodeName == 'linearGradient':
|
||||||
|
for attr in ['x1','y1','x2','y2']:
|
||||||
|
if refElem.getAttribute(attr) == '' and not elem.getAttribute(attr) == '':
|
||||||
|
refElem.setAttributeNS(None, attr, elem.getAttribute(attr))
|
||||||
|
|
||||||
|
# now remove the xlink:href from refElem
|
||||||
|
refElem.removeAttributeNS(NS['XLINK'], 'href')
|
||||||
|
|
||||||
|
# now delete elem
|
||||||
|
elem.parentNode.removeChild(elem)
|
||||||
|
numElemsRemoved += 1
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
return num
|
||||||
|
|
||||||
coord = re.compile("\\-?\\d+\\.?\\d*")
|
coord = re.compile("\\-?\\d+\\.?\\d*")
|
||||||
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
|
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
|
||||||
number = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+")
|
number = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+")
|
||||||
|
|
@ -703,6 +763,10 @@ def scourString(in_string, options=[]):
|
||||||
while removeDuplicateGradientStops(doc) > 0:
|
while removeDuplicateGradientStops(doc) > 0:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# remove gradients that are only referenced by one other gradient
|
||||||
|
while collapseSinglyReferencedGradients(doc) > 0:
|
||||||
|
pass
|
||||||
|
|
||||||
# clean path data
|
# clean path data
|
||||||
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'path') :
|
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'path') :
|
||||||
cleanPath(elem)
|
cleanPath(elem)
|
||||||
|
|
@ -732,7 +796,9 @@ def scourString(in_string, options=[]):
|
||||||
# returns the minidom doc representation of the SVG
|
# returns the minidom doc representation of the SVG
|
||||||
def scourXmlFile(filename, options=[]):
|
def scourXmlFile(filename, options=[]):
|
||||||
in_string = open(filename).read()
|
in_string = open(filename).read()
|
||||||
|
# print 'IN=',in_string
|
||||||
out_string = scourString(in_string, options)
|
out_string = scourString(in_string, options)
|
||||||
|
# print 'OUT=',out_string
|
||||||
return xml.dom.minidom.parseString(out_string)
|
return xml.dom.minidom.parseString(out_string)
|
||||||
|
|
||||||
def printHeader():
|
def printHeader():
|
||||||
|
|
|
||||||
27
testscour.py
27
testscour.py
|
|
@ -370,6 +370,33 @@ class ConvertFillRuleOpacityPropertyToAttr(unittest.TestCase):
|
||||||
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill-rule'), 'nonzero',
|
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill-rule'), 'nonzero',
|
||||||
'fill-rule property not converted to XML attribute' )
|
'fill-rule property not converted to XML attribute' )
|
||||||
|
|
||||||
|
class CollapseSinglyReferencedGradients(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/collapse-gradients.svg')
|
||||||
|
self.assertEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0,
|
||||||
|
'Singly-referenced linear gradient not collapsed' )
|
||||||
|
|
||||||
|
class InheritGradientUnitsUponCollapsing(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/collapse-gradients.svg')
|
||||||
|
# print doc.toprettyxml(' ')
|
||||||
|
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'radialGradient')[0].getAttribute('gradientUnits'),
|
||||||
|
'userSpaceOnUse',
|
||||||
|
'gradientUnits not properly inherited when collapsing gradients' )
|
||||||
|
|
||||||
|
class OverrideGradientUnitsUponCollapsing(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/collapse-gradients-gradientUnits.svg')
|
||||||
|
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'radialGradient')[0].getAttribute('gradientUnits'),
|
||||||
|
'objectBoundingBox',
|
||||||
|
'gradientUnits not properly overrode when collapsing gradients' )
|
||||||
|
|
||||||
|
class DoNotCollapseMultiplyReferencedGradients(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/dont-collapse-gradients.svg')
|
||||||
|
self.assertNotEquals(len(doc.getElementsByTagNameNS(SVGNS, 'linearGradient')), 0,
|
||||||
|
'Multiply-referenced linear gradient collapsed' )
|
||||||
|
|
||||||
|
|
||||||
#class RemoveUnreferencedFonts(unittest.TestCase):
|
#class RemoveUnreferencedFonts(unittest.TestCase):
|
||||||
# def runTest(self):
|
# def runTest(self):
|
||||||
|
|
|
||||||
10
unittests/collapse-gradients-gradientUnits.svg
Normal file
10
unittests/collapse-gradients-gradientUnits.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="blue" />
|
||||||
|
<stop offset="1" stop-color="yellow" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="g2" xlink:href="#g1" cx="50%" cy="50%" r="30%" gradientUnits="objectBoundingBox"/>
|
||||||
|
</defs>
|
||||||
|
<rect fill="url(#g2)" width="200" height="200"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 451 B |
10
unittests/collapse-gradients.svg
Normal file
10
unittests/collapse-gradients.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" spreadMethod="reflect" gradientTransform="matrix(1,2,3,4,5,6)">
|
||||||
|
<stop offset="0" stop-color="blue" />
|
||||||
|
<stop offset="1" stop-color="yellow" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="grad2" xlink:href="#grad1" cx="100" cy="100" r="70"/>
|
||||||
|
</defs>
|
||||||
|
<rect fill="url(#grad2)" width="200" height="200"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 491 B |
12
unittests/dont-collapse-gradients.svg
Normal file
12
unittests/dont-collapse-gradients.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="blue" />
|
||||||
|
<stop offset="1" stop-color="yellow" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="g2" xlink:href="#g1" cx="100" cy="100" r="70"/>
|
||||||
|
<radialGradient id="g3" xlink:href="#g1" cx="100" cy="100" r="70"/>
|
||||||
|
</defs>
|
||||||
|
<rect fill="url(#g2)" width="200" height="200"/>
|
||||||
|
<rect fill="url(#g3)" width="200" height="200" y="200"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 543 B |
Loading…
Add table
Add a link
Reference in a new issue