merge.py revision 45d2f38aa51169f400062f2153a74277b9d4d04e
145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod# Copyright 2013 Google, Inc. All Rights Reserved.
245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod#
345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod# Google Author(s): Behdad Esfahbod
445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod"""Font merger.
645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod"""
745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodimport sys
945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
1045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodimport fontTools
1145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodfrom fontTools import misc, ttLib, cffLib
1245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
1345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef _add_method(*clazzes):
1445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod  """Returns a decorator function that adds a new method to one or
1545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod  more classes."""
1645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod  def wrapper(method):
1745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod    for clazz in clazzes:
1845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod      assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
1945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod      assert not hasattr(clazz, method.func_name), \
2045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod          "Oops, class '%s' has method '%s'." % (clazz.__name__,
2145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod                                                 method.func_name)
2245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod      setattr(clazz, method.func_name, method)
2345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod    return None
2445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod  return wrapper
2545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
2645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
2745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('maxp'))
2845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef merge(self, tables, fonts):
2945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	# TODO When we correctly merge hinting data, update these values:
3045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
3145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	# TODO Assumes that all tables have format 1.0; safe assumption.
3245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	for key in set(sum((vars(table).keys() for table in tables), [])):
3345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			setattr(self, key, max(getattr(table, key) for table in tables))
3445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	return self
3545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
3645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodclass Merger:
3745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
3845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	def __init__(self, fontfiles):
3945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		self.fontfiles = fontfiles
4045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
4145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	def merge(self):
4245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
4345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega = ttLib.TTFont()
4445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
4545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		#
4645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Settle on a mega glyph order.
4745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		#
4845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
4945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		glyphOrders = [font.getGlyphOrder() for font in fonts]
5045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
5145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Reload fonts and set new glyph names on them.
5245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# TODO Is it necessary to reload font?  I think it is.  At least
5345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# it's safer, in case tables were loaded to provide glyph names.
5445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
5545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders)
5645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega.setGlyphOrder(megaGlyphOrder)
5745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
5845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		cmaps = [self._get_cmap(font) for font in fonts]
5945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
6045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		allTags = set(sum([font.keys() for font in fonts], []))
6145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		allTags.remove('GlyphOrder')
6245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		for tag in allTags:
6345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			clazz = ttLib.getTableClass(tag)
6445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
6545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			if not hasattr(clazz, 'merge'):
6645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				print "Don't know how to merge '%s', dropped." % tag
6745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				continue
6845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
6945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			# TODO For now assume all fonts have the same tables.
7045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			tables = [font[tag] for font in fonts]
7145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			table = clazz(tag)
7245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			table.merge (tables, fonts)
7345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			mega[tag] = table
7445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			print "Merged '%s'." % tag
7545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
7645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		return mega
7745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
7845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	def _get_cmap(self, font):
7945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		cmap = font['cmap']
8045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		tables = [t for t in cmap.tables
8145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			    if t.platformID == 3 and t.platEncID in [1, 10]]
8245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# XXX Handle format=14
8345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		assert len(tables)
8445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Pick table that has largest coverage
8545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		table = max(tables, key=lambda t: len(t.cmap))
8645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		return table
8745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
8845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	def _mergeGlyphOrders(self, glyphOrders):
8945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		"""Modifies passed-in glyphOrders to reflect new glyph names."""
9045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Simply append font index to the glyph name for now.
9145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega = []
9245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		for n,glyphOrder in enumerate(glyphOrders):
9345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			for i,glyphName in enumerate(glyphOrder):
9445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				glyphName += "#" + `n`
9545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				glyphOrder[i] = glyphName
9645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				mega.append(glyphName)
9745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		return mega
9845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
9945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args):
10045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	if len(args) < 1:
10145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		print >>sys.stderr, "usage: pyftmerge font..."
10245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		sys.exit(1)
10345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	merger = Merger(args)
10445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	font = merger.merge()
10545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	outfile = 'merged.ttf'
10645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	font.save(outfile)
10745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
10845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__":
10945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	main(sys.argv[1:])
110