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