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