Example #1
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
Example #2
0
    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"
Example #3
0
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",
    ]
Example #4
0
 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,
                 "transparency": 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
Example #5
0
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
Example #6
0
def buildCOLRv1(designspacePath,
                ttfPath,
                outTTFPath,
                saveWoff2,
                neutralOnly=False):
    import pathlib

    ttfPath = pathlib.Path(ttfPath)
    if outTTFPath is None:
        outTTFPath = ttfPath.parent / (ttfPath.stem + "-colrv1" +
                                       ttfPath.suffix)
    else:
        outTTFPath = pathlib.Path(outTTFPath)
    ttf = TTFont(ttfPath)

    axisTags = [axis.axisTag for axis in ttf["fvar"].axes]
    axisTagToIndex = {tag: index for index, tag in enumerate(axisTags)}
    globalAxisNames = {
        axis.axisTag
        for axis in ttf["fvar"].axes if not axis.flags & 0x0001
    }
    assert globalAxisNames == {
        axisTag
        for axisTag in axisTags if axisTag[0] != "V"
    }

    vcFont = VarCoFont(designspacePath)

    # Update the glyf table to contain bounding boxes for color glyphs
    estimateCOLRv1BoundingBoxes(vcFont, ttf, neutralOnly)

    vcData, varStore = prepareVariableComponentData(vcFont, axisTags,
                                                    globalAxisNames,
                                                    neutralOnly)
    colrGlyphs = buildCOLRGlyphs(vcData, axisTagToIndex)
    ttf["COLR"] = buildCOLR(colrGlyphs, varStore=varStore)

    ttf.save(outTTFPath)

    ttf = TTFont(outTTFPath, lazy=True)  # Load from scratch

    if saveWoff2:
        outWoff2Path = outTTFPath.parent / (outTTFPath.stem + ".woff2")
        ttf.flavor = "woff2"
        ttf.save(outWoff2Path)
Example #7
0
 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
Example #8
0
    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.BaseGlyphV1Array, ot.BaseGlyphV1Array)
        assert colr.table.BaseGlyphV1Array.BaseGlyphCount == 1
        assert isinstance(colr.table.BaseGlyphV1Array.BaseGlyphV1Record[0],
                          ot.BaseGlyphV1Record)
        assert colr.table.BaseGlyphV1Array.BaseGlyphV1Record[
            0].BaseGlyph == "d"
        assert isinstance(
            colr.table.BaseGlyphV1Array.BaseGlyphV1Record[0].LayerV1Array,
            ot.LayerV1Array,
        )
        assert (colr.table.BaseGlyphV1Array.BaseGlyphV1Record[0].LayerV1Array.
                LayerV1Record[0].LayerGlyph == "e")
Example #9
0
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
Example #10
0
 def test_explicit_version_0(self):
     colr = builder.buildCOLR({"a": [("b", 0), ("c", 1)]}, version=0)
     assert colr.version == 0
     assert hasattr(colr, "ColorLayers")
Example #11
0
 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"
Example #12
0
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