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.
This commit is contained in:
Cosimo Lupo 2022-06-27 17:17:05 +01:00
parent 99a754a48e
commit c397764720
3 changed files with 83 additions and 4 deletions

View File

@ -723,7 +723,6 @@ def _add_COLR(font, model, master_fonts, axisTags, colr_layer_reuse=True):
mapping = store.optimize() mapping = store.optimize()
colr.VarStore = store colr.VarStore = store
varIdxes = [mapping[v] for v in merger.varIdxes] varIdxes = [mapping[v] for v in merger.varIdxes]
# TODO: Optimize reusable runs of delta-set indices from multiple paints
colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes) colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes)
# rebuild LayerList to optimize PaintColrLayers layer reuse # rebuild LayerList to optimize PaintColrLayers layer reuse

View File

@ -4,6 +4,7 @@ Merge OpenType Layout tables (GDEF / GPOS / GSUB).
import os import os
import copy import copy
import enum import enum
import itertools
from operator import ior from operator import ior
import logging import logging
from fontTools.misc import classifyTools from fontTools.misc import classifyTools
@ -1122,7 +1123,11 @@ class COLRVariationMerger(VariationMerger):
def __init__(self, model, axisTags, font): def __init__(self, model, axisTags, font):
VariationMerger.__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 # set of id()s of the subtables that contain variations after merging
# and need to be upgraded to the associated VarType. # and need to be upgraded to the associated VarType.
self.varTableIds = set() self.varTableIds = set()
@ -1130,6 +1135,18 @@ class COLRVariationMerger(VariationMerger):
def mergeTables(self, font, master_ttfs, tableTags=("COLR",)): def mergeTables(self, font, master_ttfs, tableTags=("COLR",)):
VariationMerger.mergeTables(self, font, master_ttfs, tableTags) 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): def checkFormatEnum(self, out, lst, validate=lambda _: True):
fmt = out.Format fmt = out.Format
formatEnum = out.formatEnum formatEnum = out.formatEnum
@ -1209,10 +1226,14 @@ class COLRVariationMerger(VariationMerger):
baseValue, varIdx = self.storeMastersForAttr(out, lst, attr) baseValue, varIdx = self.storeMastersForAttr(out, lst, attr)
setattr(out, attr, baseValue) setattr(out, attr, baseValue)
varIdxes.append(varIdx) varIdxes.append(varIdx)
varIdxes = tuple(varIdxes)
if any(v != ot.NO_VARIATION_INDEX for v in varIdxes): if any(v != ot.NO_VARIATION_INDEX for v in varIdxes):
varIndexBase = len(self.varIdxes) # try to reuse an existing VarIndexBase for the same varIdxes, or else
self.varIdxes.extend(varIdxes) # 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 return varIndexBase

View File

@ -422,6 +422,65 @@ class COLRVariationMergerTest:
[NO_VARIATION_INDEX, NO_VARIATION_INDEX, 0, NO_VARIATION_INDEX], [NO_VARIATION_INDEX, NO_VARIATION_INDEX, 0, NO_VARIATION_INDEX],
id="sweep_grad-startAngle", 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,
},
],
[
'<Paint Format="9"><!-- PaintVarSweepGradient -->',
" <ColorLine>",
' <Extend value="pad"/>',
" <!-- StopCount=2 -->",
' <ColorStop index="0">',
' <StopOffset value="0.0"/>',
' <PaletteIndex value="0"/>',
' <Alpha value="1.0"/>',
' <VarIndexBase value="0"/>',
" </ColorStop>",
' <ColorStop index="1">',
' <StopOffset value="1.0"/>',
' <PaletteIndex value="1"/>',
' <Alpha value="1.0"/>',
' <VarIndexBase value="0"/>',
" </ColorStop>",
" </ColorLine>",
' <centerX value="0"/>',
' <centerY value="0"/>',
' <startAngle value="0.0"/>',
' <endAngle value="180.0"/>',
' <VarIndexBase/>',
"</Paint>",
],
[NO_VARIATION_INDEX, 0],
id="sweep_grad-stops-alpha-reuse-varidxbase",
),
pytest.param( pytest.param(
[ [
{ {