_k_e_r_n.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. from fontTools.ttLib import getSearchRange
  2. from fontTools.misc.textTools import safeEval, readHex
  3. from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
  4. from . import DefaultTable
  5. import struct
  6. import sys
  7. import array
  8. import logging
  9. log = logging.getLogger(__name__)
  10. class table__k_e_r_n(DefaultTable.DefaultTable):
  11. def getkern(self, format):
  12. for subtable in self.kernTables:
  13. if subtable.format == format:
  14. return subtable
  15. return None # not found
  16. def decompile(self, data, ttFont):
  17. version, nTables = struct.unpack(">HH", data[:4])
  18. apple = False
  19. if (len(data) >= 8) and (version == 1):
  20. # AAT Apple's "new" format. Hm.
  21. version, nTables = struct.unpack(">LL", data[:8])
  22. self.version = fi2fl(version, 16)
  23. data = data[8:]
  24. apple = True
  25. else:
  26. self.version = version
  27. data = data[4:]
  28. self.kernTables = []
  29. for i in range(nTables):
  30. if self.version == 1.0:
  31. # Apple
  32. length, coverage, subtableFormat = struct.unpack(">LBB", data[:6])
  33. else:
  34. # in OpenType spec the "version" field refers to the common
  35. # subtable header; the actual subtable format is stored in
  36. # the 8-15 mask bits of "coverage" field.
  37. # This "version" is always 0 so we ignore it here
  38. _, length, subtableFormat, coverage = struct.unpack(">HHBB", data[:6])
  39. if nTables == 1 and subtableFormat == 0:
  40. # The "length" value is ignored since some fonts
  41. # (like OpenSans and Calibri) have a subtable larger than
  42. # its value.
  43. (nPairs,) = struct.unpack(">H", data[6:8])
  44. calculated_length = (nPairs * 6) + 14
  45. if length != calculated_length:
  46. log.warning(
  47. "'kern' subtable longer than defined: "
  48. "%d bytes instead of %d bytes" % (calculated_length, length)
  49. )
  50. length = calculated_length
  51. if subtableFormat not in kern_classes:
  52. subtable = KernTable_format_unkown(subtableFormat)
  53. else:
  54. subtable = kern_classes[subtableFormat](apple)
  55. subtable.decompile(data[:length], ttFont)
  56. self.kernTables.append(subtable)
  57. data = data[length:]
  58. def compile(self, ttFont):
  59. if hasattr(self, "kernTables"):
  60. nTables = len(self.kernTables)
  61. else:
  62. nTables = 0
  63. if self.version == 1.0:
  64. # AAT Apple's "new" format.
  65. data = struct.pack(">LL", fl2fi(self.version, 16), nTables)
  66. else:
  67. data = struct.pack(">HH", self.version, nTables)
  68. if hasattr(self, "kernTables"):
  69. for subtable in self.kernTables:
  70. data = data + subtable.compile(ttFont)
  71. return data
  72. def toXML(self, writer, ttFont):
  73. writer.simpletag("version", value=self.version)
  74. writer.newline()
  75. for subtable in self.kernTables:
  76. subtable.toXML(writer, ttFont)
  77. def fromXML(self, name, attrs, content, ttFont):
  78. if name == "version":
  79. self.version = safeEval(attrs["value"])
  80. return
  81. if name != "kernsubtable":
  82. return
  83. if not hasattr(self, "kernTables"):
  84. self.kernTables = []
  85. format = safeEval(attrs["format"])
  86. if format not in kern_classes:
  87. subtable = KernTable_format_unkown(format)
  88. else:
  89. apple = self.version == 1.0
  90. subtable = kern_classes[format](apple)
  91. self.kernTables.append(subtable)
  92. subtable.fromXML(name, attrs, content, ttFont)
  93. class KernTable_format_0(object):
  94. # 'version' is kept for backward compatibility
  95. version = format = 0
  96. def __init__(self, apple=False):
  97. self.apple = apple
  98. def decompile(self, data, ttFont):
  99. if not self.apple:
  100. version, length, subtableFormat, coverage = struct.unpack(">HHBB", data[:6])
  101. if version != 0:
  102. from fontTools.ttLib import TTLibError
  103. raise TTLibError("unsupported kern subtable version: %d" % version)
  104. tupleIndex = None
  105. # Should we also assert length == len(data)?
  106. data = data[6:]
  107. else:
  108. length, coverage, subtableFormat, tupleIndex = struct.unpack(
  109. ">LBBH", data[:8]
  110. )
  111. data = data[8:]
  112. assert self.format == subtableFormat, "unsupported format"
  113. self.coverage = coverage
  114. self.tupleIndex = tupleIndex
  115. self.kernTable = kernTable = {}
  116. nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
  117. ">HHHH", data[:8]
  118. )
  119. data = data[8:]
  120. datas = array.array("H", data[: 6 * nPairs])
  121. if sys.byteorder != "big":
  122. datas.byteswap()
  123. it = iter(datas)
  124. glyphOrder = ttFont.getGlyphOrder()
  125. for k in range(nPairs):
  126. left, right, value = next(it), next(it), next(it)
  127. if value >= 32768:
  128. value -= 65536
  129. try:
  130. kernTable[(glyphOrder[left], glyphOrder[right])] = value
  131. except IndexError:
  132. # Slower, but will not throw an IndexError on an invalid
  133. # glyph id.
  134. kernTable[
  135. (ttFont.getGlyphName(left), ttFont.getGlyphName(right))
  136. ] = value
  137. if len(data) > 6 * nPairs + 4: # Ignore up to 4 bytes excess
  138. log.warning(
  139. "excess data in 'kern' subtable: %d bytes", len(data) - 6 * nPairs
  140. )
  141. def compile(self, ttFont):
  142. nPairs = min(len(self.kernTable), 0xFFFF)
  143. searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
  144. searchRange &= 0xFFFF
  145. entrySelector = min(entrySelector, 0xFFFF)
  146. rangeShift = min(rangeShift, 0xFFFF)
  147. data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift)
  148. # yeehee! (I mean, turn names into indices)
  149. try:
  150. reverseOrder = ttFont.getReverseGlyphMap()
  151. kernTable = sorted(
  152. (reverseOrder[left], reverseOrder[right], value)
  153. for ((left, right), value) in self.kernTable.items()
  154. )
  155. except KeyError:
  156. # Slower, but will not throw KeyError on invalid glyph id.
  157. getGlyphID = ttFont.getGlyphID
  158. kernTable = sorted(
  159. (getGlyphID(left), getGlyphID(right), value)
  160. for ((left, right), value) in self.kernTable.items()
  161. )
  162. for left, right, value in kernTable:
  163. data = data + struct.pack(">HHh", left, right, value)
  164. if not self.apple:
  165. version = 0
  166. length = len(data) + 6
  167. if length >= 0x10000:
  168. log.warning(
  169. '"kern" subtable overflow, '
  170. "truncating length value while preserving pairs."
  171. )
  172. length &= 0xFFFF
  173. header = struct.pack(">HHBB", version, length, self.format, self.coverage)
  174. else:
  175. if self.tupleIndex is None:
  176. # sensible default when compiling a TTX from an old fonttools
  177. # or when inserting a Windows-style format 0 subtable into an
  178. # Apple version=1.0 kern table
  179. log.warning("'tupleIndex' is None; default to 0")
  180. self.tupleIndex = 0
  181. length = len(data) + 8
  182. header = struct.pack(
  183. ">LBBH", length, self.coverage, self.format, self.tupleIndex
  184. )
  185. return header + data
  186. def toXML(self, writer, ttFont):
  187. attrs = dict(coverage=self.coverage, format=self.format)
  188. if self.apple:
  189. if self.tupleIndex is None:
  190. log.warning("'tupleIndex' is None; default to 0")
  191. attrs["tupleIndex"] = 0
  192. else:
  193. attrs["tupleIndex"] = self.tupleIndex
  194. writer.begintag("kernsubtable", **attrs)
  195. writer.newline()
  196. items = sorted(self.kernTable.items())
  197. for (left, right), value in items:
  198. writer.simpletag("pair", [("l", left), ("r", right), ("v", value)])
  199. writer.newline()
  200. writer.endtag("kernsubtable")
  201. writer.newline()
  202. def fromXML(self, name, attrs, content, ttFont):
  203. self.coverage = safeEval(attrs["coverage"])
  204. subtableFormat = safeEval(attrs["format"])
  205. if self.apple:
  206. if "tupleIndex" in attrs:
  207. self.tupleIndex = safeEval(attrs["tupleIndex"])
  208. else:
  209. # previous fontTools versions didn't export tupleIndex
  210. log.warning("Apple kern subtable is missing 'tupleIndex' attribute")
  211. self.tupleIndex = None
  212. else:
  213. self.tupleIndex = None
  214. assert subtableFormat == self.format, "unsupported format"
  215. if not hasattr(self, "kernTable"):
  216. self.kernTable = {}
  217. for element in content:
  218. if not isinstance(element, tuple):
  219. continue
  220. name, attrs, content = element
  221. self.kernTable[(attrs["l"], attrs["r"])] = safeEval(attrs["v"])
  222. def __getitem__(self, pair):
  223. return self.kernTable[pair]
  224. def __setitem__(self, pair, value):
  225. self.kernTable[pair] = value
  226. def __delitem__(self, pair):
  227. del self.kernTable[pair]
  228. class KernTable_format_unkown(object):
  229. def __init__(self, format):
  230. self.format = format
  231. def decompile(self, data, ttFont):
  232. self.data = data
  233. def compile(self, ttFont):
  234. return self.data
  235. def toXML(self, writer, ttFont):
  236. writer.begintag("kernsubtable", format=self.format)
  237. writer.newline()
  238. writer.comment("unknown 'kern' subtable format")
  239. writer.newline()
  240. writer.dumphex(self.data)
  241. writer.endtag("kernsubtable")
  242. writer.newline()
  243. def fromXML(self, name, attrs, content, ttFont):
  244. self.decompile(readHex(content), ttFont)
  245. kern_classes = {0: KernTable_format_0}