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