blob: 03c066dd4e1d1a19dab814943f93d463a74d42d6 [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 Esfahbodf63e80e2013-12-18 17:14:26 -050012from functools import reduce
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040013import sys
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040014import time
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040015
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040016
17def _add_method(*clazzes):
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040018 """Returns a decorator function that adds a new method to one or
19 more classes."""
20 def wrapper(method):
21 for clazz in clazzes:
22 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050023 assert not hasattr(clazz, method.__name__), \
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040024 "Oops, class '%s' has method '%s'." % (clazz.__name__,
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050025 method.__name__)
26 setattr(clazz, method.__name__, method)
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040027 return None
28 return wrapper
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040029
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080030# General utility functions for merging values from different fonts
31def assert_equal(lst):
32 first = lst[0]
33 assert all([item == first for item in lst])
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080034 return first
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080035
36def first(lst):
37 return lst[0]
38
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080039def recalculate(lst):
40 # Just return the first value, assume will be recalculated when saved
41 return lst[0]
42
43def current_time(lst):
44 return long(time.time() - _h_e_a_d.mac_epoch_diff)
45
46def bitwise_or(lst):
47 ret = 0
48 for item in lst:
49 ret |= item
50 return ret
51
52def ignore(lst):
53 assert False, "This function should not be called."
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040054
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040055@_add_method(ttLib.getTableClass('maxp'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040056def merge(self, m):
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080057 logic = {
58 '*': max,
59 'tableVersion': assert_equal,
60 'numGlyphs': sum,
61 'maxStorage': max, # FIXME: may need to be changed to sum
62 'maxFunctionDefs': sum,
63 'maxInstructionDefs': sum,
64 }
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040065 # TODO When we correctly merge hinting data, update these values:
66 # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080067 m._mergeKeys(self, logic)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040068 return True
69
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040070@_add_method(ttLib.getTableClass('head'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040071def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080072 logic = {
73 'tableVersion': max,
74 'fontRevision': max,
75 'checkSumAdjustment': recalculate,
76 'magicNumber': assert_equal,
77 'flags': first, # FIXME: replace with bit-sensitive code
78 'unitsPerEm': assert_equal,
79 'created': current_time,
80 'modified': current_time,
81 'xMin': min,
82 'yMin': min,
83 'xMax': max,
84 'yMax': max,
85 'macStyle': first,
86 'lowestRecPPEM': max,
87 'fontDirectionHint': lambda lst: 2,
88 'indexToLocFormat': recalculate,
89 'glyphDataFormat': assert_equal,
90 }
91 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040092 return True
93
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040094@_add_method(ttLib.getTableClass('hhea'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040095def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080096 logic = {
97 '*': assert_equal,
98 'tableVersion': max,
99 'ascent': max,
100 'descent': min,
101 'lineGap': max,
102 'advanceWidthMax': max,
103 'minLeftSideBearing': min,
104 'minRightSideBearing': min,
105 'xMaxExtent': max,
106 'caretSlopeRise': first, # FIXME
107 'caretSlopeRun': first, # FIXME
108 'caretOffset': first, # FIXME
109 'numberOfHMetrics': recalculate,
110 }
111 m._mergeKeys(self, logic)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400112 return True
113
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400114@_add_method(ttLib.getTableClass('OS/2'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400115def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800116 logic = {
117 '*': first,
118 'version': max,
119 'xAvgCharWidth': recalculate,
120 'fsType': first, # FIXME
121 'panose': first, # FIXME?
122 'ulUnicodeRange1': bitwise_or,
123 'ulUnicodeRange2': bitwise_or,
124 'ulUnicodeRange3': bitwise_or,
125 'ulUnicodeRange4': bitwise_or,
126 'fsFirstCharIndex': min,
127 'fsLastCharIndex': max,
128 'sTypoAscender': max,
129 'sTypoDescender': min,
130 'sTypoLineGap': max,
131 'usWinAscent': max,
132 'usWinDescent': max,
133 'ulCodePageRange1': bitwise_or,
134 'ulCodePageRange2': bitwise_or,
135 'usMaxContex': max,
136 }
137 m._mergeKeys(self, logic)
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400138 return True
139
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400140@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400141def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800142 logic = {
143 '*': first,
144 'formatType': max,
145 'isFixedPitch': min,
146 'minMemType42': max,
147 'maxMemType42': lambda lst: 0,
148 'minMemType1': max,
149 'maxMemType1': lambda lst: 0,
150 'mapping': ignore,
151 'extraNames': ignore
152 }
153 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400154 self.mapping = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400155 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400156 if hasattr(table, 'mapping'):
157 self.mapping.update(table.mapping)
158 self.extraNames = []
159 return True
160
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400161@_add_method(ttLib.getTableClass('vmtx'),
162 ttLib.getTableClass('hmtx'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400163def merge(self, m):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400164 self.metrics = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400165 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400166 self.metrics.update(table.metrics)
167 return True
168
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400169@_add_method(ttLib.getTableClass('loca'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400170def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400171 return True # Will be computed automatically
172
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400173@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400174def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400175 self.glyphs = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400176 for table in m.tables:
Behdad Esfahbod43650332013-09-20 16:33:33 -0400177 for g in table.glyphs.values():
178 # Drop hints for now, since we don't remap
179 # functions / CVT values.
180 g.removeHinting()
181 # Expand composite glyphs to load their
182 # composite glyph names.
183 if g.isComposite():
184 g.expand(table)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400185 self.glyphs.update(table.glyphs)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400186 return True
187
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400188@_add_method(ttLib.getTableClass('prep'),
189 ttLib.getTableClass('fpgm'),
190 ttLib.getTableClass('cvt '))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400191def merge(self, m):
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400192 return False # TODO We don't merge hinting data currently.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400193
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400194@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400195def merge(self, m):
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400196 # TODO Handle format=14.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400197 cmapTables = [t for table in m.tables for t in table.tables
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400198 if t.platformID == 3 and t.platEncID in [1, 10]]
199 # TODO Better handle format-4 and format-12 coexisting in same font.
200 # TODO Insert both a format-4 and format-12 if needed.
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400201 module = ttLib.getTableModule('cmap')
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400202 assert all(t.format in [4, 12] for t in cmapTables)
203 format = max(t.format for t in cmapTables)
204 cmapTable = module.cmap_classes[format](format)
205 cmapTable.cmap = {}
206 cmapTable.platformID = 3
207 cmapTable.platEncID = max(t.platEncID for t in cmapTables)
208 cmapTable.language = 0
209 for table in cmapTables:
210 # TODO handle duplicates.
211 cmapTable.cmap.update(table.cmap)
212 self.tableVersion = 0
213 self.tables = [cmapTable]
214 self.numSubTables = len(self.tables)
215 return True
216
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400217@_add_method(ttLib.getTableClass('GDEF'))
218def merge(self, m):
219 self.table = otTables.GDEF()
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400220 self.table.Version = 1.0 # TODO version 1.2...
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400221
222 if any(t.table.LigCaretList for t in m.tables):
223 glyphs = []
224 ligGlyphs = []
225 for table in m.tables:
226 if table.table.LigCaretList:
227 glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
228 ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
229 coverage = otTables.Coverage()
230 coverage.glyphs = glyphs
231 ligCaretList = otTables.LigCaretList()
232 ligCaretList.Coverage = coverage
233 ligCaretList.LigGlyph = ligGlyphs
234 ligCaretList.GlyphCount = len(ligGlyphs)
235 self.table.LigCaretList = ligCaretList
236 else:
237 self.table.LigCaretList = None
238
239 if any(t.table.MarkAttachClassDef for t in m.tables):
240 classDefs = {}
241 for table in m.tables:
242 if table.table.MarkAttachClassDef:
243 classDefs.update(table.table.MarkAttachClassDef.classDefs)
244 self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
245 self.table.MarkAttachClassDef.classDefs = classDefs
246 else:
247 self.table.MarkAttachClassDef = None
248
249 if any(t.table.GlyphClassDef for t in m.tables):
250 classDefs = {}
251 for table in m.tables:
252 if table.table.GlyphClassDef:
253 classDefs.update(table.table.GlyphClassDef.classDefs)
254 self.table.GlyphClassDef = otTables.GlyphClassDef()
255 self.table.GlyphClassDef.classDefs = classDefs
256 else:
257 self.table.GlyphClassDef = None
258
259 if any(t.table.AttachList for t in m.tables):
260 glyphs = []
261 attachPoints = []
262 for table in m.tables:
263 if table.table.AttachList:
264 glyphs.extend(table.table.AttachList.Coverage.glyphs)
265 attachPoints.extend(table.table.AttachList.AttachPoint)
266 coverage = otTables.Coverage()
267 coverage.glyphs = glyphs
268 attachList = otTables.AttachList()
269 attachList.Coverage = coverage
270 attachList.AttachPoint = attachPoints
271 attachList.GlyphCount = len(attachPoints)
272 self.table.AttachList = attachList
273 else:
274 self.table.AttachList = None
275
276 return True
277
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400278
279class Options(object):
280
281 class UnknownOptionError(Exception):
282 pass
283
284 _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
285 drop_tables = _drop_tables_default
286
287 def __init__(self, **kwargs):
288
289 self.set(**kwargs)
290
291 def set(self, **kwargs):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500292 for k,v in kwargs.items():
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400293 if not hasattr(self, k):
294 raise self.UnknownOptionError("Unknown option '%s'" % k)
295 setattr(self, k, v)
296
297 def parse_opts(self, argv, ignore_unknown=False):
298 ret = []
299 opts = {}
300 for a in argv:
301 orig_a = a
302 if not a.startswith('--'):
303 ret.append(a)
304 continue
305 a = a[2:]
306 i = a.find('=')
307 op = '='
308 if i == -1:
309 if a.startswith("no-"):
310 k = a[3:]
311 v = False
312 else:
313 k = a
314 v = True
315 else:
316 k = a[:i]
317 if k[-1] in "-+":
318 op = k[-1]+'=' # Ops is '-=' or '+=' now.
319 k = k[:-1]
320 v = a[i+1:]
321 k = k.replace('-', '_')
322 if not hasattr(self, k):
323 if ignore_unknown == True or k in ignore_unknown:
324 ret.append(orig_a)
325 continue
326 else:
327 raise self.UnknownOptionError("Unknown option '%s'" % a)
328
329 ov = getattr(self, k)
330 if isinstance(ov, bool):
331 v = bool(v)
332 elif isinstance(ov, int):
333 v = int(v)
334 elif isinstance(ov, list):
335 vv = v.split(',')
336 if vv == ['']:
337 vv = []
338 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
339 if op == '=':
340 v = vv
341 elif op == '+=':
342 v = ov
343 v.extend(vv)
344 elif op == '-=':
345 v = ov
346 for x in vv:
347 if x in v:
348 v.remove(x)
349 else:
350 assert 0
351
352 opts[k] = v
353 self.set(**opts)
354
355 return ret
356
357
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400358class Merger:
359
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400360 def __init__(self, options=None, log=None):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400361
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400362 if not log:
363 log = Logger()
364 if not options:
365 options = Options()
366
367 self.options = options
368 self.log = log
369
370 def merge(self, fontfiles):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400371
372 mega = ttLib.TTFont()
373
374 #
375 # Settle on a mega glyph order.
376 #
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400377 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400378 glyphOrders = [font.getGlyphOrder() for font in fonts]
379 megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
380 # Reload fonts and set new glyph names on them.
381 # TODO Is it necessary to reload font? I think it is. At least
382 # it's safer, in case tables were loaded to provide glyph names.
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400383 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod3235a042013-09-19 20:57:33 -0400384 for font,glyphOrder in zip(fonts, glyphOrders):
385 font.setGlyphOrder(glyphOrder)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400386 mega.setGlyphOrder(megaGlyphOrder)
387
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500388 allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400389 allTags.remove('GlyphOrder')
390 for tag in allTags:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400391
392 if tag in self.options.drop_tables:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400393 self.log("Dropping '%s'." % tag)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400394 continue
395
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400396 clazz = ttLib.getTableClass(tag)
397
398 if not hasattr(clazz, 'merge'):
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400399 self.log("Don't know how to merge '%s', dropped." % tag)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400400 continue
401
402 # TODO For now assume all fonts have the same tables.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400403 self.tables = [font[tag] for font in fonts]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400404 table = clazz(tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400405 if table.merge (self):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400406 mega[tag] = table
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400407 self.log("Merged '%s'." % tag)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400408 else:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400409 self.log("Dropped '%s'. No need to merge explicitly." % tag)
410 self.log.lapse("merge '%s'" % tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400411 del self.tables
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400412
413 return mega
414
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400415 def _mergeGlyphOrders(self, glyphOrders):
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400416 """Modifies passed-in glyphOrders to reflect new glyph names.
417 Returns glyphOrder for the merged font."""
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400418 # Simply append font index to the glyph name for now.
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400419 # TODO Even this simplistic numbering can result in conflicts.
420 # But then again, we have to improve this soon anyway.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400421 mega = []
422 for n,glyphOrder in enumerate(glyphOrders):
423 for i,glyphName in enumerate(glyphOrder):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500424 glyphName += "#" + repr(n)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400425 glyphOrder[i] = glyphName
426 mega.append(glyphName)
427 return mega
428
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800429 def _mergeKeys(self, return_table, logic):
430 logic['tableTag'] = assert_equal
431 allKeys = set.union(set(), *(vars(table).keys() for table in self.tables))
432 for key in allKeys:
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800433 try:
434 merge_logic = logic[key]
435 except KeyError:
436 merge_logic = logic['*']
437 if merge_logic == ignore:
438 continue
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800439 key_value = merge_logic([getattr(table, key) for table in self.tables])
440 setattr(return_table, key, key_value)
441
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400442
443class Logger(object):
444
445 def __init__(self, verbose=False, xml=False, timing=False):
446 self.verbose = verbose
447 self.xml = xml
448 self.timing = timing
449 self.last_time = self.start_time = time.time()
450
451 def parse_opts(self, argv):
452 argv = argv[:]
453 for v in ['verbose', 'xml', 'timing']:
454 if "--"+v in argv:
455 setattr(self, v, True)
456 argv.remove("--"+v)
457 return argv
458
459 def __call__(self, *things):
460 if not self.verbose:
461 return
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500462 print(' '.join(str(x) for x in things))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400463
464 def lapse(self, *things):
465 if not self.timing:
466 return
467 new_time = time.time()
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500468 print("Took %0.3fs to %s" %(new_time - self.last_time,
469 ' '.join(str(x) for x in things)))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400470 self.last_time = new_time
471
472 def font(self, font, file=sys.stdout):
473 if not self.xml:
474 return
475 from fontTools.misc import xmlWriter
476 writer = xmlWriter.XMLWriter(file)
477 font.disassembleInstructions = False # Work around ttLib bug
478 for tag in font.keys():
479 writer.begintag(tag)
480 writer.newline()
481 font[tag].toXML(writer, font)
482 writer.endtag(tag)
483 writer.newline()
484
485
486__all__ = [
487 'Options',
488 'Merger',
489 'Logger',
490 'main'
491]
492
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400493def main(args):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400494
495 log = Logger()
496 args = log.parse_opts(args)
497
498 options = Options()
499 args = options.parse_opts(args)
500
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400501 if len(args) < 1:
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500502 print("usage: pyftmerge font...", file=sys.stderr)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400503 sys.exit(1)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400504
505 merger = Merger(options=options, log=log)
506 font = merger.merge(args)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400507 outfile = 'merged.ttf'
508 font.save(outfile)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400509 log.lapse("compile and save font")
510
511 log.last_time = log.start_time
512 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400513
514if __name__ == "__main__":
515 main(sys.argv[1:])