colorLib: add option to disable PaintColrLayers' reuse of layers from LayerList

When building COLR masters, layer reuse may lead to different number of layers between masters, becasue some master may reuse specific layers while others may not. Add a flag to disable this optimization; will need to be run again after the VF COLR font has been merged
This commit is contained in:
Cosimo Lupo 2022-06-14 16:04:25 +01:00
parent 0d7d7d4e11
commit fc982c643b
2 changed files with 43 additions and 30 deletions

View File

@ -186,10 +186,12 @@ def populateCOLRv0(
def buildCOLR( def buildCOLR(
colorGlyphs: _ColorGlyphsDict, colorGlyphs: _ColorGlyphsDict,
version: Optional[int] = None, version: Optional[int] = None,
*,
glyphMap: Optional[Mapping[str, int]] = None, glyphMap: Optional[Mapping[str, int]] = None,
varStore: Optional[ot.VarStore] = None, varStore: Optional[ot.VarStore] = None,
varIndexMap: Optional[ot.DeltaSetIndexMap] = None, varIndexMap: Optional[ot.DeltaSetIndexMap] = None,
clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None, clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None,
allowLayerReuse: bool = True,
) -> C_O_L_R_.table_C_O_L_R_: ) -> C_O_L_R_.table_C_O_L_R_:
"""Build COLR table from color layers mapping. """Build COLR table from color layers mapping.
@ -231,7 +233,11 @@ def buildCOLR(
populateCOLRv0(colr, colorGlyphsV0, glyphMap) populateCOLRv0(colr, colorGlyphsV0, glyphMap)
colr.LayerList, colr.BaseGlyphList = buildColrV1(colorGlyphsV1, glyphMap) colr.LayerList, colr.BaseGlyphList = buildColrV1(
colorGlyphsV1,
glyphMap,
allowLayerReuse=allowLayerReuse,
)
if version is None: if version is None:
version = 1 if (varStore or colorGlyphsV1) else 0 version = 1 if (varStore or colorGlyphsV1) else 0
@ -448,12 +454,14 @@ class LayerListBuilder:
reusePool: Mapping[Tuple[Any, ...], int] reusePool: Mapping[Tuple[Any, ...], int]
tuples: Mapping[int, Tuple[Any, ...]] tuples: Mapping[int, Tuple[Any, ...]]
keepAlive: List[ot.Paint] # we need id to remain valid keepAlive: List[ot.Paint] # we need id to remain valid
allowLayerReuse: bool
def __init__(self): def __init__(self, *, allowLayerReuse=True):
self.layers = [] self.layers = []
self.reusePool = {} self.reusePool = {}
self.tuples = {} self.tuples = {}
self.keepAlive = [] self.keepAlive = []
self.allowLayerReuse = allowLayerReuse
# We need to intercept construction of PaintColrLayers # We need to intercept construction of PaintColrLayers
callbacks = _buildPaintCallbacks() callbacks = _buildPaintCallbacks()
@ -504,34 +512,35 @@ class LayerListBuilder:
# Convert maps seqs or whatever into typed objects # Convert maps seqs or whatever into typed objects
layers = [self.buildPaint(l) for l in layers] layers = [self.buildPaint(l) for l in layers]
# No reason to have a colr layers with just one entry if self.allowLayerReuse:
if len(layers) == 1: # No reason to have a colr layers with just one entry
return layers[0], {} if len(layers) == 1:
return layers[0], {}
# Look for reuse, with preference to longer sequences # Look for reuse, with preference to longer sequences
# This may make the layer list smaller # This may make the layer list smaller
found_reuse = True found_reuse = True
while found_reuse: while found_reuse:
found_reuse = False found_reuse = False
ranges = sorted( ranges = sorted(
_reuse_ranges(len(layers)), _reuse_ranges(len(layers)),
key=lambda t: (t[1] - t[0], t[1], t[0]), key=lambda t: (t[1] - t[0], t[1], t[0]),
reverse=True, reverse=True,
)
for lbound, ubound in ranges:
reuse_lbound = self.reusePool.get(
self._as_tuple(layers[lbound:ubound]), -1
) )
if reuse_lbound == -1: for lbound, ubound in ranges:
continue reuse_lbound = self.reusePool.get(
new_slice = ot.Paint() self._as_tuple(layers[lbound:ubound]), -1
new_slice.Format = int(ot.PaintFormat.PaintColrLayers) )
new_slice.NumLayers = ubound - lbound if reuse_lbound == -1:
new_slice.FirstLayerIndex = reuse_lbound continue
layers = layers[:lbound] + [new_slice] + layers[ubound:] new_slice = ot.Paint()
found_reuse = True new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
break new_slice.NumLayers = ubound - lbound
new_slice.FirstLayerIndex = reuse_lbound
layers = layers[:lbound] + [new_slice] + layers[ubound:]
found_reuse = True
break
# The layer list is now final; if it's too big we need to tree it # The layer list is now final; if it's too big we need to tree it
is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
@ -552,7 +561,7 @@ class LayerListBuilder:
layers = [listToColrLayers(l) for l in layers] layers = [listToColrLayers(l) for l in layers]
# No reason to have a colr layers with just one entry # No reason to have a colr layers with just one entry
if len(layers) == 1: if self.allowLayerReuse and len(layers) == 1:
return layers[0], {} return layers[0], {}
paint = ot.Paint() paint = ot.Paint()
@ -563,7 +572,7 @@ class LayerListBuilder:
# Register our parts for reuse provided we aren't a tree # Register our parts for reuse provided we aren't a tree
# If we are a tree the leaves registered for reuse and that will suffice # If we are a tree the leaves registered for reuse and that will suffice
if not is_tree: if self.allowLayerReuse and not is_tree:
for lbound, ubound in _reuse_ranges(len(layers)): for lbound, ubound in _reuse_ranges(len(layers)):
self.reusePool[self._as_tuple(layers[lbound:ubound])] = ( self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
lbound + paint.FirstLayerIndex lbound + paint.FirstLayerIndex
@ -603,6 +612,8 @@ def _format_glyph_errors(errors: Mapping[str, Exception]) -> str:
def buildColrV1( def buildColrV1(
colorGlyphs: _ColorGlyphsDict, colorGlyphs: _ColorGlyphsDict,
glyphMap: Optional[Mapping[str, int]] = None, glyphMap: Optional[Mapping[str, int]] = None,
*,
allowLayerReuse: bool = True,
) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]: ) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]:
if glyphMap is not None: if glyphMap is not None:
colorGlyphItems = sorted( colorGlyphItems = sorted(
@ -613,7 +624,7 @@ def buildColrV1(
errors = {} errors = {}
baseGlyphs = [] baseGlyphs = []
layerBuilder = LayerListBuilder() layerBuilder = LayerListBuilder(allowLayerReuse=allowLayerReuse)
for baseGlyph, paint in colorGlyphItems: for baseGlyph, paint in colorGlyphItems:
try: try:
baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint)) baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint))

View File

@ -838,6 +838,7 @@ class FontBuilder(object):
varStore=None, varStore=None,
varIndexMap=None, varIndexMap=None,
clipBoxes=None, clipBoxes=None,
allowLayerReuse=True,
): ):
"""Build new COLR table using color layers dictionary. """Build new COLR table using color layers dictionary.
@ -853,6 +854,7 @@ class FontBuilder(object):
varStore=varStore, varStore=varStore,
varIndexMap=varIndexMap, varIndexMap=varIndexMap,
clipBoxes=clipBoxes, clipBoxes=clipBoxes,
allowLayerReuse=allowLayerReuse,
) )
def setupCPAL( def setupCPAL(