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