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
610d5fcf498c27bc77ff6203469cc7d622a41dcebbBehdad Esfahboddef onlyExisting(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):
860884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod	lst = [item for item in lst if item is not NotImplemented]
8712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	if not lst:
880884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod		return NotImplemented
890884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod	lst = [item for item in lst if item is not None]
900884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod	if not lst:
910884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod		return None
9212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
9312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	clazz = lst[0].__class__
9412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	assert all(type(item) == clazz for item in lst), lst
950884507c0eedb210bbe691393064083dfc1aa291Behdad Esfahbod
9612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	logic = clazz.mergeMap
9712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	returnTable = clazz()
9882c54638e32a5b7c0f7ad3ac3dafacf7fa27dad4Behdad Esfahbod	returnDict = {}
9912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
10012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	allKeys = set.union(set(), *(vars(table).keys() for table in lst))
10112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	for key in allKeys:
10212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod		try:
10312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod			mergeLogic = logic[key]
10412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod		except KeyError:
10512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod			try:
10612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod				mergeLogic = logic['*']
10712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod			except KeyError:
10812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod				raise Exception("Don't know how to merge key %s of class %s" %
10912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod						(key, clazz.__name__))
11012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod		if mergeLogic is NotImplemented:
11112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod			continue
11212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod		value = mergeLogic(getattr(table, key, NotImplemented) for table in lst)
11312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod		if value is not NotImplemented:
11482c54638e32a5b7c0f7ad3ac3dafacf7fa27dad4Behdad Esfahbod			returnDict[key] = value
11582c54638e32a5b7c0f7ad3ac3dafacf7fa27dad4Behdad Esfahbod
11682c54638e32a5b7c0f7ad3ac3dafacf7fa27dad4Behdad Esfahbod	returnTable.__dict__ = returnDict
11712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
11812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	return returnTable
11912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
120201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahboddef mergeBits(bitmap):
121201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod
122201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod	def wrapper(lst):
123201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod		lst = list(lst)
124201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod		returnValue = 0
125201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod		for bitNumber in range(bitmap['size']):
126642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			try:
127201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod				mergeLogic = bitmap[bitNumber]
128642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			except KeyError:
129201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod				try:
130201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod					mergeLogic = bitmap['*']
131201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod				except KeyError:
132201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod					raise Exception("Don't know how to merge bit %s" % bitNumber)
133201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod			shiftedBit = 1 << bitNumber
134201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod			mergedValue = mergeLogic(bool(item & shiftedBit) for item in lst)
135201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod			returnValue |= mergedValue << bitNumber
136201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod		return returnValue
137201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod
138201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod	return wrapper
139642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
14065f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod
1419e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod@_add_method(DefaultTable, allowDefaultTable=True)
1423b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables):
1439e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	if not hasattr(self, 'mergeMap'):
1449e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod		m.log("Don't know how to merge '%s'." % self.tableTag)
14592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod		return NotImplemented
1469e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
14727c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	logic = self.mergeMap
14827c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod
14927c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	if isinstance(logic, dict):
15027c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod		return m.mergeObjects(self, self.mergeMap, tables)
15127c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	else:
15227c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod		return logic(tables)
15327c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod
15471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod
1559e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('maxp').mergeMap = {
1569e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'*': max,
1579e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
1589e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableVersion': equal,
1599e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'numGlyphs': sum,
16027c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	'maxStorage': first,
16127c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	'maxFunctionDefs': first,
16227c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	'maxInstructionDefs': first,
1639e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	# TODO When we correctly merge hinting data, update these values:
1649e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	# maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
1659e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
1669e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
167b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad EsfahbodheadFlagsMergeBitMap = {
168642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'size': 16,
169642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'*': bitwise_or,
170642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	1: bitwise_and, # Baseline at y = 0
171642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	2: bitwise_and, # lsb at x = 0
172642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	3: bitwise_and, # Force ppem to integer values. FIXME?
173642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	5: bitwise_and, # Font is vertical
174642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	6: lambda bit: 0, # Always set to zero
175642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	11: bitwise_and, # Font data is 'lossless'
176642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	13: bitwise_and, # Optimized for ClearType
177642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	14: bitwise_and, # Last resort font. FIXME? equal or first may be better
178642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	15: lambda bit: 0, # Always set to zero
179642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader}
180642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
1819e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('head').mergeMap = {
1829e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
1839e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableVersion': max,
1849e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'fontRevision': max,
18592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod	'checkSumAdjustment': lambda lst: 0, # We need *something* here
1869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'magicNumber': equal,
187201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod	'flags': mergeBits(headFlagsMergeBitMap),
1889e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'unitsPerEm': equal,
1899e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'created': current_time,
1909e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'modified': current_time,
1919e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'xMin': min,
1929e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'yMin': min,
1939e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'xMax': max,
1949e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'yMax': max,
1959e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'macStyle': first,
1969e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'lowestRecPPEM': max,
1979e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'fontDirectionHint': lambda lst: 2,
1989e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'indexToLocFormat': recalculate,
1999e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'glyphDataFormat': equal,
2009e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
2019e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
2029e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('hhea').mergeMap = {
2039e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'*': equal,
2049e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
2059e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableVersion': max,
2069e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'ascent': max,
2079e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'descent': min,
2089e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'lineGap': max,
2099e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'advanceWidthMax': max,
2109e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'minLeftSideBearing': min,
2119e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'minRightSideBearing': min,
2129e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'xMaxExtent': max,
213642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'caretSlopeRise': first,
214642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'caretSlopeRun': first,
215642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'caretOffset': first,
2169e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'numberOfHMetrics': recalculate,
2179e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
2189e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
219b8039e26530e2a76c3c15c3bd79b31137e3719f2Behdad Esfahbodos2FsTypeMergeBitMap = {
220642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'size': 16,
221642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'*': lambda bit: 0,
222642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	1: bitwise_or, # no embedding permitted
223642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	2: bitwise_and, # allow previewing and printing documents
224642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	3: bitwise_and, # allow editing documents
225642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	8: bitwise_or, # no subsetting permitted
226642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	9: bitwise_or, # no embedding of outlines permitted
227642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader}
228642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
229642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournaderdef mergeOs2FsType(lst):
230642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	lst = list(lst)
231642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	if all(item == 0 for item in lst):
232642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		return 0
233642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
234642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	# Compute least restrictive logic for each fsType value
235642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	for i in range(len(lst)):
236642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# unset bit 1 (no embedding permitted) if either bit 2 or 3 is set
237642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		if lst[i] & 0x000C:
238642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			lst[i] &= ~0x0002
239642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# set bit 2 (allow previewing) if bit 3 is set (allow editing)
240642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		elif lst[i] & 0x0008:
241642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			lst[i] |= 0x0004
242642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# set bits 2 and 3 if everything is allowed
243642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		elif lst[i] == 0:
244642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			lst[i] = 0x000C
245642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
246201a68182159fdf58cf54472ef5f4ea4260984e4Behdad Esfahbod	fsType = mergeBits(os2FsTypeMergeBitMap)(lst)
247642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	# unset bits 2 and 3 if bit 1 is set (some font is "no embedding")
248642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	if fsType & 0x0002:
249642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		fsType &= ~0x000C
250642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	return fsType
251642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
252642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
2539e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('OS/2').mergeMap = {
2549e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'*': first,
2559e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
2569e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'version': max,
25792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod	'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this
258642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'fsType': mergeOs2FsType, # Will be overwritten
259642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	'panose': first, # FIXME: should really be the first Latin font
2609e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'ulUnicodeRange1': bitwise_or,
2619e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'ulUnicodeRange2': bitwise_or,
2629e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'ulUnicodeRange3': bitwise_or,
2639e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'ulUnicodeRange4': bitwise_or,
2649e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'fsFirstCharIndex': min,
2659e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'fsLastCharIndex': max,
2669e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'sTypoAscender': max,
2679e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'sTypoDescender': min,
2689e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'sTypoLineGap': max,
2699e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'usWinAscent': max,
2709e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'usWinDescent': max,
2710e235becc52d6048dde39f3bb400c617877302d7Behdad Esfahbod	# Version 2,3,4
2727765421ab76c79a38a565d7c8b631b2d1a13781cBehdad Esfahbod	'ulCodePageRange1': onlyExisting(bitwise_or),
2737765421ab76c79a38a565d7c8b631b2d1a13781cBehdad Esfahbod	'ulCodePageRange2': onlyExisting(bitwise_or),
2747765421ab76c79a38a565d7c8b631b2d1a13781cBehdad Esfahbod	'usMaxContex': onlyExisting(max),
2759e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	# TODO version 5
2769e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
2779e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
278642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader@_add_method(ttLib.getTableClass('OS/2'))
279642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournaderdef merge(self, m, tables):
280642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	DefaultTable.merge(self, m, tables)
281642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	if self.version < 2:
282642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# bits 8 and 9 are reserved and should be set to zero
283642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		self.fsType &= ~0x0300
284642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	if self.version >= 3:
285642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# Only one of bits 1, 2, and 3 may be set. We already take
286642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# care of bit 1 implications in mergeOs2FsType. So unset
287642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		# bit 2 if bit 3 is already set.
288642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader		if self.fsType & 0x0008:
289642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader			self.fsType &= ~0x0004
290642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader	return self
291642eaf135d7803894c7cf56bdfd4649da9031adeRoozbeh Pournader
2929e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('post').mergeMap = {
2939e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'*': first,
2949e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
2959e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'formatType': max,
2969e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'isFixedPitch': min,
2979e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'minMemType42': max,
2989e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'maxMemType42': lambda lst: 0,
2999e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'minMemType1': max,
3009e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'maxMemType1': lambda lst: 0,
3010d5fcf498c27bc77ff6203469cc7d622a41dcebbBehdad Esfahbod	'mapping': onlyExisting(sumDicts),
302c68c0ff12fcf38d97304540b7dc0253f4142b046Behdad Esfahbod	'extraNames': lambda lst: [],
3039e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
304f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
3059e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = {
3069e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
3079e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'metrics': sumDicts,
3089e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
30965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod
3107a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh PournaderttLib.getTableClass('gasp').mergeMap = {
3117a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader	'tableTag': equal,
3127a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader	'version': max,
3137a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader	'gaspRange': first, # FIXME? Appears irreconcilable
3147a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader}
3157a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader
3167a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh PournaderttLib.getTableClass('name').mergeMap = {
3177a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader	'tableTag': equal,
3187a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader	'names': first, # FIXME? Does mixing name records make sense?
3197a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader}
3207a27214fcb96457a071c8a55b4ff2b59f5a43e58Roozbeh Pournader
3219e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('loca').mergeMap = {
32292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod	'*': recalculate,
3239e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
3249e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
3259e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod
3269e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad EsfahbodttLib.getTableClass('glyf').mergeMap = {
3279e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'tableTag': equal,
3289e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'glyphs': sumDicts,
3299e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod	'glyphOrder': sumLists,
3309e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod}
331f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
332be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('glyf'))
3333b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables):
33427c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod	for i,table in enumerate(tables):
335436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod		for g in table.glyphs.values():
33627c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod			if i:
33727c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod				# Drop hints for all but first font, since
33827c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod				# we don't map functions / CVT values.
33927c71f9f6093fed7668c73b13943f46dac19eecfBehdad Esfahbod				g.removeHinting()
340436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod			# Expand composite glyphs to load their
341436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod			# composite glyph names.
342436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod			if g.isComposite():
343436503372a7a4aeb529a360e66114ce1b1b6d9ceBehdad Esfahbod				g.expand(table)
34492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod	return DefaultTable.merge(self, m, tables)
345f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
34627c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('prep').mergeMap = lambda self, lst: first(lst)
34727c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('fpgm').mergeMap = lambda self, lst: first(lst)
34827c71f9f6093fed7668c73b13943f46dac19eecfBehdad EsfahbodttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst)
34945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
350be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod@_add_method(ttLib.getTableClass('cmap'))
3513b36f55adf2dacf187e60191b32a92703ef77abcBehdad Esfahboddef merge(self, m, tables):
35271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	# TODO Handle format=14.
3538fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	cmapTables = [(t,fontIdx) for fontIdx,table in enumerate(tables) for t in table.tables
354f480c7cf21c51ef67570f1a5fa1c1653fa817bfcBehdad Esfahbod		      if t.isUnicode()]
35571294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	# TODO Better handle format-4 and format-12 coexisting in same font.
35671294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	# TODO Insert both a format-4 and format-12 if needed.
357be4ecc7c67917d95b44d16b32d8d72446162f3ddBehdad Esfahbod	module = ttLib.getTableModule('cmap')
3588fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	assert all(t.format in [4, 12] for t,_ in cmapTables)
3598fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	format = max(t.format for t,_ in cmapTables)
36071294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	cmapTable = module.cmap_classes[format](format)
36171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	cmapTable.cmap = {}
36271294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	cmapTable.platformID = 3
3638fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	cmapTable.platEncID = max(t.platEncID for t,_ in cmapTables)
36471294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	cmapTable.language = 0
3658fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	cmap = cmapTable.cmap
3668fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	for table,fontIdx in cmapTables:
36771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod		# TODO handle duplicates.
3688fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		for uni,gid in table.cmap.items():
3698fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod			oldgid = cmap.get(uni, None)
3708fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod			if oldgid is None:
3718fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				cmap[uni] = gid
3728fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod			elif oldgid != gid:
3738fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				# Char previously mapped to oldgid, now to gid.
3748fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				# Record, to fix up in GSUB 'locl' later.
3758fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				assert m.duplicateGlyphsPerFont[fontIdx].get(oldgid, gid) == gid
3768fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				m.duplicateGlyphsPerFont[fontIdx][oldgid] = gid
37771294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	self.tableVersion = 0
37871294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	self.tables = [cmapTable]
37971294def6730c37839f03dee519b319f982587eaBehdad Esfahbod	self.numSubTables = len(self.tables)
38092fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod	return self
38171294def6730c37839f03dee519b319f982587eaBehdad Esfahbod
382c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod
3832642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.ScriptList.mergeMap = {
3842642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'ScriptCount': sum,
385972af5af639cc036cddd4534c051cc42bbf81672Behdad Esfahbod	'ScriptRecord': lambda lst: sorted(sumLists(lst), key=lambda s: s.ScriptTag),
3862642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod}
3872642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
3882642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.FeatureList.mergeMap = {
3892642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'FeatureCount': sum,
3902642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'FeatureRecord': sumLists,
3912642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod}
3922642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
3932642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.LookupList.mergeMap = {
3942642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'LookupCount': sum,
3952642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'Lookup': sumLists,
3962642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod}
3972642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
39812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.Coverage.mergeMap = {
39912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'glyphs': sumLists,
40012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
40112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
40212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.ClassDef.mergeMap = {
40312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'classDefs': sumDicts,
40412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
40512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
40612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.LigCaretList.mergeMap = {
40712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'Coverage': mergeObjects,
40812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'LigGlyphCount': sum,
40912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'LigGlyph': sumLists,
41012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
411c14ab48ae84ce77bbcc0942baaa18f8099edb447Behdad Esfahbod
41212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.AttachList.mergeMap = {
41312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'Coverage': mergeObjects,
41412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'GlyphCount': sum,
41512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'AttachPoint': sumLists,
41612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
41712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
41812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod# XXX Renumber MarkFilterSets of lookups
41912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.MarkGlyphSetsDef.mergeMap = {
42012dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'MarkSetTableFormat': equal,
42112dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'MarkSetCount': sum,
42212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'Coverage': sumLists,
42312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
42412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
42512dd547c012d4e1295a6c9c2eb48835cd57851caBehdad EsfahbodotTables.GDEF.mergeMap = {
42612dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'*': mergeObjects,
42712dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'Version': max,
42812dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
42912dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod
4302642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodotTables.GSUB.mergeMap = otTables.GPOS.mergeMap = {
4312642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	'*': mergeObjects,
43212dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'Version': max,
4332642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod}
4342642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
4352642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GDEF').mergeMap = \
4362642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GSUB').mergeMap = \
4372642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('GPOS').mergeMap = \
4382642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('BASE').mergeMap = \
4392642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('JSTF').mergeMap = \
4402642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad EsfahbodttLib.getTableClass('MATH').mergeMap = \
4412642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod{
4420d5fcf498c27bc77ff6203469cc7d622a41dcebbBehdad Esfahbod	'tableTag': onlyExisting(equal), # XXX clean me up
44312dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod	'table': mergeObjects,
44412dd547c012d4e1295a6c9c2eb48835cd57851caBehdad Esfahbod}
445f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
4468fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod@_add_method(ttLib.getTableClass('GSUB'))
4478fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahboddef merge(self, m, tables):
4488fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4498fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	assert len(tables) == len(m.duplicateGlyphsPerFont)
4508fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	for i,(table,dups) in enumerate(zip(tables, m.duplicateGlyphsPerFont)):
4518fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		if not dups: continue
4528fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		assert (table is not None and table is not NotImplemented), "Have duplicates to resolve for font %d but no GSUB" % (i + 1)
45314f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod		lookupMap = dict((id(v),v) for v in table.table.LookupList.Lookup)
45414f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod		featureMap = dict((id(v),v) for v in table.table.FeatureList.FeatureRecord)
4558fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		synthFeature = None
4568fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		synthLookup = None
4578fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		for script in table.table.ScriptList.ScriptRecord:
4588fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod			if script.ScriptTag == 'DFLT': continue # XXX
4598fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod			for langsys in [script.Script.DefaultLangSys] + [l.LangSys for l in script.Script.LangSysRecord]:
4608fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				feature = [featureMap[v] for v in langsys.FeatureIndex if featureMap[v].FeatureTag == 'locl']
4618fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				assert len(feature) <= 1
4628fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				if feature:
4638fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					feature = feature[0]
4648fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				else:
4658fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					if not synthFeature:
4668fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						synthFeature = otTables.FeatureRecord()
4678fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						synthFeature.FeatureTag = 'locl'
4688fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						f = synthFeature.Feature = otTables.Feature()
4698fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						f.FeatureParams = None
4708fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						f.LookupCount = 0
4718fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						f.LookupListIndex = []
4728fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						langsys.FeatureIndex.append(id(synthFeature))
4738fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						featureMap[id(synthFeature)] = synthFeature
4748fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						langsys.FeatureIndex.sort(key=lambda v: featureMap[v].FeatureTag)
4758fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						table.table.FeatureList.FeatureRecord.append(synthFeature)
4768fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod						table.table.FeatureList.FeatureCount += 1
4778fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					feature = synthFeature
4788fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4798fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				if not synthLookup:
4808fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					subtable = otTables.SingleSubst()
4818fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					subtable.mapping = dups
4828fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					synthLookup = otTables.Lookup()
4838fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					synthLookup.LookupFlag = 0
4848fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					synthLookup.LookupType = 1
4858fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					synthLookup.SubTableCount = 1
4868fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					synthLookup.SubTable = [subtable]
4878fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					table.table.LookupList.Lookup.append(synthLookup)
4888fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod					table.table.LookupList.LookupCount += 1
4898fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4908fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				feature.Feature.LookupListIndex[:0] = [id(synthLookup)]
4918fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod				feature.Feature.LookupCount += 1
4928fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4938fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4948fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	DefaultTable.merge(self, m, tables)
4958fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod	return self
4968fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4978fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
4982642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
4995080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.SingleSubst,
5005080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.MultipleSubst,
5015080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.AlternateSubst,
5025080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.LigatureSubst,
5035080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ReverseChainSingleSubst,
5045080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.SinglePos,
5055080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.PairPos,
5065080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.CursivePos,
5075080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.MarkBasePos,
5085080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.MarkLigPos,
5095080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.MarkMarkPos)
5105080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap):
5115080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  pass
5125080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5135080331251906861fbba4a9986efcd04978207beBehdad Esfahbod# Copied and trimmed down from subset.py
5145080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.ContextSubst,
5155080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ChainContextSubst,
5165080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ContextPos,
5175080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ChainContextPos)
5185080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef __classify_context(self):
5195080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5205080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  class ContextHelper(object):
5215080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    def __init__(self, klass, Format):
5225080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      if klass.__name__.endswith('Subst'):
5235080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Typ = 'Sub'
5245080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Type = 'Subst'
5255080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      else:
5265080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Typ = 'Pos'
5275080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Type = 'Pos'
5285080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      if klass.__name__.startswith('Chain'):
5295080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Chain = 'Chain'
5305080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      else:
5315080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        Chain = ''
5325080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      ChainTyp = Chain+Typ
5335080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5345080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      self.Typ = Typ
5355080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      self.Type = Type
5365080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      self.Chain = Chain
5375080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      self.ChainTyp = ChainTyp
5385080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5395080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      self.LookupRecord = Type+'LookupRecord'
5405080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5415080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      if Format == 1:
5425080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        self.Rule = ChainTyp+'Rule'
5435080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        self.RuleSet = ChainTyp+'RuleSet'
5445080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      elif Format == 2:
5455080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        self.Rule = ChainTyp+'ClassRule'
5465080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        self.RuleSet = ChainTyp+'ClassSet'
5475080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5485080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  if self.Format not in [1, 2, 3]:
5495080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    return None  # Don't shoot the messenger; let it go
5505080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  if not hasattr(self.__class__, "__ContextHelpers"):
5515080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    self.__class__.__ContextHelpers = {}
5525080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  if self.Format not in self.__class__.__ContextHelpers:
5535080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    helper = ContextHelper(self.__class__, self.Format)
5545080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    self.__class__.__ContextHelpers[self.Format] = helper
5555080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  return self.__class__.__ContextHelpers[self.Format]
5565080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5575080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5585080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.ContextSubst,
5595080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ChainContextSubst,
5605080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ContextPos,
5615080331251906861fbba4a9986efcd04978207beBehdad Esfahbod             otTables.ChainContextPos)
5625080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap):
5635080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  c = self.__classify_context()
5645080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5655080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  if self.Format in [1, 2]:
5665080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    for rs in getattr(self, c.RuleSet):
5675080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      if not rs: continue
5685080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      for r in getattr(rs, c.Rule):
5695080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        if not r: continue
5705080331251906861fbba4a9986efcd04978207beBehdad Esfahbod        for ll in getattr(r, c.LookupRecord):
5715080331251906861fbba4a9986efcd04978207beBehdad Esfahbod          if not ll: continue
5725080331251906861fbba4a9986efcd04978207beBehdad Esfahbod          ll.LookupListIndex = lookupMap[ll.LookupListIndex]
5735080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  elif self.Format == 3:
5745080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    for ll in getattr(self, c.LookupRecord):
5755080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      if not ll: continue
5765080331251906861fbba4a9986efcd04978207beBehdad Esfahbod      ll.LookupListIndex = lookupMap[ll.LookupListIndex]
5775080331251906861fbba4a9986efcd04978207beBehdad Esfahbod  else:
5785080331251906861fbba4a9986efcd04978207beBehdad Esfahbod    assert 0, "unknown format: %s" % self.Format
5795080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5805080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.Lookup)
5815080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap):
5825080331251906861fbba4a9986efcd04978207beBehdad Esfahbod	for st in self.SubTable:
5835080331251906861fbba4a9986efcd04978207beBehdad Esfahbod		if not st: continue
5845080331251906861fbba4a9986efcd04978207beBehdad Esfahbod		st.mapLookups(lookupMap)
5855080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5865080331251906861fbba4a9986efcd04978207beBehdad Esfahbod@_add_method(otTables.LookupList)
5875080331251906861fbba4a9986efcd04978207beBehdad Esfahboddef mapLookups(self, lookupMap):
5885080331251906861fbba4a9986efcd04978207beBehdad Esfahbod	for l in self.Lookup:
5895080331251906861fbba4a9986efcd04978207beBehdad Esfahbod		if not l: continue
5905080331251906861fbba4a9986efcd04978207beBehdad Esfahbod		l.mapLookups(lookupMap)
5915080331251906861fbba4a9986efcd04978207beBehdad Esfahbod
5922642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Feature)
5932642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap):
5942642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	self.LookupListIndex = [lookupMap[i] for i in self.LookupListIndex]
5952642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
5962642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.FeatureList)
5972642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapLookups(self, lookupMap):
5982642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	for f in self.FeatureRecord:
5992642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		if not f or not f.Feature: continue
6002642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		f.Feature.mapLookups(lookupMap)
6012642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
6022642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.DefaultLangSys,
6032642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod             otTables.LangSys)
6042642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap):
6052642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	self.FeatureIndex = [featureMap[i] for i in self.FeatureIndex]
6062642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	if self.ReqFeatureIndex != 65535:
6072642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		self.ReqFeatureIndex = featureMap[self.ReqFeatureIndex]
6082642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
6092642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.Script)
6102642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahboddef mapFeatures(self, featureMap):
6112642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	if self.DefaultLangSys:
6122642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		self.DefaultLangSys.mapFeatures(featureMap)
6132642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	for l in self.LangSysRecord:
6142642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		if not l or not l.LangSys: continue
6152642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		l.LangSys.mapFeatures(featureMap)
6162642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
6172642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod@_add_method(otTables.ScriptList)
618398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahboddef mapFeatures(self, featureMap):
6192642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	for s in self.ScriptRecord:
6202642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		if not s or not s.Script: continue
6212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		s.Script.mapFeatures(featureMap)
6222642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
6232642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
624f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Options(object):
625f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
626f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  class UnknownOptionError(Exception):
627f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    pass
628f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
629f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def __init__(self, **kwargs):
630f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
631f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.set(**kwargs)
632f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
633f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def set(self, **kwargs):
634f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod    for k,v in kwargs.items():
635f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if not hasattr(self, k):
636f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        raise self.UnknownOptionError("Unknown option '%s'" % k)
637f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      setattr(self, k, v)
638f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
639f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def parse_opts(self, argv, ignore_unknown=False):
640f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    ret = []
641f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    opts = {}
642f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    for a in argv:
643f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      orig_a = a
644f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if not a.startswith('--'):
645f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        ret.append(a)
646f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        continue
647f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      a = a[2:]
648f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      i = a.find('=')
649f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      op = '='
650f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if i == -1:
651f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        if a.startswith("no-"):
652f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          k = a[3:]
653f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v = False
654f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        else:
655f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          k = a
656f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v = True
657f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      else:
658f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        k = a[:i]
659f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        if k[-1] in "-+":
660f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          op = k[-1]+'='  # Ops is '-=' or '+=' now.
661f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          k = k[:-1]
662f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        v = a[i+1:]
663f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      k = k.replace('-', '_')
664f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if not hasattr(self, k):
665f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        if ignore_unknown == True or k in ignore_unknown:
666f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          ret.append(orig_a)
667f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          continue
668f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        else:
669f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          raise self.UnknownOptionError("Unknown option '%s'" % a)
670f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
671f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      ov = getattr(self, k)
672f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if isinstance(ov, bool):
673f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        v = bool(v)
674f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      elif isinstance(ov, int):
675f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        v = int(v)
676f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      elif isinstance(ov, list):
677f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        vv = v.split(',')
678f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        if vv == ['']:
679f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          vv = []
680f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
681f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        if op == '=':
682f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v = vv
683f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        elif op == '+=':
684f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v = ov
685f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v.extend(vv)
686f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        elif op == '-=':
687f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          v = ov
688f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          for x in vv:
689f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod            if x in v:
690f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod              v.remove(x)
691f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        else:
692f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod          assert 0
693f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
694f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      opts[k] = v
695f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.set(**opts)
696f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
697f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    return ret
698f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
699f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
7009e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbodclass Merger(object):
70145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
702f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	def __init__(self, options=None, log=None):
703f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
704f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		if not log:
705f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod			log = Logger()
706f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		if not options:
707f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod			options = Options()
708f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
709f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		self.options = options
710f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		self.log = log
71145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
712f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	def merge(self, fontfiles):
71345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
71445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega = ttLib.TTFont()
71545d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
71645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		#
71745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Settle on a mega glyph order.
71845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		#
719f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
72045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		glyphOrders = [font.getGlyphOrder() for font in fonts]
72145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
72245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Reload fonts and set new glyph names on them.
72345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# TODO Is it necessary to reload font?  I think it is.  At least
72445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# it's safer, in case tables were loaded to provide glyph names.
725f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod		fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
7263235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod		for font,glyphOrder in zip(fonts, glyphOrders):
7273235a04ea938845d656a68c242591d37148ea353Behdad Esfahbod			font.setGlyphOrder(glyphOrder)
72845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega.setGlyphOrder(megaGlyphOrder)
72945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
7302642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		for font in fonts:
7312642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod			self._preMerge(font)
7322642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
7338fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		self.duplicateGlyphsPerFont = [{} for f in fonts]
7348fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
735f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod		allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
73645d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		allTags.remove('GlyphOrder')
737d0903e3bb3f0c313532a46d99423ae6f9c327aa5Behdad Esfahbod		allTags.remove('cmap')
738d0903e3bb3f0c313532a46d99423ae6f9c327aa5Behdad Esfahbod		allTags.remove('GSUB')
739d0903e3bb3f0c313532a46d99423ae6f9c327aa5Behdad Esfahbod		allTags = ['cmap', 'GSUB'] + list(allTags)
74045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		for tag in allTags:
741f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
7422642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod			tables = [font.get(tag, NotImplemented) for font in fonts]
7432772d8496e6979f6b7992d35233e2b973d7b86b5Behdad Esfahbod
7442772d8496e6979f6b7992d35233e2b973d7b86b5Behdad Esfahbod			clazz = ttLib.getTableClass(tag)
74592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod			table = clazz(tag).merge(self, tables)
7462772d8496e6979f6b7992d35233e2b973d7b86b5Behdad Esfahbod			# XXX Clean this up and use:  table = mergeObjects(tables)
7472772d8496e6979f6b7992d35233e2b973d7b86b5Behdad Esfahbod
74892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod			if table is not NotImplemented and table is not False:
74965f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod				mega[tag] = table
750b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod				self.log("Merged '%s'." % tag)
75165f19d8440f5fa5d94d1f04593f16cd9f21f176bBehdad Esfahbod			else:
7529e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod				self.log("Dropped '%s'." % tag)
753b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod			self.log.lapse("merge '%s'" % tag)
75445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
7558fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		del self.duplicateGlyphsPerFont
7568fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
7572642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		self._postMerge(mega)
7582642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
75945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		return mega
76045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
76145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	def _mergeGlyphOrders(self, glyphOrders):
762c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod		"""Modifies passed-in glyphOrders to reflect new glyph names.
763c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod		Returns glyphOrder for the merged font."""
76445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		# Simply append font index to the glyph name for now.
765c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod		# TODO Even this simplistic numbering can result in conflicts.
766c2e27fd88f54fcc081049dabce59889cdc87450eBehdad Esfahbod		# But then again, we have to improve this soon anyway.
76745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		mega = []
76845d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		for n,glyphOrder in enumerate(glyphOrders):
76945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod			for i,glyphName in enumerate(glyphOrder):
770f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod				glyphName += "#" + repr(n)
77145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				glyphOrder[i] = glyphName
77245d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod				mega.append(glyphName)
77345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		return mega
77445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
7756baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod	def mergeObjects(self, returnTable, logic, tables):
77692fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod		# Right now we don't use self at all.  Will use in the future
77792fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod		# for options and logging.
77892fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod
77992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod		allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented))
78047bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader		for key in allKeys:
781e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader			try:
7826baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod				mergeLogic = logic[key]
783e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader			except KeyError:
7849e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod				try:
7856baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod					mergeLogic = logic['*']
7869e6adb6bd6b92765f72d0a2acf871405832aa11eBehdad Esfahbod				except KeyError:
7876baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod					raise Exception("Don't know how to merge key %s of class %s" %
7886baf26ea74247a0bed5f0b52d13bc3ef1c931204Behdad Esfahbod							(key, returnTable.__class__.__name__))
78992fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod			if mergeLogic is NotImplemented:
790e219c6c76f116a38e0187b4388d99dd59eff30bcRoozbeh Pournader				continue
79192fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod			value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
79292fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod			if value is not NotImplemented:
79392fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod				setattr(returnTable, key, value)
79492fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod
79592fd5665772996966aaa0709462ebae5a72d4e19Behdad Esfahbod		return returnTable
79647bee9cfbd47dc22895003cc94ab91f9075ca27fRoozbeh Pournader
7972642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	def _preMerge(self, font):
7982642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
7998fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		# Map indices to references
8008fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
8012642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		GDEF = font.get('GDEF')
8022642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		GSUB = font.get('GSUB')
8032642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		GPOS = font.get('GPOS')
8042642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
8052642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		for t in [GSUB, GPOS]:
806398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod			if not t: continue
807398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
8085080331251906861fbba4a9986efcd04978207beBehdad Esfahbod			if t.table.LookupList:
80914f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod				lookupMap = dict((i,id(v)) for i,v in enumerate(t.table.LookupList.Lookup))
8105080331251906861fbba4a9986efcd04978207beBehdad Esfahbod				t.table.LookupList.mapLookups(lookupMap)
8115080331251906861fbba4a9986efcd04978207beBehdad Esfahbod				if t.table.FeatureList:
8125080331251906861fbba4a9986efcd04978207beBehdad Esfahbod					# XXX Handle present FeatureList but absent LookupList
8135080331251906861fbba4a9986efcd04978207beBehdad Esfahbod					t.table.FeatureList.mapLookups(lookupMap)
8142642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
815398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod			if t.table.FeatureList and t.table.ScriptList:
81614f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod				featureMap = dict((i,id(v)) for i,v in enumerate(t.table.FeatureList.FeatureRecord))
817398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod				t.table.ScriptList.mapFeatures(featureMap)
818398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
819398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		# TODO GDEF/Lookup MarkFilteringSets
8202642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod		# TODO FeatureParams nameIDs
8212642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
8222642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod	def _postMerge(self, font):
823398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
8248fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod		# Map references back to indices
8258fec68796d90d217a7e869215810b5179cc28c16Behdad Esfahbod
826398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		GDEF = font.get('GDEF')
827398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		GSUB = font.get('GSUB')
828398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		GPOS = font.get('GPOS')
829398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
830398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		for t in [GSUB, GPOS]:
831398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod			if not t: continue
832398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
8335080331251906861fbba4a9986efcd04978207beBehdad Esfahbod			if t.table.LookupList:
83414f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod				lookupMap = dict((id(v),i) for i,v in enumerate(t.table.LookupList.Lookup))
8355080331251906861fbba4a9986efcd04978207beBehdad Esfahbod				t.table.LookupList.mapLookups(lookupMap)
8365080331251906861fbba4a9986efcd04978207beBehdad Esfahbod				if t.table.FeatureList:
8375080331251906861fbba4a9986efcd04978207beBehdad Esfahbod					# XXX Handle present FeatureList but absent LookupList
8385080331251906861fbba4a9986efcd04978207beBehdad Esfahbod					t.table.FeatureList.mapLookups(lookupMap)
839398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
840398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod			if t.table.FeatureList and t.table.ScriptList:
8415080331251906861fbba4a9986efcd04978207beBehdad Esfahbod				# XXX Handle present ScriptList but absent FeatureList
84214f13a993eda70fd97e415cc9a043d5d2416c187Behdad Esfahbod				featureMap = dict((id(v),i) for i,v in enumerate(t.table.FeatureList.FeatureRecord))
843398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod				t.table.ScriptList.mapFeatures(featureMap)
844398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod
845398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		# TODO GDEF/Lookup MarkFilteringSets
846398770d51c3ae0e7c267ef2b315beae2b6aa2df8Behdad Esfahbod		# TODO FeatureParams nameIDs
8472642934116cc0e3b873e37051cb57d3b29c0a9b4Behdad Esfahbod
848f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
849f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbodclass Logger(object):
850f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
851f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def __init__(self, verbose=False, xml=False, timing=False):
852f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.verbose = verbose
853f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.xml = xml
854f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.timing = timing
855f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.last_time = self.start_time = time.time()
856f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
857f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def parse_opts(self, argv):
858f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    argv = argv[:]
859f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    for v in ['verbose', 'xml', 'timing']:
860f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      if "--"+v in argv:
861f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        setattr(self, v, True)
862f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod        argv.remove("--"+v)
863f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    return argv
864f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
865f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def __call__(self, *things):
866f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    if not self.verbose:
867f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      return
868f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod    print(' '.join(str(x) for x in things))
869f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
870f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def lapse(self, *things):
871f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    if not self.timing:
872f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      return
873f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    new_time = time.time()
874f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod    print("Took %0.3fs to %s" %(new_time - self.last_time,
875f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod                                 ' '.join(str(x) for x in things)))
876f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    self.last_time = new_time
877f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
878f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  def font(self, font, file=sys.stdout):
879f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    if not self.xml:
880f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      return
881f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    from fontTools.misc import xmlWriter
882f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    writer = xmlWriter.XMLWriter(file)
883f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    font.disassembleInstructions = False  # Work around ttLib bug
884f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod    for tag in font.keys():
885f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      writer.begintag(tag)
886f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      writer.newline()
887f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      font[tag].toXML(writer, font)
888f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      writer.endtag(tag)
889f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod      writer.newline()
890f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
891f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
892f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod__all__ = [
893f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  'Options',
894f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  'Merger',
895f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  'Logger',
896f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod  'main'
897f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod]
898f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
89945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahboddef main(args):
900f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
901f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	log = Logger()
902f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	args = log.parse_opts(args)
903f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
904f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	options = Options()
905f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	args = options.parse_opts(args)
906f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
90745d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	if len(args) < 1:
908f63e80e3fc4ef5406109f1fbe2746e26927fdebfBehdad Esfahbod		print("usage: pyftmerge font...", file=sys.stderr)
90945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod		sys.exit(1)
910f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod
911f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	merger = Merger(options=options, log=log)
912f2d5982826530296fd7c8f9e2d2a4dc3e070934dBehdad Esfahbod	font = merger.merge(args)
91345d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	outfile = 'merged.ttf'
91445d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	font.save(outfile)
915b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod	log.lapse("compile and save font")
916b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod
917b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod	log.last_time = log.start_time
918b640f7435c3d29c57310b1a5f7d8e5b538a4c1e3Behdad Esfahbod	log.lapse("make one with everything(TOTAL TIME)")
91945d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod
92045d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbodif __name__ == "__main__":
92145d2f38aa51169f400062f2153a74277b9d4d04eBehdad Esfahbod	main(sys.argv[1:])
922