Merge sibling <g> nodes with identical attributes
In some cases, gnuplot generates a very suboptimal SVG content of the
following pattern:
<g color="black" fill="none" stroke="currentColor">
<path d="m82.5 323.3v-4.1" stroke="#000"/>
</g>
<g color="black" fill="none" stroke="currentColor">
<path d="m116.4 323.3v-4.1" stroke="#000"/>
</g>
... repeated 10+ more times here ...
<g color="black" fill="none" stroke="currentColor">
<path d="m65.4 72.8v250.5h420v-250.5h-420z" stroke="#000"/>
</g>
A more optimal pattern would be:
<g color="black" fill="none" stroke="#000">
<path d="m82.5 323.3v-4.1"/>
<path d="m116.4 323.3v-4.1"/>
... 10+ more paths here ...
<path d="m65.4 72.8v250.5h420v-250.5h-420z"/>
</g>
This patch enables that optimization by handling the merging of two
sibling <g> entries that have identical attributes. In the above
example that does not solve the rewrite from "currentColor" to "#000"
for the stroke attribute. However, the existing code already handles
that automatically after the <g> elements have been merged.
This change provides comparable results to --create-groups as shown by
the following diagram while being a distinct optimization:
+----------------------------+-------+--------+
| Test | Size | in % |
+----------------------------+-------+--------+
| baseline | 17961 | 100% |
| baseline + --create-groups | 17418 | 97.0% |
| patched | 16939 | 94.3% |
| patched + --create-groups | 16855 | 93.8% |
+----------------------------+-------+--------+
The image used in the size table above was generated based on the
instructions from https://bugs.debian.org/858039#10 with gnuplot 5.2
patchlevel 2. Beyond the test-based "--create-groups", the following
scour command-line parameters were used:
--enable-id-stripping --enable-comment-stripping \
--shorten-ids --indent=none
Note that the baseline was scour'ed repeatedly to stablize the image
size.
Signed-off-by: Niels Thykier <niels@thykier.net>
This commit is contained in:
parent
40753af88a
commit
dd2155e576
3 changed files with 115 additions and 0 deletions
|
|
@ -1134,6 +1134,75 @@ def moveCommonAttributesToParentGroup(elem, referencedElements):
|
|||
return num
|
||||
|
||||
|
||||
def mergeSiblingGroupsWithCommonAttributes(elem):
|
||||
"""
|
||||
Merge two or more sibling <g> elements with the identical attributes.
|
||||
|
||||
This function acts recursively on the given element.
|
||||
"""
|
||||
|
||||
num = 0
|
||||
i = elem.childNodes.length - 1
|
||||
while i >= 0:
|
||||
currentNode = elem.childNodes.item(i)
|
||||
if currentNode.nodeType != Node.ELEMENT_NODE or currentNode.nodeName != 'g' or \
|
||||
currentNode.namespaceURI != NS['SVG']:
|
||||
i -= 1
|
||||
continue
|
||||
attributes = {a.nodeName: a.nodeValue for a in currentNode.attributes.values()}
|
||||
runStart, runEnd = i, i
|
||||
runElements = 1
|
||||
while runStart > 0:
|
||||
nextNode = elem.childNodes.item(runStart - 1)
|
||||
if nextNode.nodeType == Node.ELEMENT_NODE:
|
||||
if nextNode.nodeName != 'g' or nextNode.namespaceURI != NS['SVG']:
|
||||
break
|
||||
nextAttributes = {a.nodeName: a.nodeValue for a in nextNode.attributes.values()}
|
||||
hasNoMergeTags = (True for n in nextNode.childNodes
|
||||
if n.nodeType == Node.ELEMENT_NODE
|
||||
and n.nodeName in ('title', 'desc')
|
||||
and n.namespaceURI == NS['SVG'])
|
||||
if attributes != nextAttributes or any(hasNoMergeTags):
|
||||
break
|
||||
else:
|
||||
runElements += 1
|
||||
runStart -= 1
|
||||
else:
|
||||
runStart -= 1
|
||||
|
||||
# Next loop will start from here
|
||||
i = runStart - 1
|
||||
|
||||
if runElements < 2:
|
||||
continue
|
||||
|
||||
# Find the <g> entry that starts the run (we might have run
|
||||
# past it into a text node or a comment node.
|
||||
while True:
|
||||
node = elem.childNodes.item(runStart)
|
||||
if node.nodeType == Node.ELEMENT_NODE and node.nodeName == 'g' and node.namespaceURI == NS['SVG']:
|
||||
break
|
||||
runStart += 1
|
||||
primaryGroup = elem.childNodes.item(runStart)
|
||||
runStart += 1
|
||||
nodes = elem.childNodes[runStart:runEnd+1]
|
||||
for node in nodes:
|
||||
if node.nodeType == Node.ELEMENT_NODE and node.nodeName == 'g' and node.namespaceURI == NS['SVG']:
|
||||
# Merge
|
||||
primaryGroup.childNodes.extend(node.childNodes)
|
||||
node.childNodes = []
|
||||
else:
|
||||
primaryGroup.childNodes.append(node)
|
||||
elem.childNodes.remove(node)
|
||||
|
||||
# each child gets the same treatment, recursively
|
||||
for childNode in elem.childNodes:
|
||||
if childNode.nodeType == Node.ELEMENT_NODE:
|
||||
num += mergeSiblingGroupsWithCommonAttributes(childNode)
|
||||
|
||||
return num
|
||||
|
||||
|
||||
def createGroupsForCommonAttributes(elem):
|
||||
"""
|
||||
Creates <g> elements to contain runs of 3 or more
|
||||
|
|
@ -3658,6 +3727,8 @@ def scourString(in_string, options=None):
|
|||
while removeDuplicateGradients(doc) > 0:
|
||||
pass
|
||||
|
||||
if options.group_collapse:
|
||||
_num_elements_removed += mergeSiblingGroupsWithCommonAttributes(doc.documentElement)
|
||||
# create <g> elements if there are runs of elements with the same attributes.
|
||||
# this MUST be before moveCommonAttributesToParentGroup.
|
||||
if options.group_create:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue