blob: fe5a7f59c0a6ab0fe89039b5c42063b614027bb7 [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 Esfahboddb2410a2013-12-19 03:30:29 -050058 'tableTag': equal,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050059 'tableVersion': equal,
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -080060 '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 = {
Behdad Esfahboddb2410a2013-12-19 03:30:29 -050073 'tableTag': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080074 'tableVersion': max,
75 'fontRevision': max,
76 'checkSumAdjustment': recalculate,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050077 'magicNumber': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080078 'flags': first, # FIXME: replace with bit-sensitive code
Behdad Esfahbod49028b32013-12-18 17:34:17 -050079 'unitsPerEm': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080080 'created': current_time,
81 'modified': current_time,
82 'xMin': min,
83 'yMin': min,
84 'xMax': max,
85 'yMax': max,
86 'macStyle': first,
87 'lowestRecPPEM': max,
88 'fontDirectionHint': lambda lst: 2,
89 'indexToLocFormat': recalculate,
Behdad Esfahbod49028b32013-12-18 17:34:17 -050090 'glyphDataFormat': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080091 }
92 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -040093 return True
94
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -040095@_add_method(ttLib.getTableClass('hhea'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -040096def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -080097 logic = {
Behdad Esfahbod49028b32013-12-18 17:34:17 -050098 '*': equal,
Behdad Esfahboddb2410a2013-12-19 03:30:29 -050099 'tableTag': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800100 'tableVersion': max,
101 'ascent': max,
102 'descent': min,
103 'lineGap': max,
104 'advanceWidthMax': max,
105 'minLeftSideBearing': min,
106 'minRightSideBearing': min,
107 'xMaxExtent': max,
108 'caretSlopeRise': first, # FIXME
109 'caretSlopeRun': first, # FIXME
110 'caretOffset': first, # FIXME
111 'numberOfHMetrics': recalculate,
112 }
113 m._mergeKeys(self, logic)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400114 return True
115
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400116@_add_method(ttLib.getTableClass('OS/2'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400117def merge(self, m):
Behdad Esfahboddb2410a2013-12-19 03:30:29 -0500118 # TODO version 5
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800119 logic = {
120 '*': first,
Behdad Esfahboddb2410a2013-12-19 03:30:29 -0500121 'tableTag': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800122 'version': max,
123 'xAvgCharWidth': recalculate,
124 'fsType': first, # FIXME
125 'panose': first, # FIXME?
126 'ulUnicodeRange1': bitwise_or,
127 'ulUnicodeRange2': bitwise_or,
128 'ulUnicodeRange3': bitwise_or,
129 'ulUnicodeRange4': bitwise_or,
130 'fsFirstCharIndex': min,
131 'fsLastCharIndex': max,
132 'sTypoAscender': max,
133 'sTypoDescender': min,
134 'sTypoLineGap': max,
135 'usWinAscent': max,
136 'usWinDescent': max,
137 'ulCodePageRange1': bitwise_or,
138 'ulCodePageRange2': bitwise_or,
139 'usMaxContex': max,
140 }
141 m._mergeKeys(self, logic)
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400142 return True
143
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400144@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400145def merge(self, m):
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800146 logic = {
147 '*': first,
Behdad Esfahboddb2410a2013-12-19 03:30:29 -0500148 'tableTag': equal,
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800149 'formatType': max,
150 'isFixedPitch': min,
151 'minMemType42': max,
152 'maxMemType42': lambda lst: 0,
153 'minMemType1': max,
154 'maxMemType1': lambda lst: 0,
155 'mapping': ignore,
156 'extraNames': ignore
157 }
158 m._mergeKeys(self, logic)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400159 self.mapping = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400160 for table in m.tables:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400161 if hasattr(table, 'mapping'):
162 self.mapping.update(table.mapping)
163 self.extraNames = []
164 return True
165
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400166@_add_method(ttLib.getTableClass('vmtx'),
167 ttLib.getTableClass('hmtx'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400168def merge(self, m):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400169 self.metrics = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400170 for table in m.tables:
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400171 self.metrics.update(table.metrics)
172 return True
173
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400174@_add_method(ttLib.getTableClass('loca'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400175def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400176 return True # Will be computed automatically
177
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400178@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400179def merge(self, m):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400180 self.glyphs = {}
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400181 for table in m.tables:
Behdad Esfahbod43650332013-09-20 16:33:33 -0400182 for g in table.glyphs.values():
183 # Drop hints for now, since we don't remap
184 # functions / CVT values.
185 g.removeHinting()
186 # Expand composite glyphs to load their
187 # composite glyph names.
188 if g.isComposite():
189 g.expand(table)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400190 self.glyphs.update(table.glyphs)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400191 return True
192
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400193@_add_method(ttLib.getTableClass('prep'),
194 ttLib.getTableClass('fpgm'),
195 ttLib.getTableClass('cvt '))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400196def merge(self, m):
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400197 return False # TODO We don't merge hinting data currently.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400198
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400199@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400200def merge(self, m):
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400201 # TODO Handle format=14.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400202 cmapTables = [t for table in m.tables for t in table.tables
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400203 if t.platformID == 3 and t.platEncID in [1, 10]]
204 # TODO Better handle format-4 and format-12 coexisting in same font.
205 # TODO Insert both a format-4 and format-12 if needed.
Behdad Esfahbodbe4ecc72013-09-19 20:37:01 -0400206 module = ttLib.getTableModule('cmap')
Behdad Esfahbod71294de2013-09-19 19:43:17 -0400207 assert all(t.format in [4, 12] for t in cmapTables)
208 format = max(t.format for t in cmapTables)
209 cmapTable = module.cmap_classes[format](format)
210 cmapTable.cmap = {}
211 cmapTable.platformID = 3
212 cmapTable.platEncID = max(t.platEncID for t in cmapTables)
213 cmapTable.language = 0
214 for table in cmapTables:
215 # TODO handle duplicates.
216 cmapTable.cmap.update(table.cmap)
217 self.tableVersion = 0
218 self.tables = [cmapTable]
219 self.numSubTables = len(self.tables)
220 return True
221
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400222@_add_method(ttLib.getTableClass('GDEF'))
223def merge(self, m):
224 self.table = otTables.GDEF()
Behdad Esfahbod60eb8042013-09-20 16:34:58 -0400225 self.table.Version = 1.0 # TODO version 1.2...
Behdad Esfahbodc14ab482013-09-19 21:22:54 -0400226
227 if any(t.table.LigCaretList for t in m.tables):
228 glyphs = []
229 ligGlyphs = []
230 for table in m.tables:
231 if table.table.LigCaretList:
232 glyphs.extend(table.table.LigCaretList.Coverage.glyphs)
233 ligGlyphs.extend(table.table.LigCaretList.LigGlyph)
234 coverage = otTables.Coverage()
235 coverage.glyphs = glyphs
236 ligCaretList = otTables.LigCaretList()
237 ligCaretList.Coverage = coverage
238 ligCaretList.LigGlyph = ligGlyphs
239 ligCaretList.GlyphCount = len(ligGlyphs)
240 self.table.LigCaretList = ligCaretList
241 else:
242 self.table.LigCaretList = None
243
244 if any(t.table.MarkAttachClassDef for t in m.tables):
245 classDefs = {}
246 for table in m.tables:
247 if table.table.MarkAttachClassDef:
248 classDefs.update(table.table.MarkAttachClassDef.classDefs)
249 self.table.MarkAttachClassDef = otTables.MarkAttachClassDef()
250 self.table.MarkAttachClassDef.classDefs = classDefs
251 else:
252 self.table.MarkAttachClassDef = None
253
254 if any(t.table.GlyphClassDef for t in m.tables):
255 classDefs = {}
256 for table in m.tables:
257 if table.table.GlyphClassDef:
258 classDefs.update(table.table.GlyphClassDef.classDefs)
259 self.table.GlyphClassDef = otTables.GlyphClassDef()
260 self.table.GlyphClassDef.classDefs = classDefs
261 else:
262 self.table.GlyphClassDef = None
263
264 if any(t.table.AttachList for t in m.tables):
265 glyphs = []
266 attachPoints = []
267 for table in m.tables:
268 if table.table.AttachList:
269 glyphs.extend(table.table.AttachList.Coverage.glyphs)
270 attachPoints.extend(table.table.AttachList.AttachPoint)
271 coverage = otTables.Coverage()
272 coverage.glyphs = glyphs
273 attachList = otTables.AttachList()
274 attachList.Coverage = coverage
275 attachList.AttachPoint = attachPoints
276 attachList.GlyphCount = len(attachPoints)
277 self.table.AttachList = attachList
278 else:
279 self.table.AttachList = None
280
281 return True
282
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400283
284class Options(object):
285
286 class UnknownOptionError(Exception):
287 pass
288
289 _drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
290 drop_tables = _drop_tables_default
291
292 def __init__(self, **kwargs):
293
294 self.set(**kwargs)
295
296 def set(self, **kwargs):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500297 for k,v in kwargs.items():
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400298 if not hasattr(self, k):
299 raise self.UnknownOptionError("Unknown option '%s'" % k)
300 setattr(self, k, v)
301
302 def parse_opts(self, argv, ignore_unknown=False):
303 ret = []
304 opts = {}
305 for a in argv:
306 orig_a = a
307 if not a.startswith('--'):
308 ret.append(a)
309 continue
310 a = a[2:]
311 i = a.find('=')
312 op = '='
313 if i == -1:
314 if a.startswith("no-"):
315 k = a[3:]
316 v = False
317 else:
318 k = a
319 v = True
320 else:
321 k = a[:i]
322 if k[-1] in "-+":
323 op = k[-1]+'=' # Ops is '-=' or '+=' now.
324 k = k[:-1]
325 v = a[i+1:]
326 k = k.replace('-', '_')
327 if not hasattr(self, k):
328 if ignore_unknown == True or k in ignore_unknown:
329 ret.append(orig_a)
330 continue
331 else:
332 raise self.UnknownOptionError("Unknown option '%s'" % a)
333
334 ov = getattr(self, k)
335 if isinstance(ov, bool):
336 v = bool(v)
337 elif isinstance(ov, int):
338 v = int(v)
339 elif isinstance(ov, list):
340 vv = v.split(',')
341 if vv == ['']:
342 vv = []
343 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
344 if op == '=':
345 v = vv
346 elif op == '+=':
347 v = ov
348 v.extend(vv)
349 elif op == '-=':
350 v = ov
351 for x in vv:
352 if x in v:
353 v.remove(x)
354 else:
355 assert 0
356
357 opts[k] = v
358 self.set(**opts)
359
360 return ret
361
362
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400363class Merger:
364
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400365 def __init__(self, options=None, log=None):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400366
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400367 if not log:
368 log = Logger()
369 if not options:
370 options = Options()
371
372 self.options = options
373 self.log = log
374
375 def merge(self, fontfiles):
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400376
377 mega = ttLib.TTFont()
378
379 #
380 # Settle on a mega glyph order.
381 #
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400382 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400383 glyphOrders = [font.getGlyphOrder() for font in fonts]
384 megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
385 # Reload fonts and set new glyph names on them.
386 # TODO Is it necessary to reload font? I think it is. At least
387 # it's safer, in case tables were loaded to provide glyph names.
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400388 fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
Behdad Esfahbod3235a042013-09-19 20:57:33 -0400389 for font,glyphOrder in zip(fonts, glyphOrders):
390 font.setGlyphOrder(glyphOrder)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400391 mega.setGlyphOrder(megaGlyphOrder)
392
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500393 allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400394 allTags.remove('GlyphOrder')
395 for tag in allTags:
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400396
397 if tag in self.options.drop_tables:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400398 self.log("Dropping '%s'." % tag)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400399 continue
400
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400401 clazz = ttLib.getTableClass(tag)
402
403 if not hasattr(clazz, 'merge'):
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400404 self.log("Don't know how to merge '%s', dropped." % tag)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400405 continue
406
407 # TODO For now assume all fonts have the same tables.
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400408 self.tables = [font[tag] for font in fonts]
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400409 table = clazz(tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400410 if table.merge (self):
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400411 mega[tag] = table
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400412 self.log("Merged '%s'." % tag)
Behdad Esfahbod65f19d82013-09-18 21:02:41 -0400413 else:
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400414 self.log("Dropped '%s'. No need to merge explicitly." % tag)
415 self.log.lapse("merge '%s'" % tag)
Behdad Esfahbod0bf4f562013-09-19 20:21:04 -0400416 del self.tables
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400417
418 return mega
419
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400420 def _mergeGlyphOrders(self, glyphOrders):
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400421 """Modifies passed-in glyphOrders to reflect new glyph names.
422 Returns glyphOrder for the merged font."""
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400423 # Simply append font index to the glyph name for now.
Behdad Esfahbodc2e27fd2013-09-20 16:25:48 -0400424 # TODO Even this simplistic numbering can result in conflicts.
425 # But then again, we have to improve this soon anyway.
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400426 mega = []
427 for n,glyphOrder in enumerate(glyphOrders):
428 for i,glyphName in enumerate(glyphOrder):
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500429 glyphName += "#" + repr(n)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400430 glyphOrder[i] = glyphName
431 mega.append(glyphName)
432 return mega
433
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800434 def _mergeKeys(self, return_table, logic):
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800435 allKeys = set.union(set(), *(vars(table).keys() for table in self.tables))
436 for key in allKeys:
Roozbeh Pournadere219c6c2013-12-18 12:15:46 -0800437 try:
438 merge_logic = logic[key]
439 except KeyError:
440 merge_logic = logic['*']
441 if merge_logic == ignore:
442 continue
Behdad Esfahboddb2410a2013-12-19 03:30:29 -0500443 key_value = merge_logic(getattr(table, key) for table in self.tables)
Roozbeh Pournader47bee9c2013-12-18 00:45:12 -0800444 setattr(return_table, key, key_value)
445
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400446
447class Logger(object):
448
449 def __init__(self, verbose=False, xml=False, timing=False):
450 self.verbose = verbose
451 self.xml = xml
452 self.timing = timing
453 self.last_time = self.start_time = time.time()
454
455 def parse_opts(self, argv):
456 argv = argv[:]
457 for v in ['verbose', 'xml', 'timing']:
458 if "--"+v in argv:
459 setattr(self, v, True)
460 argv.remove("--"+v)
461 return argv
462
463 def __call__(self, *things):
464 if not self.verbose:
465 return
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500466 print(' '.join(str(x) for x in things))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400467
468 def lapse(self, *things):
469 if not self.timing:
470 return
471 new_time = time.time()
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500472 print("Took %0.3fs to %s" %(new_time - self.last_time,
473 ' '.join(str(x) for x in things)))
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400474 self.last_time = new_time
475
476 def font(self, font, file=sys.stdout):
477 if not self.xml:
478 return
479 from fontTools.misc import xmlWriter
480 writer = xmlWriter.XMLWriter(file)
481 font.disassembleInstructions = False # Work around ttLib bug
482 for tag in font.keys():
483 writer.begintag(tag)
484 writer.newline()
485 font[tag].toXML(writer, font)
486 writer.endtag(tag)
487 writer.newline()
488
489
490__all__ = [
491 'Options',
492 'Merger',
493 'Logger',
494 'main'
495]
496
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400497def main(args):
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400498
499 log = Logger()
500 args = log.parse_opts(args)
501
502 options = Options()
503 args = options.parse_opts(args)
504
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400505 if len(args) < 1:
Behdad Esfahbodf63e80e2013-12-18 17:14:26 -0500506 print("usage: pyftmerge font...", file=sys.stderr)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400507 sys.exit(1)
Behdad Esfahbodf2d59822013-09-19 16:16:39 -0400508
509 merger = Merger(options=options, log=log)
510 font = merger.merge(args)
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400511 outfile = 'merged.ttf'
512 font.save(outfile)
Behdad Esfahbodb640f742013-09-19 20:12:56 -0400513 log.lapse("compile and save font")
514
515 log.last_time = log.start_time
516 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbod45d2f382013-09-18 20:47:53 -0400517
518if __name__ == "__main__":
519 main(sys.argv[1:])