Update to builder per review discussion

This commit is contained in:
rsheeter 2020-11-10 21:51:33 -08:00
parent 4171e28f32
commit cf2097f7c0
2 changed files with 262 additions and 219 deletions

View File

@ -152,7 +152,6 @@ def buildCOLR(
if colorGlyphsV1: if colorGlyphsV1:
colr.LayerV1List, colr.BaseGlyphV1List = buildColrV1(colorGlyphsV1, glyphMap) colr.LayerV1List, colr.BaseGlyphV1List = buildColrV1(colorGlyphsV1, glyphMap)
if version is None: if version is None:
version = 1 if (varStore or colorGlyphsV1) else 0 version = 1 if (varStore or colorGlyphsV1) else 0
elif version not in (0, 1): elif version not in (0, 1):
@ -369,15 +368,6 @@ def buildColorIndex(
return self return self
def buildPaintSolid(
paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA
) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintSolid)
self.Color = buildColorIndex(paletteIndex, alpha)
return self
def buildColorStop( def buildColorStop(
offset: _ScalarInput, offset: _ScalarInput,
paletteIndex: int, paletteIndex: int,
@ -433,17 +423,6 @@ def _to_color_line(obj):
raise TypeError(obj) raise TypeError(obj)
_PAINT_BUILDERS = {
1: lambda _, kwargs: buildPaintSolid(**kwargs),
2: lambda _, kwargs: buildPaintLinearGradient(**kwargs),
3: lambda _, kwargs: buildPaintRadialGradient(**kwargs),
4: lambda builder, kwargs: buildPaintGlyph(builder, **kwargs),
5: lambda _, kwargs: buildPaintColrGlyph(**kwargs),
6: lambda builder, kwargs: buildPaintTransform(builder, **kwargs),
7: lambda builder, kwargs: buildPaintComposite(builder, **kwargs),
}
def _as_tuple(obj) -> Tuple[Any, ...]: def _as_tuple(obj) -> Tuple[Any, ...]:
# start simple, who even cares about cyclic graphs or interesting field types # start simple, who even cares about cyclic graphs or interesting field types
def _tuple_safe(value): def _tuple_safe(value):
@ -454,6 +433,7 @@ def _as_tuple(obj) -> Tuple[Any, ...]:
elif isinstance(value, collections.abc.MutableSequence): elif isinstance(value, collections.abc.MutableSequence):
return tuple(_tuple_safe(e) for e in value) return tuple(_tuple_safe(e) for e in value)
return value return value
return tuple(_tuple_safe(obj)) return tuple(_tuple_safe(obj))
@ -468,7 +448,7 @@ def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
yield (lbound, ubound) yield (lbound, ubound)
class LayerCollector: class LayerV1ListBuilder:
slices: List[ot.Paint] slices: List[ot.Paint]
layers: List[ot.Paint] layers: List[ot.Paint]
reusePool: Mapping[Tuple[Any, ...], int] reusePool: Mapping[Tuple[Any, ...], int]
@ -478,21 +458,107 @@ class LayerCollector:
self.layers = [] self.layers = []
self.reusePool = {} self.reusePool = {}
def buildColrLayers(self, paints: List[_PaintInput]) -> ot.Paint: def buildPaintSolid(
paint = ot.Paint() self, paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA
paint.Format = int(ot.Paint.Format.PaintColrLayers) ) -> ot.Paint:
self.slices.append(paint) ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintSolid)
ot_paint.Color = buildColorIndex(paletteIndex, alpha)
return ot_paint
paints = [self.build(p) for p in paints] def buildPaintLinearGradient(
self,
colorLine: _ColorLineInput,
p0: _PointTuple,
p1: _PointTuple,
p2: Optional[_PointTuple] = None,
) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintLinearGradient)
ot_paint.ColorLine = _to_color_line(colorLine)
if p2 is None:
p2 = copy.copy(p1)
for i, (x, y) in enumerate((p0, p1, p2)):
setattr(ot_paint, f"x{i}", _to_variable_int16(x))
setattr(ot_paint, f"y{i}", _to_variable_int16(y))
return ot_paint
def buildPaintRadialGradient(
self,
colorLine: _ColorLineInput,
c0: _PointTuple,
c1: _PointTuple,
r0: _ScalarInput,
r1: _ScalarInput,
) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintRadialGradient)
ot_paint.ColorLine = _to_color_line(colorLine)
for i, (x, y), r in [(0, c0, r0), (1, c1, r1)]:
setattr(ot_paint, f"x{i}", _to_variable_int16(x))
setattr(ot_paint, f"y{i}", _to_variable_int16(y))
setattr(ot_paint, f"r{i}", _to_variable_uint16(r))
return ot_paint
def buildPaintGlyph(self, glyph: str, paint: _PaintInput) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintGlyph)
ot_paint.Glyph = glyph
ot_paint.Paint = self.buildPaint(paint)
return ot_paint
def buildPaintColrGlyph(self, glyph: str) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintColrGlyph)
ot_paint.Glyph = glyph
return ot_paint
def buildPaintTransform(
self, transform: _AffineInput, paint: _PaintInput
) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintTransform)
if not isinstance(transform, ot.Affine2x3):
transform = buildAffine2x3(transform)
ot_paint.Transform = transform
ot_paint.Paint = self.buildPaint(paint)
return ot_paint
def buildPaintComposite(
self,
mode: _CompositeInput,
source: _PaintInput,
backdrop: _PaintInput,
):
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintComposite)
ot_paint.SourcePaint = self.buildPaint(source)
ot_paint.CompositeMode = _to_composite_mode(mode)
ot_paint.BackdropPaint = self.buildPaint(backdrop)
return ot_paint
def buildColrLayers(self, paints: List[_PaintInput]) -> ot.Paint:
ot_paint = ot.Paint()
ot_paint.Format = int(ot.Paint.Format.PaintColrLayers)
self.slices.append(ot_paint)
paints = [self.buildPaint(p) for p in paints]
# Look for reuse, with preference to longer sequences # Look for reuse, with preference to longer sequences
found_reuse = True found_reuse = True
while found_reuse: while found_reuse:
found_reuse = False found_reuse = False
ranges = sorted(_reuse_ranges(len(paints)), ranges = sorted(
_reuse_ranges(len(paints)),
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: for lbound, ubound in ranges:
reuse_lbound = self.reusePool.get(_as_tuple(paints[lbound:ubound]), -1) reuse_lbound = self.reusePool.get(_as_tuple(paints[lbound:ubound]), -1)
if reuse_lbound == -1: if reuse_lbound == -1:
@ -505,62 +571,56 @@ class LayerCollector:
found_reuse = True found_reuse = True
break break
paint.NumLayers = len(paints) ot_paint.NumLayers = len(paints)
paint.FirstLayerIndex = len(self.layers) ot_paint.FirstLayerIndex = len(self.layers)
self.layers.extend(paints) self.layers.extend(paints)
# Register our parts for reuse # Register our parts for reuse
for lbound, ubound in _reuse_ranges(len(paints)): for lbound, ubound in _reuse_ranges(len(paints)):
self.reusePool[_as_tuple(paints[lbound:ubound])] = lbound + paint.FirstLayerIndex self.reusePool[_as_tuple(paints[lbound:ubound])] = (
lbound + ot_paint.FirstLayerIndex
)
return paint return ot_paint
def build(self, paint: _PaintInput) -> ot.Paint: def buildPaint(self, paint: _PaintInput) -> ot.Paint:
if isinstance(paint, ot.Paint): if isinstance(paint, ot.Paint):
return paint return paint
elif isinstance(paint, int): elif isinstance(paint, int):
paletteIndex = paint paletteIndex = paint
return buildPaintSolid(paletteIndex) return self.buildPaintSolid(paletteIndex)
elif isinstance(paint, tuple): elif isinstance(paint, tuple):
layerGlyph, paint = paint layerGlyph, paint = paint
return buildPaintGlyph(self, layerGlyph, paint) return self.buildPaintGlyph(layerGlyph, paint)
elif isinstance(paint, list): elif isinstance(paint, list):
# implicit PaintColrLayers for a list of > 1 # implicit PaintColrLayers for a list of > 1
if len(paint) == 0: if len(paint) == 0:
raise ValueError("An empty list is hard to paint") raise ValueError("An empty list is hard to paint")
elif len(paint) == 1: elif len(paint) == 1:
return self.build(paint[0]) return self.buildPaint(paint[0])
else: else:
return self.buildColrLayers(paint) return self.buildColrLayers(paint)
elif isinstance(paint, collections.abc.Mapping): elif isinstance(paint, collections.abc.Mapping):
kwargs = dict(paint) kwargs = dict(paint)
fmt = kwargs.pop("format") fmt = kwargs.pop("format")
try: try:
return _PAINT_BUILDERS[fmt](self, kwargs) return LayerV1ListBuilder._buildFunctions[fmt](self, **kwargs)
except KeyError: except KeyError:
raise NotImplementedError(fmt) raise NotImplementedError(fmt)
raise TypeError( raise TypeError(f"Not sure what to do with {type(paint).__name__}: {paint!r}")
f"Not sure what to do with {type(paint).__name__}: {paint!r}"
) def build(self) -> ot.LayerV1List:
layers = ot.LayerV1List()
layers.LayerCount = len(self.layers)
layers.Paint = self.layers
return layers
def buildPaintLinearGradient( LayerV1ListBuilder._buildFunctions = {
colorLine: _ColorLineInput, pf.value: getattr(LayerV1ListBuilder, "build" + pf.name)
p0: _PointTuple, for pf in ot.Paint.Format
p1: _PointTuple, if pf != ot.Paint.Format.PaintColrLayers
p2: Optional[_PointTuple] = None, }
) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintLinearGradient)
self.ColorLine = _to_color_line(colorLine)
if p2 is None:
p2 = copy.copy(p1)
for i, (x, y) in enumerate((p0, p1, p2)):
setattr(self, f"x{i}", _to_variable_int16(x))
setattr(self, f"y{i}", _to_variable_int16(y))
return self
def buildAffine2x3(transform: _AffineTuple) -> ot.Affine2x3: def buildAffine2x3(transform: _AffineTuple) -> ot.Affine2x3:
@ -581,70 +641,12 @@ def buildAffine2x3(transform: _AffineTuple) -> ot.Affine2x3:
return self return self
def buildPaintRadialGradient(
colorLine: _ColorLineInput,
c0: _PointTuple,
c1: _PointTuple,
r0: _ScalarInput,
r1: _ScalarInput,
) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintRadialGradient)
self.ColorLine = _to_color_line(colorLine)
for i, (x, y), r in [(0, c0, r0), (1, c1, r1)]:
setattr(self, f"x{i}", _to_variable_int16(x))
setattr(self, f"y{i}", _to_variable_int16(y))
setattr(self, f"r{i}", _to_variable_uint16(r))
return self
def buildPaintGlyph(layerCollector: LayerCollector, glyph: str, paint: _PaintInput) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintGlyph)
self.Glyph = glyph
self.Paint = layerCollector.build(paint)
return self
def buildPaintColrGlyph(
glyph: str
) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintColrGlyph)
self.Glyph = glyph
return self
def buildPaintTransform(layerCollector: LayerCollector, transform: _AffineInput, paint: _PaintInput) -> ot.Paint:
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintTransform)
if not isinstance(transform, ot.Affine2x3):
transform = buildAffine2x3(transform)
self.Transform = transform
self.Paint = layerCollector.build(paint)
return self
def buildPaintComposite(
layerCollector: LayerCollector, mode: _CompositeInput, source: _PaintInput, backdrop: _PaintInput
):
self = ot.Paint()
self.Format = int(ot.Paint.Format.PaintComposite)
self.SourcePaint = layerCollector.build(source)
self.CompositeMode = _to_composite_mode(mode)
self.BackdropPaint = layerCollector.build(backdrop)
return self
def buildBaseGlyphV1Record( def buildBaseGlyphV1Record(
baseGlyph: str, layerCollector: LayerCollector, paint: _PaintInput baseGlyph: str, layerBuilder: LayerV1ListBuilder, paint: _PaintInput
) -> ot.BaseGlyphV1List: ) -> ot.BaseGlyphV1List:
self = ot.BaseGlyphV1Record() self = ot.BaseGlyphV1Record()
self.BaseGlyph = baseGlyph self.BaseGlyph = baseGlyph
self.Paint = layerCollector.build(paint) self.Paint = layerBuilder.buildPaint(paint)
return self return self
@ -668,10 +670,10 @@ def buildColrV1(
errors = {} errors = {}
baseGlyphs = [] baseGlyphs = []
layerCollector = LayerCollector() layerBuilder = LayerV1ListBuilder()
for baseGlyph, paint in colorGlyphItems: for baseGlyph, paint in colorGlyphItems:
try: try:
baseGlyphs.append(buildBaseGlyphV1Record(baseGlyph, layerCollector, paint)) baseGlyphs.append(buildBaseGlyphV1Record(baseGlyph, layerBuilder, paint))
except (ColorLibError, OverflowError, ValueError, TypeError) as e: except (ColorLibError, OverflowError, ValueError, TypeError) as e:
errors[baseGlyph] = e errors[baseGlyph] = e
@ -682,9 +684,7 @@ def buildColrV1(
exc.errors = errors exc.errors = errors
raise exc from next(iter(errors.values())) raise exc from next(iter(errors.values()))
layers = ot.LayerV1List() layers = layerBuilder.build()
layers.LayerCount = len(layerCollector.layers)
layers.Paint = layerCollector.layers
glyphs = ot.BaseGlyphV1List() glyphs = ot.BaseGlyphV1List()
glyphs.BaseGlyphCount = len(baseGlyphs) glyphs.BaseGlyphCount = len(baseGlyphs)
glyphs.BaseGlyphV1Record = baseGlyphs glyphs.BaseGlyphV1Record = baseGlyphs

View File

@ -1,7 +1,7 @@
from fontTools.ttLib import newTable from fontTools.ttLib import newTable
from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables import otTables as ot
from fontTools.colorLib import builder from fontTools.colorLib import builder
from fontTools.colorLib.builder import LayerCollector from fontTools.colorLib.builder import LayerV1ListBuilder
from fontTools.colorLib.errors import ColorLibError from fontTools.colorLib.errors import ColorLibError
import pytest import pytest
from typing import List from typing import List
@ -208,19 +208,21 @@ def test_buildColorIndex():
def test_buildPaintSolid(): def test_buildPaintSolid():
p = builder.buildPaintSolid(0) p = LayerV1ListBuilder().buildPaintSolid(0)
assert p.Format == ot.Paint.Format.PaintSolid assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 0 assert p.Color.PaletteIndex == 0
assert p.Color.Alpha.value == 1.0 assert p.Color.Alpha.value == 1.0
assert p.Color.Alpha.varIdx == 0 assert p.Color.Alpha.varIdx == 0
p = builder.buildPaintSolid(1, alpha=0.5) p = LayerV1ListBuilder().buildPaintSolid(1, alpha=0.5)
assert p.Format == ot.Paint.Format.PaintSolid assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 1 assert p.Color.PaletteIndex == 1
assert p.Color.Alpha.value == 0.5 assert p.Color.Alpha.value == 0.5
assert p.Color.Alpha.varIdx == 0 assert p.Color.Alpha.varIdx == 0
p = builder.buildPaintSolid(3, alpha=builder.VariableFloat(0.5, varIdx=2)) p = LayerV1ListBuilder().buildPaintSolid(
3, alpha=builder.VariableFloat(0.5, varIdx=2)
)
assert p.Format == ot.Paint.Format.PaintSolid assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 3 assert p.Color.PaletteIndex == 3
assert p.Color.Alpha.value == 0.5 assert p.Color.Alpha.value == 0.5
@ -297,6 +299,7 @@ def test_buildAffine2x3():
def test_buildPaintLinearGradient(): def test_buildPaintLinearGradient():
layerBuilder = LayerV1ListBuilder()
color_stops = [ color_stops = [
builder.buildColorStop(0.0, 0), builder.buildColorStop(0.0, 0),
builder.buildColorStop(0.5, 1), builder.buildColorStop(0.5, 1),
@ -306,23 +309,24 @@ def test_buildPaintLinearGradient():
p0 = (builder.VariableInt(100), builder.VariableInt(200)) p0 = (builder.VariableInt(100), builder.VariableInt(200))
p1 = (builder.VariableInt(150), builder.VariableInt(250)) p1 = (builder.VariableInt(150), builder.VariableInt(250))
gradient = builder.buildPaintLinearGradient(color_line, p0, p1) gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1)
assert gradient.Format == 2 assert gradient.Format == 3
assert gradient.ColorLine == color_line assert gradient.ColorLine == color_line
assert (gradient.x0, gradient.y0) == p0 assert (gradient.x0, gradient.y0) == p0
assert (gradient.x1, gradient.y1) == p1 assert (gradient.x1, gradient.y1) == p1
assert (gradient.x2, gradient.y2) == p1 assert (gradient.x2, gradient.y2) == p1
gradient = builder.buildPaintLinearGradient({"stops": color_stops}, p0, p1) gradient = layerBuilder.buildPaintLinearGradient({"stops": color_stops}, p0, p1)
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
assert gradient.ColorLine.ColorStop == color_stops assert gradient.ColorLine.ColorStop == color_stops
gradient = builder.buildPaintLinearGradient(color_line, p0, p1, p2=(150, 230)) gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1, p2=(150, 230))
assert (gradient.x2.value, gradient.y2.value) == (150, 230) assert (gradient.x2.value, gradient.y2.value) == (150, 230)
assert (gradient.x2, gradient.y2) != (gradient.x1, gradient.y1) assert (gradient.x2, gradient.y2) != (gradient.x1, gradient.y1)
def test_buildPaintRadialGradient(): def test_buildPaintRadialGradient():
layerBuilder = LayerV1ListBuilder()
color_stops = [ color_stops = [
builder.buildColorStop(0.0, 0), builder.buildColorStop(0.0, 0),
builder.buildColorStop(0.5, 1), builder.buildColorStop(0.5, 1),
@ -334,7 +338,7 @@ def test_buildPaintRadialGradient():
r0 = builder.VariableInt(10) r0 = builder.VariableInt(10)
r1 = builder.VariableInt(5) r1 = builder.VariableInt(5)
gradient = builder.buildPaintRadialGradient(color_line, c0, c1, r0, r1) gradient = layerBuilder.buildPaintRadialGradient(color_line, c0, c1, r0, r1)
assert gradient.Format == ot.Paint.Format.PaintRadialGradient assert gradient.Format == ot.Paint.Format.PaintRadialGradient
assert gradient.ColorLine == color_line assert gradient.ColorLine == color_line
assert (gradient.x0, gradient.y0) == c0 assert (gradient.x0, gradient.y0) == c0
@ -342,29 +346,31 @@ def test_buildPaintRadialGradient():
assert gradient.r0 == r0 assert gradient.r0 == r0
assert gradient.r1 == r1 assert gradient.r1 == r1
gradient = builder.buildPaintRadialGradient({"stops": color_stops}, c0, c1, r0, r1) gradient = layerBuilder.buildPaintRadialGradient(
{"stops": color_stops}, c0, c1, r0, r1
)
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
assert gradient.ColorLine.ColorStop == color_stops assert gradient.ColorLine.ColorStop == color_stops
def test_buildPaintGlyph_Solid(): def test_buildPaintGlyph_Solid():
collector = LayerCollector() layerBuilder = LayerV1ListBuilder()
layer = builder.buildPaintGlyph(collector, "a", 2) layer = layerBuilder.buildPaintGlyph("a", 2)
assert layer.Glyph == "a" assert layer.Glyph == "a"
assert layer.Paint.Format == ot.Paint.Format.PaintSolid assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 2 assert layer.Paint.Color.PaletteIndex == 2
layer = builder.buildPaintGlyph(collector, "a", builder.buildPaintSolid(3, 0.9)) layer = layerBuilder.buildPaintGlyph("a", layerBuilder.buildPaintSolid(3, 0.9))
assert layer.Paint.Format == ot.Paint.Format.PaintSolid assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 3 assert layer.Paint.Color.PaletteIndex == 3
assert layer.Paint.Color.Alpha.value == 0.9 assert layer.Paint.Color.Alpha.value == 0.9
def test_buildPaintGlyph_LinearGradient(): def test_buildPaintGlyph_LinearGradient():
layer = builder.buildPaintGlyph( layerBuilder = LayerV1ListBuilder()
LayerCollector(), layer = layerBuilder.buildPaintGlyph(
"a", "a",
builder.buildPaintLinearGradient( layerBuilder.buildPaintLinearGradient(
{"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250) {"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250)
), ),
) )
@ -380,10 +386,10 @@ def test_buildPaintGlyph_LinearGradient():
def test_buildPaintGlyph_RadialGradient(): def test_buildPaintGlyph_RadialGradient():
layer = builder.buildPaintGlyph( layerBuilder = LayerV1ListBuilder()
LayerCollector(), layer = layerBuilder.buildPaintGlyph(
"a", "a",
builder.buildPaintRadialGradient( layerBuilder.buildPaintRadialGradient(
{ {
"stops": [ "stops": [
(0.0, 5), (0.0, 5),
@ -414,18 +420,19 @@ def test_buildPaintGlyph_RadialGradient():
def test_buildPaintGlyph_Dict_Solid(): def test_buildPaintGlyph_Dict_Solid():
layer = builder.buildPaintGlyph(LayerCollector(), "a", {"format": 1, "paletteIndex": 0}) layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph("a", {"format": 2, "paletteIndex": 0})
assert layer.Glyph == "a" assert layer.Glyph == "a"
assert layer.Paint.Format == ot.Paint.Format.PaintSolid assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 0 assert layer.Paint.Color.PaletteIndex == 0
def test_buildPaintGlyph_Dict_LinearGradient(): def test_buildPaintGlyph_Dict_LinearGradient():
layer = builder.buildPaintGlyph( layerBuilder = LayerV1ListBuilder()
LayerCollector(), layer = layerBuilder.buildPaintGlyph(
"a", "a",
{ {
"format": 2, "format": 3,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]}, "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"p0": (0, 0), "p0": (0, 0),
"p1": (10, 10), "p1": (10, 10),
@ -436,11 +443,11 @@ def test_buildPaintGlyph_Dict_LinearGradient():
def test_buildPaintGlyph_Dict_RadialGradient(): def test_buildPaintGlyph_Dict_RadialGradient():
layer = builder.buildPaintGlyph( layerBuilder = LayerV1ListBuilder()
LayerCollector(), layer = layerBuilder.buildPaintGlyph(
"a", "a",
{ {
"format": 3, "format": 4,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]}, "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"c0": (0, 0), "c0": (0, 0),
"c1": (10, 10), "c1": (10, 10),
@ -453,36 +460,36 @@ def test_buildPaintGlyph_Dict_RadialGradient():
def test_buildPaintColrGlyph(): def test_buildPaintColrGlyph():
paint = builder.buildPaintColrGlyph("a") paint = LayerV1ListBuilder().buildPaintColrGlyph("a")
assert paint.Format == ot.Paint.Format.PaintColrGlyph assert paint.Format == ot.Paint.Format.PaintColrGlyph
assert paint.Glyph == "a" assert paint.Glyph == "a"
def test_buildPaintTransform(): def test_buildPaintTransform():
paint = builder.buildPaintTransform( layerBuilder = LayerV1ListBuilder()
layerCollector=LayerCollector(), paint = layerBuilder.buildPaintTransform(
transform=builder.buildAffine2x3((1, 2, 3, 4, 5, 6)), transform=builder.buildAffine2x3((1, 2, 3, 4, 5, 6)),
paint=builder.buildPaintGlyph( paint=layerBuilder.buildPaintGlyph(
layerCollector=LayerCollector(),
glyph="a", glyph="a",
paint=builder.buildPaintSolid(paletteIndex=0, alpha=1.0), paint=layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0),
), ),
) )
assert paint.Format == ot.Paint.Format.PaintTransform assert paint.Format == ot.Paint.Format.PaintTransform
assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
assert paint.Paint.Paint.Format == ot.Paint.Format.PaintSolid
assert paint.Transform.xx.value == 1.0 assert paint.Transform.xx.value == 1.0
assert paint.Transform.yx.value == 2.0 assert paint.Transform.yx.value == 2.0
assert paint.Transform.xy.value == 3.0 assert paint.Transform.xy.value == 3.0
assert paint.Transform.yy.value == 4.0 assert paint.Transform.yy.value == 4.0
assert paint.Transform.dx.value == 5.0 assert paint.Transform.dx.value == 5.0
assert paint.Transform.dy.value == 6.0 assert paint.Transform.dy.value == 6.0
assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
paint = builder.buildPaintTransform( paint = layerBuilder.buildPaintTransform(
LayerCollector(),
(1, 0, 0, 0.3333, 10, 10), (1, 0, 0, 0.3333, 10, 10),
{ {
"format": 3, "format": 4,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]}, "colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"c0": (100, 100), "c0": (100, 100),
"c1": (100, 100), "c1": (100, 100),
@ -502,18 +509,17 @@ def test_buildPaintTransform():
def test_buildPaintComposite(): def test_buildPaintComposite():
collector = LayerCollector() layerBuilder = LayerV1ListBuilder()
composite = builder.buildPaintComposite( composite = layerBuilder.buildPaintComposite(
layerCollector=collector,
mode=ot.CompositeMode.SRC_OVER, mode=ot.CompositeMode.SRC_OVER,
source={ source={
"format": 7, "format": 8,
"mode": "src_over", "mode": "src_over",
"source": {"format": 4, "glyph": "c", "paint": 2}, "source": {"format": 5, "glyph": "c", "paint": 2},
"backdrop": {"format": 4, "glyph": "b", "paint": 1}, "backdrop": {"format": 5, "glyph": "b", "paint": 1},
}, },
backdrop=builder.buildPaintGlyph( backdrop=layerBuilder.buildPaintGlyph(
collector, "a", builder.buildPaintSolid(paletteIndex=0, alpha=1.0) "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
), ),
) )
@ -541,11 +547,11 @@ def test_buildColrV1():
colorGlyphs = { colorGlyphs = {
"a": [("b", 0), ("c", 1)], "a": [("b", 0), ("c", 1)],
"d": [ "d": [
("e", {"format": 1, "paletteIndex": 2, "alpha": 0.8}), ("e", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
( (
"f", "f",
{ {
"format": 3, "format": 4,
"colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"}, "colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"},
"c0": (0, 0), "c0": (0, 0),
"c1": (0, 0), "c1": (0, 0),
@ -583,6 +589,7 @@ def test_buildColrV1():
def test_split_color_glyphs_by_version(): def test_split_color_glyphs_by_version():
layerBuilder = LayerV1ListBuilder()
colorGlyphs = { colorGlyphs = {
"a": [ "a": [
("b", 0), ("b", 0),
@ -597,7 +604,9 @@ def test_split_color_glyphs_by_version():
assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]} assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]}
assert not colorGlyphsV1 assert not colorGlyphsV1
colorGlyphs = {"a": [("b", builder.buildPaintSolid(paletteIndex=0, alpha=0.0))]} colorGlyphs = {
"a": [("b", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=0.0))]
}
colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs) colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
@ -611,7 +620,7 @@ def test_split_color_glyphs_by_version():
( (
"e", "e",
{ {
"format": 2, "format": 3,
"colorLine": {"stops": [(0.0, 2), (1.0, 3)]}, "colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
"p0": (0, 0), "p0": (0, 0),
"p1": (10, 10), "p1": (10, 10),
@ -647,25 +656,27 @@ def test_build_layerv1list_empty():
colr = builder.buildCOLR( colr = builder.buildCOLR(
{ {
"a": { "a": {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": {"format": 1, "paletteIndex": 2, "alpha": 0.8}, "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
"glyph": "b", "glyph": "b",
}, },
# A list of 1 shouldn't become a PaintColrLayers # A list of 1 shouldn't become a PaintColrLayers
"b": [{ "b": [
"format": 4, # PaintGlyph {
"paint": { "format": 5, # PaintGlyph
"format": 2, "paint": {
"colorLine": { "format": 3,
"stops": [(0.0, 2), (1.0, 3)], "colorLine": {
"extend": "reflect", "stops": [(0.0, 2), (1.0, 3)],
"extend": "reflect",
},
"p0": (1, 2),
"p1": (3, 4),
"p2": (2, 2),
}, },
"p0": (1, 2), "glyph": "bb",
"p1": (3, 4), }
"p2": (2, 2), ],
},
"glyph": "bb",
}],
} }
) )
@ -687,26 +698,28 @@ def _paint_names(paints) -> List[str]:
if paint.Format == int(ot.Paint.Format.PaintGlyph): if paint.Format == int(ot.Paint.Format.PaintGlyph):
result.append(paint.Glyph) result.append(paint.Glyph)
elif paint.Format == int(ot.Paint.Format.PaintColrLayers): elif paint.Format == int(ot.Paint.Format.PaintColrLayers):
result.append(f"Layers[{paint.FirstLayerIndex}:{paint.FirstLayerIndex+paint.NumLayers}]") result.append(
f"Layers[{paint.FirstLayerIndex}:{paint.FirstLayerIndex+paint.NumLayers}]"
)
return result return result
def test_build_layerv1list_simple(): def test_build_layerv1list_simple():
# Two colr glyphs, each with two layers the first of which is common # Two colr glyphs, each with two layers the first of which is common
# All layers use the same solid paint # All layers use the same solid paint
solid_paint = {"format": 1, "paletteIndex": 2, "alpha": 0.8} solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
backdrop = { backdrop = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "back", "glyph": "back",
} }
a_foreground = { a_foreground = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "a_fore", "glyph": "a_fore",
} }
b_foreground = { b_foreground = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "b_fore", "glyph": "b_fore",
} }
@ -733,41 +746,46 @@ def test_build_layerv1list_simple():
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2 assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2 assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2
assert colr.table.LayerV1List.LayerCount == 4 assert colr.table.LayerV1List.LayerCount == 4
assert _paint_names(colr.table.LayerV1List.Paint) == ["back", "a_fore", "back", "b_fore"] assert _paint_names(colr.table.LayerV1List.Paint) == [
"back",
"a_fore",
"back",
"b_fore",
]
def test_build_layerv1list_with_sharing(): def test_build_layerv1list_with_sharing():
# Three colr glyphs, each with two layers in common # Three colr glyphs, each with two layers in common
solid_paint = {"format": 1, "paletteIndex": 2, "alpha": 0.8} solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
backdrop = [ backdrop = [
{ {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "back1", "glyph": "back1",
}, },
{ {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "back2", "glyph": "back2",
}, },
] ]
a_foreground = { a_foreground = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "a_fore", "glyph": "a_fore",
} }
b_background = { b_background = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "b_back", "glyph": "b_back",
} }
b_foreground = { b_foreground = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "b_fore", "glyph": "b_fore",
} }
c_background = { c_background = {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": solid_paint, "paint": solid_paint,
"glyph": "c_back", "glyph": "c_back",
} }
@ -789,17 +807,29 @@ def test_build_layerv1list_with_sharing():
baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 3 assert colr.table.BaseGlyphV1List.BaseGlyphCount == 3
assert len(baseGlyphs) == 3 assert len(baseGlyphs) == 3
assert (_paint_names([b.Paint for b in baseGlyphs]) == assert _paint_names([b.Paint for b in baseGlyphs]) == [
["Layers[0:3]", "Layers[3:6]", "Layers[6:8]"]) "Layers[0:3]",
assert (_paint_names(colr.table.LayerV1List.Paint) == "Layers[3:6]",
["back1", "back2", "a_fore", "b_back", "Layers[0:2]", "b_fore", "c_back", "Layers[0:2]"]) "Layers[6:8]",
]
assert _paint_names(colr.table.LayerV1List.Paint) == [
"back1",
"back2",
"a_fore",
"b_back",
"Layers[0:2]",
"b_fore",
"c_back",
"Layers[0:2]",
]
assert colr.table.LayerV1List.LayerCount == 8 assert colr.table.LayerV1List.LayerCount == 8
def test_build_layerv1list_with_overlaps(): def test_build_layerv1list_with_overlaps():
paints = [ paints = [
{ {
"format": 4, # PaintGlyph "format": 5, # PaintGlyph
"paint": {"format": 1, "paletteIndex": 2, "alpha": 0.8}, "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
"glyph": c, "glyph": c,
} }
for c in "abcdefghi" for c in "abcdefghi"
@ -818,14 +848,29 @@ def test_build_layerv1list_with_overlaps():
assertNoV0Content(colr) assertNoV0Content(colr)
baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
#assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2 # assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
assert (_paint_names(colr.table.LayerV1List.Paint) == assert _paint_names(colr.table.LayerV1List.Paint) == [
["a", "b", "c", "d", "Layers[0:4]", "e", "f", "Layers[2:4]", "Layers[5:7]", "g", "h"]) "a",
assert (_paint_names([b.Paint for b in baseGlyphs]) == "b",
["Layers[0:4]", "Layers[4:7]", "Layers[7:11]"]) "c",
"d",
"Layers[0:4]",
"e",
"f",
"Layers[2:4]",
"Layers[5:7]",
"g",
"h",
]
assert _paint_names([b.Paint for b in baseGlyphs]) == [
"Layers[0:4]",
"Layers[4:7]",
"Layers[7:11]",
]
assert colr.table.LayerV1List.LayerCount == 11 assert colr.table.LayerV1List.LayerCount == 11
class BuildCOLRTest(object): class BuildCOLRTest(object):
def test_automatic_version_all_solid_color_glyphs(self): def test_automatic_version_all_solid_color_glyphs(self):
colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}) colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]})
@ -841,7 +886,7 @@ class BuildCOLRTest(object):
( (
"b", "b",
{ {
"format": 3, "format": 4,
"colorLine": { "colorLine": {
"stops": [(0.0, 0), (1.0, 1)], "stops": [(0.0, 0), (1.0, 1)],
"extend": "repeat", "extend": "repeat",
@ -852,13 +897,13 @@ class BuildCOLRTest(object):
"r1": 2, "r1": 2,
}, },
), ),
("c", {"format": 1, "paletteIndex": 2, "alpha": 0.8}), ("c", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
], ],
"d": [ "d": [
( (
"e", "e",
{ {
"format": 2, "format": 3,
"colorLine": { "colorLine": {
"stops": [(0.0, 2), (1.0, 3)], "stops": [(0.0, 2), (1.0, 3)],
"extend": "reflect", "extend": "reflect",
@ -885,14 +930,14 @@ class BuildCOLRTest(object):
( (
"e", "e",
{ {
"format": 2, "format": 3,
"colorLine": {"stops": [(0.0, 2), (1.0, 3)]}, "colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
"p0": (1, 2), "p0": (1, 2),
"p1": (3, 4), "p1": (3, 4),
"p2": (2, 2), "p2": (2, 2),
}, },
), ),
("f", {"format": 1, "paletteIndex": 2, "alpha": 0.8}), ("f", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
], ],
} }
) )
@ -910,9 +955,7 @@ class BuildCOLRTest(object):
colr.table.BaseGlyphV1List.BaseGlyphV1Record[0], ot.BaseGlyphV1Record colr.table.BaseGlyphV1List.BaseGlyphV1Record[0], ot.BaseGlyphV1Record
) )
assert colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph == "d" assert colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph == "d"
assert isinstance( assert isinstance(colr.table.LayerV1List, ot.LayerV1List)
colr.table.LayerV1List, ot.LayerV1List
)
assert colr.table.LayerV1List.Paint[0].Glyph == "e" assert colr.table.LayerV1List.Paint[0].Glyph == "e"
def test_explicit_version_0(self): def test_explicit_version_0(self):