From caa353d5dde1f8a0a0d1b80886d6e7c08aac5bc1 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Thu, 8 Sep 2016 09:17:45 -0700 Subject: [PATCH] [varStore] Generate variation for Anchors and store in GDEF Finally! Phew... --- Lib/fontTools/ttLib/tables/otTables.py | 1 + Lib/fontTools/varLib/__init__.py | 26 +++++++++------- Lib/fontTools/varLib/builder.py | 41 ++++++++++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 0a459dd9f..98c5d7a4e 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -203,6 +203,7 @@ class VarData(BaseTable): numShorts = 0 count = len(self.VarRegionIndex) for item in self.Item: + assert len(item) == count, ("Item length mismatch", len(item), count) for i in range(count - 1, numShorts - 1, -1): if not (-128 <= item[i] <= 127): numShorts = i + 1 diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 21be608cf..58367a53f 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -85,6 +85,8 @@ def _add_fvar(font, axes, instances, axis_map): inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()} fvar.instances.append(inst) + return fvar + # TODO Move to glyf or gvar table proper def _GetCoordinates(font, glyphName): """font, glyphName --> glyph coordinates as expected by "gvar" table @@ -187,7 +189,7 @@ def _add_gvar(font, model, master_ttfs): var = GlyphVariation(support, delta) gvar.variations[glyph].append(var) -def _add_HVAR(font, model, master_ttfs, axes): +def _add_HVAR(font, model, master_ttfs, axisTags): print("Generating HVAR") @@ -201,7 +203,7 @@ def _add_HVAR(font, model, master_ttfs, axes): # We only support the direct mapping right now. supports = model.supports[1:] - varTupleList = builder.buildVarRegionList(supports, axes.keys()) + varTupleList = builder.buildVarRegionList(supports, axisTags) varTupleIndexes = list(range(len(supports))) n = len(supports) items = [] @@ -249,8 +251,8 @@ def _all_equal(lst): def buildVarDevTable(store_builder, master_values): if _all_equal(master_values): return None - deltas = master_values - return builder.buildVarDevTable(0xdeadbeef) + varIdx = store_builder.storeMasters(master_values) + return builder.buildVarDevTable(varIdx) class VariationMerger(Merger): @@ -269,15 +271,13 @@ def merge(merger, self, lst): self.XDeviceTable = XDeviceTable self.YDeviceTable = YDeviceTable -def _merge_OTL(font, model, master_fonts, axes, base_idx): +def _merge_OTL(font, model, master_fonts, axisTags, base_idx): print("Merging OpenType Layout tables") - axisTags = [] # XXX merger = VariationMerger(model, axisTags) - print("Building variations tables") - merge_tables(font, merger, master_fonts, axes, base_idx, ['GPOS']) + merge_tables(font, merger, master_fonts, axisTags, base_idx, ['GPOS']) store = merger.store_builder.finish() GDEF = font['GDEF'].table @@ -354,7 +354,8 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None): gx = TTFont(master_ttfs[base_idx]) # TODO append masters as named-instances as well; needs .designspace change. - _add_fvar(gx, axes, instances, axis_map) + fvar = _add_fvar(gx, axes, instances, axis_map) + # Normalize master locations master_locs = [models.normalizeLocation(m, axes) for m in master_locs] @@ -363,8 +364,11 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None): pprint(master_locs) # TODO Clean this up. + del instances + del axes master_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in master_locs] #instance_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in instance_locs] + axisTags = [axis.axisTag for axis in fvar.axes] # Assume single-model for now. model = models.VariationModel(master_locs) @@ -373,8 +377,8 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None): print("Building variations tables") if 'glyf' in gx: _add_gvar(gx, model, master_fonts) - _add_HVAR(gx, model, master_fonts, axes) - _merge_OTL(gx, model, master_fonts, axes, base_idx) + _add_HVAR(gx, model, master_fonts, axisTags) + _merge_OTL(gx, model, master_fonts, axisTags, base_idx) return gx, model, master_ttfs diff --git a/Lib/fontTools/varLib/builder.py b/Lib/fontTools/varLib/builder.py index 698ffb374..9c73048ce 100644 --- a/Lib/fontTools/varLib/builder.py +++ b/Lib/fontTools/varLib/builder.py @@ -10,6 +10,7 @@ def buildVarRegionAxis(axisSupport): return self def buildVarRegion(support, axisTags): + assert all(tag in axisTags for tag in support.keys()), ("Unknown axis tag found.", support, axisTags) self = ot.VarRegion() self.VarRegionAxis = [] for tag in axisTags: @@ -23,7 +24,7 @@ def buildVarRegionList(supports, axisTags): self.Region = [] for support in supports: self.Region.append(buildVarRegion(support, axisTags)) - self.VarRegionCount = len(self.Region) + self.RegionCount = len(self.Region) return self @@ -81,18 +82,40 @@ def buildVarStore(varRegionList, varDataList): return self +def _getLocationKey(loc): + return tuple(sorted(loc.items(), key=lambda kv: kv[0])) + class OnlineVarStoreBuilder(object): def __init__(self, axisTags): - self._regions = buildVarRegionList([], axisTags) - self._store = buildVarStore(self._regions, []) + self._axisTags = axisTags + self._regionMap = {} + self._regionList = buildVarRegionList([], axisTags) + self._store = buildVarStore(self._regionList, []) def setModel(self, model): self._model = model - # Store model's regions + + regionMap = self._regionMap + regionList = self._regionList + + regions = model.supports[1:] + regionIndices = [] + for region in regions: + key = _getLocationKey(region) + idx = regionMap.get(key) + if idx is None: + varRegion = buildVarRegion(region, self._axisTags) + idx = regionMap[key] = len(regionList.Region) + regionList.Region.append(varRegion) + regionIndices.append(idx) + + data = self._data = buildVarData(regionIndices, [], optimize=False) + self._outer = len(self._store.VarData) + self._store.VarData.append(data) def finish(self, optimize=True): - self._regions.VarRegionCount = len(self._regions.Region) + self._regionList.RegionCount = len(self._regionList.Region) self._store.VarDataCount = len(self._store.VarData) for data in self._store.VarData: data.ItemCount = len(data.Item) @@ -100,6 +123,14 @@ class OnlineVarStoreBuilder(object): optimizeVarData(data) return self._store + def storeMasters(self, master_values): + deltas = [int(round(d)) for d in self._model.getDeltas(master_values)[1:]] + inner = len(self._data.Item) + self._data.Item.append(deltas) + # TODO Check for full data array? + return (self._outer << 16) + inner + + # Variation helpers