• 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"""
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