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