From c397764720d4f87257209a8c0d18fb1d6d31d604 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 27 Jun 2022 17:17:05 +0100 Subject: [PATCH] COLRVariationMerger: implement reuse of VarIndexBase between tables with same varIdxes When multiple variable tables refer to the same delta-sets they can now share the same VarIndexBase so the resulting DeltaSetIndexMap is a bit smaller. For simplicity, we only reuse VarIndexBase when variable tables fully share (ie. same, and same number of) varIdxes; potentially we could reuse subsets of varIdxes (e.g. a VarColoStop.Alpha has a +0.5 delta, and later on elsewhere a PaintVarSolid.Alpha has a similar +0.5 delta; the latter could have a VarIndexBase that reuses an existing DeltaSetIndexMap entry for the former), but for now this I think is good enough. --- Lib/fontTools/varLib/__init__.py | 1 - Lib/fontTools/varLib/merger.py | 27 +++++++++++++-- Tests/varLib/merger_test.py | 59 ++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 6d83a9330..a98a45b8d 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -723,7 +723,6 @@ def _add_COLR(font, model, master_fonts, axisTags, colr_layer_reuse=True): mapping = store.optimize() colr.VarStore = store varIdxes = [mapping[v] for v in merger.varIdxes] - # TODO: Optimize reusable runs of delta-set indices from multiple paints colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes) # rebuild LayerList to optimize PaintColrLayers layer reuse diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py index 92ec0e8ae..69778fdfd 100644 --- a/Lib/fontTools/varLib/merger.py +++ b/Lib/fontTools/varLib/merger.py @@ -4,6 +4,7 @@ Merge OpenType Layout tables (GDEF / GPOS / GSUB). import os import copy import enum +import itertools from operator import ior import logging from fontTools.misc import classifyTools @@ -1122,7 +1123,11 @@ class COLRVariationMerger(VariationMerger): def __init__(self, model, axisTags, font): VariationMerger.__init__(self, model, axisTags, font) - self.varIdxes = [] + # maps {tuple(varIdxes): VarIndexBase} to facilitate reuse of VarIndexBase + # between variable tables with same varIdxes. + self.varIndexCache = {} + # total number of varIdxes (i.e. sum(len(vs) for vs in self.varIndexCache)) + self.varIndexCount = 0 # set of id()s of the subtables that contain variations after merging # and need to be upgraded to the associated VarType. self.varTableIds = set() @@ -1130,6 +1135,18 @@ class COLRVariationMerger(VariationMerger): def mergeTables(self, font, master_ttfs, tableTags=("COLR",)): VariationMerger.mergeTables(self, font, master_ttfs, tableTags) + @property + def varIdxes(self): + """Return flat list of all the varIdxes generated while merging. + + To be called after mergeTables() for building a DeltaSetIndexMap. + """ + # dict remembers insertion order (as of py37+), and we extend the cache + # of VarIndexBases incrementally as new, unique tuples of varIdxes are found + # while merging, thus we don't need to sort but we just unpack the keys and + # chain the tuples + return [v for v in itertools.chain(*self.varIndexCache.keys())] + def checkFormatEnum(self, out, lst, validate=lambda _: True): fmt = out.Format formatEnum = out.formatEnum @@ -1209,10 +1226,14 @@ class COLRVariationMerger(VariationMerger): baseValue, varIdx = self.storeMastersForAttr(out, lst, attr) setattr(out, attr, baseValue) varIdxes.append(varIdx) + varIdxes = tuple(varIdxes) if any(v != ot.NO_VARIATION_INDEX for v in varIdxes): - varIndexBase = len(self.varIdxes) - self.varIdxes.extend(varIdxes) + # try to reuse an existing VarIndexBase for the same varIdxes, or else + # create a new one at the end of self.varIndexCache (py37+ dicts are ordered) + varIndexBase = self.varIndexCache.setdefault(varIdxes, self.varIndexCount) + if varIndexBase == self.varIndexCount: + self.varIndexCount += len(varIdxes) return varIndexBase diff --git a/Tests/varLib/merger_test.py b/Tests/varLib/merger_test.py index f0a388db5..1bfedcf7c 100644 --- a/Tests/varLib/merger_test.py +++ b/Tests/varLib/merger_test.py @@ -422,6 +422,65 @@ class COLRVariationMergerTest: [NO_VARIATION_INDEX, NO_VARIATION_INDEX, 0, NO_VARIATION_INDEX], id="sweep_grad-startAngle", ), + pytest.param( + [ + { + "Format": int(ot.PaintFormat.PaintSweepGradient), + "ColorLine": { + "Extend": int(ot.ExtendMode.PAD), + "ColorStop": [ + {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0}, + {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0}, + ], + }, + "centerX": 0, + "centerY": 0, + "startAngle": 0.0, + "endAngle": 180.0, + }, + { + "Format": int(ot.PaintFormat.PaintSweepGradient), + "ColorLine": { + "Extend": int(ot.ExtendMode.PAD), + "ColorStop": [ + {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 0.5}, + {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 0.5}, + ], + }, + "centerX": 0, + "centerY": 0, + "startAngle": 0.0, + "endAngle": 180.0, + }, + ], + [ + '', + " ", + ' ', + " ", + ' ', + ' ', + ' ', + ' ', + ' ', + " ", + ' ', + ' ', + ' ', + ' ', + ' ', + " ", + " ", + ' ', + ' ', + ' ', + ' ', + ' ', + "", + ], + [NO_VARIATION_INDEX, 0], + id="sweep_grad-stops-alpha-reuse-varidxbase", + ), pytest.param( [ {