[merge] Implement a few more straightforward tables
This commit is contained in:
parent
65f19d8440
commit
f2d5982826
@ -6,6 +6,7 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import fontTools
|
||||
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))
|
||||
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'))
|
||||
def merge(self, tables, fonts):
|
||||
# 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))
|
||||
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'),
|
||||
fontTools.ttLib.getTableClass('hmtx'))
|
||||
def merge(self, tables, fonts):
|
||||
@ -60,28 +107,129 @@ def merge(self, tables, fonts):
|
||||
return True
|
||||
|
||||
@_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):
|
||||
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:
|
||||
|
||||
def __init__(self, fontfiles):
|
||||
self.fontfiles = fontfiles
|
||||
def __init__(self, options=None, log=None):
|
||||
|
||||
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()
|
||||
|
||||
#
|
||||
# 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]
|
||||
megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
|
||||
# Reload fonts and set new glyph names on them.
|
||||
# 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.
|
||||
fonts = [ttLib.TTFont(fontfile) for fontfile in self.fontfiles]
|
||||
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
||||
map(ttLib.TTFont.setGlyphOrder, fonts, glyphOrders)
|
||||
mega.setGlyphOrder(megaGlyphOrder)
|
||||
|
||||
@ -90,6 +238,11 @@ class Merger:
|
||||
allTags = set(sum([font.keys() for font in fonts], []))
|
||||
allTags.remove('GlyphOrder')
|
||||
for tag in allTags:
|
||||
|
||||
if tag in self.options.drop_tables:
|
||||
print "Dropping '%s'." % tag
|
||||
continue
|
||||
|
||||
clazz = ttLib.getTableClass(tag)
|
||||
|
||||
if not hasattr(clazz, 'merge'):
|
||||
@ -128,12 +281,71 @@ class Merger:
|
||||
mega.append(glyphName)
|
||||
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):
|
||||
|
||||
log = Logger()
|
||||
args = log.parse_opts(args)
|
||||
|
||||
options = Options()
|
||||
args = options.parse_opts(args)
|
||||
|
||||
if len(args) < 1:
|
||||
print >>sys.stderr, "usage: pyftmerge font..."
|
||||
sys.exit(1)
|
||||
merger = Merger(args)
|
||||
font = merger.merge()
|
||||
|
||||
merger = Merger(options=options, log=log)
|
||||
font = merger.merge(args)
|
||||
outfile = 'merged.ttf'
|
||||
font.save(outfile)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user