Skip to content

Commit

Permalink
Update svg emoji tools.
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
dougfelt committed Apr 13, 2016
1 parent e7a7241 commit 8397827
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 147 deletions.
161 changes: 84 additions & 77 deletions add_svg_glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,25 @@

import argparse
import glob
import logging
import os
import re
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 _g_l_y_f
from fontTools.ttLib.tables import S_V_G_ as SVG
from fontTools import ttx

from nototools import tool_utils

import add_emoji_gsub
import svg_builder
import svg_cleaner


class FontBuilder(object):
"""A utility for mutating a ttx font. This maintains glyph_order, cmap, and hmtx tables,
and optionally GSUB, glyf, and SVN tables as well."""
"""A utility for mutating a ttx font. This maintains glyph_order, cmap, and
hmtx tables, and optionally GSUB, glyf, and SVN tables as well."""

def __init__(self, font):
self.font = font;
Expand All @@ -49,8 +46,8 @@ def __init__(self, font):
self.hmtx = font['hmtx'].metrics

def init_gsub(self):
"""Call this if you are going to add ligatures to the font. Creates a GSUB table
if there isn't one already."""
"""Call this if you are going to add ligatures to the font. Creates a GSUB
table if there isn't one already."""

if hasattr(self, 'ligatures'):
return
Expand All @@ -73,8 +70,8 @@ def init_gsub(self):
self.ligatures = lookup.SubTable[0].ligatures

def init_glyf(self):
"""Call this if you need to create empty glyf entries in the font when you add a new
glyph."""
"""Call this if you need to create empty glyf entries in the font when you
add a new glyph."""

if hasattr(self, 'glyphs'):
return
Expand All @@ -87,8 +84,9 @@ def init_glyf(self):
self.glyphs = font['glyf'].glyphs

def init_svg(self):
"""Call this if you expect to add SVG images in the font. This calls init_glyf since SVG
support currently requires fallback glyf records for each SVG image."""
"""Call this if you expect to add SVG images in the font. This calls
init_glyf since SVG support currently requires fallback glyf records for
each SVG image."""

if hasattr(self, 'svgs'):
return
Expand Down Expand Up @@ -131,7 +129,8 @@ def _add_ligature(self, glyphstr):
self.ligatures[first] = [lig]

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:
self.cmap[ord(glyphstr)] = name
self.hmtx[name] = [0, 0]
Expand All @@ -140,11 +139,12 @@ def _add_empty_glyph(self, glyphstr, name):
self.glyphs[name] = _g_l_y_f.Glyph()

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
ligature (longer than one codepoint), and if it is, generate empty glyphs with cmap
entries for any missing ligature components and add a ligature record. Then generate
an empty glyph for the name. Return a tuple with the name, index, and a bool
indicating whether the glyph already existed."""
"""Convert glyphstr to a name and check if it already exists. If not, check
if it is a ligature (longer than one codepoint), and if it is, generate
empty glyphs with cmap entries for any missing ligature components and add a
ligature record. Then generate an empty glyph for the name. Return a tuple
with the name, index, and a bool indicating whether the glyph already
existed."""

name = self.glyph_name(glyphstr)
index = self.glyph_name_to_index(name)
Expand All @@ -161,8 +161,8 @@ def add_components_and_ligature(self, glyphstr):
return name, index, exists

def add_svg(self, doc, hmetrics, name, index):
"""Add an svg table entry. If hmetrics is not None, update the hmtx table. This
expects the glyph has already been added."""
"""Add an svg table entry. If hmetrics is not None, update the hmtx table.
This expects the glyph has already been added."""
# sanity check to make sure name and index correspond.
assert name == self.glyph_index_to_name(index)
if hmetrics:
Expand All @@ -171,27 +171,27 @@ def add_svg(self, doc, hmetrics, name, index):
self.svgs.append(svg_record)


def collect_glyphstr_file_pairs(prefix, ext, include=None, exclude=None, verbosity=1):
"""Scan files with the given prefix and extension, and return a list of (glyphstr,
filename) where glyphstr is the character or ligature, and filename is the image file
associated with it. The glyphstr is formed by decoding the filename (exclusive of the
prefix) as a sequence of hex codepoints separated by underscore. Include, if defined, is
a regex string to include only matched filenames. Exclude, if defined, is a regex string
to exclude matched filenames, and is applied after include."""
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, filename) where glyphstr is the character or ligature, and filename
is the image file associated with it. The glyphstr is formed by decoding the
filename (exclusive of the prefix) as a sequence of hex codepoints separated
by underscore. Include, if defined, is a regex string to include only matched
filenames. Exclude, if defined, is a regex string to exclude matched
filenames, and is applied after include."""

image_files = {}
glob_pat = "%s*.%s" % (prefix, ext)
leading = len(prefix)
trailing = len(ext) + 1 # include dot
if verbosity:
print "Looking for images matching '%s'." % glob_pat
logging.info("Looking for images matching '%s'.", glob_pat)
ex_count = 0
ex = re.compile(exclude) if exclude else None
inc = re.compile(include) if include else None
if verbosity and inc:
print "Including images matching '%s'." % include
if verbosity and ex:
print "Excluding images matching '%s'." % exclude
if inc:
logging.info("Including images matching '%s'.", include)
if ex:
logging.info("Excluding images matching '%s'.", exclude)

for image_file in glob.glob(glob_pat):
if inc and not inc.search(image_file):
Expand All @@ -211,76 +211,83 @@ def collect_glyphstr_file_pairs(prefix, ext, include=None, exclude=None, verbosi
u = unichr(int(codes, 16))
image_files[u] = image_file

if verbosity and ex_count:
print "Excluded %d files." % ex_count
if ex_count:
logging.info("Excluded %d files.", ex_count)
if not image_files:
raise Exception ("No image files matching '%s'." % glob_pat)
if verbosity:
print "Included %s files." % len(image_files)
raise Exception ("No image files matching '%s'.", glob_pat)
logging.info("Matched %s files.", len(image_files))
return image_files.items()


def sort_glyphstr_tuples(glyphstr_tuples):
"""The list contains tuples whose first element is a string representing a character or
ligature. It is sorted with shorter glyphstrs first, then alphabetically. This ensures
that ligature components are added to the font before any ligatures that contain them."""
"""The list contains tuples whose first element is a string representing a
character or ligature. It is sorted with shorter glyphstrs first, then
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]))


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
to .ttx file out_file."""

quiet = verbosity < 2
font = ttx.TTFont(quiet=quiet)
font.importXML(in_file, quiet=quiet)
font = ttx.TTFont()
font.importXML(in_file)

sort_glyphstr_tuples(pairs)

font_builder = FontBuilder(font)
# we've already sorted by length, so the longest glyphstrs are at the end. To see if
# we have ligatures, we just need to check the last one.
# we've already sorted by length, so the longest glyphstrs are at the end. To
# see if we have ligatures, we just need to check the last one.
if len(pairs[-1][0]) > 1:
font_builder.init_gsub()

img_builder = svg_builder.SvgBuilder(font_builder)
for glyphstr, filename in pairs:
if verbosity > 1:
print "Adding glyph for U+%s" % ",".join(["%04X" % ord(char) for char in glyphstr])
logging.debug("Adding glyph for U+%s", ",".join(
["%04X" % ord(char) for char in glyphstr]))
img_builder.add_from_filename(glyphstr, filename)

font.saveXML(out_file, quiet=quiet)
if verbosity:
print "added %s images to %s" % (len(pairs), out_file)
font.saveXML(out_file)
logging.info("Added %s images to %s", len(pairs), out_file)


def main(argv):
usage = """This will search for files that have image_prefix followed by one or more
hex numbers (separated by underscore if more than one), and end in ".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
then adds cmap, htmx, and potentially GSUB entries for the Unicode
characters found. The advance width will be chosen based on image aspect
ratio. If Unicode values outside the BMP are desired, the existing cmap
table should be of the appropriate (format 12) type. Only the first cmap
table and the first GSUB lookup (if existing) are modified."""
usage = """This will search for files that have image_prefix followed by one
or more hex numbers (separated by underscore if more than one), and end in
".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
then adds cmap, htmx, and potentially GSUB entries for the Unicode characters
found. The advance width will be chosen based on image aspect ratio. If
Unicode values outside the BMP are desired, the existing cmap table should be
of the appropriate (format 12) type. Only the first cmap table and the first
GSUB lookup (if existing) are modified."""

parser = argparse.ArgumentParser(
description="Update cmap, glyf, GSUB, and hmtx tables from image glyphs.", epilog=usage)
parser.add_argument('in_file', help="Input ttx file name.")
parser.add_argument('out_file', help="Output ttx file name.")
parser.add_argument('image_prefix', help="Location and prefix of image files.")
parser.add_argument('-i', '--include', help='include files whoses name matches this regex')
parser.add_argument('-e', '--exclude', help='exclude files whose name matches this regex')
parser.add_argument('--quiet', '-q', dest='v', help="quiet operation.", default=1,
action='store_const', const=0)
parser.add_argument('--verbose', '-v', dest='v', help="verbose operation.",
action='store_const', const=2)
description='Update cmap, glyf, GSUB, and hmtx tables from image glyphs.',
epilog=usage)
parser.add_argument(
'in_file', help='Input ttx file name.', metavar='fname')
parser.add_argument(
'out_file', help='Output ttx file name.', metavar='fname')
parser.add_argument(
'image_prefix', help='Location and prefix of image files.',
metavar='path')
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)

pairs = collect_glyphstr_file_pairs(args.image_prefix, 'svg', include=args.include,
exclude=args.exclude, verbosity=args.v)
add_image_glyphs(args.in_file, args.out_file, pairs, verbosity=args.v)
tool_utils.setup_logging(args.loglevel)

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__':
Expand Down
Loading

0 comments on commit 8397827

Please sign in to comment.