[varLib] Fix interpolate_layout for non-similar SinglePos

Has to be ported to varfont merger as well.
This commit is contained in:
Behdad Esfahbod 2016-10-12 16:11:20 -07:00
parent b9c38af1ff
commit b39772b256
4 changed files with 129 additions and 9 deletions

View File

@ -868,6 +868,12 @@ class ValueRecord(object):
# see ValueRecordFactory
def __init__(self, valueFormat=None):
if valueFormat is not None:
for mask, name, isDevice, signed in valueRecordFormat:
if valueFormat & mask:
setattr(self, name, None if isDevice else 0)
def getFormat(self):
format = 0
for name in self.__dict__.keys():

View File

@ -248,7 +248,8 @@ def buildVarDevTable(store_builder, master_values):
class VariationMerger(Merger):
def __init__(self, model, axisTags):
def __init__(self, model, axisTags, font):
Merger.__init__(self, font)
self.model = model
self.store_builder = builder.OnlineVarStoreBuilder(axisTags)
self.store_builder.setModel(model)
@ -302,7 +303,7 @@ def merge(merger, self, lst):
if hasattr(self, name):
deviceTable = buildVarDevTable(merger.store_builder,
[getattr(a, name) for a in lst])
[getattr(a, name, 0) for a in lst])
if deviceTable:
setattr(self, tableName, deviceTable)
@ -310,7 +311,7 @@ def merge(merger, self, lst):
def _merge_OTL(font, model, master_fonts, axisTags, base_idx):
print("Merging OpenType Layout tables")
merger = VariationMerger(model, axisTags)
merger = VariationMerger(model, axisTags, font)
merge_tables(font, merger, master_fonts, axisTags, base_idx, ['GPOS'])
store = merger.store_builder.finish()

View File

@ -9,11 +9,13 @@ from fontTools.ttLib.tables import otBase as otBase
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools.varLib import designspace, models, builder
from fontTools.varLib.merger import merge_tables, Merger
from functools import reduce
import os.path
class InstancerMerger(Merger):
def __init__(self, model, location):
def __init__(self, font, model, location):
Merger.__init__(self, font)
self.model = model
self.location = location
@ -30,6 +32,7 @@ def merge(merger, self, lst):
def merge(merger, self, lst):
model = merger.model
location = merger.location
# TODO Handle differing valueformats
for name, tableName in [('XAdvance','XAdvDevice'),
('YAdvance','YAdvDevice'),
('XPlacement','XPlaDevice'),
@ -38,10 +41,115 @@ def merge(merger, self, lst):
assert not hasattr(self, tableName)
if hasattr(self, name):
values = [getattr(a, name) for a in lst]
values = [getattr(a, name, 0) for a in lst]
value = round(model.interpolateFromMasters(location, values))
setattr(self, name, value)
def _SinglePosUpgradeToFormat2(self):
if self.Format == 2: return self
ret = ot.SinglePos()
ret.Format = 2
ret.Coverage = self.Coverage
ret.ValueFormat = self.ValueFormat
ret.Value = [self.Value for g in ret.Coverage.glyphs]
ret.ValueCount = len(ret.Value)
return ret
def _merge_GlyphOrders(font, lst, values_lst=None):
"""Takes font and list of glyph lists (must be sorted by glyph id), and returns
two things:
- Combined glyph list,
- If values_lst is None, return input glyph lists, but padded with None when a glyph
was missing in a list. Otherwise, return values_lst list-of-list, padded with None
to match combined glyph lists.
"""
dict_sets = [{g:i for i,g in enumerate(l)} for l in lst]
combined = set()
combined.update(*dict_sets)
order = font.getGlyphOrder()
order = [glyph for glyph in order if glyph in combined]
paddedValues = None
if values_lst is None:
padded = [[glyph if glyph in dict_set else None
for glyph in combined]
for dict_set in dict_sets]
else:
assert len(lst) == len(values_lst)
padded = [[values[dict_set[glyph]] if glyph in dict_set else None
for glyph in combined]
for dict_set, values in zip(dict_sets, values_lst)]
return order, padded
def _Lookup_SinglePos_get_effective_value(self, glyph):
if self is None: return None
subtables = self.SubTable
for self in subtables:
if self is None or \
type(self) != ot.SinglePos or \
self.Coverage is None or \
glyph not in self.Coverage.glyphs:
continue
if self.Format == 1:
return self.Value
elif self.Format == 2:
return self.Value[self.Coverage.glyphs.index(glyph)]
else:
assert 0
return None
@InstancerMerger.merger(ot.SinglePos)
def merge(merger, self, lst):
self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst])
assert valueFormat & ~0xF == 0, valueFormat
# If all have same coverage table and all are format 1,
if all(v.Format == 1 for v in lst) and all(self.Coverage.glyphs == v.Coverage.glyphs for v in lst):
self.Value = otBase.ValueRecord(valueFormat)
merger.mergeThings(self.Value, [v.Value for v in lst])
return
# Upgrade everything to Format=2
self.Format = 2
lst = [_SinglePosUpgradeToFormat2(v) for v in lst]
# Align them
glyphs, padded = _merge_GlyphOrders(merger.font,
[v.Coverage.glyphs for v in lst],
[v.Value for v in lst])
self.Coverage.glyphs = glyphs
self.Value = [otBase.ValueRecord(valueFormat) for g in glyphs]
self.ValueCount = len(self.Value)
for glyph,values in zip(glyphs, padded):
for i in range(len(values)):
if values[i] is not None: continue
# Fill in value from k
v = _Lookup_SinglePos_get_effective_value(merger.lookups[i], glyph)
if v is None:
v = otBase.ValueRecord(valueFormat)
values[i] = v
merger.mergeThings(self.Value, padded)
# Merge everything else; though, there shouldn't be anything else. :)
merger.mergeObjects(self, lst,
exclude=('Format', 'Coverage', 'ValueRecord', 'Value', 'ValueCount'))
#@InstancerMerger.merger(ot.PairPos)
#def merge(merger, self, lst):
# #return #XXX
# # Merge everything else; though, there shouldn't be anything else. :)
# merger.mergeObjects(self, lst,
# exclude=('Coverage', 'PairSet', 'PairSetCount'))
@InstancerMerger.merger(ot.Lookup)
def merge(merger, self, lst):
merger.lookups = lst
merger.mergeObjects(self, lst)
del merger.lookups
def interpolate_layout(designspace_filename, loc, finder):
@ -96,7 +204,7 @@ def interpolate_layout(designspace_filename, loc, finder):
model = models.VariationModel(master_locs)
assert 0 == model.mapping[base_idx]
merger = InstancerMerger(model, loc)
merger = InstancerMerger(font, model, loc)
print("Building variations tables")
merge_tables(font, merger, master_fonts, axes, base_idx, ['GPOS'])

View File

@ -10,6 +10,9 @@ class Merger(object):
mergers = None
def __init__(self, font=None):
self.font = font
@classmethod
def merger(celf, clazzes, attrs=(None,)):
assert celf != Merger, 'Subclass Merger instead.'
@ -33,13 +36,15 @@ class Merger(object):
return None
return wrapper
def mergeObjects(self, out, lst, _default={}):
keys = vars(out).keys()
assert all(vars(table).keys() == keys for table in lst)
def mergeObjects(self, out, lst, exclude=(), _default={}):
keys = sorted(vars(out).keys())
assert all(keys == sorted(vars(v).keys()) for v in lst), \
(keys, [sorted(vars(v).keys()) for v in lst])
mergers = self.mergers.get(type(out), _default)
defaultMerger = mergers.get('*', self.__class__.mergeThings)
try:
for key in keys:
if key in exclude: continue
value = getattr(out, key)
values = [getattr(table, key) for table in lst]
mergerFunc = mergers.get(key, defaultMerger)