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