[merge] Use NotImplemented as a singleton meaning "doesn't have"

And cleanup recalculate, so we don't accidentally mess something
that is NOT recalculated.
This commit is contained in:
Behdad Esfahbod 2013-12-19 04:56:50 -05:00
parent 6baf26ea74
commit 92fd566577

View File

@ -31,6 +31,7 @@ def _add_method(*clazzes, **kwargs):
return wrapper
# General utility functions for merging values from different fonts
def equal(lst):
t = iter(lst)
first = next(t)
@ -41,8 +42,7 @@ def first(lst):
return next(iter(lst))
def recalculate(lst):
# Just return the first value, assume will be recalculated when saved
return first(lst)
return NotImplemented
def current_time(lst):
return int(time.time() - _h_e_a_d.mac_epoch_diff)
@ -50,14 +50,15 @@ def current_time(lst):
def bitwise_or(lst):
return reduce(operator.or_, lst)
def ignore(lst):
assert False, "This function should not be called."
def avg_int(lst):
lst = list(lst)
return sum(lst) // len(lst)
def maybenone(func):
def nonnone(func):
"""Returns a filter func that when called with a list,
only calls func on the non-None items of the list, and
only so if there's at least one non-None item in the
list."""
list. Otherwise returns None."""
def wrapper(lst):
items = [item for item in lst if item is not None]
@ -65,6 +66,18 @@ def maybenone(func):
return wrapper
def implemented(func):
"""Returns a filter func that when called with a list,
only calls func on the non-NotImplemented items of the list,
and only so if there's at least one item remaining.
Otherwise returns NotImplemented."""
def wrapper(lst):
items = [item for item in lst if item is not NotImplemented]
return func(items) if items else NotImplemented
return wrapper
def sumLists(lst):
l = []
for item in lst:
@ -82,10 +95,9 @@ def sumDicts(lst):
def merge(self, m, tables):
if not hasattr(self, 'mergeMap'):
m.log("Don't know how to merge '%s'." % self.tableTag)
return False
return NotImplemented
m.mergeObjects(self, self.mergeMap, tables)
return True
return m.mergeObjects(self, self.mergeMap, tables)
ttLib.getTableClass('maxp').mergeMap = {
'*': max,
@ -103,7 +115,7 @@ ttLib.getTableClass('head').mergeMap = {
'tableTag': equal,
'tableVersion': max,
'fontRevision': max,
'checkSumAdjustment': recalculate,
'checkSumAdjustment': lambda lst: 0, # We need *something* here
'magicNumber': equal,
'flags': first, # FIXME: replace with bit-sensitive code
'unitsPerEm': equal,
@ -141,7 +153,7 @@ ttLib.getTableClass('OS/2').mergeMap = {
'*': first,
'tableTag': equal,
'version': max,
'xAvgCharWidth': recalculate,
'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this
'fsType': first, # FIXME
'panose': first, # FIXME?
'ulUnicodeRange1': bitwise_or,
@ -170,18 +182,9 @@ ttLib.getTableClass('post').mergeMap = {
'maxMemType42': lambda lst: 0,
'minMemType1': max,
'maxMemType1': lambda lst: 0,
'mapping': ignore,
'extraNames': ignore,
'mapping': implemented(sumDicts),
'extraNames': lambda lst: [][:],
}
@_add_method(ttLib.getTableClass('post'))
def merge(self, m, tables):
DefaultTable.merge(self, m, tables)
self.mapping = {}
for table in tables:
if hasattr(table, 'mapping'):
self.mapping.update(table.mapping)
self.extraNames = []
return True
ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = {
'tableTag': equal,
@ -189,7 +192,7 @@ ttLib.getTableClass('vmtx').mergeMap = ttLib.getTableClass('hmtx').mergeMap = {
}
ttLib.getTableClass('loca').mergeMap = {
'*': ignore,
'*': recalculate,
'tableTag': equal,
}
@ -210,14 +213,11 @@ def merge(self, m, tables):
# composite glyph names.
if g.isComposite():
g.expand(table)
DefaultTable.merge(self, m, tables)
return True
return DefaultTable.merge(self, m, tables)
@_add_method(ttLib.getTableClass('prep'),
ttLib.getTableClass('fpgm'),
ttLib.getTableClass('cvt '))
def merge(self, m):
return False # TODO We don't merge hinting data currently.
ttLib.getTableClass('prep').mergeMap = NotImplemented
ttLib.getTableClass('fpgm').mergeMap = NotImplemented
ttLib.getTableClass('cvt ').mergeMap = NotImplemented
@_add_method(ttLib.getTableClass('cmap'))
def merge(self, m, tables):
@ -240,7 +240,7 @@ def merge(self, m, tables):
self.tableVersion = 0
self.tables = [cmapTable]
self.numSubTables = len(self.tables)
return True
return self
@_add_method(ttLib.getTableClass('GDEF'))
def merge(self, m, tables):
@ -301,7 +301,7 @@ def merge(self, m, tables):
else:
self.table.AttachList = None
return True
return self
class Options(object):
@ -309,9 +309,6 @@ class Options(object):
class UnknownOptionError(Exception):
pass
_drop_tables_default = ['fpgm', 'prep', 'cvt ', 'gasp']
drop_tables = _drop_tables_default
def __init__(self, **kwargs):
self.set(**kwargs)
@ -417,16 +414,11 @@ class Merger(object):
allTags.remove('GlyphOrder')
for tag in allTags:
if tag in self.options.drop_tables:
self.log("Dropping '%s'." % tag)
continue
clazz = ttLib.getTableClass(tag)
# TODO For now assume all fonts have the same tables.
tables = [font[tag] for font in fonts]
table = clazz(tag)
if table.merge (self, tables):
tables = [font[tag] if font.has_key(tag) else NotImplemented for font in fonts]
table = clazz(tag).merge(self, tables)
if table is not NotImplemented and table is not False:
mega[tag] = table
self.log("Merged '%s'." % tag)
else:
@ -450,7 +442,13 @@ class Merger(object):
return mega
def mergeObjects(self, returnTable, logic, tables):
allKeys = set.union(set(), *(vars(table).keys() for table in tables))
# Right now we don't use self at all. Will use in the future
# for options and logging.
if logic is NotImplemented:
return NotImplemented
allKeys = set.union(set(), *(vars(table).keys() for table in tables if table is not NotImplemented))
for key in allKeys:
try:
mergeLogic = logic[key]
@ -460,10 +458,13 @@ class Merger(object):
except KeyError:
raise Exception("Don't know how to merge key %s of class %s" %
(key, returnTable.__class__.__name__))
if mergeLogic == ignore:
if mergeLogic is NotImplemented:
continue
key_value = mergeLogic(getattr(table, key) for table in tables)
setattr(returnTable, key, key_value)
value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
if value is not NotImplemented:
setattr(returnTable, key, value)
return returnTable
class Logger(object):