COLRVariationMerger: further optimize DeltaSetIndexMap

previously we only reused the VarIndexBase of a previously seen variable table when the current's varIdxes were _fully_ equal to one of the previous; now we also try to find a match anywhere in the accummulated list of self.varIdxes, including a partial match at the tail of the list.
This commit is contained in:
Cosimo Lupo 2022-06-28 14:11:28 +01:00
parent c397764720
commit d85aa2d119
2 changed files with 134 additions and 24 deletions

View File

@ -4,7 +4,6 @@ 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
@ -1126,8 +1125,8 @@ class COLRVariationMerger(VariationMerger):
# 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
# flat list of all the varIdxes generated while merging
self.varIdxes = []
# set of id()s of the subtables that contain variations after merging
# and need to be upgraded to the associated VarType.
self.varTableIds = set()
@ -1135,18 +1134,6 @@ 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
@ -1219,6 +1206,35 @@ class COLRVariationMerger(VariationMerger):
return baseValue, varIdx
def storeVariationIndices(self, varIdxes) -> int:
# try to reuse an existing VarIndexBase for the same varIdxes, or else
# create a new one
key = tuple(varIdxes)
varIndexBase = self.varIndexCache.get(key)
if varIndexBase is None:
# scan for a full match anywhere in the self.varIdxes
for i in range(len(self.varIdxes) - len(varIdxes) + 1):
if self.varIdxes[i:i+len(varIdxes)] == varIdxes:
self.varIndexCache[key] = varIndexBase = i
break
if varIndexBase is None:
# try find a partial match at the end of the self.varIdxes
for n in range(len(varIdxes)-1, 0, -1):
if self.varIdxes[-n:] == varIdxes[:n]:
varIndexBase = len(self.varIdxes) - n
self.varIndexCache[key] = varIndexBase
self.varIdxes.extend(varIdxes[n:])
break
if varIndexBase is None:
# no match found, append at the end
self.varIndexCache[key] = varIndexBase = len(self.varIdxes)
self.varIdxes.extend(varIdxes)
return varIndexBase
def mergeVariableAttrs(self, out, lst, attrs) -> int:
varIndexBase = ot.NO_VARIATION_INDEX
varIdxes = []
@ -1226,14 +1242,9 @@ 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):
# 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)
varIndexBase = self.storeVariationIndices(varIdxes)
return varIndexBase

View File

@ -283,7 +283,7 @@ class COLRVariationMergerTest:
' <y1 value="1"/>',
' <x2 value="2"/>',
' <y2 value="2"/>',
' <VarIndexBase value="2"/>',
' <VarIndexBase value="1"/>',
"</Paint>",
],
[
@ -293,7 +293,6 @@ class COLRVariationMergerTest:
NO_VARIATION_INDEX,
NO_VARIATION_INDEX,
NO_VARIATION_INDEX,
NO_VARIATION_INDEX,
1,
],
id="linear_grad-stop[0].offset-y2",
@ -475,7 +474,7 @@ class COLRVariationMergerTest:
' <centerY value="0"/>',
' <startAngle value="0.0"/>',
' <endAngle value="180.0"/>',
' <VarIndexBase/>',
" <VarIndexBase/>",
"</Paint>",
],
[NO_VARIATION_INDEX, 0],
@ -599,6 +598,106 @@ class COLRVariationMergerTest:
],
id="transform-yy-dy",
),
pytest.param(
[
{
"Format": ot.PaintFormat.PaintTransform,
"Paint": {
"Format": ot.PaintFormat.PaintSweepGradient,
"ColorLine": {
"Extend": ot.ExtendMode.PAD,
"ColorStop": [
{"StopOffset": 0.0, "PaletteIndex": 0},
{
"StopOffset": 1.0,
"PaletteIndex": 1,
"Alpha": 1.0,
},
],
},
"centerX": 0,
"centerY": 0,
"startAngle": -360,
"endAngle": 0,
},
"Transform": (1.0, 0, 0, 1.0, 0, 0),
},
{
"Format": ot.PaintFormat.PaintTransform,
"Paint": {
"Format": ot.PaintFormat.PaintSweepGradient,
"ColorLine": {
"Extend": ot.ExtendMode.PAD,
"ColorStop": [
{"StopOffset": 0.0, "PaletteIndex": 0},
{
"StopOffset": 1.0,
"PaletteIndex": 1,
"Alpha": 1.0,
},
],
},
"centerX": 256,
"centerY": 0,
"startAngle": -360,
"endAngle": 0,
},
# Transform.xx below produces the same VarStore delta as the
# above PaintSweepGradient's centerX because, when Fixed16.16
# is converted to integer, it becomes:
# floatToFixed(1.00390625, 16) == 256
# Because there is overlap between the varIdxes of the
# PaintVarTransform's Affine2x3 and the PaintSweepGradient's
# the VarIndexBase is reused (0 for both)
"Transform": (1.00390625, 0, 0, 1.0, 10, 0),
},
],
[
'<Paint Format="13"><!-- PaintVarTransform -->',
' <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/>",
" </ColorStop>",
' <ColorStop index="1">',
' <StopOffset value="1.0"/>',
' <PaletteIndex value="1"/>',
' <Alpha value="1.0"/>',
" <VarIndexBase/>",
" </ColorStop>",
" </ColorLine>",
' <centerX value="0"/>',
' <centerY value="0"/>',
' <startAngle value="-360.0"/>',
' <endAngle value="0.0"/>',
' <VarIndexBase value="0"/>',
" </Paint>",
" <Transform>",
' <xx value="1.0"/>',
' <yx value="0.0"/>',
' <xy value="0.0"/>',
' <yy value="1.0"/>',
' <dx value="0.0"/>',
' <dy value="0.0"/>',
' <VarIndexBase value="0"/>',
" </Transform>",
"</Paint>",
],
[
0,
NO_VARIATION_INDEX,
NO_VARIATION_INDEX,
NO_VARIATION_INDEX,
1,
NO_VARIATION_INDEX,
],
id="transform-xx-sweep_grad-centerx-same-varidxbase",
),
],
)
def test_merge_Paint(self, paints, ttFont, expected_xml, expected_varIdxes):