fonttools/Tests/colorLib/builder_test.py

1058 lines
35 KiB
Python
Raw Normal View History

from fontTools.ttLib import newTable
from fontTools.ttLib.tables import otTables as ot
2020-02-17 11:58:29 +00:00
from fontTools.colorLib import builder
from fontTools.colorLib.builder import LayerV1ListBuilder
2020-02-17 11:58:29 +00:00
from fontTools.colorLib.errors import ColorLibError
import pytest
from typing import List
2020-02-17 11:58:29 +00:00
def test_buildCOLR_v0():
color_layer_lists = {
"a": [("a.color0", 0), ("a.color1", 1)],
"b": [("b.color1", 1), ("b.color0", 0)],
}
colr = builder.buildCOLR(color_layer_lists)
assert colr.tableTag == "COLR"
assert colr.version == 0
assert colr.ColorLayers["a"][0].name == "a.color0"
assert colr.ColorLayers["a"][0].colorID == 0
assert colr.ColorLayers["a"][1].name == "a.color1"
assert colr.ColorLayers["a"][1].colorID == 1
assert colr.ColorLayers["b"][0].name == "b.color1"
assert colr.ColorLayers["b"][0].colorID == 1
assert colr.ColorLayers["b"][1].name == "b.color0"
assert colr.ColorLayers["b"][1].colorID == 0
def test_buildCOLR_v0_layer_as_list():
# when COLRv0 layers are encoded as plist in UFO lib, both python tuples and
# lists are encoded as plist array elements; but the latter are always decoded
# as python lists, thus after roundtripping a plist tuples become lists.
# Before FontTools 4.17.0 we were treating tuples and lists as equivalent;
# with 4.17.0, a paint of type list is used to identify a PaintColrLayers.
# This broke backward compatibility as ufo2ft is simply passing through the
# color layers as read from the UFO lib plist, and as such the latter use lists
# instead of tuples for COLRv0 layers (layerGlyph, paletteIndex) combo.
# We restore backward compat by accepting either tuples or lists (of length 2
# and only containing a str and an int) as individual top-level layers.
# https://github.com/googlefonts/ufo2ft/issues/426
color_layer_lists = {
"a": [["a.color0", 0], ["a.color1", 1]],
"b": [["b.color1", 1], ["b.color0", 0]],
}
colr = builder.buildCOLR(color_layer_lists)
assert colr.tableTag == "COLR"
2020-02-17 11:58:29 +00:00
assert colr.version == 0
assert colr.ColorLayers["a"][0].name == "a.color0"
assert colr.ColorLayers["a"][0].colorID == 0
assert colr.ColorLayers["a"][1].name == "a.color1"
assert colr.ColorLayers["a"][1].colorID == 1
assert colr.ColorLayers["b"][0].name == "b.color1"
assert colr.ColorLayers["b"][0].colorID == 1
assert colr.ColorLayers["b"][1].name == "b.color0"
assert colr.ColorLayers["b"][1].colorID == 0
def test_buildCPAL_v0():
palettes = [
[(0.68, 0.20, 0.32, 1.0), (0.45, 0.68, 0.21, 1.0)],
[(0.68, 0.20, 0.32, 0.6), (0.45, 0.68, 0.21, 0.6)],
[(0.68, 0.20, 0.32, 0.3), (0.45, 0.68, 0.21, 0.3)],
]
cpal = builder.buildCPAL(palettes)
assert cpal.tableTag == "CPAL"
assert cpal.version == 0
assert cpal.numPaletteEntries == 2
assert len(cpal.palettes) == 3
assert [tuple(c) for c in cpal.palettes[0]] == [
(82, 51, 173, 255),
(54, 173, 115, 255),
]
assert [tuple(c) for c in cpal.palettes[1]] == [
(82, 51, 173, 153),
(54, 173, 115, 153),
]
assert [tuple(c) for c in cpal.palettes[2]] == [
(82, 51, 173, 76),
(54, 173, 115, 76),
]
def test_buildCPAL_palettes_different_lengths():
with pytest.raises(ColorLibError, match="have different lengths"):
builder.buildCPAL([[(1, 1, 1, 1)], [(0, 0, 0, 1), (0.5, 0.5, 0.5, 1)]])
def test_buildPaletteLabels():
name_table = newTable("name")
name_table.names = []
name_ids = builder.buildPaletteLabels(
[None, "hi", {"en": "hello", "de": "hallo"}], name_table
)
assert name_ids == [0xFFFF, 256, 257]
assert len(name_table.names) == 3
assert str(name_table.names[0]) == "hi"
assert name_table.names[0].nameID == 256
assert str(name_table.names[1]) == "hallo"
assert name_table.names[1].nameID == 257
assert str(name_table.names[2]) == "hello"
assert name_table.names[2].nameID == 257
def test_build_CPAL_v1_types_no_labels():
palettes = [
[(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
[(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
[(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
]
paletteTypes = [
builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND,
builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND
| builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
]
cpal = builder.buildCPAL(palettes, paletteTypes=paletteTypes)
assert cpal.tableTag == "CPAL"
assert cpal.version == 1
assert cpal.numPaletteEntries == 2
assert len(cpal.palettes) == 3
assert cpal.paletteTypes == paletteTypes
assert cpal.paletteLabels == [cpal.NO_NAME_ID] * len(palettes)
assert cpal.paletteEntryLabels == [cpal.NO_NAME_ID] * cpal.numPaletteEntries
def test_build_CPAL_v1_labels():
palettes = [
[(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
[(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
[(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
]
paletteLabels = ["First", {"en": "Second", "it": "Seconda"}, None]
paletteEntryLabels = ["Foo", "Bar"]
with pytest.raises(TypeError, match="nameTable is required"):
builder.buildCPAL(palettes, paletteLabels=paletteLabels)
with pytest.raises(TypeError, match="nameTable is required"):
builder.buildCPAL(palettes, paletteEntryLabels=paletteEntryLabels)
name_table = newTable("name")
name_table.names = []
cpal = builder.buildCPAL(
palettes,
paletteLabels=paletteLabels,
paletteEntryLabels=paletteEntryLabels,
nameTable=name_table,
)
assert cpal.tableTag == "CPAL"
assert cpal.version == 1
assert cpal.numPaletteEntries == 2
assert len(cpal.palettes) == 3
assert cpal.paletteTypes == [cpal.DEFAULT_PALETTE_TYPE] * len(palettes)
assert cpal.paletteLabels == [256, 257, cpal.NO_NAME_ID]
assert cpal.paletteEntryLabels == [258, 259]
assert name_table.getDebugName(256) == "First"
assert name_table.getDebugName(257) == "Second"
assert name_table.getDebugName(258) == "Foo"
assert name_table.getDebugName(259) == "Bar"
def test_invalid_ColorPaletteType():
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
builder.ColorPaletteType(-1)
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
builder.ColorPaletteType(4)
with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
builder.ColorPaletteType("abc")
def test_buildCPAL_v1_invalid_args_length():
with pytest.raises(ColorLibError, match="Expected 2 paletteTypes, got 1"):
builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, 1, 1)]], paletteTypes=[1])
with pytest.raises(ColorLibError, match="Expected 2 paletteLabels, got 1"):
builder.buildCPAL(
[[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
paletteLabels=["foo"],
nameTable=newTable("name"),
)
with pytest.raises(ColorLibError, match="Expected 1 paletteEntryLabels, got 0"):
cpal = builder.buildCPAL(
[[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
paletteEntryLabels=[],
nameTable=newTable("name"),
)
def test_buildCPAL_invalid_color():
with pytest.raises(
ColorLibError,
match=r"In palette\[0\]\[1\]: expected \(R, G, B, A\) tuple, got \(1, 1, 1\)",
):
builder.buildCPAL([[(1, 1, 1, 1), (1, 1, 1)]])
with pytest.raises(
ColorLibError,
match=(
r"palette\[1\]\[0\] has invalid out-of-range "
r"\[0..1\] color: \(1, 1, -1, 2\)"
),
):
builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, -1, 2)]])
def test_buildColorIndex():
c = builder.buildColorIndex(0)
assert c.PaletteIndex == 0
assert c.Alpha.value == 1.0
assert c.Alpha.varIdx == 0
c = builder.buildColorIndex(1, alpha=0.5)
assert c.PaletteIndex == 1
assert c.Alpha.value == 0.5
assert c.Alpha.varIdx == 0
c = builder.buildColorIndex(3, alpha=builder.VariableFloat(0.5, varIdx=2))
assert c.PaletteIndex == 3
assert c.Alpha.value == 0.5
assert c.Alpha.varIdx == 2
def test_buildPaintSolid():
p = LayerV1ListBuilder().buildPaintSolid(0)
assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 0
assert p.Color.Alpha.value == 1.0
assert p.Color.Alpha.varIdx == 0
p = LayerV1ListBuilder().buildPaintSolid(1, alpha=0.5)
assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 1
assert p.Color.Alpha.value == 0.5
assert p.Color.Alpha.varIdx == 0
p = LayerV1ListBuilder().buildPaintSolid(
3, alpha=builder.VariableFloat(0.5, varIdx=2)
)
assert p.Format == ot.Paint.Format.PaintSolid
assert p.Color.PaletteIndex == 3
assert p.Color.Alpha.value == 0.5
assert p.Color.Alpha.varIdx == 2
def test_buildColorStop():
s = builder.buildColorStop(0.1, 2)
assert s.StopOffset == builder.VariableFloat(0.1)
assert s.Color.PaletteIndex == 2
assert s.Color.Alpha == builder._DEFAULT_ALPHA
s = builder.buildColorStop(offset=0.2, paletteIndex=3, alpha=0.4)
assert s.StopOffset == builder.VariableFloat(0.2)
assert s.Color == builder.buildColorIndex(3, alpha=0.4)
s = builder.buildColorStop(
offset=builder.VariableFloat(0.0, varIdx=1),
paletteIndex=0,
alpha=builder.VariableFloat(0.3, varIdx=2),
)
assert s.StopOffset == builder.VariableFloat(0.0, varIdx=1)
assert s.Color.PaletteIndex == 0
assert s.Color.Alpha == builder.VariableFloat(0.3, varIdx=2)
def test_buildColorLine():
stops = [(0.0, 0), (0.5, 1), (1.0, 2)]
cline = builder.buildColorLine(stops)
assert cline.Extend == builder.ExtendMode.PAD
assert cline.StopCount == 3
assert [
(cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
] == stops
cline = builder.buildColorLine(stops, extend="pad")
assert cline.Extend == builder.ExtendMode.PAD
cline = builder.buildColorLine(stops, extend=builder.ExtendMode.REPEAT)
assert cline.Extend == builder.ExtendMode.REPEAT
cline = builder.buildColorLine(stops, extend=builder.ExtendMode.REFLECT)
assert cline.Extend == builder.ExtendMode.REFLECT
cline = builder.buildColorLine([builder.buildColorStop(*s) for s in stops])
assert [
(cs.StopOffset.value, cs.Color.PaletteIndex) for cs in cline.ColorStop
] == stops
stops = [
{"offset": (0.0, 1), "paletteIndex": 0, "alpha": (0.5, 2)},
{"offset": (1.0, 3), "paletteIndex": 1, "alpha": (0.3, 4)},
]
cline = builder.buildColorLine(stops)
assert [
{
"offset": cs.StopOffset,
"paletteIndex": cs.Color.PaletteIndex,
"alpha": cs.Color.Alpha,
}
for cs in cline.ColorStop
] == stops
def test_buildAffine2x3():
matrix = builder.buildAffine2x3((1.5, 0, 0.5, 2.0, 1.0, -3.0))
assert matrix.xx == builder.VariableFloat(1.5)
assert matrix.yx == builder.VariableFloat(0.0)
assert matrix.xy == builder.VariableFloat(0.5)
assert matrix.yy == builder.VariableFloat(2.0)
assert matrix.dx == builder.VariableFloat(1.0)
assert matrix.dy == builder.VariableFloat(-3.0)
def test_buildPaintLinearGradient():
layerBuilder = LayerV1ListBuilder()
color_stops = [
builder.buildColorStop(0.0, 0),
builder.buildColorStop(0.5, 1),
builder.buildColorStop(1.0, 2, alpha=0.8),
]
color_line = builder.buildColorLine(color_stops, extend=builder.ExtendMode.REPEAT)
p0 = (builder.VariableInt(100), builder.VariableInt(200))
p1 = (builder.VariableInt(150), builder.VariableInt(250))
gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1)
assert gradient.Format == 3
assert gradient.ColorLine == color_line
assert (gradient.x0, gradient.y0) == p0
assert (gradient.x1, gradient.y1) == p1
assert (gradient.x2, gradient.y2) == p1
gradient = layerBuilder.buildPaintLinearGradient({"stops": color_stops}, p0, p1)
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
assert gradient.ColorLine.ColorStop == color_stops
gradient = layerBuilder.buildPaintLinearGradient(color_line, p0, p1, p2=(150, 230))
assert (gradient.x2.value, gradient.y2.value) == (150, 230)
assert (gradient.x2, gradient.y2) != (gradient.x1, gradient.y1)
def test_buildPaintRadialGradient():
layerBuilder = LayerV1ListBuilder()
color_stops = [
builder.buildColorStop(0.0, 0),
builder.buildColorStop(0.5, 1),
builder.buildColorStop(1.0, 2, alpha=0.8),
]
color_line = builder.buildColorLine(color_stops, extend=builder.ExtendMode.REPEAT)
c0 = (builder.VariableInt(100), builder.VariableInt(200))
c1 = (builder.VariableInt(150), builder.VariableInt(250))
r0 = builder.VariableInt(10)
r1 = builder.VariableInt(5)
gradient = layerBuilder.buildPaintRadialGradient(color_line, c0, c1, r0, r1)
assert gradient.Format == ot.Paint.Format.PaintRadialGradient
assert gradient.ColorLine == color_line
assert (gradient.x0, gradient.y0) == c0
assert (gradient.x1, gradient.y1) == c1
assert gradient.r0 == r0
assert gradient.r1 == r1
gradient = layerBuilder.buildPaintRadialGradient(
{"stops": color_stops}, c0, c1, r0, r1
)
assert gradient.ColorLine.Extend == builder.ExtendMode.PAD
assert gradient.ColorLine.ColorStop == color_stops
def test_buildPaintGlyph_Solid():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph("a", 2)
assert layer.Glyph == "a"
assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 2
layer = layerBuilder.buildPaintGlyph("a", layerBuilder.buildPaintSolid(3, 0.9))
assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 3
assert layer.Paint.Color.Alpha.value == 0.9
def test_buildPaintGlyph_LinearGradient():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph(
"a",
layerBuilder.buildPaintLinearGradient(
{"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250)
),
)
assert layer.Paint.Format == ot.Paint.Format.PaintLinearGradient
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 3
assert layer.Paint.ColorLine.ColorStop[1].StopOffset.value == 1.0
assert layer.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 4
assert layer.Paint.x0.value == 100
assert layer.Paint.y0.value == 200
assert layer.Paint.x1.value == 150
assert layer.Paint.y1.value == 250
def test_buildPaintGlyph_RadialGradient():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph(
"a",
layerBuilder.buildPaintRadialGradient(
{
"stops": [
(0.0, 5),
{"offset": 0.5, "paletteIndex": 6, "alpha": 0.8},
(1.0, 7),
]
},
(50, 50),
(75, 75),
30,
10,
),
)
assert layer.Paint.Format == ot.Paint.Format.PaintRadialGradient
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
assert layer.Paint.ColorLine.ColorStop[0].Color.PaletteIndex == 5
assert layer.Paint.ColorLine.ColorStop[1].StopOffset.value == 0.5
assert layer.Paint.ColorLine.ColorStop[1].Color.PaletteIndex == 6
assert layer.Paint.ColorLine.ColorStop[1].Color.Alpha.value == 0.8
assert layer.Paint.ColorLine.ColorStop[2].StopOffset.value == 1.0
assert layer.Paint.ColorLine.ColorStop[2].Color.PaletteIndex == 7
assert layer.Paint.x0.value == 50
assert layer.Paint.y0.value == 50
assert layer.Paint.r0.value == 30
assert layer.Paint.x1.value == 75
assert layer.Paint.y1.value == 75
assert layer.Paint.r1.value == 10
def test_buildPaintGlyph_Dict_Solid():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph("a", {"format": 2, "paletteIndex": 0})
assert layer.Glyph == "a"
assert layer.Paint.Format == ot.Paint.Format.PaintSolid
assert layer.Paint.Color.PaletteIndex == 0
def test_buildPaintGlyph_Dict_LinearGradient():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph(
"a",
{
"format": 3,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"p0": (0, 0),
"p1": (10, 10),
},
)
assert layer.Paint.Format == ot.Paint.Format.PaintLinearGradient
assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0
def test_buildPaintGlyph_Dict_RadialGradient():
layerBuilder = LayerV1ListBuilder()
layer = layerBuilder.buildPaintGlyph(
"a",
{
"format": 4,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"c0": (0, 0),
"c1": (10, 10),
"r0": 4,
"r1": 0,
},
)
assert layer.Paint.Format == ot.Paint.Format.PaintRadialGradient
assert layer.Paint.r0.value == 4
def test_buildPaintColrGlyph():
paint = LayerV1ListBuilder().buildPaintColrGlyph("a")
assert paint.Format == ot.Paint.Format.PaintColrGlyph
assert paint.Glyph == "a"
def test_buildPaintTransform():
layerBuilder = LayerV1ListBuilder()
paint = layerBuilder.buildPaintTransform(
transform=builder.buildAffine2x3((1, 2, 3, 4, 5, 6)),
paint=layerBuilder.buildPaintGlyph(
glyph="a",
paint=layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0),
),
)
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.yx.value == 2.0
assert paint.Transform.xy.value == 3.0
assert paint.Transform.yy.value == 4.0
assert paint.Transform.dx.value == 5.0
assert paint.Transform.dy.value == 6.0
paint = layerBuilder.buildPaintTransform(
(1, 0, 0, 0.3333, 10, 10),
{
"format": 4,
"colorLine": {"stops": [(0.0, 0), (1.0, 1)]},
"c0": (100, 100),
"c1": (100, 100),
"r0": 0,
"r1": 50,
},
)
assert paint.Format == ot.Paint.Format.PaintTransform
assert paint.Transform.xx.value == 1.0
assert paint.Transform.yx.value == 0.0
assert paint.Transform.xy.value == 0.0
assert paint.Transform.yy.value == 0.3333
assert paint.Transform.dx.value == 10
assert paint.Transform.dy.value == 10
assert paint.Paint.Format == ot.Paint.Format.PaintRadialGradient
def test_buildPaintComposite():
layerBuilder = LayerV1ListBuilder()
composite = layerBuilder.buildPaintComposite(
mode=ot.CompositeMode.SRC_OVER,
source={
"format": 11,
"mode": "src_over",
"source": {"format": 5, "glyph": "c", "paint": 2},
"backdrop": {"format": 5, "glyph": "b", "paint": 1},
},
backdrop=layerBuilder.buildPaintGlyph(
"a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
),
)
assert composite.Format == ot.Paint.Format.PaintComposite
assert composite.SourcePaint.Format == ot.Paint.Format.PaintComposite
assert composite.SourcePaint.SourcePaint.Format == ot.Paint.Format.PaintGlyph
assert composite.SourcePaint.SourcePaint.Glyph == "c"
assert composite.SourcePaint.SourcePaint.Paint.Format == ot.Paint.Format.PaintSolid
assert composite.SourcePaint.SourcePaint.Paint.Color.PaletteIndex == 2
assert composite.SourcePaint.CompositeMode == ot.CompositeMode.SRC_OVER
assert composite.SourcePaint.BackdropPaint.Format == ot.Paint.Format.PaintGlyph
assert composite.SourcePaint.BackdropPaint.Glyph == "b"
assert (
composite.SourcePaint.BackdropPaint.Paint.Format == ot.Paint.Format.PaintSolid
)
assert composite.SourcePaint.BackdropPaint.Paint.Color.PaletteIndex == 1
assert composite.CompositeMode == ot.CompositeMode.SRC_OVER
assert composite.BackdropPaint.Format == ot.Paint.Format.PaintGlyph
assert composite.BackdropPaint.Glyph == "a"
assert composite.BackdropPaint.Paint.Format == ot.Paint.Format.PaintSolid
assert composite.BackdropPaint.Paint.Color.PaletteIndex == 0
def test_buildPaintTranslate():
layerBuilder = LayerV1ListBuilder()
paint = layerBuilder.buildPaintTranslate(
paint=layerBuilder.buildPaintGlyph(
"a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
),
dx=123,
dy=-345,
)
assert paint.Format == ot.Paint.Format.PaintTranslate
assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
assert paint.dx.value == 123
assert paint.dy.value == -345
2020-12-02 21:21:19 -08:00
def test_buildPaintRotate():
layerBuilder = LayerV1ListBuilder()
paint = layerBuilder.buildPaintRotate(
paint=layerBuilder.buildPaintGlyph(
"a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
),
angle=15,
centerX=127,
centerY=129,
)
assert paint.Format == ot.Paint.Format.PaintRotate
assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
assert paint.angle.value == 15
assert paint.centerX.value == 127
assert paint.centerY.value == 129
def test_buildPaintSkew():
2020-12-02 21:21:19 -08:00
layerBuilder = LayerV1ListBuilder()
paint = layerBuilder.buildPaintSkew(
paint=layerBuilder.buildPaintGlyph(
"a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0)
),
xSkewAngle=15,
ySkewAngle=42,
centerX=127,
centerY=129,
)
assert paint.Format == ot.Paint.Format.PaintSkew
assert paint.Paint.Format == ot.Paint.Format.PaintGlyph
assert paint.xSkewAngle.value == 15
assert paint.ySkewAngle.value == 42
assert paint.centerX.value == 127
assert paint.centerY.value == 129
def test_buildColrV1():
colorGlyphs = {
"a": [("b", 0), ("c", 1)],
"d": [
("e", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
(
"f",
{
"format": 4,
"colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"},
"c0": (0, 0),
"c1": (0, 0),
"r0": 10,
"r1": 0,
},
),
],
"g": [("h", 5)],
}
glyphMap = {
".notdef": 0,
"a": 4,
"b": 3,
"c": 2,
"d": 1,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}
# TODO(anthrotype) should we split into two tests? - seems two distinct validations
layers, baseGlyphs = builder.buildColrV1(colorGlyphs, glyphMap)
2020-07-01 18:29:26 +01:00
assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "d"
assert baseGlyphs.BaseGlyphV1Record[1].BaseGlyph == "a"
assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g"
layers, baseGlyphs = builder.buildColrV1(colorGlyphs)
2020-07-01 18:29:26 +01:00
assert baseGlyphs.BaseGlyphCount == len(colorGlyphs)
assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "a"
assert baseGlyphs.BaseGlyphV1Record[1].BaseGlyph == "d"
assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g"
def test_split_color_glyphs_by_version():
layerBuilder = LayerV1ListBuilder()
colorGlyphs = {
"a": [
("b", 0),
("c", 1),
("d", 2),
("e", 3),
]
}
colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]}
assert not colorGlyphsV1
colorGlyphs = {
"a": [("b", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=0.0))]
}
colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert not colorGlyphsV0
assert colorGlyphsV1 == colorGlyphs
colorGlyphs = {
"a": [("b", 0)],
"c": [
("d", 1),
(
"e",
{
"format": 3,
"colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
"p0": (0, 0),
"p1": (10, 10),
},
),
],
}
colorGlyphsV0, colorGlyphsV1 = builder._split_color_glyphs_by_version(colorGlyphs)
assert colorGlyphsV0 == {"a": [("b", 0)]}
assert "a" not in colorGlyphsV1
assert "c" in colorGlyphsV1
assert len(colorGlyphsV1["c"]) == 2
def assertIsColrV1(colr):
assert colr.version == 1
assert not hasattr(colr, "ColorLayers")
assert hasattr(colr, "table")
assert isinstance(colr.table, ot.COLR)
def assertNoV0Content(colr):
assert colr.table.BaseGlyphRecordCount == 0
assert colr.table.BaseGlyphRecordArray is None
assert colr.table.LayerRecordCount == 0
assert colr.table.LayerRecordArray is None
def test_build_layerv1list_empty():
# Nobody uses PaintColrLayers (format 8), no layerlist
colr = builder.buildCOLR(
{
"a": {
"format": 5, # PaintGlyph
"paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
"glyph": "b",
},
# A list of 1 shouldn't become a PaintColrLayers
"b": [
{
"format": 5, # PaintGlyph
"paint": {
"format": 3,
"colorLine": {
"stops": [(0.0, 2), (1.0, 3)],
"extend": "reflect",
},
"p0": (1, 2),
"p1": (3, 4),
"p2": (2, 2),
},
"glyph": "bb",
}
],
}
)
assertIsColrV1(colr)
assertNoV0Content(colr)
# 2 v1 glyphs, none in LayerV1List
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2
assert colr.table.LayerV1List.LayerCount == 0
assert len(colr.table.LayerV1List.Paint) == 0
def _paint_names(paints) -> List[str]:
# prints a predictable string from a paint list to enable
# semi-readable assertions on a LayerV1List order.
result = []
for paint in paints:
if paint.Format == int(ot.Paint.Format.PaintGlyph):
result.append(paint.Glyph)
elif paint.Format == int(ot.Paint.Format.PaintColrLayers):
result.append(
f"Layers[{paint.FirstLayerIndex}:{paint.FirstLayerIndex+paint.NumLayers}]"
)
return result
2020-11-06 14:59:52 -08:00
def test_build_layerv1list_simple():
# Two colr glyphs, each with two layers the first of which is common
# All layers use the same solid paint
solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
backdrop = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "back",
}
a_foreground = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "a_fore",
}
b_foreground = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "b_fore",
}
# list => PaintColrLayers, which means contents should be in LayerV1List
colr = builder.buildCOLR(
{
"a": [
backdrop,
a_foreground,
],
"b": [
backdrop,
b_foreground,
],
}
)
assertIsColrV1(colr)
assertNoV0Content(colr)
# 2 v1 glyphs, 4 paints in LayerV1List
# A single shared backdrop isn't worth accessing by slice
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
assert len(colr.table.BaseGlyphV1List.BaseGlyphV1Record) == 2
assert colr.table.LayerV1List.LayerCount == 4
assert _paint_names(colr.table.LayerV1List.Paint) == [
"back",
"a_fore",
"back",
"b_fore",
]
def test_build_layerv1list_with_sharing():
# Three colr glyphs, each with two layers in common
solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8}
backdrop = [
{
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "back1",
},
{
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "back2",
},
]
a_foreground = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "a_fore",
}
b_background = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "b_back",
}
b_foreground = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "b_fore",
}
c_background = {
"format": 5, # PaintGlyph
"paint": solid_paint,
"glyph": "c_back",
}
# list => PaintColrLayers, which means contents should be in LayerV1List
colr = builder.buildCOLR(
{
"a": backdrop + [a_foreground],
"b": [b_background] + backdrop + [b_foreground],
"c": [c_background] + backdrop,
}
)
assertIsColrV1(colr)
assertNoV0Content(colr)
# 2 v1 glyphs, 4 paints in LayerV1List
# A single shared backdrop isn't worth accessing by slice
baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 3
assert len(baseGlyphs) == 3
assert _paint_names([b.Paint for b in baseGlyphs]) == [
"Layers[0:3]",
"Layers[3:6]",
"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
2020-11-06 14:59:52 -08:00
def test_build_layerv1list_with_overlaps():
paints = [
{
"format": 5, # PaintGlyph
"paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
2020-11-06 14:59:52 -08:00
"glyph": c,
}
for c in "abcdefghi"
]
# list => PaintColrLayers, which means contents should be in LayerV1List
colr = builder.buildCOLR(
{
"a": paints[0:4],
"b": paints[0:6],
"c": paints[2:8],
}
)
assertIsColrV1(colr)
assertNoV0Content(colr)
baseGlyphs = colr.table.BaseGlyphV1List.BaseGlyphV1Record
# assert colr.table.BaseGlyphV1List.BaseGlyphCount == 2
2020-11-06 14:59:52 -08:00
assert _paint_names(colr.table.LayerV1List.Paint) == [
"a",
"b",
"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]",
]
2020-11-06 14:59:52 -08:00
assert colr.table.LayerV1List.LayerCount == 11
class BuildCOLRTest(object):
def test_automatic_version_all_solid_color_glyphs(self):
colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]})
assert colr.version == 0
assert hasattr(colr, "ColorLayers")
assert colr.ColorLayers["a"][0].name == "b"
assert colr.ColorLayers["a"][1].name == "c"
def test_automatic_version_no_solid_color_glyphs(self):
colr = builder.buildCOLR(
{
"a": [
(
"b",
{
"format": 4,
"colorLine": {
"stops": [(0.0, 0), (1.0, 1)],
"extend": "repeat",
},
"c0": (1, 0),
"c1": (10, 0),
"r0": 4,
"r1": 2,
},
),
("c", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
],
"d": [
(
"e",
{
"format": 3,
"colorLine": {
"stops": [(0.0, 2), (1.0, 3)],
"extend": "reflect",
},
"p0": (1, 2),
"p1": (3, 4),
"p2": (2, 2),
},
),
],
}
)
assertIsColrV1(colr)
assert colr.table.BaseGlyphRecordCount == 0
assert colr.table.BaseGlyphRecordArray is None
assert colr.table.LayerRecordCount == 0
assert colr.table.LayerRecordArray is None
def test_automatic_version_mixed_solid_and_gradient_glyphs(self):
colr = builder.buildCOLR(
{
"a": [("b", 0), ("c", 1)],
"d": [
(
"e",
{
"format": 3,
"colorLine": {"stops": [(0.0, 2), (1.0, 3)]},
"p0": (1, 2),
"p1": (3, 4),
"p2": (2, 2),
},
),
("f", {"format": 2, "paletteIndex": 2, "alpha": 0.8}),
],
}
)
assertIsColrV1(colr)
assert colr.table.VarStore is None
assert colr.table.BaseGlyphRecordCount == 1
assert isinstance(colr.table.BaseGlyphRecordArray, ot.BaseGlyphRecordArray)
assert colr.table.LayerRecordCount == 2
assert isinstance(colr.table.LayerRecordArray, ot.LayerRecordArray)
assert isinstance(colr.table.BaseGlyphV1List, ot.BaseGlyphV1List)
assert colr.table.BaseGlyphV1List.BaseGlyphCount == 1
assert isinstance(
colr.table.BaseGlyphV1List.BaseGlyphV1Record[0], ot.BaseGlyphV1Record
)
assert colr.table.BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph == "d"
assert isinstance(colr.table.LayerV1List, ot.LayerV1List)
assert colr.table.LayerV1List.Paint[0].Glyph == "e"
def test_explicit_version_0(self):
colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}, version=0)
assert colr.version == 0
assert hasattr(colr, "ColorLayers")
def test_explicit_version_1(self):
colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}, version=1)
assert colr.version == 1
assert not hasattr(colr, "ColorLayers")
assert hasattr(colr, "table")
assert isinstance(colr.table, ot.COLR)
assert colr.table.VarStore is None