[merge] Move to per-class mergeMap
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
index fe5a7f5..8ec145a 100644
--- a/Lib/fontTools/merge.py
+++ b/Lib/fontTools/merge.py
@@ -9,22 +9,24 @@
 from fontTools.misc.py23 import *
 from fontTools import ttLib, cffLib
 from fontTools.ttLib.tables import otTables, _h_e_a_d
+from fontTools.ttLib.tables.DefaultTable import DefaultTable
 from functools import reduce
 import sys
 import time
 import operator
 
 
-def _add_method(*clazzes):
+def _add_method(*clazzes, **kwargs):
 	"""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.__name__), \
+			if not kwargs.get('allowDefaultTable', False):
+				assert clazz != DefaultTable, 'Oops, table class not found.'
+			assert method.__name__ not in clazz.__dict__, \
 				"Oops, class '%s' has method '%s'." % (clazz.__name__,
 								       method.__name__)
-		setattr(clazz, method.__name__, method)
+			setattr(clazz, method.__name__, method)
 		return None
 	return wrapper
 
@@ -51,111 +53,129 @@
 def ignore(lst):
 	assert False, "This function should not be called."
 
-@_add_method(ttLib.getTableClass('maxp'))
+def maybenone(func):
+	"""Returns a filter func that when called with a list,
+	only calls func on the non-None items of the list, and
+	only so if there's at least one non-None item in the
+	list."""
+
+	def wrapper(lst):
+		items = [item for item in lst if item is not None]
+		return func(items) if items else None
+
+	return wrapper
+
+def sumLists(lst):
+	l = []
+	for item in lst:
+		l.extend(item)
+	return l
+
+def sumDicts(lst):
+	d = {}
+	for item in lst:
+		d.update(item)
+	return d
+
+
+@_add_method(DefaultTable, allowDefaultTable=True)
 def merge(self, m):
-	logic = {
-		'*': max,
-		'tableTag': equal,
-		'tableVersion': equal,
-		'numGlyphs': sum,
-		'maxStorage': max, # FIXME: may need to be changed to sum
-		'maxFunctionDefs': sum,
-		'maxInstructionDefs': sum,
-	}
+	if not hasattr(self, 'mergeMap'):
+		m.log("Don't know how to merge '%s'." % self.tableTag)
+		return False
+
+	m._mergeKeys(self, self.mergeMap)
+	return True
+
+ttLib.getTableClass('maxp').mergeMap = {
+	'*': max,
+	'tableTag': equal,
+	'tableVersion': equal,
+	'numGlyphs': sum,
+	'maxStorage': max, # FIXME: may need to be changed to sum
+	'maxFunctionDefs': sum,
+	'maxInstructionDefs': sum,
 	# TODO When we correctly merge hinting data, update these values:
 	# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
-	m._mergeKeys(self, logic)
-	return True
+}
 
-@_add_method(ttLib.getTableClass('head'))
-def merge(self, m):
-	logic = {
-		'tableTag': equal,
-		'tableVersion': max,
-		'fontRevision': max,
-		'checkSumAdjustment': recalculate,
-		'magicNumber': equal,
-		'flags': first, # FIXME: replace with bit-sensitive code
-		'unitsPerEm': equal,
-		'created': current_time,
-		'modified': current_time,
-		'xMin': min,
-		'yMin': min,
-		'xMax': max,
-		'yMax': max,
-		'macStyle': first,
-		'lowestRecPPEM': max,
-		'fontDirectionHint': lambda lst: 2,
-		'indexToLocFormat': recalculate,
-		'glyphDataFormat': equal,
-	}
-	m._mergeKeys(self, logic)
-	return True
+ttLib.getTableClass('head').mergeMap = {
+	'tableTag': equal,
+	'tableVersion': max,
+	'fontRevision': max,
+	'checkSumAdjustment': recalculate,
+	'magicNumber': equal,
+	'flags': first, # FIXME: replace with bit-sensitive code
+	'unitsPerEm': equal,
+	'created': current_time,
+	'modified': current_time,
+	'xMin': min,
+	'yMin': min,
+	'xMax': max,
+	'yMax': max,
+	'macStyle': first,
+	'lowestRecPPEM': max,
+	'fontDirectionHint': lambda lst: 2,
+	'indexToLocFormat': recalculate,
+	'glyphDataFormat': equal,
+}
 
-@_add_method(ttLib.getTableClass('hhea'))
-def merge(self, m):
-	logic = {
-		'*': equal,
-		'tableTag': equal,
-		'tableVersion': max,
-		'ascent': max,
-		'descent': min,
-		'lineGap': max,
-		'advanceWidthMax': max,
-		'minLeftSideBearing': min,
-		'minRightSideBearing': min,
-		'xMaxExtent': max,
-		'caretSlopeRise': first, # FIXME
-		'caretSlopeRun': first, # FIXME
-		'caretOffset': first, # FIXME
-		'numberOfHMetrics': recalculate,
-	}
-	m._mergeKeys(self, logic)
-	return True
+ttLib.getTableClass('hhea').mergeMap = {
+	'*': equal,
+	'tableTag': equal,
+	'tableVersion': max,
+	'ascent': max,
+	'descent': min,
+	'lineGap': max,
+	'advanceWidthMax': max,
+	'minLeftSideBearing': min,
+	'minRightSideBearing': min,
+	'xMaxExtent': max,
+	'caretSlopeRise': first, # FIXME
+	'caretSlopeRun': first, # FIXME
+	'caretOffset': first, # FIXME
+	'numberOfHMetrics': recalculate,
+}
 
-@_add_method(ttLib.getTableClass('OS/2'))
-def merge(self, m):
+ttLib.getTableClass('OS/2').mergeMap = {
+	'*': first,
+	'tableTag': equal,
+	'version': max,
+	'xAvgCharWidth': recalculate,
+	'fsType': first, # FIXME
+	'panose': first, # FIXME?
+	'ulUnicodeRange1': bitwise_or,
+	'ulUnicodeRange2': bitwise_or,
+	'ulUnicodeRange3': bitwise_or,
+	'ulUnicodeRange4': bitwise_or,
+	'fsFirstCharIndex': min,
+	'fsLastCharIndex': max,
+	'sTypoAscender': max,
+	'sTypoDescender': min,
+	'sTypoLineGap': max,
+	'usWinAscent': max,
+	'usWinDescent': max,
+	'ulCodePageRange1': bitwise_or,
+	'ulCodePageRange2': bitwise_or,
+	'usMaxContex': max,
 	# TODO version 5
-	logic = {
-		'*': first,
-		'tableTag': equal,
-		'version': max,
-		'xAvgCharWidth': recalculate,
-		'fsType': first, # FIXME
-		'panose': first, # FIXME?
-		'ulUnicodeRange1': bitwise_or,
-		'ulUnicodeRange2': bitwise_or,
-		'ulUnicodeRange3': bitwise_or,
-		'ulUnicodeRange4': bitwise_or,
-		'fsFirstCharIndex': min,
-		'fsLastCharIndex': max,
-		'sTypoAscender': max,
-		'sTypoDescender': min,
-		'sTypoLineGap': max,
-		'usWinAscent': max,
-		'usWinDescent': max,
-		'ulCodePageRange1': bitwise_or,
-		'ulCodePageRange2': bitwise_or,
-		'usMaxContex': max,
-	}
-	m._mergeKeys(self, logic)
-	return True
+}
 
+ttLib.getTableClass('post').mergeMap = {
+	'*': first,
+	'tableTag': equal,
+	'formatType': max,
+	'isFixedPitch': min,
+	'minMemType42': max,
+	'maxMemType42': lambda lst: 0,
+	'minMemType1': max,
+	'maxMemType1': lambda lst: 0,
+	'mapping': ignore,
+	'extraNames': ignore,
+}
 @_add_method(ttLib.getTableClass('post'))
 def merge(self, m):
-	logic = {
-		'*': first,
-		'tableTag': equal,
-		'formatType': max,
-		'isFixedPitch': min,
-		'minMemType42': max,
-		'maxMemType42': lambda lst: 0,
-		'minMemType1': max,
-		'maxMemType1': lambda lst: 0,
-		'mapping': ignore,
-		'extraNames': ignore
-	}
-	m._mergeKeys(self, logic)
+	DefaultTable.merge(self, m)
 	self.mapping = {}
 	for table in m.tables:
 		if hasattr(table, 'mapping'):
@@ -163,21 +183,24 @@
 	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
+ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = {
+	'tableTag': equal,
+	'metrics': sumDicts,
+}
 
-@_add_method(ttLib.getTableClass('loca'))
-def merge(self, m):
-	return True # Will be computed automatically
+ttLib.getTableClass('loca').mergeMap = {
+	'*': ignore,
+	'tableTag': equal,
+}
+
+ttLib.getTableClass('glyf').mergeMap = {
+	'tableTag': equal,
+	'glyphs': sumDicts,
+	'glyphOrder': sumLists,
+}
 
 @_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
@@ -187,7 +210,7 @@
 			# composite glyph names.
 			if g.isComposite():
 				g.expand(table)
-		self.glyphs.update(table.glyphs)
+	DefaultTable.merge(self, m)
 	return True
 
 @_add_method(ttLib.getTableClass('prep'),
@@ -360,7 +383,7 @@
     return ret
 
 
-class Merger:
+class Merger(object):
 
 	def __init__(self, options=None, log=None):
 
@@ -400,10 +423,6 @@
 
 			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)
@@ -411,7 +430,7 @@
 				mega[tag] = table
 				self.log("Merged '%s'." % tag)
 			else:
-				self.log("Dropped '%s'.  No need to merge explicitly." % tag)
+				self.log("Dropped '%s'." % tag)
 			self.log.lapse("merge '%s'" % tag)
 			del self.tables
 
@@ -437,7 +456,10 @@
 			try:
 				merge_logic = logic[key]
 			except KeyError:
-				merge_logic = logic['*']
+				try:
+					merge_logic = logic['*']
+				except KeyError:
+					raise Exception("Don't know how to merge key %s" % key)
 			if merge_logic == ignore:
 				continue
 			key_value = merge_logic(getattr(table, key) for table in self.tables)