• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools import ttLib
2from fontTools.misc.testTools import getXML, parseXML
3from fontTools.ttLib import TTFont
4from fontTools.ttLib.tables.C_O_L_R_ import table_C_O_L_R_
5
6from pathlib import Path
7import binascii
8import pytest
9
10
11TEST_DATA_DIR = Path(__file__).parent / "data"
12
13
14COLR_V0_SAMPLE = (
15    (b"\x00\x00", "Version (0)"),
16    (b"\x00\x01", "BaseGlyphRecordCount (1)"),
17    (
18        b"\x00\x00\x00\x0e",
19        "Offset to BaseGlyphRecordArray from beginning of table (14)",
20    ),
21    (b"\x00\x00\x00\x14", "Offset to LayerRecordArray from beginning of table (20)"),
22    (b"\x00\x03", "LayerRecordCount (3)"),
23    (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
24    (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"),
25    (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"),
26    (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"),
27    (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"),
28    (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"),
29    (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"),
30    (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"),
31    (b"\x00\x02", "LayerRecord[3].PaletteIndex (2)"),
32)
33
34COLR_V0_DATA = b"".join(t[0] for t in COLR_V0_SAMPLE)
35
36
37COLR_V0_XML = [
38    '<version value="0"/>',
39    '<ColorGlyph name="glyph00006">',
40    '  <layer colorID="0" name="glyph00007"/>',
41    '  <layer colorID="1" name="glyph00008"/>',
42    '  <layer colorID="2" name="glyph00009"/>',
43    "</ColorGlyph>",
44]
45
46
47def dump(table, ttFont=None):
48    print("\n".join(getXML(table.toXML, ttFont)))
49
50
51def diff_binary_fragments(font_bytes, expected_fragments):
52    pos = 0
53    prev_desc = ""
54    errors = 0
55    for expected_bytes, description in expected_fragments:
56        actual_bytes = font_bytes[pos : pos + len(expected_bytes)]
57        if actual_bytes != expected_bytes:
58            print(
59                f'{description} (previous "{prev_desc}", actual_bytes: {"".join("%02x" % v for v in actual_bytes)} bytes: {str(font_bytes[pos:pos+16])}'
60            )
61            errors += 1
62        pos += len(expected_bytes)
63        prev_desc = description
64    assert errors == 0
65    assert pos == len(
66        font_bytes
67    ), f"Leftover font bytes, used {pos} of {len(font_bytes)}"
68
69
70@pytest.fixture
71def font():
72    font = ttLib.TTFont()
73    font.setGlyphOrder(["glyph%05d" % i for i in range(30)])
74    return font
75
76
77class COLR_V0_Test(object):
78    def test_decompile_and_compile(self, font):
79        colr = table_C_O_L_R_()
80        colr.decompile(COLR_V0_DATA, font)
81        diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE)
82
83    def test_decompile_and_dump_xml(self, font):
84        colr = table_C_O_L_R_()
85        colr.decompile(COLR_V0_DATA, font)
86
87        dump(colr, font)
88        assert getXML(colr.toXML, font) == COLR_V0_XML
89
90    def test_load_from_xml_and_compile(self, font):
91        colr = table_C_O_L_R_()
92        for name, attrs, content in parseXML(COLR_V0_XML):
93            colr.fromXML(name, attrs, content, font)
94
95        diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE)
96
97    def test_round_trip_xml(self, font):
98        colr = table_C_O_L_R_()
99        for name, attrs, content in parseXML(COLR_V0_XML):
100            colr.fromXML(name, attrs, content, font)
101        compiled = colr.compile(font)
102
103        colr = table_C_O_L_R_()
104        colr.decompile(compiled, font)
105        assert getXML(colr.toXML, font) == COLR_V0_XML
106
107
108COLR_V1_SAMPLE = (
109    (b"\x00\x01", "Version (1)"),
110    (b"\x00\x01", "BaseGlyphRecordCount (1)"),
111    (
112        b"\x00\x00\x00\x22",
113        "Offset to BaseGlyphRecordArray from beginning of table (34)",
114    ),
115    (b"\x00\x00\x00\x28", "Offset to LayerRecordArray from beginning of table (40)"),
116    (b"\x00\x03", "LayerRecordCount (3)"),
117    (b"\x00\x00\x00\x34", "Offset to BaseGlyphList from beginning of table (52)"),
118    (b"\x00\x00\x00\x9f", "Offset to LayerList from beginning of table (159)"),
119    (b"\x00\x00\x01\x66", "Offset to ClipList (358)"),
120    (b"\x00\x00\x00\x00", "Offset to DeltaSetIndexMap (NULL)"),
121    (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"),
122    (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
123    (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"),
124    (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"),
125    (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"),
126    (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"),
127    (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"),
128    (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"),
129    (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"),
130    (b"\x00\x02", "LayerRecord[2].PaletteIndex (2)"),
131    # BaseGlyphList
132    (b"\x00\x00\x00\x03", "BaseGlyphList.BaseGlyphCount (3)"),
133    (b"\x00\n", "BaseGlyphList.BaseGlyphPaintRecord[0].BaseGlyph (10)"),
134    (
135        b"\x00\x00\x00\x16",
136        "Offset to Paint table from beginning of BaseGlyphList (22)",
137    ),
138    (b"\x00\x0e", "BaseGlyphList.BaseGlyphPaintRecord[1].BaseGlyph (14)"),
139    (
140        b"\x00\x00\x00\x1c",
141        "Offset to Paint table from beginning of BaseGlyphList (28)",
142    ),
143    (b"\x00\x0f", "BaseGlyphList.BaseGlyphPaintRecord[2].BaseGlyph (15)"),
144    (
145        b"\x00\x00\x00\x4a",
146        "Offset to Paint table from beginning of BaseGlyphList (74)",
147    ),
148    # BaseGlyphPaintRecord[0]
149    (b"\x01", "BaseGlyphPaintRecord[0].Paint.Format (1)"),
150    (b"\x04", "BaseGlyphPaintRecord[0].Paint.NumLayers (4)"),
151    (b"\x00\x00\x00\x00", "BaseGlyphPaintRecord[0].Paint.FirstLayerIndex (0)"),
152    # BaseGlyphPaintRecord[1]
153    (b"\x20", "BaseGlyphPaintRecord[1].Paint.Format (32)"),
154    (b"\x00\x00\x0f", "Offset to SourcePaint from beginning of PaintComposite (15)"),
155    (b"\x03", "BaseGlyphPaintRecord[1].Paint.CompositeMode [SRC_OVER] (3)"),
156    (b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"),
157    (b"\x0d", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Format (13)"),
158    (b"\x00\x00\x07", "Offset to Paint from beginning of PaintVarTransform (7)"),
159    (
160        b"\x00\x00\x0a",
161        "Offset to VarAffine2x3 from beginning of PaintVarTransform (10)",
162    ),
163    (b"\x0b", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Format (11)"),
164    (b"\x00\x0a", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Glyph (10)"),
165    (b"\x00\x01\x00\x00", "VarAffine2x3.xx (1.0)"),
166    (b"\x00\x00\x00\x00", "VarAffine2x3.xy (0.0)"),
167    (b"\x00\x00\x00\x00", "VarAffine2x3.yx (0.0)"),
168    (b"\x00\x01\x00\x00", "VarAffine2x3.yy (1.0)"),
169    (b"\x01\x2c\x00\x00", "VarAffine2x3.dx (300.0)"),
170    (b"\x00\x00\x00\x00", "VarAffine2x3.dy (0.0)"),
171    (b"\x00\x00\x00\x00", "VarIndexBase (0)"),
172    (b"\x0a", "BaseGlyphPaintRecord[1].Paint.SourcePaint.Format (10)"),
173    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
174    (b"\x00\x0b", "BaseGlyphPaintRecord[1].Paint.SourcePaint.Glyph (11)"),
175    (b"\x08", "BaseGlyphPaintRecord[1].Paint.SourcePaint.Paint.Format (8)"),
176    (b"\x00\x00\x0c", "Offset to ColorLine from beginning of PaintSweepGradient (12)"),
177    (b"\x01\x03", "centerX (259)"),
178    (b"\x01\x2c", "centerY (300)"),
179    (b"\x10\x00", "startAngle (0.25)"),
180    (b"\x30\x00", "endAngle (0.75)"),
181    (b"\x00", "ColorLine.Extend (0; pad)"),
182    (b"\x00\x02", "ColorLine.StopCount (2)"),
183    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
184    (b"\x00\x03", "ColorLine.ColorStop[0].PaletteIndex (3)"),
185    (b"@\x00", "ColorLine.ColorStop[0].Alpha (1.0)"),
186    (b"@\x00", "ColorLine.ColorStop[1].StopOffset (1.0)"),
187    (b"\x00\x05", "ColorLine.ColorStop[1].PaletteIndex (5)"),
188    (b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"),
189    # LayerList
190    (b"\x00\x00\x00\x05", "LayerList.LayerCount (5)"),
191    (
192        b"\x00\x00\x00\x18",
193        "First Offset to Paint table from beginning of LayerList (24)",
194    ),
195    (
196        b"\x00\x00\x00\x27",
197        "Second Offset to Paint table from beginning of LayerList (39)",
198    ),
199    (
200        b"\x00\x00\x00\x52",
201        "Third Offset to Paint table from beginning of LayerList (82)",
202    ),
203    (
204        b"\x00\x00\x00\xa2",
205        "Fourth Offset to Paint table from beginning of LayerList (162)",
206    ),
207    (
208        b"\x00\x00\x00\xbc",
209        "Fifth Offset to Paint table from beginning of LayerList (188)",
210    ),
211    # BaseGlyphPaintRecord[2]
212    (b"\x0a", "BaseGlyphPaintRecord[2].Paint.Format (10)"),
213    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
214    (b"\x00\x0b", "BaseGlyphPaintRecord[2].Paint.Glyph (11)"),
215    # PaintVarSolid
216    (b"\x03", "LayerList.Paint[0].Paint.Format (3)"),
217    (b"\x00\x02", "Paint.PaletteIndex (2)"),
218    (b" \x00", "Paint.Alpha.value (0.5)"),
219    (b"\x00\x00\x00\x06", "VarIndexBase (6)"),
220    # PaintGlyph glyph00012
221    (b"\x0a", "LayerList.Paint[1].Format (10)"),
222    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
223    (b"\x00\x0c", "LayerList.Paint[1].Glyph (glyph00012)"),
224    (b"\x04", "LayerList.Paint[1].Paint.Format (4)"),
225    (b"\x00\x00\x10", "Offset to ColorLine from beginning of PaintLinearGradient (16)"),
226    (b"\x00\x01", "Paint.x0 (1)"),
227    (b"\x00\x02", "Paint.y0 (2)"),
228    (b"\xff\xfd", "Paint.x1 (-3)"),
229    (b"\xff\xfc", "Paint.y1 (-4)"),
230    (b"\x00\x05", "Paint.x2 (5)"),
231    (b"\x00\x06", "Paint.y2 (6)"),
232    (b"\x01", "ColorLine.Extend (1; repeat)"),
233    (b"\x00\x03", "ColorLine.StopCount (3)"),
234    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset (0.0)"),
235    (b"\x00\x03", "ColorLine.ColorStop[0].PaletteIndex (3)"),
236    (b"@\x00", "ColorLine.ColorStop[0].Alpha (1.0)"),
237    (b" \x00", "ColorLine.ColorStop[1].StopOffset (0.5)"),
238    (b"\x00\x04", "ColorLine.ColorStop[1].PaletteIndex (4)"),
239    (b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"),
240    (b"@\x00", "ColorLine.ColorStop[2].StopOffset (1.0)"),
241    (b"\x00\x05", "ColorLine.ColorStop[2].PaletteIndex (5)"),
242    (b"@\x00", "ColorLine.ColorStop[2].Alpha (1.0)"),
243    # PaintGlyph glyph00013
244    (b"\x0a", "LayerList.Paint[2].Format (10)"),
245    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
246    (b"\x00\x0d", "LayerList.Paint[2].Glyph (13)"),
247    (b"\x0c", "LayerList.Paint[2].Paint.Format (12)"),
248    (b"\x00\x00\x07", "Offset to Paint subtable from beginning of PaintTransform (7)"),
249    (
250        b"\x00\x00\x32",
251        "Offset to Affine2x3 subtable from beginning of PaintTransform (50)",
252    ),
253    (b"\x07", "LayerList.Paint[2].Paint.Paint.Format (7)"),
254    (
255        b"\x00\x00\x14",
256        "Offset to ColorLine from beginning of PaintVarRadialGradient (20)",
257    ),
258    (b"\x00\x07", "Paint.x0.value (7)"),
259    (b"\x00\x08", "Paint.y0.value (8)"),
260    (b"\x00\t", "Paint.r0.value (9)"),
261    (b"\x00\n", "Paint.x1.value (10)"),
262    (b"\x00\x0b", "Paint.y1.value (11)"),
263    (b"\x00\x0c", "Paint.r1.value (12)"),
264    (b"\xff\xff\xff\xff", "VarIndexBase (0xFFFFFFFF)"),
265    (b"\x00", "ColorLine.Extend (0; pad)"),
266    (b"\x00\x02", "ColorLine.StopCount (2)"),
267    (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"),
268    (b"\x00\x06", "ColorLine.ColorStop[0].PaletteIndex (6)"),
269    (b"@\x00", "ColorLine.ColorStop[0].Alpha.value (1.0)"),
270    (b"\xff\xff\xff\xff", "VarIndexBase (0xFFFFFFFF)"),
271    (b"@\x00", "ColorLine.ColorStop[1].StopOffset.value (1.0)"),
272    (b"\x00\x07", "ColorLine.ColorStop[1].PaletteIndex (7)"),
273    (b"\x19\x9a", "ColorLine.ColorStop[1].Alpha.value (0.4)"),
274    (b"\x00\x00\x00\x07", "VarIndexBase (7)"),
275    (b"\xff\xf3\x00\x00", "Affine2x3.xx (-13)"),
276    (b"\x00\x0e\x00\x00", "Affine2x3.xy (14)"),
277    (b"\x00\x0f\x00\x00", "Affine2x3.yx (15)"),
278    (b"\xff\xef\x00\x00", "Affine2x3.yy (-17)"),
279    (b"\x00\x12\x00\x00", "Affine2x3.yy (18)"),
280    (b"\x00\x13\x00\x00", "Affine2x3.yy (19)"),
281    # PaintTranslate
282    (b"\x0e", "LayerList.Paint[3].Format (14)"),
283    (b"\x00\x00\x08", "Offset to Paint subtable from beginning of PaintTranslate (8)"),
284    (b"\x01\x01", "dx (257)"),
285    (b"\x01\x02", "dy (258)"),
286    # PaintRotateAroundCenter
287    (b"\x1a", "LayerList.Paint[3].Paint.Format (26)"),
288    (
289        b"\x00\x00\x0a",
290        "Offset to Paint subtable from beginning of PaintRotateAroundCenter (11)",
291    ),
292    (b"\x10\x00", "angle (0.25)"),
293    (b"\x00\xff", "centerX (255)"),
294    (b"\x01\x00", "centerY (256)"),
295    # PaintSkew
296    (b"\x1c", "LayerList.Paint[3].Paint.Paint.Format (28)"),
297    (
298        b"\x00\x00\x08",
299        "Offset to Paint subtable from beginning of PaintSkew (8)",
300    ),
301    (b"\xfc\x17", "xSkewAngle (-0.0611)"),
302    (b"\x01\xc7", "ySkewAngle (0.0278)"),
303    # PaintGlyph glyph00011 (pointed to by both PaintSkew above and by LayerList[4] offset)
304    (b"\x0a", "LayerList.Paint[3].Paint.Paint.Paint.Format (10)"),
305    (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
306    (b"\x00\x0b", "LayerList.Paint[2].Glyph (11)"),
307    # PaintSolid
308    (b"\x02", "LayerList.Paint[0].Paint.Paint.Paint.Paint.Format (2)"),
309    (b"\x00\x02", "Paint.PaletteIndex (2)"),
310    (b" \x00", "Paint.Alpha (0.5)"),
311    # ClipList
312    (b"\x01", "ClipList.Format (1)"),
313    (b"\x00\x00\x00\x02", "ClipList.ClipCount (2)"),
314    (b"\x00\x0a", "ClipRecord[0].StartGlyphID (10)"),
315    (b"\x00\x0a", "ClipRecord[0].EndGlyphID (10)"),
316    (b"\x00\x00\x13", "Offset to ClipBox subtable from beginning of ClipList (19)"),
317    (b"\x00\x0e", "ClipRecord[1].StartGlyphID (14)"),
318    (b"\x00\x0f", "ClipRecord[1].EndGlyphID (15)"),
319    (b"\x00\x00\x20", "Offset to ClipBox subtable from beginning of ClipList (32)"),
320    (b"\x02", "ClipBox.Format (2)"),
321    (b"\x00\x00", "ClipBox.xMin (0)"),
322    (b"\x00\x00", "ClipBox.yMin (0)"),
323    (b"\x01\xf4", "ClipBox.xMax (500)"),
324    (b"\x01\xf4", "ClipBox.yMax (500)"),
325    (b"\x00\x00\x00\t", "ClipBox.VarIndexBase (9)"),
326    (b"\x01", "ClipBox.Format (1)"),
327    (b"\x00\x00", "ClipBox.xMin (0)"),
328    (b"\x00\x00", "ClipBox.yMin (0)"),
329    (b"\x03\xe8", "ClipBox.xMax (1000)"),
330    (b"\x03\xe8", "ClipBox.yMax (1000)"),
331)
332
333COLR_V1_DATA = b"".join(t[0] for t in COLR_V1_SAMPLE)
334
335COLR_V1_XML = [
336    '<Version value="1"/>',
337    "<!-- BaseGlyphRecordCount=1 -->",
338    "<BaseGlyphRecordArray>",
339    '  <BaseGlyphRecord index="0">',
340    '    <BaseGlyph value="glyph00006"/>',
341    '    <FirstLayerIndex value="0"/>',
342    '    <NumLayers value="3"/>',
343    "  </BaseGlyphRecord>",
344    "</BaseGlyphRecordArray>",
345    "<LayerRecordArray>",
346    '  <LayerRecord index="0">',
347    '    <LayerGlyph value="glyph00007"/>',
348    '    <PaletteIndex value="0"/>',
349    "  </LayerRecord>",
350    '  <LayerRecord index="1">',
351    '    <LayerGlyph value="glyph00008"/>',
352    '    <PaletteIndex value="1"/>',
353    "  </LayerRecord>",
354    '  <LayerRecord index="2">',
355    '    <LayerGlyph value="glyph00009"/>',
356    '    <PaletteIndex value="2"/>',
357    "  </LayerRecord>",
358    "</LayerRecordArray>",
359    "<!-- LayerRecordCount=3 -->",
360    "<BaseGlyphList>",
361    "  <!-- BaseGlyphCount=3 -->",
362    '  <BaseGlyphPaintRecord index="0">',
363    '    <BaseGlyph value="glyph00010"/>',
364    '    <Paint Format="1"><!-- PaintColrLayers -->',
365    '      <NumLayers value="4"/>',
366    '      <FirstLayerIndex value="0"/>',
367    "    </Paint>",
368    "  </BaseGlyphPaintRecord>",
369    '  <BaseGlyphPaintRecord index="1">',
370    '    <BaseGlyph value="glyph00014"/>',
371    '    <Paint Format="32"><!-- PaintComposite -->',
372    '      <SourcePaint Format="11"><!-- PaintColrGlyph -->',
373    '        <Glyph value="glyph00010"/>',
374    "      </SourcePaint>",
375    '      <CompositeMode value="src_over"/>',
376    '      <BackdropPaint Format="13"><!-- PaintVarTransform -->',
377    '        <Paint Format="11"><!-- PaintColrGlyph -->',
378    '          <Glyph value="glyph00010"/>',
379    "        </Paint>",
380    "        <Transform>",
381    '          <xx value="1.0"/>',
382    '          <yx value="0.0"/>',
383    '          <xy value="0.0"/>',
384    '          <yy value="1.0"/>',
385    '          <dx value="300.0"/>',
386    '          <dy value="0.0"/>',
387    '          <VarIndexBase value="0"/>',
388    "        </Transform>",
389    "      </BackdropPaint>",
390    "    </Paint>",
391    "  </BaseGlyphPaintRecord>",
392    '  <BaseGlyphPaintRecord index="2">',
393    '    <BaseGlyph value="glyph00015"/>',
394    '    <Paint Format="10"><!-- PaintGlyph -->',
395    '      <Paint Format="8"><!-- PaintSweepGradient -->',
396    "        <ColorLine>",
397    '          <Extend value="pad"/>',
398    "          <!-- StopCount=2 -->",
399    '          <ColorStop index="0">',
400    '            <StopOffset value="0.0"/>',
401    '            <PaletteIndex value="3"/>',
402    '            <Alpha value="1.0"/>',
403    "          </ColorStop>",
404    '          <ColorStop index="1">',
405    '            <StopOffset value="1.0"/>',
406    '            <PaletteIndex value="5"/>',
407    '            <Alpha value="1.0"/>',
408    "          </ColorStop>",
409    "        </ColorLine>",
410    '        <centerX value="259"/>',
411    '        <centerY value="300"/>',
412    '        <startAngle value="225.0"/>',
413    '        <endAngle value="315.0"/>',
414    "      </Paint>",
415    '      <Glyph value="glyph00011"/>',
416    "    </Paint>",
417    "  </BaseGlyphPaintRecord>",
418    "</BaseGlyphList>",
419    "<LayerList>",
420    "  <!-- LayerCount=5 -->",
421    '  <Paint index="0" Format="10"><!-- PaintGlyph -->',
422    '    <Paint Format="3"><!-- PaintVarSolid -->',
423    '      <PaletteIndex value="2"/>',
424    '      <Alpha value="0.5"/>',
425    '      <VarIndexBase value="6"/>',
426    "    </Paint>",
427    '    <Glyph value="glyph00011"/>',
428    "  </Paint>",
429    '  <Paint index="1" Format="10"><!-- PaintGlyph -->',
430    '    <Paint Format="4"><!-- PaintLinearGradient -->',
431    "      <ColorLine>",
432    '        <Extend value="repeat"/>',
433    "        <!-- StopCount=3 -->",
434    '        <ColorStop index="0">',
435    '          <StopOffset value="0.0"/>',
436    '          <PaletteIndex value="3"/>',
437    '          <Alpha value="1.0"/>',
438    "        </ColorStop>",
439    '        <ColorStop index="1">',
440    '          <StopOffset value="0.5"/>',
441    '          <PaletteIndex value="4"/>',
442    '          <Alpha value="1.0"/>',
443    "        </ColorStop>",
444    '        <ColorStop index="2">',
445    '          <StopOffset value="1.0"/>',
446    '          <PaletteIndex value="5"/>',
447    '          <Alpha value="1.0"/>',
448    "        </ColorStop>",
449    "      </ColorLine>",
450    '      <x0 value="1"/>',
451    '      <y0 value="2"/>',
452    '      <x1 value="-3"/>',
453    '      <y1 value="-4"/>',
454    '      <x2 value="5"/>',
455    '      <y2 value="6"/>',
456    "    </Paint>",
457    '    <Glyph value="glyph00012"/>',
458    "  </Paint>",
459    '  <Paint index="2" Format="10"><!-- PaintGlyph -->',
460    '    <Paint Format="12"><!-- PaintTransform -->',
461    '      <Paint Format="7"><!-- PaintVarRadialGradient -->',
462    "        <ColorLine>",
463    '          <Extend value="pad"/>',
464    "          <!-- StopCount=2 -->",
465    '          <ColorStop index="0">',
466    '            <StopOffset value="0.0"/>',
467    '            <PaletteIndex value="6"/>',
468    '            <Alpha value="1.0"/>',
469    "            <VarIndexBase/>",
470    "          </ColorStop>",
471    '          <ColorStop index="1">',
472    '            <StopOffset value="1.0"/>',
473    '            <PaletteIndex value="7"/>',
474    '            <Alpha value="0.4"/>',
475    '            <VarIndexBase value="7"/>',
476    "          </ColorStop>",
477    "        </ColorLine>",
478    '        <x0 value="7"/>',
479    '        <y0 value="8"/>',
480    '        <r0 value="9"/>',
481    '        <x1 value="10"/>',
482    '        <y1 value="11"/>',
483    '        <r1 value="12"/>',
484    "        <VarIndexBase/>",
485    "      </Paint>",
486    "      <Transform>",
487    '        <xx value="-13.0"/>',
488    '        <yx value="14.0"/>',
489    '        <xy value="15.0"/>',
490    '        <yy value="-17.0"/>',
491    '        <dx value="18.0"/>',
492    '        <dy value="19.0"/>',
493    "      </Transform>",
494    "    </Paint>",
495    '    <Glyph value="glyph00013"/>',
496    "  </Paint>",
497    '  <Paint index="3" Format="14"><!-- PaintTranslate -->',
498    '    <Paint Format="26"><!-- PaintRotateAroundCenter -->',
499    '      <Paint Format="28"><!-- PaintSkew -->',
500    '        <Paint Format="10"><!-- PaintGlyph -->',
501    '          <Paint Format="2"><!-- PaintSolid -->',
502    '            <PaletteIndex value="2"/>',
503    '            <Alpha value="0.5"/>',
504    "          </Paint>",
505    '          <Glyph value="glyph00011"/>',
506    "        </Paint>",
507    '        <xSkewAngle value="-11.0"/>',
508    '        <ySkewAngle value="5.0"/>',
509    "      </Paint>",
510    '      <angle value="45.0"/>',
511    '      <centerX value="255"/>',
512    '      <centerY value="256"/>',
513    "    </Paint>",
514    '    <dx value="257"/>',
515    '    <dy value="258"/>',
516    "  </Paint>",
517    '  <Paint index="4" Format="10"><!-- PaintGlyph -->',
518    '    <Paint Format="2"><!-- PaintSolid -->',
519    '      <PaletteIndex value="2"/>',
520    '      <Alpha value="0.5"/>',
521    "    </Paint>",
522    '    <Glyph value="glyph00011"/>',
523    "  </Paint>",
524    "</LayerList>",
525    '<ClipList Format="1">',
526    "  <Clip>",
527    '    <Glyph value="glyph00010"/>',
528    '    <ClipBox Format="2">',
529    '      <xMin value="0"/>',
530    '      <yMin value="0"/>',
531    '      <xMax value="500"/>',
532    '      <yMax value="500"/>',
533    '      <VarIndexBase value="9"/>',
534    "    </ClipBox>",
535    "  </Clip>",
536    "  <Clip>",
537    '    <Glyph value="glyph00014"/>',
538    '    <Glyph value="glyph00015"/>',
539    '    <ClipBox Format="1">',
540    '      <xMin value="0"/>',
541    '      <yMin value="0"/>',
542    '      <xMax value="1000"/>',
543    '      <yMax value="1000"/>',
544    "    </ClipBox>",
545    "  </Clip>",
546    "</ClipList>",
547]
548
549COLR_V1_VAR_XML = [
550    '<VarIndexMap Format="0">',
551    "  <!-- Omitted values default to 0xFFFF/0xFFFF (no variations) -->",
552    '  <Map index="0" outer="1" inner="0"/>',
553    '  <Map index="1"/>',
554    '  <Map index="2"/>',
555    '  <Map index="3" outer="1" inner="0"/>',
556    '  <Map index="4"/>',
557    '  <Map index="5"/>',
558    '  <Map index="6" outer="0" inner="2"/>',
559    '  <Map index="7" outer="0" inner="0"/>',
560    '  <Map index="8" outer="0" inner="1"/>',
561    '  <Map index="9"/>',
562    '  <Map index="10"/>',
563    '  <Map index="11" outer="0" inner="3"/>',
564    '  <Map index="12" outer="0" inner="3"/>',
565    "</VarIndexMap>",
566    '<VarStore Format="1">',
567    '  <Format value="1"/>',
568    "  <VarRegionList>",
569    "    <!-- RegionAxisCount=1 -->",
570    "    <!-- RegionCount=1 -->",
571    '    <Region index="0">',
572    '      <VarRegionAxis index="0">',
573    '        <StartCoord value="0.0"/>',
574    '        <PeakCoord value="1.0"/>',
575    '        <EndCoord value="1.0"/>',
576    "      </VarRegionAxis>",
577    "    </Region>",
578    "  </VarRegionList>",
579    "  <!-- VarDataCount=2 -->",
580    '  <VarData index="0">',
581    "    <!-- ItemCount=4 -->",
582    '    <NumShorts value="1"/>',
583    "    <!-- VarRegionCount=1 -->",
584    '    <VarRegionIndex index="0" value="0"/>',
585    '    <Item index="0" value="[-3277]"/>',
586    '    <Item index="1" value="[6553]"/>',
587    '    <Item index="2" value="[8192]"/>',
588    '    <Item index="3" value="[500]"/>',
589    "  </VarData>",
590    '  <VarData index="1">',
591    "    <!-- ItemCount=1 -->",
592    '    <NumShorts value="32769"/>',
593    "    <!-- VarRegionCount=1 -->",
594    '    <VarRegionIndex index="0" value="0"/>',
595    '    <Item index="0" value="[65536]"/>',
596    "  </VarData>",
597    "</VarStore>",
598]
599
600
601class COLR_V1_Test(object):
602    def test_decompile_and_compile(self, font):
603        colr = table_C_O_L_R_()
604        colr.decompile(COLR_V1_DATA, font)
605        diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE)
606
607    def test_decompile_and_dump_xml(self, font):
608        colr = table_C_O_L_R_()
609        colr.decompile(COLR_V1_DATA, font)
610
611        dump(colr, font)
612        assert getXML(colr.toXML, font) == COLR_V1_XML
613
614    def test_load_from_xml_and_compile(self, font):
615        colr = table_C_O_L_R_()
616        for name, attrs, content in parseXML(COLR_V1_XML):
617            colr.fromXML(name, attrs, content, font)
618        diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE)
619
620    def test_round_trip_xml(self, font):
621        colr = table_C_O_L_R_()
622        for name, attrs, content in parseXML(COLR_V1_XML):
623            colr.fromXML(name, attrs, content, font)
624        compiled = colr.compile(font)
625
626        colr = table_C_O_L_R_()
627        colr.decompile(compiled, font)
628        assert getXML(colr.toXML, font) == COLR_V1_XML
629
630    @pytest.mark.parametrize("quantization", [1, 10, 100])
631    @pytest.mark.parametrize("flavor", ["glyf", "cff"])
632    def test_computeClipBoxes(self, flavor, quantization):
633        font = TTFont()
634        font.importXML(TEST_DATA_DIR / f"COLRv1-clip-boxes-{flavor}.ttx")
635        assert font["COLR"].table.ClipList is None
636
637        font["COLR"].table.computeClipBoxes(font.getGlyphSet(), quantization)
638
639        clipList = font["COLR"].table.ClipList
640        assert len(clipList.clips) > 0
641
642        expected = TTFont()
643        expected.importXML(
644            TEST_DATA_DIR / f"COLRv1-clip-boxes-q{quantization}-expected.ttx"
645        )
646        expectedClipList = expected["COLR"].table.ClipList
647
648        assert getXML(clipList.toXML) == getXML(expectedClipList.toXML)
649
650
651class COLR_V1_Variable_Test(object):
652    def test_round_trip_xml(self, font):
653        colr = table_C_O_L_R_()
654        xml = COLR_V1_XML + COLR_V1_VAR_XML
655        for name, attrs, content in parseXML(xml):
656            colr.fromXML(name, attrs, content, font)
657        compiled = colr.compile(font)
658
659        colr = table_C_O_L_R_()
660        colr.decompile(compiled, font)
661        assert getXML(colr.toXML, font) == xml
662