from fontTools.misc.testTools import getXML, parseXML, parseXmlInto, FakeFont from fontTools.misc.textTools import deHexStr, hexStr from fontTools.misc.xmlWriter import XMLWriter from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter import fontTools.ttLib.tables.otTables as otTables from io import StringIO import unittest def makeCoverage(glyphs): coverage = otTables.Coverage() coverage.glyphs = glyphs return coverage class SingleSubstTest(unittest.TestCase): def setUp(self): self.glyphs = ".notdef A B C D E a b c d e".split() self.font = FakeFont(self.glyphs) def test_postRead_format1(self): table = otTables.SingleSubst() table.Format = 1 rawTable = { "Coverage": makeCoverage(["A", "B", "C"]), "DeltaGlyphID": 5 } table.postRead(rawTable, self.font) self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"}) def test_postRead_format2(self): table = otTables.SingleSubst() table.Format = 2 rawTable = { "Coverage": makeCoverage(["A", "B", "C"]), "GlyphCount": 3, "Substitute": ["c", "b", "a"] } table.postRead(rawTable, self.font) self.assertEqual(table.mapping, {"A": "c", "B": "b", "C": "a"}) def test_postRead_formatUnknown(self): table = otTables.SingleSubst() table.Format = 987 rawTable = {"Coverage": makeCoverage(["A", "B", "C"])} self.assertRaises(AssertionError, table.postRead, rawTable, self.font) def test_preWrite_format1(self): table = otTables.SingleSubst() table.mapping = {"A": "a", "B": "b", "C": "c"} rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 1) self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"]) self.assertEqual(rawTable["DeltaGlyphID"], 5) def test_preWrite_format2(self): table = otTables.SingleSubst() table.mapping = {"A": "c", "B": "b", "C": "a"} rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 2) self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"]) self.assertEqual(rawTable["Substitute"], ["c", "b", "a"]) def test_preWrite_emptyMapping(self): table = otTables.SingleSubst() table.mapping = {} rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 2) self.assertEqual(rawTable["Coverage"].glyphs, []) self.assertEqual(rawTable["Substitute"], []) def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.SingleSubst() table.mapping = {"A": "a", "B": "b", "C": "c"} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '', '', '', ]) def test_fromXML(self): table = otTables.SingleSubst() for name, attrs, content in parseXML( '' '' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"}) class MultipleSubstTest(unittest.TestCase): def setUp(self): self.glyphs = ".notdef c f i t c_t f_f_i".split() self.font = FakeFont(self.glyphs) def test_postRead_format1(self): makeSequence = otTables.MultipleSubst.makeSequence_ table = otTables.MultipleSubst() table.Format = 1 rawTable = { "Coverage": makeCoverage(["c_t", "f_f_i"]), "Sequence": [ makeSequence(["c", "t"]), makeSequence(["f", "f", "i"]) ] } table.postRead(rawTable, self.font) self.assertEqual(table.mapping, { "c_t": ["c", "t"], "f_f_i": ["f", "f", "i"] }) def test_postRead_formatUnknown(self): table = otTables.MultipleSubst() table.Format = 987 self.assertRaises(AssertionError, table.postRead, {}, self.font) def test_preWrite_format1(self): table = otTables.MultipleSubst() table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]} rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 1) self.assertEqual(rawTable["Coverage"].glyphs, ["c_t", "f_f_i"]) def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.MultipleSubst() table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '', '', ]) def test_fromXML(self): table = otTables.MultipleSubst() for name, attrs, content in parseXML( '' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(table.mapping, {'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']}) def test_fromXML_oldFormat(self): table = otTables.MultipleSubst() for name, attrs, content in parseXML( '' ' ' ' ' '' '' ' ' ' ' '' '' ' ' ' ' ' ' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(table.mapping, {'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']}) def test_fromXML_oldFormat_bug385(self): # https://github.com/fonttools/fonttools/issues/385 table = otTables.MultipleSubst() table.Format = 1 for name, attrs, content in parseXML( '' ' ' ' ' '' '' ' ' ' ' ' ' '' '' ' ' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(table.mapping, {'o': ['o', 'l', 'o'], 'l': ['o']}) class LigatureSubstTest(unittest.TestCase): def setUp(self): self.glyphs = ".notdef c f i t c_t f_f f_i f_f_i".split() self.font = FakeFont(self.glyphs) def makeLigature(self, s): """'ffi' --> Ligature(LigGlyph='f_f_i', Component=['f', 'f', 'i'])""" lig = otTables.Ligature() lig.Component = list(s) lig.LigGlyph = "_".join(lig.Component) return lig def makeLigatures(self, s): """'ffi fi' --> [otTables.Ligature, otTables.Ligature]""" return [self.makeLigature(lig) for lig in s.split()] def test_postRead_format1(self): table = otTables.LigatureSubst() table.Format = 1 ligs_c = otTables.LigatureSet() ligs_c.Ligature = self.makeLigatures("ct") ligs_f = otTables.LigatureSet() ligs_f.Ligature = self.makeLigatures("ffi ff fi") rawTable = { "Coverage": makeCoverage(["c", "f"]), "LigatureSet": [ligs_c, ligs_f] } table.postRead(rawTable, self.font) self.assertEqual(set(table.ligatures.keys()), {"c", "f"}) self.assertEqual(len(table.ligatures["c"]), 1) self.assertEqual(table.ligatures["c"][0].LigGlyph, "c_t") self.assertEqual(table.ligatures["c"][0].Component, ["c", "t"]) self.assertEqual(len(table.ligatures["f"]), 3) self.assertEqual(table.ligatures["f"][0].LigGlyph, "f_f_i") self.assertEqual(table.ligatures["f"][0].Component, ["f", "f", "i"]) self.assertEqual(table.ligatures["f"][1].LigGlyph, "f_f") self.assertEqual(table.ligatures["f"][1].Component, ["f", "f"]) self.assertEqual(table.ligatures["f"][2].LigGlyph, "f_i") self.assertEqual(table.ligatures["f"][2].Component, ["f", "i"]) def test_postRead_formatUnknown(self): table = otTables.LigatureSubst() table.Format = 987 rawTable = {"Coverage": makeCoverage(["f"])} self.assertRaises(AssertionError, table.postRead, rawTable, self.font) def test_preWrite_format1(self): table = otTables.LigatureSubst() table.ligatures = { "c": self.makeLigatures("ct"), "f": self.makeLigatures("ffi ff fi") } rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 1) self.assertEqual(rawTable["Coverage"].glyphs, ["c", "f"]) [c, f] = rawTable["LigatureSet"] self.assertIsInstance(c, otTables.LigatureSet) self.assertIsInstance(f, otTables.LigatureSet) [ct] = c.Ligature self.assertIsInstance(ct, otTables.Ligature) self.assertEqual(ct.LigGlyph, "c_t") self.assertEqual(ct.Component, ["c", "t"]) [ffi, ff, fi] = f.Ligature self.assertIsInstance(ffi, otTables.Ligature) self.assertEqual(ffi.LigGlyph, "f_f_i") self.assertEqual(ffi.Component, ["f", "f", "i"]) self.assertIsInstance(ff, otTables.Ligature) self.assertEqual(ff.LigGlyph, "f_f") self.assertEqual(ff.Component, ["f", "f"]) self.assertIsInstance(fi, otTables.Ligature) self.assertEqual(fi.LigGlyph, "f_i") self.assertEqual(fi.Component, ["f", "i"]) def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.LigatureSubst() table.ligatures = { "c": self.makeLigatures("ct"), "f": self.makeLigatures("ffi ff fi") } table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '', ' ', '', '', ' ', ' ', ' ', '' ]) def test_fromXML(self): table = otTables.LigatureSubst() for name, attrs, content in parseXML( '' ' ' ' ' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(set(table.ligatures.keys()), {"f"}) [ffi, ff] = table.ligatures["f"] self.assertEqual(ffi.LigGlyph, "f_f_i") self.assertEqual(ffi.Component, ["f", "f", "i"]) self.assertEqual(ff.LigGlyph, "f_f") self.assertEqual(ff.Component, ["f", "f"]) class AlternateSubstTest(unittest.TestCase): def setUp(self): self.glyphs = ".notdef G G.alt1 G.alt2 Z Z.fina".split() self.font = FakeFont(self.glyphs) def makeAlternateSet(self, s): result = otTables.AlternateSet() result.Alternate = s.split() return result def test_postRead_format1(self): table = otTables.AlternateSubst() table.Format = 1 rawTable = { "Coverage": makeCoverage(["G", "Z"]), "AlternateSet": [ self.makeAlternateSet("G.alt2 G.alt1"), self.makeAlternateSet("Z.fina") ] } table.postRead(rawTable, self.font) self.assertEqual(table.alternates, { "G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"] }) def test_postRead_formatUnknown(self): table = otTables.AlternateSubst() table.Format = 987 self.assertRaises(AssertionError, table.postRead, {}, self.font) def test_preWrite_format1(self): table = otTables.AlternateSubst() table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]} rawTable = table.preWrite(self.font) self.assertEqual(table.Format, 1) self.assertEqual(rawTable["Coverage"].glyphs, ["G", "Z"]) [g, z] = rawTable["AlternateSet"] self.assertIsInstance(g, otTables.AlternateSet) self.assertEqual(g.Alternate, ["G.alt2", "G.alt1"]) self.assertIsInstance(z, otTables.AlternateSet) self.assertEqual(z.Alternate, ["Z.fina"]) def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.AlternateSubst() table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '', ' ', ' ', '', '', ' ', '' ]) def test_fromXML(self): table = otTables.AlternateSubst() for name, attrs, content in parseXML( '' ' ' ' ' '' '' ' ' ''): table.fromXML(name, attrs, content, self.font) self.assertEqual(table.alternates, { "G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"] }) class RearrangementMorphActionTest(unittest.TestCase): def setUp(self): self.font = FakeFont(['.notdef', 'A', 'B', 'C']) def testCompile(self): r = otTables.RearrangementMorphAction() r.NewState = 0x1234 r.MarkFirst = r.DontAdvance = r.MarkLast = True r.ReservedFlags, r.Verb = 0x1FF0, 0xD writer = OTTableWriter() r.compile(writer, self.font, actionIndex=None) self.assertEqual(hexStr(writer.getAllData()), "1234fffd") def testCompileActions(self): act = otTables.RearrangementMorphAction() self.assertEqual(act.compileActions(self.font, []), (None, None)) def testDecompileToXML(self): r = otTables.RearrangementMorphAction() r.decompile(OTTableReader(deHexStr("1234fffd")), self.font, actionReader=None) toXML = lambda w, f: r.toXML(w, f, {"Test": "Foo"}, "Transition") self.assertEqual(getXML(toXML, self.font), [ '', ' ', # 0x1234 = 4660 ' ', ' ', ' ', '', ]) class ContextualMorphActionTest(unittest.TestCase): def setUp(self): self.font = FakeFont(['.notdef', 'A', 'B', 'C']) def testCompile(self): a = otTables.ContextualMorphAction() a.NewState = 0x1234 a.SetMark, a.DontAdvance, a.ReservedFlags = True, True, 0x3117 a.MarkIndex, a.CurrentIndex = 0xDEAD, 0xBEEF writer = OTTableWriter() a.compile(writer, self.font, actionIndex=None) self.assertEqual(hexStr(writer.getAllData()), "1234f117deadbeef") def testCompileActions(self): act = otTables.ContextualMorphAction() self.assertEqual(act.compileActions(self.font, []), (None, None)) def testDecompileToXML(self): a = otTables.ContextualMorphAction() a.decompile(OTTableReader(deHexStr("1234f117deadbeef")), self.font, actionReader=None) toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") self.assertEqual(getXML(toXML, self.font), [ '', ' ', # 0x1234 = 4660 ' ', ' ', ' ', # 0xDEAD = 57005 ' ', # 0xBEEF = 48879 '', ]) class LigatureMorphActionTest(unittest.TestCase): def setUp(self): self.font = FakeFont(['.notdef', 'A', 'B', 'C']) def testDecompileToXML(self): a = otTables.LigatureMorphAction() actionReader = OTTableReader(deHexStr("DEADBEEF 7FFFFFFE 80000003")) a.decompile(OTTableReader(deHexStr("1234FAB30001")), self.font, actionReader) toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") self.assertEqual(getXML(toXML, self.font), [ '', ' ', # 0x1234 = 4660 ' ', ' ', ' ', ' ', '', ]) def testCompileActions_empty(self): act = otTables.LigatureMorphAction() actions, actionIndex = act.compileActions(self.font, []) self.assertEqual(actions, b'') self.assertEqual(actionIndex, {}) def testCompileActions_shouldShareSubsequences(self): state = otTables.AATState() t = state.Transitions = {i: otTables.LigatureMorphAction() for i in range(3)} ligs = [otTables.LigAction() for _ in range(3)] for i, lig in enumerate(ligs): lig.GlyphIndexDelta = i t[0].Actions = ligs[1:2] t[1].Actions = ligs[0:3] t[2].Actions = ligs[1:3] actions, actionIndex = t[0].compileActions(self.font, [state]) self.assertEqual(actions, deHexStr("00000000 00000001 80000002 80000001")) self.assertEqual(actionIndex, { deHexStr("00000000 00000001 80000002"): 0, deHexStr("00000001 80000002"): 1, deHexStr("80000002"): 2, deHexStr("80000001"): 3, }) class InsertionMorphActionTest(unittest.TestCase): MORPH_ACTION_XML = [ '', ' ', # 0x1234 = 4660 ' ', ' ', ' ', ' ', ' ', ' ', '' ] def setUp(self): self.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D']) self.maxDiff = None def testDecompileToXML(self): a = otTables.InsertionMorphAction() actionReader = OTTableReader( deHexStr("DEAD BEEF 0002 0001 0004 0002 0003 DEAD BEEF")) a.decompile(OTTableReader(deHexStr("1234 FC43 0005 0002")), self.font, actionReader) toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition") self.assertEqual(getXML(toXML, self.font), self.MORPH_ACTION_XML) def testCompileFromXML(self): a = otTables.InsertionMorphAction() for name, attrs, content in parseXML(self.MORPH_ACTION_XML): a.fromXML(name, attrs, content, self.font) writer = OTTableWriter() a.compile( writer, self.font, actionIndex={('B', 'C'): 9, ('B', 'A', 'D'): 7}, ) self.assertEqual(hexStr(writer.getAllData()), "1234fc4300090007") def testCompileActions_empty(self): act = otTables.InsertionMorphAction() actions, actionIndex = act.compileActions(self.font, []) self.assertEqual(actions, b'') self.assertEqual(actionIndex, {}) def testCompileActions_shouldShareSubsequences(self): state = otTables.AATState() t = state.Transitions = {i: otTables.InsertionMorphAction() for i in range(3)} t[1].CurrentInsertionAction = [] t[0].MarkedInsertionAction = ['A'] t[1].CurrentInsertionAction = ['C', 'D'] t[1].MarkedInsertionAction = ['B'] t[2].CurrentInsertionAction = ['B', 'C', 'D'] t[2].MarkedInsertionAction = ['C', 'D'] actions, actionIndex = t[0].compileActions(self.font, [state]) self.assertEqual(actions, deHexStr('0002 0003 0004 0001')) self.assertEqual(actionIndex, { ('A',): 3, ('B',): 0, ('B', 'C'): 0, ('B', 'C', 'D'): 0, ('C',): 1, ('C', 'D'): 1, ('D',): 2, }) class SplitMultipleSubstTest: def overflow(self, itemName, itemRecord): from fontTools.otlLib.builder import buildMultipleSubstSubtable from fontTools.ttLib.tables.otBase import OverflowErrorRecord oldSubTable = buildMultipleSubstSubtable({'e': 1, 'a': 2, 'b': 3, 'c': 4, 'd': 5}) newSubTable = otTables.MultipleSubst() ok = otTables.splitMultipleSubst(oldSubTable, newSubTable, OverflowErrorRecord((None, None, None, itemName, itemRecord))) assert ok return oldSubTable.mapping, newSubTable.mapping def test_Coverage(self): oldMapping, newMapping = self.overflow('Coverage', None) assert oldMapping == {'a': 2, 'b': 3} assert newMapping == {'c': 4, 'd': 5, 'e': 1} def test_RangeRecord(self): oldMapping, newMapping = self.overflow('RangeRecord', None) assert oldMapping == {'a': 2, 'b': 3} assert newMapping == {'c': 4, 'd': 5, 'e': 1} def test_Sequence(self): oldMapping, newMapping = self.overflow('Sequence', 4) assert oldMapping == {'a': 2, 'b': 3,'c': 4} assert newMapping == {'d': 5, 'e': 1} def test_splitMarkBasePos(): from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable marks = { "acutecomb": (0, buildAnchor(0, 600)), "gravecomb": (0, buildAnchor(0, 590)), "cedillacomb": (1, buildAnchor(0, 0)), } bases = { "a": { 0: buildAnchor(350, 500), 1: None, }, "c": { 0: buildAnchor(300, 700), 1: buildAnchor(300, 0), }, } glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"] glyphMap = {g: i for i, g in enumerate(glyphOrder)} oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap) newSubTable = otTables.MarkBasePos() ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None) assert ok assert getXML(oldSubTable.toXML) == [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', ] assert getXML(newSubTable.toXML) == [ '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', ] class ColrV1Test(unittest.TestCase): def setUp(self): self.font = FakeFont(['.notdef', 'meh']) def test_traverseEmptyPaintColrLayersNeedsNoLayerList(self): colr = parseXmlInto( self.font, otTables.COLR(), ''' ''', ) paint = colr.BaseGlyphList.BaseGlyphPaintRecord[0].Paint # Just want to confirm we don't crash visited = [] paint.traverse(colr, lambda p: visited.append(p)) assert len(visited) == 1 if __name__ == "__main__": import sys sys.exit(unittest.main())