Update svg emoji tools.

- Reformat lines to 80 columns.

- Use logging instead of verbose/quiet other options.

- A few miscellaneous small fixes/tweaks to parameters.  Removed some
  file-path-relative stuff that assumed old directory structure.

This uses some new fns in nototools.tool_utils, see nototools#220.
pull/50/head
Doug Felt 2016-04-12 17:07:27 -07:00
parent e7a7241a92
commit 83978272f5
4 changed files with 211 additions and 147 deletions

View File

@ -19,28 +19,25 @@
import argparse import argparse
import glob import glob
import logging
import os import os
import re import re
import sys import sys
# find the noto root, so we can get nototools
# alternatively we could just define PYTHONPATH or always run this from
# noto root, but for testing we might not always be doing that.
_noto_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))
sys.path.append(_noto_root)
from fontTools.ttLib.tables import otTables from fontTools.ttLib.tables import otTables
from fontTools.ttLib.tables import _g_l_y_f from fontTools.ttLib.tables import _g_l_y_f
from fontTools.ttLib.tables import S_V_G_ as SVG from fontTools.ttLib.tables import S_V_G_ as SVG
from fontTools import ttx from fontTools import ttx
from nototools import tool_utils
import add_emoji_gsub import add_emoji_gsub
import svg_builder import svg_builder
import svg_cleaner
class FontBuilder(object): class FontBuilder(object):
"""A utility for mutating a ttx font. This maintains glyph_order, cmap, and hmtx tables, """A utility for mutating a ttx font. This maintains glyph_order, cmap, and
and optionally GSUB, glyf, and SVN tables as well.""" hmtx tables, and optionally GSUB, glyf, and SVN tables as well."""
def __init__(self, font): def __init__(self, font):
self.font = font; self.font = font;
@ -49,8 +46,8 @@ class FontBuilder(object):
self.hmtx = font['hmtx'].metrics self.hmtx = font['hmtx'].metrics
def init_gsub(self): def init_gsub(self):
"""Call this if you are going to add ligatures to the font. Creates a GSUB table """Call this if you are going to add ligatures to the font. Creates a GSUB
if there isn't one already.""" table if there isn't one already."""
if hasattr(self, 'ligatures'): if hasattr(self, 'ligatures'):
return return
@ -73,8 +70,8 @@ class FontBuilder(object):
self.ligatures = lookup.SubTable[0].ligatures self.ligatures = lookup.SubTable[0].ligatures
def init_glyf(self): def init_glyf(self):
"""Call this if you need to create empty glyf entries in the font when you add a new """Call this if you need to create empty glyf entries in the font when you
glyph.""" add a new glyph."""
if hasattr(self, 'glyphs'): if hasattr(self, 'glyphs'):
return return
@ -87,8 +84,9 @@ class FontBuilder(object):
self.glyphs = font['glyf'].glyphs self.glyphs = font['glyf'].glyphs
def init_svg(self): def init_svg(self):
"""Call this if you expect to add SVG images in the font. This calls init_glyf since SVG """Call this if you expect to add SVG images in the font. This calls
support currently requires fallback glyf records for each SVG image.""" init_glyf since SVG support currently requires fallback glyf records for
each SVG image."""
if hasattr(self, 'svgs'): if hasattr(self, 'svgs'):
return return
@ -131,7 +129,8 @@ class FontBuilder(object):
self.ligatures[first] = [lig] self.ligatures[first] = [lig]
def _add_empty_glyph(self, glyphstr, name): def _add_empty_glyph(self, glyphstr, name):
"""Create an empty glyph. If glyphstr is not a ligature, add a cmap entry for it.""" """Create an empty glyph. If glyphstr is not a ligature, add a cmap entry
for it."""
if len(glyphstr) == 1: if len(glyphstr) == 1:
self.cmap[ord(glyphstr)] = name self.cmap[ord(glyphstr)] = name
self.hmtx[name] = [0, 0] self.hmtx[name] = [0, 0]
@ -140,11 +139,12 @@ class FontBuilder(object):
self.glyphs[name] = _g_l_y_f.Glyph() self.glyphs[name] = _g_l_y_f.Glyph()
def add_components_and_ligature(self, glyphstr): def add_components_and_ligature(self, glyphstr):
"""Convert glyphstr to a name and check if it already exists. If not, check if it is a """Convert glyphstr to a name and check if it already exists. If not, check
ligature (longer than one codepoint), and if it is, generate empty glyphs with cmap if it is a ligature (longer than one codepoint), and if it is, generate
entries for any missing ligature components and add a ligature record. Then generate empty glyphs with cmap entries for any missing ligature components and add a
an empty glyph for the name. Return a tuple with the name, index, and a bool ligature record. Then generate an empty glyph for the name. Return a tuple
indicating whether the glyph already existed.""" with the name, index, and a bool indicating whether the glyph already
existed."""
name = self.glyph_name(glyphstr) name = self.glyph_name(glyphstr)
index = self.glyph_name_to_index(name) index = self.glyph_name_to_index(name)
@ -161,8 +161,8 @@ class FontBuilder(object):
return name, index, exists return name, index, exists
def add_svg(self, doc, hmetrics, name, index): def add_svg(self, doc, hmetrics, name, index):
"""Add an svg table entry. If hmetrics is not None, update the hmtx table. This """Add an svg table entry. If hmetrics is not None, update the hmtx table.
expects the glyph has already been added.""" This expects the glyph has already been added."""
# sanity check to make sure name and index correspond. # sanity check to make sure name and index correspond.
assert name == self.glyph_index_to_name(index) assert name == self.glyph_index_to_name(index)
if hmetrics: if hmetrics:
@ -171,27 +171,27 @@ class FontBuilder(object):
self.svgs.append(svg_record) self.svgs.append(svg_record)
def collect_glyphstr_file_pairs(prefix, ext, include=None, exclude=None, verbosity=1): def collect_glyphstr_file_pairs(prefix, ext, include=None, exclude=None):
"""Scan files with the given prefix and extension, and return a list of (glyphstr, """Scan files with the given prefix and extension, and return a list of
filename) where glyphstr is the character or ligature, and filename is the image file (glyphstr, filename) where glyphstr is the character or ligature, and filename
associated with it. The glyphstr is formed by decoding the filename (exclusive of the is the image file associated with it. The glyphstr is formed by decoding the
prefix) as a sequence of hex codepoints separated by underscore. Include, if defined, is filename (exclusive of the prefix) as a sequence of hex codepoints separated
a regex string to include only matched filenames. Exclude, if defined, is a regex string by underscore. Include, if defined, is a regex string to include only matched
to exclude matched filenames, and is applied after include.""" filenames. Exclude, if defined, is a regex string to exclude matched
filenames, and is applied after include."""
image_files = {} image_files = {}
glob_pat = "%s*.%s" % (prefix, ext) glob_pat = "%s*.%s" % (prefix, ext)
leading = len(prefix) leading = len(prefix)
trailing = len(ext) + 1 # include dot trailing = len(ext) + 1 # include dot
if verbosity: logging.info("Looking for images matching '%s'.", glob_pat)
print "Looking for images matching '%s'." % glob_pat
ex_count = 0 ex_count = 0
ex = re.compile(exclude) if exclude else None ex = re.compile(exclude) if exclude else None
inc = re.compile(include) if include else None inc = re.compile(include) if include else None
if verbosity and inc: if inc:
print "Including images matching '%s'." % include logging.info("Including images matching '%s'.", include)
if verbosity and ex: if ex:
print "Excluding images matching '%s'." % exclude logging.info("Excluding images matching '%s'.", exclude)
for image_file in glob.glob(glob_pat): for image_file in glob.glob(glob_pat):
if inc and not inc.search(image_file): if inc and not inc.search(image_file):
@ -211,76 +211,83 @@ def collect_glyphstr_file_pairs(prefix, ext, include=None, exclude=None, verbosi
u = unichr(int(codes, 16)) u = unichr(int(codes, 16))
image_files[u] = image_file image_files[u] = image_file
if verbosity and ex_count: if ex_count:
print "Excluded %d files." % ex_count logging.info("Excluded %d files.", ex_count)
if not image_files: if not image_files:
raise Exception ("No image files matching '%s'." % glob_pat) raise Exception ("No image files matching '%s'.", glob_pat)
if verbosity: logging.info("Matched %s files.", len(image_files))
print "Included %s files." % len(image_files)
return image_files.items() return image_files.items()
def sort_glyphstr_tuples(glyphstr_tuples): def sort_glyphstr_tuples(glyphstr_tuples):
"""The list contains tuples whose first element is a string representing a character or """The list contains tuples whose first element is a string representing a
ligature. It is sorted with shorter glyphstrs first, then alphabetically. This ensures character or ligature. It is sorted with shorter glyphstrs first, then
that ligature components are added to the font before any ligatures that contain them.""" alphabetically. This ensures that ligature components are added to the font
before any ligatures that contain them."""
glyphstr_tuples.sort(key=lambda t: (len(t[0]), t[0])) glyphstr_tuples.sort(key=lambda t: (len(t[0]), t[0]))
def add_image_glyphs(in_file, out_file, pairs, verbosity=1): def add_image_glyphs(in_file, out_file, pairs):
"""Add images from pairs (glyphstr, filename) to .ttx file in_file and write """Add images from pairs (glyphstr, filename) to .ttx file in_file and write
to .ttx file out_file.""" to .ttx file out_file."""
quiet = verbosity < 2 font = ttx.TTFont()
font = ttx.TTFont(quiet=quiet) font.importXML(in_file)
font.importXML(in_file, quiet=quiet)
sort_glyphstr_tuples(pairs) sort_glyphstr_tuples(pairs)
font_builder = FontBuilder(font) font_builder = FontBuilder(font)
# we've already sorted by length, so the longest glyphstrs are at the end. To see if # we've already sorted by length, so the longest glyphstrs are at the end. To
# we have ligatures, we just need to check the last one. # see if we have ligatures, we just need to check the last one.
if len(pairs[-1][0]) > 1: if len(pairs[-1][0]) > 1:
font_builder.init_gsub() font_builder.init_gsub()
img_builder = svg_builder.SvgBuilder(font_builder) img_builder = svg_builder.SvgBuilder(font_builder)
for glyphstr, filename in pairs: for glyphstr, filename in pairs:
if verbosity > 1: logging.debug("Adding glyph for U+%s", ",".join(
print "Adding glyph for U+%s" % ",".join(["%04X" % ord(char) for char in glyphstr]) ["%04X" % ord(char) for char in glyphstr]))
img_builder.add_from_filename(glyphstr, filename) img_builder.add_from_filename(glyphstr, filename)
font.saveXML(out_file, quiet=quiet) font.saveXML(out_file)
if verbosity: logging.info("Added %s images to %s", len(pairs), out_file)
print "added %s images to %s" % (len(pairs), out_file)
def main(argv): def main(argv):
usage = """This will search for files that have image_prefix followed by one or more usage = """This will search for files that have image_prefix followed by one
hex numbers (separated by underscore if more than one), and end in ".svg". or more hex numbers (separated by underscore if more than one), and end in
For example, if image_prefix is "icons/u", then files with names like ".svg". For example, if image_prefix is "icons/u", then files with names like
"icons/u1F4A9.svg" or "icons/u1F1EF_1F1F5.svg" will be loaded. The script "icons/u1F4A9.svg" or "icons/u1F1EF_1F1F5.svg" will be loaded. The script
then adds cmap, htmx, and potentially GSUB entries for the Unicode then adds cmap, htmx, and potentially GSUB entries for the Unicode characters
characters found. The advance width will be chosen based on image aspect found. The advance width will be chosen based on image aspect ratio. If
ratio. If Unicode values outside the BMP are desired, the existing cmap Unicode values outside the BMP are desired, the existing cmap table should be
table should be of the appropriate (format 12) type. Only the first cmap of the appropriate (format 12) type. Only the first cmap table and the first
table and the first GSUB lookup (if existing) are modified.""" GSUB lookup (if existing) are modified."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Update cmap, glyf, GSUB, and hmtx tables from image glyphs.", epilog=usage) description='Update cmap, glyf, GSUB, and hmtx tables from image glyphs.',
parser.add_argument('in_file', help="Input ttx file name.") epilog=usage)
parser.add_argument('out_file', help="Output ttx file name.") parser.add_argument(
parser.add_argument('image_prefix', help="Location and prefix of image files.") 'in_file', help='Input ttx file name.', metavar='fname')
parser.add_argument('-i', '--include', help='include files whoses name matches this regex') parser.add_argument(
parser.add_argument('-e', '--exclude', help='exclude files whose name matches this regex') 'out_file', help='Output ttx file name.', metavar='fname')
parser.add_argument('--quiet', '-q', dest='v', help="quiet operation.", default=1, parser.add_argument(
action='store_const', const=0) 'image_prefix', help='Location and prefix of image files.',
parser.add_argument('--verbose', '-v', dest='v', help="verbose operation.", metavar='path')
action='store_const', const=2) parser.add_argument(
'-i', '--include', help='include files whoses name matches this regex',
metavar='regex')
parser.add_argument(
'-e', '--exclude', help='exclude files whose name matches this regex',
metavar='regex')
parser.add_argument(
'-l', '--loglevel', help='log level name', default='warning')
args = parser.parse_args(argv) args = parser.parse_args(argv)
pairs = collect_glyphstr_file_pairs(args.image_prefix, 'svg', include=args.include, tool_utils.setup_logging(args.loglevel)
exclude=args.exclude, verbosity=args.v)
add_image_glyphs(args.in_file, args.out_file, pairs, verbosity=args.v) pairs = collect_glyphstr_file_pairs(
args.image_prefix, 'svg', include=args.include, exclude=args.exclude)
add_image_glyphs(args.in_file, args.out_file, pairs)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -33,12 +33,15 @@ the files without messing with the originals."""
import argparse import argparse
import glob import glob
import logging
import os import os
import os.path import os.path
import re import re
import shutil import shutil
import sys import sys
from nototools import tool_utils
def _is_svg(f): def _is_svg(f):
return f.endswith('.svg') return f.endswith('.svg')
@ -48,23 +51,27 @@ def _is_svg_and_startswith_emoji(f):
def _flag_rename(f): def _flag_rename(f):
"""Converts file names from region-flags files (upper-case ASCII) to our expected """Converts a file name from two-letter upper-case ASCII to our expected
'encoded-codepoint-ligature' form, mapping each character to the corresponding 'emoji_uXXXXX_XXXXX form, mapping each character to the corresponding
regional indicator symbol.""" regional indicator symbol."""
cp_strs = [] cp_strs = []
name, ext = os.path.splitext(f) name, ext = os.path.splitext(f)
if len(name) != 2:
raise ValueError('illegal flag name "%s"' % f)
for cp in name: for cp in name:
if not ('A' <= cp <= 'Z'):
raise ValueError('illegal flag name "%s"' % f)
ncp = 0x1f1e6 - 0x41 + ord(cp) ncp = 0x1f1e6 - 0x41 + ord(cp)
cp_strs.append("%04x" % ncp) cp_strs.append("%04x" % ncp)
return 'emoji_u%s%s' % ('_'.join(cp_strs), ext) return 'emoji_u%s%s' % ('_'.join(cp_strs), ext)
def copy_with_rename(src_dir, dst_dir, accept_pred=None, rename=None, verbosity=1): def copy_with_rename(src_dir, dst_dir, accept_pred=None, rename=None):
"""Copy files from src_dir to dst_dir that match accept_pred (all if None) and rename """Copy files from src_dir to dst_dir that match accept_pred (all if None) and
using rename (if not None), replacing existing files. accept_pred takes the filename rename using rename (if not None), replacing existing files. accept_pred
and returns True if the file should be copied, rename takes the filename and returns a takes the filename and returns True if the file should be copied, rename takes
new file name.""" the filename and returns a new file name."""
count = 0 count = 0
replace_count = 0 replace_count = 0
@ -75,66 +82,69 @@ def copy_with_rename(src_dir, dst_dir, accept_pred=None, rename=None, verbosity=
src = os.path.join(src_dir, src_filename) src = os.path.join(src_dir, src_filename)
dst = os.path.join(dst_dir, dst_filename) dst = os.path.join(dst_dir, dst_filename)
if os.path.exists(dst): if os.path.exists(dst):
if verbosity > 1: logging.debug('Replacing existing file %s', dst)
print "Replacing existing file " + dst
os.unlink(dst) os.unlink(dst)
replace_count += 1 replace_count += 1
shutil.copy2(src, dst) shutil.copy2(src, dst)
if verbosity > 1: logging.debug('cp -p %s %s', src, dst)
print "cp -p %s %s" % (src, dst)
count += 1 count += 1
if verbosity: if logging.getLogger().getEffectiveLevel() >= 20:
print "Copied/renamed %d files from %s to %s" % (count, src_dir, dst_dir) src_short = tool_utils.short_path(src_dir)
return count, replace_count dst_short = tool_utils.short_path(dst_dir)
logging.info('Copied %d files (replacing %d) from %s to %s',
count, replace_count, src_short, dst_short)
def build_svg_dir(dst_dir, clean=False, flags_only=False, verbosity=1): def build_svg_dir(dst_dir, clean=False, emoji_dir='', flags_dir=''):
"""Copies/renames files from noto/color_emoji/svg and then noto/third_party/region-flags/svg, """Copies/renames files from emoji_dir and then flag_dir, giving them the
giving them the standard format and prefix ('emoji_u' followed by codepoints expressed standard format and prefix ('emoji_u' followed by codepoints expressed in hex
in hex separated by underscore). If clean, removes the target dir before proceding. separated by underscore). If clean, removes the target dir before proceding.
If flags_only, only does the region-flags.""" If either emoji_dir or flag_dir are empty, skips them."""
if not os.path.isdir(dst_dir): dst_dir = tool_utils.ensure_dir_exists(dst_dir, clean=clean)
os.makedirs(dst_dir)
elif clean:
shutil.rmtree(dst_dir)
os.makedirs(dst_dir)
# get files from path relative to noto if not emoji_dir and not flag_dir:
notopath = re.match("^.*/noto/", os.path.realpath(__file__)).group() logging.warning('Nothing to do.')
return
# copy region flags, generating new names based on the tlds. if emoji_dir:
flag_dir = os.path.join(notopath, "third_party/region-flags/svg") copy_with_rename(
count, replace_count = copy_with_rename( emoji_dir, dst_dir, accept_pred=_is_svg_and_startswith_emoji)
flag_dir, dst_dir, accept_pred=_is_svg, rename=_flag_rename, verbosity=verbosity)
# copy the 'good' svg if flags_dir:
if not flags_only: copy_with_rename(
svg_dir = os.path.join(notopath, "color_emoji/svg") flags_dir, dst_dir, accept_pred=_is_svg, rename=_flag_rename)
temp_count, temp_replace_count = copy_with_rename(
svg_dir, dst_dir, accept_pred=_is_svg_and_startswith_emoji, verbosity=verbosity)
count += temp_count
replace_count += temp_replace_count
if verbosity:
if replace_count:
print "Replaced %d existing files" % replace_count
print "Created %d total files" % (count - replace_count)
def main(argv): def main(argv):
DEFAULT_EMOJI_DIR = '[emoji]/svg'
DEFAULT_FLAGS_DIR = '[emoji]/third_party/region-flags/svg'
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Collect svg files into target directory with prefix.") description='Collect svg files into target directory with prefix.')
parser.add_argument('dst_dir', help="Directory to hold symlinks to files.") parser.add_argument(
parser.add_argument('--clean', '-c', help="Replace target directory", action='store_true') 'dst_dir', help='Directory to hold copied files.', metavar='dir')
parser.add_argument('--flags_only', '-fo', help="Only copy region-flags", action='store_true') parser.add_argument(
parser.add_argument('--quiet', '-q', dest='v', help="quiet operation.", default=1, '--clean', '-c', help='Replace target directory', action='store_true')
action='store_const', const=0) parser.add_argument(
parser.add_argument('--verbose', '-v', dest='v', help="verbose operation.", '--flags_dir', '-f', metavar='dir', help='directory containing flag svg, '
action='store_const', const=2) 'default %s' % DEFAULT_FLAGS_DIR, default=DEFAULT_FLAGS_DIR)
parser.add_argument(
'--emoji_dir', '-e', metavar='dir',
help='directory containing emoji svg, default %s' % DEFAULT_EMOJI_DIR,
default=DEFAULT_EMOJI_DIR)
parser.add_argument(
'-l', '--loglevel', help='log level name/value', default='warning')
args = parser.parse_args(argv) args = parser.parse_args(argv)
build_svg_dir(args.dst_dir, clean=args.clean, flags_only=args.flags_only, verbosity=args.v) tool_utils.setup_logging(args.loglevel)
args.flags_dir = tool_utils.resolve_path(args.flags_dir)
args.emoji_dir = tool_utils.resolve_path(args.emoji_dir)
build_svg_dir(
args.dst_dir, clean=args.clean, emoji_dir=args.emoji_dir,
flags_dir=args.flags_dir)
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) main(sys.argv[1:])

View File

@ -109,7 +109,8 @@ class SvgBuilder(object):
wid = tree.attrs.get('width') wid = tree.attrs.get('width')
ht = tree.attrs.get('height') ht = tree.attrs.get('height')
if not (wid and ht): if not (wid and ht):
raise "missing viewBox and width or height attrs" raise ValueError(
'missing viewBox and width or height attrs (%s)' % filename)
x, y, w, h = 0, 0, self._strip_px(wid), self._strip_px(ht) x, y, w, h = 0, 0, self._strip_px(wid), self._strip_px(ht)
# We're going to assume default values for preserveAspectRatio for now, # We're going to assume default values for preserveAspectRatio for now,
@ -175,9 +176,11 @@ class SvgBuilder(object):
# svg element. Unlike chrome. So either we apply an inverse transform, or # svg element. Unlike chrome. So either we apply an inverse transform, or
# insert a group with the clip between the svg and its children. The latter # insert a group with the clip between the svg and its children. The latter
# seems cleaner, ultimately. # seems cleaner, ultimately.
clip_id = 'clip_' + ''.join(random.choice(string.ascii_lowercase) for i in range(8)) clip_id = 'clip_' + ''.join(
clip_text = """<g clip-path="url(#%s)"><clipPath id="%s"> random.choice(string.ascii_lowercase) for i in range(8))
<path d="M%g %gh%gv%gh%gz"/></clipPath></g>""" % (clip_id, clip_id, x, y, w, h, -w) clip_text = ('<g clip-path="url(#%s)"><clipPath id="%s">'
'<path d="M%g %gh%gv%gh%gz"/></clipPath></g>' % (
clip_id, clip_id, x, y, w, h, -w))
clip_tree = cleaner.tree_from_text(clip_text) clip_tree = cleaner.tree_from_text(clip_text)
clip_tree.contents.extend(tree.contents) clip_tree.contents.extend(tree.contents)
tree.contents = [clip_tree] tree.contents = [clip_tree]

View File

@ -17,9 +17,14 @@
import argparse import argparse
import codecs import codecs
import os.path import logging
import os
from os import path
import re import re
import sys import sys
from nototools import tool_utils
from xml.parsers import expat from xml.parsers import expat
from xml.sax import saxutils from xml.sax import saxutils
@ -115,16 +120,32 @@ 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
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 [
'x', 'y', 'id', 'version', 'viewBox', 'width', 'height', 'x', 'y', 'id', 'version', 'viewBox', 'width', 'height',
'enable-background', 'xml:space']: 'enable-background', 'xml:space']:
if k == 'viewBox':
viewBox = v
elif k == 'width':
width = v
elif k == 'height':
height = v
continue continue
v = re.sub('\s+', ' ', v) v = re.sub('\s+', ' ', v)
nattrs[k] = v nattrs[k] = v
if node.name == 'svg':
if not width or not height:
if not viewBox:
raise ValueError('no viewBox, width, or height')
width, height = viewBox.split()[2:]
nattrs['width'] = width
nattrs['height'] = height
node.attrs = nattrs node.attrs = nattrs
# 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.
@ -214,13 +235,16 @@ 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, quiet=False): def clean_svg_files(in_dir, out_dir, match_pat=None, clean=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
if not os.path.isdir(out_dir):
os.makedirs(out_dir) if clean and path.samefile(in_dir, out_dir):
if not quiet: logging.error('Cannot clean %s (same as in_dir)', out_dir)
print 'created output directory: %s' % out_dir return
out_dir = tool_utils.ensure_dir_exists(out_dir, clean=clean)
cleaner = SvgCleaner() cleaner = SvgCleaner()
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):
@ -230,25 +254,45 @@ def clean_svg_files(in_dir, out_dir, match_pat=None, quiet=False):
result = cleaner.clean_svg(in_fp.read()) result = cleaner.clean_svg(in_fp.read())
out_path = os.path.join(out_dir, file_name) out_path = os.path.join(out_dir, file_name)
with codecs.open(out_path, 'w', 'utf-8') as out_fp: with codecs.open(out_path, 'w', 'utf-8') as out_fp:
if not quiet: logging.debug('write: %s', out_path)
print 'wrote: %s' % out_path
out_fp.write(result) out_fp.write(result)
count += 1 count += 1
if not count: if not count:
print 'failed to match any files' logging.warning('Failed to match any files')
else: else:
print 'processed %s files to %s' % (count, out_dir) logging.info('Wrote %s files to %s', count, out_dir)
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Generate 'cleaned' svg files.") description="Generate 'cleaned' svg files.")
parser.add_argument('in_dir', help='Input directory.') parser.add_argument(
parser.add_argument('out_dir', help='Output directory.') 'in_dir', help='Input directory.', metavar='dir')
parser.add_argument('regex', help='Regex to select files, default matches all files.', default=None) parser.add_argument(
parser.add_argument('--quiet', '-q', help='Quiet operation.', action='store_true') '-o', '--out_dir', help='Output directory, defaults to sibling of in_dir',
metavar='dir')
parser.add_argument(
'-c', '--clean', help='Clean output directory', action='store_true')
parser.add_argument(
'-r', '--regex', help='Regex to select files, default matches all files.',
metavar='regex', default=None)
parser.add_argument(
'-q', '--quiet', dest='v', help='Quiet operation.', default=1,
action='store_const', const=0)
parser.add_argument(
'-l', '--loglevel', help='log level name/value', default='warning')
args = parser.parse_args() args = parser.parse_args()
clean_svg_files(args.in_dir, args.out_dir, match_pat=args.regex, quiet=args.quiet)
tool_utils.setup_logging(args.loglevel)
if not args.out_dir:
if args.in_dir.endswith('/'):
args.in_dir = args.in_dir[:-1]
args.out_dir = args.in_dir + '_clean'
logging.info('Writing output to %s', args.out_dir)
clean_svg_files(
args.in_dir, args.out_dir, match_pat=args.regex, clean=args.clean)
if __name__ == '__main__': if __name__ == '__main__':