• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc.py23 import bytechr, byteord, tobytes, tostr
2from fontTools import ttLib
3from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
4from fontTools.misc import sstruct
5from fontTools.misc.textTools import safeEval, readHex
6from . import DefaultTable
7import sys
8import struct
9import array
10
11
12postFormat = """
13	>
14	formatType:			16.16F
15	italicAngle:		16.16F		# italic angle in degrees
16	underlinePosition:	h
17	underlineThickness:	h
18	isFixedPitch:		L
19	minMemType42:		L			# minimum memory if TrueType font is downloaded
20	maxMemType42:		L			# maximum memory if TrueType font is downloaded
21	minMemType1:		L			# minimum memory if Type1 font is downloaded
22	maxMemType1:		L			# maximum memory if Type1 font is downloaded
23"""
24
25postFormatSize = sstruct.calcsize(postFormat)
26
27
28class table__p_o_s_t(DefaultTable.DefaultTable):
29
30	def decompile(self, data, ttFont):
31		sstruct.unpack(postFormat, data[:postFormatSize], self)
32		data = data[postFormatSize:]
33		if self.formatType == 1.0:
34			self.decode_format_1_0(data, ttFont)
35		elif self.formatType == 2.0:
36			self.decode_format_2_0(data, ttFont)
37		elif self.formatType == 3.0:
38			self.decode_format_3_0(data, ttFont)
39		elif self.formatType == 4.0:
40			self.decode_format_4_0(data, ttFont)
41		else:
42			# supported format
43			raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
44
45	def compile(self, ttFont):
46		data = sstruct.pack(postFormat, self)
47		if self.formatType == 1.0:
48			pass # we're done
49		elif self.formatType == 2.0:
50			data = data + self.encode_format_2_0(ttFont)
51		elif self.formatType == 3.0:
52			pass # we're done
53		elif self.formatType == 4.0:
54			data = data + self.encode_format_4_0(ttFont)
55		else:
56			# supported format
57			raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
58		return data
59
60	def getGlyphOrder(self):
61		"""This function will get called by a ttLib.TTFont instance.
62		Do not call this function yourself, use TTFont().getGlyphOrder()
63		or its relatives instead!
64		"""
65		if not hasattr(self, "glyphOrder"):
66			raise ttLib.TTLibError("illegal use of getGlyphOrder()")
67		glyphOrder = self.glyphOrder
68		del self.glyphOrder
69		return glyphOrder
70
71	def decode_format_1_0(self, data, ttFont):
72		self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs]
73
74	def decode_format_2_0(self, data, ttFont):
75		numGlyphs, = struct.unpack(">H", data[:2])
76		numGlyphs = int(numGlyphs)
77		if numGlyphs > ttFont['maxp'].numGlyphs:
78			# Assume the numGlyphs field is bogus, so sync with maxp.
79			# I've seen this in one font, and if the assumption is
80			# wrong elsewhere, well, so be it: it's hard enough to
81			# work around _one_ non-conforming post format...
82			numGlyphs = ttFont['maxp'].numGlyphs
83		data = data[2:]
84		indices = array.array("H")
85		indices.frombytes(data[:2*numGlyphs])
86		if sys.byteorder != "big": indices.byteswap()
87		data = data[2*numGlyphs:]
88		self.extraNames = extraNames = unpackPStrings(data)
89		self.glyphOrder = glyphOrder = [""] * int(ttFont['maxp'].numGlyphs)
90		for glyphID in range(numGlyphs):
91			index = indices[glyphID]
92			if index > 257:
93				try:
94					name = extraNames[index-258]
95				except IndexError:
96					name = ""
97			else:
98				# fetch names from standard list
99				name = standardGlyphOrder[index]
100			glyphOrder[glyphID] = name
101		self.build_psNameMapping(ttFont)
102
103	def build_psNameMapping(self, ttFont):
104		mapping = {}
105		allNames = {}
106		for i in range(ttFont['maxp'].numGlyphs):
107			glyphName = psName = self.glyphOrder[i]
108			if glyphName == "":
109				glyphName = "glyph%.5d" % i
110			if glyphName in allNames:
111				# make up a new glyphName that's unique
112				n = allNames[glyphName]
113				while (glyphName + "#" + str(n)) in allNames:
114					n += 1
115				allNames[glyphName] = n + 1
116				glyphName = glyphName + "#" + str(n)
117
118			self.glyphOrder[i] = glyphName
119			allNames[glyphName] = 1
120			if glyphName != psName:
121				mapping[glyphName] = psName
122
123		self.mapping = mapping
124
125	def decode_format_3_0(self, data, ttFont):
126		# Setting self.glyphOrder to None will cause the TTFont object
127		# try and construct glyph names from a Unicode cmap table.
128		self.glyphOrder = None
129
130	def decode_format_4_0(self, data, ttFont):
131		from fontTools import agl
132		numGlyphs = ttFont['maxp'].numGlyphs
133		indices = array.array("H")
134		indices.frombytes(data)
135		if sys.byteorder != "big": indices.byteswap()
136		# In some older fonts, the size of the post table doesn't match
137		# the number of glyphs. Sometimes it's bigger, sometimes smaller.
138		self.glyphOrder = glyphOrder = [''] * int(numGlyphs)
139		for i in range(min(len(indices),numGlyphs)):
140			if indices[i] == 0xFFFF:
141				self.glyphOrder[i] = ''
142			elif indices[i] in agl.UV2AGL:
143				self.glyphOrder[i] = agl.UV2AGL[indices[i]]
144			else:
145				self.glyphOrder[i] = "uni%04X" % indices[i]
146		self.build_psNameMapping(ttFont)
147
148	def encode_format_2_0(self, ttFont):
149		numGlyphs = ttFont['maxp'].numGlyphs
150		glyphOrder = ttFont.getGlyphOrder()
151		assert len(glyphOrder) == numGlyphs
152		indices = array.array("H")
153		extraDict = {}
154		extraNames = self.extraNames = [
155			n for n in self.extraNames if n not in standardGlyphOrder]
156		for i in range(len(extraNames)):
157			extraDict[extraNames[i]] = i
158		for glyphID in range(numGlyphs):
159			glyphName = glyphOrder[glyphID]
160			if glyphName in self.mapping:
161				psName = self.mapping[glyphName]
162			else:
163				psName = glyphName
164			if psName in extraDict:
165				index = 258 + extraDict[psName]
166			elif psName in standardGlyphOrder:
167				index = standardGlyphOrder.index(psName)
168			else:
169				index = 258 + len(extraNames)
170				extraDict[psName] = len(extraNames)
171				extraNames.append(psName)
172			indices.append(index)
173		if sys.byteorder != "big": indices.byteswap()
174		return struct.pack(">H", numGlyphs) + indices.tobytes() + packPStrings(extraNames)
175
176	def encode_format_4_0(self, ttFont):
177		from fontTools import agl
178		numGlyphs = ttFont['maxp'].numGlyphs
179		glyphOrder = ttFont.getGlyphOrder()
180		assert len(glyphOrder) == numGlyphs
181		indices = array.array("H")
182		for glyphID in glyphOrder:
183			glyphID = glyphID.split('#')[0]
184			if glyphID in agl.AGL2UV:
185				indices.append(agl.AGL2UV[glyphID])
186			elif len(glyphID) == 7 and glyphID[:3] == 'uni':
187				indices.append(int(glyphID[3:],16))
188			else:
189				indices.append(0xFFFF)
190		if sys.byteorder != "big": indices.byteswap()
191		return indices.tobytes()
192
193	def toXML(self, writer, ttFont):
194		formatstring, names, fixes = sstruct.getformat(postFormat)
195		for name in names:
196			value = getattr(self, name)
197			writer.simpletag(name, value=value)
198			writer.newline()
199		if hasattr(self, "mapping"):
200			writer.begintag("psNames")
201			writer.newline()
202			writer.comment("This file uses unique glyph names based on the information\n"
203						"found in the 'post' table. Since these names might not be unique,\n"
204						"we have to invent artificial names in case of clashes. In order to\n"
205						"be able to retain the original information, we need a name to\n"
206						"ps name mapping for those cases where they differ. That's what\n"
207						"you see below.\n")
208			writer.newline()
209			items = sorted(self.mapping.items())
210			for name, psName in items:
211				writer.simpletag("psName", name=name, psName=psName)
212				writer.newline()
213			writer.endtag("psNames")
214			writer.newline()
215		if hasattr(self, "extraNames"):
216			writer.begintag("extraNames")
217			writer.newline()
218			writer.comment("following are the name that are not taken from the standard Mac glyph order")
219			writer.newline()
220			for name in self.extraNames:
221				writer.simpletag("psName", name=name)
222				writer.newline()
223			writer.endtag("extraNames")
224			writer.newline()
225		if hasattr(self, "data"):
226			writer.begintag("hexdata")
227			writer.newline()
228			writer.dumphex(self.data)
229			writer.endtag("hexdata")
230			writer.newline()
231
232	def fromXML(self, name, attrs, content, ttFont):
233		if name not in ("psNames", "extraNames", "hexdata"):
234			setattr(self, name, safeEval(attrs["value"]))
235		elif name == "psNames":
236			self.mapping = {}
237			for element in content:
238				if not isinstance(element, tuple):
239					continue
240				name, attrs, content = element
241				if name == "psName":
242					self.mapping[attrs["name"]] = attrs["psName"]
243		elif name == "extraNames":
244			self.extraNames = []
245			for element in content:
246				if not isinstance(element, tuple):
247					continue
248				name, attrs, content = element
249				if name == "psName":
250					self.extraNames.append(attrs["name"])
251		else:
252			self.data = readHex(content)
253
254
255def unpackPStrings(data):
256	strings = []
257	index = 0
258	dataLen = len(data)
259	while index < dataLen:
260		length = byteord(data[index])
261		strings.append(tostr(data[index+1:index+1+length], encoding="latin1"))
262		index = index + 1 + length
263	return strings
264
265
266def packPStrings(strings):
267	data = b""
268	for s in strings:
269		data = data + bytechr(len(s)) + tobytes(s, encoding="latin1")
270	return data
271