blob: 6e3fd899740003200ba26bb28932fab9e460e1a7 [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 Esfahbod49028b32013-12-18 17:34:17 -050015import operator
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040016
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040017
18def _add_method(*clazzes):
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040019 """Returns a decorator function that adds a new method to one or
20 more classes."""
21 def wrapper(method):
22 for clazz in clazzes:
23 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050024 assert not hasattr(clazz, method.__name__), \
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040025 "Oops, class '%s' has method '%s'." % (clazz.__name__,
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -050026 method.__name__)
27 setattr(clazz, method.__name__, method)
Behdad Esfahbodc855f3a2013-09-19 20:09:23 -040028 return None
29 return wrapper
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040030
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080031# General utility functions for merging values from different fonts
Behdad Esfahbod49028b32013-12-18 17:34:17 -050032def equal(lst):
33 t = iter(lst)
34 first = next(t)
35 assert all(item == first for item in t)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080036 return first
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080037
38def first(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050039 return next(iter(lst))
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080040
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080041def recalculate(lst):
42 # Just return the first value, assume will be recalculated when saved
Behdad Esfahbod49028b32013-12-18 17:34:17 -050043 return first(lst)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080044
45def current_time(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050046 return int(time.time() - _h_e_a_d.mac_epoch_diff)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080047
48def bitwise_or(lst):
Behdad Esfahbod49028b32013-12-18 17:34:17 -050049 return reduce(operator.or_, lst)
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080050
51def ignore(lst):
52 assert False, "This function should not be called."
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040053
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040054@_add_method(ttLib.getTableClass('maxp'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040055def merge(self, m):
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080056 logic = {
57 '*': max,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050058 'tableVersion': equal,
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080059 'numGlyphs': sum,
60 'maxStorage': max, # FIXME: may need to be changed to sum
61 'maxFunctionDefs': sum,
62 'maxInstructionDefs': sum,
63 }
Behdad Esfahbod45d2f382013-09-18 20:47:53 -040064 # TODO When we correctly merge hinting data, update these values:
65 # maxFunctionDefs, maxInstructionDefs, maxSizeOfInstructions
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080066 m._mergeKeys(self, logic)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -040067 return True
68
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040069@_add_method(ttLib.getTableClass('head'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040070def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080071 logic = {
72 'tableVersion': max,
73 'fontRevision': max,
74 'checkSumAdjustment': recalculate,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050075 'magicNumber': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080076 'flags': first, # FIXME: replace with bit-sensitive code
Behdad Esfahbod49028b32013-12-18 17:34:17 -050077 'unitsPerEm': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080078 'created': current_time,
79 'modified': current_time,
80 'xMin': min,
81 'yMin': min,
82 'xMax': max,
83 'yMax': max,
84 'macStyle': first,
85 'lowestRecPPEM': max,
86 'fontDirectionHint': lambda lst: 2,
87 'indexToLocFormat': recalculate,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050088 'glyphDataFormat': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080089 }
90 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040091 return True
92
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040093@_add_method(ttLib.getTableClass('hhea'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040094def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080095 logic = {
Behdad Esfahbod49028b32013-12-18 17:34:17 -050096 '*': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080097 'tableVersion': max,
98 'ascent': max,
99 'descent': min,
100 'lineGap': max,
101 'advanceWidthMax': max,
102 'minLeftSideBearing': min,
103 'minRightSideBearing': min,
104 'xMaxExtent': max,
105 'caretSlopeRise': first, # FIXME
106 'caretSlopeRun': first, # FIXME
107 'caretOffset': first, # FIXME
108 'numberOfHMetrics': recalculate,
109 }
110 m._mergeKeys(self, logic)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400111 return True
112
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400113@_add_method(ttLib.getTableClass('OS/2'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400114def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800115 logic = {
116 '*': first,
117 'version': max,
118 'xAvgCharWidth': recalculate,
119 'fsType': first, # FIXME
120 'panose': first, # FIXME?
121 'ulUnicodeRange1': bitwise_or,
122 'ulUnicodeRange2': bitwise_or,
123 'ulUnicodeRange3': bitwise_or,
124 'ulUnicodeRange4': bitwise_or,
125 'fsFirstCharIndex': min,
126 'fsLastCharIndex': max,
127 'sTypoAscender': max,
128 'sTypoDescender': min,
129 'sTypoLineGap': max,
130 'usWinAscent': max,
131 'usWinDescent': max,
132 'ulCodePageRange1': bitwise_or,
133 'ulCodePageRange2': bitwise_or,
134 'usMaxContex': max,
135 }
136 m._mergeKeys(self, logic)
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400137 return True
138
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400139@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400140def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800141 logic = {
142 '*': first,
143 'formatType': max,
144 'isFixedPitch': min,
145 'minMemType42': max,
146 'maxMemType42': lambda lst: 0,
147 'minMemType1': max,
148 'maxMemType1': lambda lst: 0,
149 'mapping': ignore,
150 'extraNames': ignore
151 }
152 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400153 self.mapping = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400154 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400155 if hasattr(table, 'mapping'):
156 self.mapping.update(table.mapping)
157 self.extraNames = []
158 return True
159
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400160@_add_method(ttLib.getTableClass('vmtx'),
161 ttLib.getTableClass('hmtx'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400162def merge(self, m):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400163 self.metrics = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400164 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400165 self.metrics.update(table.metrics)
166 return True
167
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400168@_add_method(ttLib.getTableClass('loca'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400169def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400170 return True # Will be computed automatically
171
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400172@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400173def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400174 self.glyphs = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400175 for table in m.tables:
Behdad Esfahbod43650332013-09-20 16:33:33 -0400176 for g in table.glyphs.values():
177 # Drop hints for now, since we don't remap
178 # functions / CVT values.
179 g.removeHinting()
180 # Expand composite glyphs to load their
181 # composite glyph names.
182 if g.isComposite():
183 g.expand(table)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400184 self.glyphs.update(table.glyphs)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400185 return True
186
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400187@_add_method(ttLib.getTableClass('prep'),
188 ttLib.getTableClass('fpgm'),
189 ttLib.getTableClass('cvt '))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400190def merge(self, m):
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400191 return False # TODO We don't merge hinting data currently.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400192
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400193@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400194def merge(self, m):
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400195 # TODO Handle format=14.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400196 cmapTables = [t for table in m.tables for t in table.tables
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400197 if t.platformID == 3 and t.platEncID in [1, 10]]
198 # TODO Better handle format-4 and format-12 coexisting in same font.
199 # TODO Insert both a format-4 and format-12 if needed.
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400200 module = ttLib.getTableModule('cmap')
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400201 assert all(t.format in [4, 12] for t in cmapTables)
202 format = max(t.format for t in cmapTables)
203 cmapTable = module.cmap_classes[format](format)
204 cmapTable.cmap = {}
205 cmapTable.platformID = 3
206 cmapTable.platEncID = max(t.platEncID for t in cmapTables)
207 cmapTable.language = 0
208 for table in cmapTables:
209 # TODO handle duplicates.
210 cmapTable.cmap.update(table.cmap)
211 self.tableVersion = 0
212 self.tables = [cmapTable]
213 self.numSubTables = len(self.tables)
214 return True
215
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400216@_add_method(ttLib.getTableClass('GDEF'))
217def merge(self, m):
218 self.table = otTables.GDEF()
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400219 self.table.Version = 1.0 # TODO version 1.2...
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400220
221 if any(t.table.LigCaretList for t in m.tables):
222 glyphs = []
223 ligGlyphs = []
224 for table in m.tables:
225 if table.table.LigCaretList:
226 glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
227 ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
228 coverage = otTables.Coverage()
229 coverage.glyphs = glyphs
230 ligCaretList = otTables.LigCaretList()
231 ligCaretList.Coverage = coverage
232 ligCaretList.LigGlyph = ligGlyphs
233 ligCaretList.GlyphCount = len(ligGlyphs)
234 self.table.LigCaretList = ligCaretList
235 else:
236 self.table.LigCaretList = None
237
238 if any(t.table.MarkAttachClassDef for t in m.tables):
239 classDefs = {}
240 for table in m.tables:
241 if table.table.MarkAttachClassDef:
242 classDefs.update(table.table.MarkAttachClassDef.classDefs)
243 self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
244 self.table.MarkAttachClassDef.classDefs = classDefs
245 else:
246 self.table.MarkAttachClassDef = None
247
248 if any(t.table.GlyphClassDef for t in m.tables):
249 classDefs = {}
250 for table in m.tables:
251 if table.table.GlyphClassDef:
252 classDefs.update(table.table.GlyphClassDef.classDefs)
253 self.table.GlyphClassDef = otTables.GlyphClassDef()
254 self.table.GlyphClassDef.classDefs = classDefs
255 else:
256 self.table.GlyphClassDef = None
257
258 if any(t.table.AttachList for t in m.tables):
259 glyphs = []
260 attachPoints = []
261 for table in m.tables:
262 if table.table.AttachList:
263 glyphs.extend(table.table.AttachList.Coverage.glyphs)
264 attachPoints.extend(table.table.AttachList.AttachPoint)
265 coverage = otTables.Coverage()
266 coverage.glyphs = glyphs
267 attachList = otTables.AttachList()
268 attachList.Coverage = coverage
269 attachList.AttachPoint = attachPoints
270 attachList.GlyphCount = len(attachPoints)
271 self.table.AttachList = attachList
272 else:
273 self.table.AttachList = None
274
275 return True
276
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400277
278class Options(object):
279
280 class UnknownOptionError(Exception):
281 pass
282
283 _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
284 drop_tables = _drop_tables_default
285
286 def __init__(self, **kwargs):
287
288 self.set(**kwargs)
289
290 def set(self, **kwargs):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500291 for k,v in kwargs.items():
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400292 if not hasattr(self, k):
293 raise self.UnknownOptionError("Unknown option '%s'" % k)
294 setattr(self, k, v)
295
296 def parse_opts(self, argv, ignore_unknown=False):
297 ret = []
298 opts = {}
299 for a in argv:
300 orig_a = a
301 if not a.startswith('--'):
302 ret.append(a)
303 continue
304 a = a[2:]
305 i = a.find('=')
306 op = '='
307 if i == -1:
308 if a.startswith("no-"):
309 k = a[3:]
310 v = False
311 else:
312 k = a
313 v = True
314 else:
315 k = a[:i]
316 if k[-1] in "-+":
317 op = k[-1]+'=' # Ops is '-=' or '+=' now.
318 k = k[:-1]
319 v = a[i+1:]
320 k = k.replace('-', '_')
321 if not hasattr(self, k):
322 if ignore_unknown == True or k in ignore_unknown:
323 ret.append(orig_a)
324 continue
325 else:
326 raise self.UnknownOptionError("Unknown option '%s'" % a)
327
328 ov = getattr(self, k)
329 if isinstance(ov, bool):
330 v = bool(v)
331 elif isinstance(ov, int):
332 v = int(v)
333 elif isinstance(ov, list):
334 vv = v.split(',')
335 if vv == ['']:
336 vv = []
337 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
338 if op == '=':
339 v = vv
340 elif op == '+=':
341 v = ov
342 v.extend(vv)
343 elif op == '-=':
344 v = ov
345 for x in vv:
346 if x in v:
347 v.remove(x)
348 else:
349 assert 0
350
351 opts[k] = v
352 self.set(**opts)
353
354 return ret
355
356
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400357class Merger:
358
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400359 def __init__(self, options=None, log=None):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400360
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400361 if not log:
362 log = Logger()
363 if not options:
364 options = Options()
365
366 self.options = options
367 self.log = log
368
369 def merge(self, fontfiles):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400370
371 mega = ttLib.TTFont()
372
373 #
374 # Settle on a mega glyph order.
375 #
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400376 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400377 glyphOrders = [font.getGlyphOrder() for font in fonts]
378 megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
379 # Reload fonts and set new glyph names on them.
380 # TODO Is it necessary to reload font? I think it is. At least
381 # it's safer, in case tables were loaded to provide glyph names.
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400382 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod3235a042013-09-19 20:57:33 -0400383 for font,glyphOrder in zip(fonts, glyphOrders):
384 font.setGlyphOrder(glyphOrder)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400385 mega.setGlyphOrder(megaGlyphOrder)
386
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500387 allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400388 allTags.remove('GlyphOrder')
389 for tag in allTags:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400390
391 if tag in self.options.drop_tables:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400392 self.log("Dropping '%s'." % tag)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400393 continue
394
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400395 clazz = ttLib.getTableClass(tag)
396
397 if not hasattr(clazz, 'merge'):
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400398 self.log("Don't know how to merge '%s', dropped." % tag)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400399 continue
400
401 # TODO For now assume all fonts have the same tables.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400402 self.tables = [font[tag] for font in fonts]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400403 table = clazz(tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400404 if table.merge (self):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400405 mega[tag] = table
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400406 self.log("Merged '%s'." % tag)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400407 else:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400408 self.log("Dropped '%s'. No need to merge explicitly." % tag)
409 self.log.lapse("merge '%s'" % tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400410 del self.tables
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400411
412 return mega
413
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400414 def _mergeGlyphOrders(self, glyphOrders):
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400415 """Modifies passed-in glyphOrders to reflect new glyph names.
416 Returns glyphOrder for the merged font."""
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400417 # Simply append font index to the glyph name for now.
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400418 # TODO Even this simplistic numbering can result in conflicts.
419 # But then again, we have to improve this soon anyway.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400420 mega = []
421 for n,glyphOrder in enumerate(glyphOrders):
422 for i,glyphName in enumerate(glyphOrder):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500423 glyphName += "#" + repr(n)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400424 glyphOrder[i] = glyphName
425 mega.append(glyphName)
426 return mega
427
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800428 def _mergeKeys(self, return_table, logic):
Behdad Esfahbod49028b32013-12-18 17:34:17 -0500429 logic['tableTag'] = equal
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800430 allKeys = set.union(set(), *(vars(table).keys() for table in self.tables))
431 for key in allKeys:
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800432 try:
433 merge_logic = logic[key]
434 except KeyError:
435 merge_logic = logic['*']
436 if merge_logic == ignore:
437 continue
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800438 key_value = merge_logic([getattr(table, key) for table in self.tables])
439 setattr(return_table, key, key_value)
440
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400441
442class Logger(object):
443
444 def __init__(self, verbose=False, xml=False, timing=False):
445 self.verbose = verbose
446 self.xml = xml
447 self.timing = timing
448 self.last_time = self.start_time = time.time()
449
450 def parse_opts(self, argv):
451 argv = argv[:]
452 for v in ['verbose', 'xml', 'timing']:
453 if "--"+v in argv:
454 setattr(self, v, True)
455 argv.remove("--"+v)
456 return argv
457
458 def __call__(self, *things):
459 if not self.verbose:
460 return
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500461 print(' '.join(str(x) for x in things))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400462
463 def lapse(self, *things):
464 if not self.timing:
465 return
466 new_time = time.time()
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500467 print("Took %0.3fs to %s" %(new_time - self.last_time,
468 ' '.join(str(x) for x in things)))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400469 self.last_time = new_time
470
471 def font(self, font, file=sys.stdout):
472 if not self.xml:
473 return
474 from fontTools.misc import xmlWriter
475 writer = xmlWriter.XMLWriter(file)
476 font.disassembleInstructions = False # Work around ttLib bug
477 for tag in font.keys():
478 writer.begintag(tag)
479 writer.newline()
480 font[tag].toXML(writer, font)
481 writer.endtag(tag)
482 writer.newline()
483
484
485__all__ = [
486 'Options',
487 'Merger',
488 'Logger',
489 'main'
490]
491
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400492def main(args):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400493
494 log = Logger()
495 args = log.parse_opts(args)
496
497 options = Options()
498 args = options.parse_opts(args)
499
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400500 if len(args) < 1:
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500501 print("usage: pyftmerge font...", file=sys.stderr)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400502 sys.exit(1)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400503
504 merger = Merger(options=options, log=log)
505 font = merger.merge(args)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400506 outfile = 'merged.ttf'
507 font.save(outfile)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400508 log.lapse("compile and save font")
509
510 log.last_time = log.start_time
511 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400512
513if __name__ == "__main__":
514 main(sys.argv[1:])