Update svg tooling.

Tweaked svg_cleaner a bit to remove display none, add option to
strip whitespace.  Stripping viewBox was too aggressive, two of our
emoji have non-default viewBox values, so emit it when x or y
are not zero.

Added script to invoke scour (now must be part of installation).
This commit is contained in:
Doug Felt 2018-08-09 17:29:34 -07:00
parent b1246768db
commit 89c6545e63
2 changed files with 79 additions and 11 deletions

49
scour_svg.sh Executable file
View file

@ -0,0 +1,49 @@
#!/bin/bash
SRC_DIR=""
DST_DIR=""
SCOUR_ARGS="--strip-xml-prolog --enable-viewboxing --enable-id-stripping --enable-comment-stripping --shorten-ids --no-line-breaks --strip-xml-space"
while [ $# != 0 ]; do
case "$1" in
-s) SRC_DIR=${2}
shift
shift
;;
-d) DST_DIR=${2}
shift
shift
;;
*) echo "unrecognized arg $1"
exit 1
;;
esac
done
if [ -z "$SRC_DIR" ]; then
echo "missing source directory"
exit 1;
fi
if [ ! -d "$SRC_DIR" ]; then
echo "source dirctory '$SRC_DIR' does not exist"
exit 1;
fi
if [ -z "$DST_DIR" ]; then
echo "missing destination directory"
exit 1
fi
if [ ! -d "$DST_DIR" ]; then
echo "creating destination directory '$DST_DIR'"
mkdir -p "$DST_DIR"
fi
for file in "$SRC_DIR"/*.svg; do
dst="${file##*/}"
echo $dst
scour $SCOUR_ARGS -i "$file" -o "$DST_DIR/$dst"
done

View file

@ -72,18 +72,19 @@ class _Text_Node(object):
class SvgCleaner(object): class SvgCleaner(object):
"""Strip out unwanted parts of an svg file, primarily the xml declaration and """Strip out unwanted parts of an svg file, primarily the xml declaration and
doctype lines, comments, and some attributes of the outermost <svg> element. doctype lines, comments, and some attributes of the outermost <svg> element.
The id will be replaced when it is inserted into the font. viewBox causes The id will be replaced when it is inserted into the font. (viewBox causes
unwanted scaling when used in a font and its effect is difficult to unwanted scaling when used in a font and its effect is difficult to
predict. version is unneeded, xml:space is ignored (we're processing spaces predict, but for outside a font we need to keep it sometimes so we keep it).
version is unneeded, xml:space is ignored (we're processing spaces
so a request to maintain them has no effect). enable-background appears to so a request to maintain them has no effect). enable-background appears to
have no effect. x and y on the outermost svg element have no effect. We have no effect. x and y on the outermost svg element have no effect. We
keep width and height, and will elsewhere assume these are the dimensions keep width and height, and will elsewhere assume these are the dimensions
used for the character box.""" used for the character box."""
def __init__(self): def __init__(self, strip=False):
self.reader = SvgCleaner._Reader() self.reader = SvgCleaner._Reader()
self.cleaner = SvgCleaner._Cleaner() self.cleaner = SvgCleaner._Cleaner()
self.writer = SvgCleaner._Writer() self.writer = SvgCleaner._Writer(strip)
class _Reader(object): class _Reader(object):
"""Loosely based on fonttools's XMLReader. This generates a tree of nodes, """Loosely based on fonttools's XMLReader. This generates a tree of nodes,
@ -130,7 +131,7 @@ class SvgCleaner(object):
class _Cleaner(object): class _Cleaner(object):
def _clean_elem(self, node): def _clean_elem(self, node):
viewBox, width, height = None, None, None viewBox, x, y, width, height = None, None, None, None, None
nattrs = {} nattrs = {}
for k, v in node.attrs.items(): for k, v in node.attrs.items():
if node.name == 'svg' and k in [ if node.name == 'svg' and k in [
@ -153,14 +154,25 @@ class SvgCleaner(object):
nattrs[k] = v nattrs[k] = v
if node.name == 'svg': if node.name == 'svg':
if viewBox:
x, y, width, height = viewBox.split()
if not width or not height: if not width or not height:
if not viewBox: if not viewBox:
raise ValueError('no viewBox, width, or height') raise ValueError('no viewBox, width, or height')
width, height = viewBox.split()[2:]
nattrs['width'] = width nattrs['width'] = width
nattrs['height'] = height nattrs['height'] = height
# keep for svg use outside of font
if viewBox and (int(x) != 0 or int(y) != 0):
logging.warn('viewbox "%s" x: %s y: %s' % (viewBox, x, y));
nattrs['viewBox'] = viewBox
node.attrs = nattrs node.attrs = nattrs
# if display:none, skip this and its children
style = node.attrs.get('style')
if (style and 'display:none' in style) or node.attrs.get('display') == 'none':
node.contents = []
return
# scan contents. remove any empty text nodes, or empty 'g' element nodes. # scan contents. remove any empty text nodes, or empty 'g' element nodes.
# if a 'g' element has no attrs and only one subnode, replace it with the # if a 'g' element has no attrs and only one subnode, replace it with the
# subnode. # subnode.
@ -212,6 +224,9 @@ class SvgCleaner(object):
"""For text nodes, replaces sequences of whitespace with a single space. """For text nodes, replaces sequences of whitespace with a single space.
For elements, replaces sequences of whitespace in attributes, and For elements, replaces sequences of whitespace in attributes, and
removes unwanted attributes from <svg> elements.""" removes unwanted attributes from <svg> elements."""
def __init__(self, strip):
logging.warning('writer strip: %s' % strip);
self._strip = strip
def _write_node(self, node, lines, indent): def _write_node(self, node, lines, indent):
"""Node is a node generated by _Reader, either a TextNode or an """Node is a node generated by _Reader, either a TextNode or an
@ -222,7 +237,7 @@ class SvgCleaner(object):
if node.text: if node.text:
lines.append(node.text) lines.append(node.text)
else: else:
margin = ' ' * indent margin = '' if self._strip else ' ' * indent
line = [margin] line = [margin]
line.append('<%s' % node.name) line.append('<%s' % node.name)
# custom sort attributes of svg, yes this is a hack # custom sort attributes of svg, yes this is a hack
@ -258,7 +273,7 @@ class SvgCleaner(object):
# the result. # the result.
lines = [] lines = []
self._write_node(root, lines, 0) self._write_node(root, lines, 0)
return '\n'.join(lines) return ''.join(lines) if self._strip else '\n'.join(lines)
def tree_from_text(self, svg_text): def tree_from_text(self, svg_text):
return self.reader.from_text(svg_text) return self.reader.from_text(svg_text)
@ -276,7 +291,7 @@ class SvgCleaner(object):
return self.tree_to_text(tree) return self.tree_to_text(tree)
def clean_svg_files(in_dir, out_dir, match_pat=None, clean=False): def clean_svg_files(in_dir, out_dir, match_pat=None, clean=False, strip=False):
regex = re.compile(match_pat) if match_pat else None regex = re.compile(match_pat) if match_pat else None
count = 0 count = 0
@ -286,7 +301,7 @@ def clean_svg_files(in_dir, out_dir, match_pat=None, clean=False):
out_dir = tool_utils.ensure_dir_exists(out_dir, clean=clean) out_dir = tool_utils.ensure_dir_exists(out_dir, clean=clean)
cleaner = SvgCleaner() cleaner = SvgCleaner(strip)
for file_name in os.listdir(in_dir): for file_name in os.listdir(in_dir):
if regex and not regex.match(file_name): if regex and not regex.match(file_name):
continue continue
@ -320,6 +335,9 @@ def main():
metavar='regex', default=None) metavar='regex', default=None)
parser.add_argument( parser.add_argument(
'-l', '--loglevel', help='log level name/value', default='warning') '-l', '--loglevel', help='log level name/value', default='warning')
parser.add_argument(
'-w', '--strip_whitespace', help='remove newlines and indentation',
action='store_true')
args = parser.parse_args() args = parser.parse_args()
tool_utils.setup_logging(args.loglevel) tool_utils.setup_logging(args.loglevel)
@ -331,7 +349,8 @@ def main():
logging.info('Writing output to %s', args.out_dir) logging.info('Writing output to %s', args.out_dir)
clean_svg_files( clean_svg_files(
args.in_dir, args.out_dir, match_pat=args.regex, clean=args.clean) args.in_dir, args.out_dir, match_pat=args.regex, clean=args.clean,
strip=args.strip_whitespace)
if __name__ == '__main__': if __name__ == '__main__':