Implement the feature described in bug 598976: Create a <g> with the common attributes of a run of elements if there are 3 or more elements in the run.
This commit is contained in:
parent
2a6cfb6b2c
commit
404c013e5f
6 changed files with 162 additions and 7 deletions
|
|
@ -19,6 +19,9 @@ class ScourInkscape (inkex.Effect):
|
||||||
self.OptionParser.add_option("--group-collapsing", type="inkbool",
|
self.OptionParser.add_option("--group-collapsing", type="inkbool",
|
||||||
action="store", dest="group_collapse", default=True,
|
action="store", dest="group_collapse", default=True,
|
||||||
help="won't collapse <g> elements")
|
help="won't collapse <g> elements")
|
||||||
|
self.OptionParser.add_option("--create-groups", type="inkbool",
|
||||||
|
action="store", dest="group_create", default=False,
|
||||||
|
help="create <g> elements for runs of elements with identical attributes")
|
||||||
self.OptionParser.add_option("--enable-id-stripping", type="inkbool",
|
self.OptionParser.add_option("--enable-id-stripping", type="inkbool",
|
||||||
action="store", dest="strip_ids", default=False,
|
action="store", dest="strip_ids", default=False,
|
||||||
help="remove all un-referenced ID attributes")
|
help="remove all un-referenced ID attributes")
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<param name="simplify-colors" type="boolean" _gui-text="Shorten color values">true</param>
|
<param name="simplify-colors" type="boolean" _gui-text="Shorten color values">true</param>
|
||||||
<param name="style-to-xml" type="boolean" _gui-text="Convert CSS attributes to XML attributes">true</param>
|
<param name="style-to-xml" type="boolean" _gui-text="Convert CSS attributes to XML attributes">true</param>
|
||||||
<param name="group-collapsing" type="boolean" _gui-text="Group collapsing">true</param>
|
<param name="group-collapsing" type="boolean" _gui-text="Group collapsing">true</param>
|
||||||
|
<param name="create-groups" type="boolean" _gui-text="Create groups for similar attributes">true</param>
|
||||||
<param name="enable-id-stripping" type="boolean" _gui-text="Remove unused ID names for elements">false</param>
|
<param name="enable-id-stripping" type="boolean" _gui-text="Remove unused ID names for elements">false</param>
|
||||||
<param name="shorten-ids" type="boolean" _gui-text="Shorten IDs">false</param>
|
<param name="shorten-ids" type="boolean" _gui-text="Shorten IDs">false</param>
|
||||||
<param name="embed-rasters" type="boolean" _gui-text="Embed rasters">true</param>
|
<param name="embed-rasters" type="boolean" _gui-text="Embed rasters">true</param>
|
||||||
|
|
@ -18,7 +19,7 @@
|
||||||
<param name="enable-comment-stripping" type="boolean" _gui-text="Remove comments">false</param>
|
<param name="enable-comment-stripping" type="boolean" _gui-text="Remove comments">false</param>
|
||||||
<param name="renderer-workaround" type="boolean" _gui-text="Work around renderer bugs">false</param>
|
<param name="renderer-workaround" type="boolean" _gui-text="Work around renderer bugs">false</param>
|
||||||
<param name="enable-viewboxing" type="boolean" _gui-text="Enable viewboxing">false</param>
|
<param name="enable-viewboxing" type="boolean" _gui-text="Enable viewboxing">false</param>
|
||||||
<param name="strip-xml-prolog" type="boolean" _gui-text="Remove the <?xml?> declaration">false</param>
|
<param name="strip-xml-prolog" type="boolean" _gui-text="Remove the <?xml?> declaration">false</param>
|
||||||
<param name="set-precision" type="int" _gui-text="Number of significant digits for coords">5</param>
|
<param name="set-precision" type="int" _gui-text="Number of significant digits for coords">5</param>
|
||||||
<param name="indent" type="enum" _gui-text="XML indentation (pretty-printing)">
|
<param name="indent" type="enum" _gui-text="XML indentation (pretty-printing)">
|
||||||
<_item value="space">Space</_item>
|
<_item value="space">Space</_item>
|
||||||
|
|
@ -29,8 +30,9 @@
|
||||||
<page name="Help" _gui-text="Help">
|
<page name="Help" _gui-text="Help">
|
||||||
<_param name="instructions" type="description" xml:space="preserve">This extension optimizes the SVG file according to the following options:
|
<_param name="instructions" type="description" xml:space="preserve">This extension optimizes the SVG file according to the following options:
|
||||||
* Shorten color names: convert all colors to #RRGGBB or #RGB format.
|
* Shorten color names: convert all colors to #RRGGBB or #RGB format.
|
||||||
* Convert CSS attributes to XML attributes: convert styles from <style> tags and inline style="" declarations into XML attributes.
|
* Convert CSS attributes to XML attributes: convert styles from <style> tags and inline style="" declarations into XML attributes.
|
||||||
* Group collapsing: removes useless <g> elements, promoting their contents up one level. Requires "Remove unused ID names for elements" to be set.
|
* Group collapsing: removes useless <g> elements, promoting their contents up one level. Requires "Remove unused ID names for elements" to be set.
|
||||||
|
* Create groups for similar attributes: create <g> elements for runs of elements having at least one attribute in common (e.g. fill color, stroke opacity, ...).
|
||||||
* Remove unused ID names for elements: remove all unreferenced ID attributes.
|
* Remove unused ID names for elements: remove all unreferenced ID attributes.
|
||||||
* Shorten IDs: reduce the length of all ID attributes, assigning the shortest to the most-referenced elements. For instance, #linearGradient5621, referenced 100 times, can become #a.
|
* Shorten IDs: reduce the length of all ID attributes, assigning the shortest to the most-referenced elements. For instance, #linearGradient5621, referenced 100 times, can become #a.
|
||||||
* Embed rasters: embed raster images as base64-encoded data URLs.
|
* Embed rasters: embed raster images as base64-encoded data URLs.
|
||||||
|
|
|
||||||
124
scour.py
124
scour.py
|
|
@ -853,6 +853,112 @@ def moveCommonAttributesToParentGroup(elem):
|
||||||
num += (len(childElements)-1) * len(commonAttrs)
|
num += (len(childElements)-1) * len(commonAttrs)
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
def createGroupsForCommonAttributes(elem):
|
||||||
|
"""
|
||||||
|
Creates <g> elements to contain runs of 3 or more
|
||||||
|
consecutive child elements having at least one common attribute.
|
||||||
|
|
||||||
|
Common attributes are not promoted to the <g> by this function.
|
||||||
|
This is handled by moveCommonAttributesToParentGroup.
|
||||||
|
|
||||||
|
If all children have a common attribute, an extra <g> is not created.
|
||||||
|
|
||||||
|
This function acts recursively on the given element.
|
||||||
|
"""
|
||||||
|
num = 0
|
||||||
|
global numElemsRemoved
|
||||||
|
|
||||||
|
# TODO perhaps all of the Presentation attributes in http://www.w3.org/TR/SVG/struct.html#GElement
|
||||||
|
# could be added here
|
||||||
|
# Cyn: These attributes are the same as in moveAttributesToParentGroup, and must always be
|
||||||
|
for curAttr 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']:
|
||||||
|
# Iterate through the children in reverse order, so item(i) for
|
||||||
|
# items we have yet to visit still returns the correct nodes.
|
||||||
|
curChild = elem.childNodes.length - 1
|
||||||
|
while curChild >= 0:
|
||||||
|
childNode = elem.childNodes.item(curChild)
|
||||||
|
|
||||||
|
if childNode.nodeType == 1 and childNode.getAttribute(curAttr) != '':
|
||||||
|
# We're in a possible run! Track the value and run length.
|
||||||
|
value = childNode.getAttribute(curAttr)
|
||||||
|
runStart, runEnd = curChild, curChild
|
||||||
|
# Run elements includes only element tags, no whitespace/comments/etc.
|
||||||
|
# Later, we calculate a run length which includes these.
|
||||||
|
runElements = 1
|
||||||
|
|
||||||
|
# Backtrack to get all the nodes having the same
|
||||||
|
# attribute value, preserving any nodes in-between.
|
||||||
|
while runStart > 0:
|
||||||
|
nextNode = elem.childNodes.item(runStart - 1)
|
||||||
|
if nextNode.nodeType == 1:
|
||||||
|
if nextNode.getAttribute(curAttr) != value: break
|
||||||
|
else:
|
||||||
|
runElements += 1
|
||||||
|
runStart -= 1
|
||||||
|
else: runStart -= 1
|
||||||
|
|
||||||
|
if runElements >= 3:
|
||||||
|
# Include whitespace/comment/etc. nodes in the run.
|
||||||
|
while runEnd < elem.childNodes.length - 1:
|
||||||
|
if elem.childNodes.item(runEnd + 1).nodeType == 1: break
|
||||||
|
else: runEnd += 1
|
||||||
|
|
||||||
|
runLength = runEnd - runStart + 1
|
||||||
|
if runLength == elem.childNodes.length: # Every child has this
|
||||||
|
# If the current parent is a <g> already,
|
||||||
|
if elem.nodeName == 'g' and elem.namespaceURI == NS['SVG']:
|
||||||
|
# do not act altogether on this attribute; all the
|
||||||
|
# children have it in common.
|
||||||
|
# Let moveCommonAttributesToParentGroup do it.
|
||||||
|
curChild = -1
|
||||||
|
continue
|
||||||
|
# otherwise, it might be an <svg> element, and
|
||||||
|
# even if all children have the same attribute value,
|
||||||
|
# it's going to be worth making the <g> since
|
||||||
|
# <svg> doesn't support attributes like 'stroke'.
|
||||||
|
# Fall through.
|
||||||
|
|
||||||
|
# Create a <g> element from scratch.
|
||||||
|
# We need the Document for this.
|
||||||
|
document = elem.parentNode
|
||||||
|
while document.parentNode is not None:
|
||||||
|
document = document.parentNode
|
||||||
|
group = document.createElementNS(NS['SVG'], 'g')
|
||||||
|
# Move the run of elements to the group.
|
||||||
|
for dummy in xrange(runLength):
|
||||||
|
node = elem.childNodes.item(runStart)
|
||||||
|
elem.removeChild(node)
|
||||||
|
group.appendChild(node)
|
||||||
|
# Include the group in elem's children.
|
||||||
|
if elem.childNodes.length == runStart:
|
||||||
|
elem.appendChild(group)
|
||||||
|
else:
|
||||||
|
elem.insertBefore(group, elem.childNodes.item(runStart))
|
||||||
|
num += 1
|
||||||
|
curChild = runStart - 1
|
||||||
|
numElemsRemoved -= 1
|
||||||
|
else:
|
||||||
|
curChild -= 1
|
||||||
|
else:
|
||||||
|
curChild -= 1
|
||||||
|
|
||||||
|
# each child gets the same treatment, recursively
|
||||||
|
for childNode in elem.childNodes:
|
||||||
|
if childNode.nodeType == 1:
|
||||||
|
num += createGroupsForCommonAttributes(childNode)
|
||||||
|
|
||||||
|
return num
|
||||||
|
|
||||||
def removeUnusedAttributesOnParent(elem):
|
def removeUnusedAttributesOnParent(elem):
|
||||||
"""
|
"""
|
||||||
This recursively calls this function on all children of the element passed in,
|
This recursively calls this function on all children of the element passed in,
|
||||||
|
|
@ -2528,10 +2634,6 @@ def scourString(in_string, options=None):
|
||||||
referencedIDs = findReferencedElements(doc.documentElement)
|
referencedIDs = findReferencedElements(doc.documentElement)
|
||||||
bContinueLooping = (removeUnreferencedIDs(referencedIDs, identifiedElements) > 0)
|
bContinueLooping = (removeUnreferencedIDs(referencedIDs, identifiedElements) > 0)
|
||||||
|
|
||||||
if options.group_collapse:
|
|
||||||
while removeNestedGroups(doc.documentElement) > 0:
|
|
||||||
pass
|
|
||||||
|
|
||||||
while removeDuplicateGradientStops(doc) > 0:
|
while removeDuplicateGradientStops(doc) > 0:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -2543,6 +2645,11 @@ def scourString(in_string, options=None):
|
||||||
while removeDuplicateGradients(doc) > 0:
|
while removeDuplicateGradients(doc) > 0:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# create <g> elements if there are runs of elements with the same attributes.
|
||||||
|
# this MUST be before moveCommonAttributesToParentGroup.
|
||||||
|
if options.group_create:
|
||||||
|
createGroupsForCommonAttributes(doc.documentElement)
|
||||||
|
|
||||||
# move common attributes to parent group
|
# move common attributes to parent group
|
||||||
# NOTE: the if the <svg> element's immediate children
|
# NOTE: the if the <svg> element's immediate children
|
||||||
# all have the same value for an attribute, it must not
|
# all have the same value for an attribute, it must not
|
||||||
|
|
@ -2554,6 +2661,12 @@ def scourString(in_string, options=None):
|
||||||
# remove unused attributes from parent
|
# remove unused attributes from parent
|
||||||
numAttrsRemoved += removeUnusedAttributesOnParent(doc.documentElement)
|
numAttrsRemoved += removeUnusedAttributesOnParent(doc.documentElement)
|
||||||
|
|
||||||
|
# Collapse groups LAST, because we've created groups. If done before
|
||||||
|
# moveAttributesToParentGroup, empty <g>'s may remain.
|
||||||
|
if options.group_collapse:
|
||||||
|
while removeNestedGroups(doc.documentElement) > 0:
|
||||||
|
pass
|
||||||
|
|
||||||
# remove unnecessary closing point of polygons and scour points
|
# remove unnecessary closing point of polygons and scour points
|
||||||
for polygon in doc.documentElement.getElementsByTagName('polygon') :
|
for polygon in doc.documentElement.getElementsByTagName('polygon') :
|
||||||
cleanPolygon(polygon, options)
|
cleanPolygon(polygon, options)
|
||||||
|
|
@ -2665,6 +2778,9 @@ _options_parser.add_option("--disable-style-to-xml",
|
||||||
_options_parser.add_option("--disable-group-collapsing",
|
_options_parser.add_option("--disable-group-collapsing",
|
||||||
action="store_false", dest="group_collapse", default=True,
|
action="store_false", dest="group_collapse", default=True,
|
||||||
help="won't collapse <g> elements")
|
help="won't collapse <g> elements")
|
||||||
|
_options_parser.add_option("--create-groups",
|
||||||
|
action="store_true", dest="group_create", default=False,
|
||||||
|
help="create <g> elements for runs of elements with identical attributes")
|
||||||
_options_parser.add_option("--enable-id-stripping",
|
_options_parser.add_option("--enable-id-stripping",
|
||||||
action="store_true", dest="strip_ids", default=False,
|
action="store_true", dest="strip_ids", default=False,
|
||||||
help="remove all un-referenced ID attributes")
|
help="remove all un-referenced ID attributes")
|
||||||
|
|
|
||||||
22
testscour.py
22
testscour.py
|
|
@ -51,6 +51,7 @@ class ScourOptions:
|
||||||
shorten_ids = False
|
shorten_ids = False
|
||||||
strip_comments = False
|
strip_comments = False
|
||||||
remove_metadata = False
|
remove_metadata = False
|
||||||
|
group_create = False
|
||||||
|
|
||||||
class NoInkscapeElements(unittest.TestCase):
|
class NoInkscapeElements(unittest.TestCase):
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
|
|
@ -1075,6 +1076,27 @@ class MustKeepGInSwitch2(unittest.TestCase):
|
||||||
self.assertEquals(doc.getElementsByTagName('g').length, 1,
|
self.assertEquals(doc.getElementsByTagName('g').length, 1,
|
||||||
'Erroneously removed a <g> in a <switch>')
|
'Erroneously removed a <g> in a <switch>')
|
||||||
|
|
||||||
|
class GroupCreation(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/group-creation.svg',
|
||||||
|
scour.parse_args(['--create-groups'])[0])
|
||||||
|
self.assertEquals(doc.getElementsByTagName('g').length, 1,
|
||||||
|
'Did not create a <g> for a run of elements having similar attributes')
|
||||||
|
|
||||||
|
class GroupCreationForInheritableAttributesOnly(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/group-creation.svg',
|
||||||
|
scour.parse_args(['--create-groups'])[0])
|
||||||
|
self.assertEquals(doc.getElementsByTagName('g').item(0).getAttribute('y'), '',
|
||||||
|
'Promoted the uninheritable attribute y to a <g>')
|
||||||
|
|
||||||
|
class GroupNoCreation(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
doc = scour.scourXmlFile('unittests/group-no-creation.svg',
|
||||||
|
scour.parse_args(['--create-groups'])[0])
|
||||||
|
self.assertEquals(doc.getElementsByTagName('g').length, 0,
|
||||||
|
'Created a <g> for a run of elements having dissimilar attributes')
|
||||||
|
|
||||||
|
|
||||||
# TODO: write tests for --enable-viewboxing
|
# TODO: write tests for --enable-viewboxing
|
||||||
# TODO; write a test for embedding rasters
|
# TODO; write a test for embedding rasters
|
||||||
|
|
|
||||||
6
unittests/group-creation.svg
Normal file
6
unittests/group-creation.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect fill="red" stroke="blue" x="0" y="0" width="4" height="4" />
|
||||||
|
<rect fill="red" stroke="blue" x="8" y="0" width="4" height="4" />
|
||||||
|
<rect fill="red" stroke="blue" x="16" y="0" width="4" height="4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 317 B |
6
unittests/group-no-creation.svg
Normal file
6
unittests/group-no-creation.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect fill="green" stroke="blue" x="0" y="0" width="4" height="4" />
|
||||||
|
<rect fill="yellow" stroke="red" x="8" y="0" width="4" height="4" />
|
||||||
|
<rect fill="blue" stroke="red" x="16" y="0" width="4" height="4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
Loading…
Add table
Add a link
Reference in a new issue