blob: a45bde8c0e82134cdc98b1d5a817192cf14d6a73 [file] [log] [blame]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -04001# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
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
11from fontTools.ttLib.tables import otTables
12from 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
30
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040031@_add_method(ttLib.getTableClass('maxp'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040032def merge(self, m):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040033 # TODO When we correctly merge hinting data, update these values:
34 # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
35 # TODO Assumes that all tables have format 1.0; safe assumption.
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050036 allKeys = reduce(set.union, (list(vars(table).keys()) for table in m.tables), set())
Behdad Esfahbod6942b222013-09-20 16:57:28 -040037 for key in allKeys:
Behdad Esfahbod3235a042013-09-19 20:57:33 -040038 setattr(self, key, max(getattr(table, key) for table in m.tables))
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040039 return True
40
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040041@_add_method(ttLib.getTableClass('head'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040042def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040043 # TODO Check that unitsPerEm are the same.
44 # TODO Use bitwise ops for flags, macStyle, fontDirectionHint
45 minMembers = ['xMin', 'yMin']
46 # Negate some members
47 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040048 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040049 setattr(table, key, -getattr(table, key))
50 # Get max over members
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050051 allKeys = reduce(set.union, (list(vars(table).keys()) for table in m.tables), set())
Behdad Esfahbod6942b222013-09-20 16:57:28 -040052 for key in allKeys:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040053 setattr(self, key, max(getattr(table, key) for table in m.tables))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040054 # Negate them back
55 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040056 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040057 setattr(table, key, -getattr(table, key))
58 setattr(self, key, -getattr(self, key))
59 return True
60
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040061@_add_method(ttLib.getTableClass('hhea'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040062def merge(self, m):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040063 # TODO Check that ascent, descent, slope, etc are the same.
64 minMembers = ['descent', 'minLeftSideBearing', 'minRightSideBearing']
65 # Negate some members
66 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040067 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040068 setattr(table, key, -getattr(table, key))
69 # Get max over members
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050070 allKeys = reduce(set.union, (list(vars(table).keys()) for table in m.tables), set())
Behdad Esfahbod6942b222013-09-20 16:57:28 -040071 for key in allKeys:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040072 setattr(self, key, max(getattr(table, key) for table in m.tables))
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040073 # Negate them back
74 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040075 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040076 setattr(table, key, -getattr(table, key))
77 setattr(self, key, -getattr(self, key))
78 return True
79
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040080@_add_method(ttLib.getTableClass('OS/2'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040081def merge(self, m):
Behdad Esfahbod71294de2013-09-19 19:43:17 -040082 # TODO Check that weight/width/subscript/superscript/etc are the same.
83 # TODO Bitwise ops for UnicodeRange/CodePageRange.
84 # TODO Pretty much all fields generated here have bogus values.
85 # Get max over members
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050086 allKeys = reduce(set.union, (list(vars(table).keys()) for table in m.tables), set())
Behdad Esfahbod6942b222013-09-20 16:57:28 -040087 for key in allKeys:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040088 setattr(self, key, max(getattr(table, key) for table in m.tables))
Behdad Esfahbod71294de2013-09-19 19:43:17 -040089 return True
90
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040091@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040092def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040093 # TODO Check that italicAngle, underlinePosition, underlineThickness are the same.
94 minMembers = ['underlinePosition', 'minMemType42', 'minMemType1']
95 # Negate some members
96 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040097 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040098 setattr(table, key, -getattr(table, key))
99 # Get max over members
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500100 allKeys = reduce(set.union, (list(vars(table).keys()) for table in m.tables), set())
Behdad Esfahbod6942b222013-09-20 16:57:28 -0400101 if 'mapping' in allKeys:
102 allKeys.remove('mapping')
103 allKeys.remove('extraNames')
104 for key in allKeys:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400105 setattr(self, key, max(getattr(table, key) for table in m.tables))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400106 # Negate them back
107 for key in minMembers:
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400108 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400109 setattr(table, key, -getattr(table, key))
110 setattr(self, key, -getattr(self, key))
111 self.mapping = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400112 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400113 if hasattr(table, 'mapping'):
114 self.mapping.update(table.mapping)
115 self.extraNames = []
116 return True
117
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400118@_add_method(ttLib.getTableClass('vmtx'),
119 ttLib.getTableClass('hmtx'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400120def merge(self, m):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400121 self.metrics = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400122 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400123 self.metrics.update(table.metrics)
124 return True
125
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400126@_add_method(ttLib.getTableClass('loca'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400127def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400128 return True # Will be computed automatically
129
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400130@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400131def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400132 self.glyphs = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400133 for table in m.tables:
Behdad Esfahbod43650332013-09-20 16:33:33 -0400134 for g in table.glyphs.values():
135 # Drop hints for now, since we don't remap
136 # functions / CVT values.
137 g.removeHinting()
138 # Expand composite glyphs to load their
139 # composite glyph names.
140 if g.isComposite():
141 g.expand(table)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400142 self.glyphs.update(table.glyphs)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400143 return True
144
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400145@_add_method(ttLib.getTableClass('prep'),
146 ttLib.getTableClass('fpgm'),
147 ttLib.getTableClass('cvt '))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400148def merge(self, m):
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400149 return False # TODO We don't merge hinting data currently.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400150
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400151@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400152def merge(self, m):
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400153 # TODO Handle format=14.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400154 cmapTables = [t for table in m.tables for t in table.tables
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400155 if t.platformID == 3 and t.platEncID in [1, 10]]
156 # TODO Better handle format-4 and format-12 coexisting in same font.
157 # TODO Insert both a format-4 and format-12 if needed.
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400158 module = ttLib.getTableModule('cmap')
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400159 assert all(t.format in [4, 12] for t in cmapTables)
160 format = max(t.format for t in cmapTables)
161 cmapTable = module.cmap_classes[format](format)
162 cmapTable.cmap = {}
163 cmapTable.platformID = 3
164 cmapTable.platEncID = max(t.platEncID for t in cmapTables)
165 cmapTable.language = 0
166 for table in cmapTables:
167 # TODO handle duplicates.
168 cmapTable.cmap.update(table.cmap)
169 self.tableVersion = 0
170 self.tables = [cmapTable]
171 self.numSubTables = len(self.tables)
172 return True
173
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400174@_add_method(ttLib.getTableClass('GDEF'))
175def merge(self, m):
176 self.table = otTables.GDEF()
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400177 self.table.Version = 1.0 # TODO version 1.2...
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400178
179 if any(t.table.LigCaretList for t in m.tables):
180 glyphs = []
181 ligGlyphs = []
182 for table in m.tables:
183 if table.table.LigCaretList:
184 glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
185 ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
186 coverage = otTables.Coverage()
187 coverage.glyphs = glyphs
188 ligCaretList = otTables.LigCaretList()
189 ligCaretList.Coverage = coverage
190 ligCaretList.LigGlyph = ligGlyphs
191 ligCaretList.GlyphCount = len(ligGlyphs)
192 self.table.LigCaretList = ligCaretList
193 else:
194 self.table.LigCaretList = None
195
196 if any(t.table.MarkAttachClassDef for t in m.tables):
197 classDefs = {}
198 for table in m.tables:
199 if table.table.MarkAttachClassDef:
200 classDefs.update(table.table.MarkAttachClassDef.classDefs)
201 self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
202 self.table.MarkAttachClassDef.classDefs = classDefs
203 else:
204 self.table.MarkAttachClassDef = None
205
206 if any(t.table.GlyphClassDef for t in m.tables):
207 classDefs = {}
208 for table in m.tables:
209 if table.table.GlyphClassDef:
210 classDefs.update(table.table.GlyphClassDef.classDefs)
211 self.table.GlyphClassDef = otTables.GlyphClassDef()
212 self.table.GlyphClassDef.classDefs = classDefs
213 else:
214 self.table.GlyphClassDef = None
215
216 if any(t.table.AttachList for t in m.tables):
217 glyphs = []
218 attachPoints = []
219 for table in m.tables:
220 if table.table.AttachList:
221 glyphs.extend(table.table.AttachList.Coverage.glyphs)
222 attachPoints.extend(table.table.AttachList.AttachPoint)
223 coverage = otTables.Coverage()
224 coverage.glyphs = glyphs
225 attachList = otTables.AttachList()
226 attachList.Coverage = coverage
227 attachList.AttachPoint = attachPoints
228 attachList.GlyphCount = len(attachPoints)
229 self.table.AttachList = attachList
230 else:
231 self.table.AttachList = None
232
233 return True
234
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400235
236class Options(object):
237
238 class UnknownOptionError(Exception):
239 pass
240
241 _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
242 drop_tables = _drop_tables_default
243
244 def __init__(self, **kwargs):
245
246 self.set(**kwargs)
247
248 def set(self, **kwargs):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500249 for k,v in kwargs.items():
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400250 if not hasattr(self, k):
251 raise self.UnknownOptionError("Unknown option '%s'" % k)
252 setattr(self, k, v)
253
254 def parse_opts(self, argv, ignore_unknown=False):
255 ret = []
256 opts = {}
257 for a in argv:
258 orig_a = a
259 if not a.startswith('--'):
260 ret.append(a)
261 continue
262 a = a[2:]
263 i = a.find('=')
264 op = '='
265 if i == -1:
266 if a.startswith("no-"):
267 k = a[3:]
268 v = False
269 else:
270 k = a
271 v = True
272 else:
273 k = a[:i]
274 if k[-1] in "-+":
275 op = k[-1]+'=' # Ops is '-=' or '+=' now.
276 k = k[:-1]
277 v = a[i+1:]
278 k = k.replace('-', '_')
279 if not hasattr(self, k):
280 if ignore_unknown == True or k in ignore_unknown:
281 ret.append(orig_a)
282 continue
283 else:
284 raise self.UnknownOptionError("Unknown option '%s'" % a)
285
286 ov = getattr(self, k)
287 if isinstance(ov, bool):
288 v = bool(v)
289 elif isinstance(ov, int):
290 v = int(v)
291 elif isinstance(ov, list):
292 vv = v.split(',')
293 if vv == ['']:
294 vv = []
295 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
296 if op == '=':
297 v = vv
298 elif op == '+=':
299 v = ov
300 v.extend(vv)
301 elif op == '-=':
302 v = ov
303 for x in vv:
304 if x in v:
305 v.remove(x)
306 else:
307 assert 0
308
309 opts[k] = v
310 self.set(**opts)
311
312 return ret
313
314
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400315class Merger:
316
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400317 def __init__(self, options=None, log=None):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400318
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400319 if not log:
320 log = Logger()
321 if not options:
322 options = Options()
323
324 self.options = options
325 self.log = log
326
327 def merge(self, fontfiles):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400328
329 mega = ttLib.TTFont()
330
331 #
332 # Settle on a mega glyph order.
333 #
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400334 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400335 glyphOrders = [font.getGlyphOrder() for font in fonts]
336 megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
337 # Reload fonts and set new glyph names on them.
338 # TODO Is it necessary to reload font? I think it is. At least
339 # it's safer, in case tables were loaded to provide glyph names.
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400340 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod3235a042013-09-19 20:57:33 -0400341 for font,glyphOrder in zip(fonts, glyphOrders):
342 font.setGlyphOrder(glyphOrder)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400343 mega.setGlyphOrder(megaGlyphOrder)
344
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500345 allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400346 allTags.remove('GlyphOrder')
347 for tag in allTags:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400348
349 if tag in self.options.drop_tables:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400350 self.log("Dropping '%s'." % tag)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400351 continue
352
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400353 clazz = ttLib.getTableClass(tag)
354
355 if not hasattr(clazz, 'merge'):
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400356 self.log("Don't know how to merge '%s', dropped." % tag)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400357 continue
358
359 # TODO For now assume all fonts have the same tables.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400360 self.tables = [font[tag] for font in fonts]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400361 table = clazz(tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400362 if table.merge (self):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400363 mega[tag] = table
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400364 self.log("Merged '%s'." % tag)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400365 else:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400366 self.log("Dropped '%s'. No need to merge explicitly." % tag)
367 self.log.lapse("merge '%s'" % tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400368 del self.tables
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400369
370 return mega
371
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400372 def _mergeGlyphOrders(self, glyphOrders):
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400373 """Modifies passed-in glyphOrders to reflect new glyph names.
374 Returns glyphOrder for the merged font."""
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400375 # Simply append font index to the glyph name for now.
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400376 # TODO Even this simplistic numbering can result in conflicts.
377 # But then again, we have to improve this soon anyway.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400378 mega = []
379 for n,glyphOrder in enumerate(glyphOrders):
380 for i,glyphName in enumerate(glyphOrder):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500381 glyphName += "#" + repr(n)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400382 glyphOrder[i] = glyphName
383 mega.append(glyphName)
384 return mega
385
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400386
387class Logger(object):
388
389 def __init__(self, verbose=False, xml=False, timing=False):
390 self.verbose = verbose
391 self.xml = xml
392 self.timing = timing
393 self.last_time = self.start_time = time.time()
394
395 def parse_opts(self, argv):
396 argv = argv[:]
397 for v in ['verbose', 'xml', 'timing']:
398 if "--"+v in argv:
399 setattr(self, v, True)
400 argv.remove("--"+v)
401 return argv
402
403 def __call__(self, *things):
404 if not self.verbose:
405 return
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500406 print(' '.join(str(x) for x in things))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400407
408 def lapse(self, *things):
409 if not self.timing:
410 return
411 new_time = time.time()
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500412 print("Took %0.3fs to %s" %(new_time - self.last_time,
413 ' '.join(str(x) for x in things)))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400414 self.last_time = new_time
415
416 def font(self, font, file=sys.stdout):
417 if not self.xml:
418 return
419 from fontTools.misc import xmlWriter
420 writer = xmlWriter.XMLWriter(file)
421 font.disassembleInstructions = False # Work around ttLib bug
422 for tag in font.keys():
423 writer.begintag(tag)
424 writer.newline()
425 font[tag].toXML(writer, font)
426 writer.endtag(tag)
427 writer.newline()
428
429
430__all__ = [
431 'Options',
432 'Merger',
433 'Logger',
434 'main'
435]
436
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400437def main(args):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400438
439 log = Logger()
440 args = log.parse_opts(args)
441
442 options = Options()
443 args = options.parse_opts(args)
444
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400445 if len(args) < 1:
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500446 print("usage: pyftmerge font...", file=sys.stderr)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400447 sys.exit(1)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400448
449 merger = Merger(options=options, log=log)
450 font = merger.merge(args)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400451 outfile = 'merged.ttf'
452 font.save(outfile)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400453 log.lapse("compile and save font")
454
455 log.last_time = log.start_time
456 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400457
458if __name__ == "__main__":
459 main(sys.argv[1:])