• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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": indices.byteswap()
88		data = data[2*numGlyphs:]
89		self.extraNames = extraNames = unpackPStrings(data)
90		self.glyphOrder = glyphOrder = [""] * int(ttFont['maxp'].numGlyphs)
91		for glyphID in range(numGlyphs):
92			index = indices[glyphID]
93			if index > 257:
94				try:
95					name = extraNames[index-258]
96				except IndexError:
97					name = ""
98			else:
99				# fetch names from standard list
100				name = standardGlyphOrder[index]
101			glyphOrder[glyphID] = name
102		self.build_psNameMapping(ttFont)
103
104	def build_psNameMapping(self, ttFont):
105		mapping = {}
106		allNames = {}
107		for i in range(ttFont['maxp'].numGlyphs):
108			glyphName = psName = self.glyphOrder[i]
109			if glyphName == "":
110				glyphName = "glyph%.5d" % i
111			if glyphName in allNames:
112				# make up a new glyphName that's unique
113				n = allNames[glyphName]
114				while (glyphName + "#" + str(n)) in allNames:
115					n += 1
116				allNames[glyphName] = n + 1
117				glyphName = glyphName + "#" + str(n)
118
119			self.glyphOrder[i] = glyphName
120			allNames[glyphName] = 1
121			if glyphName != psName:
122				mapping[glyphName] = psName
123
124		self.mapping = mapping
125
126	def decode_format_3_0(self, data, ttFont):
127		# Setting self.glyphOrder to None will cause the TTFont object
128		# try and construct glyph names from a Unicode cmap table.
129		self.glyphOrder = None
130
131	def decode_format_4_0(self, data, ttFont):
132		from fontTools import agl
133		numGlyphs = ttFont['maxp'].numGlyphs
134		indices = array.array("H")
135		indices.fromstring(data)
136		if sys.byteorder != "big": indices.byteswap()
137		# In some older fonts, the size of the post table doesn't match
138		# the number of glyphs. Sometimes it's bigger, sometimes smaller.
139		self.glyphOrder = glyphOrder = [''] * int(numGlyphs)
140		for i in range(min(len(indices),numGlyphs)):
141			if indices[i] == 0xFFFF:
142				self.glyphOrder[i] = ''
143			elif indices[i] in agl.UV2AGL:
144				self.glyphOrder[i] = agl.UV2AGL[indices[i]]
145			else:
146				self.glyphOrder[i] = "uni%04X" % indices[i]
147		self.build_psNameMapping(ttFont)
148
149	def encode_format_2_0(self, ttFont):
150		numGlyphs = ttFont['maxp'].numGlyphs
151		glyphOrder = ttFont.getGlyphOrder()
152		assert len(glyphOrder) == numGlyphs
153		indices = array.array("H")
154		extraDict = {}
155		extraNames = self.extraNames = [
156			n for n in self.extraNames if n not in standardGlyphOrder]
157		for i in range(len(extraNames)):
158			extraDict[extraNames[i]] = i
159		for glyphID in range(numGlyphs):
160			glyphName = glyphOrder[glyphID]
161			if glyphName in self.mapping:
162				psName = self.mapping[glyphName]
163			else:
164				psName = glyphName
165			if psName in extraDict:
166				index = 258 + extraDict[psName]
167			elif psName in standardGlyphOrder:
168				index = standardGlyphOrder.index(psName)
169			else:
170				index = 258 + len(extraNames)
171				extraDict[psName] = len(extraNames)
172				extraNames.append(psName)
173			indices.append(index)
174		if sys.byteorder != "big": indices.byteswap()
175		return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames)
176
177	def encode_format_4_0(self, ttFont):
178		from fontTools import agl
179		numGlyphs = ttFont['maxp'].numGlyphs
180		glyphOrder = ttFont.getGlyphOrder()
181		assert len(glyphOrder) == numGlyphs
182		indices = array.array("H")
183		for glyphID in glyphOrder:
184			glyphID = glyphID.split('#')[0]
185			if glyphID in agl.AGL2UV:
186				indices.append(agl.AGL2UV[glyphID])
187			elif len(glyphID) == 7 and glyphID[:3] == 'uni':
188				indices.append(int(glyphID[3:],16))
189			else:
190				indices.append(0xFFFF)
191		if sys.byteorder != "big": 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