from fontTools.ttLib import newTable from fontTools.ttLib.tables import otTables as ot from fontTools.colorLib import builder from fontTools.colorLib.errors import ColorLibError import pytest 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_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_buildSolidColorPaint(): p = builder.buildSolidColorPaint(0) assert p.Format == 1 assert p.Color.PaletteIndex == 0 assert p.Color.Alpha.value == 1.0 assert p.Color.Alpha.varIdx == 0 p = builder.buildSolidColorPaint(1, alpha=0.5) assert p.Format == 1 assert p.Color.PaletteIndex == 1 assert p.Color.Alpha.value == 0.5 assert p.Color.Alpha.varIdx == 0 p = builder.buildSolidColorPaint(3, alpha=builder.VariableFloat(0.5, varIdx=2)) assert p.Format == 1 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_buildAffine2x2(): matrix = builder.buildAffine2x2(1.5, 0, 0.5, 2.0) assert matrix.xx == builder.VariableFloat(1.5) assert matrix.xy == builder.VariableFloat(0.0) assert matrix.yx == builder.VariableFloat(0.5) assert matrix.yy == builder.VariableFloat(2.0) def test_buildLinearGradientPaint(): 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 = builder.buildLinearGradientPaint(color_line, p0, p1) assert gradient.Format == 2 assert gradient.ColorLine == color_line assert (gradient.x0, gradient.y0) == p0 assert (gradient.x1, gradient.y1) == p1 assert (gradient.x2, gradient.y2) == p1 gradient = builder.buildLinearGradientPaint({"stops": color_stops}, p0, p1) assert gradient.ColorLine.Extend == builder.ExtendMode.PAD assert gradient.ColorLine.ColorStop == color_stops gradient = builder.buildLinearGradientPaint(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_buildRadialGradientPaint(): 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 = builder.buildRadialGradientPaint(color_line, c0, c1, r0, r1) assert gradient.Format == 3 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 assert gradient.Affine is None gradient = builder.buildRadialGradientPaint({"stops": color_stops}, c0, c1, r0, r1) assert gradient.ColorLine.Extend == builder.ExtendMode.PAD assert gradient.ColorLine.ColorStop == color_stops matrix = builder.buildAffine2x2(2.0, 0.0, 0.0, 2.0) gradient = builder.buildRadialGradientPaint( color_line, c0, c1, r0, r1, affine=matrix ) assert gradient.Affine == matrix gradient = builder.buildRadialGradientPaint( color_line, c0, c1, r0, r1, affine=(2.0, 0.0, 0.0, 2.0) ) assert gradient.Affine == matrix def test_buildLayerV1Record(): layer = builder.buildLayerV1Record("a", 2) assert layer.LayerGlyph == "a" assert layer.Paint.Format == 1 assert layer.Paint.Color.PaletteIndex == 2 layer = builder.buildLayerV1Record("a", builder.buildSolidColorPaint(3, 0.9)) assert layer.Paint.Format == 1 assert layer.Paint.Color.PaletteIndex == 3 assert layer.Paint.Color.Alpha.value == 0.9 layer = builder.buildLayerV1Record( "a", builder.buildLinearGradientPaint( {"stops": [(0.0, 3), (1.0, 4)]}, (100, 200), (150, 250) ), ) assert layer.Paint.Format == 2 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 layer = builder.buildLayerV1Record( "a", builder.buildRadialGradientPaint( { "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 == 3 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_buildLayerV1Record_from_dict(): layer = builder.buildLayerV1Record("a", {"format": 1, "paletteIndex": 0}) assert layer.LayerGlyph == "a" assert layer.Paint.Format == 1 assert layer.Paint.Color.PaletteIndex == 0 layer = builder.buildLayerV1Record( "a", { "format": 2, "colorLine": {"stops": [(0.0, 0), (1.0, 1)]}, "p0": (0, 0), "p1": (10, 10), }, ) assert layer.Paint.Format == 2 assert layer.Paint.ColorLine.ColorStop[0].StopOffset.value == 0.0 layer = builder.buildLayerV1Record( "a", { "format": 3, "colorLine": {"stops": [(0.0, 0), (1.0, 1)]}, "c0": (0, 0), "c1": (10, 10), "r0": 4, "r1": 0, }, ) assert layer.Paint.Format == 3 assert layer.Paint.r0.value == 4 def test_buildLayerV1List(): layers = [ ("a", 1), ("b", {"format": 1, "paletteIndex": 2, "alpha": 0.5}), ( "c", { "format": 2, "colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "repeat"}, "p0": (100, 200), "p1": (150, 250), }, ), ( "d", { "format": 3, "colorLine": { "stops": [ {"offset": 0.0, "paletteIndex": 5}, {"offset": 0.5, "paletteIndex": 6, "alpha": 0.8}, {"offset": 1.0, "paletteIndex": 7}, ] }, "c0": (50, 50), "c1": (75, 75), "r0": 30, "r1": 10, }, ), builder.buildLayerV1Record("e", builder.buildSolidColorPaint(8)), ] layers = builder.buildLayerV1List(layers) assert layers.LayerCount == len(layers.LayerV1Record) assert all(isinstance(l, ot.LayerV1Record) for l in layers.LayerV1Record) def test_buildBaseGlyphV1Record(): baseGlyphRec = builder.buildBaseGlyphV1Record("a", [("b", 0), ("c", 1)]) assert baseGlyphRec.BaseGlyph == "a" assert isinstance(baseGlyphRec.LayerV1List, ot.LayerV1List) layers = builder.buildLayerV1List([("b", 0), ("c", 1)]) baseGlyphRec = builder.buildBaseGlyphV1Record("a", layers) assert baseGlyphRec.BaseGlyph == "a" assert baseGlyphRec.LayerV1List == layers def test_buildBaseGlyphV1List(): colorGlyphs = { "a": [("b", 0), ("c", 1)], "d": [ ("e", {"format": 1, "paletteIndex": 2, "alpha": 0.8}), ( "f", { "format": 3, "colorLine": {"stops": [(0.0, 3), (1.0, 4)], "extend": "reflect"}, "c0": (0, 0), "c1": (0, 0), "r0": 10, "r1": 0, }, ), ], "g": builder.buildLayerV1List([("h", 5)]), } glyphMap = { ".notdef": 0, "a": 4, "b": 3, "c": 2, "d": 1, "e": 5, "f": 6, "g": 7, "h": 8, } baseGlyphs = builder.buildBaseGlyphV1List(colorGlyphs, glyphMap) assert baseGlyphs.BaseGlyphCount == len(colorGlyphs) assert baseGlyphs.BaseGlyphV1Record[0].BaseGlyph == "d" assert baseGlyphs.BaseGlyphV1Record[1].BaseGlyph == "a" assert baseGlyphs.BaseGlyphV1Record[2].BaseGlyph == "g" baseGlyphs = builder.buildBaseGlyphV1List(colorGlyphs) 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_splitSolidAndGradientGlyphs(): colorGlyphs = { "a": [ ("b", 0), ("c", 1), ("d", {"format": 1, "paletteIndex": 2}), ("e", builder.buildSolidColorPaint(paletteIndex=3)), ] } colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) assert colorGlyphsV0 == {"a": [("b", 0), ("c", 1), ("d", 2), ("e", 3)]} assert not colorGlyphsV1 colorGlyphs = { "a": [("b", builder.buildSolidColorPaint(paletteIndex=0, alpha=0.0))] } colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) assert not colorGlyphsV0 assert colorGlyphsV1 == colorGlyphs colorGlyphs = { "a": [("b", 0)], "c": [ ("d", 1), ( "e", { "format": 2, "colorLine": {"stops": [(0.0, 2), (1.0, 3)]}, "p0": (0, 0), "p1": (10, 10), }, ), ], } colorGlyphsV0, colorGlyphsV1 = builder._splitSolidAndGradientGlyphs(colorGlyphs) assert colorGlyphsV0 == {"a": [("b", 0)]} assert "a" not in colorGlyphsV1 assert "c" in colorGlyphsV1 assert len(colorGlyphsV1["c"]) == 2 layer_d = colorGlyphsV1["c"][0] assert layer_d[0] == "d" assert isinstance(layer_d[1], ot.Paint) assert layer_d[1].Format == 1 layer_e = colorGlyphsV1["c"][1] assert layer_e[0] == "e" assert isinstance(layer_e[1], ot.Paint) assert layer_e[1].Format == 2 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": 3, "colorLine": { "stops": [(0.0, 0), (1.0, 1)], "extend": "repeat", }, "c0": (1, 0), "c1": (10, 0), "r0": 4, "r1": 2, }, ), ("c", {"format": 1, "paletteIndex": 2, "alpha": 0.8}), ], "d": [ ( "e", { "format": 2, "colorLine": { "stops": [(0.0, 2), (1.0, 3)], "extend": "reflect", }, "p0": (1, 2), "p1": (3, 4), "p2": (2, 2), }, ) ], } ) assert colr.version == 1 assert not hasattr(colr, "ColorLayers") assert hasattr(colr, "table") assert isinstance(colr.table, ot.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": 2, "colorLine": {"stops": [(0.0, 2), (1.0, 3)]}, "p0": (1, 2), "p1": (3, 4), "p2": (2, 2), }, ) ], } ) 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 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.BaseGlyphV1List.BaseGlyphV1Record[0].LayerV1List, ot.LayerV1List ) assert ( colr.table.BaseGlyphV1List.BaseGlyphV1Record[0] .LayerV1List.LayerV1Record[0] .LayerGlyph == "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