1from fontTools.ttLib import newTable 2from fontTools.ttLib.tables._k_e_r_n import KernTable_format_0, KernTable_format_unkown 3from fontTools.misc.textTools import deHexStr 4from fontTools.misc.testTools import FakeFont, getXML, parseXML 5import itertools 6import pytest 7 8 9KERN_VER_0_FMT_0_DATA = deHexStr( 10 "0000 " # 0: version=0 11 "0001 " # 2: nTables=1 12 "0000 " # 4: version=0 (bogus field, unused) 13 "0020 " # 6: length=32 14 "00 " # 8: format=0 15 "01 " # 9: coverage=1 16 "0003 " # 10: nPairs=3 17 "000C " # 12: searchRange=12 18 "0001 " # 14: entrySelector=1 19 "0006 " # 16: rangeShift=6 20 "0004 000C FFD8 " # 18: l=4, r=12, v=-40 21 "0004 001C 0028 " # 24: l=4, r=28, v=40 22 "0005 0028 FFCE " # 30: l=5, r=40, v=-50 23) 24assert len(KERN_VER_0_FMT_0_DATA) == 36 25 26KERN_VER_0_FMT_0_XML = [ 27 '<version value="0"/>', 28 '<kernsubtable coverage="1" format="0">', 29 ' <pair l="E" r="M" v="-40"/>', 30 ' <pair l="E" r="c" v="40"/>', 31 ' <pair l="F" r="o" v="-50"/>', 32 "</kernsubtable>", 33] 34 35KERN_VER_1_FMT_0_DATA = deHexStr( 36 "0001 0000 " # 0: version=1 37 "0000 0001 " # 4: nTables=1 38 "0000 0022 " # 8: length=34 39 "00 " # 12: coverage=0 40 "00 " # 13: format=0 41 "0000 " # 14: tupleIndex=0 42 "0003 " # 16: nPairs=3 43 "000C " # 18: searchRange=12 44 "0001 " # 20: entrySelector=1 45 "0006 " # 22: rangeShift=6 46 "0004 000C FFD8 " # 24: l=4, r=12, v=-40 47 "0004 001C 0028 " # 30: l=4, r=28, v=40 48 "0005 0028 FFCE " # 36: l=5, r=40, v=-50 49) 50assert len(KERN_VER_1_FMT_0_DATA) == 42 51 52KERN_VER_1_FMT_0_XML = [ 53 '<version value="1.0"/>', 54 '<kernsubtable coverage="0" format="0" tupleIndex="0">', 55 ' <pair l="E" r="M" v="-40"/>', 56 ' <pair l="E" r="c" v="40"/>', 57 ' <pair l="F" r="o" v="-50"/>', 58 "</kernsubtable>", 59] 60 61KERN_VER_0_FMT_UNKNOWN_DATA = deHexStr( 62 "0000 " # 0: version=0 63 "0002 " # 2: nTables=2 64 "0000 " # 4: version=0 65 "000A " # 6: length=10 66 "04 " # 8: format=4 (format 4 doesn't exist) 67 "01 " # 9: coverage=1 68 "1234 5678 " # 10: garbage... 69 "0000 " # 14: version=0 70 "000A " # 16: length=10 71 "05 " # 18: format=5 (format 5 doesn't exist) 72 "01 " # 19: coverage=1 73 "9ABC DEF0 " # 20: garbage... 74) 75assert len(KERN_VER_0_FMT_UNKNOWN_DATA) == 24 76 77KERN_VER_0_FMT_UNKNOWN_XML = [ 78 '<version value="0"/>', 79 '<kernsubtable format="4">', 80 " <!-- unknown 'kern' subtable format -->", 81 " 0000000A 04011234", 82 " 5678 ", 83 "</kernsubtable>", 84 '<kernsubtable format="5">', 85 "<!-- unknown 'kern' subtable format -->", 86 " 0000000A 05019ABC", 87 " DEF0 ", 88 "</kernsubtable>", 89] 90 91KERN_VER_1_FMT_UNKNOWN_DATA = deHexStr( 92 "0001 0000 " # 0: version=1 93 "0000 0002 " # 4: nTables=2 94 "0000 000C " # 8: length=12 95 "00 " # 12: coverage=0 96 "04 " # 13: format=4 (format 4 doesn't exist) 97 "0000 " # 14: tupleIndex=0 98 "1234 5678" # 16: garbage... 99 "0000 000C " # 20: length=12 100 "00 " # 24: coverage=0 101 "05 " # 25: format=5 (format 5 doesn't exist) 102 "0000 " # 26: tupleIndex=0 103 "9ABC DEF0 " # 28: garbage... 104) 105assert len(KERN_VER_1_FMT_UNKNOWN_DATA) == 32 106 107KERN_VER_1_FMT_UNKNOWN_XML = [ 108 '<version value="1"/>', 109 '<kernsubtable format="4">', 110 " <!-- unknown 'kern' subtable format -->", 111 " 0000000C 00040000", 112 " 12345678 ", 113 "</kernsubtable>", 114 '<kernsubtable format="5">', 115 " <!-- unknown 'kern' subtable format -->", 116 " 0000000C 00050000", 117 " 9ABCDEF0 ", 118 "</kernsubtable>", 119] 120 121KERN_VER_0_FMT_0_OVERFLOWING_DATA = deHexStr( 122 "0000 " # 0: version=0 123 "0001 " # 2: nTables=1 124 "0000 " # 4: version=0 (bogus field, unused) 125 "0274 " # 6: length=628 (bogus value for 66164 % 0x10000) 126 "00 " # 8: format=0 127 "01 " # 9: coverage=1 128 "2B11 " # 10: nPairs=11025 129 "C000 " # 12: searchRange=49152 130 "000D " # 14: entrySelector=13 131 "4266 " # 16: rangeShift=16998 132) + deHexStr( 133 " ".join( 134 "%04X %04X %04X" % (a, b, 0) 135 for (a, b) in itertools.product(range(105), repeat=2) 136 ) 137) 138 139 140@pytest.fixture 141def font(): 142 return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz")) 143 144 145@pytest.fixture 146def overflowing_font(): 147 return FakeFont(["glyph%i" % i for i in range(105)]) 148 149 150class KernTableTest(object): 151 @pytest.mark.parametrize( 152 "data, version", 153 [ 154 (KERN_VER_0_FMT_0_DATA, 0), 155 (KERN_VER_1_FMT_0_DATA, 1.0), 156 ], 157 ids=["version_0", "version_1"], 158 ) 159 def test_decompile_single_format_0(self, data, font, version): 160 kern = newTable("kern") 161 kern.decompile(data, font) 162 163 assert kern.version == version 164 assert len(kern.kernTables) == 1 165 166 st = kern.kernTables[0] 167 assert st.apple is (version == 1.0) 168 assert st.format == 0 169 # horizontal kerning in OT kern is coverage 0x01, while in 170 # AAT kern it's the default (0) 171 assert st.coverage == (0 if st.apple else 1) 172 assert st.tupleIndex == (0 if st.apple else None) 173 assert len(st.kernTable) == 3 174 assert st.kernTable == {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50} 175 176 @pytest.mark.parametrize( 177 "version, expected", 178 [ 179 (0, KERN_VER_0_FMT_0_DATA), 180 (1.0, KERN_VER_1_FMT_0_DATA), 181 ], 182 ids=["version_0", "version_1"], 183 ) 184 def test_compile_single_format_0(self, font, version, expected): 185 kern = newTable("kern") 186 kern.version = version 187 apple = version == 1.0 188 st = KernTable_format_0(apple) 189 kern.kernTables = [st] 190 st.coverage = 0 if apple else 1 191 st.tupleIndex = 0 if apple else None 192 st.kernTable = {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50} 193 data = kern.compile(font) 194 assert data == expected 195 196 @pytest.mark.parametrize( 197 "xml, version", 198 [ 199 (KERN_VER_0_FMT_0_XML, 0), 200 (KERN_VER_1_FMT_0_XML, 1.0), 201 ], 202 ids=["version_0", "version_1"], 203 ) 204 def test_fromXML_single_format_0(self, xml, font, version): 205 kern = newTable("kern") 206 for name, attrs, content in parseXML(xml): 207 kern.fromXML(name, attrs, content, ttFont=font) 208 209 assert kern.version == version 210 assert len(kern.kernTables) == 1 211 212 st = kern.kernTables[0] 213 assert st.apple is (version == 1.0) 214 assert st.format == 0 215 assert st.coverage == (0 if st.apple else 1) 216 assert st.tupleIndex == (0 if st.apple else None) 217 assert len(st.kernTable) == 3 218 assert st.kernTable == {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50} 219 220 @pytest.mark.parametrize( 221 "version, expected", 222 [ 223 (0, KERN_VER_0_FMT_0_XML), 224 (1.0, KERN_VER_1_FMT_0_XML), 225 ], 226 ids=["version_0", "version_1"], 227 ) 228 def test_toXML_single_format_0(self, font, version, expected): 229 kern = newTable("kern") 230 kern.version = version 231 apple = version == 1.0 232 st = KernTable_format_0(apple) 233 kern.kernTables = [st] 234 st.coverage = 0 if apple else 1 235 st.tupleIndex = 0 if apple else None 236 st.kernTable = {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50} 237 xml = getXML(kern.toXML, font) 238 assert xml == expected 239 240 @pytest.mark.parametrize( 241 "data, version, header_length, st_length", 242 [ 243 (KERN_VER_0_FMT_UNKNOWN_DATA, 0, 4, 10), 244 (KERN_VER_1_FMT_UNKNOWN_DATA, 1.0, 8, 12), 245 ], 246 ids=["version_0", "version_1"], 247 ) 248 def test_decompile_format_unknown( 249 self, data, font, version, header_length, st_length 250 ): 251 kern = newTable("kern") 252 kern.decompile(data, font) 253 254 assert kern.version == version 255 assert len(kern.kernTables) == 2 256 257 st_data = data[header_length:] 258 st0 = kern.kernTables[0] 259 assert st0.format == 4 260 assert st0.data == st_data[:st_length] 261 st_data = st_data[st_length:] 262 263 st1 = kern.kernTables[1] 264 assert st1.format == 5 265 assert st1.data == st_data[:st_length] 266 267 @pytest.mark.parametrize( 268 "version, st_length, expected", 269 [ 270 (0, 10, KERN_VER_0_FMT_UNKNOWN_DATA), 271 (1.0, 12, KERN_VER_1_FMT_UNKNOWN_DATA), 272 ], 273 ids=["version_0", "version_1"], 274 ) 275 def test_compile_format_unknown(self, version, st_length, expected): 276 kern = newTable("kern") 277 kern.version = version 278 kern.kernTables = [] 279 280 for unknown_fmt, kern_data in zip((4, 5), ("1234 5678", "9ABC DEF0")): 281 if version > 0: 282 coverage = 0 283 header_fmt = deHexStr( 284 "%08X %02X %02X %04X" % (st_length, coverage, unknown_fmt, 0) 285 ) 286 else: 287 coverage = 1 288 header_fmt = deHexStr( 289 "%04X %04X %02X %02X" % (0, st_length, unknown_fmt, coverage) 290 ) 291 st = KernTable_format_unkown(unknown_fmt) 292 st.data = header_fmt + deHexStr(kern_data) 293 kern.kernTables.append(st) 294 295 data = kern.compile(font) 296 assert data == expected 297 298 @pytest.mark.parametrize( 299 "xml, version, st_length", 300 [ 301 (KERN_VER_0_FMT_UNKNOWN_XML, 0, 10), 302 (KERN_VER_1_FMT_UNKNOWN_XML, 1.0, 12), 303 ], 304 ids=["version_0", "version_1"], 305 ) 306 def test_fromXML_format_unknown(self, xml, font, version, st_length): 307 kern = newTable("kern") 308 for name, attrs, content in parseXML(xml): 309 kern.fromXML(name, attrs, content, ttFont=font) 310 311 assert kern.version == version 312 assert len(kern.kernTables) == 2 313 314 st0 = kern.kernTables[0] 315 assert st0.format == 4 316 assert len(st0.data) == st_length 317 318 st1 = kern.kernTables[1] 319 assert st1.format == 5 320 assert len(st1.data) == st_length 321 322 @pytest.mark.parametrize("version", [0, 1.0], ids=["version_0", "version_1"]) 323 def test_toXML_format_unknown(self, font, version): 324 kern = newTable("kern") 325 kern.version = version 326 st = KernTable_format_unkown(4) 327 st.data = b"ABCD" 328 kern.kernTables = [st] 329 330 xml = getXML(kern.toXML, font) 331 332 assert xml == [ 333 '<version value="%s"/>' % version, 334 '<kernsubtable format="4">', 335 " <!-- unknown 'kern' subtable format -->", 336 " 41424344 ", 337 "</kernsubtable>", 338 ] 339 340 def test_getkern(self): 341 table = newTable("kern") 342 table.version = 0 343 table.kernTables = [] 344 345 assert table.getkern(0) is None 346 347 st0 = KernTable_format_0() 348 table.kernTables.append(st0) 349 350 assert table.getkern(0) is st0 351 assert table.getkern(4) is None 352 353 st1 = KernTable_format_unkown(4) 354 table.kernTables.append(st1) 355 356 357class KernTable_format_0_Test(object): 358 def test_decompileBadGlyphId(self, font): 359 subtable = KernTable_format_0() 360 subtable.decompile( 361 b"\x00" 362 + b"\x00" 363 + b"\x00" 364 + b"\x1a" 365 + b"\x00" 366 + b"\x00" 367 + b"\x00" 368 + b"\x02" 369 + b"\x00" * 6 370 + b"\x00" 371 + b"\x01" 372 + b"\x00" 373 + b"\x03" 374 + b"\x00" 375 + b"\x01" 376 + b"\x00" 377 + b"\x01" 378 + b"\xFF" 379 + b"\xFF" 380 + b"\x00" 381 + b"\x02", 382 font, 383 ) 384 assert subtable[("B", "D")] == 1 385 assert subtable[("B", "glyph65535")] == 2 386 387 def test_compileOverflowingSubtable(self, overflowing_font): 388 font = overflowing_font 389 kern = newTable("kern") 390 kern.version = 0 391 st = KernTable_format_0(0) 392 kern.kernTables = [st] 393 st.coverage = 1 394 st.tupleIndex = None 395 st.kernTable = { 396 (a, b): 0 for (a, b) in itertools.product(font.getGlyphOrder(), repeat=2) 397 } 398 assert len(st.kernTable) == 11025 399 data = kern.compile(font) 400 assert data == KERN_VER_0_FMT_0_OVERFLOWING_DATA 401 402 def test_decompileOverflowingSubtable(self, overflowing_font): 403 font = overflowing_font 404 data = KERN_VER_0_FMT_0_OVERFLOWING_DATA 405 kern = newTable("kern") 406 kern.decompile(data, font) 407 408 st = kern.kernTables[0] 409 assert st.kernTable == { 410 (a, b): 0 for (a, b) in itertools.product(font.getGlyphOrder(), repeat=2) 411 } 412 413 414if __name__ == "__main__": 415 import sys 416 417 sys.exit(pytest.main(sys.argv)) 418