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