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""" 7 8from . import DefaultTable 9from fontTools.misc.loggingTools import LogMixin 10from fontTools.misc.textTools import strjoin, tobytes, tostr 11 12 13class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable): 14 extras = {0xFFFA: "ppgm", 0xFFFB: "cvt", 0xFFFC: "reserved", 0xFFFD: "fpgm"} 15 16 indextable = "TSI0" 17 18 def decompile(self, data, ttFont): 19 totalLength = len(data) 20 indextable = ttFont[self.indextable] 21 for indices, isExtra in zip( 22 (indextable.indices, indextable.extra_indices), (False, True) 23 ): 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 ) 65 nextTextOffset = totalLength 66 textLength = nextTextOffset - textOffset 67 else: 68 from fontTools import ttLib 69 70 raise ttLib.TTLibError( 71 "%r textLength (%d) must not be > 32768" % (name, textLength) 72 ) 73 text = data[textOffset : textOffset + textLength] 74 assert len(text) == textLength 75 text = tostr(text, encoding="utf-8") 76 if text: 77 programs[name] = text 78 if isExtra: 79 self.extraPrograms = programs 80 else: 81 self.glyphPrograms = programs 82 83 def compile(self, ttFont): 84 if not hasattr(self, "glyphPrograms"): 85 self.glyphPrograms = {} 86 self.extraPrograms = {} 87 data = b"" 88 indextable = ttFont[self.indextable] 89 glyphNames = ttFont.getGlyphOrder() 90 91 indices = [] 92 for i in range(len(glyphNames)): 93 if len(data) % 2: 94 data = ( 95 data + b"\015" 96 ) # align on 2-byte boundaries, fill with return chars. Yum. 97 name = glyphNames[i] 98 if name in self.glyphPrograms: 99 text = tobytes(self.glyphPrograms[name], encoding="utf-8") 100 else: 101 text = b"" 102 textLength = len(text) 103 if textLength >= 0x8000: 104 textLength = 0x8000 105 indices.append((i, textLength, len(data))) 106 data = data + text 107 108 extra_indices = [] 109 codes = sorted(self.extras.items()) 110 for i in range(len(codes)): 111 if len(data) % 2: 112 data = ( 113 data + b"\015" 114 ) # align on 2-byte boundaries, fill with return chars. 115 code, name = codes[i] 116 if name in self.extraPrograms: 117 text = tobytes(self.extraPrograms[name], encoding="utf-8") 118 else: 119 text = b"" 120 textLength = len(text) 121 if textLength >= 0x8000: 122 textLength = 0x8000 123 extra_indices.append((code, textLength, len(data))) 124 data = data + text 125 indextable.set(indices, extra_indices) 126 return data 127 128 def toXML(self, writer, ttFont): 129 names = sorted(self.glyphPrograms.keys()) 130 writer.newline() 131 for name in names: 132 text = self.glyphPrograms[name] 133 if not text: 134 continue 135 writer.begintag("glyphProgram", name=name) 136 writer.newline() 137 writer.write_noindent(text.replace("\r", "\n")) 138 writer.newline() 139 writer.endtag("glyphProgram") 140 writer.newline() 141 writer.newline() 142 extra_names = sorted(self.extraPrograms.keys()) 143 for name in extra_names: 144 text = self.extraPrograms[name] 145 if not text: 146 continue 147 writer.begintag("extraProgram", name=name) 148 writer.newline() 149 writer.write_noindent(text.replace("\r", "\n")) 150 writer.newline() 151 writer.endtag("extraProgram") 152 writer.newline() 153 writer.newline() 154 155 def fromXML(self, name, attrs, content, ttFont): 156 if not hasattr(self, "glyphPrograms"): 157 self.glyphPrograms = {} 158 self.extraPrograms = {} 159 lines = strjoin(content).replace("\r", "\n").split("\n") 160 text = "\r".join(lines[1:-1]) 161 if name == "glyphProgram": 162 self.glyphPrograms[attrs["name"]] = text 163 elif name == "extraProgram": 164 self.extraPrograms[attrs["name"]] = text 165