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