1from fontTools.misc.testTools import getXML, parseXML, parseXmlInto, FakeFont 2from fontTools.misc.textTools import deHexStr, hexStr 3from fontTools.misc.xmlWriter import XMLWriter 4from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter 5import fontTools.ttLib.tables.otTables as otTables 6from io import StringIO 7import unittest 8 9 10def makeCoverage(glyphs): 11 coverage = otTables.Coverage() 12 coverage.glyphs = glyphs 13 return coverage 14 15 16class SingleSubstTest(unittest.TestCase): 17 def setUp(self): 18 self.glyphs = ".notdef A B C D E a b c d e".split() 19 self.font = FakeFont(self.glyphs) 20 21 def test_postRead_format1(self): 22 table = otTables.SingleSubst() 23 table.Format = 1 24 rawTable = {"Coverage": makeCoverage(["A", "B", "C"]), "DeltaGlyphID": 5} 25 table.postRead(rawTable, self.font) 26 self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"}) 27 28 def test_postRead_format2(self): 29 table = otTables.SingleSubst() 30 table.Format = 2 31 rawTable = { 32 "Coverage": makeCoverage(["A", "B", "C"]), 33 "GlyphCount": 3, 34 "Substitute": ["c", "b", "a"], 35 } 36 table.postRead(rawTable, self.font) 37 self.assertEqual(table.mapping, {"A": "c", "B": "b", "C": "a"}) 38 39 def test_postRead_formatUnknown(self): 40 table = otTables.SingleSubst() 41 table.Format = 987 42 rawTable = {"Coverage": makeCoverage(["A", "B", "C"])} 43 self.assertRaises(AssertionError, table.postRead, rawTable, self.font) 44 45 def test_preWrite_format1(self): 46 table = otTables.SingleSubst() 47 table.mapping = {"A": "a", "B": "b", "C": "c"} 48 rawTable = table.preWrite(self.font) 49 self.assertEqual(table.Format, 1) 50 self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"]) 51 self.assertEqual(rawTable["DeltaGlyphID"], 5) 52 53 def test_preWrite_format2(self): 54 table = otTables.SingleSubst() 55 table.mapping = {"A": "c", "B": "b", "C": "a"} 56 rawTable = table.preWrite(self.font) 57 self.assertEqual(table.Format, 2) 58 self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"]) 59 self.assertEqual(rawTable["Substitute"], ["c", "b", "a"]) 60 61 def test_preWrite_emptyMapping(self): 62 table = otTables.SingleSubst() 63 table.mapping = {} 64 rawTable = table.preWrite(self.font) 65 self.assertEqual(table.Format, 2) 66 self.assertEqual(rawTable["Coverage"].glyphs, []) 67 self.assertEqual(rawTable["Substitute"], []) 68 69 def test_toXML2(self): 70 writer = XMLWriter(StringIO()) 71 table = otTables.SingleSubst() 72 table.mapping = {"A": "a", "B": "b", "C": "c"} 73 table.toXML2(writer, self.font) 74 self.assertEqual( 75 writer.file.getvalue().splitlines()[1:], 76 [ 77 '<Substitution in="A" out="a"/>', 78 '<Substitution in="B" out="b"/>', 79 '<Substitution in="C" out="c"/>', 80 ], 81 ) 82 83 def test_fromXML(self): 84 table = otTables.SingleSubst() 85 for name, attrs, content in parseXML( 86 '<Substitution in="A" out="a"/>' 87 '<Substitution in="B" out="b"/>' 88 '<Substitution in="C" out="c"/>' 89 ): 90 table.fromXML(name, attrs, content, self.font) 91 self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"}) 92 93 94class MultipleSubstTest(unittest.TestCase): 95 def setUp(self): 96 self.glyphs = ".notdef c f i t c_t f_f_i".split() 97 self.font = FakeFont(self.glyphs) 98 99 def test_postRead_format1(self): 100 makeSequence = otTables.MultipleSubst.makeSequence_ 101 table = otTables.MultipleSubst() 102 table.Format = 1 103 rawTable = { 104 "Coverage": makeCoverage(["c_t", "f_f_i"]), 105 "Sequence": [makeSequence(["c", "t"]), makeSequence(["f", "f", "i"])], 106 } 107 table.postRead(rawTable, self.font) 108 self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}) 109 110 def test_postRead_formatUnknown(self): 111 table = otTables.MultipleSubst() 112 table.Format = 987 113 self.assertRaises(AssertionError, table.postRead, {}, self.font) 114 115 def test_preWrite_format1(self): 116 table = otTables.MultipleSubst() 117 table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]} 118 rawTable = table.preWrite(self.font) 119 self.assertEqual(table.Format, 1) 120 self.assertEqual(rawTable["Coverage"].glyphs, ["c_t", "f_f_i"]) 121 122 def test_toXML2(self): 123 writer = XMLWriter(StringIO()) 124 table = otTables.MultipleSubst() 125 table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]} 126 table.toXML2(writer, self.font) 127 self.assertEqual( 128 writer.file.getvalue().splitlines()[1:], 129 [ 130 '<Substitution in="c_t" out="c,t"/>', 131 '<Substitution in="f_f_i" out="f,f,i"/>', 132 ], 133 ) 134 135 def test_fromXML(self): 136 table = otTables.MultipleSubst() 137 for name, attrs, content in parseXML( 138 '<Substitution in="c_t" out="c,t"/>' 139 '<Substitution in="f_f_i" out="f,f,i"/>' 140 ): 141 table.fromXML(name, attrs, content, self.font) 142 self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}) 143 144 def test_fromXML_oldFormat(self): 145 table = otTables.MultipleSubst() 146 for name, attrs, content in parseXML( 147 "<Coverage>" 148 ' <Glyph value="c_t"/>' 149 ' <Glyph value="f_f_i"/>' 150 "</Coverage>" 151 '<Sequence index="0">' 152 ' <Substitute index="0" value="c"/>' 153 ' <Substitute index="1" value="t"/>' 154 "</Sequence>" 155 '<Sequence index="1">' 156 ' <Substitute index="0" value="f"/>' 157 ' <Substitute index="1" value="f"/>' 158 ' <Substitute index="2" value="i"/>' 159 "</Sequence>" 160 ): 161 table.fromXML(name, attrs, content, self.font) 162 self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}) 163 164 def test_fromXML_oldFormat_bug385(self): 165 # https://github.com/fonttools/fonttools/issues/385 166 table = otTables.MultipleSubst() 167 table.Format = 1 168 for name, attrs, content in parseXML( 169 "<Coverage>" 170 ' <Glyph value="o"/>' 171 ' <Glyph value="l"/>' 172 "</Coverage>" 173 "<Sequence>" 174 ' <Substitute value="o"/>' 175 ' <Substitute value="l"/>' 176 ' <Substitute value="o"/>' 177 "</Sequence>" 178 "<Sequence>" 179 ' <Substitute value="o"/>' 180 "</Sequence>" 181 ): 182 table.fromXML(name, attrs, content, self.font) 183 self.assertEqual(table.mapping, {"o": ["o", "l", "o"], "l": ["o"]}) 184 185 186class LigatureSubstTest(unittest.TestCase): 187 def setUp(self): 188 self.glyphs = ".notdef c f i t c_t f_f f_i f_f_i".split() 189 self.font = FakeFont(self.glyphs) 190 191 def makeLigature(self, s): 192 """'ffi' --> Ligature(LigGlyph='f_f_i', Component=['f', 'f', 'i'])""" 193 lig = otTables.Ligature() 194 lig.Component = list(s) 195 lig.LigGlyph = "_".join(lig.Component) 196 return lig 197 198 def makeLigatures(self, s): 199 """'ffi fi' --> [otTables.Ligature, otTables.Ligature]""" 200 return [self.makeLigature(lig) for lig in s.split()] 201 202 def test_postRead_format1(self): 203 table = otTables.LigatureSubst() 204 table.Format = 1 205 ligs_c = otTables.LigatureSet() 206 ligs_c.Ligature = self.makeLigatures("ct") 207 ligs_f = otTables.LigatureSet() 208 ligs_f.Ligature = self.makeLigatures("ffi ff fi") 209 rawTable = { 210 "Coverage": makeCoverage(["c", "f"]), 211 "LigatureSet": [ligs_c, ligs_f], 212 } 213 table.postRead(rawTable, self.font) 214 self.assertEqual(set(table.ligatures.keys()), {"c", "f"}) 215 self.assertEqual(len(table.ligatures["c"]), 1) 216 self.assertEqual(table.ligatures["c"][0].LigGlyph, "c_t") 217 self.assertEqual(table.ligatures["c"][0].Component, ["c", "t"]) 218 self.assertEqual(len(table.ligatures["f"]), 3) 219 self.assertEqual(table.ligatures["f"][0].LigGlyph, "f_f_i") 220 self.assertEqual(table.ligatures["f"][0].Component, ["f", "f", "i"]) 221 self.assertEqual(table.ligatures["f"][1].LigGlyph, "f_f") 222 self.assertEqual(table.ligatures["f"][1].Component, ["f", "f"]) 223 self.assertEqual(table.ligatures["f"][2].LigGlyph, "f_i") 224 self.assertEqual(table.ligatures["f"][2].Component, ["f", "i"]) 225 226 def test_postRead_formatUnknown(self): 227 table = otTables.LigatureSubst() 228 table.Format = 987 229 rawTable = {"Coverage": makeCoverage(["f"])} 230 self.assertRaises(AssertionError, table.postRead, rawTable, self.font) 231 232 def test_preWrite_format1(self): 233 table = otTables.LigatureSubst() 234 table.ligatures = { 235 "c": self.makeLigatures("ct"), 236 "f": self.makeLigatures("ffi ff fi"), 237 } 238 rawTable = table.preWrite(self.font) 239 self.assertEqual(table.Format, 1) 240 self.assertEqual(rawTable["Coverage"].glyphs, ["c", "f"]) 241 [c, f] = rawTable["LigatureSet"] 242 self.assertIsInstance(c, otTables.LigatureSet) 243 self.assertIsInstance(f, otTables.LigatureSet) 244 [ct] = c.Ligature 245 self.assertIsInstance(ct, otTables.Ligature) 246 self.assertEqual(ct.LigGlyph, "c_t") 247 self.assertEqual(ct.Component, ["c", "t"]) 248 [ffi, ff, fi] = f.Ligature 249 self.assertIsInstance(ffi, otTables.Ligature) 250 self.assertEqual(ffi.LigGlyph, "f_f_i") 251 self.assertEqual(ffi.Component, ["f", "f", "i"]) 252 self.assertIsInstance(ff, otTables.Ligature) 253 self.assertEqual(ff.LigGlyph, "f_f") 254 self.assertEqual(ff.Component, ["f", "f"]) 255 self.assertIsInstance(fi, otTables.Ligature) 256 self.assertEqual(fi.LigGlyph, "f_i") 257 self.assertEqual(fi.Component, ["f", "i"]) 258 259 def test_toXML2(self): 260 writer = XMLWriter(StringIO()) 261 table = otTables.LigatureSubst() 262 table.ligatures = { 263 "c": self.makeLigatures("ct"), 264 "f": self.makeLigatures("ffi ff fi"), 265 } 266 table.toXML2(writer, self.font) 267 self.assertEqual( 268 writer.file.getvalue().splitlines()[1:], 269 [ 270 '<LigatureSet glyph="c">', 271 ' <Ligature components="c,t" glyph="c_t"/>', 272 "</LigatureSet>", 273 '<LigatureSet glyph="f">', 274 ' <Ligature components="f,f,i" glyph="f_f_i"/>', 275 ' <Ligature components="f,f" glyph="f_f"/>', 276 ' <Ligature components="f,i" glyph="f_i"/>', 277 "</LigatureSet>", 278 ], 279 ) 280 281 def test_fromXML(self): 282 table = otTables.LigatureSubst() 283 for name, attrs, content in parseXML( 284 '<LigatureSet glyph="f">' 285 ' <Ligature components="f,f,i" glyph="f_f_i"/>' 286 ' <Ligature components="f,f" glyph="f_f"/>' 287 "</LigatureSet>" 288 ): 289 table.fromXML(name, attrs, content, self.font) 290 self.assertEqual(set(table.ligatures.keys()), {"f"}) 291 [ffi, ff] = table.ligatures["f"] 292 self.assertEqual(ffi.LigGlyph, "f_f_i") 293 self.assertEqual(ffi.Component, ["f", "f", "i"]) 294 self.assertEqual(ff.LigGlyph, "f_f") 295 self.assertEqual(ff.Component, ["f", "f"]) 296 297 298class AlternateSubstTest(unittest.TestCase): 299 def setUp(self): 300 self.glyphs = ".notdef G G.alt1 G.alt2 Z Z.fina".split() 301 self.font = FakeFont(self.glyphs) 302 303 def makeAlternateSet(self, s): 304 result = otTables.AlternateSet() 305 result.Alternate = s.split() 306 return result 307 308 def test_postRead_format1(self): 309 table = otTables.AlternateSubst() 310 table.Format = 1 311 rawTable = { 312 "Coverage": makeCoverage(["G", "Z"]), 313 "AlternateSet": [ 314 self.makeAlternateSet("G.alt2 G.alt1"), 315 self.makeAlternateSet("Z.fina"), 316 ], 317 } 318 table.postRead(rawTable, self.font) 319 self.assertEqual(table.alternates, {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]}) 320 321 def test_postRead_formatUnknown(self): 322 table = otTables.AlternateSubst() 323 table.Format = 987 324 self.assertRaises(AssertionError, table.postRead, {}, self.font) 325 326 def test_preWrite_format1(self): 327 table = otTables.AlternateSubst() 328 table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]} 329 rawTable = table.preWrite(self.font) 330 self.assertEqual(table.Format, 1) 331 self.assertEqual(rawTable["Coverage"].glyphs, ["G", "Z"]) 332 [g, z] = rawTable["AlternateSet"] 333 self.assertIsInstance(g, otTables.AlternateSet) 334 self.assertEqual(g.Alternate, ["G.alt2", "G.alt1"]) 335 self.assertIsInstance(z, otTables.AlternateSet) 336 self.assertEqual(z.Alternate, ["Z.fina"]) 337 338 def test_toXML2(self): 339 writer = XMLWriter(StringIO()) 340 table = otTables.AlternateSubst() 341 table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]} 342 table.toXML2(writer, self.font) 343 self.assertEqual( 344 writer.file.getvalue().splitlines()[1:], 345 [ 346 '<AlternateSet glyph="G">', 347 ' <Alternate glyph="G.alt2"/>', 348 ' <Alternate glyph="G.alt1"/>', 349 "</AlternateSet>", 350 '<AlternateSet glyph="Z">', 351 ' <Alternate glyph="Z.fina"/>', 352 "</AlternateSet>", 353 ], 354 ) 355 356 def test_fromXML(self): 357 table = otTables.AlternateSubst() 358 for name, attrs, content in parseXML( 359 '<AlternateSet glyph="G">' 360 ' <Alternate glyph="G.alt2"/>' 361 ' <Alternate glyph="G.alt1"/>' 362 "</AlternateSet>" 363 '<AlternateSet glyph="Z">' 364 ' <Alternate glyph="Z.fina"/>' 365 "</AlternateSet>" 366 ): 367 table.fromXML(name, attrs, content, self.font) 368 self.assertEqual(table.alternates, {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]}) 369 370 371class RearrangementMorphActionTest(unittest.TestCase): 372 def setUp(self): 373 self.font = FakeFont([".notdef", "A", "B", "C"]) 374 375 def testCompile(self): 376 r = otTables.RearrangementMorphAction() 377 r.NewState = 0x1234 378 r.MarkFirst = r.DontAdvance = r.MarkLast = True 379 r.ReservedFlags, r.Verb = 0x1FF0, 0xD 380 writer = OTTableWriter() 381 r.compile(writer, self.font, actionIndex=None) 382 self.assertEqual(hexStr(writer.getAllData()), "1234fffd") 383 384 def testCompileActions(self): 385 act = otTables.RearrangementMorphAction() 386 self.assertEqual(act.compileActions(self.font, []), (None, None)) 387 388 def testDecompileToXML(self): 389 r = otTables.RearrangementMorphAction() 390 r.decompile(OTTableReader(deHexStr("1234fffd")), self.font, actionReader=None) 391 toXML = lambda w, f: r.toXML(w, f, {"Test": "Foo"}, "Transition") 392 self.assertEqual( 393 getXML(toXML, self.font), 394 [ 395 '<Transition Test="Foo">', 396 ' <NewState value="4660"/>', # 0x1234 = 4660 397 ' <Flags value="MarkFirst,DontAdvance,MarkLast"/>', 398 ' <ReservedFlags value="0x1FF0"/>', 399 ' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->', 400 "</Transition>", 401 ], 402 ) 403 404 405class ContextualMorphActionTest(unittest.TestCase): 406 def setUp(self): 407 self.font = FakeFont([".notdef", "A", "B", "C"]) 408 409 def testCompile(self): 410 a = otTables.ContextualMorphAction() 411 a.NewState = 0x1234 412 a.SetMark, a.DontAdvance, a.ReservedFlags = True, True, 0x3117 413 a.MarkIndex, a.CurrentIndex = 0xDEAD, 0xBEEF 414 writer = OTTableWriter() 415 a.compile(writer, self.font, actionIndex=None) 416 self.assertEqual(hexStr(writer.getAllData()), "1234f117deadbeef") 417 418 def testCompileActions(self): 419 act = otTables.ContextualMorphAction() 420 self.assertEqual(act.compileActions(self.font, []), (None, None)) 421 422 def testDecompileToXML(self): 423 a = otTables.ContextualMorphAction() 424 a.decompile( 425 OTTableReader(deHexStr("1234f117deadbeef")), self.font, actionReader=None 426 ) 427 toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") 428 self.assertEqual( 429 getXML(toXML, self.font), 430 [ 431 '<Transition Test="Foo">', 432 ' <NewState value="4660"/>', # 0x1234 = 4660 433 ' <Flags value="SetMark,DontAdvance"/>', 434 ' <ReservedFlags value="0x3117"/>', 435 ' <MarkIndex value="57005"/>', # 0xDEAD = 57005 436 ' <CurrentIndex value="48879"/>', # 0xBEEF = 48879 437 "</Transition>", 438 ], 439 ) 440 441 442class LigatureMorphActionTest(unittest.TestCase): 443 def setUp(self): 444 self.font = FakeFont([".notdef", "A", "B", "C"]) 445 446 def testDecompileToXML(self): 447 a = otTables.LigatureMorphAction() 448 actionReader = OTTableReader(deHexStr("DEADBEEF 7FFFFFFE 80000003")) 449 a.decompile(OTTableReader(deHexStr("1234FAB30001")), self.font, actionReader) 450 toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") 451 self.assertEqual( 452 getXML(toXML, self.font), 453 [ 454 '<Transition Test="Foo">', 455 ' <NewState value="4660"/>', # 0x1234 = 4660 456 ' <Flags value="SetComponent,DontAdvance"/>', 457 ' <ReservedFlags value="0x1AB3"/>', 458 ' <Action GlyphIndexDelta="-2" Flags="Store"/>', 459 ' <Action GlyphIndexDelta="3"/>', 460 "</Transition>", 461 ], 462 ) 463 464 def testCompileActions_empty(self): 465 act = otTables.LigatureMorphAction() 466 actions, actionIndex = act.compileActions(self.font, []) 467 self.assertEqual(actions, b"") 468 self.assertEqual(actionIndex, {}) 469 470 def testCompileActions_shouldShareSubsequences(self): 471 state = otTables.AATState() 472 t = state.Transitions = {i: otTables.LigatureMorphAction() for i in range(3)} 473 ligs = [otTables.LigAction() for _ in range(3)] 474 for i, lig in enumerate(ligs): 475 lig.GlyphIndexDelta = i 476 t[0].Actions = ligs[1:2] 477 t[1].Actions = ligs[0:3] 478 t[2].Actions = ligs[1:3] 479 actions, actionIndex = t[0].compileActions(self.font, [state]) 480 self.assertEqual(actions, deHexStr("00000000 00000001 80000002 80000001")) 481 self.assertEqual( 482 actionIndex, 483 { 484 deHexStr("00000000 00000001 80000002"): 0, 485 deHexStr("00000001 80000002"): 1, 486 deHexStr("80000002"): 2, 487 deHexStr("80000001"): 3, 488 }, 489 ) 490 491 492class InsertionMorphActionTest(unittest.TestCase): 493 MORPH_ACTION_XML = [ 494 '<Transition Test="Foo">', 495 ' <NewState value="4660"/>', # 0x1234 = 4660 496 ' <Flags value="SetMark,DontAdvance,CurrentIsKashidaLike,' 497 'MarkedIsKashidaLike,CurrentInsertBefore,MarkedInsertBefore"/>', 498 ' <CurrentInsertionAction glyph="B"/>', 499 ' <CurrentInsertionAction glyph="C"/>', 500 ' <MarkedInsertionAction glyph="B"/>', 501 ' <MarkedInsertionAction glyph="A"/>', 502 ' <MarkedInsertionAction glyph="D"/>', 503 "</Transition>", 504 ] 505 506 def setUp(self): 507 self.font = FakeFont([".notdef", "A", "B", "C", "D"]) 508 self.maxDiff = None 509 510 def testDecompileToXML(self): 511 a = otTables.InsertionMorphAction() 512 actionReader = OTTableReader( 513 deHexStr("DEAD BEEF 0002 0001 0004 0002 0003 DEAD BEEF") 514 ) 515 a.decompile( 516 OTTableReader(deHexStr("1234 FC43 0005 0002")), self.font, actionReader 517 ) 518 toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") 519 self.assertEqual(getXML(toXML, self.font), self.MORPH_ACTION_XML) 520 521 def testCompileFromXML(self): 522 a = otTables.InsertionMorphAction() 523 for name, attrs, content in parseXML(self.MORPH_ACTION_XML): 524 a.fromXML(name, attrs, content, self.font) 525 writer = OTTableWriter() 526 a.compile( 527 writer, 528 self.font, 529 actionIndex={("B", "C"): 9, ("B", "A", "D"): 7}, 530 ) 531 self.assertEqual(hexStr(writer.getAllData()), "1234fc4300090007") 532 533 def testCompileActions_empty(self): 534 act = otTables.InsertionMorphAction() 535 actions, actionIndex = act.compileActions(self.font, []) 536 self.assertEqual(actions, b"") 537 self.assertEqual(actionIndex, {}) 538 539 def testCompileActions_shouldShareSubsequences(self): 540 state = otTables.AATState() 541 t = state.Transitions = {i: otTables.InsertionMorphAction() for i in range(3)} 542 t[1].CurrentInsertionAction = [] 543 t[0].MarkedInsertionAction = ["A"] 544 t[1].CurrentInsertionAction = ["C", "D"] 545 t[1].MarkedInsertionAction = ["B"] 546 t[2].CurrentInsertionAction = ["B", "C", "D"] 547 t[2].MarkedInsertionAction = ["C", "D"] 548 actions, actionIndex = t[0].compileActions(self.font, [state]) 549 self.assertEqual(actions, deHexStr("0002 0003 0004 0001")) 550 self.assertEqual( 551 actionIndex, 552 { 553 ("A",): 3, 554 ("B",): 0, 555 ("B", "C"): 0, 556 ("B", "C", "D"): 0, 557 ("C",): 1, 558 ("C", "D"): 1, 559 ("D",): 2, 560 }, 561 ) 562 563 564class SplitMultipleSubstTest: 565 def overflow(self, itemName, itemRecord): 566 from fontTools.otlLib.builder import buildMultipleSubstSubtable 567 from fontTools.ttLib.tables.otBase import OverflowErrorRecord 568 569 oldSubTable = buildMultipleSubstSubtable( 570 {"e": 1, "a": 2, "b": 3, "c": 4, "d": 5} 571 ) 572 newSubTable = otTables.MultipleSubst() 573 574 ok = otTables.splitMultipleSubst( 575 oldSubTable, 576 newSubTable, 577 OverflowErrorRecord((None, None, None, itemName, itemRecord)), 578 ) 579 580 assert ok 581 return oldSubTable.mapping, newSubTable.mapping 582 583 def test_Coverage(self): 584 oldMapping, newMapping = self.overflow("Coverage", None) 585 assert oldMapping == {"a": 2, "b": 3} 586 assert newMapping == {"c": 4, "d": 5, "e": 1} 587 588 def test_RangeRecord(self): 589 oldMapping, newMapping = self.overflow("RangeRecord", None) 590 assert oldMapping == {"a": 2, "b": 3} 591 assert newMapping == {"c": 4, "d": 5, "e": 1} 592 593 def test_Sequence(self): 594 oldMapping, newMapping = self.overflow("Sequence", 4) 595 assert oldMapping == {"a": 2, "b": 3, "c": 4} 596 assert newMapping == {"d": 5, "e": 1} 597 598 599def test_splitMarkBasePos(): 600 from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable 601 602 marks = { 603 "acutecomb": (0, buildAnchor(0, 600)), 604 "gravecomb": (0, buildAnchor(0, 590)), 605 "cedillacomb": (1, buildAnchor(0, 0)), 606 } 607 bases = { 608 "a": { 609 0: buildAnchor(350, 500), 610 1: None, 611 }, 612 "c": { 613 0: buildAnchor(300, 700), 614 1: buildAnchor(300, 0), 615 }, 616 } 617 glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"] 618 glyphMap = {g: i for i, g in enumerate(glyphOrder)} 619 620 oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap) 621 newSubTable = otTables.MarkBasePos() 622 623 ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None) 624 625 assert ok 626 627 assert getXML(oldSubTable.toXML) == [ 628 '<MarkBasePos Format="1">', 629 " <MarkCoverage>", 630 ' <Glyph value="acutecomb"/>', 631 ' <Glyph value="gravecomb"/>', 632 " </MarkCoverage>", 633 " <BaseCoverage>", 634 ' <Glyph value="a"/>', 635 ' <Glyph value="c"/>', 636 " </BaseCoverage>", 637 " <!-- ClassCount=1 -->", 638 " <MarkArray>", 639 " <!-- MarkCount=2 -->", 640 ' <MarkRecord index="0">', 641 ' <Class value="0"/>', 642 ' <MarkAnchor Format="1">', 643 ' <XCoordinate value="0"/>', 644 ' <YCoordinate value="600"/>', 645 " </MarkAnchor>", 646 " </MarkRecord>", 647 ' <MarkRecord index="1">', 648 ' <Class value="0"/>', 649 ' <MarkAnchor Format="1">', 650 ' <XCoordinate value="0"/>', 651 ' <YCoordinate value="590"/>', 652 " </MarkAnchor>", 653 " </MarkRecord>", 654 " </MarkArray>", 655 " <BaseArray>", 656 " <!-- BaseCount=2 -->", 657 ' <BaseRecord index="0">', 658 ' <BaseAnchor index="0" Format="1">', 659 ' <XCoordinate value="350"/>', 660 ' <YCoordinate value="500"/>', 661 " </BaseAnchor>", 662 " </BaseRecord>", 663 ' <BaseRecord index="1">', 664 ' <BaseAnchor index="0" Format="1">', 665 ' <XCoordinate value="300"/>', 666 ' <YCoordinate value="700"/>', 667 " </BaseAnchor>", 668 " </BaseRecord>", 669 " </BaseArray>", 670 "</MarkBasePos>", 671 ] 672 673 assert getXML(newSubTable.toXML) == [ 674 '<MarkBasePos Format="1">', 675 " <MarkCoverage>", 676 ' <Glyph value="cedillacomb"/>', 677 " </MarkCoverage>", 678 " <BaseCoverage>", 679 ' <Glyph value="a"/>', 680 ' <Glyph value="c"/>', 681 " </BaseCoverage>", 682 " <!-- ClassCount=1 -->", 683 " <MarkArray>", 684 " <!-- MarkCount=1 -->", 685 ' <MarkRecord index="0">', 686 ' <Class value="0"/>', 687 ' <MarkAnchor Format="1">', 688 ' <XCoordinate value="0"/>', 689 ' <YCoordinate value="0"/>', 690 " </MarkAnchor>", 691 " </MarkRecord>", 692 " </MarkArray>", 693 " <BaseArray>", 694 " <!-- BaseCount=2 -->", 695 ' <BaseRecord index="0">', 696 ' <BaseAnchor index="0" empty="1"/>', 697 " </BaseRecord>", 698 ' <BaseRecord index="1">', 699 ' <BaseAnchor index="0" Format="1">', 700 ' <XCoordinate value="300"/>', 701 ' <YCoordinate value="0"/>', 702 " </BaseAnchor>", 703 " </BaseRecord>", 704 " </BaseArray>", 705 "</MarkBasePos>", 706 ] 707 708 709class ColrV1Test(unittest.TestCase): 710 def setUp(self): 711 self.font = FakeFont([".notdef", "meh"]) 712 713 def test_traverseEmptyPaintColrLayersNeedsNoLayerList(self): 714 colr = parseXmlInto( 715 self.font, 716 otTables.COLR(), 717 """ 718 <Version value="1"/> 719 <BaseGlyphList> 720 <BaseGlyphPaintRecord index="0"> 721 <BaseGlyph value="meh"/> 722 <Paint Format="1"><!-- PaintColrLayers --> 723 <NumLayers value="0"/> 724 <FirstLayerIndex value="42"/> 725 </Paint> 726 </BaseGlyphPaintRecord> 727 </BaseGlyphList> 728 """, 729 ) 730 paint = colr.BaseGlyphList.BaseGlyphPaintRecord[0].Paint 731 732 # Just want to confirm we don't crash 733 visited = [] 734 paint.traverse(colr, lambda p: visited.append(p)) 735 assert len(visited) == 1 736 737 738if __name__ == "__main__": 739 import sys 740 741 sys.exit(unittest.main()) 742