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_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_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_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
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
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)
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": 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")
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 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_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_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