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