from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import unichr from fontTools.misc.loggingTools import CapturingLogHandler from fontTools.ttLib import TTFont, TTLibError from fontTools.ttLib.tables.T_S_I__0 import table_T_S_I__0 from fontTools.ttLib.tables.T_S_I__1 import table_T_S_I__1 import pytest TSI1_DATA = b"""abcdefghijklmnopqrstuvxywz0123456789""" @pytest.fixture def indextable(): table = table_T_S_I__0() table.set( [(0, 1, 0), # gid 0, length=1, offset=0, text='a' (1, 5, 1), # gid 1, length=5, offset=1, text='bcdef' (2, 0, 1), # gid 2, length=0, offset=1, text='' (3, 0, 1), # gid 3, length=0, offset=1, text='' (4, 8, 6)], # gid 4, length=8, offset=6, text='ghijklmn' [(0xFFFA, 2, 14), # 'ppgm', length=2, offset=14, text='op' (0xFFFB, 4, 16), # 'cvt', length=4, offset=16, text='qrst' (0xFFFC, 6, 20), # 'reserved', length=6, offset=20, text='uvxywz' (0xFFFD, 10, 26)] # 'fpgm', length=10, offset=26, text='0123456789' ) return table @pytest.fixture def font(indextable): font = TTFont() # ['a', 'b', 'c', ...] ch = 0x61 n = len(indextable.indices) font.glyphOrder = [unichr(i) for i in range(ch, ch+n)] font['TSI0'] = indextable return font @pytest.fixture def empty_font(): font = TTFont() font.glyphOrder = [] indextable = table_T_S_I__0() indextable.set([], [(0xFFFA, 0, 0), (0xFFFB, 0, 0), (0xFFFC, 0, 0), (0xFFFD, 0, 0)]) font['TSI0'] = indextable return font def test_decompile(font): table = table_T_S_I__1() table.decompile(TSI1_DATA, font) assert table.glyphPrograms == { 'a': b'a', 'b': b'bcdef', # 'c': b'', # zero-length entries are skipped # 'd': b'', 'e': b'ghijklmn'} assert table.extraPrograms == { 'ppgm': b'op', 'cvt': b'qrst', 'reserved': b'uvxywz', 'fpgm': b'0123456789'} def test_decompile_empty(empty_font): table = table_T_S_I__1() table.decompile(b"", empty_font) assert table.glyphPrograms == {} assert table.extraPrograms == {} def test_decompile_invalid_length(empty_font): empty_font.glyphOrder = ['a'] empty_font['TSI0'].indices = [(0, 0x8000+1, 0)] table = table_T_S_I__1() with pytest.raises(TTLibError) as excinfo: table.decompile(b'', empty_font) assert excinfo.match("textLength .* must not be > 32768") def test_decompile_offset_past_end(empty_font): empty_font.glyphOrder = ['foo', 'bar'] data = b'baz' empty_font['TSI0'].indices = [(0, len(data), 0), (1, 1, len(data)+1)] table = table_T_S_I__1() with CapturingLogHandler(table.log, "WARNING") as captor: table.decompile(data, empty_font) # the 'bar' program is skipped because its offset > len(data) assert table.glyphPrograms == {'foo': b'baz'} assert any("textOffset > totalLength" in r.msg for r in captor.records) def test_decompile_magic_length_last_extra(empty_font): indextable = empty_font['TSI0'] indextable.extra_indices[-1] = (0xFFFD, 0x8000, 0) data = b"0" * (0x8000 + 1) table = table_T_S_I__1() table.decompile(data, empty_font) assert table.extraPrograms['fpgm'] == data def test_decompile_magic_length_last_glyph(empty_font): empty_font.glyphOrder = ['foo', 'bar'] indextable = empty_font['TSI0'] indextable.indices = [ (0, 3, 0), (1, 0x8000, 3)] # the actual length of 'bar' program is indextable.extra_indices = [ # the difference between the first extra's (0xFFFA, 0, 0x8004), # offset and 'bar' offset: 0x8004 - 3 (0xFFFB, 0, 0x8004), (0xFFFC, 0, 0x8004), (0xFFFD, 0, 0x8004)] foo_data = b"0" * 3 bar_data = b"1" * (0x8000 + 1) data = foo_data + bar_data table = table_T_S_I__1() table.decompile(data, empty_font) assert table.glyphPrograms['foo'] == foo_data assert table.glyphPrograms['bar'] == bar_data def test_decompile_magic_length_non_last(empty_font): indextable = empty_font['TSI0'] indextable.extra_indices = [ (0xFFFA, 3, 0), (0xFFFB, 0x8000, 3), # the actual length of 'cvt' program is: (0xFFFC, 0, 0x8004), # nextTextOffset - textOffset: 0x8004 - 3 (0xFFFD, 0, 0x8004)] ppgm_data = b"0" * 3 cvt_data = b"1" * (0x8000 + 1) data = ppgm_data + cvt_data table = table_T_S_I__1() table.decompile(data, empty_font) assert table.extraPrograms['ppgm'] == ppgm_data assert table.extraPrograms['cvt'] == cvt_data table = table_T_S_I__1() with CapturingLogHandler(table.log, "WARNING") as captor: table.decompile(data[:-1], empty_font) # last entry is truncated captor.assertRegex("nextTextOffset > totalLength") assert table.extraPrograms['cvt'] == cvt_data[:-1] if __name__ == "__main__": import sys sys.exit(pytest.main(sys.argv))