• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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