Remove gradients singly referenced from another gradient

This commit is contained in:
JSCHILL1 2009-04-24 17:01:43 -05:00
parent f6f98580c7
commit 8e9683f648
5 changed files with 132 additions and 7 deletions

View file

@ -49,6 +49,9 @@
# Next Up:
# + 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
# - Reduce #RRGGBB format to #RGB format when possible
# https://bugs.edge.launchpad.net/ubuntu/+source/human-icon-theme/+bug/361667/
@ -147,8 +150,8 @@ def findElementsWithId(node,elems={}):
findElementsWithId(child, elems)
return elems
# returns the number of times an id is referenced
# currently looks at fill, stroke and xlink:href attributes
# returns the number of times an id is referenced as well as all elements that reference it
# currently looks at fill, stroke, clip-path, mask, marker and xlink:href attributes
def findReferencedElements(node,ids={}):
# TODO: error here (ids is not cleared upon next invocation), the
# 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
id = href[1:]
if ids.has_key(id) :
ids[id] += 1
ids[id][0] += 1
ids[id][1].append(node)
else:
ids[id] = 1
ids[id] = [1,[node]]
# now get all style properties and the fill, stroke, filter attributes
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(#' :
id = val[5:val.find(')')]
if ids.has_key(id) :
ids[id] += 1
ids[id][0] += 1
ids[id][1].append(node)
else:
ids[id] = 1
ids[id] = [1,[node]]
if node.hasChildNodes() :
for child in node.childNodes:
@ -340,6 +345,61 @@ def removeDuplicateGradientStops(doc):
# linear gradients
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*")
scinumber = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+[eE][\\-\\+]?\\d+")
number = re.compile("[\\-\\+]?(\\d*\\.?)?\\d+")
@ -703,6 +763,10 @@ def scourString(in_string, options=[]):
while removeDuplicateGradientStops(doc) > 0:
pass
# remove gradients that are only referenced by one other gradient
while collapseSinglyReferencedGradients(doc) > 0:
pass
# clean path data
for elem in doc.documentElement.getElementsByTagNameNS(NS['SVG'], 'path') :
cleanPath(elem)
@ -732,7 +796,9 @@ def scourString(in_string, options=[]):
# returns the minidom doc representation of the SVG
def scourXmlFile(filename, options=[]):
in_string = open(filename).read()
# print 'IN=',in_string
out_string = scourString(in_string, options)
# print 'OUT=',out_string
return xml.dom.minidom.parseString(out_string)
def printHeader():

View file

@ -370,7 +370,34 @@ class ConvertFillRuleOpacityPropertyToAttr(unittest.TestCase):
self.assertEquals(doc.getElementsByTagNameNS(SVGNS, 'path')[1].getAttribute('fill-rule'), 'nonzero',
'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):
# def runTest(self):
# doc = scour.scourXmlFile('unittests/unreferenced-font.svg')

View 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

View 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

View 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