• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.loggingTools import CapturingLogHandler
2from fontTools.misc.textTools import tobytes
3from fontTools.ttLib import TTFont, TTLibError
4from fontTools.ttLib.tables.T_S_I__0 import table_T_S_I__0
5from fontTools.ttLib.tables.T_S_I__1 import table_T_S_I__1
6import pytest
7
8
9TSI1_DATA = b"""abcdefghijklmnopqrstuvxywz0123456789"""
10TSI1_UTF8_DATA = b"""abcd\xc3\xa9ghijklmnopqrstuvxywz0123456789"""
11
12
13@pytest.fixture
14def indextable():
15    table = table_T_S_I__0()
16    table.set(
17        [(0, 1, 0),         # gid 0, length=1, offset=0, text='a'
18         (1, 5, 1),         # gid 1, length=5, offset=1, text='bcdef'
19         (2, 0, 1),         # gid 2, length=0, offset=1, text=''
20         (3, 0, 1),         # gid 3, length=0, offset=1, text=''
21         (4, 8, 6)],        # gid 4, length=8, offset=6, text='ghijklmn'
22        [(0xFFFA, 2, 14),   # 'ppgm', length=2, offset=14, text='op'
23         (0xFFFB, 4, 16),   # 'cvt', length=4, offset=16, text='qrst'
24         (0xFFFC, 6, 20),   # 'reserved', length=6, offset=20, text='uvxywz'
25         (0xFFFD, 10, 26)]  # 'fpgm', length=10, offset=26, text='0123456789'
26    )
27    return table
28
29
30@pytest.fixture
31def font(indextable):
32    font = TTFont()
33    # ['a', 'b', 'c', ...]
34    ch = 0x61
35    n = len(indextable.indices)
36    font.glyphOrder = [chr(i) for i in range(ch, ch+n)]
37    font['TSI0'] = indextable
38    return font
39
40
41@pytest.fixture
42def empty_font():
43    font = TTFont()
44    font.glyphOrder = []
45    indextable = table_T_S_I__0()
46    indextable.set([], [(0xFFFA, 0, 0),
47                        (0xFFFB, 0, 0),
48                        (0xFFFC, 0, 0),
49                        (0xFFFD, 0, 0)])
50    font['TSI0'] = indextable
51    return font
52
53
54def test_decompile(font):
55    table = table_T_S_I__1()
56    table.decompile(TSI1_DATA, font)
57
58    assert table.glyphPrograms == {
59        'a': 'a',
60        'b': 'bcdef',
61        # 'c': '',  # zero-length entries are skipped
62        # 'd': '',
63        'e': 'ghijklmn'}
64    assert table.extraPrograms == {
65        'ppgm': 'op',
66        'cvt': 'qrst',
67        'reserved': 'uvxywz',
68        'fpgm': '0123456789'}
69
70
71def test_decompile_utf8(font):
72    table = table_T_S_I__1()
73    table.decompile(TSI1_UTF8_DATA, font)
74
75    assert table.glyphPrograms == {
76        'a': 'a',
77        'b': 'bcd\u00e9',
78        # 'c': '',  # zero-length entries are skipped
79        # 'd': '',
80        'e': 'ghijklmn'}
81    assert table.extraPrograms == {
82        'ppgm': 'op',
83        'cvt': 'qrst',
84        'reserved': 'uvxywz',
85        'fpgm': '0123456789'}
86
87
88def test_decompile_empty(empty_font):
89    table = table_T_S_I__1()
90    table.decompile(b"", empty_font)
91
92    assert table.glyphPrograms == {}
93    assert table.extraPrograms == {}
94
95
96def test_decompile_invalid_length(empty_font):
97    empty_font.glyphOrder = ['a']
98    empty_font['TSI0'].indices = [(0, 0x8000+1, 0)]
99
100    table = table_T_S_I__1()
101    with pytest.raises(TTLibError) as excinfo:
102        table.decompile(b'', empty_font)
103    assert excinfo.match("textLength .* must not be > 32768")
104
105
106def test_decompile_offset_past_end(empty_font):
107    empty_font.glyphOrder = ['foo', 'bar']
108    content = 'baz'
109    data = tobytes(content)
110    empty_font['TSI0'].indices = [(0, len(data), 0), (1, 1, len(data)+1)]
111
112    table = table_T_S_I__1()
113    with CapturingLogHandler(table.log, "WARNING") as captor:
114        table.decompile(data, empty_font)
115
116    # the 'bar' program is skipped because its offset > len(data)
117    assert table.glyphPrograms == {'foo': 'baz'}
118    assert any("textOffset > totalLength" in r.msg for r in captor.records)
119
120
121def test_decompile_magic_length_last_extra(empty_font):
122    indextable = empty_font['TSI0']
123    indextable.extra_indices[-1] = (0xFFFD, 0x8000, 0)
124    content = "0" * (0x8000 + 1)
125    data = tobytes(content)
126
127    table = table_T_S_I__1()
128    table.decompile(data, empty_font)
129
130    assert table.extraPrograms['fpgm'] == content
131
132
133def test_decompile_magic_length_last_glyph(empty_font):
134    empty_font.glyphOrder = ['foo', 'bar']
135    indextable = empty_font['TSI0']
136    indextable.indices = [
137        (0, 3, 0),
138        (1, 0x8000, 3)]           # the actual length of 'bar' program is
139    indextable.extra_indices = [  # the difference between the first extra's
140        (0xFFFA, 0, 0x8004),      # offset and 'bar' offset: 0x8004 - 3
141        (0xFFFB, 0, 0x8004),
142        (0xFFFC, 0, 0x8004),
143        (0xFFFD, 0, 0x8004)]
144    foo_content = "0" * 3
145    bar_content = "1" * (0x8000 + 1)
146    data = tobytes(foo_content + bar_content)
147
148    table = table_T_S_I__1()
149    table.decompile(data, empty_font)
150
151    assert table.glyphPrograms['foo'] == foo_content
152    assert table.glyphPrograms['bar'] == bar_content
153
154
155def test_decompile_magic_length_non_last(empty_font):
156    indextable = empty_font['TSI0']
157    indextable.extra_indices = [
158        (0xFFFA, 3, 0),
159        (0xFFFB, 0x8000, 3),  # the actual length of 'cvt' program is:
160        (0xFFFC, 0, 0x8004),  # nextTextOffset - textOffset: 0x8004 - 3
161        (0xFFFD, 0, 0x8004)]
162    ppgm_content = "0" * 3
163    cvt_content = "1" * (0x8000 + 1)
164    data = tobytes(ppgm_content + cvt_content)
165
166    table = table_T_S_I__1()
167    table.decompile(data, empty_font)
168
169    assert table.extraPrograms['ppgm'] == ppgm_content
170    assert table.extraPrograms['cvt'] == cvt_content
171
172    table = table_T_S_I__1()
173    with CapturingLogHandler(table.log, "WARNING") as captor:
174        table.decompile(data[:-1], empty_font)  # last entry is truncated
175    captor.assertRegex("nextTextOffset > totalLength")
176    assert table.extraPrograms['cvt'] == cvt_content[:-1]
177
178
179if __name__ == "__main__":
180    import sys
181    sys.exit(pytest.main(sys.argv))
182