blob: 21c49ad4ba7fc68f90d6e7afc9c47e762c348f15 [file] [log] [blame]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -04001# Copyright 2013 Google, Inc. All Rights Reserved.
2#
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -08003# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
Behdad Esfahbod45d2f382013-09-18 20:47:53 -04004
5"""Font merger.
6"""
7
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -05008from __future__ import print_function, division
9from fontTools.misc.py23 import *
10from fontTools import ttLib, cffLib
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080011from fontTools.ttLib.tables import otTables, _h_e_a_d
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050012from fontTools.ttLib.tables.DefaultTable import DefaultTable
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050013from functools import reduce
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040014import sys
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040015import time
Behdad Esfahbod49028b32013-12-18 17:34:17 -050016import operator
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040017
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040018
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050019def _add_method(*clazzes, **kwargs):
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040020 """Returns a decorator function that adds a new method to one or
21 more classes."""
22 def wrapper(method):
23 for clazz in clazzes:
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050024 if not kwargs.get('allowDefaultTable', False):
25 assert clazz != DefaultTable, 'Oops, table class not found.'
26 assert method.__name__ not in clazz.__dict__, \
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040027 "Oops, class '%s' has method '%s'." % (clazz.__name__,
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050028 method.__name__)
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050029 setattr(clazz, method.__name__, method)
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040030 return None
31 return wrapper
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040032
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080033# General utility functions for merging values from different fonts
Behdad Esfahbod49028b32013-12-18 17:34:17 -050034def equal(lst):
35 t = iter(lst)
36 first = next(t)
37 assert all(item == first for item in t)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080038 return first
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080039
40def first(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050041 return next(iter(lst))
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080042
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080043def recalculate(lst):
44 # Just return the first value, assume will be recalculated when saved
Behdad Esfahbod49028b32013-12-18 17:34:17 -050045 return first(lst)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080046
47def current_time(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050048 return int(time.time() - _h_e_a_d.mac_epoch_diff)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080049
50def bitwise_or(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050051 return reduce(operator.or_, lst)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080052
53def ignore(lst):
54 assert False, "This function should not be called."
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040055
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050056def maybenone(func):
57 """Returns a filter func that when called with a list,
58 only calls func on the non-None items of the list, and
59 only so if there's at least one non-None item in the
60 list."""
61
62 def wrapper(lst):
63 items = [item for item in lst if item is not None]
64 return func(items) if items else None
65
66 return wrapper
67
68def sumLists(lst):
69 l = []
70 for item in lst:
71 l.extend(item)
72 return l
73
74def sumDicts(lst):
75 d = {}
76 for item in lst:
77 d.update(item)
78 return d
79
80
81@_add_method(DefaultTable, allowDefaultTable=True)
Behdad Esfahbod3b36f552013-12-19 04:45:17 -050082def merge(self, m, tables):
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050083 if not hasattr(self, 'mergeMap'):
84 m.log("Don't know how to merge '%s'." % self.tableTag)
85 return False
86
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -050087 m.mergeObjects(self, self.mergeMap, tables)
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -050088 return True
89
90ttLib.getTableClass('maxp').mergeMap = {
91 '*': max,
92 'tableTag': equal,
93 'tableVersion': equal,
94 'numGlyphs': sum,
95 'maxStorage': max, # FIXME: may need to be changed to sum
96 'maxFunctionDefs': sum,
97 'maxInstructionDefs': sum,
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040098 # TODO When we correctly merge hinting data, update these values:
99 # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500100}
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400101
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500102ttLib.getTableClass('head').mergeMap = {
103 'tableTag': equal,
104 'tableVersion': max,
105 'fontRevision': max,
106 'checkSumAdjustment': recalculate,
107 'magicNumber': equal,
108 'flags': first, # FIXME: replace with bit-sensitive code
109 'unitsPerEm': equal,
110 'created': current_time,
111 'modified': current_time,
112 'xMin': min,
113 'yMin': min,
114 'xMax': max,
115 'yMax': max,
116 'macStyle': first,
117 'lowestRecPPEM': max,
118 'fontDirectionHint': lambda lst: 2,
119 'indexToLocFormat': recalculate,
120 'glyphDataFormat': equal,
121}
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400122
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500123ttLib.getTableClass('hhea').mergeMap = {
124 '*': equal,
125 'tableTag': equal,
126 'tableVersion': max,
127 'ascent': max,
128 'descent': min,
129 'lineGap': max,
130 'advanceWidthMax': max,
131 'minLeftSideBearing': min,
132 'minRightSideBearing': min,
133 'xMaxExtent': max,
134 'caretSlopeRise': first, # FIXME
135 'caretSlopeRun': first, # FIXME
136 'caretOffset': first, # FIXME
137 'numberOfHMetrics': recalculate,
138}
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400139
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500140ttLib.getTableClass('OS/2').mergeMap = {
141 '*': first,
142 'tableTag': equal,
143 'version': max,
144 'xAvgCharWidth': recalculate,
145 'fsType': first, # FIXME
146 'panose': first, # FIXME?
147 'ulUnicodeRange1': bitwise_or,
148 'ulUnicodeRange2': bitwise_or,
149 'ulUnicodeRange3': bitwise_or,
150 'ulUnicodeRange4': bitwise_or,
151 'fsFirstCharIndex': min,
152 'fsLastCharIndex': max,
153 'sTypoAscender': max,
154 'sTypoDescender': min,
155 'sTypoLineGap': max,
156 'usWinAscent': max,
157 'usWinDescent': max,
158 'ulCodePageRange1': bitwise_or,
159 'ulCodePageRange2': bitwise_or,
160 'usMaxContex': max,
Behdad Esfahboddb2410a2013-12-19 03:30:29 -0500161 # TODO version 5
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500162}
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400163
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500164ttLib.getTableClass('post').mergeMap = {
165 '*': first,
166 'tableTag': equal,
167 'formatType': max,
168 'isFixedPitch': min,
169 'minMemType42': max,
170 'maxMemType42': lambda lst: 0,
171 'minMemType1': max,
172 'maxMemType1': lambda lst: 0,
173 'mapping': ignore,
174 'extraNames': ignore,
175}
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400176@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500177def merge(self, m, tables):
178 DefaultTable.merge(self, m, tables)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400179 self.mapping = {}
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500180 for table in tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400181 if hasattr(table, 'mapping'):
182 self.mapping.update(table.mapping)
183 self.extraNames = []
184 return True
185
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500186ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = {
187 'tableTag': equal,
188 'metrics': sumDicts,
189}
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400190
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500191ttLib.getTableClass('loca').mergeMap = {
192 '*': ignore,
193 'tableTag': equal,
194}
195
196ttLib.getTableClass('glyf').mergeMap = {
197 'tableTag': equal,
198 'glyphs': sumDicts,
199 'glyphOrder': sumLists,
200}
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400201
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400202@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500203def merge(self, m, tables):
204 for table in tables:
Behdad Esfahbod43650332013-09-20 16:33:33 -0400205 for g in table.glyphs.values():
206 # Drop hints for now, since we don't remap
207 # functions / CVT values.
208 g.removeHinting()
209 # Expand composite glyphs to load their
210 # composite glyph names.
211 if g.isComposite():
212 g.expand(table)
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500213 DefaultTable.merge(self, m, tables)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400214 return True
215
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400216@_add_method(ttLib.getTableClass('prep'),
217 ttLib.getTableClass('fpgm'),
218 ttLib.getTableClass('cvt '))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400219def merge(self, m):
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400220 return False # TODO We don't merge hinting data currently.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400221
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400222@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500223def merge(self, m, tables):
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400224 # TODO Handle format=14.
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500225 cmapTables = [t for table in tables for t in table.tables
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400226 if t.platformID == 3 and t.platEncID in [1, 10]]
227 # TODO Better handle format-4 and format-12 coexisting in same font.
228 # TODO Insert both a format-4 and format-12 if needed.
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400229 module = ttLib.getTableModule('cmap')
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400230 assert all(t.format in [4, 12] for t in cmapTables)
231 format = max(t.format for t in cmapTables)
232 cmapTable = module.cmap_classes[format](format)
233 cmapTable.cmap = {}
234 cmapTable.platformID = 3
235 cmapTable.platEncID = max(t.platEncID for t in cmapTables)
236 cmapTable.language = 0
237 for table in cmapTables:
238 # TODO handle duplicates.
239 cmapTable.cmap.update(table.cmap)
240 self.tableVersion = 0
241 self.tables = [cmapTable]
242 self.numSubTables = len(self.tables)
243 return True
244
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400245@_add_method(ttLib.getTableClass('GDEF'))
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500246def merge(self, m, tables):
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400247 self.table = otTables.GDEF()
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400248 self.table.Version = 1.0 # TODO version 1.2...
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400249
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500250 if any(t.table.LigCaretList for t in tables):
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400251 glyphs = []
252 ligGlyphs = []
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500253 for table in tables:
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400254 if table.table.LigCaretList:
255 glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
256 ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
257 coverage = otTables.Coverage()
258 coverage.glyphs = glyphs
259 ligCaretList = otTables.LigCaretList()
260 ligCaretList.Coverage = coverage
261 ligCaretList.LigGlyph = ligGlyphs
262 ligCaretList.GlyphCount = len(ligGlyphs)
263 self.table.LigCaretList = ligCaretList
264 else:
265 self.table.LigCaretList = None
266
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500267 if any(t.table.MarkAttachClassDef for t in tables):
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400268 classDefs = {}
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500269 for table in tables:
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400270 if table.table.MarkAttachClassDef:
271 classDefs.update(table.table.MarkAttachClassDef.classDefs)
272 self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
273 self.table.MarkAttachClassDef.classDefs = classDefs
274 else:
275 self.table.MarkAttachClassDef = None
276
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500277 if any(t.table.GlyphClassDef for t in tables):
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400278 classDefs = {}
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500279 for table in tables:
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400280 if table.table.GlyphClassDef:
281 classDefs.update(table.table.GlyphClassDef.classDefs)
282 self.table.GlyphClassDef = otTables.GlyphClassDef()
283 self.table.GlyphClassDef.classDefs = classDefs
284 else:
285 self.table.GlyphClassDef = None
286
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500287 if any(t.table.AttachList for t in tables):
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400288 glyphs = []
289 attachPoints = []
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500290 for table in tables:
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400291 if table.table.AttachList:
292 glyphs.extend(table.table.AttachList.Coverage.glyphs)
293 attachPoints.extend(table.table.AttachList.AttachPoint)
294 coverage = otTables.Coverage()
295 coverage.glyphs = glyphs
296 attachList = otTables.AttachList()
297 attachList.Coverage = coverage
298 attachList.AttachPoint = attachPoints
299 attachList.GlyphCount = len(attachPoints)
300 self.table.AttachList = attachList
301 else:
302 self.table.AttachList = None
303
304 return True
305
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400306
307class Options(object):
308
309 class UnknownOptionError(Exception):
310 pass
311
312 _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
313 drop_tables = _drop_tables_default
314
315 def __init__(self, **kwargs):
316
317 self.set(**kwargs)
318
319 def set(self, **kwargs):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500320 for k,v in kwargs.items():
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400321 if not hasattr(self, k):
322 raise self.UnknownOptionError("Unknown option '%s'" % k)
323 setattr(self, k, v)
324
325 def parse_opts(self, argv, ignore_unknown=False):
326 ret = []
327 opts = {}
328 for a in argv:
329 orig_a = a
330 if not a.startswith('--'):
331 ret.append(a)
332 continue
333 a = a[2:]
334 i = a.find('=')
335 op = '='
336 if i == -1:
337 if a.startswith("no-"):
338 k = a[3:]
339 v = False
340 else:
341 k = a
342 v = True
343 else:
344 k = a[:i]
345 if k[-1] in "-+":
346 op = k[-1]+'=' # Ops is '-=' or '+=' now.
347 k = k[:-1]
348 v = a[i+1:]
349 k = k.replace('-', '_')
350 if not hasattr(self, k):
351 if ignore_unknown == True or k in ignore_unknown:
352 ret.append(orig_a)
353 continue
354 else:
355 raise self.UnknownOptionError("Unknown option '%s'" % a)
356
357 ov = getattr(self, k)
358 if isinstance(ov, bool):
359 v = bool(v)
360 elif isinstance(ov, int):
361 v = int(v)
362 elif isinstance(ov, list):
363 vv = v.split(',')
364 if vv == ['']:
365 vv = []
366 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
367 if op == '=':
368 v = vv
369 elif op == '+=':
370 v = ov
371 v.extend(vv)
372 elif op == '-=':
373 v = ov
374 for x in vv:
375 if x in v:
376 v.remove(x)
377 else:
378 assert 0
379
380 opts[k] = v
381 self.set(**opts)
382
383 return ret
384
385
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500386class Merger(object):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400387
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400388 def __init__(self, options=None, log=None):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400389
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400390 if not log:
391 log = Logger()
392 if not options:
393 options = Options()
394
395 self.options = options
396 self.log = log
397
398 def merge(self, fontfiles):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400399
400 mega = ttLib.TTFont()
401
402 #
403 # Settle on a mega glyph order.
404 #
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400405 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400406 glyphOrders = [font.getGlyphOrder() for font in fonts]
407 megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
408 # Reload fonts and set new glyph names on them.
409 # TODO Is it necessary to reload font? I think it is. At least
410 # it's safer, in case tables were loaded to provide glyph names.
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400411 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod3235a042013-09-19 20:57:33 -0400412 for font,glyphOrder in zip(fonts, glyphOrders):
413 font.setGlyphOrder(glyphOrder)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400414 mega.setGlyphOrder(megaGlyphOrder)
415
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500416 allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400417 allTags.remove('GlyphOrder')
418 for tag in allTags:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400419
420 if tag in self.options.drop_tables:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400421 self.log("Dropping '%s'." % tag)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400422 continue
423
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400424 clazz = ttLib.getTableClass(tag)
425
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400426 # TODO For now assume all fonts have the same tables.
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500427 tables = [font[tag] for font in fonts]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400428 table = clazz(tag)
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500429 if table.merge (self, tables):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400430 mega[tag] = table
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400431 self.log("Merged '%s'." % tag)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400432 else:
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500433 self.log("Dropped '%s'." % tag)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400434 self.log.lapse("merge '%s'" % tag)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400435
436 return mega
437
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400438 def _mergeGlyphOrders(self, glyphOrders):
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400439 """Modifies passed-in glyphOrders to reflect new glyph names.
440 Returns glyphOrder for the merged font."""
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400441 # Simply append font index to the glyph name for now.
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400442 # TODO Even this simplistic numbering can result in conflicts.
443 # But then again, we have to improve this soon anyway.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400444 mega = []
445 for n,glyphOrder in enumerate(glyphOrders):
446 for i,glyphName in enumerate(glyphOrder):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500447 glyphName += "#" + repr(n)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400448 glyphOrder[i] = glyphName
449 mega.append(glyphName)
450 return mega
451
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -0500452 def mergeObjects(self, returnTable, logic, tables):
Behdad Esfahbod3b36f552013-12-19 04:45:17 -0500453 allKeys = set.union(set(), *(vars(table).keys() for table in tables))
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800454 for key in allKeys:
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800455 try:
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -0500456 mergeLogic = logic[key]
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800457 except KeyError:
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500458 try:
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -0500459 mergeLogic = logic['*']
Behdad Esfahbod9e6adb62013-12-19 04:20:26 -0500460 except KeyError:
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -0500461 raise Exception("Don't know how to merge key %s of class %s" %
462 (key, returnTable.__class__.__name__))
463 if mergeLogic == ignore:
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800464 continue
Behdad Esfahbod6baf26e2013-12-19 04:47:34 -0500465 key_value = mergeLogic(getattr(table, key) for table in tables)
466 setattr(returnTable, key, key_value)
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800467
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400468
469class Logger(object):
470
471 def __init__(self, verbose=False, xml=False, timing=False):
472 self.verbose = verbose
473 self.xml = xml
474 self.timing = timing
475 self.last_time = self.start_time = time.time()
476
477 def parse_opts(self, argv):
478 argv = argv[:]
479 for v in ['verbose', 'xml', 'timing']:
480 if "--"+v in argv:
481 setattr(self, v, True)
482 argv.remove("--"+v)
483 return argv
484
485 def __call__(self, *things):
486 if not self.verbose:
487 return
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500488 print(' '.join(str(x) for x in things))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400489
490 def lapse(self, *things):
491 if not self.timing:
492 return
493 new_time = time.time()
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500494 print("Took %0.3fs to %s" %(new_time - self.last_time,
495 ' '.join(str(x) for x in things)))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400496 self.last_time = new_time
497
498 def font(self, font, file=sys.stdout):
499 if not self.xml:
500 return
501 from fontTools.misc import xmlWriter
502 writer = xmlWriter.XMLWriter(file)
503 font.disassembleInstructions = False # Work around ttLib bug
504 for tag in font.keys():
505 writer.begintag(tag)
506 writer.newline()
507 font[tag].toXML(writer, font)
508 writer.endtag(tag)
509 writer.newline()
510
511
512__all__ = [
513 'Options',
514 'Merger',
515 'Logger',
516 'main'
517]
518
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400519def main(args):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400520
521 log = Logger()
522 args = log.parse_opts(args)
523
524 options = Options()
525 args = options.parse_opts(args)
526
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400527 if len(args) < 1:
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500528 print("usage: pyftmerge font...", file=sys.stderr)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400529 sys.exit(1)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400530
531 merger = Merger(options=options, log=log)
532 font = merger.merge(args)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400533 outfile = 'merged.ttf'
534 font.save(outfile)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400535 log.lapse("compile and save font")
536
537 log.last_time = log.start_time
538 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400539
540if __name__ == "__main__":
541 main(sys.argv[1:])