merge.py revision f2d5982826530296fd7c8f9e2d2a4dc3e070934d
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): 1545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod """Returns a decorator function that adds a new method to one or 1645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod more classes.""" 1745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def wrapper(method): 1845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for clazz in clazzes: 1945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.' 2045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod assert not hasattr(clazz, method.func_name), \ 2145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod "Oops, class '%s' has method '%s'." % (clazz.__name__, 2245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod method.func_name) 2345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod setattr(clazz, method.func_name, method) 2445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return None 2545d2f38aa51169f400062f2153a74277b9d4d04eBehdad 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 74f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('post')) 75f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 76f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Check that italicAngle, underlinePosition, underlineThickness are the same. 77f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod minMembers = ['underlinePosition', 'minMemType42', 'minMemType1'] 78f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate some members 79f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 80f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 81f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 82f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Get max over members 83f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys = set(sum((vars(table).keys() for table in tables), [])) 84f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if 'mapping' in keys: 85f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys.remove('mapping') 86f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod keys.remove('extraNames') 87f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in keys: 88f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, max(getattr(table, key) for table in tables)) 89f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # Negate them back 90f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for key in minMembers: 91f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 92f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(table, key, -getattr(table, key)) 93f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, key, -getattr(self, key)) 94f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.mapping = {} 95f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 96f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if hasattr(table, 'mapping'): 97f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.mapping.update(table.mapping) 98f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.extraNames = [] 99f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True 100f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 10165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('vmtx'), 10265f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod fontTools.ttLib.getTableClass('hmtx')) 10365f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahboddef merge(self, tables, fonts): 10465f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod self.metrics = {} 10565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod for table in tables: 10665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod self.metrics.update(table.metrics) 10765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return True 10865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 10965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('loca')) 11065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahboddef merge(self, tables, fonts): 111f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True # Will be computed automatically 112f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 113f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('glyf')) 114f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 115f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.glyphs = {} 116f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for table in tables: 117f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.glyphs.update(table.glyphs) 118f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod # TODO Drop hints? 119f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return True 120f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 121f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod@_add_method(fontTools.ttLib.getTableClass('prep'), 122f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fontTools.ttLib.getTableClass('fpgm'), 123f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fontTools.ttLib.getTableClass('cvt ')) 124f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahboddef merge(self, tables, fonts): 12565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod return False # Will be computed automatically 12645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 127f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 128f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Options(object): 129f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 130f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod class UnknownOptionError(Exception): 131f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod pass 132f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 133f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp'] 134f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod drop_tables = _drop_tables_default 135f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 136f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, **kwargs): 137f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 138f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**kwargs) 139f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 140f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def set(self, **kwargs): 141f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for k,v in kwargs.iteritems(): 142f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 143f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % k) 144f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, k, v) 145f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 146f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv, ignore_unknown=False): 147f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret = [] 148f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts = {} 149f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for a in argv: 150f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod orig_a = a 151f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not a.startswith('--'): 152f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(a) 153f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 154f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod a = a[2:] 155f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod i = a.find('=') 156f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = '=' 157f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if i == -1: 158f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if a.startswith("no-"): 159f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[3:] 160f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = False 161f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 162f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a 163f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = True 164f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 165f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[:i] 166f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if k[-1] in "-+": 167f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = k[-1]+'=' # Ops is '-=' or '+=' now. 168f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k[:-1] 169f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = a[i+1:] 170f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k.replace('-', '_') 171f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 172f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if ignore_unknown == True or k in ignore_unknown: 173f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(orig_a) 174f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 175f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 176f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % a) 177f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 178f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ov = getattr(self, k) 179f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if isinstance(ov, bool): 180f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = bool(v) 181f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, int): 182f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = int(v) 183f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, list): 184f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = v.split(',') 185f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if vv == ['']: 186f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [] 187f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 188f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if op == '=': 189f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = vv 190f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '+=': 191f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 192f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.extend(vv) 193f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '-=': 194f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 195f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for x in vv: 196f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if x in v: 197f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.remove(x) 198f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 199f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod assert 0 200f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 201f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts[k] = v 202f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**opts) 203f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 204f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return ret 205f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 206f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 20745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodclass Merger: 20845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 209f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, options=None, log=None): 210f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 211f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not log: 212f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 213f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not options: 214f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 215f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 216f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.options = options 217f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.log = log 21845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 219f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def merge(self, fontfiles): 22045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 22145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = ttLib.TTFont() 22245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 22345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 22445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Settle on a mega glyph order. 22545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 226f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 22745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrders = [font.getGlyphOrder() for font in fonts] 22845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) 22945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Reload fonts and set new glyph names on them. 23045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO Is it necessary to reload font? I think it is. At least 23145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # it's safer, in case tables were loaded to provide glyph names. 232f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 23345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders) 23445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.setGlyphOrder(megaGlyphOrder) 23545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 23645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod cmaps = [self._get_cmap(font) for font in fonts] 23745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 23845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags = set(sum([font.keys() for font in fonts], [])) 23945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags.remove('GlyphOrder') 24045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for tag in allTags: 241f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 242f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if tag in self.options.drop_tables: 243f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod print "Dropping '%s'." % tag 244f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 245f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 24645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod clazz = ttLib.getTableClass(tag) 24745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 24845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if not hasattr(clazz, 'merge'): 24945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod print "Don't know how to merge '%s', dropped." % tag 25045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod continue 25145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 25245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO For now assume all fonts have the same tables. 25345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod tables = [font[tag] for font in fonts] 25445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod table = clazz(tag) 25565f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod if table.merge (tables, fonts): 25665f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod mega[tag] = table 25765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod print "Merged '%s'." % tag 25865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod else: 25965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod print "Dropped '%s'. No need to merge explicitly." % tag 26045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 26145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 26245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 26345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _get_cmap(self, font): 26445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod cmap = font['cmap'] 26545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod tables = [t for t in cmap.tables 26645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if t.platformID == 3 and t.platEncID in [1, 10]] 26745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # XXX Handle format=14 26845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod assert len(tables) 26945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Pick table that has largest coverage 27045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod table = max(tables, key=lambda t: len(t.cmap)) 27145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return table 27245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 27345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _mergeGlyphOrders(self, glyphOrders): 27445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod """Modifies passed-in glyphOrders to reflect new glyph names.""" 27545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Simply append font index to the glyph name for now. 27645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = [] 27745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for n,glyphOrder in enumerate(glyphOrders): 27845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for i,glyphName in enumerate(glyphOrder): 27945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphName += "#" + `n` 28045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrder[i] = glyphName 28145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.append(glyphName) 28245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 28345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 284f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 285f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Logger(object): 286f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 287f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, verbose=False, xml=False, timing=False): 288f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.verbose = verbose 289f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.xml = xml 290f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.timing = timing 291f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = self.start_time = time.time() 292f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 293f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv): 294f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv = argv[:] 295f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for v in ['verbose', 'xml', 'timing']: 296f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if "--"+v in argv: 297f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, v, True) 298f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv.remove("--"+v) 299f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return argv 300f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 301f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __call__(self, *things): 302f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.verbose: 303f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 304f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod print ' '.join(str(x) for x in things) 305f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 306f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def lapse(self, *things): 307f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.timing: 308f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 309f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod new_time = time.time() 310f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod print "Took %0.3fs to %s" %(new_time - self.last_time, 311f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ' '.join(str(x) for x in things)) 312f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = new_time 313f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 314f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def font(self, font, file=sys.stdout): 315f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.xml: 316f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 317f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod from fontTools.misc import xmlWriter 318f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer = xmlWriter.XMLWriter(file) 319f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font.disassembleInstructions = False # Work around ttLib bug 320f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for tag in font.keys(): 321f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.begintag(tag) 322f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 323f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font[tag].toXML(writer, font) 324f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.endtag(tag) 325f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 326f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 327f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 328f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod__all__ = [ 329f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Options', 330f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Merger', 331f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Logger', 332f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'main' 333f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod] 334f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 33545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args): 336f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 337f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 338f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = log.parse_opts(args) 339f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 340f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 341f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = options.parse_opts(args) 342f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 34345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if len(args) < 1: 34445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod print >>sys.stderr, "usage: pyftmerge font..." 34545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod sys.exit(1) 346f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 347f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod merger = Merger(options=options, log=log) 348f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font = merger.merge(args) 34945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod outfile = 'merged.ttf' 35045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod font.save(outfile) 35145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 35245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__": 35345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod main(sys.argv[1:]) 354