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": 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 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 273