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