merge.py revision b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3
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 9f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodimport time 1045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 1145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodimport fontTools 1245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodfrom fontTools import misc, ttLib, cffLib 1345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 1445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef _add_method(*clazzes): 15c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod """Returns a decorator function that adds a new method to one or 16c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod more classes.""" 17c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod def wrapper(method): 18c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod for clazz in clazzes: 19c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.' 20c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod assert not hasattr(clazz, method.func_name), \ 21c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod "Oops, class '%s' has method '%s'." % (clazz.__name__, 22c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod method.func_name) 23c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod setattr(clazz, method.func_name, method) 24c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod return None 25c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod return wrapper 2645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 2745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 2845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('maxp')) 2945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef merge(self, tables, fonts): 3045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO When we correctly merge hinting data, update these values: 3145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions 3245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO Assumes that all tables have format 1.0; safe assumption. 3345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for key in set(sum((vars(table).keys() for table in tables), [])): 3445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 3565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return True 3665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 37f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('head')) 38f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 39f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Check that unitsPerEm are the same. 40f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Use bitwise ops for flags, macStyle, fontDirectionHint 41f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod minMembers = ['xMin', 'yMin'] 42f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate some members 43f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 44f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 45f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 46f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Get max over members 47f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in set(sum((vars(table).keys() for table in tables), [])): 48f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 49f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate them back 50f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 51f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 52f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 53f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, -getattr(self, key)) 54f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True 55f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 5665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('hhea')) 5765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahboddef merge(self, tables, fonts): 5865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod # TODO Check that ascent, descent, slope, etc are the same. 5965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod minMembers = ['descent', 'minLeftSideBearing', 'minRightSideBearing'] 6065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod # Negate some members 6165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for key in minMembers: 6265f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for table in tables: 6365f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod setattr(table, key, -getattr(table, key)) 6465f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod # Get max over members 6565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for key in set(sum((vars(table).keys() for table in tables), [])): 6665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 6765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod # Negate them back 6865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for key in minMembers: 6965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for table in tables: 7065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod setattr(table, key, -getattr(table, key)) 7165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod setattr(self, key, -getattr(self, key)) 7265f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return True 7365f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 7471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('OS/2')) 7571294def6730c37839f03dee519b319f982587eaBehdad Esfahboddef merge(self, tables, fonts): 7671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Check that weight/width/subscript/superscript/etc are the same. 7771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Bitwise ops for UnicodeRange/CodePageRange. 7871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Pretty much all fields generated here have bogus values. 7971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # Get max over members 8071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod for key in set(sum((vars(table).keys() for table in tables), [])): 8171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 8271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod return True 8371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 84f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('post')) 85f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 86f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Check that italicAngle, underlinePosition, underlineThickness are the same. 87f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod minMembers = ['underlinePosition', 'minMemType42', 'minMemType1'] 88f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate some members 89f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 90f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 91f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 92f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Get max over members 93f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys = set(sum((vars(table).keys() for table in tables), [])) 94f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if 'mapping' in keys: 95f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys.remove('mapping') 96f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys.remove('extraNames') 97f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in keys: 98f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 99f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate them back 100f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 101f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 102f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 103f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, -getattr(self, key)) 104f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.mapping = {} 105f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 106f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if hasattr(table, 'mapping'): 107f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.mapping.update(table.mapping) 108f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.extraNames = [] 109f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True 110f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 11165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('vmtx'), 11265f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod fontTools.ttLib.getTableClass('hmtx')) 11365f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahboddef merge(self, tables, fonts): 11465f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod self.metrics = {} 11565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for table in tables: 11665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod self.metrics.update(table.metrics) 11765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return True 11865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 11965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('loca')) 12065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahboddef merge(self, tables, fonts): 121f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True # Will be computed automatically 122f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 123f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('glyf')) 124f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 125f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.glyphs = {} 126f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 127f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.glyphs.update(table.glyphs) 128f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Drop hints? 129f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True 130f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 131f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('prep'), 132f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fontTools.ttLib.getTableClass('fpgm'), 133f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fontTools.ttLib.getTableClass('cvt ')) 134f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 13565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return False # Will be computed automatically 13645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 13771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('cmap')) 13871294def6730c37839f03dee519b319f982587eaBehdad Esfahboddef merge(self, tables, fonts): 13971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Handle format=14. 14071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTables = [t for table in tables for t in table.tables 14171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod if t.platformID == 3 and t.platEncID in [1, 10]] 14271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Better handle format-4 and format-12 coexisting in same font. 14371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Insert both a format-4 and format-12 if needed. 14471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod module = fontTools.ttLib.getTableModule('cmap') 14571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod assert all(t.format in [4, 12] for t in cmapTables) 14671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod format = max(t.format for t in cmapTables) 14771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable = module.cmap_classes[format](format) 14871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap = {} 14971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platformID = 3 15071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platEncID = max(t.platEncID for t in cmapTables) 15171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.language = 0 15271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod for table in cmapTables: 15371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO handle duplicates. 15471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap.update(table.cmap) 15571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tableVersion = 0 15671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tables = [cmapTable] 15771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.numSubTables = len(self.tables) 15871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod return True 15971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 160f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 161f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Options(object): 162f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 163f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod class UnknownOptionError(Exception): 164f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod pass 165f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 166f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp'] 167f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod drop_tables = _drop_tables_default 168f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 169f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, **kwargs): 170f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 171f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**kwargs) 172f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 173f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def set(self, **kwargs): 174f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for k,v in kwargs.iteritems(): 175f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 176f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % k) 177f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, k, v) 178f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 179f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv, ignore_unknown=False): 180f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret = [] 181f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts = {} 182f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for a in argv: 183f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod orig_a = a 184f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not a.startswith('--'): 185f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(a) 186f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 187f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod a = a[2:] 188f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod i = a.find('=') 189f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = '=' 190f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if i == -1: 191f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if a.startswith("no-"): 192f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[3:] 193f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = False 194f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 195f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a 196f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = True 197f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 198f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[:i] 199f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if k[-1] in "-+": 200f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = k[-1]+'=' # Ops is '-=' or '+=' now. 201f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k[:-1] 202f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = a[i+1:] 203f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k.replace('-', '_') 204f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 205f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if ignore_unknown == True or k in ignore_unknown: 206f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(orig_a) 207f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 208f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 209f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % a) 210f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 211f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ov = getattr(self, k) 212f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if isinstance(ov, bool): 213f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = bool(v) 214f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, int): 215f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = int(v) 216f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, list): 217f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = v.split(',') 218f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if vv == ['']: 219f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [] 220f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 221f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if op == '=': 222f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = vv 223f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '+=': 224f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 225f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.extend(vv) 226f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '-=': 227f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 228f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for x in vv: 229f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if x in v: 230f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.remove(x) 231f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 232f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod assert 0 233f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 234f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts[k] = v 235f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**opts) 236f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 237f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return ret 238f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 239f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 24045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodclass Merger: 24145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 242f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, options=None, log=None): 243f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 244f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not log: 245f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 246f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not options: 247f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 248f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 249f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.options = options 250f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.log = log 25145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 252f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def merge(self, fontfiles): 25345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 25445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = ttLib.TTFont() 25545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 25645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 25745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Settle on a mega glyph order. 25845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 259f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 26045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrders = [font.getGlyphOrder() for font in fonts] 26145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) 26245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Reload fonts and set new glyph names on them. 26345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO Is it necessary to reload font? I think it is. At least 26445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # it's safer, in case tables were loaded to provide glyph names. 265f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 26645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders) 26745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.setGlyphOrder(megaGlyphOrder) 26845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 26945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod cmaps = [self._get_cmap(font) for font in fonts] 27045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 27145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags = set(sum([font.keys() for font in fonts], [])) 27245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags.remove('GlyphOrder') 27345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for tag in allTags: 274f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 275f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if tag in self.options.drop_tables: 276b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Dropping '%s'." % tag) 277f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 278f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 27945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod clazz = ttLib.getTableClass(tag) 28045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 28145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if not hasattr(clazz, 'merge'): 282b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Don't know how to merge '%s', dropped." % tag) 28345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod continue 28445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 28545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO For now assume all fonts have the same tables. 28645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod tables = [font[tag] for font in fonts] 28745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod table = clazz(tag) 28865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod if table.merge (tables, fonts): 28965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod mega[tag] = table 290b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Merged '%s'." % tag) 29165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod else: 292b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Dropped '%s'. No need to merge explicitly." % tag) 293b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log.lapse("merge '%s'" % tag) 29445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 29545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 29645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 29745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _get_cmap(self, font): 29845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod cmap = font['cmap'] 29945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod tables = [t for t in cmap.tables 30045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if t.platformID == 3 and t.platEncID in [1, 10]] 30145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # XXX Handle format=14 30245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod assert len(tables) 30345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Pick table that has largest coverage 30445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod table = max(tables, key=lambda t: len(t.cmap)) 30545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return table 30645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 30745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _mergeGlyphOrders(self, glyphOrders): 30845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod """Modifies passed-in glyphOrders to reflect new glyph names.""" 30945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Simply append font index to the glyph name for now. 31045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = [] 31145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for n,glyphOrder in enumerate(glyphOrders): 31245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for i,glyphName in enumerate(glyphOrder): 31345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphName += "#" + `n` 31445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrder[i] = glyphName 31545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.append(glyphName) 31645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 31745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 318f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 319f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Logger(object): 320f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 321f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, verbose=False, xml=False, timing=False): 322f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.verbose = verbose 323f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.xml = xml 324f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.timing = timing 325f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = self.start_time = time.time() 326f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 327f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv): 328f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv = argv[:] 329f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for v in ['verbose', 'xml', 'timing']: 330f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if "--"+v in argv: 331f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, v, True) 332f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv.remove("--"+v) 333f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return argv 334f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 335f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __call__(self, *things): 336f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.verbose: 337f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 338f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod print ' '.join(str(x) for x in things) 339f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 340f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def lapse(self, *things): 341f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.timing: 342f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 343f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod new_time = time.time() 344f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod print "Took %0.3fs to %s" %(new_time - self.last_time, 345f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ' '.join(str(x) for x in things)) 346f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = new_time 347f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 348f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def font(self, font, file=sys.stdout): 349f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.xml: 350f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 351f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod from fontTools.misc import xmlWriter 352f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer = xmlWriter.XMLWriter(file) 353f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font.disassembleInstructions = False # Work around ttLib bug 354f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for tag in font.keys(): 355f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.begintag(tag) 356f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 357f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font[tag].toXML(writer, font) 358f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.endtag(tag) 359f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 360f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 361f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 362f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod__all__ = [ 363f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Options', 364f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Merger', 365f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Logger', 366f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'main' 367f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod] 368f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 36945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args): 370f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 371f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 372f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = log.parse_opts(args) 373f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 374f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 375f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = options.parse_opts(args) 376f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 37745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if len(args) < 1: 37845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod print >>sys.stderr, "usage: pyftmerge font..." 37945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod sys.exit(1) 380f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 381f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod merger = Merger(options=options, log=log) 382f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font = merger.merge(args) 38345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod outfile = 'merged.ttf' 38445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod font.save(outfile) 385b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("compile and save font") 386b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod 387b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.last_time = log.start_time 388b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("make one with everything(TOTAL TIME)") 38945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 39045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__": 39145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod main(sys.argv[1:]) 392