xref: /aosp_15_r20/external/fonttools/Tests/voltLib/parser_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.voltLib import ast
2*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.error import VoltLibError
3*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.parser import Parser
4*e1fe3e4aSElliott Hughesfrom io import StringIO
5*e1fe3e4aSElliott Hughesimport unittest
6*e1fe3e4aSElliott Hughes
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughesclass ParserTest(unittest.TestCase):
9*e1fe3e4aSElliott Hughes    def __init__(self, methodName):
10*e1fe3e4aSElliott Hughes        unittest.TestCase.__init__(self, methodName)
11*e1fe3e4aSElliott Hughes        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
12*e1fe3e4aSElliott Hughes        # and fires deprecation warnings if a program uses the old name.
13*e1fe3e4aSElliott Hughes        if not hasattr(self, "assertRaisesRegex"):
14*e1fe3e4aSElliott Hughes            self.assertRaisesRegex = self.assertRaisesRegexp
15*e1fe3e4aSElliott Hughes
16*e1fe3e4aSElliott Hughes    def assertSubEqual(self, sub, glyph_ref, replacement_ref):
17*e1fe3e4aSElliott Hughes        glyphs = [[g.glyph for g in v] for v in sub.mapping.keys()]
18*e1fe3e4aSElliott Hughes        replacement = [[g.glyph for g in v] for v in sub.mapping.values()]
19*e1fe3e4aSElliott Hughes
20*e1fe3e4aSElliott Hughes        self.assertEqual(glyphs, glyph_ref)
21*e1fe3e4aSElliott Hughes        self.assertEqual(replacement, replacement_ref)
22*e1fe3e4aSElliott Hughes
23*e1fe3e4aSElliott Hughes    def test_def_glyph_base(self):
24*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
25*e1fe3e4aSElliott Hughes            'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH'
26*e1fe3e4aSElliott Hughes        ).statements
27*e1fe3e4aSElliott Hughes        self.assertEqual(
28*e1fe3e4aSElliott Hughes            (
29*e1fe3e4aSElliott Hughes                def_glyph.name,
30*e1fe3e4aSElliott Hughes                def_glyph.id,
31*e1fe3e4aSElliott Hughes                def_glyph.unicode,
32*e1fe3e4aSElliott Hughes                def_glyph.type,
33*e1fe3e4aSElliott Hughes                def_glyph.components,
34*e1fe3e4aSElliott Hughes            ),
35*e1fe3e4aSElliott Hughes            (".notdef", 0, None, "BASE", None),
36*e1fe3e4aSElliott Hughes        )
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes    def test_def_glyph_base_with_unicode(self):
39*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
40*e1fe3e4aSElliott Hughes            'DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH'
41*e1fe3e4aSElliott Hughes        ).statements
42*e1fe3e4aSElliott Hughes        self.assertEqual(
43*e1fe3e4aSElliott Hughes            (
44*e1fe3e4aSElliott Hughes                def_glyph.name,
45*e1fe3e4aSElliott Hughes                def_glyph.id,
46*e1fe3e4aSElliott Hughes                def_glyph.unicode,
47*e1fe3e4aSElliott Hughes                def_glyph.type,
48*e1fe3e4aSElliott Hughes                def_glyph.components,
49*e1fe3e4aSElliott Hughes            ),
50*e1fe3e4aSElliott Hughes            ("space", 3, [0x0020], "BASE", None),
51*e1fe3e4aSElliott Hughes        )
52*e1fe3e4aSElliott Hughes
53*e1fe3e4aSElliott Hughes    def test_def_glyph_base_with_unicodevalues(self):
54*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse_(
55*e1fe3e4aSElliott Hughes            'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" ' "TYPE BASE END_GLYPH"
56*e1fe3e4aSElliott Hughes        ).statements
57*e1fe3e4aSElliott Hughes        self.assertEqual(
58*e1fe3e4aSElliott Hughes            (
59*e1fe3e4aSElliott Hughes                def_glyph.name,
60*e1fe3e4aSElliott Hughes                def_glyph.id,
61*e1fe3e4aSElliott Hughes                def_glyph.unicode,
62*e1fe3e4aSElliott Hughes                def_glyph.type,
63*e1fe3e4aSElliott Hughes                def_glyph.components,
64*e1fe3e4aSElliott Hughes            ),
65*e1fe3e4aSElliott Hughes            ("CR", 2, [0x0009], "BASE", None),
66*e1fe3e4aSElliott Hughes        )
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    def test_def_glyph_base_with_mult_unicodevalues(self):
69*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
70*e1fe3e4aSElliott Hughes            'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009,U+000D" ' "TYPE BASE END_GLYPH"
71*e1fe3e4aSElliott Hughes        ).statements
72*e1fe3e4aSElliott Hughes        self.assertEqual(
73*e1fe3e4aSElliott Hughes            (
74*e1fe3e4aSElliott Hughes                def_glyph.name,
75*e1fe3e4aSElliott Hughes                def_glyph.id,
76*e1fe3e4aSElliott Hughes                def_glyph.unicode,
77*e1fe3e4aSElliott Hughes                def_glyph.type,
78*e1fe3e4aSElliott Hughes                def_glyph.components,
79*e1fe3e4aSElliott Hughes            ),
80*e1fe3e4aSElliott Hughes            ("CR", 2, [0x0009, 0x000D], "BASE", None),
81*e1fe3e4aSElliott Hughes        )
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    def test_def_glyph_base_with_empty_unicodevalues(self):
84*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse_(
85*e1fe3e4aSElliott Hughes            'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" ' "TYPE BASE END_GLYPH"
86*e1fe3e4aSElliott Hughes        ).statements
87*e1fe3e4aSElliott Hughes        self.assertEqual(
88*e1fe3e4aSElliott Hughes            (
89*e1fe3e4aSElliott Hughes                def_glyph.name,
90*e1fe3e4aSElliott Hughes                def_glyph.id,
91*e1fe3e4aSElliott Hughes                def_glyph.unicode,
92*e1fe3e4aSElliott Hughes                def_glyph.type,
93*e1fe3e4aSElliott Hughes                def_glyph.components,
94*e1fe3e4aSElliott Hughes            ),
95*e1fe3e4aSElliott Hughes            ("i.locl", 269, None, "BASE", None),
96*e1fe3e4aSElliott Hughes        )
97*e1fe3e4aSElliott Hughes
98*e1fe3e4aSElliott Hughes    def test_def_glyph_base_2_components(self):
99*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
100*e1fe3e4aSElliott Hughes            'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH'
101*e1fe3e4aSElliott Hughes        ).statements
102*e1fe3e4aSElliott Hughes        self.assertEqual(
103*e1fe3e4aSElliott Hughes            (
104*e1fe3e4aSElliott Hughes                def_glyph.name,
105*e1fe3e4aSElliott Hughes                def_glyph.id,
106*e1fe3e4aSElliott Hughes                def_glyph.unicode,
107*e1fe3e4aSElliott Hughes                def_glyph.type,
108*e1fe3e4aSElliott Hughes                def_glyph.components,
109*e1fe3e4aSElliott Hughes            ),
110*e1fe3e4aSElliott Hughes            ("glyphBase", 320, None, "BASE", 2),
111*e1fe3e4aSElliott Hughes        )
112*e1fe3e4aSElliott Hughes
113*e1fe3e4aSElliott Hughes    def test_def_glyph_ligature_2_components(self):
114*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
115*e1fe3e4aSElliott Hughes            'DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH'
116*e1fe3e4aSElliott Hughes        ).statements
117*e1fe3e4aSElliott Hughes        self.assertEqual(
118*e1fe3e4aSElliott Hughes            (
119*e1fe3e4aSElliott Hughes                def_glyph.name,
120*e1fe3e4aSElliott Hughes                def_glyph.id,
121*e1fe3e4aSElliott Hughes                def_glyph.unicode,
122*e1fe3e4aSElliott Hughes                def_glyph.type,
123*e1fe3e4aSElliott Hughes                def_glyph.components,
124*e1fe3e4aSElliott Hughes            ),
125*e1fe3e4aSElliott Hughes            ("f_f", 320, None, "LIGATURE", 2),
126*e1fe3e4aSElliott Hughes        )
127*e1fe3e4aSElliott Hughes
128*e1fe3e4aSElliott Hughes    def test_def_glyph_mark(self):
129*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
130*e1fe3e4aSElliott Hughes            'DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH'
131*e1fe3e4aSElliott Hughes        ).statements
132*e1fe3e4aSElliott Hughes        self.assertEqual(
133*e1fe3e4aSElliott Hughes            (
134*e1fe3e4aSElliott Hughes                def_glyph.name,
135*e1fe3e4aSElliott Hughes                def_glyph.id,
136*e1fe3e4aSElliott Hughes                def_glyph.unicode,
137*e1fe3e4aSElliott Hughes                def_glyph.type,
138*e1fe3e4aSElliott Hughes                def_glyph.components,
139*e1fe3e4aSElliott Hughes            ),
140*e1fe3e4aSElliott Hughes            ("brevecomb", 320, None, "MARK", None),
141*e1fe3e4aSElliott Hughes        )
142*e1fe3e4aSElliott Hughes
143*e1fe3e4aSElliott Hughes    def test_def_glyph_component(self):
144*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse(
145*e1fe3e4aSElliott Hughes            'DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH'
146*e1fe3e4aSElliott Hughes        ).statements
147*e1fe3e4aSElliott Hughes        self.assertEqual(
148*e1fe3e4aSElliott Hughes            (
149*e1fe3e4aSElliott Hughes                def_glyph.name,
150*e1fe3e4aSElliott Hughes                def_glyph.id,
151*e1fe3e4aSElliott Hughes                def_glyph.unicode,
152*e1fe3e4aSElliott Hughes                def_glyph.type,
153*e1fe3e4aSElliott Hughes                def_glyph.components,
154*e1fe3e4aSElliott Hughes            ),
155*e1fe3e4aSElliott Hughes            ("f.f_f", 320, None, "COMPONENT", None),
156*e1fe3e4aSElliott Hughes        )
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughes    def test_def_glyph_no_type(self):
159*e1fe3e4aSElliott Hughes        [def_glyph] = self.parse('DEF_GLYPH "glyph20" ID 20 END_GLYPH').statements
160*e1fe3e4aSElliott Hughes        self.assertEqual(
161*e1fe3e4aSElliott Hughes            (
162*e1fe3e4aSElliott Hughes                def_glyph.name,
163*e1fe3e4aSElliott Hughes                def_glyph.id,
164*e1fe3e4aSElliott Hughes                def_glyph.unicode,
165*e1fe3e4aSElliott Hughes                def_glyph.type,
166*e1fe3e4aSElliott Hughes                def_glyph.components,
167*e1fe3e4aSElliott Hughes            ),
168*e1fe3e4aSElliott Hughes            ("glyph20", 20, None, None, None),
169*e1fe3e4aSElliott Hughes        )
170*e1fe3e4aSElliott Hughes
171*e1fe3e4aSElliott Hughes    def test_def_glyph_case_sensitive(self):
172*e1fe3e4aSElliott Hughes        def_glyphs = self.parse(
173*e1fe3e4aSElliott Hughes            'DEF_GLYPH "A" ID 3 UNICODE 65 TYPE BASE END_GLYPH\n'
174*e1fe3e4aSElliott Hughes            'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH'
175*e1fe3e4aSElliott Hughes        ).statements
176*e1fe3e4aSElliott Hughes        self.assertEqual(
177*e1fe3e4aSElliott Hughes            (
178*e1fe3e4aSElliott Hughes                def_glyphs[0].name,
179*e1fe3e4aSElliott Hughes                def_glyphs[0].id,
180*e1fe3e4aSElliott Hughes                def_glyphs[0].unicode,
181*e1fe3e4aSElliott Hughes                def_glyphs[0].type,
182*e1fe3e4aSElliott Hughes                def_glyphs[0].components,
183*e1fe3e4aSElliott Hughes            ),
184*e1fe3e4aSElliott Hughes            ("A", 3, [0x41], "BASE", None),
185*e1fe3e4aSElliott Hughes        )
186*e1fe3e4aSElliott Hughes        self.assertEqual(
187*e1fe3e4aSElliott Hughes            (
188*e1fe3e4aSElliott Hughes                def_glyphs[1].name,
189*e1fe3e4aSElliott Hughes                def_glyphs[1].id,
190*e1fe3e4aSElliott Hughes                def_glyphs[1].unicode,
191*e1fe3e4aSElliott Hughes                def_glyphs[1].type,
192*e1fe3e4aSElliott Hughes                def_glyphs[1].components,
193*e1fe3e4aSElliott Hughes            ),
194*e1fe3e4aSElliott Hughes            ("a", 4, [0x61], "BASE", None),
195*e1fe3e4aSElliott Hughes        )
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes    def test_def_group_glyphs(self):
198*e1fe3e4aSElliott Hughes        [def_group] = self.parse(
199*e1fe3e4aSElliott Hughes            'DEF_GROUP "aaccented"\n'
200*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
201*e1fe3e4aSElliott Hughes            'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
202*e1fe3e4aSElliott Hughes            'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
203*e1fe3e4aSElliott Hughes            "END_GROUP"
204*e1fe3e4aSElliott Hughes        ).statements
205*e1fe3e4aSElliott Hughes        self.assertEqual(
206*e1fe3e4aSElliott Hughes            (def_group.name, def_group.enum.glyphSet()),
207*e1fe3e4aSElliott Hughes            (
208*e1fe3e4aSElliott Hughes                "aaccented",
209*e1fe3e4aSElliott Hughes                (
210*e1fe3e4aSElliott Hughes                    "aacute",
211*e1fe3e4aSElliott Hughes                    "abreve",
212*e1fe3e4aSElliott Hughes                    "acircumflex",
213*e1fe3e4aSElliott Hughes                    "adieresis",
214*e1fe3e4aSElliott Hughes                    "ae",
215*e1fe3e4aSElliott Hughes                    "agrave",
216*e1fe3e4aSElliott Hughes                    "amacron",
217*e1fe3e4aSElliott Hughes                    "aogonek",
218*e1fe3e4aSElliott Hughes                    "aring",
219*e1fe3e4aSElliott Hughes                    "atilde",
220*e1fe3e4aSElliott Hughes                ),
221*e1fe3e4aSElliott Hughes            ),
222*e1fe3e4aSElliott Hughes        )
223*e1fe3e4aSElliott Hughes
224*e1fe3e4aSElliott Hughes    def test_def_group_groups(self):
225*e1fe3e4aSElliott Hughes        [group1, group2, test_group] = self.parse(
226*e1fe3e4aSElliott Hughes            'DEF_GROUP "Group1"\n'
227*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
228*e1fe3e4aSElliott Hughes            "END_GROUP\n"
229*e1fe3e4aSElliott Hughes            'DEF_GROUP "Group2"\n'
230*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
231*e1fe3e4aSElliott Hughes            "END_GROUP\n"
232*e1fe3e4aSElliott Hughes            'DEF_GROUP "TestGroup"\n'
233*e1fe3e4aSElliott Hughes            ' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
234*e1fe3e4aSElliott Hughes            "END_GROUP"
235*e1fe3e4aSElliott Hughes        ).statements
236*e1fe3e4aSElliott Hughes        groups = [g.group for g in test_group.enum.enum]
237*e1fe3e4aSElliott Hughes        self.assertEqual((test_group.name, groups), ("TestGroup", ["Group1", "Group2"]))
238*e1fe3e4aSElliott Hughes
239*e1fe3e4aSElliott Hughes    def test_def_group_groups_not_yet_defined(self):
240*e1fe3e4aSElliott Hughes        [group1, test_group1, test_group2, test_group3, group2] = self.parse(
241*e1fe3e4aSElliott Hughes            'DEF_GROUP "Group1"\n'
242*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
243*e1fe3e4aSElliott Hughes            "END_GROUP\n"
244*e1fe3e4aSElliott Hughes            'DEF_GROUP "TestGroup1"\n'
245*e1fe3e4aSElliott Hughes            ' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
246*e1fe3e4aSElliott Hughes            "END_GROUP\n"
247*e1fe3e4aSElliott Hughes            'DEF_GROUP "TestGroup2"\n'
248*e1fe3e4aSElliott Hughes            ' ENUM GROUP "Group2" END_ENUM\n'
249*e1fe3e4aSElliott Hughes            "END_GROUP\n"
250*e1fe3e4aSElliott Hughes            'DEF_GROUP "TestGroup3"\n'
251*e1fe3e4aSElliott Hughes            ' ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n'
252*e1fe3e4aSElliott Hughes            "END_GROUP\n"
253*e1fe3e4aSElliott Hughes            'DEF_GROUP "Group2"\n'
254*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
255*e1fe3e4aSElliott Hughes            "END_GROUP"
256*e1fe3e4aSElliott Hughes        ).statements
257*e1fe3e4aSElliott Hughes        groups = [g.group for g in test_group1.enum.enum]
258*e1fe3e4aSElliott Hughes        self.assertEqual(
259*e1fe3e4aSElliott Hughes            (test_group1.name, groups), ("TestGroup1", ["Group1", "Group2"])
260*e1fe3e4aSElliott Hughes        )
261*e1fe3e4aSElliott Hughes        groups = [g.group for g in test_group2.enum.enum]
262*e1fe3e4aSElliott Hughes        self.assertEqual((test_group2.name, groups), ("TestGroup2", ["Group2"]))
263*e1fe3e4aSElliott Hughes        groups = [g.group for g in test_group3.enum.enum]
264*e1fe3e4aSElliott Hughes        self.assertEqual(
265*e1fe3e4aSElliott Hughes            (test_group3.name, groups), ("TestGroup3", ["Group2", "Group1"])
266*e1fe3e4aSElliott Hughes        )
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughes    # def test_def_group_groups_undefined(self):
269*e1fe3e4aSElliott Hughes    #     with self.assertRaisesRegex(
270*e1fe3e4aSElliott Hughes    #             VoltLibError,
271*e1fe3e4aSElliott Hughes    #             r'Group "Group2" is used but undefined.'):
272*e1fe3e4aSElliott Hughes    #         [group1, test_group, group2] = self.parse(
273*e1fe3e4aSElliott Hughes    #             'DEF_GROUP "Group1"\n'
274*e1fe3e4aSElliott Hughes    #             'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
275*e1fe3e4aSElliott Hughes    #             'END_GROUP\n'
276*e1fe3e4aSElliott Hughes    #             'DEF_GROUP "TestGroup"\n'
277*e1fe3e4aSElliott Hughes    #             'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
278*e1fe3e4aSElliott Hughes    #             'END_GROUP\n'
279*e1fe3e4aSElliott Hughes    #         ).statements
280*e1fe3e4aSElliott Hughes
281*e1fe3e4aSElliott Hughes    def test_def_group_glyphs_and_group(self):
282*e1fe3e4aSElliott Hughes        [def_group1, def_group2] = self.parse(
283*e1fe3e4aSElliott Hughes            'DEF_GROUP "aaccented"\n'
284*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" '
285*e1fe3e4aSElliott Hughes            'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" '
286*e1fe3e4aSElliott Hughes            'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n'
287*e1fe3e4aSElliott Hughes            "END_GROUP\n"
288*e1fe3e4aSElliott Hughes            'DEF_GROUP "KERN_lc_a_2ND"\n'
289*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n'
290*e1fe3e4aSElliott Hughes            "END_GROUP"
291*e1fe3e4aSElliott Hughes        ).statements
292*e1fe3e4aSElliott Hughes        items = def_group2.enum.enum
293*e1fe3e4aSElliott Hughes        self.assertEqual(
294*e1fe3e4aSElliott Hughes            (def_group2.name, items[0].glyphSet(), items[1].group),
295*e1fe3e4aSElliott Hughes            ("KERN_lc_a_2ND", ("a",), "aaccented"),
296*e1fe3e4aSElliott Hughes        )
297*e1fe3e4aSElliott Hughes
298*e1fe3e4aSElliott Hughes    def test_def_group_range(self):
299*e1fe3e4aSElliott Hughes        def_group = self.parse(
300*e1fe3e4aSElliott Hughes            'DEF_GLYPH "a" ID 163 UNICODE 97 TYPE BASE END_GLYPH\n'
301*e1fe3e4aSElliott Hughes            'DEF_GLYPH "agrave" ID 194 UNICODE 224 TYPE BASE END_GLYPH\n'
302*e1fe3e4aSElliott Hughes            'DEF_GLYPH "aacute" ID 195 UNICODE 225 TYPE BASE END_GLYPH\n'
303*e1fe3e4aSElliott Hughes            'DEF_GLYPH "acircumflex" ID 196 UNICODE 226 TYPE BASE END_GLYPH\n'
304*e1fe3e4aSElliott Hughes            'DEF_GLYPH "atilde" ID 197 UNICODE 227 TYPE BASE END_GLYPH\n'
305*e1fe3e4aSElliott Hughes            'DEF_GLYPH "c" ID 165 UNICODE 99 TYPE BASE END_GLYPH\n'
306*e1fe3e4aSElliott Hughes            'DEF_GLYPH "ccaron" ID 209 UNICODE 269 TYPE BASE END_GLYPH\n'
307*e1fe3e4aSElliott Hughes            'DEF_GLYPH "ccedilla" ID 210 UNICODE 231 TYPE BASE END_GLYPH\n'
308*e1fe3e4aSElliott Hughes            'DEF_GLYPH "cdotaccent" ID 210 UNICODE 267 TYPE BASE END_GLYPH\n'
309*e1fe3e4aSElliott Hughes            'DEF_GROUP "KERN_lc_a_2ND"\n'
310*e1fe3e4aSElliott Hughes            ' ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" '
311*e1fe3e4aSElliott Hughes            "END_ENUM\n"
312*e1fe3e4aSElliott Hughes            "END_GROUP"
313*e1fe3e4aSElliott Hughes        ).statements[-1]
314*e1fe3e4aSElliott Hughes        self.assertEqual(
315*e1fe3e4aSElliott Hughes            (def_group.name, def_group.enum.glyphSet()),
316*e1fe3e4aSElliott Hughes            (
317*e1fe3e4aSElliott Hughes                "KERN_lc_a_2ND",
318*e1fe3e4aSElliott Hughes                (
319*e1fe3e4aSElliott Hughes                    "a",
320*e1fe3e4aSElliott Hughes                    "agrave",
321*e1fe3e4aSElliott Hughes                    "aacute",
322*e1fe3e4aSElliott Hughes                    "acircumflex",
323*e1fe3e4aSElliott Hughes                    "atilde",
324*e1fe3e4aSElliott Hughes                    "b",
325*e1fe3e4aSElliott Hughes                    "c",
326*e1fe3e4aSElliott Hughes                    "ccaron",
327*e1fe3e4aSElliott Hughes                    "ccedilla",
328*e1fe3e4aSElliott Hughes                    "cdotaccent",
329*e1fe3e4aSElliott Hughes                ),
330*e1fe3e4aSElliott Hughes            ),
331*e1fe3e4aSElliott Hughes        )
332*e1fe3e4aSElliott Hughes
333*e1fe3e4aSElliott Hughes    def test_group_duplicate(self):
334*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
335*e1fe3e4aSElliott Hughes            VoltLibError,
336*e1fe3e4aSElliott Hughes            'Glyph group "dupe" already defined, ' "group names are case insensitive",
337*e1fe3e4aSElliott Hughes            self.parse,
338*e1fe3e4aSElliott Hughes            'DEF_GROUP "dupe"\n'
339*e1fe3e4aSElliott Hughes            'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
340*e1fe3e4aSElliott Hughes            "END_GROUP\n"
341*e1fe3e4aSElliott Hughes            'DEF_GROUP "dupe"\n'
342*e1fe3e4aSElliott Hughes            'ENUM GLYPH "x" END_ENUM\n'
343*e1fe3e4aSElliott Hughes            "END_GROUP",
344*e1fe3e4aSElliott Hughes        )
345*e1fe3e4aSElliott Hughes
346*e1fe3e4aSElliott Hughes    def test_group_duplicate_case_insensitive(self):
347*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
348*e1fe3e4aSElliott Hughes            VoltLibError,
349*e1fe3e4aSElliott Hughes            'Glyph group "Dupe" already defined, ' "group names are case insensitive",
350*e1fe3e4aSElliott Hughes            self.parse,
351*e1fe3e4aSElliott Hughes            'DEF_GROUP "dupe"\n'
352*e1fe3e4aSElliott Hughes            'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
353*e1fe3e4aSElliott Hughes            "END_GROUP\n"
354*e1fe3e4aSElliott Hughes            'DEF_GROUP "Dupe"\n'
355*e1fe3e4aSElliott Hughes            'ENUM GLYPH "x" END_ENUM\n'
356*e1fe3e4aSElliott Hughes            "END_GROUP",
357*e1fe3e4aSElliott Hughes        )
358*e1fe3e4aSElliott Hughes
359*e1fe3e4aSElliott Hughes    def test_script_without_langsys(self):
360*e1fe3e4aSElliott Hughes        [script] = self.parse(
361*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n' "END_SCRIPT"
362*e1fe3e4aSElliott Hughes        ).statements
363*e1fe3e4aSElliott Hughes        self.assertEqual((script.name, script.tag, script.langs), ("Latin", "latn", []))
364*e1fe3e4aSElliott Hughes
365*e1fe3e4aSElliott Hughes    def test_langsys_normal(self):
366*e1fe3e4aSElliott Hughes        [def_script] = self.parse(
367*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
368*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
369*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
370*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n\n'
371*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
372*e1fe3e4aSElliott Hughes            "END_SCRIPT"
373*e1fe3e4aSElliott Hughes        ).statements
374*e1fe3e4aSElliott Hughes        self.assertEqual((def_script.name, def_script.tag), ("Latin", "latn"))
375*e1fe3e4aSElliott Hughes        def_lang = def_script.langs[0]
376*e1fe3e4aSElliott Hughes        self.assertEqual((def_lang.name, def_lang.tag), ("Romanian", "ROM "))
377*e1fe3e4aSElliott Hughes        def_lang = def_script.langs[1]
378*e1fe3e4aSElliott Hughes        self.assertEqual((def_lang.name, def_lang.tag), ("Moldavian", "MOL "))
379*e1fe3e4aSElliott Hughes
380*e1fe3e4aSElliott Hughes    def test_langsys_no_script_name(self):
381*e1fe3e4aSElliott Hughes        [langsys] = self.parse(
382*e1fe3e4aSElliott Hughes            'DEF_SCRIPT TAG "latn"\n\n'
383*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
384*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
385*e1fe3e4aSElliott Hughes            "END_SCRIPT"
386*e1fe3e4aSElliott Hughes        ).statements
387*e1fe3e4aSElliott Hughes        self.assertEqual((langsys.name, langsys.tag), (None, "latn"))
388*e1fe3e4aSElliott Hughes        lang = langsys.langs[0]
389*e1fe3e4aSElliott Hughes        self.assertEqual((lang.name, lang.tag), ("Default", "dflt"))
390*e1fe3e4aSElliott Hughes
391*e1fe3e4aSElliott Hughes    def test_langsys_no_script_tag_fails(self):
392*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r'.*Expected "TAG"'):
393*e1fe3e4aSElliott Hughes            [langsys] = self.parse(
394*e1fe3e4aSElliott Hughes                'DEF_SCRIPT NAME "Latin"\n\n'
395*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
396*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
397*e1fe3e4aSElliott Hughes                "END_SCRIPT"
398*e1fe3e4aSElliott Hughes            ).statements
399*e1fe3e4aSElliott Hughes
400*e1fe3e4aSElliott Hughes    def test_langsys_duplicate_script(self):
401*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
402*e1fe3e4aSElliott Hughes            VoltLibError,
403*e1fe3e4aSElliott Hughes            'Script "DFLT" already defined, ' "script tags are case insensitive",
404*e1fe3e4aSElliott Hughes        ):
405*e1fe3e4aSElliott Hughes            [langsys1, langsys2] = self.parse(
406*e1fe3e4aSElliott Hughes                'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
407*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
408*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
409*e1fe3e4aSElliott Hughes                "END_SCRIPT\n"
410*e1fe3e4aSElliott Hughes                'DEF_SCRIPT TAG "DFLT"\n\n'
411*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
412*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
413*e1fe3e4aSElliott Hughes                "END_SCRIPT"
414*e1fe3e4aSElliott Hughes            ).statements
415*e1fe3e4aSElliott Hughes
416*e1fe3e4aSElliott Hughes    def test_langsys_duplicate_lang(self):
417*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
418*e1fe3e4aSElliott Hughes            VoltLibError,
419*e1fe3e4aSElliott Hughes            'Language "dflt" already defined in script "DFLT", '
420*e1fe3e4aSElliott Hughes            "language tags are case insensitive",
421*e1fe3e4aSElliott Hughes        ):
422*e1fe3e4aSElliott Hughes            [langsys] = self.parse(
423*e1fe3e4aSElliott Hughes                'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
424*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
425*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
426*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
427*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
428*e1fe3e4aSElliott Hughes                "END_SCRIPT"
429*e1fe3e4aSElliott Hughes            ).statements
430*e1fe3e4aSElliott Hughes
431*e1fe3e4aSElliott Hughes    def test_langsys_lang_in_separate_scripts(self):
432*e1fe3e4aSElliott Hughes        [langsys1, langsys2] = self.parse(
433*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
434*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
435*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
436*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
437*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
438*e1fe3e4aSElliott Hughes            "END_SCRIPT\n"
439*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
440*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
441*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
442*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
443*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
444*e1fe3e4aSElliott Hughes            "END_SCRIPT"
445*e1fe3e4aSElliott Hughes        ).statements
446*e1fe3e4aSElliott Hughes        self.assertEqual(
447*e1fe3e4aSElliott Hughes            (langsys1.langs[0].tag, langsys1.langs[1].tag), ("dflt", "ROM ")
448*e1fe3e4aSElliott Hughes        )
449*e1fe3e4aSElliott Hughes        self.assertEqual(
450*e1fe3e4aSElliott Hughes            (langsys2.langs[0].tag, langsys2.langs[1].tag), ("dflt", "ROM ")
451*e1fe3e4aSElliott Hughes        )
452*e1fe3e4aSElliott Hughes
453*e1fe3e4aSElliott Hughes    def test_langsys_no_lang_name(self):
454*e1fe3e4aSElliott Hughes        [langsys] = self.parse(
455*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
456*e1fe3e4aSElliott Hughes            'DEF_LANGSYS TAG "dflt"\n\n'
457*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
458*e1fe3e4aSElliott Hughes            "END_SCRIPT"
459*e1fe3e4aSElliott Hughes        ).statements
460*e1fe3e4aSElliott Hughes        self.assertEqual((langsys.name, langsys.tag), ("Latin", "latn"))
461*e1fe3e4aSElliott Hughes        lang = langsys.langs[0]
462*e1fe3e4aSElliott Hughes        self.assertEqual((lang.name, lang.tag), (None, "dflt"))
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes    def test_langsys_no_langsys_tag_fails(self):
465*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r'.*Expected "TAG"'):
466*e1fe3e4aSElliott Hughes            [langsys] = self.parse(
467*e1fe3e4aSElliott Hughes                'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
468*e1fe3e4aSElliott Hughes                'DEF_LANGSYS NAME "Default"\n\n'
469*e1fe3e4aSElliott Hughes                "END_LANGSYS\n"
470*e1fe3e4aSElliott Hughes                "END_SCRIPT"
471*e1fe3e4aSElliott Hughes            ).statements
472*e1fe3e4aSElliott Hughes
473*e1fe3e4aSElliott Hughes    def test_feature(self):
474*e1fe3e4aSElliott Hughes        [def_script] = self.parse(
475*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
476*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
477*e1fe3e4aSElliott Hughes            'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
478*e1fe3e4aSElliott Hughes            ' LOOKUP "fraclookup"\n'
479*e1fe3e4aSElliott Hughes            "END_FEATURE\n"
480*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
481*e1fe3e4aSElliott Hughes            "END_SCRIPT"
482*e1fe3e4aSElliott Hughes        ).statements
483*e1fe3e4aSElliott Hughes        def_feature = def_script.langs[0].features[0]
484*e1fe3e4aSElliott Hughes        self.assertEqual(
485*e1fe3e4aSElliott Hughes            (def_feature.name, def_feature.tag, def_feature.lookups),
486*e1fe3e4aSElliott Hughes            ("Fractions", "frac", ["fraclookup"]),
487*e1fe3e4aSElliott Hughes        )
488*e1fe3e4aSElliott Hughes        [def_script] = self.parse(
489*e1fe3e4aSElliott Hughes            'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
490*e1fe3e4aSElliott Hughes            'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
491*e1fe3e4aSElliott Hughes            'DEF_FEATURE NAME "Kerning" TAG "kern"\n'
492*e1fe3e4aSElliott Hughes            ' LOOKUP "kern1" LOOKUP "kern2"\n'
493*e1fe3e4aSElliott Hughes            "END_FEATURE\n"
494*e1fe3e4aSElliott Hughes            "END_LANGSYS\n"
495*e1fe3e4aSElliott Hughes            "END_SCRIPT"
496*e1fe3e4aSElliott Hughes        ).statements
497*e1fe3e4aSElliott Hughes        def_feature = def_script.langs[0].features[0]
498*e1fe3e4aSElliott Hughes        self.assertEqual(
499*e1fe3e4aSElliott Hughes            (def_feature.name, def_feature.tag, def_feature.lookups),
500*e1fe3e4aSElliott Hughes            ("Kerning", "kern", ["kern1", "kern2"]),
501*e1fe3e4aSElliott Hughes        )
502*e1fe3e4aSElliott Hughes
503*e1fe3e4aSElliott Hughes    def test_lookup_duplicate(self):
504*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
505*e1fe3e4aSElliott Hughes            VoltLibError,
506*e1fe3e4aSElliott Hughes            'Lookup "dupe" already defined, ' "lookup names are case insensitive",
507*e1fe3e4aSElliott Hughes        ):
508*e1fe3e4aSElliott Hughes            [lookup1, lookup2] = self.parse(
509*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "dupe"\n'
510*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
511*e1fe3e4aSElliott Hughes                'SUB GLYPH "a"\n'
512*e1fe3e4aSElliott Hughes                'WITH GLYPH "a.alt"\n'
513*e1fe3e4aSElliott Hughes                "END_SUB\n"
514*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION\n"
515*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "dupe"\n'
516*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
517*e1fe3e4aSElliott Hughes                'SUB GLYPH "b"\n'
518*e1fe3e4aSElliott Hughes                'WITH GLYPH "b.alt"\n'
519*e1fe3e4aSElliott Hughes                "END_SUB\n"
520*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION\n"
521*e1fe3e4aSElliott Hughes            ).statements
522*e1fe3e4aSElliott Hughes
523*e1fe3e4aSElliott Hughes    def test_lookup_duplicate_insensitive_case(self):
524*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
525*e1fe3e4aSElliott Hughes            VoltLibError,
526*e1fe3e4aSElliott Hughes            'Lookup "Dupe" already defined, ' "lookup names are case insensitive",
527*e1fe3e4aSElliott Hughes        ):
528*e1fe3e4aSElliott Hughes            [lookup1, lookup2] = self.parse(
529*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "dupe"\n'
530*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
531*e1fe3e4aSElliott Hughes                'SUB GLYPH "a"\n'
532*e1fe3e4aSElliott Hughes                'WITH GLYPH "a.alt"\n'
533*e1fe3e4aSElliott Hughes                "END_SUB\n"
534*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION\n"
535*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "Dupe"\n'
536*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
537*e1fe3e4aSElliott Hughes                'SUB GLYPH "b"\n'
538*e1fe3e4aSElliott Hughes                'WITH GLYPH "b.alt"\n'
539*e1fe3e4aSElliott Hughes                "END_SUB\n"
540*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION\n"
541*e1fe3e4aSElliott Hughes            ).statements
542*e1fe3e4aSElliott Hughes
543*e1fe3e4aSElliott Hughes    def test_lookup_name_starts_with_letter(self):
544*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
545*e1fe3e4aSElliott Hughes            VoltLibError, r'Lookup name "\\lookupname" must start with a letter'
546*e1fe3e4aSElliott Hughes        ):
547*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
548*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "\\lookupname"\n'
549*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
550*e1fe3e4aSElliott Hughes                'SUB GLYPH "a"\n'
551*e1fe3e4aSElliott Hughes                'WITH GLYPH "a.alt"\n'
552*e1fe3e4aSElliott Hughes                "END_SUB\n"
553*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION\n"
554*e1fe3e4aSElliott Hughes            ).statements
555*e1fe3e4aSElliott Hughes
556*e1fe3e4aSElliott Hughes    def test_lookup_comments(self):
557*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
558*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "test" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR\n'
559*e1fe3e4aSElliott Hughes            'COMMENTS "Hello\\nWorld"\n'
560*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
561*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
562*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
563*e1fe3e4aSElliott Hughes            'SUB GLYPH "a"\n'
564*e1fe3e4aSElliott Hughes            'WITH GLYPH "b"\n'
565*e1fe3e4aSElliott Hughes            "END_SUB\n"
566*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
567*e1fe3e4aSElliott Hughes        ).statements
568*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "test")
569*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.comments, "Hello\nWorld")
570*e1fe3e4aSElliott Hughes
571*e1fe3e4aSElliott Hughes    def test_substitution_empty(self):
572*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r"Expected SUB"):
573*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
574*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "empty_substitution" PROCESS_BASE PROCESS_MARKS '
575*e1fe3e4aSElliott Hughes                "ALL DIRECTION LTR\n"
576*e1fe3e4aSElliott Hughes                "IN_CONTEXT\n"
577*e1fe3e4aSElliott Hughes                "END_CONTEXT\n"
578*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
579*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION"
580*e1fe3e4aSElliott Hughes            ).statements
581*e1fe3e4aSElliott Hughes
582*e1fe3e4aSElliott Hughes    def test_substitution_invalid_many_to_many(self):
583*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
584*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
585*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
586*e1fe3e4aSElliott Hughes                "ALL DIRECTION LTR\n"
587*e1fe3e4aSElliott Hughes                "IN_CONTEXT\n"
588*e1fe3e4aSElliott Hughes                "END_CONTEXT\n"
589*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
590*e1fe3e4aSElliott Hughes                'SUB GLYPH "f" GLYPH "i"\n'
591*e1fe3e4aSElliott Hughes                'WITH GLYPH "f.alt" GLYPH "i.alt"\n'
592*e1fe3e4aSElliott Hughes                "END_SUB\n"
593*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION"
594*e1fe3e4aSElliott Hughes            ).statements
595*e1fe3e4aSElliott Hughes
596*e1fe3e4aSElliott Hughes    def test_substitution_invalid_reverse_chaining_single(self):
597*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
598*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
599*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
600*e1fe3e4aSElliott Hughes                "ALL DIRECTION LTR REVERSAL\n"
601*e1fe3e4aSElliott Hughes                "IN_CONTEXT\n"
602*e1fe3e4aSElliott Hughes                "END_CONTEXT\n"
603*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
604*e1fe3e4aSElliott Hughes                'SUB GLYPH "f" GLYPH "i"\n'
605*e1fe3e4aSElliott Hughes                'WITH GLYPH "f_i"\n'
606*e1fe3e4aSElliott Hughes                "END_SUB\n"
607*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION"
608*e1fe3e4aSElliott Hughes            ).statements
609*e1fe3e4aSElliott Hughes
610*e1fe3e4aSElliott Hughes    def test_substitution_invalid_mixed(self):
611*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
612*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
613*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
614*e1fe3e4aSElliott Hughes                "ALL DIRECTION LTR\n"
615*e1fe3e4aSElliott Hughes                "IN_CONTEXT\n"
616*e1fe3e4aSElliott Hughes                "END_CONTEXT\n"
617*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
618*e1fe3e4aSElliott Hughes                'SUB GLYPH "fi"\n'
619*e1fe3e4aSElliott Hughes                'WITH GLYPH "f" GLYPH "i"\n'
620*e1fe3e4aSElliott Hughes                "END_SUB\n"
621*e1fe3e4aSElliott Hughes                'SUB GLYPH "f" GLYPH "l"\n'
622*e1fe3e4aSElliott Hughes                'WITH GLYPH "f_l"\n'
623*e1fe3e4aSElliott Hughes                "END_SUB\n"
624*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION"
625*e1fe3e4aSElliott Hughes            ).statements
626*e1fe3e4aSElliott Hughes
627*e1fe3e4aSElliott Hughes    def test_substitution_single(self):
628*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
629*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL '
630*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
631*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
632*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
633*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
634*e1fe3e4aSElliott Hughes            'SUB GLYPH "a"\n'
635*e1fe3e4aSElliott Hughes            'WITH GLYPH "a.sc"\n'
636*e1fe3e4aSElliott Hughes            "END_SUB\n"
637*e1fe3e4aSElliott Hughes            'SUB GLYPH "b"\n'
638*e1fe3e4aSElliott Hughes            'WITH GLYPH "b.sc"\n'
639*e1fe3e4aSElliott Hughes            "END_SUB\n"
640*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
641*e1fe3e4aSElliott Hughes        ).statements
642*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "smcp")
643*e1fe3e4aSElliott Hughes        self.assertSubEqual(lookup.sub, [["a"], ["b"]], [["a.sc"], ["b.sc"]])
644*e1fe3e4aSElliott Hughes
645*e1fe3e4aSElliott Hughes    def test_substitution_single_in_context(self):
646*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
647*e1fe3e4aSElliott Hughes            'DEF_GROUP "Denominators"\n'
648*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "one.dnom" GLYPH "two.dnom" END_ENUM\n'
649*e1fe3e4aSElliott Hughes            "END_GROUP\n"
650*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL '
651*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
652*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
653*e1fe3e4aSElliott Hughes            ' LEFT ENUM GROUP "Denominators" GLYPH "fraction" END_ENUM\n'
654*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
655*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
656*e1fe3e4aSElliott Hughes            'SUB GLYPH "one"\n'
657*e1fe3e4aSElliott Hughes            'WITH GLYPH "one.dnom"\n'
658*e1fe3e4aSElliott Hughes            "END_SUB\n"
659*e1fe3e4aSElliott Hughes            'SUB GLYPH "two"\n'
660*e1fe3e4aSElliott Hughes            'WITH GLYPH "two.dnom"\n'
661*e1fe3e4aSElliott Hughes            "END_SUB\n"
662*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
663*e1fe3e4aSElliott Hughes        ).statements
664*e1fe3e4aSElliott Hughes        context = lookup.context[0]
665*e1fe3e4aSElliott Hughes
666*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "fracdnom")
667*e1fe3e4aSElliott Hughes        self.assertEqual(context.ex_or_in, "IN_CONTEXT")
668*e1fe3e4aSElliott Hughes        self.assertEqual(len(context.left), 1)
669*e1fe3e4aSElliott Hughes        self.assertEqual(len(context.left[0]), 1)
670*e1fe3e4aSElliott Hughes        self.assertEqual(len(context.left[0][0].enum), 2)
671*e1fe3e4aSElliott Hughes        self.assertEqual(context.left[0][0].enum[0].group, "Denominators")
672*e1fe3e4aSElliott Hughes        self.assertEqual(context.left[0][0].enum[1].glyph, "fraction")
673*e1fe3e4aSElliott Hughes        self.assertEqual(context.right, [])
674*e1fe3e4aSElliott Hughes        self.assertSubEqual(
675*e1fe3e4aSElliott Hughes            lookup.sub, [["one"], ["two"]], [["one.dnom"], ["two.dnom"]]
676*e1fe3e4aSElliott Hughes        )
677*e1fe3e4aSElliott Hughes
678*e1fe3e4aSElliott Hughes    def test_substitution_single_in_contexts(self):
679*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
680*e1fe3e4aSElliott Hughes            'DEF_GROUP "Hebrew"\n'
681*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "uni05D0" GLYPH "uni05D1" END_ENUM\n'
682*e1fe3e4aSElliott Hughes            "END_GROUP\n"
683*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
684*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
685*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
686*e1fe3e4aSElliott Hughes            ' RIGHT GROUP "Hebrew"\n'
687*e1fe3e4aSElliott Hughes            ' RIGHT GLYPH "one.Hebr"\n'
688*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
689*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
690*e1fe3e4aSElliott Hughes            ' LEFT GROUP "Hebrew"\n'
691*e1fe3e4aSElliott Hughes            ' LEFT GLYPH "one.Hebr"\n'
692*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
693*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
694*e1fe3e4aSElliott Hughes            'SUB GLYPH "dollar"\n'
695*e1fe3e4aSElliott Hughes            'WITH GLYPH "dollar.Hebr"\n'
696*e1fe3e4aSElliott Hughes            "END_SUB\n"
697*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
698*e1fe3e4aSElliott Hughes        ).statements
699*e1fe3e4aSElliott Hughes        context1 = lookup.context[0]
700*e1fe3e4aSElliott Hughes        context2 = lookup.context[1]
701*e1fe3e4aSElliott Hughes
702*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "HebrewCurrency")
703*e1fe3e4aSElliott Hughes
704*e1fe3e4aSElliott Hughes        self.assertEqual(context1.ex_or_in, "IN_CONTEXT")
705*e1fe3e4aSElliott Hughes        self.assertEqual(context1.left, [])
706*e1fe3e4aSElliott Hughes        self.assertEqual(len(context1.right), 2)
707*e1fe3e4aSElliott Hughes        self.assertEqual(len(context1.right[0]), 1)
708*e1fe3e4aSElliott Hughes        self.assertEqual(len(context1.right[1]), 1)
709*e1fe3e4aSElliott Hughes        self.assertEqual(context1.right[0][0].group, "Hebrew")
710*e1fe3e4aSElliott Hughes        self.assertEqual(context1.right[1][0].glyph, "one.Hebr")
711*e1fe3e4aSElliott Hughes
712*e1fe3e4aSElliott Hughes        self.assertEqual(context2.ex_or_in, "IN_CONTEXT")
713*e1fe3e4aSElliott Hughes        self.assertEqual(len(context2.left), 2)
714*e1fe3e4aSElliott Hughes        self.assertEqual(len(context2.left[0]), 1)
715*e1fe3e4aSElliott Hughes        self.assertEqual(len(context2.left[1]), 1)
716*e1fe3e4aSElliott Hughes        self.assertEqual(context2.left[0][0].group, "Hebrew")
717*e1fe3e4aSElliott Hughes        self.assertEqual(context2.left[1][0].glyph, "one.Hebr")
718*e1fe3e4aSElliott Hughes        self.assertEqual(context2.right, [])
719*e1fe3e4aSElliott Hughes
720*e1fe3e4aSElliott Hughes    def test_substitution_skip_base(self):
721*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
722*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
723*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
724*e1fe3e4aSElliott Hughes            "END_GROUP\n"
725*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL '
726*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
727*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
728*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
729*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
730*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
731*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
732*e1fe3e4aSElliott Hughes            "END_SUB\n"
733*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
734*e1fe3e4aSElliott Hughes        ).statements
735*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_base), ("SomeSub", False))
736*e1fe3e4aSElliott Hughes
737*e1fe3e4aSElliott Hughes    def test_substitution_process_base(self):
738*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
739*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
740*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
741*e1fe3e4aSElliott Hughes            "END_GROUP\n"
742*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
743*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
744*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
745*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
746*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
747*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
748*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
749*e1fe3e4aSElliott Hughes            "END_SUB\n"
750*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
751*e1fe3e4aSElliott Hughes        ).statements
752*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_base), ("SomeSub", True))
753*e1fe3e4aSElliott Hughes
754*e1fe3e4aSElliott Hughes    def test_substitution_process_marks(self):
755*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
756*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
757*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
758*e1fe3e4aSElliott Hughes            "END_GROUP\n"
759*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "SomeMarks"\n'
760*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
761*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
762*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
763*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
764*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
765*e1fe3e4aSElliott Hughes            "END_SUB\n"
766*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
767*e1fe3e4aSElliott Hughes        ).statements
768*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", "SomeMarks"))
769*e1fe3e4aSElliott Hughes
770*e1fe3e4aSElliott Hughes    def test_substitution_process_marks_all(self):
771*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
772*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL\n'
773*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
774*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
775*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
776*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
777*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
778*e1fe3e4aSElliott Hughes            "END_SUB\n"
779*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
780*e1fe3e4aSElliott Hughes        ).statements
781*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", True))
782*e1fe3e4aSElliott Hughes
783*e1fe3e4aSElliott Hughes    def test_substitution_process_marks_none(self):
784*e1fe3e4aSElliott Hughes        [lookup] = self.parse_(
785*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "NONE"\n'
786*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
787*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
788*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
789*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
790*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
791*e1fe3e4aSElliott Hughes            "END_SUB\n"
792*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
793*e1fe3e4aSElliott Hughes        ).statements
794*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", False))
795*e1fe3e4aSElliott Hughes
796*e1fe3e4aSElliott Hughes    def test_substitution_process_marks_bad(self):
797*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
798*e1fe3e4aSElliott Hughes            VoltLibError, "Expected ALL, NONE, MARK_GLYPH_SET or an ID"
799*e1fe3e4aSElliott Hughes        ):
800*e1fe3e4aSElliott Hughes            self.parse(
801*e1fe3e4aSElliott Hughes                'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
802*e1fe3e4aSElliott Hughes                "END_ENUM END_GROUP\n"
803*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS SomeMarks '
804*e1fe3e4aSElliott Hughes                "AS_SUBSTITUTION\n"
805*e1fe3e4aSElliott Hughes                'SUB GLYPH "A" WITH GLYPH "A.c2sc"\n'
806*e1fe3e4aSElliott Hughes                "END_SUB\n"
807*e1fe3e4aSElliott Hughes                "END_SUBSTITUTION"
808*e1fe3e4aSElliott Hughes            )
809*e1fe3e4aSElliott Hughes
810*e1fe3e4aSElliott Hughes    def test_substitution_skip_marks(self):
811*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
812*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
813*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
814*e1fe3e4aSElliott Hughes            "END_GROUP\n"
815*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS DIRECTION LTR\n'
816*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
817*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
818*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
819*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
820*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
821*e1fe3e4aSElliott Hughes            "END_SUB\n"
822*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
823*e1fe3e4aSElliott Hughes        ).statements
824*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", False))
825*e1fe3e4aSElliott Hughes
826*e1fe3e4aSElliott Hughes    def test_substitution_mark_attachment(self):
827*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
828*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
829*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
830*e1fe3e4aSElliott Hughes            "END_GROUP\n"
831*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE '
832*e1fe3e4aSElliott Hughes            'PROCESS_MARKS "SomeMarks" DIRECTION RTL\n'
833*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
834*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
835*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
836*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
837*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
838*e1fe3e4aSElliott Hughes            "END_SUB\n"
839*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
840*e1fe3e4aSElliott Hughes        ).statements
841*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", "SomeMarks"))
842*e1fe3e4aSElliott Hughes
843*e1fe3e4aSElliott Hughes    def test_substitution_mark_glyph_set(self):
844*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
845*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
846*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
847*e1fe3e4aSElliott Hughes            "END_GROUP\n"
848*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE '
849*e1fe3e4aSElliott Hughes            'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" DIRECTION RTL\n'
850*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
851*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
852*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
853*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
854*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
855*e1fe3e4aSElliott Hughes            "END_SUB\n"
856*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
857*e1fe3e4aSElliott Hughes        ).statements
858*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.mark_glyph_set), ("SomeSub", "SomeMarks"))
859*e1fe3e4aSElliott Hughes
860*e1fe3e4aSElliott Hughes    def test_substitution_process_all_marks(self):
861*e1fe3e4aSElliott Hughes        [group, lookup] = self.parse(
862*e1fe3e4aSElliott Hughes            'DEF_GROUP "SomeMarks"\n'
863*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
864*e1fe3e4aSElliott Hughes            "END_GROUP\n"
865*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
866*e1fe3e4aSElliott Hughes            "DIRECTION RTL\n"
867*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
868*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
869*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
870*e1fe3e4aSElliott Hughes            'SUB GLYPH "A"\n'
871*e1fe3e4aSElliott Hughes            'WITH GLYPH "A.c2sc"\n'
872*e1fe3e4aSElliott Hughes            "END_SUB\n"
873*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
874*e1fe3e4aSElliott Hughes        ).statements
875*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", True))
876*e1fe3e4aSElliott Hughes
877*e1fe3e4aSElliott Hughes    def test_substitution_no_reversal(self):
878*e1fe3e4aSElliott Hughes        # TODO: check right context with no reversal
879*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
880*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL '
881*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
882*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
883*e1fe3e4aSElliott Hughes            ' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
884*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
885*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
886*e1fe3e4aSElliott Hughes            'SUB GLYPH "a"\n'
887*e1fe3e4aSElliott Hughes            'WITH GLYPH "a.alt"\n'
888*e1fe3e4aSElliott Hughes            "END_SUB\n"
889*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
890*e1fe3e4aSElliott Hughes        ).statements
891*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.reversal), ("Lookup", None))
892*e1fe3e4aSElliott Hughes
893*e1fe3e4aSElliott Hughes    def test_substitution_reversal(self):
894*e1fe3e4aSElliott Hughes        lookup = self.parse(
895*e1fe3e4aSElliott Hughes            'DEF_GROUP "DFLT_Num_standardFigures"\n'
896*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n'
897*e1fe3e4aSElliott Hughes            "END_GROUP\n"
898*e1fe3e4aSElliott Hughes            'DEF_GROUP "DFLT_Num_numerators"\n'
899*e1fe3e4aSElliott Hughes            ' ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n'
900*e1fe3e4aSElliott Hughes            "END_GROUP\n"
901*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL '
902*e1fe3e4aSElliott Hughes            "DIRECTION LTR REVERSAL\n"
903*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
904*e1fe3e4aSElliott Hughes            ' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
905*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
906*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
907*e1fe3e4aSElliott Hughes            'SUB GROUP "DFLT_Num_standardFigures"\n'
908*e1fe3e4aSElliott Hughes            'WITH GROUP "DFLT_Num_numerators"\n'
909*e1fe3e4aSElliott Hughes            "END_SUB\n"
910*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
911*e1fe3e4aSElliott Hughes        ).statements[-1]
912*e1fe3e4aSElliott Hughes        self.assertEqual((lookup.name, lookup.reversal), ("RevLookup", True))
913*e1fe3e4aSElliott Hughes
914*e1fe3e4aSElliott Hughes    def test_substitution_single_to_multiple(self):
915*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
916*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL '
917*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
918*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
919*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
920*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
921*e1fe3e4aSElliott Hughes            'SUB GLYPH "aacute"\n'
922*e1fe3e4aSElliott Hughes            'WITH GLYPH "a" GLYPH "acutecomb"\n'
923*e1fe3e4aSElliott Hughes            "END_SUB\n"
924*e1fe3e4aSElliott Hughes            'SUB GLYPH "agrave"\n'
925*e1fe3e4aSElliott Hughes            'WITH GLYPH "a" GLYPH "gravecomb"\n'
926*e1fe3e4aSElliott Hughes            "END_SUB\n"
927*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
928*e1fe3e4aSElliott Hughes        ).statements
929*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "ccmp")
930*e1fe3e4aSElliott Hughes        self.assertSubEqual(
931*e1fe3e4aSElliott Hughes            lookup.sub,
932*e1fe3e4aSElliott Hughes            [["aacute"], ["agrave"]],
933*e1fe3e4aSElliott Hughes            [["a", "acutecomb"], ["a", "gravecomb"]],
934*e1fe3e4aSElliott Hughes        )
935*e1fe3e4aSElliott Hughes
936*e1fe3e4aSElliott Hughes    def test_substitution_multiple_to_single(self):
937*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
938*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL '
939*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
940*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
941*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
942*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
943*e1fe3e4aSElliott Hughes            'SUB GLYPH "f" GLYPH "i"\n'
944*e1fe3e4aSElliott Hughes            'WITH GLYPH "f_i"\n'
945*e1fe3e4aSElliott Hughes            "END_SUB\n"
946*e1fe3e4aSElliott Hughes            'SUB GLYPH "f" GLYPH "t"\n'
947*e1fe3e4aSElliott Hughes            'WITH GLYPH "f_t"\n'
948*e1fe3e4aSElliott Hughes            "END_SUB\n"
949*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
950*e1fe3e4aSElliott Hughes        ).statements
951*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "liga")
952*e1fe3e4aSElliott Hughes        self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]], [["f_i"], ["f_t"]])
953*e1fe3e4aSElliott Hughes
954*e1fe3e4aSElliott Hughes    def test_substitution_reverse_chaining_single(self):
955*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
956*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
957*e1fe3e4aSElliott Hughes            "DIRECTION LTR REVERSAL\n"
958*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
959*e1fe3e4aSElliott Hughes            " RIGHT ENUM "
960*e1fe3e4aSElliott Hughes            'GLYPH "fraction" '
961*e1fe3e4aSElliott Hughes            'RANGE "zero.numr" TO "nine.numr" '
962*e1fe3e4aSElliott Hughes            "END_ENUM\n"
963*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
964*e1fe3e4aSElliott Hughes            "AS_SUBSTITUTION\n"
965*e1fe3e4aSElliott Hughes            'SUB RANGE "zero" TO "nine"\n'
966*e1fe3e4aSElliott Hughes            'WITH RANGE "zero.numr" TO "nine.numr"\n'
967*e1fe3e4aSElliott Hughes            "END_SUB\n"
968*e1fe3e4aSElliott Hughes            "END_SUBSTITUTION"
969*e1fe3e4aSElliott Hughes        ).statements
970*e1fe3e4aSElliott Hughes
971*e1fe3e4aSElliott Hughes        mapping = lookup.sub.mapping
972*e1fe3e4aSElliott Hughes        glyphs = [[(r.start, r.end) for r in v] for v in mapping.keys()]
973*e1fe3e4aSElliott Hughes        replacement = [[(r.start, r.end) for r in v] for v in mapping.values()]
974*e1fe3e4aSElliott Hughes
975*e1fe3e4aSElliott Hughes        self.assertEqual(lookup.name, "numr")
976*e1fe3e4aSElliott Hughes        self.assertEqual(glyphs, [[("zero", "nine")]])
977*e1fe3e4aSElliott Hughes        self.assertEqual(replacement, [[("zero.numr", "nine.numr")]])
978*e1fe3e4aSElliott Hughes
979*e1fe3e4aSElliott Hughes        self.assertEqual(len(lookup.context[0].right), 1)
980*e1fe3e4aSElliott Hughes        self.assertEqual(len(lookup.context[0].right[0]), 1)
981*e1fe3e4aSElliott Hughes        enum = lookup.context[0].right[0][0]
982*e1fe3e4aSElliott Hughes        self.assertEqual(len(enum.enum), 2)
983*e1fe3e4aSElliott Hughes        self.assertEqual(enum.enum[0].glyph, "fraction")
984*e1fe3e4aSElliott Hughes        self.assertEqual(
985*e1fe3e4aSElliott Hughes            (enum.enum[1].start, enum.enum[1].end), ("zero.numr", "nine.numr")
986*e1fe3e4aSElliott Hughes        )
987*e1fe3e4aSElliott Hughes
988*e1fe3e4aSElliott Hughes    # GPOS
989*e1fe3e4aSElliott Hughes    #  ATTACH_CURSIVE
990*e1fe3e4aSElliott Hughes    #  ATTACH
991*e1fe3e4aSElliott Hughes    #  ADJUST_PAIR
992*e1fe3e4aSElliott Hughes    #  ADJUST_SINGLE
993*e1fe3e4aSElliott Hughes    def test_position_empty(self):
994*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(
995*e1fe3e4aSElliott Hughes            VoltLibError, "Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE"
996*e1fe3e4aSElliott Hughes        ):
997*e1fe3e4aSElliott Hughes            [lookup] = self.parse(
998*e1fe3e4aSElliott Hughes                'DEF_LOOKUP "empty_position" PROCESS_BASE PROCESS_MARKS ALL '
999*e1fe3e4aSElliott Hughes                "DIRECTION LTR\n"
1000*e1fe3e4aSElliott Hughes                "EXCEPT_CONTEXT\n"
1001*e1fe3e4aSElliott Hughes                ' LEFT GLYPH "glyph"\n'
1002*e1fe3e4aSElliott Hughes                "END_CONTEXT\n"
1003*e1fe3e4aSElliott Hughes                "AS_POSITION\n"
1004*e1fe3e4aSElliott Hughes                "END_POSITION"
1005*e1fe3e4aSElliott Hughes            ).statements
1006*e1fe3e4aSElliott Hughes
1007*e1fe3e4aSElliott Hughes    def test_position_attach(self):
1008*e1fe3e4aSElliott Hughes        [lookup, anchor1, anchor2, anchor3, anchor4] = self.parse(
1009*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL '
1010*e1fe3e4aSElliott Hughes            "DIRECTION RTL\n"
1011*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
1012*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
1013*e1fe3e4aSElliott Hughes            "AS_POSITION\n"
1014*e1fe3e4aSElliott Hughes            'ATTACH GLYPH "a" GLYPH "e"\n'
1015*e1fe3e4aSElliott Hughes            'TO GLYPH "acutecomb" AT ANCHOR "top" '
1016*e1fe3e4aSElliott Hughes            'GLYPH "gravecomb" AT ANCHOR "top"\n'
1017*e1fe3e4aSElliott Hughes            "END_ATTACH\n"
1018*e1fe3e4aSElliott Hughes            "END_POSITION\n"
1019*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 '
1020*e1fe3e4aSElliott Hughes            "AT  POS DX 0 DY 450 END_POS END_ANCHOR\n"
1021*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 '
1022*e1fe3e4aSElliott Hughes            "AT  POS DX 0 DY 450 END_POS END_ANCHOR\n"
1023*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 '
1024*e1fe3e4aSElliott Hughes            "AT  POS DX 210 DY 450 END_POS END_ANCHOR\n"
1025*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 '
1026*e1fe3e4aSElliott Hughes            "AT  POS DX 215 DY 450 END_POS END_ANCHOR"
1027*e1fe3e4aSElliott Hughes        ).statements
1028*e1fe3e4aSElliott Hughes        pos = lookup.pos
1029*e1fe3e4aSElliott Hughes        coverage = [g.glyph for g in pos.coverage]
1030*e1fe3e4aSElliott Hughes        coverage_to = [[[g.glyph for g in e], a] for (e, a) in pos.coverage_to]
1031*e1fe3e4aSElliott Hughes        self.assertEqual(
1032*e1fe3e4aSElliott Hughes            (lookup.name, coverage, coverage_to),
1033*e1fe3e4aSElliott Hughes            (
1034*e1fe3e4aSElliott Hughes                "anchor_top",
1035*e1fe3e4aSElliott Hughes                ["a", "e"],
1036*e1fe3e4aSElliott Hughes                [[["acutecomb"], "top"], [["gravecomb"], "top"]],
1037*e1fe3e4aSElliott Hughes            ),
1038*e1fe3e4aSElliott Hughes        )
1039*e1fe3e4aSElliott Hughes        self.assertEqual(
1040*e1fe3e4aSElliott Hughes            (
1041*e1fe3e4aSElliott Hughes                anchor1.name,
1042*e1fe3e4aSElliott Hughes                anchor1.gid,
1043*e1fe3e4aSElliott Hughes                anchor1.glyph_name,
1044*e1fe3e4aSElliott Hughes                anchor1.component,
1045*e1fe3e4aSElliott Hughes                anchor1.locked,
1046*e1fe3e4aSElliott Hughes                anchor1.pos,
1047*e1fe3e4aSElliott Hughes            ),
1048*e1fe3e4aSElliott Hughes            ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {}, {})),
1049*e1fe3e4aSElliott Hughes        )
1050*e1fe3e4aSElliott Hughes        self.assertEqual(
1051*e1fe3e4aSElliott Hughes            (
1052*e1fe3e4aSElliott Hughes                anchor2.name,
1053*e1fe3e4aSElliott Hughes                anchor2.gid,
1054*e1fe3e4aSElliott Hughes                anchor2.glyph_name,
1055*e1fe3e4aSElliott Hughes                anchor2.component,
1056*e1fe3e4aSElliott Hughes                anchor2.locked,
1057*e1fe3e4aSElliott Hughes                anchor2.pos,
1058*e1fe3e4aSElliott Hughes            ),
1059*e1fe3e4aSElliott Hughes            ("MARK_top", 121, "gravecomb", 1, False, (None, 0, 450, {}, {}, {})),
1060*e1fe3e4aSElliott Hughes        )
1061*e1fe3e4aSElliott Hughes        self.assertEqual(
1062*e1fe3e4aSElliott Hughes            (
1063*e1fe3e4aSElliott Hughes                anchor3.name,
1064*e1fe3e4aSElliott Hughes                anchor3.gid,
1065*e1fe3e4aSElliott Hughes                anchor3.glyph_name,
1066*e1fe3e4aSElliott Hughes                anchor3.component,
1067*e1fe3e4aSElliott Hughes                anchor3.locked,
1068*e1fe3e4aSElliott Hughes                anchor3.pos,
1069*e1fe3e4aSElliott Hughes            ),
1070*e1fe3e4aSElliott Hughes            ("top", 31, "a", 1, False, (None, 210, 450, {}, {}, {})),
1071*e1fe3e4aSElliott Hughes        )
1072*e1fe3e4aSElliott Hughes        self.assertEqual(
1073*e1fe3e4aSElliott Hughes            (
1074*e1fe3e4aSElliott Hughes                anchor4.name,
1075*e1fe3e4aSElliott Hughes                anchor4.gid,
1076*e1fe3e4aSElliott Hughes                anchor4.glyph_name,
1077*e1fe3e4aSElliott Hughes                anchor4.component,
1078*e1fe3e4aSElliott Hughes                anchor4.locked,
1079*e1fe3e4aSElliott Hughes                anchor4.pos,
1080*e1fe3e4aSElliott Hughes            ),
1081*e1fe3e4aSElliott Hughes            ("top", 35, "e", 1, False, (None, 215, 450, {}, {}, {})),
1082*e1fe3e4aSElliott Hughes        )
1083*e1fe3e4aSElliott Hughes
1084*e1fe3e4aSElliott Hughes    def test_position_attach_cursive(self):
1085*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
1086*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL '
1087*e1fe3e4aSElliott Hughes            "DIRECTION RTL\n"
1088*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
1089*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
1090*e1fe3e4aSElliott Hughes            "AS_POSITION\n"
1091*e1fe3e4aSElliott Hughes            'ATTACH_CURSIVE\nEXIT  GLYPH "a" GLYPH "b"\nENTER  GLYPH "c"\n'
1092*e1fe3e4aSElliott Hughes            "END_ATTACH\n"
1093*e1fe3e4aSElliott Hughes            "END_POSITION"
1094*e1fe3e4aSElliott Hughes        ).statements
1095*e1fe3e4aSElliott Hughes        exit = [[g.glyph for g in v] for v in lookup.pos.coverages_exit]
1096*e1fe3e4aSElliott Hughes        enter = [[g.glyph for g in v] for v in lookup.pos.coverages_enter]
1097*e1fe3e4aSElliott Hughes        self.assertEqual(
1098*e1fe3e4aSElliott Hughes            (lookup.name, exit, enter), ("SomeLookup", [["a", "b"]], [["c"]])
1099*e1fe3e4aSElliott Hughes        )
1100*e1fe3e4aSElliott Hughes
1101*e1fe3e4aSElliott Hughes    def test_position_adjust_pair(self):
1102*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
1103*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
1104*e1fe3e4aSElliott Hughes            "DIRECTION RTL\n"
1105*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
1106*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
1107*e1fe3e4aSElliott Hughes            "AS_POSITION\n"
1108*e1fe3e4aSElliott Hughes            "ADJUST_PAIR\n"
1109*e1fe3e4aSElliott Hughes            ' FIRST  GLYPH "A"\n'
1110*e1fe3e4aSElliott Hughes            ' SECOND  GLYPH "V"\n'
1111*e1fe3e4aSElliott Hughes            " 1 2 BY POS ADV -30 END_POS POS END_POS\n"
1112*e1fe3e4aSElliott Hughes            " 2 1 BY POS ADV -30 END_POS POS END_POS\n\n"
1113*e1fe3e4aSElliott Hughes            "END_ADJUST\n"
1114*e1fe3e4aSElliott Hughes            "END_POSITION"
1115*e1fe3e4aSElliott Hughes        ).statements
1116*e1fe3e4aSElliott Hughes        coverages_1 = [[g.glyph for g in v] for v in lookup.pos.coverages_1]
1117*e1fe3e4aSElliott Hughes        coverages_2 = [[g.glyph for g in v] for v in lookup.pos.coverages_2]
1118*e1fe3e4aSElliott Hughes        self.assertEqual(
1119*e1fe3e4aSElliott Hughes            (lookup.name, coverages_1, coverages_2, lookup.pos.adjust_pair),
1120*e1fe3e4aSElliott Hughes            (
1121*e1fe3e4aSElliott Hughes                "kern1",
1122*e1fe3e4aSElliott Hughes                [["A"]],
1123*e1fe3e4aSElliott Hughes                [["V"]],
1124*e1fe3e4aSElliott Hughes                {
1125*e1fe3e4aSElliott Hughes                    (1, 2): (
1126*e1fe3e4aSElliott Hughes                        (-30, None, None, {}, {}, {}),
1127*e1fe3e4aSElliott Hughes                        (None, None, None, {}, {}, {}),
1128*e1fe3e4aSElliott Hughes                    ),
1129*e1fe3e4aSElliott Hughes                    (2, 1): (
1130*e1fe3e4aSElliott Hughes                        (-30, None, None, {}, {}, {}),
1131*e1fe3e4aSElliott Hughes                        (None, None, None, {}, {}, {}),
1132*e1fe3e4aSElliott Hughes                    ),
1133*e1fe3e4aSElliott Hughes                },
1134*e1fe3e4aSElliott Hughes            ),
1135*e1fe3e4aSElliott Hughes        )
1136*e1fe3e4aSElliott Hughes
1137*e1fe3e4aSElliott Hughes    def test_position_adjust_single(self):
1138*e1fe3e4aSElliott Hughes        [lookup] = self.parse(
1139*e1fe3e4aSElliott Hughes            'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
1140*e1fe3e4aSElliott Hughes            "DIRECTION LTR\n"
1141*e1fe3e4aSElliott Hughes            "IN_CONTEXT\n"
1142*e1fe3e4aSElliott Hughes            # ' LEFT GLYPH "leftGlyph"\n'
1143*e1fe3e4aSElliott Hughes            # ' RIGHT GLYPH "rightGlyph"\n'
1144*e1fe3e4aSElliott Hughes            "END_CONTEXT\n"
1145*e1fe3e4aSElliott Hughes            "AS_POSITION\n"
1146*e1fe3e4aSElliott Hughes            "ADJUST_SINGLE"
1147*e1fe3e4aSElliott Hughes            ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS'
1148*e1fe3e4aSElliott Hughes            ' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n'
1149*e1fe3e4aSElliott Hughes            "END_ADJUST\n"
1150*e1fe3e4aSElliott Hughes            "END_POSITION"
1151*e1fe3e4aSElliott Hughes        ).statements
1152*e1fe3e4aSElliott Hughes        pos = lookup.pos
1153*e1fe3e4aSElliott Hughes        adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single]
1154*e1fe3e4aSElliott Hughes        self.assertEqual(
1155*e1fe3e4aSElliott Hughes            (lookup.name, adjust),
1156*e1fe3e4aSElliott Hughes            (
1157*e1fe3e4aSElliott Hughes                "TestLookup",
1158*e1fe3e4aSElliott Hughes                [
1159*e1fe3e4aSElliott Hughes                    [["glyph1"], (0, 123, None, {}, {}, {})],
1160*e1fe3e4aSElliott Hughes                    [["glyph2"], (0, 456, None, {}, {}, {})],
1161*e1fe3e4aSElliott Hughes                ],
1162*e1fe3e4aSElliott Hughes            ),
1163*e1fe3e4aSElliott Hughes        )
1164*e1fe3e4aSElliott Hughes
1165*e1fe3e4aSElliott Hughes    def test_def_anchor(self):
1166*e1fe3e4aSElliott Hughes        [anchor1, anchor2, anchor3] = self.parse(
1167*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 120 GLYPH a '
1168*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 250 DY 450 END_POS END_ANCHOR\n"
1169*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb '
1170*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 0 DY 450 END_POS END_ANCHOR\n"
1171*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "bottom" ON 120 GLYPH a '
1172*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 250 DY 0 END_POS END_ANCHOR"
1173*e1fe3e4aSElliott Hughes        ).statements
1174*e1fe3e4aSElliott Hughes        self.assertEqual(
1175*e1fe3e4aSElliott Hughes            (
1176*e1fe3e4aSElliott Hughes                anchor1.name,
1177*e1fe3e4aSElliott Hughes                anchor1.gid,
1178*e1fe3e4aSElliott Hughes                anchor1.glyph_name,
1179*e1fe3e4aSElliott Hughes                anchor1.component,
1180*e1fe3e4aSElliott Hughes                anchor1.locked,
1181*e1fe3e4aSElliott Hughes                anchor1.pos,
1182*e1fe3e4aSElliott Hughes            ),
1183*e1fe3e4aSElliott Hughes            ("top", 120, "a", 1, False, (None, 250, 450, {}, {}, {})),
1184*e1fe3e4aSElliott Hughes        )
1185*e1fe3e4aSElliott Hughes        self.assertEqual(
1186*e1fe3e4aSElliott Hughes            (
1187*e1fe3e4aSElliott Hughes                anchor2.name,
1188*e1fe3e4aSElliott Hughes                anchor2.gid,
1189*e1fe3e4aSElliott Hughes                anchor2.glyph_name,
1190*e1fe3e4aSElliott Hughes                anchor2.component,
1191*e1fe3e4aSElliott Hughes                anchor2.locked,
1192*e1fe3e4aSElliott Hughes                anchor2.pos,
1193*e1fe3e4aSElliott Hughes            ),
1194*e1fe3e4aSElliott Hughes            ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {}, {})),
1195*e1fe3e4aSElliott Hughes        )
1196*e1fe3e4aSElliott Hughes        self.assertEqual(
1197*e1fe3e4aSElliott Hughes            (
1198*e1fe3e4aSElliott Hughes                anchor3.name,
1199*e1fe3e4aSElliott Hughes                anchor3.gid,
1200*e1fe3e4aSElliott Hughes                anchor3.glyph_name,
1201*e1fe3e4aSElliott Hughes                anchor3.component,
1202*e1fe3e4aSElliott Hughes                anchor3.locked,
1203*e1fe3e4aSElliott Hughes                anchor3.pos,
1204*e1fe3e4aSElliott Hughes            ),
1205*e1fe3e4aSElliott Hughes            ("bottom", 120, "a", 1, False, (None, 250, 0, {}, {}, {})),
1206*e1fe3e4aSElliott Hughes        )
1207*e1fe3e4aSElliott Hughes
1208*e1fe3e4aSElliott Hughes    def test_def_anchor_multi_component(self):
1209*e1fe3e4aSElliott Hughes        [anchor1, anchor2] = self.parse(
1210*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 120 GLYPH a '
1211*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 250 DY 450 END_POS END_ANCHOR\n"
1212*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 120 GLYPH a '
1213*e1fe3e4aSElliott Hughes            "COMPONENT 2 AT  POS DX 250 DY 450 END_POS END_ANCHOR"
1214*e1fe3e4aSElliott Hughes        ).statements
1215*e1fe3e4aSElliott Hughes        self.assertEqual(
1216*e1fe3e4aSElliott Hughes            (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component),
1217*e1fe3e4aSElliott Hughes            ("top", 120, "a", 1),
1218*e1fe3e4aSElliott Hughes        )
1219*e1fe3e4aSElliott Hughes        self.assertEqual(
1220*e1fe3e4aSElliott Hughes            (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component),
1221*e1fe3e4aSElliott Hughes            ("top", 120, "a", 2),
1222*e1fe3e4aSElliott Hughes        )
1223*e1fe3e4aSElliott Hughes
1224*e1fe3e4aSElliott Hughes    def test_def_anchor_duplicate(self):
1225*e1fe3e4aSElliott Hughes        self.assertRaisesRegex(
1226*e1fe3e4aSElliott Hughes            VoltLibError,
1227*e1fe3e4aSElliott Hughes            'Anchor "dupe" already defined, ' "anchor names are case insensitive",
1228*e1fe3e4aSElliott Hughes            self.parse,
1229*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "dupe" ON 120 GLYPH a '
1230*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 250 DY 450 END_POS END_ANCHOR\n"
1231*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "dupe" ON 120 GLYPH a '
1232*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 250 DY 450 END_POS END_ANCHOR",
1233*e1fe3e4aSElliott Hughes        )
1234*e1fe3e4aSElliott Hughes
1235*e1fe3e4aSElliott Hughes    def test_def_anchor_locked(self):
1236*e1fe3e4aSElliott Hughes        [anchor] = self.parse(
1237*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "top" ON 120 GLYPH a '
1238*e1fe3e4aSElliott Hughes            "COMPONENT 1 LOCKED AT  POS DX 250 DY 450 END_POS END_ANCHOR"
1239*e1fe3e4aSElliott Hughes        ).statements
1240*e1fe3e4aSElliott Hughes        self.assertEqual(
1241*e1fe3e4aSElliott Hughes            (
1242*e1fe3e4aSElliott Hughes                anchor.name,
1243*e1fe3e4aSElliott Hughes                anchor.gid,
1244*e1fe3e4aSElliott Hughes                anchor.glyph_name,
1245*e1fe3e4aSElliott Hughes                anchor.component,
1246*e1fe3e4aSElliott Hughes                anchor.locked,
1247*e1fe3e4aSElliott Hughes                anchor.pos,
1248*e1fe3e4aSElliott Hughes            ),
1249*e1fe3e4aSElliott Hughes            ("top", 120, "a", 1, True, (None, 250, 450, {}, {}, {})),
1250*e1fe3e4aSElliott Hughes        )
1251*e1fe3e4aSElliott Hughes
1252*e1fe3e4aSElliott Hughes    def test_anchor_adjust_device(self):
1253*e1fe3e4aSElliott Hughes        [anchor] = self.parse(
1254*e1fe3e4aSElliott Hughes            'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph '
1255*e1fe3e4aSElliott Hughes            "COMPONENT 1 AT  POS DX 0 DY 456 ADJUST_BY 12 AT 34 "
1256*e1fe3e4aSElliott Hughes            "ADJUST_BY 56 AT 78 END_POS END_ANCHOR"
1257*e1fe3e4aSElliott Hughes        ).statements
1258*e1fe3e4aSElliott Hughes        self.assertEqual(
1259*e1fe3e4aSElliott Hughes            (anchor.name, anchor.pos),
1260*e1fe3e4aSElliott Hughes            ("MARK_top", (None, 0, 456, {}, {}, {34: 12, 78: 56})),
1261*e1fe3e4aSElliott Hughes        )
1262*e1fe3e4aSElliott Hughes
1263*e1fe3e4aSElliott Hughes    def test_ppem(self):
1264*e1fe3e4aSElliott Hughes        [grid_ppem, pres_ppem, ppos_ppem] = self.parse(
1265*e1fe3e4aSElliott Hughes            "GRID_PPEM 20\n" "PRESENTATION_PPEM 72\n" "PPOSITIONING_PPEM 144"
1266*e1fe3e4aSElliott Hughes        ).statements
1267*e1fe3e4aSElliott Hughes        self.assertEqual(
1268*e1fe3e4aSElliott Hughes            (
1269*e1fe3e4aSElliott Hughes                (grid_ppem.name, grid_ppem.value),
1270*e1fe3e4aSElliott Hughes                (pres_ppem.name, pres_ppem.value),
1271*e1fe3e4aSElliott Hughes                (ppos_ppem.name, ppos_ppem.value),
1272*e1fe3e4aSElliott Hughes            ),
1273*e1fe3e4aSElliott Hughes            (("GRID_PPEM", 20), ("PRESENTATION_PPEM", 72), ("PPOSITIONING_PPEM", 144)),
1274*e1fe3e4aSElliott Hughes        )
1275*e1fe3e4aSElliott Hughes
1276*e1fe3e4aSElliott Hughes    def test_compiler_flags(self):
1277*e1fe3e4aSElliott Hughes        [setting1, setting2] = self.parse(
1278*e1fe3e4aSElliott Hughes            "COMPILER_USEEXTENSIONLOOKUPS\n" "COMPILER_USEPAIRPOSFORMAT2"
1279*e1fe3e4aSElliott Hughes        ).statements
1280*e1fe3e4aSElliott Hughes        self.assertEqual(
1281*e1fe3e4aSElliott Hughes            ((setting1.name, setting1.value), (setting2.name, setting2.value)),
1282*e1fe3e4aSElliott Hughes            (
1283*e1fe3e4aSElliott Hughes                ("COMPILER_USEEXTENSIONLOOKUPS", True),
1284*e1fe3e4aSElliott Hughes                ("COMPILER_USEPAIRPOSFORMAT2", True),
1285*e1fe3e4aSElliott Hughes            ),
1286*e1fe3e4aSElliott Hughes        )
1287*e1fe3e4aSElliott Hughes
1288*e1fe3e4aSElliott Hughes    def test_cmap(self):
1289*e1fe3e4aSElliott Hughes        [cmap_format1, cmap_format2, cmap_format3] = self.parse(
1290*e1fe3e4aSElliott Hughes            "CMAP_FORMAT 0 3 4\n" "CMAP_FORMAT 1 0 6\n" "CMAP_FORMAT 3 1 4"
1291*e1fe3e4aSElliott Hughes        ).statements
1292*e1fe3e4aSElliott Hughes        self.assertEqual(
1293*e1fe3e4aSElliott Hughes            (
1294*e1fe3e4aSElliott Hughes                (cmap_format1.name, cmap_format1.value),
1295*e1fe3e4aSElliott Hughes                (cmap_format2.name, cmap_format2.value),
1296*e1fe3e4aSElliott Hughes                (cmap_format3.name, cmap_format3.value),
1297*e1fe3e4aSElliott Hughes            ),
1298*e1fe3e4aSElliott Hughes            (
1299*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (0, 3, 4)),
1300*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (1, 0, 6)),
1301*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (3, 1, 4)),
1302*e1fe3e4aSElliott Hughes            ),
1303*e1fe3e4aSElliott Hughes        )
1304*e1fe3e4aSElliott Hughes
1305*e1fe3e4aSElliott Hughes    def test_do_not_touch_cmap(self):
1306*e1fe3e4aSElliott Hughes        [option1, option2, option3, option4] = self.parse(
1307*e1fe3e4aSElliott Hughes            "DO_NOT_TOUCH_CMAP\n"
1308*e1fe3e4aSElliott Hughes            "CMAP_FORMAT 0 3 4\n"
1309*e1fe3e4aSElliott Hughes            "CMAP_FORMAT 1 0 6\n"
1310*e1fe3e4aSElliott Hughes            "CMAP_FORMAT 3 1 4"
1311*e1fe3e4aSElliott Hughes        ).statements
1312*e1fe3e4aSElliott Hughes        self.assertEqual(
1313*e1fe3e4aSElliott Hughes            (
1314*e1fe3e4aSElliott Hughes                (option1.name, option1.value),
1315*e1fe3e4aSElliott Hughes                (option2.name, option2.value),
1316*e1fe3e4aSElliott Hughes                (option3.name, option3.value),
1317*e1fe3e4aSElliott Hughes                (option4.name, option4.value),
1318*e1fe3e4aSElliott Hughes            ),
1319*e1fe3e4aSElliott Hughes            (
1320*e1fe3e4aSElliott Hughes                ("DO_NOT_TOUCH_CMAP", True),
1321*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (0, 3, 4)),
1322*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (1, 0, 6)),
1323*e1fe3e4aSElliott Hughes                ("CMAP_FORMAT", (3, 1, 4)),
1324*e1fe3e4aSElliott Hughes            ),
1325*e1fe3e4aSElliott Hughes        )
1326*e1fe3e4aSElliott Hughes
1327*e1fe3e4aSElliott Hughes    def test_stop_at_end(self):
1328*e1fe3e4aSElliott Hughes        doc = self.parse_('DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0')
1329*e1fe3e4aSElliott Hughes        [def_glyph] = doc.statements
1330*e1fe3e4aSElliott Hughes        self.assertEqual(
1331*e1fe3e4aSElliott Hughes            (
1332*e1fe3e4aSElliott Hughes                def_glyph.name,
1333*e1fe3e4aSElliott Hughes                def_glyph.id,
1334*e1fe3e4aSElliott Hughes                def_glyph.unicode,
1335*e1fe3e4aSElliott Hughes                def_glyph.type,
1336*e1fe3e4aSElliott Hughes                def_glyph.components,
1337*e1fe3e4aSElliott Hughes            ),
1338*e1fe3e4aSElliott Hughes            (".notdef", 0, None, "BASE", None),
1339*e1fe3e4aSElliott Hughes        )
1340*e1fe3e4aSElliott Hughes        self.assertEqual(
1341*e1fe3e4aSElliott Hughes            str(doc), '\nDEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\n'
1342*e1fe3e4aSElliott Hughes        )
1343*e1fe3e4aSElliott Hughes
1344*e1fe3e4aSElliott Hughes    def parse_(self, text):
1345*e1fe3e4aSElliott Hughes        return Parser(StringIO(text)).parse()
1346*e1fe3e4aSElliott Hughes
1347*e1fe3e4aSElliott Hughes    def parse(self, text):
1348*e1fe3e4aSElliott Hughes        doc = self.parse_(text)
1349*e1fe3e4aSElliott Hughes        self.assertEqual("\n".join(str(s) for s in doc.statements), text)
1350*e1fe3e4aSElliott Hughes        return Parser(StringIO(text)).parse()
1351*e1fe3e4aSElliott Hughes
1352*e1fe3e4aSElliott Hughes
1353*e1fe3e4aSElliott Hughesif __name__ == "__main__":
1354*e1fe3e4aSElliott Hughes    import sys
1355*e1fe3e4aSElliott Hughes
1356*e1fe3e4aSElliott Hughes    sys.exit(unittest.main())
1357