[merge] Implement a few more straightforward tables
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
index 2cb0ae0..ff860f1 100644
--- a/Lib/fontTools/merge.py
+++ b/Lib/fontTools/merge.py
@@ -6,6 +6,7 @@
"""
import sys
+import time
import fontTools
from fontTools import misc, ttLib, cffLib
@@ -33,6 +34,25 @@
setattr(self, key, max(getattr(table, key) for table in tables))
return True
+@_add_method(fontTools.ttLib.getTableClass('head'))
+def merge(self, tables, fonts):
+ # TODO Check that unitsPerEm are the same.
+ # TODO Use bitwise ops for flags, macStyle, fontDirectionHint
+ minMembers = ['xMin', 'yMin']
+ # Negate some members
+ for key in minMembers:
+ for table in tables:
+ setattr(table, key, -getattr(table, key))
+ # Get max over members
+ for key in set(sum((vars(table).keys() for table in tables), [])):
+ setattr(self, key, max(getattr(table, key) for table in tables))
+ # Negate them back
+ for key in minMembers:
+ for table in tables:
+ setattr(table, key, -getattr(table, key))
+ setattr(self, key, -getattr(self, key))
+ return True
+
@_add_method(fontTools.ttLib.getTableClass('hhea'))
def merge(self, tables, fonts):
# TODO Check that ascent, descent, slope, etc are the same.
@@ -51,6 +71,33 @@
setattr(self, key, -getattr(self, key))
return True
+@_add_method(fontTools.ttLib.getTableClass('post'))
+def merge(self, tables, fonts):
+ # TODO Check that italicAngle, underlinePosition, underlineThickness are the same.
+ minMembers = ['underlinePosition', 'minMemType42', 'minMemType1']
+ # Negate some members
+ for key in minMembers:
+ for table in tables:
+ setattr(table, key, -getattr(table, key))
+ # Get max over members
+ keys = set(sum((vars(table).keys() for table in tables), []))
+ if 'mapping' in keys:
+ keys.remove('mapping')
+ keys.remove('extraNames')
+ for key in keys:
+ setattr(self, key, max(getattr(table, key) for table in tables))
+ # Negate them back
+ for key in minMembers:
+ for table in tables:
+ setattr(table, key, -getattr(table, key))
+ setattr(self, key, -getattr(self, key))
+ self.mapping = {}
+ for table in tables:
+ if hasattr(table, 'mapping'):
+ self.mapping.update(table.mapping)
+ self.extraNames = []
+ return True
+
@_add_method(fontTools.ttLib.getTableClass('vmtx'),
fontTools.ttLib.getTableClass('hmtx'))
def merge(self, tables, fonts):
@@ -61,27 +108,128 @@
@_add_method(fontTools.ttLib.getTableClass('loca'))
def merge(self, tables, fonts):
+ return True # Will be computed automatically
+
+@_add_method(fontTools.ttLib.getTableClass('glyf'))
+def merge(self, tables, fonts):
+ self.glyphs = {}
+ for table in tables:
+ self.glyphs.update(table.glyphs)
+ # TODO Drop hints?
+ return True
+
+@_add_method(fontTools.ttLib.getTableClass('prep'),
+ fontTools.ttLib.getTableClass('fpgm'),
+ fontTools.ttLib.getTableClass('cvt '))
+def merge(self, tables, fonts):
return False # Will be computed automatically
+
+class Options(object):
+
+ class UnknownOptionError(Exception):
+ pass
+
+ _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
+ drop_tables = _drop_tables_default
+
+ def __init__(self, **kwargs):
+
+ self.set(**kwargs)
+
+ def set(self, **kwargs):
+ for k,v in kwargs.iteritems():
+ if not hasattr(self, k):
+ raise self.UnknownOptionError("Unknown option '%s'" % k)
+ setattr(self, k, v)
+
+ def parse_opts(self, argv, ignore_unknown=False):
+ ret = []
+ opts = {}
+ for a in argv:
+ orig_a = a
+ if not a.startswith('--'):
+ ret.append(a)
+ continue
+ a = a[2:]
+ i = a.find('=')
+ op = '='
+ if i == -1:
+ if a.startswith("no-"):
+ k = a[3:]
+ v = False
+ else:
+ k = a
+ v = True
+ else:
+ k = a[:i]
+ if k[-1] in "-+":
+ op = k[-1]+'=' # Ops is '-=' or '+=' now.
+ k = k[:-1]
+ v = a[i+1:]
+ k = k.replace('-', '_')
+ if not hasattr(self, k):
+ if ignore_unknown == True or k in ignore_unknown:
+ ret.append(orig_a)
+ continue
+ else:
+ raise self.UnknownOptionError("Unknown option '%s'" % a)
+
+ ov = getattr(self, k)
+ if isinstance(ov, bool):
+ v = bool(v)
+ elif isinstance(ov, int):
+ v = int(v)
+ elif isinstance(ov, list):
+ vv = v.split(',')
+ if vv == ['']:
+ vv = []
+ vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
+ if op == '=':
+ v = vv
+ elif op == '+=':
+ v = ov
+ v.extend(vv)
+ elif op == '-=':
+ v = ov
+ for x in vv:
+ if x in v:
+ v.remove(x)
+ else:
+ assert 0
+
+ opts[k] = v
+ self.set(**opts)
+
+ return ret
+
+
class Merger:
- def __init__(self, fontfiles):
- self.fontfiles = fontfiles
+ def __init__(self, options=None, log=None):
- def merge(self):
+ if not log:
+ log = Logger()
+ if not options:
+ options = Options()
+
+ self.options = options
+ self.log = log
+
+ def merge(self, fontfiles):
mega = ttLib.TTFont()
#
# Settle on a mega glyph order.
#
- fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
+ fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
glyphOrders = [font.getGlyphOrder() for font in fonts]
megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
# Reload fonts and set new glyph names on them.
# TODO Is it necessary to reload font? I think it is. At least
# it's safer, in case tables were loaded to provide glyph names.
- fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
+ fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders)
mega.setGlyphOrder(megaGlyphOrder)
@@ -90,6 +238,11 @@
allTags = set(sum([font.keys() for font in fonts], []))
allTags.remove('GlyphOrder')
for tag in allTags:
+
+ if tag in self.options.drop_tables:
+ print "Dropping '%s'." % tag
+ continue
+
clazz = ttLib.getTableClass(tag)
if not hasattr(clazz, 'merge'):
@@ -128,12 +281,71 @@
mega.append(glyphName)
return mega
+
+class Logger(object):
+
+ def __init__(self, verbose=False, xml=False, timing=False):
+ self.verbose = verbose
+ self.xml = xml
+ self.timing = timing
+ self.last_time = self.start_time = time.time()
+
+ def parse_opts(self, argv):
+ argv = argv[:]
+ for v in ['verbose', 'xml', 'timing']:
+ if "--"+v in argv:
+ setattr(self, v, True)
+ argv.remove("--"+v)
+ return argv
+
+ def __call__(self, *things):
+ if not self.verbose:
+ return
+ print ' '.join(str(x) for x in things)
+
+ def lapse(self, *things):
+ if not self.timing:
+ return
+ new_time = time.time()
+ print "Took %0.3fs to %s" %(new_time - self.last_time,
+ ' '.join(str(x) for x in things))
+ self.last_time = new_time
+
+ def font(self, font, file=sys.stdout):
+ if not self.xml:
+ return
+ from fontTools.misc import xmlWriter
+ writer = xmlWriter.XMLWriter(file)
+ font.disassembleInstructions = False # Work around ttLib bug
+ for tag in font.keys():
+ writer.begintag(tag)
+ writer.newline()
+ font[tag].toXML(writer, font)
+ writer.endtag(tag)
+ writer.newline()
+
+
+__all__ = [
+ 'Options',
+ 'Merger',
+ 'Logger',
+ 'main'
+]
+
def main(args):
+
+ log = Logger()
+ args = log.parse_opts(args)
+
+ options = Options()
+ args = options.parse_opts(args)
+
if len(args) < 1:
print >>sys.stderr, "usage: pyftmerge font..."
sys.exit(1)
- merger = Merger(args)
- font = merger.merge()
+
+ merger = Merger(options=options, log=log)
+ font = merger.merge(args)
outfile = 'merged.ttf'
font.save(outfile)