_p_o_s_t.py revision 180ace6a5ff1399ec53bc696e8bef7cce6eef39a
1import sys
2from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
3from . import DefaultTable
4import struct
5from fontTools.misc import sstruct
6import array
7from fontTools import ttLib
8from fontTools.misc.textTools import safeEval, readHex
9from types import TupleType
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		else:
40			# supported format
41			raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
42
43	def compile(self, ttFont):
44		data = sstruct.pack(postFormat, self)
45		if self.formatType == 1.0:
46			pass # we're done
47		elif self.formatType == 2.0:
48			data = data + self.encode_format_2_0(ttFont)
49		elif self.formatType == 3.0:
50			pass # we're done
51		else:
52			# supported format
53			raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
54		return data
55
56	def getGlyphOrder(self):
57		"""This function will get called by a ttLib.TTFont instance.
58		Do not call this function yourself, use TTFont().getGlyphOrder()
59		or its relatives instead!
60		"""
61		if not hasattr(self, "glyphOrder"):
62			raise ttLib.TTLibError, "illegal use of getGlyphOrder()"
63		glyphOrder = self.glyphOrder
64		del self.glyphOrder
65		return glyphOrder
66
67	def decode_format_1_0(self, data, ttFont):
68		self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs]
69
70	def decode_format_2_0(self, data, ttFont):
71		numGlyphs, = struct.unpack(">H", data[:2])
72		numGlyphs = int(numGlyphs)
73		if numGlyphs > ttFont['maxp'].numGlyphs:
74			# Assume the numGlyphs field is bogus, so sync with maxp.
75			# I've seen this in one font, and if the assumption is
76			# wrong elsewhere, well, so be it: it's hard enough to
77			# work around _one_ non-conforming post format...
78			numGlyphs = ttFont['maxp'].numGlyphs
79		data = data[2:]
80		indices = array.array("H")
81		indices.fromstring(data[:2*numGlyphs])
82		if sys.byteorder != "big":
83			indices.byteswap()
84		data = data[2*numGlyphs:]
85		self.extraNames = extraNames = unpackPStrings(data)
86		self.glyphOrder = glyphOrder = [None] * int(ttFont['maxp'].numGlyphs)
87		for glyphID in range(numGlyphs):
88			index = indices[glyphID]
89			if index > 257:
90				name = extraNames[index-258]
91			else:
92				# fetch names from standard list
93				name = standardGlyphOrder[index]
94			glyphOrder[glyphID] = name
95		#AL990511: code added to handle the case of new glyphs without
96		#          entries into the 'post' table
97		if numGlyphs < ttFont['maxp'].numGlyphs:
98			for i in range(numGlyphs, ttFont['maxp'].numGlyphs):
99				glyphOrder[i] = "glyph#%.5d" % i
100				self.extraNames.append(glyphOrder[i])
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 in allNames:
109				# make up a new glyphName that's unique
110				n = allNames[glyphName]
111				allNames[glyphName] = n + 1
112				glyphName = glyphName + "#" + `n`
113				self.glyphOrder[i] = glyphName
114				mapping[glyphName] = psName
115			else:
116				allNames[glyphName] = 1
117		self.mapping = mapping
118
119	def decode_format_3_0(self, data, ttFont):
120		# Setting self.glyphOrder to None will cause the TTFont object
121		# try and construct glyph names from a Unicode cmap table.
122		self.glyphOrder = None
123
124	def encode_format_2_0(self, ttFont):
125		numGlyphs = ttFont['maxp'].numGlyphs
126		glyphOrder = ttFont.getGlyphOrder()
127		assert len(glyphOrder) == numGlyphs
128		indices = array.array("H")
129		extraDict = {}
130		extraNames = self.extraNames
131		for i in range(len(extraNames)):
132			extraDict[extraNames[i]] = i
133		for glyphID in range(numGlyphs):
134			glyphName = glyphOrder[glyphID]
135			if glyphName in self.mapping:
136				psName = self.mapping[glyphName]
137			else:
138				psName = glyphName
139			if psName in extraDict:
140				index = 258 + extraDict[psName]
141			elif psName in standardGlyphOrder:
142				index = standardGlyphOrder.index(psName)
143			else:
144				index = 258 + len(extraNames)
145				extraDict[psName] = len(extraNames)
146				extraNames.append(psName)
147			indices.append(index)
148		if sys.byteorder != "big":
149			indices.byteswap()
150		return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames)
151
152	def toXML(self, writer, ttFont):
153		formatstring, names, fixes = sstruct.getformat(postFormat)
154		for name in names:
155			value = getattr(self, name)
156			writer.simpletag(name, value=value)
157			writer.newline()
158		if hasattr(self, "mapping"):
159			writer.begintag("psNames")
160			writer.newline()
161			writer.comment("This file uses unique glyph names based on the information\n"
162						"found in the 'post' table. Since these names might not be unique,\n"
163						"we have to invent artificial names in case of clashes. In order to\n"
164						"be able to retain the original information, we need a name to\n"
165						"ps name mapping for those cases where they differ. That's what\n"
166						"you see below.\n")
167			writer.newline()
168			items = self.mapping.items()
169			items.sort()
170			for name, psName in items:
171				writer.simpletag("psName", name=name, psName=psName)
172				writer.newline()
173			writer.endtag("psNames")
174			writer.newline()
175		if hasattr(self, "extraNames"):
176			writer.begintag("extraNames")
177			writer.newline()
178			writer.comment("following are the name that are not taken from the standard Mac glyph order")
179			writer.newline()
180			for name in self.extraNames:
181				writer.simpletag("psName", name=name)
182				writer.newline()
183			writer.endtag("extraNames")
184			writer.newline()
185		if hasattr(self, "data"):
186			writer.begintag("hexdata")
187			writer.newline()
188			writer.dumphex(self.data)
189			writer.endtag("hexdata")
190			writer.newline()
191
192	def fromXML(self, (name, attrs, content), ttFont):
193		if name not in ("psNames", "extraNames", "hexdata"):
194			setattr(self, name, safeEval(attrs["value"]))
195		elif name == "psNames":
196			self.mapping = {}
197			for element in content:
198				if type(element) != TupleType:
199					continue
200				name, attrs, content = element
201				if name == "psName":
202					self.mapping[attrs["name"]] = attrs["psName"]
203		elif name == "extraNames":
204			self.extraNames = []
205			for element in content:
206				if type(element) != TupleType:
207					continue
208				name, attrs, content = element
209				if name == "psName":
210					self.extraNames.append(attrs["name"])
211		else:
212			self.data = readHex(content)
213
214
215def unpackPStrings(data):
216	strings = []
217	index = 0
218	dataLen = len(data)
219	while index < dataLen:
220		length = ord(data[index])
221		strings.append(data[index+1:index+1+length])
222		index = index + 1 + length
223	return strings
224
225
226def packPStrings(strings):
227	data = ""
228	for s in strings:
229		data = data + chr(len(s)) + s
230	return data
231
232