xref: /aosp_15_r20/external/fonttools/Tests/ttLib/tables/TupleVariation_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import CapturingLogHandler
2*e1fe3e4aSElliott Hughesfrom fontTools.misc.testTools import parseXML
3*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import deHexStr, hexStr
4*e1fe3e4aSElliott Hughesfrom fontTools.misc.xmlWriter import XMLWriter
5*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.TupleVariation import (
6*e1fe3e4aSElliott Hughes    log,
7*e1fe3e4aSElliott Hughes    TupleVariation,
8*e1fe3e4aSElliott Hughes    compileSharedTuples,
9*e1fe3e4aSElliott Hughes    decompileSharedTuples,
10*e1fe3e4aSElliott Hughes    compileTupleVariationStore,
11*e1fe3e4aSElliott Hughes    decompileTupleVariationStore,
12*e1fe3e4aSElliott Hughes    inferRegion_,
13*e1fe3e4aSElliott Hughes)
14*e1fe3e4aSElliott Hughesfrom io import BytesIO
15*e1fe3e4aSElliott Hughesimport random
16*e1fe3e4aSElliott Hughesimport unittest
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughesdef hexencode(s):
20*e1fe3e4aSElliott Hughes    h = hexStr(s).upper()
21*e1fe3e4aSElliott Hughes    return " ".join([h[i : i + 2] for i in range(0, len(h), 2)])
22*e1fe3e4aSElliott Hughes
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott HughesAXES = {
25*e1fe3e4aSElliott Hughes    "wdth": (0.25, 0.375, 0.5),
26*e1fe3e4aSElliott Hughes    "wght": (0.0, 1.0, 1.0),
27*e1fe3e4aSElliott Hughes    "opsz": (-0.75, -0.75, 0.0),
28*e1fe3e4aSElliott Hughes}
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughes# Shared tuples in the 'gvar' table of the Skia font, as printed
32*e1fe3e4aSElliott Hughes# in Apple's TrueType specification.
33*e1fe3e4aSElliott Hughes# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
34*e1fe3e4aSElliott HughesSKIA_GVAR_SHARED_TUPLES_DATA = deHexStr(
35*e1fe3e4aSElliott Hughes    "40 00 00 00 C0 00 00 00 00 00 40 00 00 00 C0 00 "
36*e1fe3e4aSElliott Hughes    "C0 00 C0 00 40 00 C0 00 40 00 40 00 C0 00 40 00"
37*e1fe3e4aSElliott Hughes)
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott HughesSKIA_GVAR_SHARED_TUPLES = [
40*e1fe3e4aSElliott Hughes    {"wght": 1.0, "wdth": 0.0},
41*e1fe3e4aSElliott Hughes    {"wght": -1.0, "wdth": 0.0},
42*e1fe3e4aSElliott Hughes    {"wght": 0.0, "wdth": 1.0},
43*e1fe3e4aSElliott Hughes    {"wght": 0.0, "wdth": -1.0},
44*e1fe3e4aSElliott Hughes    {"wght": -1.0, "wdth": -1.0},
45*e1fe3e4aSElliott Hughes    {"wght": 1.0, "wdth": -1.0},
46*e1fe3e4aSElliott Hughes    {"wght": 1.0, "wdth": 1.0},
47*e1fe3e4aSElliott Hughes    {"wght": -1.0, "wdth": 1.0},
48*e1fe3e4aSElliott Hughes]
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughes# Tuple Variation Store of uppercase I in the Skia font, as printed in Apple's
52*e1fe3e4aSElliott Hughes# TrueType spec. The actual Skia font uses a different table for uppercase I
53*e1fe3e4aSElliott Hughes# than what is printed in Apple's spec, but we still want to make sure that
54*e1fe3e4aSElliott Hughes# we can parse the data as it appears in the specification.
55*e1fe3e4aSElliott Hughes# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
56*e1fe3e4aSElliott HughesSKIA_GVAR_I_DATA = deHexStr(
57*e1fe3e4aSElliott Hughes    "00 08 00 24 00 33 20 00 00 15 20 01 00 1B 20 02 "
58*e1fe3e4aSElliott Hughes    "00 24 20 03 00 15 20 04 00 26 20 07 00 0D 20 06 "
59*e1fe3e4aSElliott Hughes    "00 1A 20 05 00 40 01 01 01 81 80 43 FF 7E FF 7E "
60*e1fe3e4aSElliott Hughes    "FF 7E FF 7E 00 81 45 01 01 01 03 01 04 01 04 01 "
61*e1fe3e4aSElliott Hughes    "04 01 02 80 40 00 82 81 81 04 3A 5A 3E 43 20 81 "
62*e1fe3e4aSElliott Hughes    "04 0E 40 15 45 7C 83 00 0D 9E F3 F2 F0 F0 F0 F0 "
63*e1fe3e4aSElliott Hughes    "F3 9E A0 A1 A1 A1 9F 80 00 91 81 91 00 0D 0A 0A "
64*e1fe3e4aSElliott Hughes    "09 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0B 80 00 15 81 "
65*e1fe3e4aSElliott Hughes    "81 00 C4 89 00 C4 83 00 0D 80 99 98 96 96 96 96 "
66*e1fe3e4aSElliott Hughes    "99 80 82 83 83 83 81 80 40 FF 18 81 81 04 E6 F9 "
67*e1fe3e4aSElliott Hughes    "10 21 02 81 04 E8 E5 EB 4D DA 83 00 0D CE D3 D4 "
68*e1fe3e4aSElliott Hughes    "D3 D3 D3 D5 D2 CE CC CD CD CD CD 80 00 A1 81 91 "
69*e1fe3e4aSElliott Hughes    "00 0D 07 03 04 02 02 02 03 03 07 07 08 08 08 07 "
70*e1fe3e4aSElliott Hughes    "80 00 09 81 81 00 28 40 00 A4 02 24 24 66 81 04 "
71*e1fe3e4aSElliott Hughes    "08 FA FA FA 28 83 00 82 02 FF FF FF 83 02 01 01 "
72*e1fe3e4aSElliott Hughes    "01 84 91 00 80 06 07 08 08 08 08 0A 07 80 03 FE "
73*e1fe3e4aSElliott Hughes    "FF FF FF 81 00 08 81 82 02 EE EE EE 8B 6D 00"
74*e1fe3e4aSElliott Hughes)
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes
77*e1fe3e4aSElliott Hughesclass TupleVariationTest(unittest.TestCase):
78*e1fe3e4aSElliott Hughes    def __init__(self, methodName):
79*e1fe3e4aSElliott Hughes        unittest.TestCase.__init__(self, methodName)
80*e1fe3e4aSElliott Hughes        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
81*e1fe3e4aSElliott Hughes        # and fires deprecation warnings if a program uses the old name.
82*e1fe3e4aSElliott Hughes        if not hasattr(self, "assertRaisesRegex"):
83*e1fe3e4aSElliott Hughes            self.assertRaisesRegex = self.assertRaisesRegexp
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes    def test_equal(self):
86*e1fe3e4aSElliott Hughes        var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
87*e1fe3e4aSElliott Hughes        var2 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
88*e1fe3e4aSElliott Hughes        self.assertEqual(var1, var2)
89*e1fe3e4aSElliott Hughes
90*e1fe3e4aSElliott Hughes    def test_equal_differentAxes(self):
91*e1fe3e4aSElliott Hughes        var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
92*e1fe3e4aSElliott Hughes        var2 = TupleVariation({"wght": (0.7, 0.8, 0.9)}, [(0, 0), (9, 8), (7, 6)])
93*e1fe3e4aSElliott Hughes        self.assertNotEqual(var1, var2)
94*e1fe3e4aSElliott Hughes
95*e1fe3e4aSElliott Hughes    def test_equal_differentCoordinates(self):
96*e1fe3e4aSElliott Hughes        var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
97*e1fe3e4aSElliott Hughes        var2 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8)])
98*e1fe3e4aSElliott Hughes        self.assertNotEqual(var1, var2)
99*e1fe3e4aSElliott Hughes
100*e1fe3e4aSElliott Hughes    def test_hasImpact_someDeltasNotZero(self):
101*e1fe3e4aSElliott Hughes        axes = {"wght": (0.0, 1.0, 1.0)}
102*e1fe3e4aSElliott Hughes        var = TupleVariation(axes, [(0, 0), (9, 8), (7, 6)])
103*e1fe3e4aSElliott Hughes        self.assertTrue(var.hasImpact())
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes    def test_hasImpact_allDeltasZero(self):
106*e1fe3e4aSElliott Hughes        axes = {"wght": (0.0, 1.0, 1.0)}
107*e1fe3e4aSElliott Hughes        var = TupleVariation(axes, [(0, 0), (0, 0), (0, 0)])
108*e1fe3e4aSElliott Hughes        self.assertTrue(var.hasImpact())
109*e1fe3e4aSElliott Hughes
110*e1fe3e4aSElliott Hughes    def test_hasImpact_allDeltasNone(self):
111*e1fe3e4aSElliott Hughes        axes = {"wght": (0.0, 1.0, 1.0)}
112*e1fe3e4aSElliott Hughes        var = TupleVariation(axes, [None, None, None])
113*e1fe3e4aSElliott Hughes        self.assertFalse(var.hasImpact())
114*e1fe3e4aSElliott Hughes
115*e1fe3e4aSElliott Hughes    def test_toXML_badDeltaFormat(self):
116*e1fe3e4aSElliott Hughes        writer = XMLWriter(BytesIO())
117*e1fe3e4aSElliott Hughes        g = TupleVariation(AXES, ["String"])
118*e1fe3e4aSElliott Hughes        with CapturingLogHandler(log, "ERROR") as captor:
119*e1fe3e4aSElliott Hughes            g.toXML(writer, ["wdth"])
120*e1fe3e4aSElliott Hughes        self.assertIn("bad delta format", [r.msg for r in captor.records])
121*e1fe3e4aSElliott Hughes        self.assertEqual(
122*e1fe3e4aSElliott Hughes            [
123*e1fe3e4aSElliott Hughes                "<tuple>",
124*e1fe3e4aSElliott Hughes                '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
125*e1fe3e4aSElliott Hughes                "<!-- bad delta #0 -->",
126*e1fe3e4aSElliott Hughes                "</tuple>",
127*e1fe3e4aSElliott Hughes            ],
128*e1fe3e4aSElliott Hughes            TupleVariationTest.xml_lines(writer),
129*e1fe3e4aSElliott Hughes        )
130*e1fe3e4aSElliott Hughes
131*e1fe3e4aSElliott Hughes    def test_toXML_constants(self):
132*e1fe3e4aSElliott Hughes        writer = XMLWriter(BytesIO())
133*e1fe3e4aSElliott Hughes        g = TupleVariation(AXES, [42, None, 23, 0, -17, None])
134*e1fe3e4aSElliott Hughes        g.toXML(writer, ["wdth", "wght", "opsz"])
135*e1fe3e4aSElliott Hughes        self.assertEqual(
136*e1fe3e4aSElliott Hughes            [
137*e1fe3e4aSElliott Hughes                "<tuple>",
138*e1fe3e4aSElliott Hughes                '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
139*e1fe3e4aSElliott Hughes                '<coord axis="wght" value="1.0"/>',
140*e1fe3e4aSElliott Hughes                '<coord axis="opsz" value="-0.75"/>',
141*e1fe3e4aSElliott Hughes                '<delta cvt="0" value="42"/>',
142*e1fe3e4aSElliott Hughes                '<delta cvt="2" value="23"/>',
143*e1fe3e4aSElliott Hughes                '<delta cvt="3" value="0"/>',
144*e1fe3e4aSElliott Hughes                '<delta cvt="4" value="-17"/>',
145*e1fe3e4aSElliott Hughes                "</tuple>",
146*e1fe3e4aSElliott Hughes            ],
147*e1fe3e4aSElliott Hughes            TupleVariationTest.xml_lines(writer),
148*e1fe3e4aSElliott Hughes        )
149*e1fe3e4aSElliott Hughes
150*e1fe3e4aSElliott Hughes    def test_toXML_points(self):
151*e1fe3e4aSElliott Hughes        writer = XMLWriter(BytesIO())
152*e1fe3e4aSElliott Hughes        g = TupleVariation(AXES, [(9, 8), None, (7, 6), (0, 0), (-1, -2), None])
153*e1fe3e4aSElliott Hughes        g.toXML(writer, ["wdth", "wght", "opsz"])
154*e1fe3e4aSElliott Hughes        self.assertEqual(
155*e1fe3e4aSElliott Hughes            [
156*e1fe3e4aSElliott Hughes                "<tuple>",
157*e1fe3e4aSElliott Hughes                '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
158*e1fe3e4aSElliott Hughes                '<coord axis="wght" value="1.0"/>',
159*e1fe3e4aSElliott Hughes                '<coord axis="opsz" value="-0.75"/>',
160*e1fe3e4aSElliott Hughes                '<delta pt="0" x="9" y="8"/>',
161*e1fe3e4aSElliott Hughes                '<delta pt="2" x="7" y="6"/>',
162*e1fe3e4aSElliott Hughes                '<delta pt="3" x="0" y="0"/>',
163*e1fe3e4aSElliott Hughes                '<delta pt="4" x="-1" y="-2"/>',
164*e1fe3e4aSElliott Hughes                "</tuple>",
165*e1fe3e4aSElliott Hughes            ],
166*e1fe3e4aSElliott Hughes            TupleVariationTest.xml_lines(writer),
167*e1fe3e4aSElliott Hughes        )
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes    def test_toXML_allDeltasNone(self):
170*e1fe3e4aSElliott Hughes        writer = XMLWriter(BytesIO())
171*e1fe3e4aSElliott Hughes        axes = {"wght": (0.0, 1.0, 1.0)}
172*e1fe3e4aSElliott Hughes        g = TupleVariation(axes, [None] * 5)
173*e1fe3e4aSElliott Hughes        g.toXML(writer, ["wght", "wdth"])
174*e1fe3e4aSElliott Hughes        self.assertEqual(
175*e1fe3e4aSElliott Hughes            [
176*e1fe3e4aSElliott Hughes                "<tuple>",
177*e1fe3e4aSElliott Hughes                '<coord axis="wght" value="1.0"/>',
178*e1fe3e4aSElliott Hughes                "<!-- no deltas -->",
179*e1fe3e4aSElliott Hughes                "</tuple>",
180*e1fe3e4aSElliott Hughes            ],
181*e1fe3e4aSElliott Hughes            TupleVariationTest.xml_lines(writer),
182*e1fe3e4aSElliott Hughes        )
183*e1fe3e4aSElliott Hughes
184*e1fe3e4aSElliott Hughes    def test_toXML_axes_floats(self):
185*e1fe3e4aSElliott Hughes        writer = XMLWriter(BytesIO())
186*e1fe3e4aSElliott Hughes        axes = {
187*e1fe3e4aSElliott Hughes            "wght": (0.0, 0.2999878, 0.7000122),
188*e1fe3e4aSElliott Hughes            "wdth": (0.0, 0.4000244, 0.4000244),
189*e1fe3e4aSElliott Hughes        }
190*e1fe3e4aSElliott Hughes        g = TupleVariation(axes, [None] * 5)
191*e1fe3e4aSElliott Hughes        g.toXML(writer, ["wght", "wdth"])
192*e1fe3e4aSElliott Hughes        self.assertEqual(
193*e1fe3e4aSElliott Hughes            [
194*e1fe3e4aSElliott Hughes                '<coord axis="wght" min="0.0" value="0.3" max="0.7"/>',
195*e1fe3e4aSElliott Hughes                '<coord axis="wdth" value="0.4"/>',
196*e1fe3e4aSElliott Hughes            ],
197*e1fe3e4aSElliott Hughes            TupleVariationTest.xml_lines(writer)[1:3],
198*e1fe3e4aSElliott Hughes        )
199*e1fe3e4aSElliott Hughes
200*e1fe3e4aSElliott Hughes    def test_fromXML_badDeltaFormat(self):
201*e1fe3e4aSElliott Hughes        g = TupleVariation({}, [])
202*e1fe3e4aSElliott Hughes        with CapturingLogHandler(log, "WARNING") as captor:
203*e1fe3e4aSElliott Hughes            for name, attrs, content in parseXML('<delta a="1" b="2"/>'):
204*e1fe3e4aSElliott Hughes                g.fromXML(name, attrs, content)
205*e1fe3e4aSElliott Hughes        self.assertIn("bad delta format: a, b", [r.msg for r in captor.records])
206*e1fe3e4aSElliott Hughes
207*e1fe3e4aSElliott Hughes    def test_fromXML_constants(self):
208*e1fe3e4aSElliott Hughes        g = TupleVariation({}, [None] * 4)
209*e1fe3e4aSElliott Hughes        for name, attrs, content in parseXML(
210*e1fe3e4aSElliott Hughes            '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
211*e1fe3e4aSElliott Hughes            '<coord axis="wght" value="1.0"/>'
212*e1fe3e4aSElliott Hughes            '<coord axis="opsz" value="-0.75"/>'
213*e1fe3e4aSElliott Hughes            '<delta cvt="1" value="42"/>'
214*e1fe3e4aSElliott Hughes            '<delta cvt="2" value="-23"/>'
215*e1fe3e4aSElliott Hughes        ):
216*e1fe3e4aSElliott Hughes            g.fromXML(name, attrs, content)
217*e1fe3e4aSElliott Hughes        self.assertEqual(AXES, g.axes)
218*e1fe3e4aSElliott Hughes        self.assertEqual([None, 42, -23, None], g.coordinates)
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughes    def test_fromXML_points(self):
221*e1fe3e4aSElliott Hughes        g = TupleVariation({}, [None] * 4)
222*e1fe3e4aSElliott Hughes        for name, attrs, content in parseXML(
223*e1fe3e4aSElliott Hughes            '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
224*e1fe3e4aSElliott Hughes            '<coord axis="wght" value="1.0"/>'
225*e1fe3e4aSElliott Hughes            '<coord axis="opsz" value="-0.75"/>'
226*e1fe3e4aSElliott Hughes            '<delta pt="1" x="33" y="44"/>'
227*e1fe3e4aSElliott Hughes            '<delta pt="2" x="-2" y="170"/>'
228*e1fe3e4aSElliott Hughes        ):
229*e1fe3e4aSElliott Hughes            g.fromXML(name, attrs, content)
230*e1fe3e4aSElliott Hughes        self.assertEqual(AXES, g.axes)
231*e1fe3e4aSElliott Hughes        self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
232*e1fe3e4aSElliott Hughes
233*e1fe3e4aSElliott Hughes    def test_fromXML_axes_floats(self):
234*e1fe3e4aSElliott Hughes        g = TupleVariation({}, [None] * 4)
235*e1fe3e4aSElliott Hughes        for name, attrs, content in parseXML(
236*e1fe3e4aSElliott Hughes            '<coord axis="wght" min="0.0" value="0.3" max="0.7"/>'
237*e1fe3e4aSElliott Hughes            '<coord axis="wdth" value="0.4"/>'
238*e1fe3e4aSElliott Hughes        ):
239*e1fe3e4aSElliott Hughes            g.fromXML(name, attrs, content)
240*e1fe3e4aSElliott Hughes
241*e1fe3e4aSElliott Hughes        self.assertEqual(g.axes["wght"][0], 0)
242*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(g.axes["wght"][1], 0.2999878)
243*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(g.axes["wght"][2], 0.7000122)
244*e1fe3e4aSElliott Hughes
245*e1fe3e4aSElliott Hughes        self.assertEqual(g.axes["wdth"][0], 0)
246*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(g.axes["wdth"][1], 0.4000244)
247*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(g.axes["wdth"][2], 0.4000244)
248*e1fe3e4aSElliott Hughes
249*e1fe3e4aSElliott Hughes    def test_compile_sharedPeaks_nonIntermediate_sharedPoints(self):
250*e1fe3e4aSElliott Hughes        var = TupleVariation(
251*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
252*e1fe3e4aSElliott Hughes        )
253*e1fe3e4aSElliott Hughes        axisTags = ["wght", "wdth"]
254*e1fe3e4aSElliott Hughes        sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
255*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b"")
256*e1fe3e4aSElliott Hughes        # len(deltas)=8; flags=None; tupleIndex=0x77
257*e1fe3e4aSElliott Hughes        # embeddedPeaks=[]; intermediateCoord=[]
258*e1fe3e4aSElliott Hughes        self.assertEqual("00 08 00 77", hexencode(tup))
259*e1fe3e4aSElliott Hughes        self.assertEqual(
260*e1fe3e4aSElliott Hughes            "02 07 08 09 " "02 04 05 06",  # deltaX: [7, 8, 9]  # deltaY: [4, 5, 6]
261*e1fe3e4aSElliott Hughes            hexencode(deltas),
262*e1fe3e4aSElliott Hughes        )
263*e1fe3e4aSElliott Hughes
264*e1fe3e4aSElliott Hughes    def test_compile_sharedPeaks_intermediate_sharedPoints(self):
265*e1fe3e4aSElliott Hughes        var = TupleVariation(
266*e1fe3e4aSElliott Hughes            {"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)}, [(7, 4), (8, 5), (9, 6)]
267*e1fe3e4aSElliott Hughes        )
268*e1fe3e4aSElliott Hughes        axisTags = ["wght", "wdth"]
269*e1fe3e4aSElliott Hughes        sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
270*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b"")
271*e1fe3e4aSElliott Hughes        # len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77
272*e1fe3e4aSElliott Hughes        # embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)]
273*e1fe3e4aSElliott Hughes        self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup))
274*e1fe3e4aSElliott Hughes        self.assertEqual(
275*e1fe3e4aSElliott Hughes            "02 07 08 09 " "02 04 05 06",  # deltaX: [7, 8, 9]  # deltaY: [4, 5, 6]
276*e1fe3e4aSElliott Hughes            hexencode(deltas),
277*e1fe3e4aSElliott Hughes        )
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes    def test_compile_sharedPeaks_nonIntermediate_privatePoints(self):
280*e1fe3e4aSElliott Hughes        var = TupleVariation(
281*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
282*e1fe3e4aSElliott Hughes        )
283*e1fe3e4aSElliott Hughes        axisTags = ["wght", "wdth"]
284*e1fe3e4aSElliott Hughes        sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
285*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags, sharedPeakIndices)
286*e1fe3e4aSElliott Hughes        # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
287*e1fe3e4aSElliott Hughes        # embeddedPeak=[]; intermediateCoord=[]
288*e1fe3e4aSElliott Hughes        self.assertEqual("00 09 20 77", hexencode(tup))
289*e1fe3e4aSElliott Hughes        self.assertEqual(
290*e1fe3e4aSElliott Hughes            "00 "  # all points in glyph
291*e1fe3e4aSElliott Hughes            "02 07 08 09 "  # deltaX: [7, 8, 9]
292*e1fe3e4aSElliott Hughes            "02 04 05 06",  # deltaY: [4, 5, 6]
293*e1fe3e4aSElliott Hughes            hexencode(deltas),
294*e1fe3e4aSElliott Hughes        )
295*e1fe3e4aSElliott Hughes
296*e1fe3e4aSElliott Hughes    def test_compile_sharedPeaks_intermediate_privatePoints(self):
297*e1fe3e4aSElliott Hughes        var = TupleVariation(
298*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0)}, [(7, 4), (8, 5), (9, 6)]
299*e1fe3e4aSElliott Hughes        )
300*e1fe3e4aSElliott Hughes        axisTags = ["wght", "wdth"]
301*e1fe3e4aSElliott Hughes        sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
302*e1fe3e4aSElliott Hughes        tuple, deltas = var.compile(axisTags, sharedPeakIndices)
303*e1fe3e4aSElliott Hughes        # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
304*e1fe3e4aSElliott Hughes        # embeddedPeak=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)]
305*e1fe3e4aSElliott Hughes        self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00", hexencode(tuple))
306*e1fe3e4aSElliott Hughes        self.assertEqual(
307*e1fe3e4aSElliott Hughes            "00 "  # all points in glyph
308*e1fe3e4aSElliott Hughes            "02 07 08 09 "  # deltaX: [7, 8, 9]
309*e1fe3e4aSElliott Hughes            "02 04 05 06",  # deltaY: [4, 5, 6]
310*e1fe3e4aSElliott Hughes            hexencode(deltas),
311*e1fe3e4aSElliott Hughes        )
312*e1fe3e4aSElliott Hughes
313*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_nonIntermediate_sharedPoints(self):
314*e1fe3e4aSElliott Hughes        var = TupleVariation(
315*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
316*e1fe3e4aSElliott Hughes        )
317*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
318*e1fe3e4aSElliott Hughes        # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
319*e1fe3e4aSElliott Hughes        # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
320*e1fe3e4aSElliott Hughes        self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup))
321*e1fe3e4aSElliott Hughes        self.assertEqual(
322*e1fe3e4aSElliott Hughes            "02 07 08 09 " "02 04 05 06",  # deltaX: [7, 8, 9]  # deltaY: [4, 5, 6]
323*e1fe3e4aSElliott Hughes            hexencode(deltas),
324*e1fe3e4aSElliott Hughes        )
325*e1fe3e4aSElliott Hughes
326*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_nonIntermediate_sharedConstants(self):
327*e1fe3e4aSElliott Hughes        var = TupleVariation(
328*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [3, 1, 4]
329*e1fe3e4aSElliott Hughes        )
330*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
331*e1fe3e4aSElliott Hughes        # len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE
332*e1fe3e4aSElliott Hughes        # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
333*e1fe3e4aSElliott Hughes        self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup))
334*e1fe3e4aSElliott Hughes        self.assertEqual("02 03 01 04", hexencode(deltas))  # delta: [3, 1, 4]
335*e1fe3e4aSElliott Hughes
336*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_intermediate_sharedPoints(self):
337*e1fe3e4aSElliott Hughes        var = TupleVariation(
338*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
339*e1fe3e4aSElliott Hughes        )
340*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
341*e1fe3e4aSElliott Hughes        # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
342*e1fe3e4aSElliott Hughes        # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)]
343*e1fe3e4aSElliott Hughes        self.assertEqual(
344*e1fe3e4aSElliott Hughes            "00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33", hexencode(tup)
345*e1fe3e4aSElliott Hughes        )
346*e1fe3e4aSElliott Hughes        self.assertEqual(
347*e1fe3e4aSElliott Hughes            "02 07 08 09 " "02 04 05 06",  # deltaX: [7, 8, 9]  # deltaY: [4, 5, 6]
348*e1fe3e4aSElliott Hughes            hexencode(deltas),
349*e1fe3e4aSElliott Hughes        )
350*e1fe3e4aSElliott Hughes
351*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_nonIntermediate_privatePoints(self):
352*e1fe3e4aSElliott Hughes        var = TupleVariation(
353*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
354*e1fe3e4aSElliott Hughes        )
355*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"])
356*e1fe3e4aSElliott Hughes        # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
357*e1fe3e4aSElliott Hughes        # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
358*e1fe3e4aSElliott Hughes        self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup))
359*e1fe3e4aSElliott Hughes        self.assertEqual(
360*e1fe3e4aSElliott Hughes            "00 "  # all points in glyph
361*e1fe3e4aSElliott Hughes            "02 07 08 09 "  # deltaX: [7, 8, 9]
362*e1fe3e4aSElliott Hughes            "02 04 05 06",  # deltaY: [4, 5, 6]
363*e1fe3e4aSElliott Hughes            hexencode(deltas),
364*e1fe3e4aSElliott Hughes        )
365*e1fe3e4aSElliott Hughes
366*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_nonIntermediate_privateConstants(self):
367*e1fe3e4aSElliott Hughes        var = TupleVariation(
368*e1fe3e4aSElliott Hughes            {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [7, 8, 9]
369*e1fe3e4aSElliott Hughes        )
370*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"])
371*e1fe3e4aSElliott Hughes        # len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
372*e1fe3e4aSElliott Hughes        # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
373*e1fe3e4aSElliott Hughes        self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup))
374*e1fe3e4aSElliott Hughes        self.assertEqual(
375*e1fe3e4aSElliott Hughes            "00 " "02 07 08 09",  # all points in glyph  # delta: [7, 8, 9]
376*e1fe3e4aSElliott Hughes            hexencode(deltas),
377*e1fe3e4aSElliott Hughes        )
378*e1fe3e4aSElliott Hughes
379*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_intermediate_privatePoints(self):
380*e1fe3e4aSElliott Hughes        var = TupleVariation(
381*e1fe3e4aSElliott Hughes            {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [(7, 4), (8, 5), (9, 6)]
382*e1fe3e4aSElliott Hughes        )
383*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"])
384*e1fe3e4aSElliott Hughes        # len(deltas)=9;
385*e1fe3e4aSElliott Hughes        # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
386*e1fe3e4aSElliott Hughes        # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
387*e1fe3e4aSElliott Hughes        self.assertEqual(
388*e1fe3e4aSElliott Hughes            "00 09 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tup)
389*e1fe3e4aSElliott Hughes        )
390*e1fe3e4aSElliott Hughes        self.assertEqual(
391*e1fe3e4aSElliott Hughes            "00 "  # all points in glyph
392*e1fe3e4aSElliott Hughes            "02 07 08 09 "  # deltaX: [7, 8, 9]
393*e1fe3e4aSElliott Hughes            "02 04 05 06",  # deltaY: [4, 5, 6]
394*e1fe3e4aSElliott Hughes            hexencode(deltas),
395*e1fe3e4aSElliott Hughes        )
396*e1fe3e4aSElliott Hughes
397*e1fe3e4aSElliott Hughes    def test_compile_embeddedPeak_intermediate_privateConstants(self):
398*e1fe3e4aSElliott Hughes        var = TupleVariation(
399*e1fe3e4aSElliott Hughes            {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [7, 8, 9]
400*e1fe3e4aSElliott Hughes        )
401*e1fe3e4aSElliott Hughes        tup, deltas = var.compile(axisTags=["wght", "wdth"])
402*e1fe3e4aSElliott Hughes        # len(deltas)=5;
403*e1fe3e4aSElliott Hughes        # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
404*e1fe3e4aSElliott Hughes        # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
405*e1fe3e4aSElliott Hughes        self.assertEqual(
406*e1fe3e4aSElliott Hughes            "00 05 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tup)
407*e1fe3e4aSElliott Hughes        )
408*e1fe3e4aSElliott Hughes        self.assertEqual(
409*e1fe3e4aSElliott Hughes            "00 " "02 07 08 09",  # all points in glyph  # delta: [7, 8, 9]
410*e1fe3e4aSElliott Hughes            hexencode(deltas),
411*e1fe3e4aSElliott Hughes        )
412*e1fe3e4aSElliott Hughes
413*e1fe3e4aSElliott Hughes    def test_compileCoord(self):
414*e1fe3e4aSElliott Hughes        var = TupleVariation(
415*e1fe3e4aSElliott Hughes            {"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4
416*e1fe3e4aSElliott Hughes        )
417*e1fe3e4aSElliott Hughes        self.assertEqual("C0 00 20 00", hexencode(var.compileCoord(["wght", "wdth"])))
418*e1fe3e4aSElliott Hughes        self.assertEqual("20 00 C0 00", hexencode(var.compileCoord(["wdth", "wght"])))
419*e1fe3e4aSElliott Hughes        self.assertEqual("C0 00", hexencode(var.compileCoord(["wght"])))
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes    def test_compileIntermediateCoord(self):
422*e1fe3e4aSElliott Hughes        var = TupleVariation(
423*e1fe3e4aSElliott Hughes            {"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4
424*e1fe3e4aSElliott Hughes        )
425*e1fe3e4aSElliott Hughes        self.assertEqual(
426*e1fe3e4aSElliott Hughes            "C0 00 19 9A 00 00 26 66",
427*e1fe3e4aSElliott Hughes            hexencode(var.compileIntermediateCoord(["wght", "wdth"])),
428*e1fe3e4aSElliott Hughes        )
429*e1fe3e4aSElliott Hughes        self.assertEqual(
430*e1fe3e4aSElliott Hughes            "19 9A C0 00 26 66 00 00",
431*e1fe3e4aSElliott Hughes            hexencode(var.compileIntermediateCoord(["wdth", "wght"])),
432*e1fe3e4aSElliott Hughes        )
433*e1fe3e4aSElliott Hughes        self.assertEqual(None, var.compileIntermediateCoord(["wght"]))
434*e1fe3e4aSElliott Hughes        self.assertEqual(
435*e1fe3e4aSElliott Hughes            "19 9A 26 66", hexencode(var.compileIntermediateCoord(["wdth"]))
436*e1fe3e4aSElliott Hughes        )
437*e1fe3e4aSElliott Hughes
438*e1fe3e4aSElliott Hughes    def test_decompileCoord(self):
439*e1fe3e4aSElliott Hughes        decompileCoord = TupleVariation.decompileCoord_
440*e1fe3e4aSElliott Hughes        data = deHexStr("DE AD C0 00 20 00 DE AD")
441*e1fe3e4aSElliott Hughes        self.assertEqual(
442*e1fe3e4aSElliott Hughes            ({"wght": -1.0, "wdth": 0.5}, 6), decompileCoord(["wght", "wdth"], data, 2)
443*e1fe3e4aSElliott Hughes        )
444*e1fe3e4aSElliott Hughes
445*e1fe3e4aSElliott Hughes    def test_decompileCoord_roundTrip(self):
446*e1fe3e4aSElliott Hughes        # Make sure we are not affected by https://github.com/fonttools/fonttools/issues/286
447*e1fe3e4aSElliott Hughes        data = deHexStr("7F B9 80 35")
448*e1fe3e4aSElliott Hughes        values, _ = TupleVariation.decompileCoord_(["wght", "wdth"], data, 0)
449*e1fe3e4aSElliott Hughes        axisValues = {axis: (val, val, val) for axis, val in values.items()}
450*e1fe3e4aSElliott Hughes        var = TupleVariation(axisValues, [None] * 4)
451*e1fe3e4aSElliott Hughes        self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
452*e1fe3e4aSElliott Hughes
453*e1fe3e4aSElliott Hughes    def test_compilePoints(self):
454*e1fe3e4aSElliott Hughes        compilePoints = lambda p: TupleVariation.compilePoints(set(p))
455*e1fe3e4aSElliott Hughes        self.assertEqual("00", hexencode(compilePoints(set())))  # all points in glyph
456*e1fe3e4aSElliott Hughes        self.assertEqual("01 00 07", hexencode(compilePoints([7])))
457*e1fe3e4aSElliott Hughes        self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535])))
458*e1fe3e4aSElliott Hughes        self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15])))
459*e1fe3e4aSElliott Hughes        self.assertEqual(
460*e1fe3e4aSElliott Hughes            "06 05 07 01 F7 02 01 F2",
461*e1fe3e4aSElliott Hughes            hexencode(compilePoints([7, 8, 255, 257, 258, 500])),
462*e1fe3e4aSElliott Hughes        )
463*e1fe3e4aSElliott Hughes        self.assertEqual("03 01 07 01 80 01 EC", hexencode(compilePoints([7, 8, 500])))
464*e1fe3e4aSElliott Hughes        self.assertEqual(
465*e1fe3e4aSElliott Hughes            "04 01 07 01 81 BE E7 0C 0F",
466*e1fe3e4aSElliott Hughes            hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE])),
467*e1fe3e4aSElliott Hughes        )
468*e1fe3e4aSElliott Hughes        self.maxDiff = None
469*e1fe3e4aSElliott Hughes        self.assertEqual(
470*e1fe3e4aSElliott Hughes            "81 2C"
471*e1fe3e4aSElliott Hughes            + " 7F 00"  # 300 points (0x12c) in total
472*e1fe3e4aSElliott Hughes            + (127 * " 01")
473*e1fe3e4aSElliott Hughes            + " 7F"  # first run, contains 128 points: [0 .. 127]
474*e1fe3e4aSElliott Hughes            + (128 * " 01")
475*e1fe3e4aSElliott Hughes            + " 2B"  # second run, contains 128 points: [128 .. 255]
476*e1fe3e4aSElliott Hughes            + (44 * " 01"),  # third run, contains 44 points: [256 .. 299]
477*e1fe3e4aSElliott Hughes            hexencode(compilePoints(range(300))),
478*e1fe3e4aSElliott Hughes        )
479*e1fe3e4aSElliott Hughes        self.assertEqual(
480*e1fe3e4aSElliott Hughes            "81 8F"
481*e1fe3e4aSElliott Hughes            + " 7F 00"  # 399 points (0x18f) in total
482*e1fe3e4aSElliott Hughes            + (127 * " 01")
483*e1fe3e4aSElliott Hughes            + " 7F"  # first run, contains 128 points: [0 .. 127]
484*e1fe3e4aSElliott Hughes            + (128 * " 01")
485*e1fe3e4aSElliott Hughes            + " 7F"  # second run, contains 128 points: [128 .. 255]
486*e1fe3e4aSElliott Hughes            + (128 * " 01")
487*e1fe3e4aSElliott Hughes            + " 0E"  # third run, contains 128 points: [256 .. 383]
488*e1fe3e4aSElliott Hughes            + (15 * " 01"),  # fourth run, contains 15 points: [384 .. 398]
489*e1fe3e4aSElliott Hughes            hexencode(compilePoints(range(399))),
490*e1fe3e4aSElliott Hughes        )
491*e1fe3e4aSElliott Hughes
492*e1fe3e4aSElliott Hughes    def test_decompilePoints(self):
493*e1fe3e4aSElliott Hughes        numPointsInGlyph = 65536
494*e1fe3e4aSElliott Hughes        allPoints = list(range(numPointsInGlyph))
495*e1fe3e4aSElliott Hughes
496*e1fe3e4aSElliott Hughes        def decompilePoints(data, offset):
497*e1fe3e4aSElliott Hughes            points, offset = TupleVariation.decompilePoints_(
498*e1fe3e4aSElliott Hughes                numPointsInGlyph, deHexStr(data), offset, "gvar"
499*e1fe3e4aSElliott Hughes            )
500*e1fe3e4aSElliott Hughes            # Conversion to list needed for Python 3.
501*e1fe3e4aSElliott Hughes            return (list(points), offset)
502*e1fe3e4aSElliott Hughes
503*e1fe3e4aSElliott Hughes        # all points in glyph
504*e1fe3e4aSElliott Hughes        self.assertEqual((allPoints, 1), decompilePoints("00", 0))
505*e1fe3e4aSElliott Hughes        # all points in glyph (in overly verbose encoding, not explicitly prohibited by spec)
506*e1fe3e4aSElliott Hughes        self.assertEqual((allPoints, 2), decompilePoints("80 00", 0))
507*e1fe3e4aSElliott Hughes        # 2 points; first run: [9, 9+6]
508*e1fe3e4aSElliott Hughes        self.assertEqual(([9, 15], 4), decompilePoints("02 01 09 06", 0))
509*e1fe3e4aSElliott Hughes        # 2 points; first run: [0xBEEF, 0xCAFE]. (0x0C0F = 0xCAFE - 0xBEEF)
510*e1fe3e4aSElliott Hughes        self.assertEqual(([0xBEEF, 0xCAFE], 6), decompilePoints("02 81 BE EF 0C 0F", 0))
511*e1fe3e4aSElliott Hughes        # 1 point; first run: [7]
512*e1fe3e4aSElliott Hughes        self.assertEqual(([7], 3), decompilePoints("01 00 07", 0))
513*e1fe3e4aSElliott Hughes        # 1 point; first run: [7] in overly verbose encoding
514*e1fe3e4aSElliott Hughes        self.assertEqual(([7], 4), decompilePoints("01 80 00 07", 0))
515*e1fe3e4aSElliott Hughes        # 1 point; first run: [65535]; requires words to be treated as unsigned numbers
516*e1fe3e4aSElliott Hughes        self.assertEqual(([65535], 4), decompilePoints("01 80 FF FF", 0))
517*e1fe3e4aSElliott Hughes        # 4 points; first run: [7, 8]; second run: [255, 257]. 257 is stored in delta-encoded bytes (0xFF + 2).
518*e1fe3e4aSElliott Hughes        self.assertEqual(
519*e1fe3e4aSElliott Hughes            ([7, 8, 263, 265], 7), decompilePoints("04 01 07 01 01 FF 02", 0)
520*e1fe3e4aSElliott Hughes        )
521*e1fe3e4aSElliott Hughes        # combination of all encodings, preceded and followed by 4 bytes of unused data
522*e1fe3e4aSElliott Hughes        data = "DE AD DE AD 04 01 07 01 81 BE E7 0C 0F DE AD DE AD"
523*e1fe3e4aSElliott Hughes        self.assertEqual(([7, 8, 0xBEEF, 0xCAFE], 13), decompilePoints(data, 4))
524*e1fe3e4aSElliott Hughes        self.assertSetEqual(
525*e1fe3e4aSElliott Hughes            set(range(300)),
526*e1fe3e4aSElliott Hughes            set(
527*e1fe3e4aSElliott Hughes                decompilePoints(
528*e1fe3e4aSElliott Hughes                    "81 2C"
529*e1fe3e4aSElliott Hughes                    + " 7F 00"  # 300 points (0x12c) in total
530*e1fe3e4aSElliott Hughes                    + (127 * " 01")
531*e1fe3e4aSElliott Hughes                    + " 7F"  # first run, contains 128 points: [0 .. 127]
532*e1fe3e4aSElliott Hughes                    + (128 * " 01")
533*e1fe3e4aSElliott Hughes                    + " AB"  # second run, contains 128 points: [128 .. 255]
534*e1fe3e4aSElliott Hughes                    + (44 * " 00 01"),  # third run, contains 44 points: [256 .. 299]
535*e1fe3e4aSElliott Hughes                    0,
536*e1fe3e4aSElliott Hughes                )[0]
537*e1fe3e4aSElliott Hughes            ),
538*e1fe3e4aSElliott Hughes        )
539*e1fe3e4aSElliott Hughes        self.assertSetEqual(
540*e1fe3e4aSElliott Hughes            set(range(399)),
541*e1fe3e4aSElliott Hughes            set(
542*e1fe3e4aSElliott Hughes                decompilePoints(
543*e1fe3e4aSElliott Hughes                    "81 8F"
544*e1fe3e4aSElliott Hughes                    + " 7F 00"  # 399 points (0x18f) in total
545*e1fe3e4aSElliott Hughes                    + (127 * " 01")
546*e1fe3e4aSElliott Hughes                    + " 7F"  # first run, contains 128 points: [0 .. 127]
547*e1fe3e4aSElliott Hughes                    + (128 * " 01")
548*e1fe3e4aSElliott Hughes                    + " FF"  # second run, contains 128 points: [128 .. 255]
549*e1fe3e4aSElliott Hughes                    + (128 * " 00 01")
550*e1fe3e4aSElliott Hughes                    + " 8E"  # third run, contains 128 points: [256 .. 383]
551*e1fe3e4aSElliott Hughes                    + (15 * " 00 01"),  # fourth run, contains 15 points: [384 .. 398]
552*e1fe3e4aSElliott Hughes                    0,
553*e1fe3e4aSElliott Hughes                )[0]
554*e1fe3e4aSElliott Hughes            ),
555*e1fe3e4aSElliott Hughes        )
556*e1fe3e4aSElliott Hughes
557*e1fe3e4aSElliott Hughes    def test_decompilePoints_shouldAcceptBadPointNumbers(self):
558*e1fe3e4aSElliott Hughes        decompilePoints = TupleVariation.decompilePoints_
559*e1fe3e4aSElliott Hughes        # 2 points; first run: [3, 9].
560*e1fe3e4aSElliott Hughes        numPointsInGlyph = 8
561*e1fe3e4aSElliott Hughes        with CapturingLogHandler(log, "WARNING") as captor:
562*e1fe3e4aSElliott Hughes            decompilePoints(numPointsInGlyph, deHexStr("02 01 03 06"), 0, "cvar")
563*e1fe3e4aSElliott Hughes        self.assertIn(
564*e1fe3e4aSElliott Hughes            "point 9 out of range in 'cvar' table", [r.msg for r in captor.records]
565*e1fe3e4aSElliott Hughes        )
566*e1fe3e4aSElliott Hughes
567*e1fe3e4aSElliott Hughes    def test_decompilePoints_roundTrip(self):
568*e1fe3e4aSElliott Hughes        numPointsInGlyph = (
569*e1fe3e4aSElliott Hughes            500  # greater than 255, so we also exercise code path for 16-bit encoding
570*e1fe3e4aSElliott Hughes        )
571*e1fe3e4aSElliott Hughes        compile = lambda points: TupleVariation.compilePoints(points)
572*e1fe3e4aSElliott Hughes        decompile = lambda data: set(
573*e1fe3e4aSElliott Hughes            TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0]
574*e1fe3e4aSElliott Hughes        )
575*e1fe3e4aSElliott Hughes        for i in range(50):
576*e1fe3e4aSElliott Hughes            points = set(random.sample(range(numPointsInGlyph), 30))
577*e1fe3e4aSElliott Hughes            self.assertSetEqual(
578*e1fe3e4aSElliott Hughes                points,
579*e1fe3e4aSElliott Hughes                decompile(compile(points)),
580*e1fe3e4aSElliott Hughes                "failed round-trip decompile/compilePoints; points=%s" % points,
581*e1fe3e4aSElliott Hughes            )
582*e1fe3e4aSElliott Hughes        allPoints = set(range(numPointsInGlyph))
583*e1fe3e4aSElliott Hughes        self.assertSetEqual(allPoints, decompile(compile(allPoints)))
584*e1fe3e4aSElliott Hughes        self.assertSetEqual(allPoints, decompile(compile(set())))
585*e1fe3e4aSElliott Hughes
586*e1fe3e4aSElliott Hughes    def test_compileDeltas_points(self):
587*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [None, (1, 0), (2, 0), None, (4, 0), None])
588*e1fe3e4aSElliott Hughes        # deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0]
589*e1fe3e4aSElliott Hughes        self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas()))
590*e1fe3e4aSElliott Hughes
591*e1fe3e4aSElliott Hughes    def test_compileDeltas_constants(self):
592*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [None, 1, 2, None, 4, None])
593*e1fe3e4aSElliott Hughes        # delta for cvts: [1, 2, 4]
594*e1fe3e4aSElliott Hughes        self.assertEqual("02 01 02 04", hexencode(var.compileDeltas()))
595*e1fe3e4aSElliott Hughes
596*e1fe3e4aSElliott Hughes    def test_compileDeltaValues(self):
597*e1fe3e4aSElliott Hughes        compileDeltaValues = lambda values: hexencode(
598*e1fe3e4aSElliott Hughes            TupleVariation.compileDeltaValues_(values)
599*e1fe3e4aSElliott Hughes        )
600*e1fe3e4aSElliott Hughes        # zeroes
601*e1fe3e4aSElliott Hughes        self.assertEqual("80", compileDeltaValues([0]))
602*e1fe3e4aSElliott Hughes        self.assertEqual("BF", compileDeltaValues([0] * 64))
603*e1fe3e4aSElliott Hughes        self.assertEqual("BF 80", compileDeltaValues([0] * 65))
604*e1fe3e4aSElliott Hughes        self.assertEqual("BF A3", compileDeltaValues([0] * 100))
605*e1fe3e4aSElliott Hughes        self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256))
606*e1fe3e4aSElliott Hughes        # bytes
607*e1fe3e4aSElliott Hughes        self.assertEqual("00 01", compileDeltaValues([1]))
608*e1fe3e4aSElliott Hughes        self.assertEqual(
609*e1fe3e4aSElliott Hughes            "06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2])
610*e1fe3e4aSElliott Hughes        )
611*e1fe3e4aSElliott Hughes        self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64))
612*e1fe3e4aSElliott Hughes        self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65))
613*e1fe3e4aSElliott Hughes        # words
614*e1fe3e4aSElliott Hughes        self.assertEqual("40 66 66", compileDeltaValues([0x6666]))
615*e1fe3e4aSElliott Hughes        self.assertEqual(
616*e1fe3e4aSElliott Hughes            "43 66 66 7F FF FF FF 80 00",
617*e1fe3e4aSElliott Hughes            compileDeltaValues([0x6666, 32767, -1, -32768]),
618*e1fe3e4aSElliott Hughes        )
619*e1fe3e4aSElliott Hughes        self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64))
620*e1fe3e4aSElliott Hughes        self.assertEqual(
621*e1fe3e4aSElliott Hughes            "7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65)
622*e1fe3e4aSElliott Hughes        )
623*e1fe3e4aSElliott Hughes        # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run
624*e1fe3e4aSElliott Hughes        self.assertEqual(
625*e1fe3e4aSElliott Hughes            "04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127])
626*e1fe3e4aSElliott Hughes        )
627*e1fe3e4aSElliott Hughes        self.assertEqual(
628*e1fe3e4aSElliott Hughes            "01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127])
629*e1fe3e4aSElliott Hughes        )
630*e1fe3e4aSElliott Hughes        self.assertEqual(
631*e1fe3e4aSElliott Hughes            "01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127])
632*e1fe3e4aSElliott Hughes        )
633*e1fe3e4aSElliott Hughes        self.assertEqual(
634*e1fe3e4aSElliott Hughes            "01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127])
635*e1fe3e4aSElliott Hughes        )
636*e1fe3e4aSElliott Hughes        # bytes, zeroes
637*e1fe3e4aSElliott Hughes        self.assertEqual("01 01 00", compileDeltaValues([1, 0]))
638*e1fe3e4aSElliott Hughes        self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0]))
639*e1fe3e4aSElliott Hughes        # words, bytes, words: a single byte is more compact when encoded as part of the words run
640*e1fe3e4aSElliott Hughes        self.assertEqual(
641*e1fe3e4aSElliott Hughes            "42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])
642*e1fe3e4aSElliott Hughes        )
643*e1fe3e4aSElliott Hughes        self.assertEqual(
644*e1fe3e4aSElliott Hughes            "40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])
645*e1fe3e4aSElliott Hughes        )
646*e1fe3e4aSElliott Hughes        # words, zeroes, words
647*e1fe3e4aSElliott Hughes        self.assertEqual(
648*e1fe3e4aSElliott Hughes            "40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])
649*e1fe3e4aSElliott Hughes        )
650*e1fe3e4aSElliott Hughes        self.assertEqual(
651*e1fe3e4aSElliott Hughes            "40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])
652*e1fe3e4aSElliott Hughes        )
653*e1fe3e4aSElliott Hughes        self.assertEqual(
654*e1fe3e4aSElliott Hughes            "40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777])
655*e1fe3e4aSElliott Hughes        )
656*e1fe3e4aSElliott Hughes        # words, zeroes, bytes
657*e1fe3e4aSElliott Hughes        self.assertEqual(
658*e1fe3e4aSElliott Hughes            "40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3])
659*e1fe3e4aSElliott Hughes        )
660*e1fe3e4aSElliott Hughes        self.assertEqual(
661*e1fe3e4aSElliott Hughes            "40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3])
662*e1fe3e4aSElliott Hughes        )
663*e1fe3e4aSElliott Hughes        self.assertEqual(
664*e1fe3e4aSElliott Hughes            "40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3])
665*e1fe3e4aSElliott Hughes        )
666*e1fe3e4aSElliott Hughes        # words, zeroes
667*e1fe3e4aSElliott Hughes        self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0]))
668*e1fe3e4aSElliott Hughes        self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
669*e1fe3e4aSElliott Hughes
670*e1fe3e4aSElliott Hughes    def test_decompileDeltas(self):
671*e1fe3e4aSElliott Hughes        decompileDeltas = TupleVariation.decompileDeltas_
672*e1fe3e4aSElliott Hughes        # 83 = zero values (0x80), count = 4 (1 + 0x83 & 0x3F)
673*e1fe3e4aSElliott Hughes        self.assertEqual(([0, 0, 0, 0], 1), decompileDeltas(4, deHexStr("83"), 0))
674*e1fe3e4aSElliott Hughes        # 41 01 02 FF FF = signed 16-bit values (0x40), count = 2 (1 + 0x41 & 0x3F)
675*e1fe3e4aSElliott Hughes        self.assertEqual(
676*e1fe3e4aSElliott Hughes            ([258, -1], 5), decompileDeltas(2, deHexStr("41 01 02 FF FF"), 0)
677*e1fe3e4aSElliott Hughes        )
678*e1fe3e4aSElliott Hughes        # 01 81 07 = signed 8-bit values, count = 2 (1 + 0x01 & 0x3F)
679*e1fe3e4aSElliott Hughes        self.assertEqual(([-127, 7], 3), decompileDeltas(2, deHexStr("01 81 07"), 0))
680*e1fe3e4aSElliott Hughes        # combination of all three encodings, preceded and followed by 4 bytes of unused data
681*e1fe3e4aSElliott Hughes        data = deHexStr("DE AD BE EF 83 40 01 02 01 81 80 DE AD BE EF")
682*e1fe3e4aSElliott Hughes        self.assertEqual(
683*e1fe3e4aSElliott Hughes            ([0, 0, 0, 0, 258, -127, -128], 11), decompileDeltas(7, data, 4)
684*e1fe3e4aSElliott Hughes        )
685*e1fe3e4aSElliott Hughes
686*e1fe3e4aSElliott Hughes    def test_decompileDeltas_roundTrip(self):
687*e1fe3e4aSElliott Hughes        numDeltas = 30
688*e1fe3e4aSElliott Hughes        compile = TupleVariation.compileDeltaValues_
689*e1fe3e4aSElliott Hughes        decompile = lambda data: TupleVariation.decompileDeltas_(numDeltas, data, 0)[0]
690*e1fe3e4aSElliott Hughes        for i in range(50):
691*e1fe3e4aSElliott Hughes            deltas = random.sample(range(-128, 127), 10)
692*e1fe3e4aSElliott Hughes            deltas.extend(random.sample(range(-32768, 32767), 10))
693*e1fe3e4aSElliott Hughes            deltas.extend([0] * 10)
694*e1fe3e4aSElliott Hughes            random.shuffle(deltas)
695*e1fe3e4aSElliott Hughes            self.assertListEqual(deltas, decompile(compile(deltas)))
696*e1fe3e4aSElliott Hughes
697*e1fe3e4aSElliott Hughes    def test_compileSharedTuples(self):
698*e1fe3e4aSElliott Hughes        # Below, the peak coordinate {"wght": 1.0, "wdth": 0.8} appears
699*e1fe3e4aSElliott Hughes        # three times (most frequent sorted first); {"wght": 1.0, "wdth": 0.5}
700*e1fe3e4aSElliott Hughes        # and {"wght": 1.0, "wdth": 0.7} both appears two times (tie) and
701*e1fe3e4aSElliott Hughes        # are sorted alphanumerically to ensure determinism.
702*e1fe3e4aSElliott Hughes        # The peak coordinate {"wght": 1.0, "wdth": 0.9} appears only once
703*e1fe3e4aSElliott Hughes        # and is thus ignored.
704*e1fe3e4aSElliott Hughes        # Because the start and end of variation ranges is not encoded
705*e1fe3e4aSElliott Hughes        # into the shared pool, they should get ignored.
706*e1fe3e4aSElliott Hughes        deltas = [None] * 4
707*e1fe3e4aSElliott Hughes        variations = [
708*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0)}, deltas),
709*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0)}, deltas),
710*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0)}, deltas),
711*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.5, 1.0)}, deltas),
712*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0)}, deltas),
713*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0)}, deltas),
714*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.4, 0.8, 1.0)}, deltas),
715*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.5, 1.0)}, deltas),
716*e1fe3e4aSElliott Hughes        ]
717*e1fe3e4aSElliott Hughes        result = compileSharedTuples(["wght", "wdth"], variations)
718*e1fe3e4aSElliott Hughes        self.assertEqual(
719*e1fe3e4aSElliott Hughes            [hexencode(c) for c in result],
720*e1fe3e4aSElliott Hughes            ["40 00 33 33", "40 00 20 00", "40 00 2C CD"],
721*e1fe3e4aSElliott Hughes        )
722*e1fe3e4aSElliott Hughes
723*e1fe3e4aSElliott Hughes    def test_decompileSharedTuples_Skia(self):
724*e1fe3e4aSElliott Hughes        sharedTuples = decompileSharedTuples(
725*e1fe3e4aSElliott Hughes            axisTags=["wght", "wdth"],
726*e1fe3e4aSElliott Hughes            sharedTupleCount=8,
727*e1fe3e4aSElliott Hughes            data=SKIA_GVAR_SHARED_TUPLES_DATA,
728*e1fe3e4aSElliott Hughes            offset=0,
729*e1fe3e4aSElliott Hughes        )
730*e1fe3e4aSElliott Hughes        self.assertEqual(sharedTuples, SKIA_GVAR_SHARED_TUPLES)
731*e1fe3e4aSElliott Hughes
732*e1fe3e4aSElliott Hughes    def test_decompileSharedTuples_empty(self):
733*e1fe3e4aSElliott Hughes        self.assertEqual(decompileSharedTuples(["wght"], 0, b"", 0), [])
734*e1fe3e4aSElliott Hughes
735*e1fe3e4aSElliott Hughes    def test_compileTupleVariationStore_allVariationsRedundant(self):
736*e1fe3e4aSElliott Hughes        axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)}
737*e1fe3e4aSElliott Hughes        variations = [
738*e1fe3e4aSElliott Hughes            TupleVariation(axes, [None] * 4),
739*e1fe3e4aSElliott Hughes            TupleVariation(axes, [None] * 4),
740*e1fe3e4aSElliott Hughes            TupleVariation(axes, [None] * 4),
741*e1fe3e4aSElliott Hughes        ]
742*e1fe3e4aSElliott Hughes        self.assertEqual(
743*e1fe3e4aSElliott Hughes            compileTupleVariationStore(
744*e1fe3e4aSElliott Hughes                variations,
745*e1fe3e4aSElliott Hughes                pointCount=8,
746*e1fe3e4aSElliott Hughes                axisTags=["wght", "opsz"],
747*e1fe3e4aSElliott Hughes                sharedTupleIndices={},
748*e1fe3e4aSElliott Hughes            ),
749*e1fe3e4aSElliott Hughes            (0, b"", b""),
750*e1fe3e4aSElliott Hughes        )
751*e1fe3e4aSElliott Hughes
752*e1fe3e4aSElliott Hughes    def test_compileTupleVariationStore_noVariations(self):
753*e1fe3e4aSElliott Hughes        self.assertEqual(
754*e1fe3e4aSElliott Hughes            compileTupleVariationStore(
755*e1fe3e4aSElliott Hughes                variations=[],
756*e1fe3e4aSElliott Hughes                pointCount=8,
757*e1fe3e4aSElliott Hughes                axisTags=["wght", "opsz"],
758*e1fe3e4aSElliott Hughes                sharedTupleIndices={},
759*e1fe3e4aSElliott Hughes            ),
760*e1fe3e4aSElliott Hughes            (0, b"", b""),
761*e1fe3e4aSElliott Hughes        )
762*e1fe3e4aSElliott Hughes
763*e1fe3e4aSElliott Hughes    def test_compileTupleVariationStore_roundTrip_cvar(self):
764*e1fe3e4aSElliott Hughes        deltas = [1, 2, 3, 4]
765*e1fe3e4aSElliott Hughes        variations = [
766*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
767*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
768*e1fe3e4aSElliott Hughes        ]
769*e1fe3e4aSElliott Hughes        tupleVariationCount, tuples, data = compileTupleVariationStore(
770*e1fe3e4aSElliott Hughes            variations, pointCount=4, axisTags=["wght", "wdth"], sharedTupleIndices={}
771*e1fe3e4aSElliott Hughes        )
772*e1fe3e4aSElliott Hughes        self.assertEqual(
773*e1fe3e4aSElliott Hughes            decompileTupleVariationStore(
774*e1fe3e4aSElliott Hughes                "cvar",
775*e1fe3e4aSElliott Hughes                ["wght", "wdth"],
776*e1fe3e4aSElliott Hughes                tupleVariationCount,
777*e1fe3e4aSElliott Hughes                pointCount=4,
778*e1fe3e4aSElliott Hughes                sharedTuples={},
779*e1fe3e4aSElliott Hughes                data=(tuples + data),
780*e1fe3e4aSElliott Hughes                pos=0,
781*e1fe3e4aSElliott Hughes                dataPos=len(tuples),
782*e1fe3e4aSElliott Hughes            ),
783*e1fe3e4aSElliott Hughes            variations,
784*e1fe3e4aSElliott Hughes        )
785*e1fe3e4aSElliott Hughes
786*e1fe3e4aSElliott Hughes    def test_compileTupleVariationStore_roundTrip_gvar(self):
787*e1fe3e4aSElliott Hughes        deltas = [(1, 1), (2, 2), (3, 3), (4, 4)]
788*e1fe3e4aSElliott Hughes        variations = [
789*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
790*e1fe3e4aSElliott Hughes            TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
791*e1fe3e4aSElliott Hughes        ]
792*e1fe3e4aSElliott Hughes        tupleVariationCount, tuples, data = compileTupleVariationStore(
793*e1fe3e4aSElliott Hughes            variations, pointCount=4, axisTags=["wght", "wdth"], sharedTupleIndices={}
794*e1fe3e4aSElliott Hughes        )
795*e1fe3e4aSElliott Hughes        self.assertEqual(
796*e1fe3e4aSElliott Hughes            decompileTupleVariationStore(
797*e1fe3e4aSElliott Hughes                "gvar",
798*e1fe3e4aSElliott Hughes                ["wght", "wdth"],
799*e1fe3e4aSElliott Hughes                tupleVariationCount,
800*e1fe3e4aSElliott Hughes                pointCount=4,
801*e1fe3e4aSElliott Hughes                sharedTuples={},
802*e1fe3e4aSElliott Hughes                data=(tuples + data),
803*e1fe3e4aSElliott Hughes                pos=0,
804*e1fe3e4aSElliott Hughes                dataPos=len(tuples),
805*e1fe3e4aSElliott Hughes            ),
806*e1fe3e4aSElliott Hughes            variations,
807*e1fe3e4aSElliott Hughes        )
808*e1fe3e4aSElliott Hughes
809*e1fe3e4aSElliott Hughes    def test_decompileTupleVariationStore_Skia_I(self):
810*e1fe3e4aSElliott Hughes        tvar = decompileTupleVariationStore(
811*e1fe3e4aSElliott Hughes            tableTag="gvar",
812*e1fe3e4aSElliott Hughes            axisTags=["wght", "wdth"],
813*e1fe3e4aSElliott Hughes            tupleVariationCount=8,
814*e1fe3e4aSElliott Hughes            pointCount=18,
815*e1fe3e4aSElliott Hughes            sharedTuples=SKIA_GVAR_SHARED_TUPLES,
816*e1fe3e4aSElliott Hughes            data=SKIA_GVAR_I_DATA,
817*e1fe3e4aSElliott Hughes            pos=4,
818*e1fe3e4aSElliott Hughes            dataPos=36,
819*e1fe3e4aSElliott Hughes        )
820*e1fe3e4aSElliott Hughes        self.assertEqual(len(tvar), 8)
821*e1fe3e4aSElliott Hughes        self.assertEqual(tvar[0].axes, {"wght": (0.0, 1.0, 1.0)})
822*e1fe3e4aSElliott Hughes        self.assertEqual(
823*e1fe3e4aSElliott Hughes            " ".join(["%d,%d" % c for c in tvar[0].coordinates]),
824*e1fe3e4aSElliott Hughes            "257,0 -127,0 -128,58 -130,90 -130,62 -130,67 -130,32 -127,0 "
825*e1fe3e4aSElliott Hughes            "257,0 259,14 260,64 260,21 260,69 258,124 0,0 130,0 0,0 0,0",
826*e1fe3e4aSElliott Hughes        )
827*e1fe3e4aSElliott Hughes
828*e1fe3e4aSElliott Hughes    def test_decompileTupleVariationStore_empty(self):
829*e1fe3e4aSElliott Hughes        self.assertEqual(
830*e1fe3e4aSElliott Hughes            decompileTupleVariationStore(
831*e1fe3e4aSElliott Hughes                tableTag="gvar",
832*e1fe3e4aSElliott Hughes                axisTags=[],
833*e1fe3e4aSElliott Hughes                tupleVariationCount=0,
834*e1fe3e4aSElliott Hughes                pointCount=5,
835*e1fe3e4aSElliott Hughes                sharedTuples=[],
836*e1fe3e4aSElliott Hughes                data=b"",
837*e1fe3e4aSElliott Hughes                pos=4,
838*e1fe3e4aSElliott Hughes                dataPos=4,
839*e1fe3e4aSElliott Hughes            ),
840*e1fe3e4aSElliott Hughes            [],
841*e1fe3e4aSElliott Hughes        )
842*e1fe3e4aSElliott Hughes
843*e1fe3e4aSElliott Hughes    def test_getTupleSize(self):
844*e1fe3e4aSElliott Hughes        getTupleSize = TupleVariation.getTupleSize_
845*e1fe3e4aSElliott Hughes        numAxes = 3
846*e1fe3e4aSElliott Hughes        self.assertEqual(4 + numAxes * 2, getTupleSize(0x8042, numAxes))
847*e1fe3e4aSElliott Hughes        self.assertEqual(4 + numAxes * 4, getTupleSize(0x4077, numAxes))
848*e1fe3e4aSElliott Hughes        self.assertEqual(4, getTupleSize(0x2077, numAxes))
849*e1fe3e4aSElliott Hughes        self.assertEqual(4, getTupleSize(11, numAxes))
850*e1fe3e4aSElliott Hughes
851*e1fe3e4aSElliott Hughes    def test_inferRegion(self):
852*e1fe3e4aSElliott Hughes        start, end = inferRegion_({"wght": -0.3, "wdth": 0.7})
853*e1fe3e4aSElliott Hughes        self.assertEqual(start, {"wght": -0.3, "wdth": 0.0})
854*e1fe3e4aSElliott Hughes        self.assertEqual(end, {"wght": 0.0, "wdth": 0.7})
855*e1fe3e4aSElliott Hughes
856*e1fe3e4aSElliott Hughes    @staticmethod
857*e1fe3e4aSElliott Hughes    def xml_lines(writer):
858*e1fe3e4aSElliott Hughes        content = writer.file.getvalue().decode("utf-8")
859*e1fe3e4aSElliott Hughes        return [line.strip() for line in content.splitlines()][1:]
860*e1fe3e4aSElliott Hughes
861*e1fe3e4aSElliott Hughes    def test_getCoordWidth(self):
862*e1fe3e4aSElliott Hughes        empty = TupleVariation({}, [])
863*e1fe3e4aSElliott Hughes        self.assertEqual(empty.getCoordWidth(), 0)
864*e1fe3e4aSElliott Hughes
865*e1fe3e4aSElliott Hughes        empty = TupleVariation({}, [None])
866*e1fe3e4aSElliott Hughes        self.assertEqual(empty.getCoordWidth(), 0)
867*e1fe3e4aSElliott Hughes
868*e1fe3e4aSElliott Hughes        gvarTuple = TupleVariation({}, [None, (0, 0)])
869*e1fe3e4aSElliott Hughes        self.assertEqual(gvarTuple.getCoordWidth(), 2)
870*e1fe3e4aSElliott Hughes
871*e1fe3e4aSElliott Hughes        cvarTuple = TupleVariation({}, [None, 0])
872*e1fe3e4aSElliott Hughes        self.assertEqual(cvarTuple.getCoordWidth(), 1)
873*e1fe3e4aSElliott Hughes
874*e1fe3e4aSElliott Hughes        cvarTuple.coordinates[1] *= 1.0
875*e1fe3e4aSElliott Hughes        self.assertEqual(cvarTuple.getCoordWidth(), 1)
876*e1fe3e4aSElliott Hughes
877*e1fe3e4aSElliott Hughes        with self.assertRaises(TypeError):
878*e1fe3e4aSElliott Hughes            TupleVariation({}, [None, "a"]).getCoordWidth()
879*e1fe3e4aSElliott Hughes
880*e1fe3e4aSElliott Hughes    def test_scaleDeltas_cvar(self):
881*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [100, None])
882*e1fe3e4aSElliott Hughes
883*e1fe3e4aSElliott Hughes        var.scaleDeltas(1.0)
884*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [100, None])
885*e1fe3e4aSElliott Hughes
886*e1fe3e4aSElliott Hughes        var.scaleDeltas(0.333)
887*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(var.coordinates[0], 33.3)
888*e1fe3e4aSElliott Hughes        self.assertIsNone(var.coordinates[1])
889*e1fe3e4aSElliott Hughes
890*e1fe3e4aSElliott Hughes        var.scaleDeltas(0.0)
891*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [0, None])
892*e1fe3e4aSElliott Hughes
893*e1fe3e4aSElliott Hughes    def test_scaleDeltas_gvar(self):
894*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [(100, 200), None])
895*e1fe3e4aSElliott Hughes
896*e1fe3e4aSElliott Hughes        var.scaleDeltas(1.0)
897*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(100, 200), None])
898*e1fe3e4aSElliott Hughes
899*e1fe3e4aSElliott Hughes        var.scaleDeltas(0.333)
900*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(var.coordinates[0][0], 33.3)
901*e1fe3e4aSElliott Hughes        self.assertAlmostEqual(var.coordinates[0][1], 66.6)
902*e1fe3e4aSElliott Hughes        self.assertIsNone(var.coordinates[1])
903*e1fe3e4aSElliott Hughes
904*e1fe3e4aSElliott Hughes        var.scaleDeltas(0.0)
905*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(0, 0), None])
906*e1fe3e4aSElliott Hughes
907*e1fe3e4aSElliott Hughes    def test_roundDeltas_cvar(self):
908*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [55.5, None, 99.9])
909*e1fe3e4aSElliott Hughes        var.roundDeltas()
910*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [56, None, 100])
911*e1fe3e4aSElliott Hughes
912*e1fe3e4aSElliott Hughes    def test_roundDeltas_gvar(self):
913*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [(55.5, 100.0), None, (99.9, 100.0)])
914*e1fe3e4aSElliott Hughes        var.roundDeltas()
915*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(56, 100), None, (100, 100)])
916*e1fe3e4aSElliott Hughes
917*e1fe3e4aSElliott Hughes    def test_calcInferredDeltas(self):
918*e1fe3e4aSElliott Hughes        var = TupleVariation({}, [(0, 0), None, None, None])
919*e1fe3e4aSElliott Hughes        coords = [(1, 1), (1, 1), (1, 1), (1, 1)]
920*e1fe3e4aSElliott Hughes
921*e1fe3e4aSElliott Hughes        var.calcInferredDeltas(coords, [])
922*e1fe3e4aSElliott Hughes
923*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(0, 0), (0, 0), (0, 0), (0, 0)])
924*e1fe3e4aSElliott Hughes
925*e1fe3e4aSElliott Hughes    def test_calcInferredDeltas_invalid(self):
926*e1fe3e4aSElliott Hughes        # cvar tuples can't have inferred deltas
927*e1fe3e4aSElliott Hughes        with self.assertRaises(TypeError):
928*e1fe3e4aSElliott Hughes            TupleVariation({}, [0]).calcInferredDeltas([], [])
929*e1fe3e4aSElliott Hughes
930*e1fe3e4aSElliott Hughes        # origCoords must have same length as self.coordinates
931*e1fe3e4aSElliott Hughes        with self.assertRaises(ValueError):
932*e1fe3e4aSElliott Hughes            TupleVariation({}, [(0, 0), None]).calcInferredDeltas([], [])
933*e1fe3e4aSElliott Hughes
934*e1fe3e4aSElliott Hughes        # at least 4 phantom points required
935*e1fe3e4aSElliott Hughes        with self.assertRaises(AssertionError):
936*e1fe3e4aSElliott Hughes            TupleVariation({}, [(0, 0), None]).calcInferredDeltas([(0, 0), (0, 0)], [])
937*e1fe3e4aSElliott Hughes
938*e1fe3e4aSElliott Hughes        with self.assertRaises(AssertionError):
939*e1fe3e4aSElliott Hughes            TupleVariation({}, [(0, 0)] + [None] * 5).calcInferredDeltas(
940*e1fe3e4aSElliott Hughes                [(0, 0)] * 6, [1, 0]  # endPts not in increasing order
941*e1fe3e4aSElliott Hughes            )
942*e1fe3e4aSElliott Hughes
943*e1fe3e4aSElliott Hughes    def test_optimize(self):
944*e1fe3e4aSElliott Hughes        var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 5)
945*e1fe3e4aSElliott Hughes
946*e1fe3e4aSElliott Hughes        var.optimize([(0, 0)] * 5, [0])
947*e1fe3e4aSElliott Hughes
948*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [None, None, None, None, None])
949*e1fe3e4aSElliott Hughes
950*e1fe3e4aSElliott Hughes    def test_optimize_isComposite(self):
951*e1fe3e4aSElliott Hughes        # when a composite glyph's deltas are all (0, 0), we still want
952*e1fe3e4aSElliott Hughes        # to write out an entry in gvar, else macOS doesn't apply any
953*e1fe3e4aSElliott Hughes        # variations to the composite glyph (even if its individual components
954*e1fe3e4aSElliott Hughes        # do vary).
955*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/1381
956*e1fe3e4aSElliott Hughes        var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 5)
957*e1fe3e4aSElliott Hughes        var.optimize([(0, 0)] * 5, [0], isComposite=True)
958*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(0, 0)] * 5)
959*e1fe3e4aSElliott Hughes
960*e1fe3e4aSElliott Hughes        # it takes more than 128 (0, 0) deltas before the optimized tuple with
961*e1fe3e4aSElliott Hughes        # (None) inferred deltas (except for the first) becomes smaller than
962*e1fe3e4aSElliott Hughes        # the un-optimized one that has all deltas explicitly set to (0, 0).
963*e1fe3e4aSElliott Hughes        var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 129)
964*e1fe3e4aSElliott Hughes        var.optimize([(0, 0)] * 129, list(range(129 - 4)), isComposite=True)
965*e1fe3e4aSElliott Hughes        self.assertEqual(var.coordinates, [(0, 0)] + [None] * 128)
966*e1fe3e4aSElliott Hughes
967*e1fe3e4aSElliott Hughes    def test_sum_deltas_gvar(self):
968*e1fe3e4aSElliott Hughes        var1 = TupleVariation(
969*e1fe3e4aSElliott Hughes            {},
970*e1fe3e4aSElliott Hughes            [
971*e1fe3e4aSElliott Hughes                (-20, 0),
972*e1fe3e4aSElliott Hughes                (-20, 0),
973*e1fe3e4aSElliott Hughes                (20, 0),
974*e1fe3e4aSElliott Hughes                (20, 0),
975*e1fe3e4aSElliott Hughes                (0, 0),
976*e1fe3e4aSElliott Hughes                (0, 0),
977*e1fe3e4aSElliott Hughes                (0, 0),
978*e1fe3e4aSElliott Hughes                (0, 0),
979*e1fe3e4aSElliott Hughes            ],
980*e1fe3e4aSElliott Hughes        )
981*e1fe3e4aSElliott Hughes        var2 = TupleVariation(
982*e1fe3e4aSElliott Hughes            {},
983*e1fe3e4aSElliott Hughes            [
984*e1fe3e4aSElliott Hughes                (-10, 0),
985*e1fe3e4aSElliott Hughes                (-10, 0),
986*e1fe3e4aSElliott Hughes                (10, 0),
987*e1fe3e4aSElliott Hughes                (10, 0),
988*e1fe3e4aSElliott Hughes                (0, 0),
989*e1fe3e4aSElliott Hughes                (20, 0),
990*e1fe3e4aSElliott Hughes                (0, 0),
991*e1fe3e4aSElliott Hughes                (0, 0),
992*e1fe3e4aSElliott Hughes            ],
993*e1fe3e4aSElliott Hughes        )
994*e1fe3e4aSElliott Hughes
995*e1fe3e4aSElliott Hughes        var1 += var2
996*e1fe3e4aSElliott Hughes
997*e1fe3e4aSElliott Hughes        self.assertEqual(
998*e1fe3e4aSElliott Hughes            var1.coordinates,
999*e1fe3e4aSElliott Hughes            [
1000*e1fe3e4aSElliott Hughes                (-30, 0),
1001*e1fe3e4aSElliott Hughes                (-30, 0),
1002*e1fe3e4aSElliott Hughes                (30, 0),
1003*e1fe3e4aSElliott Hughes                (30, 0),
1004*e1fe3e4aSElliott Hughes                (0, 0),
1005*e1fe3e4aSElliott Hughes                (20, 0),
1006*e1fe3e4aSElliott Hughes                (0, 0),
1007*e1fe3e4aSElliott Hughes                (0, 0),
1008*e1fe3e4aSElliott Hughes            ],
1009*e1fe3e4aSElliott Hughes        )
1010*e1fe3e4aSElliott Hughes
1011*e1fe3e4aSElliott Hughes    def test_sum_deltas_gvar_invalid_length(self):
1012*e1fe3e4aSElliott Hughes        var1 = TupleVariation({}, [(1, 2)])
1013*e1fe3e4aSElliott Hughes        var2 = TupleVariation({}, [(1, 2), (3, 4)])
1014*e1fe3e4aSElliott Hughes
1015*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ValueError, "deltas with different lengths"):
1016*e1fe3e4aSElliott Hughes            var1 += var2
1017*e1fe3e4aSElliott Hughes
1018*e1fe3e4aSElliott Hughes    def test_sum_deltas_gvar_with_inferred_points(self):
1019*e1fe3e4aSElliott Hughes        var1 = TupleVariation({}, [(1, 2), None])
1020*e1fe3e4aSElliott Hughes        var2 = TupleVariation({}, [(2, 3), None])
1021*e1fe3e4aSElliott Hughes
1022*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(ValueError, "deltas with inferred points"):
1023*e1fe3e4aSElliott Hughes            var1 += var2
1024*e1fe3e4aSElliott Hughes
1025*e1fe3e4aSElliott Hughes    def test_sum_deltas_cvar(self):
1026*e1fe3e4aSElliott Hughes        axes = {"wght": (0.0, 1.0, 1.0)}
1027*e1fe3e4aSElliott Hughes        var1 = TupleVariation(axes, [0, 1, None, None])
1028*e1fe3e4aSElliott Hughes        var2 = TupleVariation(axes, [None, 2, None, 3])
1029*e1fe3e4aSElliott Hughes        var3 = TupleVariation(axes, [None, None, None, 4])
1030*e1fe3e4aSElliott Hughes
1031*e1fe3e4aSElliott Hughes        var1 += var2
1032*e1fe3e4aSElliott Hughes        var1 += var3
1033*e1fe3e4aSElliott Hughes
1034*e1fe3e4aSElliott Hughes        self.assertEqual(var1.coordinates, [0, 3, None, 7])
1035*e1fe3e4aSElliott Hughes
1036*e1fe3e4aSElliott Hughes
1037*e1fe3e4aSElliott Hughesif __name__ == "__main__":
1038*e1fe3e4aSElliott Hughes    import sys
1039*e1fe3e4aSElliott Hughes
1040*e1fe3e4aSElliott Hughes    sys.exit(unittest.main())
1041