blob: 36b1f76993f33e883cc8dbc1b445ab5a1b15b03d [file] [log] [blame]
Behdad Esfahbod1ae29592014-01-14 15:07:50 +08001from __future__ import print_function, division, absolute_import
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05002from fontTools.misc.py23 import *
Just7842e561999-12-16 21:34:53 +00003from fontTools import ttLib
Behdad Esfahbod7ed91ec2013-11-27 15:16:28 -05004from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
5from fontTools.misc import sstruct
Just7842e561999-12-16 21:34:53 +00006from fontTools.misc.textTools import safeEval, readHex
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05007from . import DefaultTable
8import sys
9import struct
10import array
Just7842e561999-12-16 21:34:53 +000011
12
13postFormat = """
14 >
15 formatType: 16.16F
16 italicAngle: 16.16F # italic angle in degrees
17 underlinePosition: h
18 underlineThickness: h
fcoiffie04985bf2006-01-12 14:04:40 +000019 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
Just7842e561999-12-16 21:34:53 +000024"""
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)
Olivier Berten4a73f8b2014-01-28 14:37:15 +010040 elif self.formatType == 4.0:
41 self.decode_format_4_0(data, ttFont)
Just7842e561999-12-16 21:34:53 +000042 else:
43 # supported format
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050044 raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
Just7842e561999-12-16 21:34:53 +000045
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
Olivier Berten4a73f8b2014-01-28 14:37:15 +010054 elif self.formatType == 4.0:
55 data = data + self.encode_format_4_0(ttFont)
Just7842e561999-12-16 21:34:53 +000056 else:
57 # supported format
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050058 raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType)
Just7842e561999-12-16 21:34:53 +000059 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"):
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050067 raise ttLib.TTLibError("illegal use of getGlyphOrder()")
Just7842e561999-12-16 21:34:53 +000068 glyphOrder = self.glyphOrder
69 del self.glyphOrder
70 return glyphOrder
71
72 def decode_format_1_0(self, data, ttFont):
jvrc2b05cc2001-11-05 19:32:30 +000073 self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs]
Just7842e561999-12-16 21:34:53 +000074
75 def decode_format_2_0(self, data, ttFont):
76 numGlyphs, = struct.unpack(">H", data[:2])
77 numGlyphs = int(numGlyphs)
jvrf9104bc2002-01-17 09:36:30 +000078 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
Just7842e561999-12-16 21:34:53 +000084 data = data[2:]
85 indices = array.array("H")
86 indices.fromstring(data[:2*numGlyphs])
Behdad Esfahbod180ace62013-11-27 02:40:30 -050087 if sys.byteorder != "big":
Just7842e561999-12-16 21:34:53 +000088 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]
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500113 if glyphName in allNames:
Just7842e561999-12-16 21:34:53 +0000114 # make up a new glyphName that's unique
Behdad Esfahbod85be2e02013-07-24 19:04:57 -0400115 n = allNames[glyphName]
116 allNames[glyphName] = n + 1
Behdad Esfahboddc7e6f32013-11-27 02:44:56 -0500117 glyphName = glyphName + "#" + repr(n)
Just7842e561999-12-16 21:34:53 +0000118 self.glyphOrder[i] = glyphName
119 mapping[glyphName] = psName
Behdad Esfahbod85be2e02013-07-24 19:04:57 -0400120 else:
121 allNames[glyphName] = 1
Just7842e561999-12-16 21:34:53 +0000122 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
Olivier Berten4a73f8b2014-01-28 14:37:15 +0100129 def decode_format_4_0(self, data, ttFont):
130 from fontTools import agl
131 numGlyphs = ttFont['maxp'].numGlyphs
132 indices = struct.unpack(">"+str(int(len(data)/2))+"H", data)
133 # In some older fonts, the size of the post table doesn't match
134 # the number of glyphs. Sometimes it's bigger, sometimes smaller.
135 self.glyphOrder = glyphOrder = [''] * int(numGlyphs)
136 for i in range(min(len(indices),numGlyphs)):
137 if indices[i] == 0xFFFF:
138 self.glyphOrder[i] = ''
139 elif indices[i] in agl.UV2AGL:
140 self.glyphOrder[i] = agl.UV2AGL[indices[i]]
141 else:
142 self.glyphOrder[i] = "uni%04X" % indices[i]
143 self.build_psNameMapping(ttFont)
144
Just7842e561999-12-16 21:34:53 +0000145 def encode_format_2_0(self, ttFont):
146 numGlyphs = ttFont['maxp'].numGlyphs
147 glyphOrder = ttFont.getGlyphOrder()
148 assert len(glyphOrder) == numGlyphs
149 indices = array.array("H")
jvr0d762b02002-05-04 22:03:05 +0000150 extraDict = {}
151 extraNames = self.extraNames
152 for i in range(len(extraNames)):
153 extraDict[extraNames[i]] = i
Just7842e561999-12-16 21:34:53 +0000154 for glyphID in range(numGlyphs):
155 glyphName = glyphOrder[glyphID]
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500156 if glyphName in self.mapping:
Just7842e561999-12-16 21:34:53 +0000157 psName = self.mapping[glyphName]
158 else:
159 psName = glyphName
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500160 if psName in extraDict:
jvr0d762b02002-05-04 22:03:05 +0000161 index = 258 + extraDict[psName]
Just7842e561999-12-16 21:34:53 +0000162 elif psName in standardGlyphOrder:
163 index = standardGlyphOrder.index(psName)
164 else:
jvr0d762b02002-05-04 22:03:05 +0000165 index = 258 + len(extraNames)
166 extraDict[psName] = len(extraNames)
167 extraNames.append(psName)
Just7842e561999-12-16 21:34:53 +0000168 indices.append(index)
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500169 if sys.byteorder != "big":
Just7842e561999-12-16 21:34:53 +0000170 indices.byteswap()
jvr0d762b02002-05-04 22:03:05 +0000171 return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames)
Just7842e561999-12-16 21:34:53 +0000172
Olivier Berten4a73f8b2014-01-28 14:37:15 +0100173 def encode_format_4_0(self, ttFont):
174 from fontTools import agl
175 numGlyphs = ttFont['maxp'].numGlyphs
176 glyphOrder = ttFont.getGlyphOrder()
177 assert len(glyphOrder) == numGlyphs
178 data = ''
179 for glyphID in glyphOrder:
180 glyphID = glyphID.split('#')[0]
181 if glyphID in agl.AGL2UV:
182 data += struct.pack(">H", agl.AGL2UV[glyphID])
183 elif len(glyphID) == 7 and glyphID[:3] == 'uni':
184 data += struct.pack(">H", int(glyphID[3:],16))
185 else:
186 data += struct.pack(">H", 0xFFFF)
187 return data
188
Just7842e561999-12-16 21:34:53 +0000189 def toXML(self, writer, ttFont):
190 formatstring, names, fixes = sstruct.getformat(postFormat)
191 for name in names:
192 value = getattr(self, name)
193 writer.simpletag(name, value=value)
194 writer.newline()
195 if hasattr(self, "mapping"):
196 writer.begintag("psNames")
197 writer.newline()
198 writer.comment("This file uses unique glyph names based on the information\n"
199 "found in the 'post' table. Since these names might not be unique,\n"
200 "we have to invent artificial names in case of clashes. In order to\n"
201 "be able to retain the original information, we need a name to\n"
202 "ps name mapping for those cases where they differ. That's what\n"
203 "you see below.\n")
204 writer.newline()
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500205 items = sorted(self.mapping.items())
Just7842e561999-12-16 21:34:53 +0000206 for name, psName in items:
207 writer.simpletag("psName", name=name, psName=psName)
208 writer.newline()
209 writer.endtag("psNames")
210 writer.newline()
211 if hasattr(self, "extraNames"):
212 writer.begintag("extraNames")
213 writer.newline()
214 writer.comment("following are the name that are not taken from the standard Mac glyph order")
215 writer.newline()
216 for name in self.extraNames:
217 writer.simpletag("psName", name=name)
218 writer.newline()
219 writer.endtag("extraNames")
220 writer.newline()
221 if hasattr(self, "data"):
222 writer.begintag("hexdata")
223 writer.newline()
224 writer.dumphex(self.data)
225 writer.endtag("hexdata")
226 writer.newline()
227
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500228 def fromXML(self, name, attrs, content, ttFont):
Just7842e561999-12-16 21:34:53 +0000229 if name not in ("psNames", "extraNames", "hexdata"):
230 setattr(self, name, safeEval(attrs["value"]))
231 elif name == "psNames":
232 self.mapping = {}
233 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500234 if not isinstance(element, tuple):
Just7842e561999-12-16 21:34:53 +0000235 continue
236 name, attrs, content = element
237 if name == "psName":
238 self.mapping[attrs["name"]] = attrs["psName"]
239 elif name == "extraNames":
240 self.extraNames = []
241 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500242 if not isinstance(element, tuple):
Just7842e561999-12-16 21:34:53 +0000243 continue
244 name, attrs, content = element
245 if name == "psName":
246 self.extraNames.append(attrs["name"])
247 else:
248 self.data = readHex(content)
249
250
251def unpackPStrings(data):
252 strings = []
jvrf9104bc2002-01-17 09:36:30 +0000253 index = 0
254 dataLen = len(data)
255 while index < dataLen:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500256 length = byteord(data[index])
Behdad Esfahbod43d7ac12013-12-16 00:04:51 -0500257 strings.append(tostr(data[index+1:index+1+length], encoding="latin1"))
jvrf9104bc2002-01-17 09:36:30 +0000258 index = index + 1 + length
Just7842e561999-12-16 21:34:53 +0000259 return strings
260
jvrf9104bc2002-01-17 09:36:30 +0000261
Just7842e561999-12-16 21:34:53 +0000262def packPStrings(strings):
Behdad Esfahbod5f6418d2013-11-27 22:00:49 -0500263 data = b""
Just7842e561999-12-16 21:34:53 +0000264 for s in strings:
Behdad Esfahbod43d7ac12013-12-16 00:04:51 -0500265 data = data + bytechr(len(s)) + tobytes(s, encoding="latin1")
Just7842e561999-12-16 21:34:53 +0000266 return data
267