from fontTools.ttLib import newTable
from fontTools.ttLib.tables import otTables as ot
from fontTools.colorLib import builder
from fontTools.colorLib.builder import LayerV1ListBuilder
from fontTools.colorLib.errors import ColorLibError
import pytest
from typing import List


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"
    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": 10,
            "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_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_buildPaintRotate():
    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)
    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)
    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


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


def test_build_layerv1list_with_overlaps():
    paints = [
        {
            "format": 5,  # PaintGlyph
            "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8},
            "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

    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]",
    ]
    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
