xref: /aosp_15_r20/external/fonttools/Tests/varLib/merger_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from copy import deepcopy
2import string
3from fontTools.colorLib.builder import LayerListBuilder, buildCOLR, buildClipList
4from fontTools.misc.testTools import getXML
5from fontTools.varLib.merger import COLRVariationMerger
6from fontTools.varLib.models import VariationModel
7from fontTools.ttLib import TTFont
8from fontTools.ttLib.tables import otTables as ot
9from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
10from io import BytesIO
11import pytest
12
13
14NO_VARIATION_INDEX = ot.NO_VARIATION_INDEX
15
16
17def dump_xml(table, ttFont=None):
18    xml = getXML(table.toXML, ttFont)
19    print("[")
20    for line in xml:
21        print(f"  {line!r},")
22    print("]")
23    return xml
24
25
26def compile_decompile(table, ttFont):
27    writer = OTTableWriter(tableTag="COLR")
28    # compile itself may modify a table, safer to copy it first
29    table = deepcopy(table)
30    table.compile(writer, ttFont)
31    data = writer.getAllData()
32
33    reader = OTTableReader(data, tableTag="COLR")
34    table2 = table.__class__()
35    table2.decompile(reader, ttFont)
36
37    return table2
38
39
40@pytest.fixture
41def ttFont():
42    font = TTFont()
43    font.setGlyphOrder([".notdef"] + list(string.ascii_letters))
44    return font
45
46
47def build_paint(data):
48    return LayerListBuilder().buildPaint(data)
49
50
51class COLRVariationMergerTest:
52    @pytest.mark.parametrize(
53        "paints, expected_xml, expected_varIdxes",
54        [
55            pytest.param(
56                [
57                    {
58                        "Format": int(ot.PaintFormat.PaintSolid),
59                        "PaletteIndex": 0,
60                        "Alpha": 1.0,
61                    },
62                    {
63                        "Format": int(ot.PaintFormat.PaintSolid),
64                        "PaletteIndex": 0,
65                        "Alpha": 1.0,
66                    },
67                ],
68                [
69                    '<Paint Format="2"><!-- PaintSolid -->',
70                    '  <PaletteIndex value="0"/>',
71                    '  <Alpha value="1.0"/>',
72                    "</Paint>",
73                ],
74                [],
75                id="solid-same",
76            ),
77            pytest.param(
78                [
79                    {
80                        "Format": int(ot.PaintFormat.PaintSolid),
81                        "PaletteIndex": 0,
82                        "Alpha": 1.0,
83                    },
84                    {
85                        "Format": int(ot.PaintFormat.PaintSolid),
86                        "PaletteIndex": 0,
87                        "Alpha": 0.5,
88                    },
89                ],
90                [
91                    '<Paint Format="3"><!-- PaintVarSolid -->',
92                    '  <PaletteIndex value="0"/>',
93                    '  <Alpha value="1.0"/>',
94                    '  <VarIndexBase value="0"/>',
95                    "</Paint>",
96                ],
97                [0],
98                id="solid-alpha",
99            ),
100            pytest.param(
101                [
102                    {
103                        "Format": int(ot.PaintFormat.PaintLinearGradient),
104                        "ColorLine": {
105                            "Extend": int(ot.ExtendMode.PAD),
106                            "ColorStop": [
107                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
108                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
109                            ],
110                        },
111                        "x0": 0,
112                        "y0": 0,
113                        "x1": 1,
114                        "y1": 1,
115                        "x2": 2,
116                        "y2": 2,
117                    },
118                    {
119                        "Format": int(ot.PaintFormat.PaintLinearGradient),
120                        "ColorLine": {
121                            "Extend": int(ot.ExtendMode.PAD),
122                            "ColorStop": [
123                                {"StopOffset": 0.1, "PaletteIndex": 0, "Alpha": 1.0},
124                                {"StopOffset": 0.9, "PaletteIndex": 1, "Alpha": 1.0},
125                            ],
126                        },
127                        "x0": 0,
128                        "y0": 0,
129                        "x1": 1,
130                        "y1": 1,
131                        "x2": 2,
132                        "y2": 2,
133                    },
134                ],
135                [
136                    '<Paint Format="5"><!-- PaintVarLinearGradient -->',
137                    "  <ColorLine>",
138                    '    <Extend value="pad"/>',
139                    "    <!-- StopCount=2 -->",
140                    '    <ColorStop index="0">',
141                    '      <StopOffset value="0.0"/>',
142                    '      <PaletteIndex value="0"/>',
143                    '      <Alpha value="1.0"/>',
144                    '      <VarIndexBase value="0"/>',
145                    "    </ColorStop>",
146                    '    <ColorStop index="1">',
147                    '      <StopOffset value="1.0"/>',
148                    '      <PaletteIndex value="1"/>',
149                    '      <Alpha value="1.0"/>',
150                    '      <VarIndexBase value="2"/>',
151                    "    </ColorStop>",
152                    "  </ColorLine>",
153                    '  <x0 value="0"/>',
154                    '  <y0 value="0"/>',
155                    '  <x1 value="1"/>',
156                    '  <y1 value="1"/>',
157                    '  <x2 value="2"/>',
158                    '  <y2 value="2"/>',
159                    "  <VarIndexBase/>",
160                    "</Paint>",
161                ],
162                [0, NO_VARIATION_INDEX, 1, NO_VARIATION_INDEX],
163                id="linear_grad-stop-offsets",
164            ),
165            pytest.param(
166                [
167                    {
168                        "Format": int(ot.PaintFormat.PaintLinearGradient),
169                        "ColorLine": {
170                            "Extend": int(ot.ExtendMode.PAD),
171                            "ColorStop": [
172                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
173                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
174                            ],
175                        },
176                        "x0": 0,
177                        "y0": 0,
178                        "x1": 1,
179                        "y1": 1,
180                        "x2": 2,
181                        "y2": 2,
182                    },
183                    {
184                        "Format": int(ot.PaintFormat.PaintLinearGradient),
185                        "ColorLine": {
186                            "Extend": int(ot.ExtendMode.PAD),
187                            "ColorStop": [
188                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 0.5},
189                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
190                            ],
191                        },
192                        "x0": 0,
193                        "y0": 0,
194                        "x1": 1,
195                        "y1": 1,
196                        "x2": 2,
197                        "y2": 2,
198                    },
199                ],
200                [
201                    '<Paint Format="5"><!-- PaintVarLinearGradient -->',
202                    "  <ColorLine>",
203                    '    <Extend value="pad"/>',
204                    "    <!-- StopCount=2 -->",
205                    '    <ColorStop index="0">',
206                    '      <StopOffset value="0.0"/>',
207                    '      <PaletteIndex value="0"/>',
208                    '      <Alpha value="1.0"/>',
209                    '      <VarIndexBase value="0"/>',
210                    "    </ColorStop>",
211                    '    <ColorStop index="1">',
212                    '      <StopOffset value="1.0"/>',
213                    '      <PaletteIndex value="1"/>',
214                    '      <Alpha value="1.0"/>',
215                    "      <VarIndexBase/>",
216                    "    </ColorStop>",
217                    "  </ColorLine>",
218                    '  <x0 value="0"/>',
219                    '  <y0 value="0"/>',
220                    '  <x1 value="1"/>',
221                    '  <y1 value="1"/>',
222                    '  <x2 value="2"/>',
223                    '  <y2 value="2"/>',
224                    "  <VarIndexBase/>",
225                    "</Paint>",
226                ],
227                [NO_VARIATION_INDEX, 0],
228                id="linear_grad-stop[0].alpha",
229            ),
230            pytest.param(
231                [
232                    {
233                        "Format": int(ot.PaintFormat.PaintLinearGradient),
234                        "ColorLine": {
235                            "Extend": int(ot.ExtendMode.PAD),
236                            "ColorStop": [
237                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
238                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
239                            ],
240                        },
241                        "x0": 0,
242                        "y0": 0,
243                        "x1": 1,
244                        "y1": 1,
245                        "x2": 2,
246                        "y2": 2,
247                    },
248                    {
249                        "Format": int(ot.PaintFormat.PaintLinearGradient),
250                        "ColorLine": {
251                            "Extend": int(ot.ExtendMode.PAD),
252                            "ColorStop": [
253                                {"StopOffset": -0.5, "PaletteIndex": 0, "Alpha": 1.0},
254                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
255                            ],
256                        },
257                        "x0": 0,
258                        "y0": 0,
259                        "x1": 1,
260                        "y1": 1,
261                        "x2": 2,
262                        "y2": -200,
263                    },
264                ],
265                [
266                    '<Paint Format="5"><!-- PaintVarLinearGradient -->',
267                    "  <ColorLine>",
268                    '    <Extend value="pad"/>',
269                    "    <!-- StopCount=2 -->",
270                    '    <ColorStop index="0">',
271                    '      <StopOffset value="0.0"/>',
272                    '      <PaletteIndex value="0"/>',
273                    '      <Alpha value="1.0"/>',
274                    '      <VarIndexBase value="0"/>',
275                    "    </ColorStop>",
276                    '    <ColorStop index="1">',
277                    '      <StopOffset value="1.0"/>',
278                    '      <PaletteIndex value="1"/>',
279                    '      <Alpha value="1.0"/>',
280                    "      <VarIndexBase/>",
281                    "    </ColorStop>",
282                    "  </ColorLine>",
283                    '  <x0 value="0"/>',
284                    '  <y0 value="0"/>',
285                    '  <x1 value="1"/>',
286                    '  <y1 value="1"/>',
287                    '  <x2 value="2"/>',
288                    '  <y2 value="2"/>',
289                    '  <VarIndexBase value="1"/>',
290                    "</Paint>",
291                ],
292                [
293                    0,
294                    NO_VARIATION_INDEX,
295                    NO_VARIATION_INDEX,
296                    NO_VARIATION_INDEX,
297                    NO_VARIATION_INDEX,
298                    NO_VARIATION_INDEX,
299                    1,
300                ],
301                id="linear_grad-stop[0].offset-y2",
302            ),
303            pytest.param(
304                [
305                    {
306                        "Format": int(ot.PaintFormat.PaintRadialGradient),
307                        "ColorLine": {
308                            "Extend": int(ot.ExtendMode.PAD),
309                            "ColorStop": [
310                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
311                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
312                            ],
313                        },
314                        "x0": 0,
315                        "y0": 0,
316                        "r0": 0,
317                        "x1": 1,
318                        "y1": 1,
319                        "r1": 1,
320                    },
321                    {
322                        "Format": int(ot.PaintFormat.PaintRadialGradient),
323                        "ColorLine": {
324                            "Extend": int(ot.ExtendMode.PAD),
325                            "ColorStop": [
326                                {"StopOffset": 0.1, "PaletteIndex": 0, "Alpha": 0.6},
327                                {"StopOffset": 0.9, "PaletteIndex": 1, "Alpha": 0.7},
328                            ],
329                        },
330                        "x0": -1,
331                        "y0": -2,
332                        "r0": 3,
333                        "x1": -4,
334                        "y1": -5,
335                        "r1": 6,
336                    },
337                ],
338                [
339                    '<Paint Format="7"><!-- PaintVarRadialGradient -->',
340                    "  <ColorLine>",
341                    '    <Extend value="pad"/>',
342                    "    <!-- StopCount=2 -->",
343                    '    <ColorStop index="0">',
344                    '      <StopOffset value="0.0"/>',
345                    '      <PaletteIndex value="0"/>',
346                    '      <Alpha value="1.0"/>',
347                    '      <VarIndexBase value="0"/>',
348                    "    </ColorStop>",
349                    '    <ColorStop index="1">',
350                    '      <StopOffset value="1.0"/>',
351                    '      <PaletteIndex value="1"/>',
352                    '      <Alpha value="1.0"/>',
353                    '      <VarIndexBase value="2"/>',
354                    "    </ColorStop>",
355                    "  </ColorLine>",
356                    '  <x0 value="0"/>',
357                    '  <y0 value="0"/>',
358                    '  <r0 value="0"/>',
359                    '  <x1 value="1"/>',
360                    '  <y1 value="1"/>',
361                    '  <r1 value="1"/>',
362                    '  <VarIndexBase value="4"/>',
363                    "</Paint>",
364                ],
365                [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
366                id="radial_grad-all-different",
367            ),
368            pytest.param(
369                [
370                    {
371                        "Format": int(ot.PaintFormat.PaintSweepGradient),
372                        "ColorLine": {
373                            "Extend": int(ot.ExtendMode.REPEAT),
374                            "ColorStop": [
375                                {"StopOffset": 0.4, "PaletteIndex": 0, "Alpha": 1.0},
376                                {"StopOffset": 0.6, "PaletteIndex": 1, "Alpha": 1.0},
377                            ],
378                        },
379                        "centerX": 0,
380                        "centerY": 0,
381                        "startAngle": 0,
382                        "endAngle": 180.0,
383                    },
384                    {
385                        "Format": int(ot.PaintFormat.PaintSweepGradient),
386                        "ColorLine": {
387                            "Extend": int(ot.ExtendMode.REPEAT),
388                            "ColorStop": [
389                                {"StopOffset": 0.4, "PaletteIndex": 0, "Alpha": 1.0},
390                                {"StopOffset": 0.6, "PaletteIndex": 1, "Alpha": 1.0},
391                            ],
392                        },
393                        "centerX": 0,
394                        "centerY": 0,
395                        "startAngle": 90.0,
396                        "endAngle": 180.0,
397                    },
398                ],
399                [
400                    '<Paint Format="9"><!-- PaintVarSweepGradient -->',
401                    "  <ColorLine>",
402                    '    <Extend value="repeat"/>',
403                    "    <!-- StopCount=2 -->",
404                    '    <ColorStop index="0">',
405                    '      <StopOffset value="0.4"/>',
406                    '      <PaletteIndex value="0"/>',
407                    '      <Alpha value="1.0"/>',
408                    "      <VarIndexBase/>",
409                    "    </ColorStop>",
410                    '    <ColorStop index="1">',
411                    '      <StopOffset value="0.6"/>',
412                    '      <PaletteIndex value="1"/>',
413                    '      <Alpha value="1.0"/>',
414                    "      <VarIndexBase/>",
415                    "    </ColorStop>",
416                    "  </ColorLine>",
417                    '  <centerX value="0"/>',
418                    '  <centerY value="0"/>',
419                    '  <startAngle value="0.0"/>',
420                    '  <endAngle value="180.0"/>',
421                    '  <VarIndexBase value="0"/>',
422                    "</Paint>",
423                ],
424                [NO_VARIATION_INDEX, NO_VARIATION_INDEX, 0, NO_VARIATION_INDEX],
425                id="sweep_grad-startAngle",
426            ),
427            pytest.param(
428                [
429                    {
430                        "Format": int(ot.PaintFormat.PaintSweepGradient),
431                        "ColorLine": {
432                            "Extend": int(ot.ExtendMode.PAD),
433                            "ColorStop": [
434                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 1.0},
435                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 1.0},
436                            ],
437                        },
438                        "centerX": 0,
439                        "centerY": 0,
440                        "startAngle": 0.0,
441                        "endAngle": 180.0,
442                    },
443                    {
444                        "Format": int(ot.PaintFormat.PaintSweepGradient),
445                        "ColorLine": {
446                            "Extend": int(ot.ExtendMode.PAD),
447                            "ColorStop": [
448                                {"StopOffset": 0.0, "PaletteIndex": 0, "Alpha": 0.5},
449                                {"StopOffset": 1.0, "PaletteIndex": 1, "Alpha": 0.5},
450                            ],
451                        },
452                        "centerX": 0,
453                        "centerY": 0,
454                        "startAngle": 0.0,
455                        "endAngle": 180.0,
456                    },
457                ],
458                [
459                    '<Paint Format="9"><!-- PaintVarSweepGradient -->',
460                    "  <ColorLine>",
461                    '    <Extend value="pad"/>',
462                    "    <!-- StopCount=2 -->",
463                    '    <ColorStop index="0">',
464                    '      <StopOffset value="0.0"/>',
465                    '      <PaletteIndex value="0"/>',
466                    '      <Alpha value="1.0"/>',
467                    '      <VarIndexBase value="0"/>',
468                    "    </ColorStop>",
469                    '    <ColorStop index="1">',
470                    '      <StopOffset value="1.0"/>',
471                    '      <PaletteIndex value="1"/>',
472                    '      <Alpha value="1.0"/>',
473                    '      <VarIndexBase value="0"/>',
474                    "    </ColorStop>",
475                    "  </ColorLine>",
476                    '  <centerX value="0"/>',
477                    '  <centerY value="0"/>',
478                    '  <startAngle value="0.0"/>',
479                    '  <endAngle value="180.0"/>',
480                    "  <VarIndexBase/>",
481                    "</Paint>",
482                ],
483                [NO_VARIATION_INDEX, 0],
484                id="sweep_grad-stops-alpha-reuse-varidxbase",
485            ),
486            pytest.param(
487                [
488                    {
489                        "Format": int(ot.PaintFormat.PaintTransform),
490                        "Paint": {
491                            "Format": int(ot.PaintFormat.PaintRadialGradient),
492                            "ColorLine": {
493                                "Extend": int(ot.ExtendMode.PAD),
494                                "ColorStop": [
495                                    {
496                                        "StopOffset": 0.0,
497                                        "PaletteIndex": 0,
498                                        "Alpha": 1.0,
499                                    },
500                                    {
501                                        "StopOffset": 1.0,
502                                        "PaletteIndex": 1,
503                                        "Alpha": 1.0,
504                                    },
505                                ],
506                            },
507                            "x0": 0,
508                            "y0": 0,
509                            "r0": 0,
510                            "x1": 1,
511                            "y1": 1,
512                            "r1": 1,
513                        },
514                        "Transform": {
515                            "xx": 1.0,
516                            "xy": 0.0,
517                            "yx": 0.0,
518                            "yy": 1.0,
519                            "dx": 0.0,
520                            "dy": 0.0,
521                        },
522                    },
523                    {
524                        "Format": int(ot.PaintFormat.PaintTransform),
525                        "Paint": {
526                            "Format": int(ot.PaintFormat.PaintRadialGradient),
527                            "ColorLine": {
528                                "Extend": int(ot.ExtendMode.PAD),
529                                "ColorStop": [
530                                    {
531                                        "StopOffset": 0.0,
532                                        "PaletteIndex": 0,
533                                        "Alpha": 1.0,
534                                    },
535                                    {
536                                        "StopOffset": 1.0,
537                                        "PaletteIndex": 1,
538                                        "Alpha": 1.0,
539                                    },
540                                ],
541                            },
542                            "x0": 0,
543                            "y0": 0,
544                            "r0": 0,
545                            "x1": 1,
546                            "y1": 1,
547                            "r1": 1,
548                        },
549                        "Transform": {
550                            "xx": 1.0,
551                            "xy": 0.0,
552                            "yx": 0.0,
553                            "yy": 0.5,
554                            "dx": 0.0,
555                            "dy": -100.0,
556                        },
557                    },
558                ],
559                [
560                    '<Paint Format="13"><!-- PaintVarTransform -->',
561                    '  <Paint Format="6"><!-- PaintRadialGradient -->',
562                    "    <ColorLine>",
563                    '      <Extend value="pad"/>',
564                    "      <!-- StopCount=2 -->",
565                    '      <ColorStop index="0">',
566                    '        <StopOffset value="0.0"/>',
567                    '        <PaletteIndex value="0"/>',
568                    '        <Alpha value="1.0"/>',
569                    "      </ColorStop>",
570                    '      <ColorStop index="1">',
571                    '        <StopOffset value="1.0"/>',
572                    '        <PaletteIndex value="1"/>',
573                    '        <Alpha value="1.0"/>',
574                    "      </ColorStop>",
575                    "    </ColorLine>",
576                    '    <x0 value="0"/>',
577                    '    <y0 value="0"/>',
578                    '    <r0 value="0"/>',
579                    '    <x1 value="1"/>',
580                    '    <y1 value="1"/>',
581                    '    <r1 value="1"/>',
582                    "  </Paint>",
583                    "  <Transform>",
584                    '    <xx value="1.0"/>',
585                    '    <yx value="0.0"/>',
586                    '    <xy value="0.0"/>',
587                    '    <yy value="1.0"/>',
588                    '    <dx value="0.0"/>',
589                    '    <dy value="0.0"/>',
590                    '    <VarIndexBase value="0"/>',
591                    "  </Transform>",
592                    "</Paint>",
593                ],
594                [
595                    NO_VARIATION_INDEX,
596                    NO_VARIATION_INDEX,
597                    NO_VARIATION_INDEX,
598                    0,
599                    NO_VARIATION_INDEX,
600                    1,
601                ],
602                id="transform-yy-dy",
603            ),
604            pytest.param(
605                [
606                    {
607                        "Format": ot.PaintFormat.PaintTransform,
608                        "Paint": {
609                            "Format": ot.PaintFormat.PaintSweepGradient,
610                            "ColorLine": {
611                                "Extend": ot.ExtendMode.PAD,
612                                "ColorStop": [
613                                    {"StopOffset": 0.0, "PaletteIndex": 0},
614                                    {
615                                        "StopOffset": 1.0,
616                                        "PaletteIndex": 1,
617                                        "Alpha": 1.0,
618                                    },
619                                ],
620                            },
621                            "centerX": 0,
622                            "centerY": 0,
623                            "startAngle": 0,
624                            "endAngle": 360,
625                        },
626                        "Transform": (1.0, 0, 0, 1.0, 0, 0),
627                    },
628                    {
629                        "Format": ot.PaintFormat.PaintTransform,
630                        "Paint": {
631                            "Format": ot.PaintFormat.PaintSweepGradient,
632                            "ColorLine": {
633                                "Extend": ot.ExtendMode.PAD,
634                                "ColorStop": [
635                                    {"StopOffset": 0.0, "PaletteIndex": 0},
636                                    {
637                                        "StopOffset": 1.0,
638                                        "PaletteIndex": 1,
639                                        "Alpha": 1.0,
640                                    },
641                                ],
642                            },
643                            "centerX": 256,
644                            "centerY": 0,
645                            "startAngle": 0,
646                            "endAngle": 360,
647                        },
648                        # Transform.xx below produces the same VarStore delta as the
649                        # above PaintSweepGradient's centerX because, when Fixed16.16
650                        # is converted to integer, it becomes:
651                        # floatToFixed(1.00390625, 16) == 256
652                        # Because there is overlap between the varIdxes of the
653                        # PaintVarTransform's Affine2x3 and the PaintSweepGradient's
654                        # the VarIndexBase is reused (0 for both)
655                        "Transform": (1.00390625, 0, 0, 1.0, 10, 0),
656                    },
657                ],
658                [
659                    '<Paint Format="13"><!-- PaintVarTransform -->',
660                    '  <Paint Format="9"><!-- PaintVarSweepGradient -->',
661                    "    <ColorLine>",
662                    '      <Extend value="pad"/>',
663                    "      <!-- StopCount=2 -->",
664                    '      <ColorStop index="0">',
665                    '        <StopOffset value="0.0"/>',
666                    '        <PaletteIndex value="0"/>',
667                    '        <Alpha value="1.0"/>',
668                    "        <VarIndexBase/>",
669                    "      </ColorStop>",
670                    '      <ColorStop index="1">',
671                    '        <StopOffset value="1.0"/>',
672                    '        <PaletteIndex value="1"/>',
673                    '        <Alpha value="1.0"/>',
674                    "        <VarIndexBase/>",
675                    "      </ColorStop>",
676                    "    </ColorLine>",
677                    '    <centerX value="0"/>',
678                    '    <centerY value="0"/>',
679                    '    <startAngle value="0.0"/>',
680                    '    <endAngle value="360.0"/>',
681                    '    <VarIndexBase value="0"/>',
682                    "  </Paint>",
683                    "  <Transform>",
684                    '    <xx value="1.0"/>',
685                    '    <yx value="0.0"/>',
686                    '    <xy value="0.0"/>',
687                    '    <yy value="1.0"/>',
688                    '    <dx value="0.0"/>',
689                    '    <dy value="0.0"/>',
690                    '    <VarIndexBase value="0"/>',
691                    "  </Transform>",
692                    "</Paint>",
693                ],
694                [
695                    0,
696                    NO_VARIATION_INDEX,
697                    NO_VARIATION_INDEX,
698                    NO_VARIATION_INDEX,
699                    1,
700                    NO_VARIATION_INDEX,
701                ],
702                id="transform-xx-sweep_grad-centerx-same-varidxbase",
703            ),
704        ],
705    )
706    def test_merge_Paint(self, paints, ttFont, expected_xml, expected_varIdxes):
707        paints = [build_paint(p) for p in paints]
708        out = deepcopy(paints[0])
709
710        model = VariationModel([{}, {"ZZZZ": 1.0}])
711        merger = COLRVariationMerger(model, ["ZZZZ"], ttFont)
712
713        merger.mergeThings(out, paints)
714
715        assert compile_decompile(out, ttFont) == out
716        assert dump_xml(out, ttFont) == expected_xml
717        assert merger.varIdxes == expected_varIdxes
718
719    def test_merge_ClipList(self, ttFont):
720        clipLists = [
721            buildClipList(clips)
722            for clips in [
723                {
724                    "A": (0, 0, 1000, 1000),
725                    "B": (0, 0, 1000, 1000),
726                    "C": (0, 0, 1000, 1000),
727                    "D": (0, 0, 1000, 1000),
728                },
729                {
730                    # non-default masters' clip boxes can be 'sparse'
731                    # (i.e. can omit explicit clip box for some glyphs)
732                    # "A": (0, 0, 1000, 1000),
733                    "B": (10, 0, 1000, 1000),
734                    "C": (20, 20, 1020, 1020),
735                    "D": (20, 20, 1020, 1020),
736                },
737            ]
738        ]
739        out = deepcopy(clipLists[0])
740
741        model = VariationModel([{}, {"ZZZZ": 1.0}])
742        merger = COLRVariationMerger(model, ["ZZZZ"], ttFont)
743
744        merger.mergeThings(out, clipLists)
745
746        assert compile_decompile(out, ttFont) == out
747        assert dump_xml(out, ttFont) == [
748            '<ClipList Format="1">',
749            "  <Clip>",
750            '    <Glyph value="A"/>',
751            '    <ClipBox Format="1">',
752            '      <xMin value="0"/>',
753            '      <yMin value="0"/>',
754            '      <xMax value="1000"/>',
755            '      <yMax value="1000"/>',
756            "    </ClipBox>",
757            "  </Clip>",
758            "  <Clip>",
759            '    <Glyph value="B"/>',
760            '    <ClipBox Format="2">',
761            '      <xMin value="0"/>',
762            '      <yMin value="0"/>',
763            '      <xMax value="1000"/>',
764            '      <yMax value="1000"/>',
765            '      <VarIndexBase value="0"/>',
766            "    </ClipBox>",
767            "  </Clip>",
768            "  <Clip>",
769            '    <Glyph value="C"/>',
770            '    <Glyph value="D"/>',
771            '    <ClipBox Format="2">',
772            '      <xMin value="0"/>',
773            '      <yMin value="0"/>',
774            '      <xMax value="1000"/>',
775            '      <yMax value="1000"/>',
776            '      <VarIndexBase value="4"/>',
777            "    </ClipBox>",
778            "  </Clip>",
779            "</ClipList>",
780        ]
781        assert merger.varIdxes == [
782            0,
783            NO_VARIATION_INDEX,
784            NO_VARIATION_INDEX,
785            NO_VARIATION_INDEX,
786            1,
787            1,
788            1,
789            1,
790        ]
791
792    @pytest.mark.parametrize(
793        "master_layer_reuse",
794        [
795            pytest.param(False, id="no-reuse"),
796            pytest.param(True, id="with-reuse"),
797        ],
798    )
799    @pytest.mark.parametrize(
800        "color_glyphs, output_layer_reuse, expected_xml, expected_varIdxes",
801        [
802            pytest.param(
803                [
804                    {
805                        "A": {
806                            "Format": int(ot.PaintFormat.PaintColrLayers),
807                            "Layers": [
808                                {
809                                    "Format": int(ot.PaintFormat.PaintGlyph),
810                                    "Paint": {
811                                        "Format": int(ot.PaintFormat.PaintSolid),
812                                        "PaletteIndex": 0,
813                                        "Alpha": 1.0,
814                                    },
815                                    "Glyph": "B",
816                                },
817                                {
818                                    "Format": int(ot.PaintFormat.PaintGlyph),
819                                    "Paint": {
820                                        "Format": int(ot.PaintFormat.PaintSolid),
821                                        "PaletteIndex": 1,
822                                        "Alpha": 1.0,
823                                    },
824                                    "Glyph": "B",
825                                },
826                            ],
827                        },
828                    },
829                    {
830                        "A": {
831                            "Format": ot.PaintFormat.PaintColrLayers,
832                            "Layers": [
833                                {
834                                    "Format": int(ot.PaintFormat.PaintGlyph),
835                                    "Paint": {
836                                        "Format": int(ot.PaintFormat.PaintSolid),
837                                        "PaletteIndex": 0,
838                                        "Alpha": 1.0,
839                                    },
840                                    "Glyph": "B",
841                                },
842                                {
843                                    "Format": int(ot.PaintFormat.PaintGlyph),
844                                    "Paint": {
845                                        "Format": int(ot.PaintFormat.PaintSolid),
846                                        "PaletteIndex": 1,
847                                        "Alpha": 1.0,
848                                    },
849                                    "Glyph": "B",
850                                },
851                            ],
852                        },
853                    },
854                ],
855                False,
856                [
857                    "<COLR>",
858                    '  <Version value="1"/>',
859                    "  <!-- BaseGlyphRecordCount=0 -->",
860                    "  <!-- LayerRecordCount=0 -->",
861                    "  <BaseGlyphList>",
862                    "    <!-- BaseGlyphCount=1 -->",
863                    '    <BaseGlyphPaintRecord index="0">',
864                    '      <BaseGlyph value="A"/>',
865                    '      <Paint Format="1"><!-- PaintColrLayers -->',
866                    '        <NumLayers value="2"/>',
867                    '        <FirstLayerIndex value="0"/>',
868                    "      </Paint>",
869                    "    </BaseGlyphPaintRecord>",
870                    "  </BaseGlyphList>",
871                    "  <LayerList>",
872                    "    <!-- LayerCount=2 -->",
873                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
874                    '      <Paint Format="2"><!-- PaintSolid -->',
875                    '        <PaletteIndex value="0"/>',
876                    '        <Alpha value="1.0"/>',
877                    "      </Paint>",
878                    '      <Glyph value="B"/>',
879                    "    </Paint>",
880                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
881                    '      <Paint Format="2"><!-- PaintSolid -->',
882                    '        <PaletteIndex value="1"/>',
883                    '        <Alpha value="1.0"/>',
884                    "      </Paint>",
885                    '      <Glyph value="B"/>',
886                    "    </Paint>",
887                    "  </LayerList>",
888                    "</COLR>",
889                ],
890                [],
891                id="no-variation",
892            ),
893            pytest.param(
894                [
895                    {
896                        "A": {
897                            "Format": int(ot.PaintFormat.PaintColrLayers),
898                            "Layers": [
899                                {
900                                    "Format": int(ot.PaintFormat.PaintGlyph),
901                                    "Paint": {
902                                        "Format": int(ot.PaintFormat.PaintSolid),
903                                        "PaletteIndex": 0,
904                                        "Alpha": 1.0,
905                                    },
906                                    "Glyph": "B",
907                                },
908                                {
909                                    "Format": int(ot.PaintFormat.PaintGlyph),
910                                    "Paint": {
911                                        "Format": int(ot.PaintFormat.PaintSolid),
912                                        "PaletteIndex": 1,
913                                        "Alpha": 1.0,
914                                    },
915                                    "Glyph": "B",
916                                },
917                            ],
918                        },
919                        "C": {
920                            "Format": int(ot.PaintFormat.PaintColrLayers),
921                            "Layers": [
922                                {
923                                    "Format": int(ot.PaintFormat.PaintGlyph),
924                                    "Paint": {
925                                        "Format": int(ot.PaintFormat.PaintSolid),
926                                        "PaletteIndex": 2,
927                                        "Alpha": 1.0,
928                                    },
929                                    "Glyph": "B",
930                                },
931                                {
932                                    "Format": int(ot.PaintFormat.PaintGlyph),
933                                    "Paint": {
934                                        "Format": int(ot.PaintFormat.PaintSolid),
935                                        "PaletteIndex": 3,
936                                        "Alpha": 1.0,
937                                    },
938                                    "Glyph": "B",
939                                },
940                            ],
941                        },
942                    },
943                    {
944                        # NOTE: 'A' is missing from non-default master
945                        "C": {
946                            "Format": int(ot.PaintFormat.PaintColrLayers),
947                            "Layers": [
948                                {
949                                    "Format": int(ot.PaintFormat.PaintGlyph),
950                                    "Paint": {
951                                        "Format": int(ot.PaintFormat.PaintSolid),
952                                        "PaletteIndex": 2,
953                                        "Alpha": 0.5,
954                                    },
955                                    "Glyph": "B",
956                                },
957                                {
958                                    "Format": int(ot.PaintFormat.PaintGlyph),
959                                    "Paint": {
960                                        "Format": int(ot.PaintFormat.PaintSolid),
961                                        "PaletteIndex": 3,
962                                        "Alpha": 0.5,
963                                    },
964                                    "Glyph": "B",
965                                },
966                            ],
967                        },
968                    },
969                ],
970                False,
971                [
972                    "<COLR>",
973                    '  <Version value="1"/>',
974                    "  <!-- BaseGlyphRecordCount=0 -->",
975                    "  <!-- LayerRecordCount=0 -->",
976                    "  <BaseGlyphList>",
977                    "    <!-- BaseGlyphCount=2 -->",
978                    '    <BaseGlyphPaintRecord index="0">',
979                    '      <BaseGlyph value="A"/>',
980                    '      <Paint Format="1"><!-- PaintColrLayers -->',
981                    '        <NumLayers value="2"/>',
982                    '        <FirstLayerIndex value="0"/>',
983                    "      </Paint>",
984                    "    </BaseGlyphPaintRecord>",
985                    '    <BaseGlyphPaintRecord index="1">',
986                    '      <BaseGlyph value="C"/>',
987                    '      <Paint Format="1"><!-- PaintColrLayers -->',
988                    '        <NumLayers value="2"/>',
989                    '        <FirstLayerIndex value="2"/>',
990                    "      </Paint>",
991                    "    </BaseGlyphPaintRecord>",
992                    "  </BaseGlyphList>",
993                    "  <LayerList>",
994                    "    <!-- LayerCount=4 -->",
995                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
996                    '      <Paint Format="2"><!-- PaintSolid -->',
997                    '        <PaletteIndex value="0"/>',
998                    '        <Alpha value="1.0"/>',
999                    "      </Paint>",
1000                    '      <Glyph value="B"/>',
1001                    "    </Paint>",
1002                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
1003                    '      <Paint Format="2"><!-- PaintSolid -->',
1004                    '        <PaletteIndex value="1"/>',
1005                    '        <Alpha value="1.0"/>',
1006                    "      </Paint>",
1007                    '      <Glyph value="B"/>',
1008                    "    </Paint>",
1009                    '    <Paint index="2" Format="10"><!-- PaintGlyph -->',
1010                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1011                    '        <PaletteIndex value="2"/>',
1012                    '        <Alpha value="1.0"/>',
1013                    '        <VarIndexBase value="0"/>',
1014                    "      </Paint>",
1015                    '      <Glyph value="B"/>',
1016                    "    </Paint>",
1017                    '    <Paint index="3" Format="10"><!-- PaintGlyph -->',
1018                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1019                    '        <PaletteIndex value="3"/>',
1020                    '        <Alpha value="1.0"/>',
1021                    '        <VarIndexBase value="0"/>',
1022                    "      </Paint>",
1023                    '      <Glyph value="B"/>',
1024                    "    </Paint>",
1025                    "  </LayerList>",
1026                    "</COLR>",
1027                ],
1028                [0],
1029                id="sparse-masters",
1030            ),
1031            pytest.param(
1032                [
1033                    {
1034                        "A": {
1035                            "Format": int(ot.PaintFormat.PaintColrLayers),
1036                            "Layers": [
1037                                {
1038                                    "Format": int(ot.PaintFormat.PaintGlyph),
1039                                    "Paint": {
1040                                        "Format": int(ot.PaintFormat.PaintSolid),
1041                                        "PaletteIndex": 0,
1042                                        "Alpha": 1.0,
1043                                    },
1044                                    "Glyph": "B",
1045                                },
1046                                {
1047                                    "Format": int(ot.PaintFormat.PaintGlyph),
1048                                    "Paint": {
1049                                        "Format": int(ot.PaintFormat.PaintSolid),
1050                                        "PaletteIndex": 1,
1051                                        "Alpha": 1.0,
1052                                    },
1053                                    "Glyph": "B",
1054                                },
1055                                {
1056                                    "Format": int(ot.PaintFormat.PaintGlyph),
1057                                    "Paint": {
1058                                        "Format": int(ot.PaintFormat.PaintSolid),
1059                                        "PaletteIndex": 2,
1060                                        "Alpha": 1.0,
1061                                    },
1062                                    "Glyph": "B",
1063                                },
1064                            ],
1065                        },
1066                        "C": {
1067                            "Format": int(ot.PaintFormat.PaintColrLayers),
1068                            "Layers": [
1069                                # 'C' reuses layers 1-3 from 'A'
1070                                {
1071                                    "Format": int(ot.PaintFormat.PaintGlyph),
1072                                    "Paint": {
1073                                        "Format": int(ot.PaintFormat.PaintSolid),
1074                                        "PaletteIndex": 1,
1075                                        "Alpha": 1.0,
1076                                    },
1077                                    "Glyph": "B",
1078                                },
1079                                {
1080                                    "Format": int(ot.PaintFormat.PaintGlyph),
1081                                    "Paint": {
1082                                        "Format": int(ot.PaintFormat.PaintSolid),
1083                                        "PaletteIndex": 2,
1084                                        "Alpha": 1.0,
1085                                    },
1086                                    "Glyph": "B",
1087                                },
1088                            ],
1089                        },
1090                        "D": {  # identical to 'C'
1091                            "Format": int(ot.PaintFormat.PaintColrLayers),
1092                            "Layers": [
1093                                {
1094                                    "Format": int(ot.PaintFormat.PaintGlyph),
1095                                    "Paint": {
1096                                        "Format": int(ot.PaintFormat.PaintSolid),
1097                                        "PaletteIndex": 1,
1098                                        "Alpha": 1.0,
1099                                    },
1100                                    "Glyph": "B",
1101                                },
1102                                {
1103                                    "Format": int(ot.PaintFormat.PaintGlyph),
1104                                    "Paint": {
1105                                        "Format": int(ot.PaintFormat.PaintSolid),
1106                                        "PaletteIndex": 2,
1107                                        "Alpha": 1.0,
1108                                    },
1109                                    "Glyph": "B",
1110                                },
1111                            ],
1112                        },
1113                        "E": {  # superset of 'C' or 'D'
1114                            "Format": int(ot.PaintFormat.PaintColrLayers),
1115                            "Layers": [
1116                                {
1117                                    "Format": int(ot.PaintFormat.PaintGlyph),
1118                                    "Paint": {
1119                                        "Format": int(ot.PaintFormat.PaintSolid),
1120                                        "PaletteIndex": 1,
1121                                        "Alpha": 1.0,
1122                                    },
1123                                    "Glyph": "B",
1124                                },
1125                                {
1126                                    "Format": int(ot.PaintFormat.PaintGlyph),
1127                                    "Paint": {
1128                                        "Format": int(ot.PaintFormat.PaintSolid),
1129                                        "PaletteIndex": 2,
1130                                        "Alpha": 1.0,
1131                                    },
1132                                    "Glyph": "B",
1133                                },
1134                                {
1135                                    "Format": int(ot.PaintFormat.PaintGlyph),
1136                                    "Paint": {
1137                                        "Format": int(ot.PaintFormat.PaintSolid),
1138                                        "PaletteIndex": 3,
1139                                        "Alpha": 1.0,
1140                                    },
1141                                    "Glyph": "B",
1142                                },
1143                            ],
1144                        },
1145                    },
1146                    {
1147                        # NOTE: 'A' is missing from non-default master
1148                        "C": {
1149                            "Format": int(ot.PaintFormat.PaintColrLayers),
1150                            "Layers": [
1151                                {
1152                                    "Format": int(ot.PaintFormat.PaintGlyph),
1153                                    "Paint": {
1154                                        "Format": int(ot.PaintFormat.PaintSolid),
1155                                        "PaletteIndex": 1,
1156                                        "Alpha": 0.5,
1157                                    },
1158                                    "Glyph": "B",
1159                                },
1160                                {
1161                                    "Format": int(ot.PaintFormat.PaintGlyph),
1162                                    "Paint": {
1163                                        "Format": int(ot.PaintFormat.PaintSolid),
1164                                        "PaletteIndex": 2,
1165                                        "Alpha": 0.5,
1166                                    },
1167                                    "Glyph": "B",
1168                                },
1169                            ],
1170                        },
1171                        "D": {  # same as 'C'
1172                            "Format": int(ot.PaintFormat.PaintColrLayers),
1173                            "Layers": [
1174                                {
1175                                    "Format": int(ot.PaintFormat.PaintGlyph),
1176                                    "Paint": {
1177                                        "Format": int(ot.PaintFormat.PaintSolid),
1178                                        "PaletteIndex": 1,
1179                                        "Alpha": 0.5,
1180                                    },
1181                                    "Glyph": "B",
1182                                },
1183                                {
1184                                    "Format": int(ot.PaintFormat.PaintGlyph),
1185                                    "Paint": {
1186                                        "Format": int(ot.PaintFormat.PaintSolid),
1187                                        "PaletteIndex": 2,
1188                                        "Alpha": 0.5,
1189                                    },
1190                                    "Glyph": "B",
1191                                },
1192                            ],
1193                        },
1194                        "E": {  # first two layers vary the same way as 'C' or 'D'
1195                            "Format": int(ot.PaintFormat.PaintColrLayers),
1196                            "Layers": [
1197                                {
1198                                    "Format": int(ot.PaintFormat.PaintGlyph),
1199                                    "Paint": {
1200                                        "Format": int(ot.PaintFormat.PaintSolid),
1201                                        "PaletteIndex": 1,
1202                                        "Alpha": 0.5,
1203                                    },
1204                                    "Glyph": "B",
1205                                },
1206                                {
1207                                    "Format": int(ot.PaintFormat.PaintGlyph),
1208                                    "Paint": {
1209                                        "Format": int(ot.PaintFormat.PaintSolid),
1210                                        "PaletteIndex": 2,
1211                                        "Alpha": 0.5,
1212                                    },
1213                                    "Glyph": "B",
1214                                },
1215                                {
1216                                    "Format": int(ot.PaintFormat.PaintGlyph),
1217                                    "Paint": {
1218                                        "Format": int(ot.PaintFormat.PaintSolid),
1219                                        "PaletteIndex": 3,
1220                                        "Alpha": 1.0,
1221                                    },
1222                                    "Glyph": "B",
1223                                },
1224                            ],
1225                        },
1226                    },
1227                ],
1228                True,  # reuse
1229                [
1230                    "<COLR>",
1231                    '  <Version value="1"/>',
1232                    "  <!-- BaseGlyphRecordCount=0 -->",
1233                    "  <!-- LayerRecordCount=0 -->",
1234                    "  <BaseGlyphList>",
1235                    "    <!-- BaseGlyphCount=4 -->",
1236                    '    <BaseGlyphPaintRecord index="0">',
1237                    '      <BaseGlyph value="A"/>',
1238                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1239                    '        <NumLayers value="3"/>',
1240                    '        <FirstLayerIndex value="0"/>',
1241                    "      </Paint>",
1242                    "    </BaseGlyphPaintRecord>",
1243                    '    <BaseGlyphPaintRecord index="1">',
1244                    '      <BaseGlyph value="C"/>',
1245                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1246                    '        <NumLayers value="2"/>',
1247                    '        <FirstLayerIndex value="3"/>',
1248                    "      </Paint>",
1249                    "    </BaseGlyphPaintRecord>",
1250                    '    <BaseGlyphPaintRecord index="2">',
1251                    '      <BaseGlyph value="D"/>',
1252                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1253                    '        <NumLayers value="2"/>',
1254                    '        <FirstLayerIndex value="3"/>',
1255                    "      </Paint>",
1256                    "    </BaseGlyphPaintRecord>",
1257                    '    <BaseGlyphPaintRecord index="3">',
1258                    '      <BaseGlyph value="E"/>',
1259                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1260                    '        <NumLayers value="2"/>',
1261                    '        <FirstLayerIndex value="5"/>',
1262                    "      </Paint>",
1263                    "    </BaseGlyphPaintRecord>",
1264                    "  </BaseGlyphList>",
1265                    "  <LayerList>",
1266                    "    <!-- LayerCount=7 -->",
1267                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
1268                    '      <Paint Format="2"><!-- PaintSolid -->',
1269                    '        <PaletteIndex value="0"/>',
1270                    '        <Alpha value="1.0"/>',
1271                    "      </Paint>",
1272                    '      <Glyph value="B"/>',
1273                    "    </Paint>",
1274                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
1275                    '      <Paint Format="2"><!-- PaintSolid -->',
1276                    '        <PaletteIndex value="1"/>',
1277                    '        <Alpha value="1.0"/>',
1278                    "      </Paint>",
1279                    '      <Glyph value="B"/>',
1280                    "    </Paint>",
1281                    '    <Paint index="2" Format="10"><!-- PaintGlyph -->',
1282                    '      <Paint Format="2"><!-- PaintSolid -->',
1283                    '        <PaletteIndex value="2"/>',
1284                    '        <Alpha value="1.0"/>',
1285                    "      </Paint>",
1286                    '      <Glyph value="B"/>',
1287                    "    </Paint>",
1288                    '    <Paint index="3" Format="10"><!-- PaintGlyph -->',
1289                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1290                    '        <PaletteIndex value="1"/>',
1291                    '        <Alpha value="1.0"/>',
1292                    '        <VarIndexBase value="0"/>',
1293                    "      </Paint>",
1294                    '      <Glyph value="B"/>',
1295                    "    </Paint>",
1296                    '    <Paint index="4" Format="10"><!-- PaintGlyph -->',
1297                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1298                    '        <PaletteIndex value="2"/>',
1299                    '        <Alpha value="1.0"/>',
1300                    '        <VarIndexBase value="0"/>',
1301                    "      </Paint>",
1302                    '      <Glyph value="B"/>',
1303                    "    </Paint>",
1304                    '    <Paint index="5" Format="1"><!-- PaintColrLayers -->',
1305                    '      <NumLayers value="2"/>',
1306                    '      <FirstLayerIndex value="3"/>',
1307                    "    </Paint>",
1308                    '    <Paint index="6" Format="10"><!-- PaintGlyph -->',
1309                    '      <Paint Format="2"><!-- PaintSolid -->',
1310                    '        <PaletteIndex value="3"/>',
1311                    '        <Alpha value="1.0"/>',
1312                    "      </Paint>",
1313                    '      <Glyph value="B"/>',
1314                    "    </Paint>",
1315                    "  </LayerList>",
1316                    "</COLR>",
1317                ],
1318                [0],
1319                id="sparse-masters-with-reuse",
1320            ),
1321            pytest.param(
1322                [
1323                    {
1324                        "A": {
1325                            "Format": int(ot.PaintFormat.PaintColrLayers),
1326                            "Layers": [
1327                                {
1328                                    "Format": int(ot.PaintFormat.PaintGlyph),
1329                                    "Paint": {
1330                                        "Format": int(ot.PaintFormat.PaintSolid),
1331                                        "PaletteIndex": 0,
1332                                        "Alpha": 1.0,
1333                                    },
1334                                    "Glyph": "B",
1335                                },
1336                                {
1337                                    "Format": int(ot.PaintFormat.PaintGlyph),
1338                                    "Paint": {
1339                                        "Format": int(ot.PaintFormat.PaintSolid),
1340                                        "PaletteIndex": 1,
1341                                        "Alpha": 1.0,
1342                                    },
1343                                    "Glyph": "B",
1344                                },
1345                                {
1346                                    "Format": int(ot.PaintFormat.PaintGlyph),
1347                                    "Paint": {
1348                                        "Format": int(ot.PaintFormat.PaintSolid),
1349                                        "PaletteIndex": 2,
1350                                        "Alpha": 1.0,
1351                                    },
1352                                    "Glyph": "B",
1353                                },
1354                            ],
1355                        },
1356                        "C": {  # 'C' shares layer 1 and 2 with 'A'
1357                            "Format": int(ot.PaintFormat.PaintColrLayers),
1358                            "Layers": [
1359                                {
1360                                    "Format": int(ot.PaintFormat.PaintGlyph),
1361                                    "Paint": {
1362                                        "Format": int(ot.PaintFormat.PaintSolid),
1363                                        "PaletteIndex": 1,
1364                                        "Alpha": 1.0,
1365                                    },
1366                                    "Glyph": "B",
1367                                },
1368                                {
1369                                    "Format": int(ot.PaintFormat.PaintGlyph),
1370                                    "Paint": {
1371                                        "Format": int(ot.PaintFormat.PaintSolid),
1372                                        "PaletteIndex": 2,
1373                                        "Alpha": 1.0,
1374                                    },
1375                                    "Glyph": "B",
1376                                },
1377                            ],
1378                        },
1379                    },
1380                    {
1381                        "A": {
1382                            "Format": int(ot.PaintFormat.PaintColrLayers),
1383                            "Layers": [
1384                                {
1385                                    "Format": int(ot.PaintFormat.PaintGlyph),
1386                                    "Paint": {
1387                                        "Format": int(ot.PaintFormat.PaintSolid),
1388                                        "PaletteIndex": 0,
1389                                        "Alpha": 1.0,
1390                                    },
1391                                    "Glyph": "B",
1392                                },
1393                                {
1394                                    "Format": int(ot.PaintFormat.PaintGlyph),
1395                                    "Paint": {
1396                                        "Format": int(ot.PaintFormat.PaintSolid),
1397                                        "PaletteIndex": 1,
1398                                        "Alpha": 0.9,
1399                                    },
1400                                    "Glyph": "B",
1401                                },
1402                                {
1403                                    "Format": int(ot.PaintFormat.PaintGlyph),
1404                                    "Paint": {
1405                                        "Format": int(ot.PaintFormat.PaintSolid),
1406                                        "PaletteIndex": 2,
1407                                        "Alpha": 1.0,
1408                                    },
1409                                    "Glyph": "B",
1410                                },
1411                            ],
1412                        },
1413                        "C": {
1414                            "Format": int(ot.PaintFormat.PaintColrLayers),
1415                            "Layers": [
1416                                {
1417                                    "Format": int(ot.PaintFormat.PaintGlyph),
1418                                    "Paint": {
1419                                        "Format": int(ot.PaintFormat.PaintSolid),
1420                                        "PaletteIndex": 1,
1421                                        "Alpha": 0.5,
1422                                    },
1423                                    "Glyph": "B",
1424                                },
1425                                {
1426                                    "Format": int(ot.PaintFormat.PaintGlyph),
1427                                    "Paint": {
1428                                        "Format": int(ot.PaintFormat.PaintSolid),
1429                                        "PaletteIndex": 2,
1430                                        "Alpha": 1.0,
1431                                    },
1432                                    "Glyph": "B",
1433                                },
1434                            ],
1435                        },
1436                    },
1437                ],
1438                True,
1439                [
1440                    # a different Alpha variation is applied to a shared layer between
1441                    # 'A' and 'C' and thus they are no longer shared.
1442                    "<COLR>",
1443                    '  <Version value="1"/>',
1444                    "  <!-- BaseGlyphRecordCount=0 -->",
1445                    "  <!-- LayerRecordCount=0 -->",
1446                    "  <BaseGlyphList>",
1447                    "    <!-- BaseGlyphCount=2 -->",
1448                    '    <BaseGlyphPaintRecord index="0">',
1449                    '      <BaseGlyph value="A"/>',
1450                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1451                    '        <NumLayers value="3"/>',
1452                    '        <FirstLayerIndex value="0"/>',
1453                    "      </Paint>",
1454                    "    </BaseGlyphPaintRecord>",
1455                    '    <BaseGlyphPaintRecord index="1">',
1456                    '      <BaseGlyph value="C"/>',
1457                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1458                    '        <NumLayers value="2"/>',
1459                    '        <FirstLayerIndex value="3"/>',
1460                    "      </Paint>",
1461                    "    </BaseGlyphPaintRecord>",
1462                    "  </BaseGlyphList>",
1463                    "  <LayerList>",
1464                    "    <!-- LayerCount=5 -->",
1465                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
1466                    '      <Paint Format="2"><!-- PaintSolid -->',
1467                    '        <PaletteIndex value="0"/>',
1468                    '        <Alpha value="1.0"/>',
1469                    "      </Paint>",
1470                    '      <Glyph value="B"/>',
1471                    "    </Paint>",
1472                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
1473                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1474                    '        <PaletteIndex value="1"/>',
1475                    '        <Alpha value="1.0"/>',
1476                    '        <VarIndexBase value="0"/>',
1477                    "      </Paint>",
1478                    '      <Glyph value="B"/>',
1479                    "    </Paint>",
1480                    '    <Paint index="2" Format="10"><!-- PaintGlyph -->',
1481                    '      <Paint Format="2"><!-- PaintSolid -->',
1482                    '        <PaletteIndex value="2"/>',
1483                    '        <Alpha value="1.0"/>',
1484                    "      </Paint>",
1485                    '      <Glyph value="B"/>',
1486                    "    </Paint>",
1487                    '    <Paint index="3" Format="10"><!-- PaintGlyph -->',
1488                    '      <Paint Format="3"><!-- PaintVarSolid -->',
1489                    '        <PaletteIndex value="1"/>',
1490                    '        <Alpha value="1.0"/>',
1491                    '        <VarIndexBase value="1"/>',
1492                    "      </Paint>",
1493                    '      <Glyph value="B"/>',
1494                    "    </Paint>",
1495                    '    <Paint index="4" Format="10"><!-- PaintGlyph -->',
1496                    '      <Paint Format="2"><!-- PaintSolid -->',
1497                    '        <PaletteIndex value="2"/>',
1498                    '        <Alpha value="1.0"/>',
1499                    "      </Paint>",
1500                    '      <Glyph value="B"/>',
1501                    "    </Paint>",
1502                    "  </LayerList>",
1503                    "</COLR>",
1504                ],
1505                [0, 1],
1506                id="shared-master-layers-different-variations",
1507            ),
1508        ],
1509    )
1510    def test_merge_full_table(
1511        self,
1512        color_glyphs,
1513        ttFont,
1514        expected_xml,
1515        expected_varIdxes,
1516        master_layer_reuse,
1517        output_layer_reuse,
1518    ):
1519        master_ttfs = [deepcopy(ttFont) for _ in range(len(color_glyphs))]
1520        for ttf, glyphs in zip(master_ttfs, color_glyphs):
1521            # merge algorithm is expected to work the same even if the master COLRs
1522            # may differ as to the layer reuse, hence we try both ways
1523            ttf["COLR"] = buildCOLR(glyphs, allowLayerReuse=master_layer_reuse)
1524        vf = deepcopy(master_ttfs[0])
1525
1526        model = VariationModel([{}, {"ZZZZ": 1.0}])
1527        merger = COLRVariationMerger(
1528            model, ["ZZZZ"], vf, allowLayerReuse=output_layer_reuse
1529        )
1530
1531        merger.mergeTables(vf, master_ttfs)
1532
1533        out = vf["COLR"].table
1534
1535        assert compile_decompile(out, vf) == out
1536        assert dump_xml(out, vf) == expected_xml
1537        assert merger.varIdxes == expected_varIdxes
1538
1539    @pytest.mark.parametrize(
1540        "color_glyphs, before_xml, expected_xml",
1541        [
1542            pytest.param(
1543                {
1544                    "A": {
1545                        "Format": int(ot.PaintFormat.PaintColrLayers),
1546                        "Layers": [
1547                            {
1548                                "Format": int(ot.PaintFormat.PaintGlyph),
1549                                "Paint": {
1550                                    "Format": int(ot.PaintFormat.PaintSolid),
1551                                    "PaletteIndex": 0,
1552                                    "Alpha": 1.0,
1553                                },
1554                                "Glyph": "B",
1555                            },
1556                            {
1557                                "Format": int(ot.PaintFormat.PaintGlyph),
1558                                "Paint": {
1559                                    "Format": int(ot.PaintFormat.PaintSolid),
1560                                    "PaletteIndex": 1,
1561                                    "Alpha": 1.0,
1562                                },
1563                                "Glyph": "C",
1564                            },
1565                            {
1566                                "Format": int(ot.PaintFormat.PaintGlyph),
1567                                "Paint": {
1568                                    "Format": int(ot.PaintFormat.PaintSolid),
1569                                    "PaletteIndex": 2,
1570                                    "Alpha": 1.0,
1571                                },
1572                                "Glyph": "D",
1573                            },
1574                        ],
1575                    },
1576                    "E": {
1577                        "Format": int(ot.PaintFormat.PaintColrLayers),
1578                        "Layers": [
1579                            {
1580                                "Format": int(ot.PaintFormat.PaintGlyph),
1581                                "Paint": {
1582                                    "Format": int(ot.PaintFormat.PaintSolid),
1583                                    "PaletteIndex": 1,
1584                                    "Alpha": 1.0,
1585                                },
1586                                "Glyph": "C",
1587                            },
1588                            {
1589                                "Format": int(ot.PaintFormat.PaintGlyph),
1590                                "Paint": {
1591                                    "Format": int(ot.PaintFormat.PaintSolid),
1592                                    "PaletteIndex": 2,
1593                                    "Alpha": 1.0,
1594                                },
1595                                "Glyph": "D",
1596                            },
1597                            {
1598                                "Format": int(ot.PaintFormat.PaintGlyph),
1599                                "Paint": {
1600                                    "Format": int(ot.PaintFormat.PaintSolid),
1601                                    "PaletteIndex": 3,
1602                                    "Alpha": 1.0,
1603                                },
1604                                "Glyph": "F",
1605                            },
1606                        ],
1607                    },
1608                    "G": {
1609                        "Format": int(ot.PaintFormat.PaintColrGlyph),
1610                        "Glyph": "E",
1611                    },
1612                },
1613                [
1614                    "<COLR>",
1615                    '  <Version value="1"/>',
1616                    "  <!-- BaseGlyphRecordCount=0 -->",
1617                    "  <!-- LayerRecordCount=0 -->",
1618                    "  <BaseGlyphList>",
1619                    "    <!-- BaseGlyphCount=3 -->",
1620                    '    <BaseGlyphPaintRecord index="0">',
1621                    '      <BaseGlyph value="A"/>',
1622                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1623                    '        <NumLayers value="3"/>',
1624                    '        <FirstLayerIndex value="0"/>',
1625                    "      </Paint>",
1626                    "    </BaseGlyphPaintRecord>",
1627                    '    <BaseGlyphPaintRecord index="1">',
1628                    '      <BaseGlyph value="E"/>',
1629                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1630                    '        <NumLayers value="2"/>',
1631                    '        <FirstLayerIndex value="3"/>',
1632                    "      </Paint>",
1633                    "    </BaseGlyphPaintRecord>",
1634                    '    <BaseGlyphPaintRecord index="2">',
1635                    '      <BaseGlyph value="G"/>',
1636                    '      <Paint Format="11"><!-- PaintColrGlyph -->',
1637                    '        <Glyph value="E"/>',
1638                    "      </Paint>",
1639                    "    </BaseGlyphPaintRecord>",
1640                    "  </BaseGlyphList>",
1641                    "  <LayerList>",
1642                    "    <!-- LayerCount=5 -->",
1643                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
1644                    '      <Paint Format="2"><!-- PaintSolid -->',
1645                    '        <PaletteIndex value="0"/>',
1646                    '        <Alpha value="1.0"/>',
1647                    "      </Paint>",
1648                    '      <Glyph value="B"/>',
1649                    "    </Paint>",
1650                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
1651                    '      <Paint Format="2"><!-- PaintSolid -->',
1652                    '        <PaletteIndex value="1"/>',
1653                    '        <Alpha value="1.0"/>',
1654                    "      </Paint>",
1655                    '      <Glyph value="C"/>',
1656                    "    </Paint>",
1657                    '    <Paint index="2" Format="10"><!-- PaintGlyph -->',
1658                    '      <Paint Format="2"><!-- PaintSolid -->',
1659                    '        <PaletteIndex value="2"/>',
1660                    '        <Alpha value="1.0"/>',
1661                    "      </Paint>",
1662                    '      <Glyph value="D"/>',
1663                    "    </Paint>",
1664                    '    <Paint index="3" Format="1"><!-- PaintColrLayers -->',
1665                    '      <NumLayers value="2"/>',
1666                    '      <FirstLayerIndex value="1"/>',
1667                    "    </Paint>",
1668                    '    <Paint index="4" Format="10"><!-- PaintGlyph -->',
1669                    '      <Paint Format="2"><!-- PaintSolid -->',
1670                    '        <PaletteIndex value="3"/>',
1671                    '        <Alpha value="1.0"/>',
1672                    "      </Paint>",
1673                    '      <Glyph value="F"/>',
1674                    "    </Paint>",
1675                    "  </LayerList>",
1676                    "</COLR>",
1677                ],
1678                [
1679                    "<COLR>",
1680                    '  <Version value="1"/>',
1681                    "  <!-- BaseGlyphRecordCount=0 -->",
1682                    "  <!-- LayerRecordCount=0 -->",
1683                    "  <BaseGlyphList>",
1684                    "    <!-- BaseGlyphCount=3 -->",
1685                    '    <BaseGlyphPaintRecord index="0">',
1686                    '      <BaseGlyph value="A"/>',
1687                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1688                    '        <NumLayers value="3"/>',
1689                    '        <FirstLayerIndex value="0"/>',
1690                    "      </Paint>",
1691                    "    </BaseGlyphPaintRecord>",
1692                    '    <BaseGlyphPaintRecord index="1">',
1693                    '      <BaseGlyph value="E"/>',
1694                    '      <Paint Format="1"><!-- PaintColrLayers -->',
1695                    '        <NumLayers value="3"/>',
1696                    '        <FirstLayerIndex value="3"/>',
1697                    "      </Paint>",
1698                    "    </BaseGlyphPaintRecord>",
1699                    '    <BaseGlyphPaintRecord index="2">',
1700                    '      <BaseGlyph value="G"/>',
1701                    '      <Paint Format="11"><!-- PaintColrGlyph -->',
1702                    '        <Glyph value="E"/>',
1703                    "      </Paint>",
1704                    "    </BaseGlyphPaintRecord>",
1705                    "  </BaseGlyphList>",
1706                    "  <LayerList>",
1707                    "    <!-- LayerCount=6 -->",
1708                    '    <Paint index="0" Format="10"><!-- PaintGlyph -->',
1709                    '      <Paint Format="2"><!-- PaintSolid -->',
1710                    '        <PaletteIndex value="0"/>',
1711                    '        <Alpha value="1.0"/>',
1712                    "      </Paint>",
1713                    '      <Glyph value="B"/>',
1714                    "    </Paint>",
1715                    '    <Paint index="1" Format="10"><!-- PaintGlyph -->',
1716                    '      <Paint Format="2"><!-- PaintSolid -->',
1717                    '        <PaletteIndex value="1"/>',
1718                    '        <Alpha value="1.0"/>',
1719                    "      </Paint>",
1720                    '      <Glyph value="C"/>',
1721                    "    </Paint>",
1722                    '    <Paint index="2" Format="10"><!-- PaintGlyph -->',
1723                    '      <Paint Format="2"><!-- PaintSolid -->',
1724                    '        <PaletteIndex value="2"/>',
1725                    '        <Alpha value="1.0"/>',
1726                    "      </Paint>",
1727                    '      <Glyph value="D"/>',
1728                    "    </Paint>",
1729                    '    <Paint index="3" Format="10"><!-- PaintGlyph -->',
1730                    '      <Paint Format="2"><!-- PaintSolid -->',
1731                    '        <PaletteIndex value="1"/>',
1732                    '        <Alpha value="1.0"/>',
1733                    "      </Paint>",
1734                    '      <Glyph value="C"/>',
1735                    "    </Paint>",
1736                    '    <Paint index="4" Format="10"><!-- PaintGlyph -->',
1737                    '      <Paint Format="2"><!-- PaintSolid -->',
1738                    '        <PaletteIndex value="2"/>',
1739                    '        <Alpha value="1.0"/>',
1740                    "      </Paint>",
1741                    '      <Glyph value="D"/>',
1742                    "    </Paint>",
1743                    '    <Paint index="5" Format="10"><!-- PaintGlyph -->',
1744                    '      <Paint Format="2"><!-- PaintSolid -->',
1745                    '        <PaletteIndex value="3"/>',
1746                    '        <Alpha value="1.0"/>',
1747                    "      </Paint>",
1748                    '      <Glyph value="F"/>',
1749                    "    </Paint>",
1750                    "  </LayerList>",
1751                    "</COLR>",
1752                ],
1753                id="simple-reuse",
1754            ),
1755            pytest.param(
1756                {
1757                    "A": {
1758                        "Format": int(ot.PaintFormat.PaintGlyph),
1759                        "Paint": {
1760                            "Format": int(ot.PaintFormat.PaintSolid),
1761                            "PaletteIndex": 0,
1762                            "Alpha": 1.0,
1763                        },
1764                        "Glyph": "B",
1765                    },
1766                },
1767                [
1768                    "<COLR>",
1769                    '  <Version value="1"/>',
1770                    "  <!-- BaseGlyphRecordCount=0 -->",
1771                    "  <!-- LayerRecordCount=0 -->",
1772                    "  <BaseGlyphList>",
1773                    "    <!-- BaseGlyphCount=1 -->",
1774                    '    <BaseGlyphPaintRecord index="0">',
1775                    '      <BaseGlyph value="A"/>',
1776                    '      <Paint Format="10"><!-- PaintGlyph -->',
1777                    '        <Paint Format="2"><!-- PaintSolid -->',
1778                    '          <PaletteIndex value="0"/>',
1779                    '          <Alpha value="1.0"/>',
1780                    "        </Paint>",
1781                    '        <Glyph value="B"/>',
1782                    "      </Paint>",
1783                    "    </BaseGlyphPaintRecord>",
1784                    "  </BaseGlyphList>",
1785                    "</COLR>",
1786                ],
1787                [
1788                    "<COLR>",
1789                    '  <Version value="1"/>',
1790                    "  <!-- BaseGlyphRecordCount=0 -->",
1791                    "  <!-- LayerRecordCount=0 -->",
1792                    "  <BaseGlyphList>",
1793                    "    <!-- BaseGlyphCount=1 -->",
1794                    '    <BaseGlyphPaintRecord index="0">',
1795                    '      <BaseGlyph value="A"/>',
1796                    '      <Paint Format="10"><!-- PaintGlyph -->',
1797                    '        <Paint Format="2"><!-- PaintSolid -->',
1798                    '          <PaletteIndex value="0"/>',
1799                    '          <Alpha value="1.0"/>',
1800                    "        </Paint>",
1801                    '        <Glyph value="B"/>',
1802                    "      </Paint>",
1803                    "    </BaseGlyphPaintRecord>",
1804                    "  </BaseGlyphList>",
1805                    "</COLR>",
1806                ],
1807                id="no-layer-list",
1808            ),
1809        ],
1810    )
1811    def test_expandPaintColrLayers(
1812        self, color_glyphs, ttFont, before_xml, expected_xml
1813    ):
1814        colr = buildCOLR(color_glyphs, allowLayerReuse=True)
1815
1816        assert dump_xml(colr.table, ttFont) == before_xml
1817
1818        before_layer_count = 0
1819        reuses_colr_layers = False
1820        if colr.table.LayerList:
1821            before_layer_count = len(colr.table.LayerList.Paint)
1822            reuses_colr_layers = any(
1823                p.Format == ot.PaintFormat.PaintColrLayers
1824                for p in colr.table.LayerList.Paint
1825            )
1826
1827        COLRVariationMerger.expandPaintColrLayers(colr.table)
1828
1829        assert dump_xml(colr.table, ttFont) == expected_xml
1830
1831        after_layer_count = (
1832            0 if not colr.table.LayerList else len(colr.table.LayerList.Paint)
1833        )
1834
1835        if reuses_colr_layers:
1836            assert not any(
1837                p.Format == ot.PaintFormat.PaintColrLayers
1838                for p in colr.table.LayerList.Paint
1839            )
1840            assert after_layer_count > before_layer_count
1841        else:
1842            assert after_layer_count == before_layer_count
1843
1844        if colr.table.LayerList:
1845            assert len({id(p) for p in colr.table.LayerList.Paint}) == after_layer_count
1846
1847
1848class SparsePositioningMergerTest:
1849    def test_zero_kern_at_default(self):
1850        # https://github.com/fonttools/fonttools/issues/3111
1851
1852        pytest.importorskip("ufo2ft")
1853        pytest.importorskip("ufoLib2")
1854
1855        from fontTools.designspaceLib import DesignSpaceDocument
1856        from ufo2ft import compileVariableTTF
1857        from ufoLib2 import Font
1858
1859        ds = DesignSpaceDocument()
1860        ds.addAxisDescriptor(
1861            name="wght", tag="wght", minimum=100, maximum=900, default=400
1862        )
1863        ds.addSourceDescriptor(font=Font(), location=dict(wght=100))
1864        ds.addSourceDescriptor(font=Font(), location=dict(wght=400))
1865        ds.addSourceDescriptor(font=Font(), location=dict(wght=900))
1866
1867        ds.sources[0].font.newGlyph("a").unicode = ord("a")
1868        ds.sources[0].font.newGlyph("b").unicode = ord("b")
1869        ds.sources[0].font.features.text = "feature kern { pos a b b' 100; } kern;"
1870
1871        ds.sources[1].font.newGlyph("a").unicode = ord("a")
1872        ds.sources[1].font.newGlyph("b").unicode = ord("b")
1873        ds.sources[1].font.features.text = "feature kern { pos a b b' 0; } kern;"
1874
1875        ds.sources[2].font.newGlyph("a").unicode = ord("a")
1876        ds.sources[2].font.newGlyph("b").unicode = ord("b")
1877        ds.sources[2].font.features.text = "feature kern { pos a b b' -100; } kern;"
1878
1879        font = compileVariableTTF(ds, inplace=True)
1880        b = BytesIO()
1881        font.save(b)
1882
1883        assert font["GDEF"].table.VarStore.VarData[0].Item[0] == [100, -100]
1884
1885    def test_sparse_cursive(self):
1886        # https://github.com/fonttools/fonttools/issues/3168
1887
1888        pytest.importorskip("ufo2ft")
1889        pytest.importorskip("ufoLib2")
1890
1891        from fontTools.designspaceLib import DesignSpaceDocument
1892        from ufo2ft import compileVariableTTF
1893        from ufoLib2 import Font
1894
1895        ds = DesignSpaceDocument()
1896        ds.addAxisDescriptor(
1897            name="wght", tag="wght", minimum=100, maximum=900, default=400
1898        )
1899        ds.addSourceDescriptor(font=Font(), location=dict(wght=100))
1900        ds.addSourceDescriptor(font=Font(), location=dict(wght=400))
1901        ds.addSourceDescriptor(font=Font(), location=dict(wght=900))
1902
1903        ds.sources[0].font.newGlyph("a").unicode = ord("a")
1904        ds.sources[0].font.newGlyph("b").unicode = ord("b")
1905        ds.sources[0].font.newGlyph("c").unicode = ord("c")
1906        ds.sources[
1907            0
1908        ].font.features.text = """
1909        feature curs {
1910          position cursive a <anchor 400 20> <anchor 0 -20>;
1911          position cursive c <anchor NULL> <anchor 0 -20>;
1912        } curs;
1913        """
1914
1915        ds.sources[1].font.newGlyph("a").unicode = ord("a")
1916        ds.sources[1].font.newGlyph("b").unicode = ord("b")
1917        ds.sources[1].font.newGlyph("c").unicode = ord("c")
1918        ds.sources[
1919            1
1920        ].font.features.text = """
1921        feature curs {
1922          position cursive a <anchor 500 20> <anchor 0 -20>;
1923          position cursive b <anchor 50 22> <anchor 0 -10>;
1924          position cursive c <anchor NULL> <anchor 0 -20>;
1925        } curs;
1926        """
1927
1928        ds.sources[2].font.newGlyph("a").unicode = ord("a")
1929        ds.sources[2].font.newGlyph("b").unicode = ord("b")
1930        ds.sources[2].font.newGlyph("c").unicode = ord("c")
1931        ds.sources[
1932            2
1933        ].font.features.text = """
1934        feature curs {
1935          position cursive b <anchor 100 40> <anchor 0 -30>;
1936          position cursive c <anchor NULL> <anchor 0 -20>;
1937        } curs;
1938        """
1939
1940        font = compileVariableTTF(ds, inplace=True)
1941        b = BytesIO()
1942        font.save(b)
1943
1944        assert font["GDEF"].table.VarStore.VarData[0].Item[0] == [-100, 0]
1945