Index: Source/devtools/scripts/optimize_svg_file.py |
diff --git a/Source/devtools/scripts/optimize_svg_file.py b/Source/devtools/scripts/optimize_svg_file.py |
deleted file mode 100755 |
index 4394daa5efcfb7782692a77a7848b0a7c2aacd4f..0000000000000000000000000000000000000000 |
--- a/Source/devtools/scripts/optimize_svg_file.py |
+++ /dev/null |
@@ -1,348 +0,0 @@ |
-#!/usr/bin/env python |
-# Copyright (c) 2014 Google Inc. All rights reserved. |
-# |
-# Redistribution and use in source and binary forms, with or without |
-# modification, are permitted provided that the following conditions are |
-# met: |
-# |
-# * Redistributions of source code must retain the above copyright |
-# notice, this list of conditions and the following disclaimer. |
-# * Redistributions in binary form must reproduce the above |
-# copyright notice, this list of conditions and the following disclaimer |
-# in the documentation and/or other materials provided with the |
-# distribution. |
-# * Neither the name of Google Inc. nor the names of its |
-# contributors may be used to endorse or promote products derived from |
-# this software without specific prior written permission. |
-# |
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
- |
-import re |
-import string |
-import sys |
-import xml.dom.minidom |
- |
- |
-def _optimize_number(value): |
- try: |
- if value[0] == "#" or value[0] == "n": |
- return value |
- numeric = round(float(value), 2) |
- short = int(numeric) |
- if short == numeric: |
- return str(short) |
- return str(numeric) |
- except: |
- return value |
- |
- |
-def _optimize_value(value, default): |
- value = value.strip() |
- if value.endswith("px"): |
- value = value[:-2] |
- if value.endswith("pt"): |
- print "WARNING: 'pt' size units are undesirable." |
- if len(value) == 7 and value[0] == "#" and value[1] == value[2] and value[3] == value[4] and value[6] == value[6]: |
- value = "#" + value[1] + value[3] + value[5] |
- value = _optimize_number(value) |
- if value == default: |
- value = "" |
- return value |
- |
- |
-def _optimize_values(node, defaults): |
- items = {} |
- if node.hasAttribute("style"): |
- for item in node.getAttribute("style").strip(";").split(";"): |
- [key, value] = item.split(":", 1) |
- key = key.strip() |
- if key not in defaults: |
- continue |
- items[key] = _optimize_value(value, defaults[key]) |
- |
- for key in defaults.keys(): |
- if node.hasAttribute(key): |
- value = _optimize_value(node.getAttribute(key), defaults[key]) |
- items[key] = value |
- |
- if len([(key, value) for key, value in items.iteritems() if value != ""]) > 4: |
- style = [] |
- for key, value in items.iteritems(): |
- if node.hasAttribute(key): |
- node.removeAttribute(key) |
- if value != "": |
- style.append(key + ":" + value) |
- node.setAttribute("style", string.join(sorted(style), ";")) |
- else: |
- if node.hasAttribute("style"): |
- node.removeAttribute("style") |
- for key, value in items.iteritems(): |
- if value == "": |
- if node.hasAttribute(key): |
- node.removeAttribute(key) |
- else: |
- node.setAttribute(key, value) |
- |
- |
-def _optimize_path(value): |
- path = [] |
- commands = "mMzZlLhHvVcCsSqQtTaA" |
- last = 0 |
- raw = " " + value + " " |
- for i in range(len(raw)): |
- if raw[i] in [" ", ","]: |
- if last < i: |
- path.append(raw[last:i]) |
- # Consumed whitespace |
- last = i + 1 |
- elif raw[i] == "-" and raw[i - 1] != "e" and raw[i - 1] != "e": |
- if last < i: |
- path.append(raw[last:i]) |
- last = i |
- elif raw[i] in commands: |
- if last < i: |
- path.append(raw[last:i]) |
- path.append(raw[i]) |
- # Consumed command |
- last = i + 1 |
- out = [] |
- need_space = False |
- for item in path: |
- if item in commands: |
- need_space = False |
- else: |
- item = _optimize_number(item) |
- if need_space and item[0] != "-": |
- out.append(" ") |
- need_space = True |
- out.append(item) |
- return string.join(out, "") |
- |
- |
-def _optimize_paths(dom): |
- for node in dom.getElementsByTagName("path"): |
- path = node.getAttribute("d") |
- node.setAttribute("d", _optimize_path(path)) |
- |
- |
-def _check_groups(dom, errors): |
- if len(dom.getElementsByTagName("g")) != 0: |
- errors.append("Groups are prohibited.") |
- |
- |
-def _check_text(dom, errors): |
- if len(dom.getElementsByTagName("text")) != 0: |
- errors.append("Text elements prohibited.") |
- |
- |
-def _check_transform(dom, errors): |
- if (any(path.hasAttribute("transform") for path in dom.getElementsByTagName("path")) or |
- any(rect.hasAttribute("transform") for rect in dom.getElementsByTagName("rect"))): |
- errors.append("Transforms are prohibited.") |
- |
- |
-def _cleanup_dom_recursively(node, dtd): |
- junk = [] |
- for child in node.childNodes: |
- if child.nodeName in dtd: |
- _cleanup_dom_recursively(child, dtd[child.nodeName]) |
- else: |
- junk.append(child) |
- |
- for child in junk: |
- node.removeChild(child) |
- |
- |
-def _cleanup_dom(dom): |
- dtd = { |
- "svg": { |
- "sodipodi:namedview": { |
- "inkscape:grid": {}}, |
- "defs": { |
- "linearGradient": { |
- "stop": {}}, |
- "radialGradient": { |
- "stop": {}}}, |
- "path": {}, |
- "rect": {}}} |
- _cleanup_dom_recursively(dom, dtd) |
- |
- |
-def _cleanup_sodipodi(dom): |
- for node in dom.getElementsByTagName("svg"): |
- for key in node.attributes.keys(): |
- if key not in ["height", "version", "width", "xml:space", "xmlns", "xmlns:xlink", "xmlns:sodipodi", "xmlns:inkscape"]: |
- node.removeAttribute(key) |
- |
- for node in dom.getElementsByTagName("sodipodi:namedview"): |
- for key in node.attributes.keys(): |
- if key != "showgrid": |
- node.removeAttribute(key) |
- |
- for nodeName in ["defs", "linearGradient", "path", "radialGradient", "rect", "stop", "svg"]: |
- for node in dom.getElementsByTagName(nodeName): |
- for key in node.attributes.keys(): |
- if key.startswith("sodipodi:") or key.startswith("inkscape:"): |
- node.removeAttribute(key) |
- |
- |
-def _cleanup_ids(dom): |
- for nodeName in ["defs", "path", "rect", "sodipodi:namedview", "stop", "svg"]: |
- for node in dom.getElementsByTagName(nodeName): |
- if node.hasAttribute("id"): |
- node.removeAttribute("id") |
- |
- |
-def _optimize_path_attributes(dom): |
- defaults = { |
- "fill": "#000", |
- "fill-opacity": "1", |
- "fill-rule": "nonzero", |
- "opacity": "1", |
- "stroke": "none", |
- "stroke-dasharray": "none", |
- "stroke-linecap": "butt", |
- "stroke-linejoin": "miter", |
- "stroke-miterlimit": "4", |
- "stroke-opacity": "1", |
- "stroke-width": "1"} |
- for nodeName in ["path", "rect"]: |
- for node in dom.getElementsByTagName(nodeName): |
- _optimize_values(node, defaults) |
- |
- |
-def _optimize_stop_attributes(dom): |
- defaults = { |
- "stop-color": "#000", |
- "stop-opacity": "1"} |
- for node in dom.getElementsByTagName("stop"): |
- _optimize_values(node, defaults) |
- |
- |
-def _cleanup_gradients(dom): |
- while True: |
- gradients = [] |
- for nodeName in ["linearGradient", "radialGradient"]: |
- for node in dom.getElementsByTagName(nodeName): |
- name = node.getAttribute("id") |
- gradients.append({"node": node, "ref": "#" + name, "url": "url(#" + name + ")", "has_ref": False}) |
- for nodeName in ["linearGradient", "path", "radialGradient", "rect"]: |
- for node in dom.getElementsByTagName(nodeName): |
- for key in node.attributes.keys(): |
- if key == "id": |
- continue |
- value = node.getAttribute(key) |
- for gradient in gradients: |
- if gradient["has_ref"] == False: |
- if value == gradient["ref"] or value.find(gradient["url"]) != -1: |
- gradient["has_ref"] = True |
- finished = True |
- for gradient in gradients: |
- if gradient["has_ref"] == False: |
- gradient["node"].parentNode.removeChild(gradient["node"]) |
- finished = False |
- if finished: |
- break |
- |
- |
-def _generate_name(num): |
- letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
- n = len(letters) |
- if num < n: |
- return letters[num] |
- return letters[num / n] + letters[num % n] |
- |
- |
-def _optimize_gradient_ids(dom): |
- gradients = [] |
- names = {} |
- for nodeName in ["linearGradient", "radialGradient"]: |
- for node in dom.getElementsByTagName(nodeName): |
- name = node.getAttribute("id") |
- gradients.append({"node": node, "name": name, "ref": "#" + name, "url": "url(#" + name + ")", "new_name": None}) |
- names[name] = True |
- cntr = 0 |
- for gradient in gradients: |
- if len(gradient["name"]) > 2: |
- while True: |
- new_name = _generate_name(cntr) |
- cntr = cntr + 1 |
- if new_name not in names: |
- gradient["new_name"] = new_name |
- gradient["node"].setAttribute("id", new_name) |
- break |
- if cntr == 0: |
- return |
- gradients = [gradient for gradient in gradients if gradient["new_name"] is not None] |
- for nodeName in ["linearGradient", "path", "radialGradient", "rect"]: |
- for node in dom.getElementsByTagName(nodeName): |
- for key in node.attributes.keys(): |
- if key == "id": |
- continue |
- value = node.getAttribute(key) |
- for gradient in gradients: |
- if value == gradient["ref"]: |
- node.setAttribute(key, "#" + gradient["new_name"]) |
- elif value.find(gradient["url"]) != -1: |
- value = value.replace(gradient["url"], "url(#" + gradient["new_name"] + ")") |
- node.setAttribute(key, value) |
- |
- |
-def _build_xml(dom): |
- raw_xml = dom.toxml("utf-8") |
- # Turn to one-node-per-line |
- pretty_xml = re.sub("([^?])(/?>)(?!</)", "\\1\\n\\2", raw_xml) |
- return pretty_xml |
- |
- |
-def optimize_svg(file, errors): |
- try: |
- dom = xml.dom.minidom.parse(file) |
- except: |
- errors.append("Can't parse XML.") |
- return |
- |
- _check_groups(dom, errors) |
- _check_text(dom, errors) |
- _check_transform(dom, errors) |
- if len(errors) != 0: |
- return |
- |
- _cleanup_dom(dom) |
- _cleanup_ids(dom) |
- _cleanup_sodipodi(dom) |
- _cleanup_gradients(dom) |
- |
- _optimize_gradient_ids(dom) |
- _optimize_path_attributes(dom) |
- _optimize_stop_attributes(dom) |
- _optimize_paths(dom) |
- # TODO: Bake nested gradients |
- # TODO: Optimize gradientTransform |
- |
- with open(file, "w") as text_file: |
- text_file.write(_build_xml(dom)) |
- |
- |
-if __name__ == '__main__': |
- if len(sys.argv) != 1: |
- print('usage: %s input_file' % sys.argv[0]) |
- sys.exit(1) |
- errors = [] |
- optimize_svg(sys.argv[1], errors) |
- for error in errors: |
- print "ERROR: %s" % (error) |
- if len(errors) != 0: |
- sys.exit(1) |
- else: |
- sys.exit(0) |