Behdad Esfahbod | 1ae2959 | 2014-01-14 15:07:50 +0800 | [diff] [blame] | 1 | from __future__ import print_function, division, absolute_import |
Behdad Esfahbod | 30e691e | 2013-11-27 17:27:45 -0500 | [diff] [blame] | 2 | from fontTools.misc.py23 import * |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 3 | from fontTools import ttLib |
Behdad Esfahbod | 7ed91ec | 2013-11-27 15:16:28 -0500 | [diff] [blame] | 4 | from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder |
| 5 | from fontTools.misc import sstruct |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 6 | from fontTools.misc.textTools import safeEval, readHex |
Behdad Esfahbod | 30e691e | 2013-11-27 17:27:45 -0500 | [diff] [blame] | 7 | from . import DefaultTable |
| 8 | import sys |
| 9 | import struct |
| 10 | import array |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 11 | |
| 12 | |
| 13 | postFormat = """ |
| 14 | > |
| 15 | formatType: 16.16F |
| 16 | italicAngle: 16.16F # italic angle in degrees |
| 17 | underlinePosition: h |
| 18 | underlineThickness: h |
fcoiffie | 04985bf | 2006-01-12 14:04:40 +0000 | [diff] [blame] | 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 |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 24 | """ |
| 25 | |
| 26 | postFormatSize = sstruct.calcsize(postFormat) |
| 27 | |
| 28 | |
| 29 | class 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 Berten | 4a73f8b | 2014-01-28 14:37:15 +0100 | [diff] [blame^] | 40 | elif self.formatType == 4.0: |
| 41 | self.decode_format_4_0(data, ttFont) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 42 | else: |
| 43 | # supported format |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 44 | raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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 |
Olivier Berten | 4a73f8b | 2014-01-28 14:37:15 +0100 | [diff] [blame^] | 54 | elif self.formatType == 4.0: |
| 55 | data = data + self.encode_format_4_0(ttFont) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 56 | else: |
| 57 | # supported format |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 58 | raise ttLib.TTLibError("'post' table format %f not supported" % self.formatType) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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"): |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 67 | raise ttLib.TTLibError("illegal use of getGlyphOrder()") |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 68 | glyphOrder = self.glyphOrder |
| 69 | del self.glyphOrder |
| 70 | return glyphOrder |
| 71 | |
| 72 | def decode_format_1_0(self, data, ttFont): |
jvr | c2b05cc | 2001-11-05 19:32:30 +0000 | [diff] [blame] | 73 | self.glyphOrder = standardGlyphOrder[:ttFont["maxp"].numGlyphs] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 74 | |
| 75 | def decode_format_2_0(self, data, ttFont): |
| 76 | numGlyphs, = struct.unpack(">H", data[:2]) |
| 77 | numGlyphs = int(numGlyphs) |
jvr | f9104bc | 2002-01-17 09:36:30 +0000 | [diff] [blame] | 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 |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 84 | data = data[2:] |
| 85 | indices = array.array("H") |
| 86 | indices.fromstring(data[:2*numGlyphs]) |
Behdad Esfahbod | 180ace6 | 2013-11-27 02:40:30 -0500 | [diff] [blame] | 87 | if sys.byteorder != "big": |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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] |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 113 | if glyphName in allNames: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 114 | # make up a new glyphName that's unique |
Behdad Esfahbod | 85be2e0 | 2013-07-24 19:04:57 -0400 | [diff] [blame] | 115 | n = allNames[glyphName] |
| 116 | allNames[glyphName] = n + 1 |
Behdad Esfahbod | dc7e6f3 | 2013-11-27 02:44:56 -0500 | [diff] [blame] | 117 | glyphName = glyphName + "#" + repr(n) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 118 | self.glyphOrder[i] = glyphName |
| 119 | mapping[glyphName] = psName |
Behdad Esfahbod | 85be2e0 | 2013-07-24 19:04:57 -0400 | [diff] [blame] | 120 | else: |
| 121 | allNames[glyphName] = 1 |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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 | |
Olivier Berten | 4a73f8b | 2014-01-28 14:37:15 +0100 | [diff] [blame^] | 129 | 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 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 145 | 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") |
jvr | 0d762b0 | 2002-05-04 22:03:05 +0000 | [diff] [blame] | 150 | extraDict = {} |
| 151 | extraNames = self.extraNames |
| 152 | for i in range(len(extraNames)): |
| 153 | extraDict[extraNames[i]] = i |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 154 | for glyphID in range(numGlyphs): |
| 155 | glyphName = glyphOrder[glyphID] |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 156 | if glyphName in self.mapping: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 157 | psName = self.mapping[glyphName] |
| 158 | else: |
| 159 | psName = glyphName |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 160 | if psName in extraDict: |
jvr | 0d762b0 | 2002-05-04 22:03:05 +0000 | [diff] [blame] | 161 | index = 258 + extraDict[psName] |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 162 | elif psName in standardGlyphOrder: |
| 163 | index = standardGlyphOrder.index(psName) |
| 164 | else: |
jvr | 0d762b0 | 2002-05-04 22:03:05 +0000 | [diff] [blame] | 165 | index = 258 + len(extraNames) |
| 166 | extraDict[psName] = len(extraNames) |
| 167 | extraNames.append(psName) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 168 | indices.append(index) |
Behdad Esfahbod | 180ace6 | 2013-11-27 02:40:30 -0500 | [diff] [blame] | 169 | if sys.byteorder != "big": |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 170 | indices.byteswap() |
jvr | 0d762b0 | 2002-05-04 22:03:05 +0000 | [diff] [blame] | 171 | return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(extraNames) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 172 | |
Olivier Berten | 4a73f8b | 2014-01-28 14:37:15 +0100 | [diff] [blame^] | 173 | 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 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 189 | 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 Esfahbod | ac1b435 | 2013-11-27 04:15:34 -0500 | [diff] [blame] | 205 | items = sorted(self.mapping.items()) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 206 | 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 Esfahbod | 3a9fd30 | 2013-11-27 03:19:32 -0500 | [diff] [blame] | 228 | def fromXML(self, name, attrs, content, ttFont): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 229 | 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 Esfahbod | b774f9f | 2013-11-27 05:17:37 -0500 | [diff] [blame] | 234 | if not isinstance(element, tuple): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 235 | 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 Esfahbod | b774f9f | 2013-11-27 05:17:37 -0500 | [diff] [blame] | 242 | if not isinstance(element, tuple): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 243 | 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 | |
| 251 | def unpackPStrings(data): |
| 252 | strings = [] |
jvr | f9104bc | 2002-01-17 09:36:30 +0000 | [diff] [blame] | 253 | index = 0 |
| 254 | dataLen = len(data) |
| 255 | while index < dataLen: |
Behdad Esfahbod | 319c5fd | 2013-11-27 18:13:48 -0500 | [diff] [blame] | 256 | length = byteord(data[index]) |
Behdad Esfahbod | 43d7ac1 | 2013-12-16 00:04:51 -0500 | [diff] [blame] | 257 | strings.append(tostr(data[index+1:index+1+length], encoding="latin1")) |
jvr | f9104bc | 2002-01-17 09:36:30 +0000 | [diff] [blame] | 258 | index = index + 1 + length |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 259 | return strings |
| 260 | |
jvr | f9104bc | 2002-01-17 09:36:30 +0000 | [diff] [blame] | 261 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 262 | def packPStrings(strings): |
Behdad Esfahbod | 5f6418d | 2013-11-27 22:00:49 -0500 | [diff] [blame] | 263 | data = b"" |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 264 | for s in strings: |
Behdad Esfahbod | 43d7ac1 | 2013-12-16 00:04:51 -0500 | [diff] [blame] | 265 | data = data + bytechr(len(s)) + tobytes(s, encoding="latin1") |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 266 | return data |
| 267 | |