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