[merge] Implement a few more straightforward tables

This commit is contained in:
Behdad Esfahbod 2013-09-19 16:16:39 -04:00
parent 65f19d8440
commit f2d5982826

View File

@ -6,6 +6,7 @@
""" """
import sys import sys
import time
import fontTools import fontTools
from fontTools import misc, ttLib, cffLib from fontTools import misc, ttLib, cffLib
@ -33,6 +34,25 @@ def merge(self, tables, fonts):
setattr(self, key, max(getattr(table, key) for table in tables)) setattr(self, key, max(getattr(table, key) for table in tables))
return True return True
@_add_method(fontTools.ttLib.getTableClass('head'))
def merge(self, tables, fonts):
# TODO Check that unitsPerEm are the same.
# TODO Use bitwise ops for flags, macStyle, fontDirectionHint
minMembers = ['xMin', 'yMin']
# Negate some members
for key in minMembers:
for table in tables:
setattr(table, key, -getattr(table, key))
# Get max over members
for key in set(sum((vars(table).keys() for table in tables), [])):
setattr(self, key, max(getattr(table, key) for table in tables))
# Negate them back
for key in minMembers:
for table in tables:
setattr(table, key, -getattr(table, key))
setattr(self, key, -getattr(self, key))
return True
@_add_method(fontTools.ttLib.getTableClass('hhea')) @_add_method(fontTools.ttLib.getTableClass('hhea'))
def merge(self, tables, fonts): def merge(self, tables, fonts):
# TODO Check that ascent, descent, slope, etc are the same. # TODO Check that ascent, descent, slope, etc are the same.
@ -51,6 +71,33 @@ def merge(self, tables, fonts):
setattr(self, key, -getattr(self, key)) setattr(self, key, -getattr(self, key))
return True return True
@_add_method(fontTools.ttLib.getTableClass('post'))
def merge(self, tables, fonts):
# TODO Check that italicAngle, underlinePosition, underlineThickness are the same.
minMembers = ['underlinePosition', 'minMemType42', 'minMemType1']
# Negate some members
for key in minMembers:
for table in tables:
setattr(table, key, -getattr(table, key))
# Get max over members
keys = set(sum((vars(table).keys() for table in tables), []))
if 'mapping' in keys:
keys.remove('mapping')
keys.remove('extraNames')
for key in keys:
setattr(self, key, max(getattr(table, key) for table in tables))
# Negate them back
for key in minMembers:
for table in tables:
setattr(table, key, -getattr(table, key))
setattr(self, key, -getattr(self, key))
self.mapping = {}
for table in tables:
if hasattr(table, 'mapping'):
self.mapping.update(table.mapping)
self.extraNames = []
return True
@_add_method(fontTools.ttLib.getTableClass('vmtx'), @_add_method(fontTools.ttLib.getTableClass('vmtx'),
fontTools.ttLib.getTableClass('hmtx')) fontTools.ttLib.getTableClass('hmtx'))
def merge(self, tables, fonts): def merge(self, tables, fonts):
@ -60,28 +107,129 @@ def merge(self, tables, fonts):
return True return True
@_add_method(fontTools.ttLib.getTableClass('loca')) @_add_method(fontTools.ttLib.getTableClass('loca'))
def merge(self, tables, fonts):
return True # Will be computed automatically
@_add_method(fontTools.ttLib.getTableClass('glyf'))
def merge(self, tables, fonts):
self.glyphs = {}
for table in tables:
self.glyphs.update(table.glyphs)
# TODO Drop hints?
return True
@_add_method(fontTools.ttLib.getTableClass('prep'),
fontTools.ttLib.getTableClass('fpgm'),
fontTools.ttLib.getTableClass('cvt '))
def merge(self, tables, fonts): def merge(self, tables, fonts):
return False # Will be computed automatically return False # Will be computed automatically
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)
def set(self, **kwargs):
for k,v in kwargs.iteritems():
if not hasattr(self, k):
raise self.UnknownOptionError("Unknown option '%s'" % k)
setattr(self, k, v)
def parse_opts(self, argv, ignore_unknown=False):
ret = []
opts = {}
for a in argv:
orig_a = a
if not a.startswith('--'):
ret.append(a)
continue
a = a[2:]
i = a.find('=')
op = '='
if i == -1:
if a.startswith("no-"):
k = a[3:]
v = False
else:
k = a
v = True
else:
k = a[:i]
if k[-1] in "-+":
op = k[-1]+'=' # Ops is '-=' or '+=' now.
k = k[:-1]
v = a[i+1:]
k = k.replace('-', '_')
if not hasattr(self, k):
if ignore_unknown == True or k in ignore_unknown:
ret.append(orig_a)
continue
else:
raise self.UnknownOptionError("Unknown option '%s'" % a)
ov = getattr(self, k)
if isinstance(ov, bool):
v = bool(v)
elif isinstance(ov, int):
v = int(v)
elif isinstance(ov, list):
vv = v.split(',')
if vv == ['']:
vv = []
vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
if op == '=':
v = vv
elif op == '+=':
v = ov
v.extend(vv)
elif op == '-=':
v = ov
for x in vv:
if x in v:
v.remove(x)
else:
assert 0
opts[k] = v
self.set(**opts)
return ret
class Merger: class Merger:
def __init__(self, fontfiles): def __init__(self, options=None, log=None):
self.fontfiles = fontfiles
def merge(self): if not log:
log = Logger()
if not options:
options = Options()
self.options = options
self.log = log
def merge(self, fontfiles):
mega = ttLib.TTFont() mega = ttLib.TTFont()
# #
# Settle on a mega glyph order. # Settle on a mega glyph order.
# #
fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles] fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
glyphOrders = [font.getGlyphOrder() for font in fonts] glyphOrders = [font.getGlyphOrder() for font in fonts]
megaGlyphOrder = self._mergeGlyphOrders(glyphOrders) megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
# Reload fonts and set new glyph names on them. # Reload fonts and set new glyph names on them.
# TODO Is it necessary to reload font? I think it is. At least # TODO Is it necessary to reload font? I think it is. At least
# it's safer, in case tables were loaded to provide glyph names. # it's safer, in case tables were loaded to provide glyph names.
fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles] fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders) map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders)
mega.setGlyphOrder(megaGlyphOrder) mega.setGlyphOrder(megaGlyphOrder)
@ -90,6 +238,11 @@ class Merger:
allTags = set(sum([font.keys() for font in fonts], [])) allTags = set(sum([font.keys() for font in fonts], []))
allTags.remove('GlyphOrder') allTags.remove('GlyphOrder')
for tag in allTags: for tag in allTags:
if tag in self.options.drop_tables:
print "Dropping '%s'." % tag
continue
clazz = ttLib.getTableClass(tag) clazz = ttLib.getTableClass(tag)
if not hasattr(clazz, 'merge'): if not hasattr(clazz, 'merge'):
@ -128,12 +281,71 @@ class Merger:
mega.append(glyphName) mega.append(glyphName)
return mega return mega
class Logger(object):
def __init__(self, verbose=False, xml=False, timing=False):
self.verbose = verbose
self.xml = xml
self.timing = timing
self.last_time = self.start_time = time.time()
def parse_opts(self, argv):
argv = argv[:]
for v in ['verbose', 'xml', 'timing']:
if "--"+v in argv:
setattr(self, v, True)
argv.remove("--"+v)
return argv
def __call__(self, *things):
if not self.verbose:
return
print ' '.join(str(x) for x in things)
def lapse(self, *things):
if not self.timing:
return
new_time = time.time()
print "Took %0.3fs to %s" %(new_time - self.last_time,
' '.join(str(x) for x in things))
self.last_time = new_time
def font(self, font, file=sys.stdout):
if not self.xml:
return
from fontTools.misc import xmlWriter
writer = xmlWriter.XMLWriter(file)
font.disassembleInstructions = False # Work around ttLib bug
for tag in font.keys():
writer.begintag(tag)
writer.newline()
font[tag].toXML(writer, font)
writer.endtag(tag)
writer.newline()
__all__ = [
'Options',
'Merger',
'Logger',
'main'
]
def main(args): def main(args):
log = Logger()
args = log.parse_opts(args)
options = Options()
args = options.parse_opts(args)
if len(args) < 1: if len(args) < 1:
print >>sys.stderr, "usage: pyftmerge font..." print >>sys.stderr, "usage: pyftmerge font..."
sys.exit(1) sys.exit(1)
merger = Merger(args)
font = merger.merge() merger = Merger(options=options, log=log)
font = merger.merge(args)
outfile = 'merged.ttf' outfile = 'merged.ttf'
font.save(outfile) font.save(outfile)