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