xref: /aosp_15_r20/external/fonttools/Tests/varLib/interpolate_layout_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.ttLib import TTFont
2from fontTools.varLib import build
3from fontTools.varLib.interpolate_layout import interpolate_layout
4from fontTools.varLib.interpolate_layout import main as interpolate_layout_main
5from fontTools.designspaceLib import DesignSpaceDocument, DesignSpaceDocumentError
6from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
7import difflib
8import os
9import shutil
10import sys
11import tempfile
12import unittest
13
14
15class InterpolateLayoutTest(unittest.TestCase):
16    def __init__(self, methodName):
17        unittest.TestCase.__init__(self, methodName)
18        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
19        # and fires deprecation warnings if a program uses the old name.
20        if not hasattr(self, "assertRaisesRegex"):
21            self.assertRaisesRegex = self.assertRaisesRegexp
22
23    def setUp(self):
24        self.tempdir = None
25        self.num_tempfiles = 0
26
27    def tearDown(self):
28        if self.tempdir:
29            shutil.rmtree(self.tempdir)
30
31    @staticmethod
32    def get_test_input(test_file_or_folder):
33        path, _ = os.path.split(__file__)
34        return os.path.join(path, "data", test_file_or_folder)
35
36    @staticmethod
37    def get_test_output(test_file_or_folder):
38        path, _ = os.path.split(__file__)
39        return os.path.join(path, "data", "test_results", test_file_or_folder)
40
41    @staticmethod
42    def get_file_list(folder, suffix, prefix=""):
43        all_files = os.listdir(folder)
44        file_list = []
45        for p in all_files:
46            if p.startswith(prefix) and p.endswith(suffix):
47                file_list.append(os.path.abspath(os.path.join(folder, p)))
48        return file_list
49
50    def temp_path(self, suffix):
51        self.temp_dir()
52        self.num_tempfiles += 1
53        return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
54
55    def temp_dir(self):
56        if not self.tempdir:
57            self.tempdir = tempfile.mkdtemp()
58
59    def read_ttx(self, path):
60        lines = []
61        with open(path, "r", encoding="utf-8") as ttx:
62            for line in ttx.readlines():
63                # Elide ttFont attributes because ttLibVersion may change.
64                if line.startswith("<ttFont "):
65                    lines.append("<ttFont>\n")
66                else:
67                    lines.append(line.rstrip() + "\n")
68        return lines
69
70    def expect_ttx(self, font, expected_ttx, tables):
71        path = self.temp_path(suffix=".ttx")
72        font.saveXML(path, tables=tables)
73        actual = self.read_ttx(path)
74        expected = self.read_ttx(expected_ttx)
75        if actual != expected:
76            for line in difflib.unified_diff(
77                expected, actual, fromfile=expected_ttx, tofile=path
78            ):
79                sys.stdout.write(line)
80            self.fail("TTX output is different from expected")
81
82    def check_ttx_dump(self, font, expected_ttx, tables, suffix):
83        """Ensure the TTX dump is the same after saving and reloading the font."""
84        path = self.temp_path(suffix=suffix)
85        font.save(path)
86        self.expect_ttx(TTFont(path), expected_ttx, tables)
87
88    def compile_font(self, path, suffix, temp_dir, features=None, cfg=None):
89        ttx_filename = os.path.basename(path)
90        savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
91        font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
92        if cfg:
93            font.cfg.update(cfg)
94        font.importXML(path)
95        if features:
96            addOpenTypeFeaturesFromString(font, features)
97        font.save(savepath, reorderTables=None)
98        return font, savepath
99
100    # -----
101    # Tests
102    # -----
103
104    def test_varlib_interpolate_layout_GSUB_only_ttf(self):
105        """Only GSUB, and only in the base master.
106
107        The variable font will inherit the GSUB table from the
108        base master.
109        """
110        suffix = ".ttf"
111        ds_path = self.get_test_input("InterpolateLayout.designspace")
112        ufo_dir = self.get_test_input("master_ufo")
113        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
114
115        self.temp_dir()
116        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
117        for path in ttx_paths:
118            self.compile_font(path, suffix, self.tempdir)
119
120        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
121        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
122
123        tables = ["GSUB"]
124        expected_ttx_path = self.get_test_output("InterpolateLayout.ttx")
125        self.expect_ttx(instfont, expected_ttx_path, tables)
126        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
127
128    def test_varlib_interpolate_layout_no_GSUB_ttf(self):
129        """The base master has no GSUB table.
130
131        The variable font will end up without a GSUB table.
132        """
133        suffix = ".ttf"
134        ds_path = self.get_test_input("InterpolateLayout2.designspace")
135        ufo_dir = self.get_test_input("master_ufo")
136        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
137
138        self.temp_dir()
139        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
140        for path in ttx_paths:
141            self.compile_font(path, suffix, self.tempdir)
142
143        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
144        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
145
146        tables = ["GSUB"]
147        expected_ttx_path = self.get_test_output("InterpolateLayout2.ttx")
148        self.expect_ttx(instfont, expected_ttx_path, tables)
149        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
150
151    def test_varlib_interpolate_layout_GSUB_only_no_axes_ttf(self):
152        """Only GSUB, and only in the base master.
153        Designspace file has no <axes> element.
154
155        The variable font will inherit the GSUB table from the
156        base master.
157        """
158        ds_path = self.get_test_input("InterpolateLayout3.designspace")
159        with self.assertRaisesRegex(DesignSpaceDocumentError, "No axes defined"):
160            instfont = interpolate_layout(ds_path, {"weight": 500})
161
162    def test_varlib_interpolate_layout_GPOS_only_size_feat_same_val_ttf(self):
163        """Only GPOS; 'size' feature; same values in all masters."""
164        suffix = ".ttf"
165        ds_path = self.get_test_input("InterpolateLayout.designspace")
166        ufo_dir = self.get_test_input("master_ufo")
167        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
168
169        fea_str = """
170        feature size {
171            parameters 10.0 0;
172        } size;
173        """
174        features = [fea_str] * 2
175
176        self.temp_dir()
177        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
178        for i, path in enumerate(ttx_paths):
179            self.compile_font(path, suffix, self.tempdir, features[i])
180
181        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
182        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
183
184        tables = ["GPOS"]
185        expected_ttx_path = self.get_test_output(
186            "InterpolateLayoutGPOS_size_feat_same.ttx"
187        )
188        self.expect_ttx(instfont, expected_ttx_path, tables)
189        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
190
191    def test_varlib_interpolate_layout_GPOS_only_LookupType_1_same_val_ttf(self):
192        """Only GPOS; LookupType 1; same values in all masters."""
193        suffix = ".ttf"
194        ds_path = self.get_test_input("InterpolateLayout.designspace")
195        ufo_dir = self.get_test_input("master_ufo")
196        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
197
198        fea_str = """
199        feature xxxx {
200            pos A <-80 0 -160 0>;
201        } xxxx;
202        """
203        features = [fea_str] * 2
204
205        self.temp_dir()
206        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
207        for i, path in enumerate(ttx_paths):
208            self.compile_font(path, suffix, self.tempdir, features[i])
209
210        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
211        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
212
213        tables = ["GPOS"]
214        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_same.ttx")
215        self.expect_ttx(instfont, expected_ttx_path, tables)
216        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
217
218    def test_varlib_interpolate_layout_GPOS_only_LookupType_1_diff_val_ttf(self):
219        """Only GPOS; LookupType 1; different values in each master."""
220        suffix = ".ttf"
221        ds_path = self.get_test_input("InterpolateLayout.designspace")
222        ufo_dir = self.get_test_input("master_ufo")
223        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
224
225        fea_str_0 = """
226        feature xxxx {
227            pos A <-80 0 -160 0>;
228        } xxxx;
229        """
230        fea_str_1 = """
231        feature xxxx {
232            pos A <-97 0 -195 0>;
233        } xxxx;
234        """
235        features = [fea_str_0, fea_str_1]
236
237        self.temp_dir()
238        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
239        for i, path in enumerate(ttx_paths):
240            self.compile_font(path, suffix, self.tempdir, features[i])
241
242        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
243        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
244
245        tables = ["GPOS"]
246        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_diff.ttx")
247        self.expect_ttx(instfont, expected_ttx_path, tables)
248        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
249
250    def test_varlib_interpolate_layout_GPOS_only_LookupType_1_diff2_val_ttf(self):
251        """Only GPOS; LookupType 1; different values and items in each master."""
252        suffix = ".ttf"
253        ds_path = self.get_test_input("InterpolateLayout.designspace")
254        ufo_dir = self.get_test_input("master_ufo")
255        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
256
257        fea_str_0 = """
258        feature xxxx {
259            pos A <-80 0 -160 0>;
260            pos a <-55 0 -105 0>;
261        } xxxx;
262        """
263        fea_str_1 = """
264        feature xxxx {
265            pos A <-97 0 -195 0>;
266        } xxxx;
267        """
268        features = [fea_str_0, fea_str_1]
269
270        self.temp_dir()
271        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
272        for i, path in enumerate(ttx_paths):
273            self.compile_font(path, suffix, self.tempdir, features[i])
274
275        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
276        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
277
278        tables = ["GPOS"]
279        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_diff2.ttx")
280        self.expect_ttx(instfont, expected_ttx_path, tables)
281        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
282
283    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_same_val_ttf(
284        self,
285    ):
286        """Only GPOS; LookupType 2 specific pairs; same values in all masters."""
287        suffix = ".ttf"
288        ds_path = self.get_test_input("InterpolateLayout.designspace")
289        ufo_dir = self.get_test_input("master_ufo")
290        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
291
292        fea_str = """
293        feature xxxx {
294            pos A a -53;
295        } xxxx;
296        """
297        features = [fea_str] * 2
298
299        self.temp_dir()
300        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
301        for i, path in enumerate(ttx_paths):
302            self.compile_font(path, suffix, self.tempdir, features[i])
303
304        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
305        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
306
307        tables = ["GPOS"]
308        expected_ttx_path = self.get_test_output(
309            "InterpolateLayoutGPOS_2_spec_same.ttx"
310        )
311        self.expect_ttx(instfont, expected_ttx_path, tables)
312        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
313
314    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_diff_val_ttf(
315        self,
316    ):
317        """Only GPOS; LookupType 2 specific pairs; different values in each master."""
318        suffix = ".ttf"
319        ds_path = self.get_test_input("InterpolateLayout.designspace")
320        ufo_dir = self.get_test_input("master_ufo")
321        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
322
323        fea_str_0 = """
324        feature xxxx {
325            pos A a -53;
326        } xxxx;
327        """
328        fea_str_1 = """
329        feature xxxx {
330            pos A a -27;
331        } xxxx;
332        """
333        features = [fea_str_0, fea_str_1]
334
335        self.temp_dir()
336        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
337        for i, path in enumerate(ttx_paths):
338            self.compile_font(path, suffix, self.tempdir, features[i])
339
340        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
341        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
342
343        tables = ["GPOS"]
344        expected_ttx_path = self.get_test_output(
345            "InterpolateLayoutGPOS_2_spec_diff.ttx"
346        )
347        self.expect_ttx(instfont, expected_ttx_path, tables)
348        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
349
350    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_diff2_val_ttf(
351        self,
352    ):
353        """Only GPOS; LookupType 2 specific pairs; different values and items in each master."""
354        suffix = ".ttf"
355        ds_path = self.get_test_input("InterpolateLayout.designspace")
356        ufo_dir = self.get_test_input("master_ufo")
357        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
358
359        fea_str_0 = """
360        feature xxxx {
361            pos A a -53;
362        } xxxx;
363        """
364        fea_str_1 = """
365        feature xxxx {
366            pos A a -27;
367            pos a a 19;
368        } xxxx;
369        """
370        features = [fea_str_0, fea_str_1]
371
372        self.temp_dir()
373        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
374        for i, path in enumerate(ttx_paths):
375            self.compile_font(path, suffix, self.tempdir, features[i])
376
377        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
378        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
379
380        tables = ["GPOS"]
381        expected_ttx_path = self.get_test_output(
382            "InterpolateLayoutGPOS_2_spec_diff2.ttx"
383        )
384        self.expect_ttx(instfont, expected_ttx_path, tables)
385        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
386
387    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_same_val_ttf(
388        self,
389    ):
390        """Only GPOS; LookupType 2 class pairs; same values in all masters."""
391        suffix = ".ttf"
392        ds_path = self.get_test_input("InterpolateLayout.designspace")
393        ufo_dir = self.get_test_input("master_ufo")
394        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
395
396        fea_str = """
397        feature xxxx {
398            pos [A] [a] -53;
399        } xxxx;
400        """
401        features = [fea_str] * 2
402
403        self.temp_dir()
404        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
405        for i, path in enumerate(ttx_paths):
406            self.compile_font(path, suffix, self.tempdir, features[i])
407
408        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
409        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
410
411        tables = ["GPOS"]
412        expected_ttx_path = self.get_test_output(
413            "InterpolateLayoutGPOS_2_class_same.ttx"
414        )
415        self.expect_ttx(instfont, expected_ttx_path, tables)
416        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
417
418    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_diff_val_ttf(
419        self,
420    ):
421        """Only GPOS; LookupType 2 class pairs; different values in each master."""
422        suffix = ".ttf"
423        ds_path = self.get_test_input("InterpolateLayout.designspace")
424        ufo_dir = self.get_test_input("master_ufo")
425        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
426
427        fea_str_0 = """
428        feature xxxx {
429            pos [A] [a] -53;
430        } xxxx;
431        """
432        fea_str_1 = """
433        feature xxxx {
434            pos [A] [a] -27;
435        } xxxx;
436        """
437        features = [fea_str_0, fea_str_1]
438
439        self.temp_dir()
440        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
441        for i, path in enumerate(ttx_paths):
442            self.compile_font(path, suffix, self.tempdir, features[i])
443
444        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
445        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
446
447        tables = ["GPOS"]
448        expected_ttx_path = self.get_test_output(
449            "InterpolateLayoutGPOS_2_class_diff.ttx"
450        )
451        self.expect_ttx(instfont, expected_ttx_path, tables)
452        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
453
454    def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_diff2_val_ttf(
455        self,
456    ):
457        """Only GPOS; LookupType 2 class pairs; different values and items in each master."""
458        suffix = ".ttf"
459        ds_path = self.get_test_input("InterpolateLayout.designspace")
460        ufo_dir = self.get_test_input("master_ufo")
461        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
462
463        fea_str_0 = """
464        feature xxxx {
465            pos [A] [a] -53;
466        } xxxx;
467        """
468        fea_str_1 = """
469        feature xxxx {
470            pos [A] [a] -27;
471            pos [a] [a] 19;
472        } xxxx;
473        """
474        features = [fea_str_0, fea_str_1]
475
476        self.temp_dir()
477        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
478        for i, path in enumerate(ttx_paths):
479            self.compile_font(path, suffix, self.tempdir, features[i])
480
481        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
482        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
483
484        tables = ["GPOS"]
485        expected_ttx_path = self.get_test_output(
486            "InterpolateLayoutGPOS_2_class_diff2.ttx"
487        )
488        self.expect_ttx(instfont, expected_ttx_path, tables)
489        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
490
491    def test_varlib_interpolate_layout_GPOS_only_LookupType_3_same_val_ttf(self):
492        """Only GPOS; LookupType 3; same values in all masters."""
493        suffix = ".ttf"
494        ds_path = self.get_test_input("InterpolateLayout.designspace")
495        ufo_dir = self.get_test_input("master_ufo")
496        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
497
498        fea_str = """
499        feature xxxx {
500            pos cursive a <anchor 60 15> <anchor 405 310>;
501        } xxxx;
502        """
503        features = [fea_str] * 2
504
505        self.temp_dir()
506        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
507        for i, path in enumerate(ttx_paths):
508            self.compile_font(path, suffix, self.tempdir, features[i])
509
510        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
511        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
512
513        tables = ["GPOS"]
514        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_3_same.ttx")
515        self.expect_ttx(instfont, expected_ttx_path, tables)
516        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
517
518    def test_varlib_interpolate_layout_GPOS_only_LookupType_3_diff_val_ttf(self):
519        """Only GPOS; LookupType 3; different values in each master."""
520        suffix = ".ttf"
521        ds_path = self.get_test_input("InterpolateLayout.designspace")
522        ufo_dir = self.get_test_input("master_ufo")
523        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
524
525        fea_str_0 = """
526        feature xxxx {
527            pos cursive a <anchor 60 15> <anchor 405 310>;
528        } xxxx;
529        """
530        fea_str_1 = """
531        feature xxxx {
532            pos cursive a <anchor 38 42> <anchor 483 279>;
533        } xxxx;
534        """
535        features = [fea_str_0, fea_str_1]
536
537        self.temp_dir()
538        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
539        for i, path in enumerate(ttx_paths):
540            self.compile_font(path, suffix, self.tempdir, features[i])
541
542        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
543        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
544
545        tables = ["GPOS"]
546        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_3_diff.ttx")
547        self.expect_ttx(instfont, expected_ttx_path, tables)
548        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
549
550    def test_varlib_interpolate_layout_GPOS_only_LookupType_4_same_val_ttf(self):
551        """Only GPOS; LookupType 4; same values in all masters."""
552        suffix = ".ttf"
553        ds_path = self.get_test_input("InterpolateLayout.designspace")
554        ufo_dir = self.get_test_input("master_ufo")
555        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
556
557        fea_str = """
558        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
559        feature xxxx {
560            pos base a <anchor 260 500> mark @MARKS_ABOVE;
561        } xxxx;
562        """
563        features = [fea_str] * 2
564
565        self.temp_dir()
566        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
567        for i, path in enumerate(ttx_paths):
568            self.compile_font(path, suffix, self.tempdir, features[i])
569
570        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
571        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
572
573        tables = ["GPOS"]
574        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_4_same.ttx")
575        self.expect_ttx(instfont, expected_ttx_path, tables)
576        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
577
578    def test_varlib_interpolate_layout_GPOS_only_LookupType_4_diff_val_ttf(self):
579        """Only GPOS; LookupType 4; different values in each master."""
580        suffix = ".ttf"
581        ds_path = self.get_test_input("InterpolateLayout.designspace")
582        ufo_dir = self.get_test_input("master_ufo")
583        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
584
585        fea_str_0 = """
586        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
587        feature xxxx {
588            pos base a <anchor 260 500> mark @MARKS_ABOVE;
589        } xxxx;
590        """
591        fea_str_1 = """
592        markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
593        feature xxxx {
594            pos base a <anchor 285 520> mark @MARKS_ABOVE;
595        } xxxx;
596        """
597        features = [fea_str_0, fea_str_1]
598
599        self.temp_dir()
600        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
601        for i, path in enumerate(ttx_paths):
602            self.compile_font(path, suffix, self.tempdir, features[i])
603
604        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
605        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
606
607        tables = ["GPOS"]
608        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_4_diff.ttx")
609        self.expect_ttx(instfont, expected_ttx_path, tables)
610        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
611
612    def test_varlib_interpolate_layout_GPOS_only_LookupType_5_same_val_ttf(self):
613        """Only GPOS; LookupType 5; same values in all masters."""
614        suffix = ".ttf"
615        ds_path = self.get_test_input("InterpolateLayout.designspace")
616        ufo_dir = self.get_test_input("master_ufo")
617        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
618
619        fea_str = """
620        markClass uni0330 <anchor 0 -50> @MARKS_BELOW;
621        feature xxxx {
622            pos ligature f_t <anchor 115 -50> mark @MARKS_BELOW
623                ligComponent <anchor 430 -50> mark @MARKS_BELOW;
624        } xxxx;
625        """
626        features = [fea_str] * 2
627
628        self.temp_dir()
629        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
630        for i, path in enumerate(ttx_paths):
631            self.compile_font(path, suffix, self.tempdir, features[i])
632
633        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
634        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
635
636        tables = ["GPOS"]
637        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_5_same.ttx")
638        self.expect_ttx(instfont, expected_ttx_path, tables)
639        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
640
641    def test_varlib_interpolate_layout_GPOS_only_LookupType_5_diff_val_ttf(self):
642        """Only GPOS; LookupType 5; different values in each master."""
643        suffix = ".ttf"
644        ds_path = self.get_test_input("InterpolateLayout.designspace")
645        ufo_dir = self.get_test_input("master_ufo")
646        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
647
648        fea_str_0 = """
649        markClass uni0330 <anchor 0 -50> @MARKS_BELOW;
650        feature xxxx {
651            pos ligature f_t <anchor 115 -50> mark @MARKS_BELOW
652                ligComponent <anchor 430 -50> mark @MARKS_BELOW;
653        } xxxx;
654        """
655        fea_str_1 = """
656        markClass uni0330 <anchor 0 -20> @MARKS_BELOW;
657        feature xxxx {
658            pos ligature f_t <anchor 173 -20> mark @MARKS_BELOW
659                ligComponent <anchor 577 -20> mark @MARKS_BELOW;
660        } xxxx;
661        """
662        features = [fea_str_0, fea_str_1]
663
664        self.temp_dir()
665        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
666        for i, path in enumerate(ttx_paths):
667            self.compile_font(path, suffix, self.tempdir, features[i])
668
669        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
670        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
671
672        tables = ["GPOS"]
673        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_5_diff.ttx")
674        self.expect_ttx(instfont, expected_ttx_path, tables)
675        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
676
677    def test_varlib_interpolate_layout_GPOS_only_LookupType_6_same_val_ttf(self):
678        """Only GPOS; LookupType 6; same values in all masters."""
679        suffix = ".ttf"
680        ds_path = self.get_test_input("InterpolateLayout.designspace")
681        ufo_dir = self.get_test_input("master_ufo")
682        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
683
684        fea_str = """
685        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
686        feature xxxx {
687            pos mark uni0308 <anchor 0 675> mark @MARKS_ABOVE;
688        } xxxx;
689        """
690        features = [fea_str] * 2
691
692        self.temp_dir()
693        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
694        for i, path in enumerate(ttx_paths):
695            self.compile_font(path, suffix, self.tempdir, features[i])
696
697        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
698        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
699
700        tables = ["GPOS"]
701        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_6_same.ttx")
702        self.expect_ttx(instfont, expected_ttx_path, tables)
703        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
704
705    def test_varlib_interpolate_layout_GPOS_only_LookupType_6_diff_val_ttf(self):
706        """Only GPOS; LookupType 6; different values in each master."""
707        suffix = ".ttf"
708        ds_path = self.get_test_input("InterpolateLayout.designspace")
709        ufo_dir = self.get_test_input("master_ufo")
710        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
711
712        fea_str_0 = """
713        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
714        feature xxxx {
715            pos mark uni0308 <anchor 0 675> mark @MARKS_ABOVE;
716        } xxxx;
717        """
718        fea_str_1 = """
719        markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
720        feature xxxx {
721            pos mark uni0308 <anchor 0 730> mark @MARKS_ABOVE;
722        } xxxx;
723        """
724        features = [fea_str_0, fea_str_1]
725
726        self.temp_dir()
727        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
728        for i, path in enumerate(ttx_paths):
729            self.compile_font(path, suffix, self.tempdir, features[i])
730
731        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
732        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
733
734        tables = ["GPOS"]
735        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_6_diff.ttx")
736        self.expect_ttx(instfont, expected_ttx_path, tables)
737        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
738
739    def test_varlib_interpolate_layout_GPOS_only_LookupType_7_same_val_ttf(self):
740        """Only GPOS; LookupType 7; same values in all masters."""
741        suffix = ".ttf"
742        ds_path = self.get_test_input("InterpolateLayout.designspace")
743        ufo_dir = self.get_test_input("master_ufo")
744        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
745
746        fea_str = """
747        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
748        lookup CNTXT_PAIR_POS {
749            pos A a -23;
750        } CNTXT_PAIR_POS;
751
752        lookup CNTXT_MARK_TO_BASE {
753            pos base a <anchor 260 500> mark @MARKS_ABOVE;
754        } CNTXT_MARK_TO_BASE;
755
756        feature xxxx {
757            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
758        } xxxx;
759        """
760        features = [fea_str] * 2
761
762        self.temp_dir()
763        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
764        cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
765        for i, path in enumerate(ttx_paths):
766            self.compile_font(path, suffix, self.tempdir, features[i], cfg)
767
768        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
769        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
770
771        tables = ["GPOS"]
772        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_7_same.ttx")
773        self.expect_ttx(instfont, expected_ttx_path, tables)
774        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
775
776    def test_varlib_interpolate_layout_GPOS_only_LookupType_7_diff_val_ttf(self):
777        """Only GPOS; LookupType 7; different values in each master."""
778        suffix = ".ttf"
779        ds_path = self.get_test_input("InterpolateLayout.designspace")
780        ufo_dir = self.get_test_input("master_ufo")
781        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
782
783        fea_str_0 = """
784        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
785        lookup CNTXT_PAIR_POS {
786            pos A a -23;
787        } CNTXT_PAIR_POS;
788
789        lookup CNTXT_MARK_TO_BASE {
790            pos base a <anchor 260 500> mark @MARKS_ABOVE;
791        } CNTXT_MARK_TO_BASE;
792
793        feature xxxx {
794            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
795        } xxxx;
796        """
797        fea_str_1 = """
798        markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
799        lookup CNTXT_PAIR_POS {
800            pos A a 57;
801        } CNTXT_PAIR_POS;
802
803        lookup CNTXT_MARK_TO_BASE {
804            pos base a <anchor 285 520> mark @MARKS_ABOVE;
805        } CNTXT_MARK_TO_BASE;
806
807        feature xxxx {
808            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
809        } xxxx;
810        """
811        features = [fea_str_0, fea_str_1]
812
813        self.temp_dir()
814        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
815        cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
816        for i, path in enumerate(ttx_paths):
817            self.compile_font(path, suffix, self.tempdir, features[i], cfg)
818
819        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
820        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
821
822        tables = ["GPOS"]
823        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_7_diff.ttx")
824        self.expect_ttx(instfont, expected_ttx_path, tables)
825        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
826
827    def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
828        """Only GPOS; LookupType 8; same values in all masters."""
829        suffix = ".ttf"
830        ds_path = self.get_test_input("InterpolateLayout.designspace")
831        ufo_dir = self.get_test_input("master_ufo")
832        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
833
834        fea_str = """
835        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
836        lookup CNTXT_PAIR_POS {
837            pos A a -23;
838        } CNTXT_PAIR_POS;
839
840        lookup CNTXT_MARK_TO_BASE {
841            pos base a <anchor 260 500> mark @MARKS_ABOVE;
842        } CNTXT_MARK_TO_BASE;
843
844        feature xxxx {
845            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
846        } xxxx;
847        """
848        features = [fea_str] * 2
849
850        self.temp_dir()
851        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
852        for i, path in enumerate(ttx_paths):
853            self.compile_font(path, suffix, self.tempdir, features[i])
854
855        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
856        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
857
858        tables = ["GPOS"]
859        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_8_same.ttx")
860        self.expect_ttx(instfont, expected_ttx_path, tables)
861        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
862
863    def test_varlib_interpolate_layout_GPOS_only_LookupType_8_diff_val_ttf(self):
864        """Only GPOS; LookupType 8; different values in each master."""
865        suffix = ".ttf"
866        ds_path = self.get_test_input("InterpolateLayout.designspace")
867        ufo_dir = self.get_test_input("master_ufo")
868        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
869
870        fea_str_0 = """
871        markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
872        lookup CNTXT_PAIR_POS {
873            pos A a -23;
874        } CNTXT_PAIR_POS;
875
876        lookup CNTXT_MARK_TO_BASE {
877            pos base a <anchor 260 500> mark @MARKS_ABOVE;
878        } CNTXT_MARK_TO_BASE;
879
880        feature xxxx {
881            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
882        } xxxx;
883        """
884        fea_str_1 = """
885        markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
886        lookup CNTXT_PAIR_POS {
887            pos A a 57;
888        } CNTXT_PAIR_POS;
889
890        lookup CNTXT_MARK_TO_BASE {
891            pos base a <anchor 285 520> mark @MARKS_ABOVE;
892        } CNTXT_MARK_TO_BASE;
893
894        feature xxxx {
895            pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
896        } xxxx;
897        """
898        features = [fea_str_0, fea_str_1]
899
900        self.temp_dir()
901        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
902        for i, path in enumerate(ttx_paths):
903            self.compile_font(path, suffix, self.tempdir, features[i])
904
905        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
906        instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
907
908        tables = ["GPOS"]
909        expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_8_diff.ttx")
910        self.expect_ttx(instfont, expected_ttx_path, tables)
911        self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
912
913    def test_varlib_interpolate_layout_main_ttf(self):
914        """Mostly for testing varLib.interpolate_layout.main()"""
915        suffix = ".ttf"
916        ds_path = self.get_test_input("Build.designspace")
917        ufo_dir = self.get_test_input("master_ufo")
918        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
919
920        self.temp_dir()
921        ttf_dir = os.path.join(self.tempdir, "master_ttf_interpolatable")
922        os.makedirs(ttf_dir)
923        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
924        for path in ttx_paths:
925            self.compile_font(path, suffix, ttf_dir)
926
927        finder = lambda s: s.replace(ufo_dir, ttf_dir).replace(".ufo", suffix)
928        varfont, _, _ = build(ds_path, finder)
929        varfont_name = "InterpolateLayoutMain"
930        varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
931        varfont.save(varfont_path)
932
933        ds_copy = os.path.splitext(varfont_path)[0] + ".designspace"
934        shutil.copy2(ds_path, ds_copy)
935        args = [ds_copy, "weight=500", "contrast=50"]
936        interpolate_layout_main(args)
937
938        instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix
939        instfont = TTFont(instfont_path)
940        tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"]
941        expected_ttx_path = self.get_test_output(varfont_name + ".ttx")
942        self.expect_ttx(instfont, expected_ttx_path, tables)
943
944
945if __name__ == "__main__":
946    sys.exit(unittest.main())
947