_p_o_s_t.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. from fontTools import ttLib
  2. from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
  3. from fontTools.misc import sstruct
  4. from fontTools.misc.textTools import bytechr, byteord, tobytes, tostr, safeEval, readHex
  5. from . import DefaultTable
  6. import sys
  7. import struct
  8. import array
  9. import logging
  10. log = logging.getLogger(__name__)
  11. postFormat = """
  12. >
  13. formatType: 16.16F
  14. italicAngle: 16.16F # italic angle in degrees
  15. underlinePosition: h
  16. underlineThickness: h
  17. isFixedPitch: L
  18. minMemType42: L # minimum memory if TrueType font is downloaded
  19. maxMemType42: L # maximum memory if TrueType font is downloaded
  20. minMemType1: L # minimum memory if Type1 font is downloaded
  21. maxMemType1: L # maximum memory if Type1 font is downloaded
  22. """
  23. postFormatSize = sstruct.calcsize(postFormat)
  24. class table__p_o_s_t(DefaultTable.DefaultTable):
  25. def decompile(self, data, ttFont):
  26. sstruct.unpack(postFormat, data[:postFormatSize], self)
  27. data = data[postFormatSize:]
  28. if self.formatType == 1.0:
  29. self.decode_format_1_0(data, ttFont)
  30. elif self.formatType == 2.0:
  31. self.decode_format_2_0(data, ttFont)
  32. elif self.formatType == 3.0:
  33. self.decode_format_3_0(data, ttFont)
  34. elif self.formatType == 4.0:
  35. self.decode_format_4_0(data, ttFont)
  36. else:
  37. # supported format
  38. raise ttLib.TTLibError(
  39. "'post' table format %f not supported" % self.formatType
  40. )
  41. def compile(self, ttFont):
  42. data = sstruct.pack(postFormat, self)
  43. if self.formatType == 1.0:
  44. pass # we're done
  45. elif self.formatType == 2.0:
  46. data = data + self.encode_format_2_0(ttFont)
  47. elif self.formatType == 3.0:
  48. pass # we're done
  49. elif self.formatType == 4.0:
  50. data = data + self.encode_format_4_0(ttFont)
  51. else:
  52. # supported format
  53. raise ttLib.TTLibError(
  54. "'post' table format %f not supported" % self.formatType
  55. )
  56. return data
  57. def getGlyphOrder(self):
  58. """This function will get called by a ttLib.TTFont instance.
  59. Do not call this function yourself, use TTFont().getGlyphOrder()
  60. or its relatives instead!
  61. """
  62. if not hasattr(self, "glyphOrder"):
  63. raise ttLib.TTLibError("illegal use of getGlyphOrder()")
  64. glyphOrder = self.glyphOrder
  65. del self.glyphOrder
  66. return glyphOrder
  67. def decode_format_1_0(self, data, ttFont):
  68. self.glyphOrder = standardGlyphOrder[: ttFont["maxp"].numGlyphs]
  69. def decode_format_2_0(self, data, ttFont):
  70. (numGlyphs,) = struct.unpack(">H", data[:2])
  71. numGlyphs = int(numGlyphs)
  72. if numGlyphs > ttFont["maxp"].numGlyphs:
  73. # Assume the numGlyphs field is bogus, so sync with maxp.
  74. # I've seen this in one font, and if the assumption is
  75. # wrong elsewhere, well, so be it: it's hard enough to
  76. # work around _one_ non-conforming post format...
  77. numGlyphs = ttFont["maxp"].numGlyphs
  78. data = data[2:]
  79. indices = array.array("H")
  80. indices.frombytes(data[: 2 * numGlyphs])
  81. if sys.byteorder != "big":
  82. indices.byteswap()
  83. data = data[2 * numGlyphs :]
  84. maxIndex = max(indices)
  85. self.extraNames = extraNames = unpackPStrings(data, maxIndex - 257)
  86. self.glyphOrder = glyphOrder = [""] * int(ttFont["maxp"].numGlyphs)
  87. for glyphID in range(numGlyphs):
  88. index = indices[glyphID]
  89. if index > 257:
  90. try:
  91. name = extraNames[index - 258]
  92. except IndexError:
  93. name = ""
  94. else:
  95. # fetch names from standard list
  96. name = standardGlyphOrder[index]
  97. glyphOrder[glyphID] = name
  98. self.build_psNameMapping(ttFont)
  99. def build_psNameMapping(self, ttFont):
  100. mapping = {}
  101. allNames = {}
  102. for i in range(ttFont["maxp"].numGlyphs):
  103. glyphName = psName = self.glyphOrder[i]
  104. if glyphName == "":
  105. glyphName = "glyph%.5d" % i
  106. if glyphName in allNames:
  107. # make up a new glyphName that's unique
  108. n = allNames[glyphName]
  109. while (glyphName + "#" + str(n)) in allNames:
  110. n += 1
  111. allNames[glyphName] = n + 1
  112. glyphName = glyphName + "#" + str(n)
  113. self.glyphOrder[i] = glyphName
  114. allNames[glyphName] = 1
  115. if glyphName != psName:
  116. mapping[glyphName] = psName
  117. self.mapping = mapping
  118. def decode_format_3_0(self, data, ttFont):
  119. # Setting self.glyphOrder to None will cause the TTFont object
  120. # try and construct glyph names from a Unicode cmap table.
  121. self.glyphOrder = None
  122. def decode_format_4_0(self, data, ttFont):
  123. from fontTools import agl
  124. numGlyphs = ttFont["maxp"].numGlyphs
  125. indices = array.array("H")
  126. indices.frombytes(data)
  127. if sys.byteorder != "big":
  128. indices.byteswap()
  129. # In some older fonts, the size of the post table doesn't match
  130. # the number of glyphs. Sometimes it's bigger, sometimes smaller.
  131. self.glyphOrder = glyphOrder = [""] * int(numGlyphs)
  132. for i in range(min(len(indices), numGlyphs)):
  133. if indices[i] == 0xFFFF:
  134. self.glyphOrder[i] = ""
  135. elif indices[i] in agl.UV2AGL:
  136. self.glyphOrder[i] = agl.UV2AGL[indices[i]]
  137. else:
  138. self.glyphOrder[i] = "uni%04X" % indices[i]
  139. self.build_psNameMapping(ttFont)
  140. def encode_format_2_0(self, ttFont):
  141. numGlyphs = ttFont["maxp"].numGlyphs
  142. glyphOrder = ttFont.getGlyphOrder()
  143. assert len(glyphOrder) == numGlyphs
  144. indices = array.array("H")
  145. extraDict = {}
  146. extraNames = self.extraNames = [
  147. n for n in self.extraNames if n not in standardGlyphOrder
  148. ]
  149. for i in range(len(extraNames)):
  150. extraDict[extraNames[i]] = i
  151. for glyphID in range(numGlyphs):
  152. glyphName = glyphOrder[glyphID]
  153. if glyphName in self.mapping:
  154. psName = self.mapping[glyphName]
  155. else:
  156. psName = glyphName
  157. if psName in extraDict:
  158. index = 258 + extraDict[psName]
  159. elif psName in standardGlyphOrder:
  160. index = standardGlyphOrder.index(psName)
  161. else:
  162. index = 258 + len(extraNames)
  163. extraDict[psName] = len(extraNames)
  164. extraNames.append(psName)
  165. indices.append(index)
  166. if sys.byteorder != "big":
  167. indices.byteswap()
  168. return (
  169. struct.pack(">H", numGlyphs) + indices.tobytes() + packPStrings(extraNames)
  170. )
  171. def encode_format_4_0(self, ttFont):
  172. from fontTools import agl
  173. numGlyphs = ttFont["maxp"].numGlyphs
  174. glyphOrder = ttFont.getGlyphOrder()
  175. assert len(glyphOrder) == numGlyphs
  176. indices = array.array("H")
  177. for glyphID in glyphOrder:
  178. glyphID = glyphID.split("#")[0]
  179. if glyphID in agl.AGL2UV:
  180. indices.append(agl.AGL2UV[glyphID])
  181. elif len(glyphID) == 7 and glyphID[:3] == "uni":
  182. indices.append(int(glyphID[3:], 16))
  183. else:
  184. indices.append(0xFFFF)
  185. if sys.byteorder != "big":
  186. indices.byteswap()
  187. return indices.tobytes()
  188. def toXML(self, writer, ttFont):
  189. formatstring, names, fixes = sstruct.getformat(postFormat)
  190. for name in names:
  191. value = getattr(self, name)
  192. writer.simpletag(name, value=value)
  193. writer.newline()
  194. if hasattr(self, "mapping"):
  195. writer.begintag("psNames")
  196. writer.newline()
  197. writer.comment(
  198. "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. )
  205. writer.newline()
  206. items = sorted(self.mapping.items())
  207. for name, psName in items:
  208. writer.simpletag("psName", name=name, psName=psName)
  209. writer.newline()
  210. writer.endtag("psNames")
  211. writer.newline()
  212. if hasattr(self, "extraNames"):
  213. writer.begintag("extraNames")
  214. writer.newline()
  215. writer.comment(
  216. "following are the name that are not taken from the standard Mac glyph order"
  217. )
  218. writer.newline()
  219. for name in self.extraNames:
  220. writer.simpletag("psName", name=name)
  221. writer.newline()
  222. writer.endtag("extraNames")
  223. writer.newline()
  224. if hasattr(self, "data"):
  225. writer.begintag("hexdata")
  226. writer.newline()
  227. writer.dumphex(self.data)
  228. writer.endtag("hexdata")
  229. writer.newline()
  230. def fromXML(self, name, attrs, content, ttFont):
  231. if name not in ("psNames", "extraNames", "hexdata"):
  232. setattr(self, name, safeEval(attrs["value"]))
  233. elif name == "psNames":
  234. self.mapping = {}
  235. for element in content:
  236. if not isinstance(element, tuple):
  237. continue
  238. name, attrs, content = element
  239. if name == "psName":
  240. self.mapping[attrs["name"]] = attrs["psName"]
  241. elif name == "extraNames":
  242. self.extraNames = []
  243. for element in content:
  244. if not isinstance(element, tuple):
  245. continue
  246. name, attrs, content = element
  247. if name == "psName":
  248. self.extraNames.append(attrs["name"])
  249. else:
  250. self.data = readHex(content)
  251. def unpackPStrings(data, n):
  252. # extract n Pascal strings from data.
  253. # if there is not enough data, use ""
  254. strings = []
  255. index = 0
  256. dataLen = len(data)
  257. for _ in range(n):
  258. if dataLen <= index:
  259. length = 0
  260. else:
  261. length = byteord(data[index])
  262. index += 1
  263. if dataLen <= index + length - 1:
  264. name = ""
  265. else:
  266. name = tostr(data[index : index + length], encoding="latin1")
  267. strings.append(name)
  268. index += length
  269. if index < dataLen:
  270. log.warning("%d extra bytes in post.stringData array", dataLen - index)
  271. elif dataLen < index:
  272. log.warning("not enough data in post.stringData array")
  273. return strings
  274. def packPStrings(strings):
  275. data = b""
  276. for s in strings:
  277. data = data + bytechr(len(s)) + tobytes(s, encoding="latin1")
  278. return data