merge.py revision b8039e26530e2a76c3c15c3bd79b31137e3719f2
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 81ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 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.""" 22c68c0ff12fcf38d97304540b7dc0253f4142b046Behdad Esfahbod allowDefault = kwargs.get('allowDefaultTable', False) 23c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod def wrapper(method): 24c855f3ab69f31004b6aaca33ac549d384f1efc54Behdad Esfahbod for clazz in clazzes: 2535e3c7270d93778f51b0b16110acb2ed234f5593Behdad Esfahbod assert allowDefault or 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): 36477dad1ee854c3701f8b0b6ff338fb7523ea27b6Behdad Esfahbod lst = list(lst) 3749028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod t = iter(lst) 3849028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod first = next(t) 39477dad1ee854c3701f8b0b6ff338fb7523ea27b6Behdad Esfahbod assert all(item == first for item in t), "Expected all items to be equal: %s" % lst 40e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader return first 4147bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 4247bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournaderdef first(lst): 4349028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return next(iter(lst)) 4447bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 45e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef recalculate(lst): 4692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 47e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 48e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef current_time(lst): 4949028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return int(time.time() - _h_e_a_d.mac_epoch_diff) 50e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 51642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournaderdef bitwise_and(lst): 52642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader return reduce(operator.and_, lst) 53642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 54e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournaderdef bitwise_or(lst): 5549028b3ba7196b0389ac964f3a2db163367d24b8Behdad Esfahbod return reduce(operator.or_, lst) 56e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader 5792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahboddef avg_int(lst): 5892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod lst = list(lst) 5992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return sum(lst) // len(lst) 6045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 61233663207d75f02a0dcc7892b8ea658d414ac921Behdad Esfahboddef implementedFilter(func): 6292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod """Returns a filter func that when called with a list, 6392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod only calls func on the non-NotImplemented items of the list, 6492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod and only so if there's at least one item remaining. 6592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod Otherwise returns NotImplemented.""" 6692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 6792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod def wrapper(lst): 6892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod items = [item for item in lst if item is not NotImplemented] 6992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return func(items) if items else NotImplemented 7092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 7192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return wrapper 7292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 739e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahboddef sumLists(lst): 749e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod l = [] 759e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod for item in lst: 769e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod l.extend(item) 779e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return l 789e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 799e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahboddef sumDicts(lst): 809e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod d = {} 819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod for item in lst: 829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod d.update(item) 839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod return d 84f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 8512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahboddef mergeObjects(lst): 8612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod lst = [item for item in lst if item is not None and item is not NotImplemented] 8712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if not lst: 8812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod return None # Not all can be NotImplemented 8912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 9012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod clazz = lst[0].__class__ 9112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod assert all(type(item) == clazz for item in lst), lst 9212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod logic = clazz.mergeMap 9312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod returnTable = clazz() 9412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 9512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod allKeys = set.union(set(), *(vars(table).keys() for table in lst)) 9612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod for key in allKeys: 9712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod try: 9812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod mergeLogic = logic[key] 9912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod except KeyError: 10012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod try: 10112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod mergeLogic = logic['*'] 10212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod except KeyError: 10312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod raise Exception("Don't know how to merge key %s of class %s" % 10412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod (key, clazz.__name__)) 10512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if mergeLogic is NotImplemented: 10612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod continue 10712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod value = mergeLogic(getattr(table, key, NotImplemented) for table in lst) 10812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod if value is not NotImplemented: 10912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod setattr(returnTable, key, value) 11012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 11112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod return returnTable 11212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 113b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahboddef mergeBits(bitmap, lst): 114642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader lst = list(lst) 115642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader returnValue = 0 116b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbod for bitNumber in range(bitmap['size']): 117642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader try: 118b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbod mergeLogic = bitmap[bitNumber] 119642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader except KeyError: 120642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader try: 121b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbod mergeLogic = bitmap['*'] 122642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader except KeyError: 123642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader raise Exception("Don't know how to merge bit %s" % bitNumber) 124642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader shiftedBit = 1 << bitNumber 125642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader mergedValue = mergeLogic(bool(item & shiftedBit) for item in lst) 126642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader returnValue |= mergedValue << bitNumber 127642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader return returnValue 128642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 12965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 1309e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod@_add_method(DefaultTable, allowDefaultTable=True) 1313b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 1329e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod if not hasattr(self, 'mergeMap'): 1339e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod m.log("Don't know how to merge '%s'." % self.tableTag) 13492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 1359e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 13627c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod logic = self.mergeMap 13727c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod 13827c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod if isinstance(logic, dict): 13927c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod return m.mergeObjects(self, self.mergeMap, tables) 14027c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod else: 14127c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod return logic(tables) 14227c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod 14371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 1449e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('maxp').mergeMap = { 1459e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': max, 1469e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1479e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': equal, 1489e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'numGlyphs': sum, 14927c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod 'maxStorage': first, 15027c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod 'maxFunctionDefs': first, 15127c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod 'maxInstructionDefs': first, 1529e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # TODO When we correctly merge hinting data, update these values: 1539e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions 1549e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 1559e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 156b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad EsfahbodheadFlagsMergeBitMap = { 157642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'size': 16, 158642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader '*': bitwise_or, 159642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 1: bitwise_and, # Baseline at y = 0 160642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 2: bitwise_and, # lsb at x = 0 161642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 3: bitwise_and, # Force ppem to integer values. FIXME? 162642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 5: bitwise_and, # Font is vertical 163642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 6: lambda bit: 0, # Always set to zero 164642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 11: bitwise_and, # Font data is 'lossless' 165642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 13: bitwise_and, # Optimized for ClearType 166642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 14: bitwise_and, # Last resort font. FIXME? equal or first may be better 167642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 15: lambda bit: 0, # Always set to zero 168642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader} 169642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 1709e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('head').mergeMap = { 1719e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1729e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': max, 1739e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fontRevision': max, 17492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'checkSumAdjustment': lambda lst: 0, # We need *something* here 1759e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'magicNumber': equal, 176b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbod 'flags': lambda lst: mergeBits(headFlagsMergeBitMap, lst), 1779e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'unitsPerEm': equal, 1789e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'created': current_time, 1799e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'modified': current_time, 1809e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMin': min, 1819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'yMin': min, 1829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMax': max, 1839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'yMax': max, 1849e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'macStyle': first, 1859e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'lowestRecPPEM': max, 1869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fontDirectionHint': lambda lst: 2, 1879e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'indexToLocFormat': recalculate, 1889e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphDataFormat': equal, 1899e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 1909e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 1919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('hhea').mergeMap = { 1929e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': equal, 1939e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 1949e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableVersion': max, 1959e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ascent': max, 1969e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'descent': min, 1979e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'lineGap': max, 1989e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'advanceWidthMax': max, 1999e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minLeftSideBearing': min, 2009e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minRightSideBearing': min, 2019e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'xMaxExtent': max, 202642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'caretSlopeRise': first, 203642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'caretSlopeRun': first, 204642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'caretOffset': first, 2059e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'numberOfHMetrics': recalculate, 2069e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 2079e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 208b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbodos2FsTypeMergeBitMap = { 209642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'size': 16, 210642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader '*': lambda bit: 0, 211642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 1: bitwise_or, # no embedding permitted 212642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 2: bitwise_and, # allow previewing and printing documents 213642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 3: bitwise_and, # allow editing documents 214642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 8: bitwise_or, # no subsetting permitted 215642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 9: bitwise_or, # no embedding of outlines permitted 216642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader} 217642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 218642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournaderdef mergeOs2FsType(lst): 219642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader lst = list(lst) 220642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if all(item == 0 for item in lst): 221642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader return 0 222642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 223642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # Compute least restrictive logic for each fsType value 224642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader for i in range(len(lst)): 225642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # unset bit 1 (no embedding permitted) if either bit 2 or 3 is set 226642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if lst[i] & 0x000C: 227642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader lst[i] &= ~0x0002 228642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # set bit 2 (allow previewing) if bit 3 is set (allow editing) 229642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader elif lst[i] & 0x0008: 230642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader lst[i] |= 0x0004 231642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # set bits 2 and 3 if everything is allowed 232642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader elif lst[i] == 0: 233642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader lst[i] = 0x000C 234642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 235b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbod fsType = mergeBits(os2FsTypeMergeBitMap, lst) 236642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # unset bits 2 and 3 if bit 1 is set (some font is "no embedding") 237642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if fsType & 0x0002: 238642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader fsType &= ~0x000C 239642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader return fsType 240642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 241642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 2429e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('OS/2').mergeMap = { 2439e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': first, 2449e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2459e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'version': max, 24692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this 247642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'fsType': mergeOs2FsType, # Will be overwritten 248642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 'panose': first, # FIXME: should really be the first Latin font 2499e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange1': bitwise_or, 2509e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange2': bitwise_or, 2519e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange3': bitwise_or, 2529e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulUnicodeRange4': bitwise_or, 2539e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fsFirstCharIndex': min, 2549e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'fsLastCharIndex': max, 2559e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoAscender': max, 2569e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoDescender': min, 2579e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'sTypoLineGap': max, 2589e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usWinAscent': max, 2599e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usWinDescent': max, 2609e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulCodePageRange1': bitwise_or, 2619e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'ulCodePageRange2': bitwise_or, 2629e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'usMaxContex': max, 2639e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod # TODO version 5 2649e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 2659e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 266642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader@_add_method(ttLib.getTableClass('OS/2')) 267642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournaderdef merge(self, m, tables): 268642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader DefaultTable.merge(self, m, tables) 269642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if self.version < 2: 270642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # bits 8 and 9 are reserved and should be set to zero 271642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader self.fsType &= ~0x0300 272642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if self.version >= 3: 273642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # Only one of bits 1, 2, and 3 may be set. We already take 274642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # care of bit 1 implications in mergeOs2FsType. So unset 275642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader # bit 2 if bit 3 is already set. 276642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader if self.fsType & 0x0008: 277642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader self.fsType &= ~0x0004 278642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader return self 279642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader 2809e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('post').mergeMap = { 2819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod '*': first, 2829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'formatType': max, 2849e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'isFixedPitch': min, 2859e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minMemType42': max, 2869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxMemType42': lambda lst: 0, 2879e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'minMemType1': max, 2889e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'maxMemType1': lambda lst: 0, 289233663207d75f02a0dcc7892b8ea658d414ac921Behdad Esfahbod 'mapping': implementedFilter(sumDicts), 290c68c0ff12fcf38d97304540b7dc0253f4142b046Behdad Esfahbod 'extraNames': lambda lst: [], 2919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 292f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 2939e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = { 2949e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 2959e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'metrics': sumDicts, 2969e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 29765f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod 2987a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh PournaderttLib.getTableClass('gasp').mergeMap = { 2997a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 'tableTag': equal, 3007a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 'version': max, 3017a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 'gaspRange': first, # FIXME? Appears irreconcilable 3027a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader} 3037a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 3047a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh PournaderttLib.getTableClass('name').mergeMap = { 3057a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 'tableTag': equal, 3067a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 'names': first, # FIXME? Does mixing name records make sense? 3077a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader} 3087a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader 3099e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('loca').mergeMap = { 31092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod '*': recalculate, 3119e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 3129e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 3139e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 3149e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('glyf').mergeMap = { 3159e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'tableTag': equal, 3169e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphs': sumDicts, 3179e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod 'glyphOrder': sumLists, 3189e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod} 319f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 320be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('glyf')) 3213b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 32227c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod for i,table in enumerate(tables): 323436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod for g in table.glyphs.values(): 32427c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod if i: 32527c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod # Drop hints for all but first font, since 32627c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod # we don't map functions / CVT values. 32727c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod g.removeHinting() 328436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # Expand composite glyphs to load their 329436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod # composite glyph names. 330436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod if g.isComposite(): 331436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod g.expand(table) 33292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return DefaultTable.merge(self, m, tables) 333f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 33427c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('prep').mergeMap = lambda self, lst: first(lst) 33527c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('fpgm').mergeMap = lambda self, lst: first(lst) 33627c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst) 33745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 338be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('cmap')) 3393b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables): 34071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Handle format=14. 3413b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahbod cmapTables = [t for table in tables for t in table.tables 342f480c7cf21c51ef67570f1a5fa1c1653fa817bfcBehdad Esfahbod if t.isUnicode()] 34371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Better handle format-4 and format-12 coexisting in same font. 34471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO Insert both a format-4 and format-12 if needed. 345be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod module = ttLib.getTableModule('cmap') 34671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod assert all(t.format in [4, 12] for t in cmapTables) 34771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod format = max(t.format for t in cmapTables) 34871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable = module.cmap_classes[format](format) 34971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap = {} 35071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platformID = 3 35171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.platEncID = max(t.platEncID for t in cmapTables) 35271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.language = 0 35371294def6730c37839f03dee519b319f982587eaBehdad Esfahbod for table in cmapTables: 35471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod # TODO handle duplicates. 35571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod cmapTable.cmap.update(table.cmap) 35671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tableVersion = 0 35771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.tables = [cmapTable] 35871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod self.numSubTables = len(self.tables) 35992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return self 36071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod 361c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod 3622642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.ScriptList.mergeMap = { 3632642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'ScriptCount': sum, 364972af5af639cc036cddd4534c051cc42bbf81672Behdad Esfahbod 'ScriptRecord': lambda lst: sorted(sumLists(lst), key=lambda s: s.ScriptTag), 3652642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 3662642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3672642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.FeatureList.mergeMap = { 3682642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'FeatureCount': sum, 3692642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'FeatureRecord': sumLists, 3702642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 3712642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 3722642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.LookupList.mergeMap = { 3732642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'LookupCount': sum, 3742642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'Lookup': sumLists, 3752642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 3762642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 37712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.Coverage.mergeMap = { 37812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'glyphs': sumLists, 37912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 38012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 38112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.ClassDef.mergeMap = { 38212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'classDefs': sumDicts, 38312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 38412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 38512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.LigCaretList.mergeMap = { 38612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': mergeObjects, 38712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'LigGlyphCount': sum, 38812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'LigGlyph': sumLists, 38912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 390c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod 39112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.AttachList.mergeMap = { 39212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': mergeObjects, 39312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'GlyphCount': sum, 39412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'AttachPoint': sumLists, 39512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 39612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 39712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod# XXX Renumber MarkFilterSets of lookups 39812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.MarkGlyphSetsDef.mergeMap = { 39912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'MarkSetTableFormat': equal, 40012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'MarkSetCount': sum, 40112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Coverage': sumLists, 40212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 40312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 40412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.GDEF.mergeMap = { 40512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod '*': mergeObjects, 40612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Version': max, 40712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 40812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 4092642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.GSUB.mergeMap = otTables.GPOS.mergeMap = { 4102642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod '*': mergeObjects, 41112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'Version': max, 4122642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod} 4132642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 4142642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GDEF').mergeMap = \ 4152642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GSUB').mergeMap = \ 4162642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GPOS').mergeMap = \ 4172642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('BASE').mergeMap = \ 4182642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('JSTF').mergeMap = \ 4192642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('MATH').mergeMap = \ 4202642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod{ 4212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 'tableTag': equal, 42212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod 'table': mergeObjects, 42312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod} 424f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 4252642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 4265080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.SingleSubst, 4275080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.MultipleSubst, 4285080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.AlternateSubst, 4295080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.LigatureSubst, 4305080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ReverseChainSingleSubst, 4315080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.SinglePos, 4325080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.PairPos, 4335080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.CursivePos, 4345080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.MarkBasePos, 4355080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.MarkLigPos, 4365080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.MarkMarkPos) 4375080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap): 4385080331251906861fbba4a9986efcd04978207beBehdad Esfahbod pass 4395080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4405080331251906861fbba4a9986efcd04978207beBehdad Esfahbod# Copied and trimmed down from subset.py 4415080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.ContextSubst, 4425080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ChainContextSubst, 4435080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ContextPos, 4445080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ChainContextPos) 4455080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef __classify_context(self): 4465080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4475080331251906861fbba4a9986efcd04978207beBehdad Esfahbod class ContextHelper(object): 4485080331251906861fbba4a9986efcd04978207beBehdad Esfahbod def __init__(self, klass, Format): 4495080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if klass.__name__.endswith('Subst'): 4505080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Typ = 'Sub' 4515080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Type = 'Subst' 4525080331251906861fbba4a9986efcd04978207beBehdad Esfahbod else: 4535080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Typ = 'Pos' 4545080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Type = 'Pos' 4555080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if klass.__name__.startswith('Chain'): 4565080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Chain = 'Chain' 4575080331251906861fbba4a9986efcd04978207beBehdad Esfahbod else: 4585080331251906861fbba4a9986efcd04978207beBehdad Esfahbod Chain = '' 4595080331251906861fbba4a9986efcd04978207beBehdad Esfahbod ChainTyp = Chain+Typ 4605080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4615080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.Typ = Typ 4625080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.Type = Type 4635080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.Chain = Chain 4645080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.ChainTyp = ChainTyp 4655080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4665080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.LookupRecord = Type+'LookupRecord' 4675080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4685080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if Format == 1: 4695080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.Rule = ChainTyp+'Rule' 4705080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.RuleSet = ChainTyp+'RuleSet' 4715080331251906861fbba4a9986efcd04978207beBehdad Esfahbod elif Format == 2: 4725080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.Rule = ChainTyp+'ClassRule' 4735080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.RuleSet = ChainTyp+'ClassSet' 4745080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4755080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if self.Format not in [1, 2, 3]: 4765080331251906861fbba4a9986efcd04978207beBehdad Esfahbod return None # Don't shoot the messenger; let it go 4775080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not hasattr(self.__class__, "__ContextHelpers"): 4785080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.__class__.__ContextHelpers = {} 4795080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if self.Format not in self.__class__.__ContextHelpers: 4805080331251906861fbba4a9986efcd04978207beBehdad Esfahbod helper = ContextHelper(self.__class__, self.Format) 4815080331251906861fbba4a9986efcd04978207beBehdad Esfahbod self.__class__.__ContextHelpers[self.Format] = helper 4825080331251906861fbba4a9986efcd04978207beBehdad Esfahbod return self.__class__.__ContextHelpers[self.Format] 4835080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4845080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4855080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.ContextSubst, 4865080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ChainContextSubst, 4875080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ContextPos, 4885080331251906861fbba4a9986efcd04978207beBehdad Esfahbod otTables.ChainContextPos) 4895080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap): 4905080331251906861fbba4a9986efcd04978207beBehdad Esfahbod c = self.__classify_context() 4915080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 4925080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if self.Format in [1, 2]: 4935080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for rs in getattr(self, c.RuleSet): 4945080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not rs: continue 4955080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for r in getattr(rs, c.Rule): 4965080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not r: continue 4975080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for ll in getattr(r, c.LookupRecord): 4985080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not ll: continue 4995080331251906861fbba4a9986efcd04978207beBehdad Esfahbod ll.LookupListIndex = lookupMap[ll.LookupListIndex] 5005080331251906861fbba4a9986efcd04978207beBehdad Esfahbod elif self.Format == 3: 5015080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for ll in getattr(self, c.LookupRecord): 5025080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not ll: continue 5035080331251906861fbba4a9986efcd04978207beBehdad Esfahbod ll.LookupListIndex = lookupMap[ll.LookupListIndex] 5045080331251906861fbba4a9986efcd04978207beBehdad Esfahbod else: 5055080331251906861fbba4a9986efcd04978207beBehdad Esfahbod assert 0, "unknown format: %s" % self.Format 5065080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 5075080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.Lookup) 5085080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap): 5095080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for st in self.SubTable: 5105080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not st: continue 5115080331251906861fbba4a9986efcd04978207beBehdad Esfahbod st.mapLookups(lookupMap) 5125080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 5135080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.LookupList) 5145080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap): 5155080331251906861fbba4a9986efcd04978207beBehdad Esfahbod for l in self.Lookup: 5165080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if not l: continue 5175080331251906861fbba4a9986efcd04978207beBehdad Esfahbod l.mapLookups(lookupMap) 5185080331251906861fbba4a9986efcd04978207beBehdad Esfahbod 5192642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Feature) 5202642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap): 5212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.LookupListIndex = [lookupMap[i] for i in self.LookupListIndex] 5222642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5232642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.FeatureList) 5242642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap): 5252642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for f in self.FeatureRecord: 5262642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not f or not f.Feature: continue 5272642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod f.Feature.mapLookups(lookupMap) 5282642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5292642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.DefaultLangSys, 5302642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod otTables.LangSys) 5312642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap): 5322642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.FeatureIndex = [featureMap[i] for i in self.FeatureIndex] 5332642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if self.ReqFeatureIndex != 65535: 5342642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.ReqFeatureIndex = featureMap[self.ReqFeatureIndex] 5352642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5362642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Script) 5372642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap): 5382642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if self.DefaultLangSys: 5392642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self.DefaultLangSys.mapFeatures(featureMap) 5402642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for l in self.LangSysRecord: 5412642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not l or not l.LangSys: continue 5422642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod l.LangSys.mapFeatures(featureMap) 5432642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5442642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.ScriptList) 545398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahboddef mapFeatures(self, featureMap): 5462642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for s in self.ScriptRecord: 5472642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod if not s or not s.Script: continue 5482642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod s.Script.mapFeatures(featureMap) 5492642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 5502642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 551f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Options(object): 552f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 553f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod class UnknownOptionError(Exception): 554f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod pass 555f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 556f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, **kwargs): 557f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 558f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**kwargs) 559f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 560f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def set(self, **kwargs): 561f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod for k,v in kwargs.items(): 562f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 563f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % k) 564f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, k, v) 565f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 566f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv, ignore_unknown=False): 567f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret = [] 568f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts = {} 569f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for a in argv: 570f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod orig_a = a 571f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not a.startswith('--'): 572f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(a) 573f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 574f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod a = a[2:] 575f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod i = a.find('=') 576f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = '=' 577f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if i == -1: 578f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if a.startswith("no-"): 579f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[3:] 580f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = False 581f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 582f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a 583f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = True 584f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 585f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = a[:i] 586f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if k[-1] in "-+": 587f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod op = k[-1]+'=' # Ops is '-=' or '+=' now. 588f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k[:-1] 589f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = a[i+1:] 590f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod k = k.replace('-', '_') 591f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not hasattr(self, k): 592f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if ignore_unknown == True or k in ignore_unknown: 593f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ret.append(orig_a) 594f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod continue 595f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 596f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod raise self.UnknownOptionError("Unknown option '%s'" % a) 597f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 598f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod ov = getattr(self, k) 599f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if isinstance(ov, bool): 600f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = bool(v) 601f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, int): 602f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = int(v) 603f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif isinstance(ov, list): 604f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = v.split(',') 605f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if vv == ['']: 606f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [] 607f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 608f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if op == '=': 609f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = vv 610f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '+=': 611f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 612f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.extend(vv) 613f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod elif op == '-=': 614f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v = ov 615f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for x in vv: 616f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if x in v: 617f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod v.remove(x) 618f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod else: 619f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod assert 0 620f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 621f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod opts[k] = v 622f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.set(**opts) 623f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 624f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return ret 625f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 626f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 6279e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbodclass Merger(object): 62845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 629f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, options=None, log=None): 630f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 631f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not log: 632f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 633f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not options: 634f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 635f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 636f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.options = options 637f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.log = log 63845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 639f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def merge(self, fontfiles): 64045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 64145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = ttLib.TTFont() 64245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 64345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 64445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Settle on a mega glyph order. 64545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # 646f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 64745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrders = [font.getGlyphOrder() for font in fonts] 64845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) 64945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Reload fonts and set new glyph names on them. 65045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # TODO Is it necessary to reload font? I think it is. At least 65145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # it's safer, in case tables were loaded to provide glyph names. 652f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles] 6533235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod for font,glyphOrder in zip(fonts, glyphOrders): 6543235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod font.setGlyphOrder(glyphOrder) 65545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.setGlyphOrder(megaGlyphOrder) 65645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 6572642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for font in fonts: 6582642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self._preMerge(font) 6592642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 660f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod allTags = reduce(set.union, (list(font.keys()) for font in fonts), set()) 66145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod allTags.remove('GlyphOrder') 66245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for tag in allTags: 663f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 66445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod clazz = ttLib.getTableClass(tag) 66545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 6662642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod tables = [font.get(tag, NotImplemented) for font in fonts] 66792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod table = clazz(tag).merge(self, tables) 66892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if table is not NotImplemented and table is not False: 66965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod mega[tag] = table 670b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log("Merged '%s'." % tag) 67165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod else: 6729e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod self.log("Dropped '%s'." % tag) 673b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod self.log.lapse("merge '%s'" % tag) 67445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 6752642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod self._postMerge(mega) 6762642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 67745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 67845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 67945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod def _mergeGlyphOrders(self, glyphOrders): 680c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod """Modifies passed-in glyphOrders to reflect new glyph names. 681c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod Returns glyphOrder for the merged font.""" 68245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod # Simply append font index to the glyph name for now. 683c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod # TODO Even this simplistic numbering can result in conflicts. 684c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod # But then again, we have to improve this soon anyway. 68545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega = [] 68645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for n,glyphOrder in enumerate(glyphOrders): 68745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod for i,glyphName in enumerate(glyphOrder): 688f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod glyphName += "#" + repr(n) 68945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod glyphOrder[i] = glyphName 69045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod mega.append(glyphName) 69145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod return mega 69245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 6936baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod def mergeObjects(self, returnTable, logic, tables): 69492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod # Right now we don't use self at all. Will use in the future 69592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod # for options and logging. 69692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 69792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if logic is NotImplemented: 69892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return NotImplemented 69992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 70092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented)) 70147bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader for key in allKeys: 702e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader try: 7036baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod mergeLogic = logic[key] 704e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader except KeyError: 7059e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod try: 7066baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod mergeLogic = logic['*'] 7079e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod except KeyError: 7086baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod raise Exception("Don't know how to merge key %s of class %s" % 7096baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod (key, returnTable.__class__.__name__)) 71092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if mergeLogic is NotImplemented: 711e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader continue 71292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod value = mergeLogic(getattr(table, key, NotImplemented) for table in tables) 71392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod if value is not NotImplemented: 71492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod setattr(returnTable, key, value) 71592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod 71692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod return returnTable 71747bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader 7182642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod def _preMerge(self, font): 7192642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 7202642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GDEF = font.get('GDEF') 7212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GSUB = font.get('GSUB') 7222642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod GPOS = font.get('GPOS') 7232642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 7242642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod for t in [GSUB, GPOS]: 725398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod if not t: continue 726398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 7275080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if t.table.LookupList: 728b76d6ff14a8283f7057847494c3c5c81ff236a6eBehdad Esfahbod lookupMap = {i:id(v) for i,v in enumerate(t.table.LookupList.Lookup)} 7295080331251906861fbba4a9986efcd04978207beBehdad Esfahbod t.table.LookupList.mapLookups(lookupMap) 7305080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if t.table.FeatureList: 7315080331251906861fbba4a9986efcd04978207beBehdad Esfahbod # XXX Handle present FeatureList but absent LookupList 7325080331251906861fbba4a9986efcd04978207beBehdad Esfahbod t.table.FeatureList.mapLookups(lookupMap) 7332642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 734398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod if t.table.FeatureList and t.table.ScriptList: 735b76d6ff14a8283f7057847494c3c5c81ff236a6eBehdad Esfahbod featureMap = {i:id(v) for i,v in enumerate(t.table.FeatureList.FeatureRecord)} 736398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod t.table.ScriptList.mapFeatures(featureMap) 737398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 738398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod # TODO GDEF/Lookup MarkFilteringSets 7392642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod # TODO FeatureParams nameIDs 7402642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 7412642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod def _postMerge(self, font): 742398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 743398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod GDEF = font.get('GDEF') 744398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod GSUB = font.get('GSUB') 745398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod GPOS = font.get('GPOS') 746398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 747398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod for t in [GSUB, GPOS]: 748398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod if not t: continue 749398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 7505080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if t.table.LookupList: 751b76d6ff14a8283f7057847494c3c5c81ff236a6eBehdad Esfahbod lookupMap = {id(v):i for i,v in enumerate(t.table.LookupList.Lookup)} 7525080331251906861fbba4a9986efcd04978207beBehdad Esfahbod t.table.LookupList.mapLookups(lookupMap) 7535080331251906861fbba4a9986efcd04978207beBehdad Esfahbod if t.table.FeatureList: 7545080331251906861fbba4a9986efcd04978207beBehdad Esfahbod # XXX Handle present FeatureList but absent LookupList 7555080331251906861fbba4a9986efcd04978207beBehdad Esfahbod t.table.FeatureList.mapLookups(lookupMap) 756398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 757398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod if t.table.FeatureList and t.table.ScriptList: 7585080331251906861fbba4a9986efcd04978207beBehdad Esfahbod # XXX Handle present ScriptList but absent FeatureList 759b76d6ff14a8283f7057847494c3c5c81ff236a6eBehdad Esfahbod featureMap = {id(v):i for i,v in enumerate(t.table.FeatureList.FeatureRecord)} 760398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod t.table.ScriptList.mapFeatures(featureMap) 761398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod 762398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod # TODO GDEF/Lookup MarkFilteringSets 763398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod # TODO FeatureParams nameIDs 7642642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod 765f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 766f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Logger(object): 767f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 768f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __init__(self, verbose=False, xml=False, timing=False): 769f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.verbose = verbose 770f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.xml = xml 771f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.timing = timing 772f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = self.start_time = time.time() 773f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 774f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def parse_opts(self, argv): 775f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv = argv[:] 776f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for v in ['verbose', 'xml', 'timing']: 777f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if "--"+v in argv: 778f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod setattr(self, v, True) 779f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod argv.remove("--"+v) 780f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return argv 781f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 782f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def __call__(self, *things): 783f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.verbose: 784f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 785f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print(' '.join(str(x) for x in things)) 786f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 787f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def lapse(self, *things): 788f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.timing: 789f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 790f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod new_time = time.time() 791f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print("Took %0.3fs to %s" %(new_time - self.last_time, 792f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod ' '.join(str(x) for x in things))) 793f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod self.last_time = new_time 794f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 795f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod def font(self, font, file=sys.stdout): 796f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod if not self.xml: 797f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod return 798f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod from fontTools.misc import xmlWriter 799f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer = xmlWriter.XMLWriter(file) 800f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font.disassembleInstructions = False # Work around ttLib bug 801f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod for tag in font.keys(): 802f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.begintag(tag) 803f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 804f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font[tag].toXML(writer, font) 805f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.endtag(tag) 806f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod writer.newline() 807f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 808f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 809f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod__all__ = [ 810f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Options', 811f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Merger', 812f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'Logger', 813f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 'main' 814f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod] 815f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 81645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args): 817f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 818f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod log = Logger() 819f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = log.parse_opts(args) 820f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 821f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod options = Options() 822f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod args = options.parse_opts(args) 823f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 82445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod if len(args) < 1: 825f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod print("usage: pyftmerge font...", file=sys.stderr) 82645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod sys.exit(1) 827f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod 828f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod merger = Merger(options=options, log=log) 829f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod font = merger.merge(args) 83045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod outfile = 'merged.ttf' 83145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod font.save(outfile) 832b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("compile and save font") 833b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod 834b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.last_time = log.start_time 835b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod log.lapse("make one with everything(TOTAL TIME)") 83645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod 83745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__": 83845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod main(sys.argv[1:]) 839