Merge branch 'merge'
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
new file mode 100644
index 0000000..b576b59
--- /dev/null
+++ b/Lib/fontTools/merge.py
@@ -0,0 +1,457 @@
+# Copyright 2013 Google, Inc. All Rights Reserved.
+#
+# Google Author(s): Behdad Esfahbod
+
+"""Font merger.
+"""
+
+import sys
+import time
+
+import fontTools
+from fontTools import misc, ttLib, cffLib
+from fontTools.ttLib.tables import otTables
+
+def _add_method(*clazzes):
+	"""Returns a decorator function that adds a new method to one or
+	more classes."""
+	def wrapper(method):
+		for clazz in clazzes:
+			assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
+			assert not hasattr(clazz, method.func_name), \
+				"Oops, class '%s' has method '%s'." % (clazz.__name__,
+								       method.func_name)
+		setattr(clazz, method.func_name, method)
+		return None
+	return wrapper
+
+
+@_add_method(ttLib.getTableClass('maxp'))
+def merge(self, m):
+	# TODO When we correctly merge hinting data, update these values:
+	# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
+	# TODO Assumes that all tables have format 1.0; safe assumption.
+	allKeys = reduce(set.union, (vars(table).keys() for table in m.tables), set())
+	for key in allKeys:
+		setattr(self, key, max(getattr(table, key) for table in m.tables))
+	return True
+
+@_add_method(ttLib.getTableClass('head'))
+def merge(self, m):
+	# 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 m.tables:
+			setattr(table, key, -getattr(table, key))
+	# Get max over members
+	allKeys = reduce(set.union, (vars(table).keys() for table in m.tables), set())
+	for key in allKeys:
+		setattr(self, key, max(getattr(table, key) for table in m.tables))
+	# Negate them back
+	for key in minMembers:
+		for table in m.tables:
+			setattr(table, key, -getattr(table, key))
+		setattr(self, key, -getattr(self, key))
+	return True
+
+@_add_method(ttLib.getTableClass('hhea'))
+def merge(self, m):
+	# TODO Check that ascent, descent, slope, etc are the same.
+	minMembers = ['descent', 'minLeftSideBearing', 'minRightSideBearing']
+	# Negate some members
+	for key in minMembers:
+		for table in m.tables:
+			setattr(table, key, -getattr(table, key))
+	# Get max over members
+	allKeys = reduce(set.union, (vars(table).keys() for table in m.tables), set())
+	for key in allKeys:
+		setattr(self, key, max(getattr(table, key) for table in m.tables))
+	# Negate them back
+	for key in minMembers:
+		for table in m.tables:
+			setattr(table, key, -getattr(table, key))
+		setattr(self, key, -getattr(self, key))
+	return True
+
+@_add_method(ttLib.getTableClass('OS/2'))
+def merge(self, m):
+	# TODO Check that weight/width/subscript/superscript/etc are the same.
+	# TODO Bitwise ops for UnicodeRange/CodePageRange.
+	# TODO Pretty much all fields generated here have bogus values.
+	# Get max over members
+	allKeys = reduce(set.union, (vars(table).keys() for table in m.tables), set())
+	for key in allKeys:
+		setattr(self, key, max(getattr(table, key) for table in m.tables))
+	return True
+
+@_add_method(ttLib.getTableClass('post'))
+def merge(self, m):
+	# TODO Check that italicAngle, underlinePosition, underlineThickness are the same.
+	minMembers = ['underlinePosition', 'minMemType42', 'minMemType1']
+	# Negate some members
+	for key in minMembers:
+		for table in m.tables:
+			setattr(table, key, -getattr(table, key))
+	# Get max over members
+	allKeys = reduce(set.union, (vars(table).keys() for table in m.tables), set())
+	if 'mapping' in allKeys:
+		allKeys.remove('mapping')
+	allKeys.remove('extraNames')
+	for key in allKeys:
+		setattr(self, key, max(getattr(table, key) for table in m.tables))
+	# Negate them back
+	for key in minMembers:
+		for table in m.tables:
+			setattr(table, key, -getattr(table, key))
+		setattr(self, key, -getattr(self, key))
+	self.mapping = {}
+	for table in m.tables:
+		if hasattr(table, 'mapping'):
+			self.mapping.update(table.mapping)
+	self.extraNames = []
+	return True
+
+@_add_method(ttLib.getTableClass('vmtx'),
+             ttLib.getTableClass('hmtx'))
+def merge(self, m):
+	self.metrics = {}
+	for table in m.tables:
+		self.metrics.update(table.metrics)
+	return True
+
+@_add_method(ttLib.getTableClass('loca'))
+def merge(self, m):
+	return True # Will be computed automatically
+
+@_add_method(ttLib.getTableClass('glyf'))
+def merge(self, m):
+	self.glyphs = {}
+	for table in m.tables:
+		for g in table.glyphs.values():
+			# Drop hints for now, since we don't remap
+			# functions / CVT values.
+			g.removeHinting()
+			# Expand composite glyphs to load their
+			# composite glyph names.
+			if g.isComposite():
+				g.expand(table)
+		self.glyphs.update(table.glyphs)
+	return True
+
+@_add_method(ttLib.getTableClass('prep'),
+	     ttLib.getTableClass('fpgm'),
+	     ttLib.getTableClass('cvt '))
+def merge(self, m):
+	return False # TODO We don't merge hinting data currently.
+
+@_add_method(ttLib.getTableClass('cmap'))
+def merge(self, m):
+	# TODO Handle format=14.
+	cmapTables = [t for table in m.tables for t in table.tables
+		      if t.platformID == 3 and t.platEncID in [1, 10]]
+	# TODO Better handle format-4 and format-12 coexisting in same font.
+	# TODO Insert both a format-4 and format-12 if needed.
+	module = ttLib.getTableModule('cmap')
+	assert all(t.format in [4, 12] for t in cmapTables)
+	format = max(t.format for t in cmapTables)
+	cmapTable = module.cmap_classes[format](format)
+	cmapTable.cmap = {}
+	cmapTable.platformID = 3
+	cmapTable.platEncID = max(t.platEncID for t in cmapTables)
+	cmapTable.language = 0
+	for table in cmapTables:
+		# TODO handle duplicates.
+		cmapTable.cmap.update(table.cmap)
+	self.tableVersion = 0
+	self.tables = [cmapTable]
+	self.numSubTables = len(self.tables)
+	return True
+
+@_add_method(ttLib.getTableClass('GDEF'))
+def merge(self, m):
+	self.table = otTables.GDEF()
+	self.table.Version = 1.0 # TODO version 1.2...
+
+	if any(t.table.LigCaretList for t in m.tables):
+		glyphs = []
+		ligGlyphs = []
+		for table in m.tables:
+			if table.table.LigCaretList:
+				glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
+				ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
+		coverage = otTables.Coverage()
+		coverage.glyphs = glyphs
+		ligCaretList = otTables.LigCaretList()
+		ligCaretList.Coverage = coverage
+		ligCaretList.LigGlyph = ligGlyphs
+		ligCaretList.GlyphCount = len(ligGlyphs)
+		self.table.LigCaretList = ligCaretList
+	else:
+		self.table.LigCaretList = None
+
+	if any(t.table.MarkAttachClassDef for t in m.tables):
+		classDefs = {}
+		for table in m.tables:
+			if table.table.MarkAttachClassDef:
+				classDefs.update(table.table.MarkAttachClassDef.classDefs)
+		self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
+		self.table.MarkAttachClassDef.classDefs = classDefs
+	else:
+		self.table.MarkAttachClassDef = None
+
+	if any(t.table.GlyphClassDef for t in m.tables):
+		classDefs = {}
+		for table in m.tables:
+			if table.table.GlyphClassDef:
+				classDefs.update(table.table.GlyphClassDef.classDefs)
+		self.table.GlyphClassDef = otTables.GlyphClassDef()
+		self.table.GlyphClassDef.classDefs = classDefs
+	else:
+		self.table.GlyphClassDef = None
+
+	if any(t.table.AttachList for t in m.tables):
+		glyphs = []
+		attachPoints = []
+		for table in m.tables:
+			if table.table.AttachList:
+				glyphs.extend(table.table.AttachList.Coverage.glyphs)
+				attachPoints.extend(table.table.AttachList.AttachPoint)
+		coverage = otTables.Coverage()
+		coverage.glyphs = glyphs
+		attachList = otTables.AttachList()
+		attachList.Coverage = coverage
+		attachList.AttachPoint = attachPoints
+		attachList.GlyphCount = len(attachPoints)
+		self.table.AttachList = attachList
+	else:
+		self.table.AttachList = None
+
+	return True
+
+
+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, options=None, log=None):
+
+		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 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 fontfiles]
+		for font,glyphOrder in zip(fonts, glyphOrders):
+			font.setGlyphOrder(glyphOrder)
+		mega.setGlyphOrder(megaGlyphOrder)
+
+		allTags = reduce(set.union, (font.keys() for font in fonts), set())
+		allTags.remove('GlyphOrder')
+		for tag in allTags:
+
+			if tag in self.options.drop_tables:
+				self.log("Dropping '%s'." % tag)
+				continue
+
+			clazz = ttLib.getTableClass(tag)
+
+			if not hasattr(clazz, 'merge'):
+				self.log("Don't know how to merge '%s', dropped." % tag)
+				continue
+
+			# TODO For now assume all fonts have the same tables.
+			self.tables = [font[tag] for font in fonts]
+			table = clazz(tag)
+			if table.merge (self):
+				mega[tag] = table
+				self.log("Merged '%s'." % tag)
+			else:
+				self.log("Dropped '%s'.  No need to merge explicitly." % tag)
+			self.log.lapse("merge '%s'" % tag)
+			del self.tables
+
+		return mega
+
+	def _mergeGlyphOrders(self, glyphOrders):
+		"""Modifies passed-in glyphOrders to reflect new glyph names.
+		Returns glyphOrder for the merged font."""
+		# Simply append font index to the glyph name for now.
+		# TODO Even this simplistic numbering can result in conflicts.
+		# But then again, we have to improve this soon anyway.
+		mega = []
+		for n,glyphOrder in enumerate(glyphOrders):
+			for i,glyphName in enumerate(glyphOrder):
+				glyphName += "#" + `n`
+				glyphOrder[i] = glyphName
+				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(options=options, log=log)
+	font = merger.merge(args)
+	outfile = 'merged.ttf'
+	font.save(outfile)
+	log.lapse("compile and save font")
+
+	log.last_time = log.start_time
+	log.lapse("make one with everything(TOTAL TIME)")
+
+if __name__ == "__main__":
+	main(sys.argv[1:])
diff --git a/Lib/fontTools/subset.py b/Lib/fontTools/subset.py
index a3b9f70..8ce37f2 100644
--- a/Lib/fontTools/subset.py
+++ b/Lib/fontTools/subset.py
@@ -2161,8 +2161,8 @@
               for g in args))
 
   font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
-  subsetter = Subsetter(options=options, log=log)
   log.lapse("load font")
+  subsetter = Subsetter(options=options, log=log)
 
   names = font.getGlyphNames()
   log.lapse("loading glyph names")
diff --git a/Tools/pyftmerge b/Tools/pyftmerge
new file mode 100755
index 0000000..2479258
--- /dev/null
+++ b/Tools/pyftmerge
@@ -0,0 +1,6 @@
+#! /usr/bin/env python
+
+import sys
+from fontTools import merge
+
+merge.main(sys.argv[1:])