merge.py revision 45d2f38aa51169f400062f2153a74277b9d4d04e
1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5"""Font merger.
6"""
7
8import sys
9
10import fontTools
11from fontTools import misc, ttLib, cffLib
12
13def _add_method(*clazzes):
14  """Returns a decorator function that adds a new method to one or
15  more classes."""
16  def wrapper(method):
17    for clazz in clazzes:
18      assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
19      assert not hasattr(clazz, method.func_name), \
20          "Oops, class '%s' has method '%s'." % (clazz.__name__,
21                                                 method.func_name)
22      setattr(clazz, method.func_name, method)
23    return None
24  return wrapper
25
26
27@_add_method(fontTools.ttLib.getTableClass('maxp'))
28def merge(self, tables, fonts):
29	# TODO When we correctly merge hinting data, update these values:
30	# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
31	# TODO Assumes that all tables have format 1.0; safe assumption.
32	for key in set(sum((vars(table).keys() for table in tables), [])):
33			setattr(self, key, max(getattr(table, key) for table in tables))
34	return self
35
36class Merger:
37
38	def __init__(self, fontfiles):
39		self.fontfiles = fontfiles
40
41	def merge(self):
42
43		mega = ttLib.TTFont()
44
45		#
46		# Settle on a mega glyph order.
47		#
48		fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
49		glyphOrders = [font.getGlyphOrder() for font in fonts]
50		megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
51		# Reload fonts and set new glyph names on them.
52		# TODO Is it necessary to reload font?  I think it is.  At least
53		# it's safer, in case tables were loaded to provide glyph names.
54		fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
55		map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders)
56		mega.setGlyphOrder(megaGlyphOrder)
57
58		cmaps = [self._get_cmap(font) for font in fonts]
59
60		allTags = set(sum([font.keys() for font in fonts], []))
61		allTags.remove('GlyphOrder')
62		for tag in allTags:
63			clazz = ttLib.getTableClass(tag)
64
65			if not hasattr(clazz, 'merge'):
66				print "Don't know how to merge '%s', dropped." % tag
67				continue
68
69			# TODO For now assume all fonts have the same tables.
70			tables = [font[tag] for font in fonts]
71			table = clazz(tag)
72			table.merge (tables, fonts)
73			mega[tag] = table
74			print "Merged '%s'." % tag
75
76		return mega
77
78	def _get_cmap(self, font):
79		cmap = font['cmap']
80		tables = [t for t in cmap.tables
81			    if t.platformID == 3 and t.platEncID in [1, 10]]
82		# XXX Handle format=14
83		assert len(tables)
84		# Pick table that has largest coverage
85		table = max(tables, key=lambda t: len(t.cmap))
86		return table
87
88	def _mergeGlyphOrders(self, glyphOrders):
89		"""Modifies passed-in glyphOrders to reflect new glyph names."""
90		# Simply append font index to the glyph name for now.
91		mega = []
92		for n,glyphOrder in enumerate(glyphOrders):
93			for i,glyphName in enumerate(glyphOrder):
94				glyphName += "#" + `n`
95				glyphOrder[i] = glyphName
96				mega.append(glyphName)
97		return mega
98
99def main(args):
100	if len(args) < 1:
101		print >>sys.stderr, "usage: pyftmerge font..."
102		sys.exit(1)
103	merger = Merger(args)
104	font = merger.merge()
105	outfile = 'merged.ttf'
106	font.save(outfile)
107
108if __name__ == "__main__":
109	main(sys.argv[1:])
110