_p_o_s_t.py revision ac1b4359467ca3deab03186a15eae1d55eb35567
1import sys 2from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder 3from . import DefaultTable 4import struct 5from fontTools.misc import sstruct 6import array 7from fontTools import ttLib 8from fontTools.misc.textTools import safeEval, readHex 9from types import TupleType 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 else: 40 # supported format 41 raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType) 42 43 def compile(self, ttFont): 44 data = sstruct.pack(postFormat, self) 45 if self.formatType == 1.0: 46 pass # we're done 47 elif self.formatType == 2.0: 48 data = data + self.encode_format_2_0(ttFont) 49 elif self.formatType == 3.0: 50 pass # we're done 51 else: 52 # supported format 53 raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType) 54 return data 55 56 def getGlyphOrder(self): 57 """This function will get called by a ttLib.TTFont instance. 58 Do not call this function yourself, use TTFont().getGlyphOrder() 59 or its relatives instead! 60 """ 61 if not hasattr(self, "glyphOrder"): 62 raise ttLib.TTLibError("illegal use of getGlyphOrder()") 63 glyphOrder = self.glyphOrder 64 del self.glyphOrder 65 return glyphOrder 66 67 def decode_format_1_0(self, data, ttFont): 68 self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs] 69 70 def decode_format_2_0(self, data, ttFont): 71 numGlyphs, = struct.unpack(">H", data[:2]) 72 numGlyphs = int(numGlyphs) 73 if numGlyphs > ttFont['maxp'].numGlyphs: 74 # Assume the numGlyphs field is bogus, so sync with maxp. 75 # I've seen this in one font, and if the assumption is 76 # wrong elsewhere, well, so be it: it's hard enough to 77 # work around _one_ non-conforming post format... 78 numGlyphs = ttFont['maxp'].numGlyphs 79 data = data[2:] 80 indices = array.array("H") 81 indices.fromstring(data[:2*numGlyphs]) 82 if sys.byteorder != "big": 83 indices.byteswap() 84 data = data[2*numGlyphs:] 85 self.extraNames = extraNames = unpackPStrings(data) 86 self.glyphOrder = glyphOrder = [None] * int(ttFont['maxp'].numGlyphs) 87 for glyphID in range(numGlyphs): 88 index = indices[glyphID] 89 if index > 257: 90 name = extraNames[index-258] 91 else: 92 # fetch names from standard list 93 name = standardGlyphOrder[index] 94 glyphOrder[glyphID] = name 95 #AL990511: code added to handle the case of new glyphs without 96 # entries into the 'post' table 97 if numGlyphs < ttFont['maxp'].numGlyphs: 98 for i in range(numGlyphs, ttFont['maxp'].numGlyphs): 99 glyphOrder[i] = "glyph#%.5d" % i 100 self.extraNames.append(glyphOrder[i]) 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 in allNames: 109 # make up a new glyphName that's unique 110 n = allNames[glyphName] 111 allNames[glyphName] = n + 1 112 glyphName = glyphName + "#" + repr(n) 113 self.glyphOrder[i] = glyphName 114 mapping[glyphName] = psName 115 else: 116 allNames[glyphName] = 1 117 self.mapping = mapping 118 119 def decode_format_3_0(self, data, ttFont): 120 # Setting self.glyphOrder to None will cause the TTFont object 121 # try and construct glyph names from a Unicode cmap table. 122 self.glyphOrder = None 123 124 def encode_format_2_0(self, ttFont): 125 numGlyphs = ttFont['maxp'].numGlyphs 126 glyphOrder = ttFont.getGlyphOrder() 127 assert len(glyphOrder) == numGlyphs 128 indices = array.array("H") 129 extraDict = {} 130 extraNames = self.extraNames 131 for i in range(len(extraNames)): 132 extraDict[extraNames[i]] = i 133 for glyphID in range(numGlyphs): 134 glyphName = glyphOrder[glyphID] 135 if glyphName in self.mapping: 136 psName = self.mapping[glyphName] 137 else: 138 psName = glyphName 139 if psName in extraDict: 140 index = 258 + extraDict[psName] 141 elif psName in standardGlyphOrder: 142 index = standardGlyphOrder.index(psName) 143 else: 144 index = 258 + len(extraNames) 145 extraDict[psName] = len(extraNames) 146 extraNames.append(psName) 147 indices.append(index) 148 if sys.byteorder != "big": 149 indices.byteswap() 150 return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames) 151 152 def toXML(self, writer, ttFont): 153 formatstring, names, fixes = sstruct.getformat(postFormat) 154 for name in names: 155 value = getattr(self, name) 156 writer.simpletag(name, value=value) 157 writer.newline() 158 if hasattr(self, "mapping"): 159 writer.begintag("psNames") 160 writer.newline() 161 writer.comment("This file uses unique glyph names based on the information\n" 162 "found in the 'post' table. Since these names might not be unique,\n" 163 "we have to invent artificial names in case of clashes. In order to\n" 164 "be able to retain the original information, we need a name to\n" 165 "ps name mapping for those cases where they differ. That's what\n" 166 "you see below.\n") 167 writer.newline() 168 items = sorted(self.mapping.items()) 169 for name, psName in items: 170 writer.simpletag("psName", name=name, psName=psName) 171 writer.newline() 172 writer.endtag("psNames") 173 writer.newline() 174 if hasattr(self, "extraNames"): 175 writer.begintag("extraNames") 176 writer.newline() 177 writer.comment("following are the name that are not taken from the standard Mac glyph order") 178 writer.newline() 179 for name in self.extraNames: 180 writer.simpletag("psName", name=name) 181 writer.newline() 182 writer.endtag("extraNames") 183 writer.newline() 184 if hasattr(self, "data"): 185 writer.begintag("hexdata") 186 writer.newline() 187 writer.dumphex(self.data) 188 writer.endtag("hexdata") 189 writer.newline() 190 191 def fromXML(self, name, attrs, content, ttFont): 192 if name not in ("psNames", "extraNames", "hexdata"): 193 setattr(self, name, safeEval(attrs["value"])) 194 elif name == "psNames": 195 self.mapping = {} 196 for element in content: 197 if not isinstance(element, TupleType): 198 continue 199 name, attrs, content = element 200 if name == "psName": 201 self.mapping[attrs["name"]] = attrs["psName"] 202 elif name == "extraNames": 203 self.extraNames = [] 204 for element in content: 205 if not isinstance(element, TupleType): 206 continue 207 name, attrs, content = element 208 if name == "psName": 209 self.extraNames.append(attrs["name"]) 210 else: 211 self.data = readHex(content) 212 213 214def unpackPStrings(data): 215 strings = [] 216 index = 0 217 dataLen = len(data) 218 while index < dataLen: 219 length = ord(data[index]) 220 strings.append(data[index+1:index+1+length]) 221 index = index + 1 + length 222 return strings 223 224 225def packPStrings(strings): 226 data = "" 227 for s in strings: 228 data = data + chr(len(s)) + s 229 return data 230 231