merge.py revision 2642934116cc0e3b873e37051cb57d3b29c0a9b4
145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod# Copyright 2013 Google, Inc. All Rights Reserved. 245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod# 347bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader# Google Author(s): Behdad Esfahbod, Roozbeh Pournader 445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod"""Font merger. 645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod""" 745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 8f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbodfrom __future__ import print_function, division 9f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbodfrom fontTools.misc.py23 import * 10f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbodfrom fontTools import ttLib, cffLib 11e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderfrom fontTools.ttLib.tables import otTables, _h_e_a_d 129e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbodfrom fontTools.ttLib.tables.DefaultTable import DefaultTable 13f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbodfrom functools import reduce 1445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodimport sys 15f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodimport time 1649028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbodimport operator 1745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 1845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 199e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahboddef _add_method(*clazzes, **kwargs): 20c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod """Returns a decorator function that adds a new method to one or 21c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod more classes.""" 22c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod def wrapper(method): 23c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod for clazz in clazzes: 249e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod if not kwargs.get('allowDefaultTable', False): 259e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod assert clazz != DefaultTable, 'Oops, table class not found.' 269e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod assert method.__name__ not in clazz.__dict__, \ 27c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod "Oops, class '%s' has method '%s'." % (clazz.__name__, 28f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod method.__name__) 299e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod setattr(clazz, method.__name__, method) 30c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod return None 31c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod return wrapper 3245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 3347bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader# General utility functions for merging values from different fonts 3492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 3549028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahboddef equal(lst): 3649028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod t = iter(lst) 3749028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod first = next(t) 3849028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod assert all(item == first for item in t) 39e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader return first 4047bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 4147bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournaderdef first(lst): 4249028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return next(iter(lst)) 4347bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 44e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef recalculate(lst): 4592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 46e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 47e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef current_time(lst): 4849028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return int(time.time() - _h_e_a_d.mac_epoch_diff) 49e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 50e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef bitwise_or(lst): 5149028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return reduce(operator.or_, lst) 52e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 5392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahboddef avg_int(lst): 5492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod lst = list(lst) 5592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return sum(lst) // len(lst) 5645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 5792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahboddef nonnone(func): 589e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod """Returns a filter func that when called with a list, 599e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod only calls func on the non-None items of the list, and 609e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod only so if there's at least one non-None item in the 6192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod list. Otherwise returns None.""" 6265f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 639e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod def wrapper(lst): 649e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod items = [item for item in lst if item is not None] 659e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return func(items) if items else None 669e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 679e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return wrapper 689e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 6992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahboddef implemented(func): 7092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod """Returns a filter func that when called with a list, 7192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod only calls func on the non-NotImplemented items of the list, 7292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod and only so if there's at least one item remaining. 7392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod Otherwise returns NotImplemented.""" 7492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 7592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod def wrapper(lst): 7692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod items = [item for item in lst if item is not NotImplemented] 7792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return func(items) if items else NotImplemented 7892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 7992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return wrapper 8092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahboddef sumLists(lst): 829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod l = [] 839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod for item in lst: 849e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod l.extend(item) 859e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return l 869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 879e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahboddef sumDicts(lst): 889e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod d = {} 899e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod for item in lst: 909e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod d.update(item) 919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return d 92f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 9312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahboddef mergeObjects(lst): 9412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod lst = [item for item in lst if item is not None and item is not NotImplemented] 9512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if not lst: 9612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod return None # Not all can be NotImplemented 9712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 9812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod clazz = lst[0].__class__ 9912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod assert all(type(item) == clazz for item in lst), lst 10012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod logic = clazz.mergeMap 10112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod returnTable = clazz() 10212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 10312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod allKeys = set.union(set(), *(vars(table).keys() for table in lst)) 10412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod for key in allKeys: 10512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod try: 10612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod mergeLogic = logic[key] 10712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod except KeyError: 10812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod try: 10912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod mergeLogic = logic['*'] 11012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod except KeyError: 11112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod raise Exception("Don't know how to merge key %s of class %s" % 11212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod (key, clazz.__name__)) 11312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if mergeLogic is NotImplemented: 11412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod continue 11512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod value = mergeLogic(getattr(table, key, NotImplemented) for table in lst) 11612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if value is not NotImplemented: 11712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod setattr(returnTable, key, value) 11812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 11912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod return returnTable 12012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 12165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 1229e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod@_add_method(DefaultTable, allowDefaultTable=True) 1233b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 1249e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod if not hasattr(self, 'mergeMap'): 1259e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod m.log("Don't know how to merge '%s'." % self.tableTag) 12692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 1279e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 12892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return m.mergeObjects(self, self.mergeMap, tables) 12971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 1309e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('maxp').mergeMap = { 1319e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': max, 1329e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1339e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': equal, 1349e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'numGlyphs': sum, 1359e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxStorage': max, # FIXME: may need to be changed to sum 1369e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxFunctionDefs': sum, 1379e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxInstructionDefs': sum, 1389e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # TODO When we correctly merge hinting data, update these values: 1399e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions 1409e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 1419e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 1429e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('head').mergeMap = { 1439e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1449e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': max, 1459e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fontRevision': max, 14692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'checkSumAdjustment': lambda lst: 0, # We need *something* here 1479e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'magicNumber': equal, 1489e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'flags': first, # FIXME: replace with bit-sensitive code 1499e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'unitsPerEm': equal, 1509e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'created': current_time, 1519e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'modified': current_time, 1529e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMin': min, 1539e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'yMin': min, 1549e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMax': max, 1559e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'yMax': max, 1569e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'macStyle': first, 1579e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'lowestRecPPEM': max, 1589e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fontDirectionHint': lambda lst: 2, 1599e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'indexToLocFormat': recalculate, 1609e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphDataFormat': equal, 1619e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 1629e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 1639e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('hhea').mergeMap = { 1649e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': equal, 1659e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1669e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': max, 1679e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ascent': max, 1689e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'descent': min, 1699e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'lineGap': max, 1709e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'advanceWidthMax': max, 1719e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minLeftSideBearing': min, 1729e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minRightSideBearing': min, 1739e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMaxExtent': max, 1749e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'caretSlopeRise': first, # FIXME 1759e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'caretSlopeRun': first, # FIXME 1769e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'caretOffset': first, # FIXME 1779e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'numberOfHMetrics': recalculate, 1789e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 1799e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 1809e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('OS/2').mergeMap = { 1819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': first, 1829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'version': max, 18492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this 1859e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fsType': first, # FIXME 1869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'panose': first, # FIXME? 1879e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange1': bitwise_or, 1889e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange2': bitwise_or, 1899e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange3': bitwise_or, 1909e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange4': bitwise_or, 1919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fsFirstCharIndex': min, 1929e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fsLastCharIndex': max, 1939e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoAscender': max, 1949e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoDescender': min, 1959e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoLineGap': max, 1969e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usWinAscent': max, 1979e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usWinDescent': max, 1989e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulCodePageRange1': bitwise_or, 1999e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulCodePageRange2': bitwise_or, 2009e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usMaxContex': max, 2019e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # TODO version 5 2029e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 2039e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 2049e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('post').mergeMap = { 2059e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': first, 2069e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2079e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'formatType': max, 2089e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'isFixedPitch': min, 2099e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minMemType42': max, 2109e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxMemType42': lambda lst: 0, 2119e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minMemType1': max, 2129e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxMemType1': lambda lst: 0, 21392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'mapping': implemented(sumDicts), 21492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'extraNames': lambda lst: [][:], 2159e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 216f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 2179e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = { 2189e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2199e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'metrics': sumDicts, 2209e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 22165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 2229e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('loca').mergeMap = { 22392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod '*': recalculate, 2249e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2259e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 2269e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 2279e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('glyf').mergeMap = { 2289e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2299e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphs': sumDicts, 2309e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphOrder': sumLists, 2319e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 232f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 233be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('glyf')) 2343b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 2353b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahbod for table in tables: 236436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod for g in table.glyphs.values(): 237436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # Drop hints for now, since we don't remap 238436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # functions / CVT values. 239436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod g.removeHinting() 240436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # Expand composite glyphs to load their 241436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # composite glyph names. 242436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod if g.isComposite(): 243436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod g.expand(table) 24492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return DefaultTable.merge(self, m, tables) 245f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 24692fd5665772996966aaa0709462ebae5a72d4e19Behdad EsfahbodttLib.getTableClass('prep').mergeMap = NotImplemented 24792fd5665772996966aaa0709462ebae5a72d4e19Behdad EsfahbodttLib.getTableClass('fpgm').mergeMap = NotImplemented 24892fd5665772996966aaa0709462ebae5a72d4e19Behdad EsfahbodttLib.getTableClass('cvt ').mergeMap = NotImplemented 24945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 250be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('cmap')) 2513b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 25271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Handle format=14. 2533b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahbod cmapTables = [t for table in tables for t in table.tables 25471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod if t.platformID == 3 and t.platEncID in [1, 10]] 25571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Better handle format-4 and format-12 coexisting in same font. 25671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Insert both a format-4 and format-12 if needed. 257be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod module = ttLib.getTableModule('cmap') 25871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod assert all(t.format in [4, 12] for t in cmapTables) 25971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod format = max(t.format for t in cmapTables) 26071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable = module.cmap_classes[format](format) 26171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap = {} 26271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platformID = 3 26371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platEncID = max(t.platEncID for t in cmapTables) 26471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.language = 0 26571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod for table in cmapTables: 26671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO handle duplicates. 26771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap.update(table.cmap) 26871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tableVersion = 0 26971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tables = [cmapTable] 27071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.numSubTables = len(self.tables) 27192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return self 27271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 273c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod 2742642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.ScriptList.mergeMap = { 2752642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'ScriptCount': sum, 2762642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'ScriptRecord': sumLists, 2772642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 2782642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 2792642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.FeatureList.mergeMap = { 2802642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'FeatureCount': sum, 2812642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'FeatureRecord': sumLists, 2822642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 2832642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 2842642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.LookupList.mergeMap = { 2852642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'LookupCount': sum, 2862642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'Lookup': sumLists, 2872642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 2882642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 28912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.Coverage.mergeMap = { 29012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'glyphs': sumLists, 29112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 29212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 29312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.ClassDef.mergeMap = { 29412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'classDefs': sumDicts, 29512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 29612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 29712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.LigCaretList.mergeMap = { 29812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': mergeObjects, 29912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'LigGlyphCount': sum, 30012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'LigGlyph': sumLists, 30112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 302c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod 30312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.AttachList.mergeMap = { 30412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': mergeObjects, 30512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'GlyphCount': sum, 30612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'AttachPoint': sumLists, 30712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 30812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 30912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod# XXX Renumber MarkFilterSets of lookups 31012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.MarkGlyphSetsDef.mergeMap = { 31112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'MarkSetTableFormat': equal, 31212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'MarkSetCount': sum, 31312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': sumLists, 31412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 31512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 31612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.GDEF.mergeMap = { 31712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod '*': mergeObjects, 31812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Version': max, 31912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 32012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 3212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.GSUB.mergeMap = otTables.GPOS.mergeMap = { 3222642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod '*': mergeObjects, 32312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Version': max, 3242642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 3252642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3262642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GDEF').mergeMap = \ 3272642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GSUB').mergeMap = \ 3282642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GPOS').mergeMap = \ 3292642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('BASE').mergeMap = \ 3302642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('JSTF').mergeMap = \ 3312642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('MATH').mergeMap = \ 3322642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod{ 3332642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'tableTag': equal, 33412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'table': mergeObjects, 33512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 336f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 3372642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3382642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Feature) 3392642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap): 3402642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.LookupListIndex = [lookupMap[i] for i in self.LookupListIndex] 3412642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3422642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.FeatureList) 3432642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap): 3442642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for f in self.FeatureRecord: 3452642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not f or not f.Feature: continue 3462642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod f.Feature.mapLookups(lookupMap) 3472642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3482642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.DefaultLangSys, 3492642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod otTables.LangSys) 3502642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap): 3512642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.FeatureIndex = [featureMap[i] for i in self.FeatureIndex] 3522642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if self.ReqFeatureIndex != 65535: 3532642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.ReqFeatureIndex = featureMap[self.ReqFeatureIndex] 3542642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3552642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Script) 3562642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap): 3572642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if self.DefaultLangSys: 3582642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.DefaultLangSys.mapFeatures(featureMap) 3592642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for l in self.LangSysRecord: 3602642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not l or not l.LangSys: continue 3612642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod l.LangSys.mapFeatures(featureMap) 3622642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3632642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.ScriptList) 3642642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, feature_indices): 3652642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for s in self.ScriptRecord: 3662642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not s or not s.Script: continue 3672642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod s.Script.mapFeatures(featureMap) 3682642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3692642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 370f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Options(object): 371f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 372f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod class UnknownOptionError(Exception): 373f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod pass 374f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 375f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, **kwargs): 376f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 377f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**kwargs) 378f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 379f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def set(self, **kwargs): 380f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod for k,v in kwargs.items(): 381f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 382f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % k) 383f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, k, v) 384f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 385f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv, ignore_unknown=False): 386f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret = [] 387f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts = {} 388f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for a in argv: 389f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod orig_a = a 390f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not a.startswith('--'): 391f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(a) 392f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 393f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod a = a[2:] 394f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod i = a.find('=') 395f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = '=' 396f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if i == -1: 397f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if a.startswith("no-"): 398f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[3:] 399f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = False 400f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 401f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a 402f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = True 403f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 404f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[:i] 405f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if k[-1] in "-+": 406f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = k[-1]+'=' # Ops is '-=' or '+=' now. 407f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k[:-1] 408f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = a[i+1:] 409f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k.replace('-', '_') 410f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 411f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if ignore_unknown == True or k in ignore_unknown: 412f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(orig_a) 413f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 414f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 415f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % a) 416f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 417f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ov = getattr(self, k) 418f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if isinstance(ov, bool): 419f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = bool(v) 420f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, int): 421f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = int(v) 422f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, list): 423f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = v.split(',') 424f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if vv == ['']: 425f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [] 426f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 427f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if op == '=': 428f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = vv 429f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '+=': 430f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 431f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.extend(vv) 432f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '-=': 433f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 434f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for x in vv: 435f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if x in v: 436f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.remove(x) 437f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 438f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod assert 0 439f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 440f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts[k] = v 441f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**opts) 442f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 443f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return ret 444f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 445f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 4469e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbodclass Merger(object): 44745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 448f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, options=None, log=None): 449f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 450f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not log: 451f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 452f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not options: 453f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 454f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 455f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.options = options 456f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.log = log 45745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 458f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def merge(self, fontfiles): 45945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 46045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = ttLib.TTFont() 46145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 46245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 46345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Settle on a mega glyph order. 46445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 465f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 46645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrders = [font.getGlyphOrder() for font in fonts] 46745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) 46845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Reload fonts and set new glyph names on them. 46945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO Is it necessary to reload font? I think it is. At least 47045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # it's safer, in case tables were loaded to provide glyph names. 471f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 4723235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod for font,glyphOrder in zip(fonts, glyphOrders): 4733235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod font.setGlyphOrder(glyphOrder) 47445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.setGlyphOrder(megaGlyphOrder) 47545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 4762642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for font in fonts: 4772642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self._preMerge(font) 4782642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 479f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod allTags = reduce(set.union, (list(font.keys()) for font in fonts), set()) 48045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags.remove('GlyphOrder') 48145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for tag in allTags: 482f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 48345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod clazz = ttLib.getTableClass(tag) 48445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 4852642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod tables = [font.get(tag, NotImplemented) for font in fonts] 48692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod table = clazz(tag).merge(self, tables) 48792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if table is not NotImplemented and table is not False: 48865f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod mega[tag] = table 489b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Merged '%s'." % tag) 49065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod else: 4919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod self.log("Dropped '%s'." % tag) 492b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log.lapse("merge '%s'" % tag) 49345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 4942642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self._postMerge(mega) 4952642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 49645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 49745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 49845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _mergeGlyphOrders(self, glyphOrders): 499c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod """Modifies passed-in glyphOrders to reflect new glyph names. 500c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod Returns glyphOrder for the merged font.""" 50145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Simply append font index to the glyph name for now. 502c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod # TODO Even this simplistic numbering can result in conflicts. 503c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod # But then again, we have to improve this soon anyway. 50445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = [] 50545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for n,glyphOrder in enumerate(glyphOrders): 50645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for i,glyphName in enumerate(glyphOrder): 507f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod glyphName += "#" + repr(n) 50845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrder[i] = glyphName 50945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.append(glyphName) 51045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 51145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 5126baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod def mergeObjects(self, returnTable, logic, tables): 51392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod # Right now we don't use self at all. Will use in the future 51492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod # for options and logging. 51592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 51692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if logic is NotImplemented: 51792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 51892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 51992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented)) 52047bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader for key in allKeys: 521e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader try: 5226baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod mergeLogic = logic[key] 523e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader except KeyError: 5249e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod try: 5256baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod mergeLogic = logic['*'] 5269e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod except KeyError: 5276baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod raise Exception("Don't know how to merge key %s of class %s" % 5286baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod (key, returnTable.__class__.__name__)) 52992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if mergeLogic is NotImplemented: 530e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader continue 53192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod value = mergeLogic(getattr(table, key, NotImplemented) for table in tables) 53292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if value is not NotImplemented: 53392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod setattr(returnTable, key, value) 53492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 53592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return returnTable 53647bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 5372642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod def _preMerge(self, font): 5382642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5392642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod return 5402642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GDEF = font.get('GDEF') 5412642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GSUB = font.get('GSUB') 5422642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GPOS = font.get('GPOS') 5432642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5442642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for t in [GSUB, GPOS]: 5452642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not t or not t.table.LookupList or not t.table.FeatureList: continue 5462642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod lookupMap = dict(enumerate(t.table.LookupList.Lookup)) 5472642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod t.table.FeatureList.mapLookups(lookupMap) 5482642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5492642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod # TODO FeatureParams nameIDs 5502642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5512642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod def _postMerge(self, font): 5522642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod pass 5532642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 554f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 555f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Logger(object): 556f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 557f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, verbose=False, xml=False, timing=False): 558f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.verbose = verbose 559f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.xml = xml 560f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.timing = timing 561f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = self.start_time = time.time() 562f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 563f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv): 564f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv = argv[:] 565f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for v in ['verbose', 'xml', 'timing']: 566f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if "--"+v in argv: 567f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, v, True) 568f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv.remove("--"+v) 569f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return argv 570f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 571f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __call__(self, *things): 572f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.verbose: 573f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 574f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print(' '.join(str(x) for x in things)) 575f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 576f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def lapse(self, *things): 577f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.timing: 578f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 579f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod new_time = time.time() 580f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print("Took %0.3fs to %s" %(new_time - self.last_time, 581f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod ' '.join(str(x) for x in things))) 582f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = new_time 583f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 584f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def font(self, font, file=sys.stdout): 585f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.xml: 586f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 587f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod from fontTools.misc import xmlWriter 588f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer = xmlWriter.XMLWriter(file) 589f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font.disassembleInstructions = False # Work around ttLib bug 590f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for tag in font.keys(): 591f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.begintag(tag) 592f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 593f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font[tag].toXML(writer, font) 594f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.endtag(tag) 595f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 596f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 597f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 598f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod__all__ = [ 599f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Options', 600f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Merger', 601f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Logger', 602f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'main' 603f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod] 604f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 60545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args): 606f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 607f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 608f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = log.parse_opts(args) 609f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 610f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 611f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = options.parse_opts(args) 612f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 61345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if len(args) < 1: 614f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print("usage: pyftmerge font...", file=sys.stderr) 61545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod sys.exit(1) 616f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 617f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod merger = Merger(options=options, log=log) 618f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font = merger.merge(args) 61945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod outfile = 'merged.ttf' 62045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod font.save(outfile) 621b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("compile and save font") 622b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod 623b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.last_time = log.start_time 624b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("make one with everything(TOTAL TIME)") 62545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 62645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__": 62745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod main(sys.argv[1:]) 628