1""" TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT) 2tool to store its hinting source data. 3 4TSI1 contains the text of the glyph programs in the form of low-level assembly 5code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'. 6""" 7from __future__ import print_function, division, absolute_import 8from fontTools.misc.py23 import * 9from . import DefaultTable 10from fontTools.misc.loggingTools import LogMixin 11 12 13class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable): 14 15 extras = {0xfffa: "ppgm", 0xfffb: "cvt", 0xfffc: "reserved", 0xfffd: "fpgm"} 16 17 indextable = "TSI0" 18 19 def decompile(self, data, ttFont): 20 totalLength = len(data) 21 indextable = ttFont[self.indextable] 22 for indices, isExtra in zip( 23 (indextable.indices, indextable.extra_indices), (False, True)): 24 programs = {} 25 for i, (glyphID, textLength, textOffset) in enumerate(indices): 26 if isExtra: 27 name = self.extras[glyphID] 28 else: 29 name = ttFont.getGlyphName(glyphID) 30 if textOffset > totalLength: 31 self.log.warning("textOffset > totalLength; %r skipped" % name) 32 continue 33 if textLength < 0x8000: 34 # If the length stored in the record is less than 32768, then use 35 # that as the length of the record. 36 pass 37 elif textLength == 0x8000: 38 # If the length is 32768, compute the actual length as follows: 39 isLast = i == (len(indices)-1) 40 if isLast: 41 if isExtra: 42 # For the last "extra" record (the very last record of the 43 # table), the length is the difference between the total 44 # length of the TSI1 table and the textOffset of the final 45 # record. 46 nextTextOffset = totalLength 47 else: 48 # For the last "normal" record (the last record just prior 49 # to the record containing the "magic number"), the length 50 # is the difference between the textOffset of the record 51 # following the "magic number" (0xFFFE) record (i.e. the 52 # first "extra" record), and the textOffset of the last 53 # "normal" record. 54 nextTextOffset = indextable.extra_indices[0][2] 55 else: 56 # For all other records with a length of 0x8000, the length is 57 # the difference between the textOffset of the record in 58 # question and the textOffset of the next record. 59 nextTextOffset = indices[i+1][2] 60 assert nextTextOffset >= textOffset, "entries not sorted by offset" 61 if nextTextOffset > totalLength: 62 self.log.warning( 63 "nextTextOffset > totalLength; %r truncated" % name) 64 nextTextOffset = totalLength 65 textLength = nextTextOffset - textOffset 66 else: 67 from fontTools import ttLib 68 raise ttLib.TTLibError( 69 "%r textLength (%d) must not be > 32768" % (name, textLength)) 70 text = data[textOffset:textOffset+textLength] 71 assert len(text) == textLength 72 text = tounicode(text, encoding='utf-8') 73 if text: 74 programs[name] = text 75 if isExtra: 76 self.extraPrograms = programs 77 else: 78 self.glyphPrograms = programs 79 80 def compile(self, ttFont): 81 if not hasattr(self, "glyphPrograms"): 82 self.glyphPrograms = {} 83 self.extraPrograms = {} 84 data = b'' 85 indextable = ttFont[self.indextable] 86 glyphNames = ttFont.getGlyphOrder() 87 88 indices = [] 89 for i in range(len(glyphNames)): 90 if len(data) % 2: 91 data = data + b"\015" # align on 2-byte boundaries, fill with return chars. Yum. 92 name = glyphNames[i] 93 if name in self.glyphPrograms: 94 text = tobytes(self.glyphPrograms[name], encoding="utf-8") 95 else: 96 text = b"" 97 textLength = len(text) 98 if textLength >= 0x8000: 99 textLength = 0x8000 100 indices.append((i, textLength, len(data))) 101 data = data + text 102 103 extra_indices = [] 104 codes = sorted(self.extras.items()) 105 for i in range(len(codes)): 106 if len(data) % 2: 107 data = data + b"\015" # align on 2-byte boundaries, fill with return chars. 108 code, name = codes[i] 109 if name in self.extraPrograms: 110 text = tobytes(self.extraPrograms[name], encoding="utf-8") 111 else: 112 text = b"" 113 textLength = len(text) 114 if textLength >= 0x8000: 115 textLength = 0x8000 116 extra_indices.append((code, textLength, len(data))) 117 data = data + text 118 indextable.set(indices, extra_indices) 119 return data 120 121 def toXML(self, writer, ttFont): 122 names = sorted(self.glyphPrograms.keys()) 123 writer.newline() 124 for name in names: 125 text = self.glyphPrograms[name] 126 if not text: 127 continue 128 writer.begintag("glyphProgram", name=name) 129 writer.newline() 130 writer.write_noindent(text.replace("\r", "\n")) 131 writer.newline() 132 writer.endtag("glyphProgram") 133 writer.newline() 134 writer.newline() 135 extra_names = sorted(self.extraPrograms.keys()) 136 for name in extra_names: 137 text = self.extraPrograms[name] 138 if not text: 139 continue 140 writer.begintag("extraProgram", name=name) 141 writer.newline() 142 writer.write_noindent(text.replace("\r", "\n")) 143 writer.newline() 144 writer.endtag("extraProgram") 145 writer.newline() 146 writer.newline() 147 148 def fromXML(self, name, attrs, content, ttFont): 149 if not hasattr(self, "glyphPrograms"): 150 self.glyphPrograms = {} 151 self.extraPrograms = {} 152 lines = strjoin(content).replace("\r", "\n").split("\n") 153 text = '\r'.join(lines[1:-1]) 154 if name == "glyphProgram": 155 self.glyphPrograms[attrs["name"]] = text 156 elif name == "extraProgram": 157 self.extraPrograms[attrs["name"]] = text 158