1 from __future__ import ( 2 print_function, division, absolute_import, unicode_literals 3 ) 4 from fontTools.misc.py23 import unichr, tobytes 5 from fontTools.misc.loggingTools import CapturingLogHandler 6 from fontTools.ttLib import TTFont, TTLibError 7 from fontTools.ttLib.tables.T_S_I__0 import table_T_S_I__0 8 from fontTools.ttLib.tables.T_S_I__1 import table_T_S_I__1 9 import pytest 10 11 12 TSI1_DATA = b"""abcdefghijklmnopqrstuvxywz0123456789""" 13 TSI1_UTF8_DATA = b"""abcd\xc3\xa9ghijklmnopqrstuvxywz0123456789""" 14 15 16 @pytest.fixture 17 def 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 34 def 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 45 def 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 57 def 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 74 def 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 91 def 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 99 def 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 109 def 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 124 def 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 136 def 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 158 def 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 182 if __name__ == "__main__": 183 import sys 184 sys.exit(pytest.main(sys.argv)) 185