aboutsummaryrefslogtreecommitdiff
path: root/Tests/varLib/merger_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/varLib/merger_test.py')
-rw-r--r--Tests/varLib/merger_test.py1844
1 files changed, 1844 insertions, 0 deletions
diff --git a/Tests/varLib/merger_test.py b/Tests/varLib/merger_test.py
new file mode 100644
index 00000000..aa7a6998
--- /dev/null
+++ b/Tests/varLib/merger_test.py
@@ -0,0 +1,1844 @@
+from copy import deepcopy
+import string
+from fontTools.colorLib.builder import LayerListBuilder, buildCOLR, buildClipList
+from fontTools.misc.testTools import getXML
+from fontTools.varLib.merger import COLRVariationMerger
+from fontTools.varLib.models import VariationModel
+from fontTools.ttLib import TTFont
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
+import pytest
+
+
+NO_VARIATION_INDEX = ot.NO_VARIATION_INDEX
+
+
+def dump_xml(table, ttFont=None):
+ xml = getXML(table.toXML, ttFont)
+ print("[")
+ for line in xml:
+ print(f" {line!r},")
+ print("]")
+ return xml
+
+
+def compile_decompile(table, ttFont):
+ writer = OTTableWriter(tableTag="COLR")
+ # compile itself may modify a table, safer to copy it first
+ table = deepcopy(table)
+ table.compile(writer, ttFont)
+ data = writer.getAllData()
+
+ reader = OTTableReader(data, tableTag="COLR")
+ table2 = table.__class__()
+ table2.decompile(reader, ttFont)
+
+ return table2
+
+
+@pytest.fixture
+def ttFont():
+ font = TTFont()
+ font.setGlyphOrder([".notdef"] + list(string.ascii_letters))
+ return font
+
+
+def build_paint(data):
+ return LayerListBuilder().buildPaint(data)
+
+
+class COLRVariationMergerTest:
+ @pytest.mark.parametrize(
+ "paints, expected_xml, expected_varIdxes",
+ [
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ ],
+ [
+ '<Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ "</Paint>",
+ ],
+ [],
+ id="solid-same",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 0.5,
+ },
+ ],
+ [
+ '<Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ "</Paint>",
+ ],
+ [0],
+ id="solid-alpha",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": 2,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.1, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 0.9, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": 2,
+ },
+ ],
+ [
+ '<Paint Format="5"><!-- PaintVarLinearGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="2"/>',
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="0"/>',
+ ' <y0 value="0"/>',
+ ' <x1 value="1"/>',
+ ' <y1 value="1"/>',
+ ' <x2 value="2"/>',
+ ' <y2 value="2"/>',
+ " <VarIndexBase/>",
+ "</Paint>",
+ ],
+ [0, NO_VARIATION_INDEX, 1, NO_VARIATION_INDEX],
+ id="linear_grad-stop-offsets",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": 2,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 0.5},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": 2,
+ },
+ ],
+ [
+ '<Paint Format="5"><!-- PaintVarLinearGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="0"/>',
+ ' <y0 value="0"/>',
+ ' <x1 value="1"/>',
+ ' <y1 value="1"/>',
+ ' <x2 value="2"/>',
+ ' <y2 value="2"/>',
+ " <VarIndexBase/>",
+ "</Paint>",
+ ],
+ [NO_VARIATION_INDEX, 0],
+ id="linear_grad-stop[0].alpha",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": 2,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintLinearGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": -0.5, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "x1": 1,
+ "y1": 1,
+ "x2": 2,
+ "y2": -200,
+ },
+ ],
+ [
+ '<Paint Format="5"><!-- PaintVarLinearGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="0"/>',
+ ' <y0 value="0"/>',
+ ' <x1 value="1"/>',
+ ' <y1 value="1"/>',
+ ' <x2 value="2"/>',
+ ' <y2 value="2"/>',
+ ' <VarIndexBase value="1"/>',
+ "</Paint>",
+ ],
+ [
+ 0,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ 1,
+ ],
+ id="linear_grad-stop[0].offset-y2",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "r0": 0,
+ "x1": 1,
+ "y1": 1,
+ "r1": 1,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.1, "PaletteIndex": 0, "Alpha": 0.6},
+ {"StopOffset": 0.9, "PaletteIndex": 1, "Alpha": 0.7},
+ ],
+ },
+ "x0": -1,
+ "y0": -2,
+ "r0": 3,
+ "x1": -4,
+ "y1": -5,
+ "r1": 6,
+ },
+ ],
+ [
+ '<Paint Format="7"><!-- PaintVarRadialGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="2"/>',
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="0"/>',
+ ' <y0 value="0"/>',
+ ' <r0 value="0"/>',
+ ' <x1 value="1"/>',
+ ' <y1 value="1"/>',
+ ' <r1 value="1"/>',
+ ' <VarIndexBase value="4"/>',
+ "</Paint>",
+ ],
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ id="radial_grad-all-different",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintSweepGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.REPEAT),
+ "ColorStop": [
+ {"StopOffset": 0.4, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 0.6, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "centerX": 0,
+ "centerY": 0,
+ "startAngle": 0,
+ "endAngle": 180.0,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintSweepGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.REPEAT),
+ "ColorStop": [
+ {"StopOffset": 0.4, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 0.6, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "centerX": 0,
+ "centerY": 0,
+ "startAngle": 90.0,
+ "endAngle": 180.0,
+ },
+ ],
+ [
+ '<Paint Format="9"><!-- PaintVarSweepGradient -->',
+ " <ColorLine>",
+ ' <Extend value="repeat"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.4"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="0.6"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <centerX value="0"/>',
+ ' <centerY value="0"/>',
+ ' <startAngle value="0.0"/>',
+ ' <endAngle value="180.0"/>',
+ ' <VarIndexBase value="0"/>',
+ "</Paint>",
+ ],
+ [NO_VARIATION_INDEX, NO_VARIATION_INDEX, 0, NO_VARIATION_INDEX],
+ id="sweep_grad-startAngle",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintSweepGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
+ ],
+ },
+ "centerX": 0,
+ "centerY": 0,
+ "startAngle": 0.0,
+ "endAngle": 180.0,
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintSweepGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 0.5},
+ {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 0.5},
+ ],
+ },
+ "centerX": 0,
+ "centerY": 0,
+ "startAngle": 0.0,
+ "endAngle": 180.0,
+ },
+ ],
+ [
+ '<Paint Format="9"><!-- PaintVarSweepGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <centerX value="0"/>',
+ ' <centerY value="0"/>',
+ ' <startAngle value="0.0"/>',
+ ' <endAngle value="180.0"/>',
+ " <VarIndexBase/>",
+ "</Paint>",
+ ],
+ [NO_VARIATION_INDEX, 0],
+ id="sweep_grad-stops-alpha-reuse-varidxbase",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": int(ot.PaintFormat.PaintTransform),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {
+ "StopOffset": 0.0,
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ {
+ "StopOffset": 1.0,
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "r0": 0,
+ "x1": 1,
+ "y1": 1,
+ "r1": 1,
+ },
+ "Transform": {
+ "xx": 1.0,
+ "xy": 0.0,
+ "yx": 0.0,
+ "yy": 1.0,
+ "dx": 0.0,
+ "dy": 0.0,
+ },
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintTransform),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintRadialGradient),
+ "ColorLine": {
+ "Extend": int(ot.ExtendMode.PAD),
+ "ColorStop": [
+ {
+ "StopOffset": 0.0,
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ {
+ "StopOffset": 1.0,
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ ],
+ },
+ "x0": 0,
+ "y0": 0,
+ "r0": 0,
+ "x1": 1,
+ "y1": 1,
+ "r1": 1,
+ },
+ "Transform": {
+ "xx": 1.0,
+ "xy": 0.0,
+ "yx": 0.0,
+ "yy": 0.5,
+ "dx": 0.0,
+ "dy": -100.0,
+ },
+ },
+ ],
+ [
+ '<Paint Format="13"><!-- PaintVarTransform -->',
+ ' <Paint Format="6"><!-- PaintRadialGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <x0 value="0"/>',
+ ' <y0 value="0"/>',
+ ' <r0 value="0"/>',
+ ' <x1 value="1"/>',
+ ' <y1 value="1"/>',
+ ' <r1 value="1"/>',
+ " </Paint>",
+ " <Transform>",
+ ' <xx value="1.0"/>',
+ ' <yx value="0.0"/>',
+ ' <xy value="0.0"/>',
+ ' <yy value="1.0"/>',
+ ' <dx value="0.0"/>',
+ ' <dy value="0.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Transform>",
+ "</Paint>",
+ ],
+ [
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ 0,
+ NO_VARIATION_INDEX,
+ 1,
+ ],
+ id="transform-yy-dy",
+ ),
+ pytest.param(
+ [
+ {
+ "Format": ot.PaintFormat.PaintTransform,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintSweepGradient,
+ "ColorLine": {
+ "Extend": ot.ExtendMode.PAD,
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0},
+ {
+ "StopOffset": 1.0,
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ ],
+ },
+ "centerX": 0,
+ "centerY": 0,
+ "startAngle": 0,
+ "endAngle": 360,
+ },
+ "Transform": (1.0, 0, 0, 1.0, 0, 0),
+ },
+ {
+ "Format": ot.PaintFormat.PaintTransform,
+ "Paint": {
+ "Format": ot.PaintFormat.PaintSweepGradient,
+ "ColorLine": {
+ "Extend": ot.ExtendMode.PAD,
+ "ColorStop": [
+ {"StopOffset": 0.0, "PaletteIndex": 0},
+ {
+ "StopOffset": 1.0,
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ ],
+ },
+ "centerX": 256,
+ "centerY": 0,
+ "startAngle": 0,
+ "endAngle": 360,
+ },
+ # Transform.xx below produces the same VarStore delta as the
+ # above PaintSweepGradient's centerX because, when Fixed16.16
+ # is converted to integer, it becomes:
+ # floatToFixed(1.00390625, 16) == 256
+ # Because there is overlap between the varIdxes of the
+ # PaintVarTransform's Affine2x3 and the PaintSweepGradient's
+ # the VarIndexBase is reused (0 for both)
+ "Transform": (1.00390625, 0, 0, 1.0, 10, 0),
+ },
+ ],
+ [
+ '<Paint Format="13"><!-- PaintVarTransform -->',
+ ' <Paint Format="9"><!-- PaintVarSweepGradient -->',
+ " <ColorLine>",
+ ' <Extend value="pad"/>',
+ " <!-- StopCount=2 -->",
+ ' <ColorStop index="0">',
+ ' <StopOffset value="0.0"/>',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ ' <ColorStop index="1">',
+ ' <StopOffset value="1.0"/>',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " <VarIndexBase/>",
+ " </ColorStop>",
+ " </ColorLine>",
+ ' <centerX value="0"/>',
+ ' <centerY value="0"/>',
+ ' <startAngle value="0.0"/>',
+ ' <endAngle value="360.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ " <Transform>",
+ ' <xx value="1.0"/>',
+ ' <yx value="0.0"/>',
+ ' <xy value="0.0"/>',
+ ' <yy value="1.0"/>',
+ ' <dx value="0.0"/>',
+ ' <dy value="0.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Transform>",
+ "</Paint>",
+ ],
+ [
+ 0,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ 1,
+ NO_VARIATION_INDEX,
+ ],
+ id="transform-xx-sweep_grad-centerx-same-varidxbase",
+ ),
+ ],
+ )
+ def test_merge_Paint(self, paints, ttFont, expected_xml, expected_varIdxes):
+ paints = [build_paint(p) for p in paints]
+ out = deepcopy(paints[0])
+
+ model = VariationModel([{}, {"ZZZZ": 1.0}])
+ merger = COLRVariationMerger(model, ["ZZZZ"], ttFont)
+
+ merger.mergeThings(out, paints)
+
+ assert compile_decompile(out, ttFont) == out
+ assert dump_xml(out, ttFont) == expected_xml
+ assert merger.varIdxes == expected_varIdxes
+
+ def test_merge_ClipList(self, ttFont):
+ clipLists = [
+ buildClipList(clips)
+ for clips in [
+ {
+ "A": (0, 0, 1000, 1000),
+ "B": (0, 0, 1000, 1000),
+ "C": (0, 0, 1000, 1000),
+ "D": (0, 0, 1000, 1000),
+ },
+ {
+ # non-default masters' clip boxes can be 'sparse'
+ # (i.e. can omit explicit clip box for some glyphs)
+ # "A": (0, 0, 1000, 1000),
+ "B": (10, 0, 1000, 1000),
+ "C": (20, 20, 1020, 1020),
+ "D": (20, 20, 1020, 1020),
+ },
+ ]
+ ]
+ out = deepcopy(clipLists[0])
+
+ model = VariationModel([{}, {"ZZZZ": 1.0}])
+ merger = COLRVariationMerger(model, ["ZZZZ"], ttFont)
+
+ merger.mergeThings(out, clipLists)
+
+ assert compile_decompile(out, ttFont) == out
+ assert dump_xml(out, ttFont) == [
+ '<ClipList Format="1">',
+ " <Clip>",
+ ' <Glyph value="A"/>',
+ ' <ClipBox Format="1">',
+ ' <xMin value="0"/>',
+ ' <yMin value="0"/>',
+ ' <xMax value="1000"/>',
+ ' <yMax value="1000"/>',
+ " </ClipBox>",
+ " </Clip>",
+ " <Clip>",
+ ' <Glyph value="B"/>',
+ ' <ClipBox Format="2">',
+ ' <xMin value="0"/>',
+ ' <yMin value="0"/>',
+ ' <xMax value="1000"/>',
+ ' <yMax value="1000"/>',
+ ' <VarIndexBase value="0"/>',
+ " </ClipBox>",
+ " </Clip>",
+ " <Clip>",
+ ' <Glyph value="C"/>',
+ ' <Glyph value="D"/>',
+ ' <ClipBox Format="2">',
+ ' <xMin value="0"/>',
+ ' <yMin value="0"/>',
+ ' <xMax value="1000"/>',
+ ' <yMax value="1000"/>',
+ ' <VarIndexBase value="4"/>',
+ " </ClipBox>",
+ " </Clip>",
+ "</ClipList>",
+ ]
+ assert merger.varIdxes == [
+ 0,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ NO_VARIATION_INDEX,
+ 1,
+ 1,
+ 1,
+ 1,
+ ]
+
+ @pytest.mark.parametrize(
+ "master_layer_reuse",
+ [
+ pytest.param(False, id="no-reuse"),
+ pytest.param(True, id="with-reuse"),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "color_glyphs, output_layer_reuse, expected_xml, expected_varIdxes",
+ [
+ pytest.param(
+ [
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ {
+ "A": {
+ "Format": ot.PaintFormat.PaintColrLayers,
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ ],
+ False,
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=1 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=2 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ [],
+ id="no-variation",
+ ),
+ pytest.param(
+ [
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "C": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 3,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ {
+ # NOTE: 'A' is missing from non-default master
+ "C": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 3,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ ],
+ False,
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=2 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="1">',
+ ' <BaseGlyph value="C"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="2"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=4 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ [0],
+ id="sparse-masters",
+ ),
+ pytest.param(
+ [
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "C": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ # 'C' reuses layers 1-3 from 'A'
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "D": { # identical to 'C'
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "E": { # superset of 'C' or 'D'
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 3,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ {
+ # NOTE: 'A' is missing from non-default master
+ "C": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "D": { # same as 'C'
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "E": { # first two layers vary the same way as 'C' or 'D'
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 3,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ ],
+ True, # reuse
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=4 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="3"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="1">',
+ ' <BaseGlyph value="C"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="2">',
+ ' <BaseGlyph value="D"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="3">',
+ ' <BaseGlyph value="E"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="5"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=7 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="4" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="5" Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ ' <Paint index="6" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ [0],
+ id="sparse-masters-with-reuse",
+ ),
+ pytest.param(
+ [
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "C": { # 'C' shares layer 1 and 2 with 'A'
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 0.9,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ "C": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 0.5,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ ],
+ },
+ },
+ ],
+ True,
+ [
+ # a different Alpha variation is applied to a shared layer between
+ # 'A' and 'C' and thus they are no longer shared.
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=2 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="3"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="1">',
+ ' <BaseGlyph value="C"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=5 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="3"><!-- PaintVarSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ ' <VarIndexBase value="1"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="4" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ [0, 1],
+ id="shared-master-layers-different-variations",
+ ),
+ ],
+ )
+ def test_merge_full_table(
+ self,
+ color_glyphs,
+ ttFont,
+ expected_xml,
+ expected_varIdxes,
+ master_layer_reuse,
+ output_layer_reuse,
+ ):
+ master_ttfs = [deepcopy(ttFont) for _ in range(len(color_glyphs))]
+ for ttf, glyphs in zip(master_ttfs, color_glyphs):
+ # merge algorithm is expected to work the same even if the master COLRs
+ # may differ as to the layer reuse, hence we try both ways
+ ttf["COLR"] = buildCOLR(glyphs, allowLayerReuse=master_layer_reuse)
+ vf = deepcopy(master_ttfs[0])
+
+ model = VariationModel([{}, {"ZZZZ": 1.0}])
+ merger = COLRVariationMerger(
+ model, ["ZZZZ"], vf, allowLayerReuse=output_layer_reuse
+ )
+
+ merger.mergeTables(vf, master_ttfs)
+
+ out = vf["COLR"].table
+
+ assert compile_decompile(out, vf) == out
+ assert dump_xml(out, vf) == expected_xml
+ assert merger.varIdxes == expected_varIdxes
+
+ @pytest.mark.parametrize(
+ "color_glyphs, before_xml, expected_xml",
+ [
+ pytest.param(
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "C",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "D",
+ },
+ ],
+ },
+ "E": {
+ "Format": int(ot.PaintFormat.PaintColrLayers),
+ "Layers": [
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 1,
+ "Alpha": 1.0,
+ },
+ "Glyph": "C",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 2,
+ "Alpha": 1.0,
+ },
+ "Glyph": "D",
+ },
+ {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 3,
+ "Alpha": 1.0,
+ },
+ "Glyph": "F",
+ },
+ ],
+ },
+ "G": {
+ "Format": int(ot.PaintFormat.PaintColrGlyph),
+ "Glyph": "E",
+ },
+ },
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=3 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="3"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="1">',
+ ' <BaseGlyph value="E"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="2">',
+ ' <BaseGlyph value="G"/>',
+ ' <Paint Format="11"><!-- PaintColrGlyph -->',
+ ' <Glyph value="E"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=5 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="C"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="D"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="2"/>',
+ ' <FirstLayerIndex value="1"/>',
+ " </Paint>",
+ ' <Paint index="4" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="F"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=3 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="3"/>',
+ ' <FirstLayerIndex value="0"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="1">',
+ ' <BaseGlyph value="E"/>',
+ ' <Paint Format="1"><!-- PaintColrLayers -->',
+ ' <NumLayers value="3"/>',
+ ' <FirstLayerIndex value="3"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ ' <BaseGlyphPaintRecord index="2">',
+ ' <BaseGlyph value="G"/>',
+ ' <Paint Format="11"><!-- PaintColrGlyph -->',
+ ' <Glyph value="E"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ " <LayerList>",
+ " <!-- LayerCount=6 -->",
+ ' <Paint index="0" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ ' <Paint index="1" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="C"/>',
+ " </Paint>",
+ ' <Paint index="2" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="D"/>',
+ " </Paint>",
+ ' <Paint index="3" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="1"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="C"/>',
+ " </Paint>",
+ ' <Paint index="4" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="D"/>',
+ " </Paint>",
+ ' <Paint index="5" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="3"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="F"/>',
+ " </Paint>",
+ " </LayerList>",
+ "</COLR>",
+ ],
+ id="simple-reuse",
+ ),
+ pytest.param(
+ {
+ "A": {
+ "Format": int(ot.PaintFormat.PaintGlyph),
+ "Paint": {
+ "Format": int(ot.PaintFormat.PaintSolid),
+ "PaletteIndex": 0,
+ "Alpha": 1.0,
+ },
+ "Glyph": "B",
+ },
+ },
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=1 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ "</COLR>",
+ ],
+ [
+ "<COLR>",
+ ' <Version value="1"/>',
+ " <!-- BaseGlyphRecordCount=0 -->",
+ " <!-- LayerRecordCount=0 -->",
+ " <BaseGlyphList>",
+ " <!-- BaseGlyphCount=1 -->",
+ ' <BaseGlyphPaintRecord index="0">',
+ ' <BaseGlyph value="A"/>',
+ ' <Paint Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="0"/>',
+ ' <Alpha value="1.0"/>',
+ " </Paint>",
+ ' <Glyph value="B"/>',
+ " </Paint>",
+ " </BaseGlyphPaintRecord>",
+ " </BaseGlyphList>",
+ "</COLR>",
+ ],
+ id="no-layer-list",
+ ),
+ ],
+ )
+ def test_expandPaintColrLayers(
+ self, color_glyphs, ttFont, before_xml, expected_xml
+ ):
+ colr = buildCOLR(color_glyphs, allowLayerReuse=True)
+
+ assert dump_xml(colr.table, ttFont) == before_xml
+
+ before_layer_count = 0
+ reuses_colr_layers = False
+ if colr.table.LayerList:
+ before_layer_count = len(colr.table.LayerList.Paint)
+ reuses_colr_layers = any(
+ p.Format == ot.PaintFormat.PaintColrLayers
+ for p in colr.table.LayerList.Paint
+ )
+
+ COLRVariationMerger.expandPaintColrLayers(colr.table)
+
+ assert dump_xml(colr.table, ttFont) == expected_xml
+
+ after_layer_count = (
+ 0 if not colr.table.LayerList else len(colr.table.LayerList.Paint)
+ )
+
+ if reuses_colr_layers:
+ assert not any(
+ p.Format == ot.PaintFormat.PaintColrLayers
+ for p in colr.table.LayerList.Paint
+ )
+ assert after_layer_count > before_layer_count
+ else:
+ assert after_layer_count == before_layer_count
+
+ if colr.table.LayerList:
+ assert len({id(p) for p in colr.table.LayerList.Paint}) == after_layer_count