• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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