Home | History | Annotate | Download | only in tables
      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