Ensure depth-first analysis of common attributes so they bubble up to the top
This commit is contained in:
parent
b0788ba18a
commit
2342830671
3 changed files with 100 additions and 17 deletions
91
scour.py
91
scour.py
|
|
@ -39,24 +39,12 @@
|
||||||
# <rect fill="red" ... />
|
# <rect fill="red" ... />
|
||||||
# </g>
|
# </g>
|
||||||
# in this case, fill="blue" should be removed
|
# in this case, fill="blue" should be removed
|
||||||
# * Move common attributes up to a parent group:
|
|
||||||
# <g>
|
|
||||||
# <rect fill="white"/>
|
|
||||||
# <rect fill="white"/>
|
|
||||||
# <rect fill="white"/>
|
|
||||||
# </g>
|
|
||||||
# becomes:
|
|
||||||
# <g fill="white">
|
|
||||||
# <rect />
|
|
||||||
# <rect />
|
|
||||||
# <rect />
|
|
||||||
# </g>
|
|
||||||
|
|
||||||
# Next Up:
|
# Next Up:
|
||||||
# - analyze all children of a group, if they have common attributes, then move them to the group
|
# + analyze all children of a group, if they have common inheritable attributes, then move them to the group
|
||||||
|
# - crunch *opacity, offset, svg:x,y, stroke-miterlimit, stroke-width numbers (remove trailing zeros, reduce prec, integerize)
|
||||||
# - analyze a group and its children, if a group's attribute is not being used by any children
|
# - analyze a group and its children, if a group's attribute is not being used by any children
|
||||||
# (or descendants?) then remove it
|
# (or descendants?) then remove it
|
||||||
# - crunch *opacity, offset, svg:x,y, transform numbers (remove trailing zeros, reduce prec, integerize)
|
|
||||||
# - add an option to remove ids if they match the Inkscape-style of IDs
|
# - add an option to remove ids if they match the Inkscape-style of IDs
|
||||||
# - investigate point-reducing algorithms
|
# - investigate point-reducing algorithms
|
||||||
# - parse transform attribute
|
# - parse transform attribute
|
||||||
|
|
@ -609,10 +597,12 @@ def removeNamespacedElements(node, namespaces):
|
||||||
num += removeNamespacedElements(child, namespaces)
|
num += removeNamespacedElements(child, namespaces)
|
||||||
return num
|
return num
|
||||||
|
|
||||||
# this walks further and further down the tree, removing groups
|
|
||||||
# which do not have any attributes or a title/desc child and
|
|
||||||
# promoting their children up one level
|
|
||||||
def removeNestedGroups(node):
|
def removeNestedGroups(node):
|
||||||
|
"""
|
||||||
|
This walks further and further down the tree, removing groups
|
||||||
|
which do not have any attributes or a title/desc child and
|
||||||
|
promoting their children up one level
|
||||||
|
"""
|
||||||
global numElemsRemoved
|
global numElemsRemoved
|
||||||
num = 0
|
num = 0
|
||||||
|
|
||||||
|
|
@ -640,6 +630,69 @@ def removeNestedGroups(node):
|
||||||
num += removeNestedGroups(child)
|
num += removeNestedGroups(child)
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
def moveCommonAttributesToParentGroup(elem):
|
||||||
|
"""
|
||||||
|
This iterates through all children of the passed in g element and
|
||||||
|
removes common inheritable attributes from the children and places
|
||||||
|
them in the parent group
|
||||||
|
"""
|
||||||
|
num = 0
|
||||||
|
|
||||||
|
childElements = []
|
||||||
|
# recurse first into the children (depth-first)
|
||||||
|
for child in elem.childNodes:
|
||||||
|
if child.nodeType == 1:
|
||||||
|
childElements.append(child)
|
||||||
|
num += moveCommonAttributesToParentGroup(child)
|
||||||
|
|
||||||
|
# only process the children if there are more than one element
|
||||||
|
if len(childElements) <= 1: return num
|
||||||
|
|
||||||
|
commonAttrs = {}
|
||||||
|
# add all inheritable properties of the first child element
|
||||||
|
attrList = childElements[0].attributes
|
||||||
|
for num in range(attrList.length):
|
||||||
|
attr = attrList.item(num)
|
||||||
|
# this is most of the inheritable properties from http://www.w3.org/TR/SVG11/propidx.html
|
||||||
|
# and http://www.w3.org/TR/SVGTiny12/attributeTable.html
|
||||||
|
if attr.nodeName in ['clip-rule',
|
||||||
|
'display-align',
|
||||||
|
'fill', 'fill-opacity', 'fill-rule',
|
||||||
|
'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch',
|
||||||
|
'font-style', 'font-variant', 'font-weight',
|
||||||
|
'letter-spacing',
|
||||||
|
'pointer-events', 'shape-rendering',
|
||||||
|
'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin',
|
||||||
|
'stroke-miterlimit', 'stroke-opacity', 'stroke-width',
|
||||||
|
'text-anchor', 'text-decoration', 'text-rendering', 'visibility',
|
||||||
|
'word-spacing', 'writing-mode']:
|
||||||
|
# we just add all the attributes from the first child
|
||||||
|
commonAttrs[attr.nodeName] = attr.nodeValue
|
||||||
|
|
||||||
|
# for each subsequent child element
|
||||||
|
for childNum in range(len(childElements)):
|
||||||
|
if childNum == 0: continue
|
||||||
|
child = childElements[childNum]
|
||||||
|
distinctAttrs = []
|
||||||
|
# loop through all current 'common' attributes
|
||||||
|
for name in commonAttrs.keys():
|
||||||
|
# if this child doesn't match that attribute, schedule it for removal
|
||||||
|
if child.getAttribute(name) != commonAttrs[name]:
|
||||||
|
distinctAttrs.append(name)
|
||||||
|
# remove those attributes which are not common
|
||||||
|
for name in distinctAttrs:
|
||||||
|
del commonAttrs[name]
|
||||||
|
|
||||||
|
# commonAttrs now has all the inheritable attributes which are common among all child elements
|
||||||
|
for name in commonAttrs.keys():
|
||||||
|
for child in childElements:
|
||||||
|
child.removeAttribute(name)
|
||||||
|
elem.setAttribute(name, commonAttrs[name])
|
||||||
|
|
||||||
|
# update our statistic (we remove N*M attributes and add back in M attributes)
|
||||||
|
num += (len(childElements)-1) * len(commonAttrs)
|
||||||
|
return num
|
||||||
|
|
||||||
def removeDuplicateGradientStops(doc):
|
def removeDuplicateGradientStops(doc):
|
||||||
global numElemsRemoved
|
global numElemsRemoved
|
||||||
num = 0
|
num = 0
|
||||||
|
|
@ -2072,6 +2125,10 @@ def scourString(in_string, options=None):
|
||||||
while removeNestedGroups(doc.documentElement) > 0:
|
while removeNestedGroups(doc.documentElement) > 0:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# move common attributes to parent group
|
||||||
|
# TODO: should make sure this is called with most-nested groups first
|
||||||
|
numAttrsRemoved += moveCommonAttributesToParentGroup(doc.documentElement)
|
||||||
|
|
||||||
while removeDuplicateGradientStops(doc) > 0:
|
while removeDuplicateGradientStops(doc) > 0:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
18
testscour.py
18
testscour.py
|
|
@ -874,6 +874,24 @@ class MoveSVGElementsToDefaultNamespace(unittest.TestCase):
|
||||||
xmlstring = scour.scourString(open('unittests/xml-ns-decl.svg').read())
|
xmlstring = scour.scourString(open('unittests/xml-ns-decl.svg').read())
|
||||||
self.assert_( xmlstring.find('<rect ') != -1,
|
self.assert_( xmlstring.find('<rect ') != -1,
|
||||||
'Did not bring SVG elements into the default namespace')
|
'Did not bring SVG elements into the default namespace')
|
||||||
|
|
||||||
|
class MoveCommonAttributesToParent(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
g = scour.scourXmlFile('unittests/move-common-attributes-to-parent.svg').getElementsByTagNameNS(SVGNS, 'g')[0]
|
||||||
|
self.assertEquals( g.getAttribute('fill'), '#0F0',
|
||||||
|
'Did not move common fill attribute to parent group')
|
||||||
|
|
||||||
|
class RemoveCommonAttributesFromChild(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
r = scour.scourXmlFile('unittests/move-common-attributes-to-parent.svg').getElementsByTagNameNS(SVGNS, 'rect')[0]
|
||||||
|
self.assertNotEquals( r.getAttribute('fill'), '#0F0',
|
||||||
|
'Did not remove common fill attribute from child')
|
||||||
|
|
||||||
|
class PropagateCommonAttributesUp(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
g = scour.scourXmlFile('unittests/move-common-attributes-to-grandparent.svg').getElementsByTagNameNS(SVGNS, 'g')[0]
|
||||||
|
self.assertEquals( g.getAttribute('fill'), '#0F0',
|
||||||
|
'Did not move common fill attribute to grandparent')
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
||||||
8
unittests/move-common-attributes-to-parent.svg
Normal file
8
unittests/move-common-attributes-to-parent.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill-opacity="0.4">
|
||||||
|
<rect fill="#0F0" stroke="#0F0" stroke-width="5" width="100" height="300"/>
|
||||||
|
<rect fill="#0F0" width="200" height="100" />
|
||||||
|
<circle fill="#0F0" stroke="0F0" cx="50" cy="50" r="20" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 317 B |
Loading…
Add table
Add a link
Reference in a new issue