colorLib: factor out LayerReuseCache class

This commit is contained in:
Cosimo Lupo 2022-07-04 12:40:47 +01:00
parent c2887caf95
commit eb00e499c0

View File

@ -449,30 +449,15 @@ def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
yield (lbound, ubound) yield (lbound, ubound)
class LayerListBuilder: class LayerReuseCache:
layers: List[ot.Paint]
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, *, allowLayerReuse=True): def __init__(self):
self.layers = []
self.reusePool = {} self.reusePool = {}
self.tuples = {} self.tuples = {}
self.keepAlive = [] self.keepAlive = []
self.allowLayerReuse = allowLayerReuse
# We need to intercept construction of PaintColrLayers
callbacks = _buildPaintCallbacks()
callbacks[
(
BuildCallback.BEFORE_BUILD,
ot.Paint,
ot.PaintFormat.PaintColrLayers,
)
] = self._beforeBuildPaintColrLayers
self.tableBuilder = TableBuilder(callbacks)
def _paint_tuple(self, paint: ot.Paint): def _paint_tuple(self, paint: ot.Paint):
# start simple, who even cares about cyclic graphs or interesting field types # start simple, who even cares about cyclic graphs or interesting field types
@ -499,26 +484,7 @@ class LayerListBuilder:
def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]: def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
return tuple(self._paint_tuple(p) for p in paints) return tuple(self._paint_tuple(p) for p in paints)
# COLR layers is unusual in that it modifies shared state def try_reuse(self, layers: List[ot.Paint]) -> List[ot.Paint]:
# so we need a callback into an object
def _beforeBuildPaintColrLayers(self, dest, source):
# Sketchy gymnastics: a sequence input will have dropped it's layers
# into NumLayers; get it back
if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
layers = source["NumLayers"]
else:
layers = source["Layers"]
# Convert maps seqs or whatever into typed objects
layers = [self.buildPaint(l) for l in layers]
if self.allowLayerReuse:
# No reason to have a colr layers with just one entry
if len(layers) == 1:
return layers[0], {}
# Look for reuse, with preference to longer sequences
# 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
@ -541,6 +507,59 @@ class LayerListBuilder:
layers = layers[:lbound] + [new_slice] + layers[ubound:] layers = layers[:lbound] + [new_slice] + layers[ubound:]
found_reuse = True found_reuse = True
break break
return layers
def add(self, layers: List[ot.Paint], first_layer_index: int):
for lbound, ubound in _reuse_ranges(len(layers)):
self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
lbound + first_layer_index
)
class LayerListBuilder:
layers: List[ot.Paint]
cache: LayerReuseCache
allowLayerReuse: bool
def __init__(self, *, allowLayerReuse=True):
self.layers = []
if allowLayerReuse:
self.cache = LayerReuseCache()
else:
self.cache = None
# We need to intercept construction of PaintColrLayers
callbacks = _buildPaintCallbacks()
callbacks[
(
BuildCallback.BEFORE_BUILD,
ot.Paint,
ot.PaintFormat.PaintColrLayers,
)
] = self._beforeBuildPaintColrLayers
self.tableBuilder = TableBuilder(callbacks)
# COLR layers is unusual in that it modifies shared state
# so we need a callback into an object
def _beforeBuildPaintColrLayers(self, dest, source):
# Sketchy gymnastics: a sequence input will have dropped it's layers
# into NumLayers; get it back
if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
layers = source["NumLayers"]
else:
layers = source["Layers"]
# Convert maps seqs or whatever into typed objects
layers = [self.buildPaint(l) for l in layers]
# No reason to have a colr layers with just one entry
if len(layers) == 1:
return layers[0], {}
if self.cache is not None:
# Look for reuse, with preference to longer sequences
# This may make the layer list smaller
layers = self.cache.try_reuse(layers)
# 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
@ -561,7 +580,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 self.allowLayerReuse and len(layers) == 1: if len(layers) == 1:
return layers[0], {} return layers[0], {}
paint = ot.Paint() paint = ot.Paint()
@ -572,11 +591,8 @@ 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 self.allowLayerReuse and not is_tree: if self.cache is not None and not is_tree:
for lbound, ubound in _reuse_ranges(len(layers)): self.cache.add(layers, paint.FirstLayerIndex)
self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
lbound + paint.FirstLayerIndex
)
# we've fully built dest; empty source prevents generalized build from kicking in # we've fully built dest; empty source prevents generalized build from kicking in
return paint, {} return paint, {}