xref: /aosp_15_r20/external/fonttools/Tests/ttLib/tables/otTables_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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