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