• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.loggingTools import CapturingLogHandler
2from fontTools.misc.testTools import FakeFont, makeXMLWriter
3from fontTools.misc.textTools import deHexStr
4import fontTools.ttLib.tables.otConverters as otConverters
5from fontTools.ttLib import newTable
6from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
7import unittest
8
9
10class Char64Test(unittest.TestCase):
11    font = FakeFont([])
12    converter = otConverters.Char64("char64", 0, None, None)
13
14    def test_read(self):
15        reader = OTTableReader(b"Hello\0junk after zero byte" + 100 * b"\0")
16        self.assertEqual(self.converter.read(reader, self.font, {}), "Hello")
17        self.assertEqual(reader.pos, 64)
18
19    def test_read_replace_not_ascii(self):
20        reader = OTTableReader(b"Hello \xE4 world" + 100 * b"\0")
21        with CapturingLogHandler(otConverters.log, "WARNING") as captor:
22            data = self.converter.read(reader, self.font, {})
23        self.assertEqual(data, "Hello � world")
24        self.assertEqual(reader.pos, 64)
25        self.assertIn('replaced non-ASCII characters in "Hello � world"',
26                      [r.msg for r in captor.records])
27
28    def test_write(self):
29        writer = OTTableWriter()
30        self.converter.write(writer, self.font, {}, "Hello world")
31        self.assertEqual(writer.getData(), b"Hello world" + 53 * b"\0")
32
33    def test_write_replace_not_ascii(self):
34        writer = OTTableWriter()
35        with CapturingLogHandler(otConverters.log, "WARNING") as captor:
36            self.converter.write(writer, self.font, {}, "Hello ☃")
37        self.assertEqual(writer.getData(), b"Hello ?" + 57 * b"\0")
38        self.assertIn('replacing non-ASCII characters in "Hello ☃"',
39                      [r.msg for r in captor.records])
40
41    def test_write_truncated(self):
42        writer = OTTableWriter()
43        with CapturingLogHandler(otConverters.log, "WARNING") as captor:
44            self.converter.write(writer, self.font, {}, "A" * 80)
45        self.assertEqual(writer.getData(), b"A" * 64)
46        self.assertIn('truncating overlong "' + "A" * 80 + '" to 64 bytes',
47                      [r.msg for r in captor.records])
48
49    def test_xmlRead(self):
50        value = self.converter.xmlRead({"value": "Foo"}, [], self.font)
51        self.assertEqual(value, "Foo")
52
53    def test_xmlWrite(self):
54        writer = makeXMLWriter()
55        self.converter.xmlWrite(writer, self.font, "Hello world", "Element",
56                                [("attr", "v")])
57        xml = writer.file.getvalue().decode("utf-8").rstrip()
58        self.assertEqual(xml, '<Element attr="v" value="Hello world"/>')
59
60
61class GlyphIDTest(unittest.TestCase):
62    font = FakeFont(".notdef A B C".split())
63    converter = otConverters.GlyphID('GlyphID', 0, None, None)
64
65    def test_readArray(self):
66        reader = OTTableReader(deHexStr("0002 0001 DEAD 0002"))
67        self.assertEqual(self.converter.readArray(reader, self.font, {}, 4),
68                         ["B", "A", "glyph57005", "B"])
69        self.assertEqual(reader.pos, 8)
70
71    def test_read(self):
72        reader = OTTableReader(deHexStr("0003"))
73        self.assertEqual(self.converter.read(reader, self.font, {}), "C")
74        self.assertEqual(reader.pos, 2)
75
76    def test_write(self):
77        writer = OTTableWriter()
78        self.converter.write(writer, self.font, {}, "B")
79        self.assertEqual(writer.getData(), deHexStr("0002"))
80
81
82class LongTest(unittest.TestCase):
83    font = FakeFont([])
84    converter = otConverters.Long('Long', 0, None, None)
85
86    def test_read(self):
87        reader = OTTableReader(deHexStr("FF0000EE"))
88        self.assertEqual(self.converter.read(reader, self.font, {}), -16776978)
89        self.assertEqual(reader.pos, 4)
90
91    def test_write(self):
92        writer = OTTableWriter()
93        self.converter.write(writer, self.font, {}, -16777213)
94        self.assertEqual(writer.getData(), deHexStr("FF000003"))
95
96    def test_xmlRead(self):
97        value = self.converter.xmlRead({"value": "314159"}, [], self.font)
98        self.assertEqual(value, 314159)
99
100    def test_xmlWrite(self):
101        writer = makeXMLWriter()
102        self.converter.xmlWrite(writer, self.font, 291, "Foo", [("attr", "v")])
103        xml = writer.file.getvalue().decode("utf-8").rstrip()
104        self.assertEqual(xml, '<Foo attr="v" value="291"/>')
105
106
107class NameIDTest(unittest.TestCase):
108    converter = otConverters.NameID('NameID', 0, None, None)
109
110    def makeFont(self):
111        nameTable = newTable('name')
112        nameTable.setName(u"Demibold Condensed", 0x123, 3, 0, 0x409)
113        nameTable.setName(u"Copyright 2018", 0, 3, 0, 0x409)
114        return {"name": nameTable}
115
116    def test_read(self):
117        font = self.makeFont()
118        reader = OTTableReader(deHexStr("0123"))
119        self.assertEqual(self.converter.read(reader, font, {}), 0x123)
120
121    def test_write(self):
122        writer = OTTableWriter()
123        self.converter.write(writer, self.makeFont(), {}, 0x123)
124        self.assertEqual(writer.getData(), deHexStr("0123"))
125
126    def test_xmlWrite(self):
127        writer = makeXMLWriter()
128        self.converter.xmlWrite(writer, self.makeFont(), 291,
129                                "FooNameID", [("attr", "val")])
130        xml = writer.file.getvalue().decode("utf-8").rstrip()
131        self.assertEqual(
132            xml,
133            '<FooNameID attr="val" value="291"/>  <!-- Demibold Condensed -->')
134
135    def test_xmlWrite_missingID(self):
136        writer = makeXMLWriter()
137        with CapturingLogHandler(otConverters.log, "WARNING") as captor:
138            self.converter.xmlWrite(writer, self.makeFont(), 666,
139                                    "Entity", [("attrib", "val")])
140        self.assertIn("name id 666 missing from name table",
141                      [r.msg for r in captor.records])
142        xml = writer.file.getvalue().decode("utf-8").rstrip()
143        self.assertEqual(
144            xml,
145            '<Entity attrib="val"'
146            ' value="666"/>  <!-- missing from name table -->')
147
148    def test_xmlWrite_NULL(self):
149        writer = makeXMLWriter()
150        self.converter.xmlWrite(writer, self.makeFont(), 0,
151                                "FooNameID", [("attr", "val")])
152        xml = writer.file.getvalue().decode("utf-8").rstrip()
153        self.assertEqual(
154            xml, '<FooNameID attr="val" value="0"/>')
155
156
157class UInt8Test(unittest.TestCase):
158    font = FakeFont([])
159    converter = otConverters.UInt8("UInt8", 0, None, None)
160
161    def test_read(self):
162        reader = OTTableReader(deHexStr("FE"))
163        self.assertEqual(self.converter.read(reader, self.font, {}), 254)
164        self.assertEqual(reader.pos, 1)
165
166    def test_write(self):
167        writer = OTTableWriter()
168        self.converter.write(writer, self.font, {}, 253)
169        self.assertEqual(writer.getData(), deHexStr("FD"))
170
171    def test_xmlRead(self):
172        value = self.converter.xmlRead({"value": "254"}, [], self.font)
173        self.assertEqual(value, 254)
174
175    def test_xmlWrite(self):
176        writer = makeXMLWriter()
177        self.converter.xmlWrite(writer, self.font, 251, "Foo", [("attr", "v")])
178        xml = writer.file.getvalue().decode("utf-8").rstrip()
179        self.assertEqual(xml, '<Foo attr="v" value="251"/>')
180
181
182class AATLookupTest(unittest.TestCase):
183    font = FakeFont(".notdef A B C D E F G H A.alt B.alt".split())
184    converter = otConverters.AATLookup("AATLookup", 0, None,
185                                       tableClass=otConverters.GlyphID)
186
187    def __init__(self, methodName):
188        unittest.TestCase.__init__(self, methodName)
189        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
190        # and fires deprecation warnings if a program uses the old name.
191        if not hasattr(self, "assertRaisesRegex"):
192            self.assertRaisesRegex = self.assertRaisesRegexp
193
194    def test_readFormat0(self):
195        reader = OTTableReader(deHexStr("0000 0000 0001 0002 0000 7D00 0001"))
196        self.assertEqual(self.converter.read(reader, self.font, None), {
197            ".notdef": ".notdef",
198            "A": "A",
199            "B": "B",
200            "C": ".notdef",
201            "D": "glyph32000",
202            "E": "A"
203        })
204
205    def test_readFormat2(self):
206        reader = OTTableReader(deHexStr(
207            "0002 0006 0002 000C 0001 0006 "
208            "0002 0001 0003 "   # glyph A..B: map to C
209            "0007 0005 0008 "   # glyph E..G: map to H
210            "FFFF FFFF FFFF"))  # end of search table
211        self.assertEqual(self.converter.read(reader, self.font, None), {
212            "A": "C",
213            "B": "C",
214            "E": "H",
215            "F": "H",
216            "G": "H",
217        })
218
219    def test_readFormat4(self):
220        reader = OTTableReader(deHexStr(
221            "0004 0006 0003 000C 0001 0006 "
222            "0002 0001 001E "  # glyph 1..2: mapping at offset 0x1E
223            "0005 0004 001E "  # glyph 4..5: mapping at offset 0x1E
224            "FFFF FFFF FFFF "  # end of search table
225            "0007 0008"))      # offset 0x18: glyphs [7, 8] = [G, H]
226        self.assertEqual(self.converter.read(reader, self.font, None), {
227            "A": "G",
228            "B": "H",
229            "D": "G",
230            "E": "H",
231        })
232
233    def test_readFormat6(self):
234        reader = OTTableReader(deHexStr(
235            "0006 0004 0002 0008 0001 0004 "
236            "0003 0001 "   # C --> A
237            "0005 0002 "   # E --> B
238            "FFFF FFFF"))  # end of search table
239        self.assertEqual(self.converter.read(reader, self.font, None), {
240            "C": "A",
241            "E": "B",
242        })
243
244    def test_readFormat8(self):
245        reader = OTTableReader(deHexStr(
246            "0008 "
247            "0003 0003 "        # first: C, count: 3
248            "0007 0001 0002"))  # [G, A, B]
249        self.assertEqual(self.converter.read(reader, self.font, None), {
250            "C": "G",
251            "D": "A",
252            "E": "B",
253        })
254
255    def test_readUnknownFormat(self):
256        reader = OTTableReader(deHexStr("0009"))
257        self.assertRaisesRegex(
258            AssertionError,
259            "unsupported lookup format: 9",
260            self.converter.read, reader, self.font, None)
261
262    def test_writeFormat0(self):
263        writer = OTTableWriter()
264        font = FakeFont(".notdef A B C".split())
265        self.converter.write(writer, font, {}, {
266            ".notdef": ".notdef",
267            "A": "C",
268            "B": "C",
269            "C": "A"
270        })
271        self.assertEqual(writer.getData(), deHexStr("0000 0000 0003 0003 0001"))
272
273    def test_writeFormat2(self):
274        writer = OTTableWriter()
275        font = FakeFont(".notdef A B C D E F G H".split())
276        self.converter.write(writer, font, {}, {
277            "B": "C",
278            "C": "C",
279            "D": "C",
280            "E": "C",
281            "G": "A",
282            "H": "A",
283        })
284        self.assertEqual(writer.getData(), deHexStr(
285            "0002 "            # format=2
286            "0006 "            # binSrchHeader.unitSize=6
287            "0002 "            # binSrchHeader.nUnits=2
288            "000C "            # binSrchHeader.searchRange=12
289            "0001 "            # binSrchHeader.entrySelector=1
290            "0000 "            # binSrchHeader.rangeShift=0
291            "0005 0002 0003 "  # segments[0].lastGlyph=E, firstGlyph=B, value=C
292            "0008 0007 0001 "  # segments[1].lastGlyph=H, firstGlyph=G, value=A
293            "FFFF FFFF 0000 "  # segments[2]=<END>
294        ))
295
296    def test_writeFormat6(self):
297        writer = OTTableWriter()
298        font = FakeFont(".notdef A B C D E".split())
299        self.converter.write(writer, font, {}, {
300            "A": "C",
301            "C": "B",
302            "D": "D",
303            "E": "E",
304        })
305        self.assertEqual(writer.getData(), deHexStr(
306            "0006 "         # format=6
307            "0004 "         # binSrchHeader.unitSize=4
308            "0004 "         # binSrchHeader.nUnits=4
309            "0010 "         # binSrchHeader.searchRange=16
310            "0002 "         # binSrchHeader.entrySelector=2
311            "0000 "         # binSrchHeader.rangeShift=0
312            "0001 0003 "    # entries[0].glyph=A, .value=C
313            "0003 0002 "    # entries[1].glyph=C, .value=B
314            "0004 0004 "    # entries[2].glyph=D, .value=D
315            "0005 0005 "    # entries[3].glyph=E, .value=E
316            "FFFF 0000 "    # entries[4]=<END>
317        ))
318
319    def test_writeFormat8(self):
320        writer = OTTableWriter()
321        font = FakeFont(".notdef A B C D E F G H".split())
322        self.converter.write(writer, font, {}, {
323            "B": "B",
324            "C": "A",
325            "D": "B",
326            "E": "C",
327            "F": "B",
328            "G": "A",
329        })
330        self.assertEqual(writer.getData(), deHexStr(
331            "0008 "                          # format=8
332            "0002 "                          # firstGlyph=B
333            "0006 "                          # glyphCount=6
334            "0002 0001 0002 0003 0002 0001"  # valueArray=[B, A, B, C, B, A]
335        ))
336
337    def test_xmlRead(self):
338        value = self.converter.xmlRead({}, [
339            ("Lookup", {"glyph": "A", "value": "A.alt"}, []),
340            ("Lookup", {"glyph": "B", "value": "B.alt"}, []),
341        ], self.font)
342        self.assertEqual(value, {"A": "A.alt", "B": "B.alt"})
343
344    def test_xmlWrite(self):
345        writer = makeXMLWriter()
346        self.converter.xmlWrite(writer, self.font,
347                                value={"A": "A.alt", "B": "B.alt"},
348                                name="Foo", attrs=[("attr", "val")])
349        xml = writer.file.getvalue().decode("utf-8").splitlines()
350        self.assertEqual(xml, [
351            '<Foo attr="val">',
352            '  <Lookup glyph="A" value="A.alt"/>',
353            '  <Lookup glyph="B" value="B.alt"/>',
354            '</Foo>',
355        ])
356
357
358class LazyListTest(unittest.TestCase):
359
360    def test_slice(self):
361        ll = otConverters._LazyList([10, 11, 12, 13])
362        sl = ll[:]
363
364        self.assertIsNot(sl, ll)
365        self.assertIsInstance(sl, list)
366        self.assertEqual([10, 11, 12, 13], sl)
367
368        self.assertEqual([11, 12], ll[1:3])
369
370    def test_getitem(self):
371        count = 2
372        reader = OTTableReader(b"\x00\xFE\xFF\x00\x00\x00", offset=1)
373        converter = otConverters.UInt8("UInt8", 0, None, None)
374        recordSize = converter.staticSize
375        l = otConverters._LazyList()
376        l.reader = reader
377        l.pos = l.reader.pos
378        l.font = None
379        l.conv = converter
380        l.recordSize = recordSize
381        l.extend(otConverters._MissingItem([i]) for i in range(count))
382        reader.advance(count * recordSize)
383
384        self.assertEqual(l[0], 254)
385        self.assertEqual(l[1], 255)
386
387    def test_add_both_LazyList(self):
388        ll1 = otConverters._LazyList([1])
389        ll2 = otConverters._LazyList([2])
390
391        l3 = ll1 + ll2
392
393        self.assertIsInstance(l3, list)
394        self.assertEqual([1, 2], l3)
395
396    def test_add_LazyList_and_list(self):
397        ll1 = otConverters._LazyList([1])
398        l2 = [2]
399
400        l3 = ll1 + l2
401
402        self.assertIsInstance(l3, list)
403        self.assertEqual([1, 2], l3)
404
405    def test_add_not_implemented(self):
406        with self.assertRaises(TypeError):
407            otConverters._LazyList() + 0
408        with self.assertRaises(TypeError):
409            otConverters._LazyList() + tuple()
410
411    def test_radd_list_and_LazyList(self):
412        l1 = [1]
413        ll2 = otConverters._LazyList([2])
414
415        l3 = l1 + ll2
416
417        self.assertIsInstance(l3, list)
418        self.assertEqual([1, 2], l3)
419
420    def test_radd_not_implemented(self):
421        with self.assertRaises(TypeError):
422            0 + otConverters._LazyList()
423        with self.assertRaises(TypeError):
424            tuple() + otConverters._LazyList()
425
426
427if __name__ == "__main__":
428    import sys
429    sys.exit(unittest.main())
430