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