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