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