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