Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools import ttLib
      4 from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
      5 from fontTools.misc import sstruct
      6 from fontTools.misc.textTools import safeEval, readHex
      7 from . import DefaultTable
      8 import sys
      9 import struct
     10 import array
     11 
     12 
     13 postFormat = """
     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 
     26 postFormatSize = sstruct.calcsize(postFormat)
     27 
     28 
     29 class 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 
    256 def 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 
    267 def 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