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