1from fontTools.ttLib import TTFont 2from fontTools.varLib import build 3from fontTools.varLib.mutator import main as mutator 4from fontTools.varLib.mutator import instantiateVariableFont as make_instance 5import difflib 6import os 7import shutil 8import sys 9import tempfile 10import unittest 11 12 13class MutatorTest(unittest.TestCase): 14 def __init__(self, methodName): 15 unittest.TestCase.__init__(self, methodName) 16 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 17 # and fires deprecation warnings if a program uses the old name. 18 if not hasattr(self, "assertRaisesRegex"): 19 self.assertRaisesRegex = self.assertRaisesRegexp 20 21 def setUp(self): 22 self.tempdir = None 23 self.num_tempfiles = 0 24 25 def tearDown(self): 26 if self.tempdir: 27 shutil.rmtree(self.tempdir) 28 29 @staticmethod 30 def get_test_input(test_file_or_folder): 31 path, _ = os.path.split(__file__) 32 return os.path.join(path, "data", test_file_or_folder) 33 34 @staticmethod 35 def get_test_output(test_file_or_folder): 36 path, _ = os.path.split(__file__) 37 return os.path.join(path, "data", "test_results", test_file_or_folder) 38 39 @staticmethod 40 def get_file_list(folder, suffix, prefix=""): 41 all_files = os.listdir(folder) 42 file_list = [] 43 for p in all_files: 44 if p.startswith(prefix) and p.endswith(suffix): 45 file_list.append(os.path.abspath(os.path.join(folder, p))) 46 return file_list 47 48 def temp_path(self, suffix): 49 self.temp_dir() 50 self.num_tempfiles += 1 51 return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix)) 52 53 def temp_dir(self): 54 if not self.tempdir: 55 self.tempdir = tempfile.mkdtemp() 56 57 def read_ttx(self, path): 58 lines = [] 59 with open(path, "r", encoding="utf-8") as ttx: 60 for line in ttx.readlines(): 61 # Elide ttFont attributes because ttLibVersion may change. 62 if line.startswith("<ttFont "): 63 lines.append("<ttFont>\n") 64 else: 65 lines.append(line.rstrip() + "\n") 66 return lines 67 68 def expect_ttx(self, font, expected_ttx, tables): 69 path = self.temp_path(suffix=".ttx") 70 font.saveXML(path, tables=tables) 71 actual = self.read_ttx(path) 72 expected = self.read_ttx(expected_ttx) 73 if actual != expected: 74 for line in difflib.unified_diff( 75 expected, actual, fromfile=expected_ttx, tofile=path 76 ): 77 sys.stdout.write(line) 78 self.fail("TTX output is different from expected") 79 80 def compile_font(self, path, suffix, temp_dir): 81 ttx_filename = os.path.basename(path) 82 savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix)) 83 font = TTFont(recalcBBoxes=False, recalcTimestamp=False) 84 font.importXML(path) 85 font.save(savepath, reorderTables=None) 86 return font, savepath 87 88 # ----- 89 # Tests 90 # ----- 91 92 def test_varlib_mutator_ttf(self): 93 suffix = ".ttf" 94 ds_path = self.get_test_input("Build.designspace") 95 ufo_dir = self.get_test_input("master_ufo") 96 ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf") 97 98 self.temp_dir() 99 ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-") 100 for path in ttx_paths: 101 self.compile_font(path, suffix, self.tempdir) 102 103 finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix) 104 varfont, _, _ = build(ds_path, finder) 105 varfont_name = "Mutator" 106 varfont_path = os.path.join(self.tempdir, varfont_name + suffix) 107 varfont.save(varfont_path) 108 109 args = [varfont_path, "wght=500", "cntr=50"] 110 mutator(args) 111 112 instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix 113 instfont = TTFont(instfont_path) 114 tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"] 115 expected_ttx_path = self.get_test_output(varfont_name + ".ttx") 116 self.expect_ttx(instfont, expected_ttx_path, tables) 117 118 def test_varlib_mutator_getvar_ttf(self): 119 suffix = ".ttf" 120 ttx_dir = self.get_test_input("master_ttx_getvar_ttf") 121 122 self.temp_dir() 123 ttx_paths = self.get_file_list(ttx_dir, ".ttx", "Mutator_Getvar") 124 for path in ttx_paths: 125 self.compile_font(path, suffix, self.tempdir) 126 127 varfont_name = "Mutator_Getvar" 128 varfont_path = os.path.join(self.tempdir, varfont_name + suffix) 129 130 args = [varfont_path, "wdth=80", "ASCN=628"] 131 mutator(args) 132 133 instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix 134 instfont = TTFont(instfont_path) 135 tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"] 136 expected_ttx_path = self.get_test_output(varfont_name + "-instance.ttx") 137 self.expect_ttx(instfont, expected_ttx_path, tables) 138 139 def test_varlib_mutator_iup_ttf(self): 140 suffix = ".ttf" 141 ufo_dir = self.get_test_input("master_ufo") 142 ttx_dir = self.get_test_input("master_ttx_varfont_ttf") 143 144 self.temp_dir() 145 ttx_paths = self.get_file_list(ttx_dir, ".ttx", "Mutator_IUP") 146 for path in ttx_paths: 147 self.compile_font(path, suffix, self.tempdir) 148 149 varfont_name = "Mutator_IUP" 150 varfont_path = os.path.join(self.tempdir, varfont_name + suffix) 151 152 args = [varfont_path, "wdth=80", "ASCN=628"] 153 mutator(args) 154 155 instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix 156 instfont = TTFont(instfont_path) 157 tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"] 158 expected_ttx_path = self.get_test_output(varfont_name + "-instance.ttx") 159 self.expect_ttx(instfont, expected_ttx_path, tables) 160 161 def test_varlib_mutator_CFF2(self): 162 suffix = ".otf" 163 ttx_dir = self.get_test_input("master_ttx_varfont_otf") 164 165 self.temp_dir() 166 ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestCFF2VF") 167 for path in ttx_paths: 168 self.compile_font(path, suffix, self.tempdir) 169 170 varfont_name = "TestCFF2VF" 171 varfont_path = os.path.join(self.tempdir, varfont_name + suffix) 172 173 expected_ttx_name = "InterpolateTestCFF2VF" 174 tables = ["hmtx", "CFF2"] 175 loc = {"wght": float(200)} 176 177 varfont = TTFont(varfont_path) 178 new_font = make_instance(varfont, loc) 179 expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx") 180 self.expect_ttx(new_font, expected_ttx_path, tables) 181 182 183if __name__ == "__main__": 184 sys.exit(unittest.main()) 185