xref: /aosp_15_r20/external/fonttools/Tests/varLib/interpolatable_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.ttLib import TTFont
2from fontTools.varLib.interpolatable import main as interpolatable_main
3import os
4import shutil
5import sys
6import tempfile
7import unittest
8import pytest
9
10try:
11    import scipy
12except:
13    scipy = None
14
15try:
16    import munkres
17except ImportError:
18    munkres = None
19
20
21@unittest.skipUnless(scipy or munkres, "scipy or munkres not installed")
22class InterpolatableTest(unittest.TestCase):
23    def __init__(self, methodName):
24        unittest.TestCase.__init__(self, methodName)
25        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
26        # and fires deprecation warnings if a program uses the old name.
27        if not hasattr(self, "assertRaisesRegex"):
28            self.assertRaisesRegex = self.assertRaisesRegexp
29
30    def setUp(self):
31        self.tempdir = None
32        self.num_tempfiles = 0
33
34    def tearDown(self):
35        if self.tempdir:
36            shutil.rmtree(self.tempdir)
37
38    @staticmethod
39    def get_test_input(*test_file_or_folder):
40        path, _ = os.path.split(__file__)
41        return os.path.join(path, "data", *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 sorted(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, "tmp%d%s" % (self.num_tempfiles, suffix))
56
57    def temp_dir(self):
58        if not self.tempdir:
59            self.tempdir = tempfile.mkdtemp()
60
61    def compile_font(self, path, suffix, temp_dir):
62        ttx_filename = os.path.basename(path)
63        savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
64        font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
65        font.importXML(path)
66        font.save(savepath, reorderTables=None)
67        return font, savepath
68
69    # -----
70    # Tests
71    # -----
72
73    def test_interpolatable_ttf(self):
74        suffix = ".ttf"
75        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
76
77        self.temp_dir()
78        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
79        for path in ttx_paths:
80            self.compile_font(path, suffix, self.tempdir)
81
82        ttf_paths = self.get_file_list(self.tempdir, suffix)
83        self.assertIsNone(interpolatable_main(ttf_paths))
84
85    def test_interpolatable_otf(self):
86        suffix = ".otf"
87        ttx_dir = self.get_test_input("master_ttx_interpolatable_otf")
88
89        self.temp_dir()
90        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
91        for path in ttx_paths:
92            self.compile_font(path, suffix, self.tempdir)
93
94        otf_paths = self.get_file_list(self.tempdir, suffix)
95        self.assertIsNone(interpolatable_main(otf_paths))
96
97    def test_interpolatable_ufo(self):
98        ttx_dir = self.get_test_input("master_ufo")
99        ufo_paths = self.get_file_list(ttx_dir, ".ufo", "TestFamily2-")
100        self.assertIsNone(interpolatable_main(ufo_paths))
101
102    def test_designspace(self):
103        designspace_path = self.get_test_input("InterpolateLayout.designspace")
104        self.assertIsNone(interpolatable_main([designspace_path]))
105
106    def test_glyphsapp(self):
107        pytest.importorskip("glyphsLib")
108        glyphsapp_path = self.get_test_input("InterpolateLayout.glyphs")
109        self.assertIsNone(interpolatable_main([glyphsapp_path]))
110
111    def test_VF(self):
112        suffix = ".ttf"
113        ttx_dir = self.get_test_input("master_ttx_varfont_ttf")
114
115        self.temp_dir()
116        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "SparseMasters-")
117        for path in ttx_paths:
118            self.compile_font(path, suffix, self.tempdir)
119
120        ttf_paths = self.get_file_list(self.tempdir, suffix)
121
122        problems = interpolatable_main(["--quiet"] + ttf_paths)
123        self.assertIsNone(problems)
124
125    def test_sparse_interpolatable_ttfs(self):
126        suffix = ".ttf"
127        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
128
129        self.temp_dir()
130        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "SparseMasters-")
131        for path in ttx_paths:
132            self.compile_font(path, suffix, self.tempdir)
133
134        ttf_paths = self.get_file_list(self.tempdir, suffix)
135
136        # without --ignore-missing
137        problems = interpolatable_main(["--quiet"] + ttf_paths)
138        self.assertEqual(
139            problems["a"],
140            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
141        )
142        self.assertEqual(
143            problems["s"],
144            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
145        )
146        self.assertEqual(
147            problems["edotabove"],
148            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
149        )
150        self.assertEqual(
151            problems["dotabovecomb"],
152            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
153        )
154
155        # normal order, with --ignore-missing
156        self.assertIsNone(interpolatable_main(["--ignore-missing"] + ttf_paths))
157        # purposely putting the sparse master (medium) first
158        self.assertIsNone(
159            interpolatable_main(
160                ["--ignore-missing"] + [ttf_paths[1]] + [ttf_paths[0]] + [ttf_paths[2]]
161            )
162        )
163        # purposely putting the sparse master (medium) last
164        self.assertIsNone(
165            interpolatable_main(
166                ["--ignore-missing"] + [ttf_paths[0]] + [ttf_paths[2]] + [ttf_paths[1]]
167            )
168        )
169
170    def test_sparse_interpolatable_ufos(self):
171        ttx_dir = self.get_test_input("master_ufo")
172        ufo_paths = self.get_file_list(ttx_dir, ".ufo", "SparseMasters-")
173
174        # without --ignore-missing
175        problems = interpolatable_main(["--quiet"] + ufo_paths)
176        self.assertEqual(
177            problems["a"],
178            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
179        )
180        self.assertEqual(
181            problems["s"],
182            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
183        )
184        self.assertEqual(
185            problems["edotabove"],
186            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
187        )
188        self.assertEqual(
189            problems["dotabovecomb"],
190            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
191        )
192
193        # normal order, with --ignore-missing
194        self.assertIsNone(interpolatable_main(["--ignore-missing"] + ufo_paths))
195        # purposely putting the sparse master (medium) first
196        self.assertIsNone(
197            interpolatable_main(
198                ["--ignore-missing"] + [ufo_paths[1]] + [ufo_paths[0]] + [ufo_paths[2]]
199            )
200        )
201        # purposely putting the sparse master (medium) last
202        self.assertIsNone(
203            interpolatable_main(
204                ["--ignore-missing"] + [ufo_paths[0]] + [ufo_paths[2]] + [ufo_paths[1]]
205            )
206        )
207
208    def test_sparse_designspace(self):
209        designspace_path = self.get_test_input("SparseMasters_ufo.designspace")
210
211        problems = interpolatable_main(["--quiet", designspace_path])
212        self.assertEqual(
213            problems["a"],
214            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
215        )
216        self.assertEqual(
217            problems["s"],
218            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
219        )
220        self.assertEqual(
221            problems["edotabove"],
222            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
223        )
224        self.assertEqual(
225            problems["dotabovecomb"],
226            [{"type": "missing", "master": "SparseMasters-Medium", "master_idx": 1}],
227        )
228
229        # normal order, with --ignore-missing
230        self.assertIsNone(interpolatable_main(["--ignore-missing", designspace_path]))
231
232    def test_sparse_glyphsapp(self):
233        pytest.importorskip("glyphsLib")
234        glyphsapp_path = self.get_test_input("SparseMasters.glyphs")
235
236        problems = interpolatable_main(["--quiet", glyphsapp_path])
237        self.assertEqual(
238            problems["a"],
239            [{"type": "missing", "master": "Sparse Masters-Medium", "master_idx": 1}],
240        )
241        self.assertEqual(
242            problems["s"],
243            [{"type": "missing", "master": "Sparse Masters-Medium", "master_idx": 1}],
244        )
245        self.assertEqual(
246            problems["edotabove"],
247            [{"type": "missing", "master": "Sparse Masters-Medium", "master_idx": 1}],
248        )
249        self.assertEqual(
250            problems["dotabovecomb"],
251            [{"type": "missing", "master": "Sparse Masters-Medium", "master_idx": 1}],
252        )
253
254        # normal order, with --ignore-missing
255        self.assertIsNone(interpolatable_main(["--ignore-missing", glyphsapp_path]))
256
257    def test_interpolatable_varComposite(self):
258        input_path = self.get_test_input(
259            "..", "..", "ttLib", "data", "varc-ac00-ac01.ttf"
260        )
261        # This particular test font which was generated by machine-learning
262        # exhibits an "error" in one of the masters; it's a false-positive.
263        # Just make sure the code runs.
264        interpolatable_main((input_path,))
265
266
267if __name__ == "__main__":
268    sys.exit(unittest.main())
269