G__l_a_t.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.fixedTools import floatToFixedToStr
  3. from fontTools.misc.textTools import safeEval
  4. # from itertools import *
  5. from functools import partial
  6. from . import DefaultTable
  7. from . import grUtils
  8. import struct
  9. Glat_format_0 = """
  10. > # big endian
  11. version: 16.16F
  12. """
  13. Glat_format_3 = """
  14. >
  15. version: 16.16F
  16. compression:L # compression scheme or reserved
  17. """
  18. Glat_format_1_entry = """
  19. >
  20. attNum: B # Attribute number of first attribute
  21. num: B # Number of attributes in this run
  22. """
  23. Glat_format_23_entry = """
  24. >
  25. attNum: H # Attribute number of first attribute
  26. num: H # Number of attributes in this run
  27. """
  28. Glat_format_3_octabox_metrics = """
  29. >
  30. subboxBitmap: H # Which subboxes exist on 4x4 grid
  31. diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
  32. diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
  33. diagPosMin: B # Defines minimum positively-sloped diagonal (di)
  34. diagPosMax: B # Defines maximum positively-sloped diagonal (da)
  35. """
  36. Glat_format_3_subbox_entry = """
  37. >
  38. left: B # xi
  39. right: B # xa
  40. bottom: B # yi
  41. top: B # ya
  42. diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
  43. diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
  44. diagPosMin: B # Defines minimum positively-sloped diagonal (di)
  45. diagPosMax: B # Defines maximum positively-sloped diagonal (da)
  46. """
  47. class _Object:
  48. pass
  49. class _Dict(dict):
  50. pass
  51. class table_G__l_a_t(DefaultTable.DefaultTable):
  52. """Graphite Glyph Attributes table
  53. See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
  54. """
  55. def __init__(self, tag=None):
  56. DefaultTable.DefaultTable.__init__(self, tag)
  57. self.scheme = 0
  58. def decompile(self, data, ttFont):
  59. sstruct.unpack2(Glat_format_0, data, self)
  60. self.version = float(floatToFixedToStr(self.version, precisionBits=16))
  61. if self.version <= 1.9:
  62. decoder = partial(self.decompileAttributes12, fmt=Glat_format_1_entry)
  63. elif self.version <= 2.9:
  64. decoder = partial(self.decompileAttributes12, fmt=Glat_format_23_entry)
  65. elif self.version >= 3.0:
  66. (data, self.scheme) = grUtils.decompress(data)
  67. sstruct.unpack2(Glat_format_3, data, self)
  68. self.hasOctaboxes = (self.compression & 1) == 1
  69. decoder = self.decompileAttributes3
  70. gloc = ttFont["Gloc"]
  71. self.attributes = {}
  72. count = 0
  73. for s, e in zip(gloc, gloc[1:]):
  74. self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
  75. count += 1
  76. def decompileAttributes12(self, data, fmt):
  77. attributes = _Dict()
  78. while len(data) > 3:
  79. e, data = sstruct.unpack2(fmt, data, _Object())
  80. keys = range(e.attNum, e.attNum + e.num)
  81. if len(data) >= 2 * e.num:
  82. vals = struct.unpack_from((">%dh" % e.num), data)
  83. attributes.update(zip(keys, vals))
  84. data = data[2 * e.num :]
  85. return attributes
  86. def decompileAttributes3(self, data):
  87. if self.hasOctaboxes:
  88. o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
  89. numsub = bin(o.subboxBitmap).count("1")
  90. o.subboxes = []
  91. for b in range(numsub):
  92. if len(data) >= 8:
  93. subbox, data = sstruct.unpack2(
  94. Glat_format_3_subbox_entry, data, _Object()
  95. )
  96. o.subboxes.append(subbox)
  97. attrs = self.decompileAttributes12(data, Glat_format_23_entry)
  98. if self.hasOctaboxes:
  99. attrs.octabox = o
  100. return attrs
  101. def compile(self, ttFont):
  102. data = sstruct.pack(Glat_format_0, self)
  103. if self.version <= 1.9:
  104. encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
  105. elif self.version <= 2.9:
  106. encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
  107. elif self.version >= 3.0:
  108. self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
  109. data = sstruct.pack(Glat_format_3, self)
  110. encoder = self.compileAttributes3
  111. glocs = []
  112. for n in range(len(self.attributes)):
  113. glocs.append(len(data))
  114. data += encoder(self.attributes[ttFont.getGlyphName(n)])
  115. glocs.append(len(data))
  116. ttFont["Gloc"].set(glocs)
  117. if self.version >= 3.0:
  118. data = grUtils.compress(self.scheme, data)
  119. return data
  120. def compileAttributes12(self, attrs, fmt):
  121. data = b""
  122. for e in grUtils.entries(attrs):
  123. data += sstruct.pack(fmt, {"attNum": e[0], "num": e[1]}) + struct.pack(
  124. (">%dh" % len(e[2])), *e[2]
  125. )
  126. return data
  127. def compileAttributes3(self, attrs):
  128. if self.hasOctaboxes:
  129. o = attrs.octabox
  130. data = sstruct.pack(Glat_format_3_octabox_metrics, o)
  131. numsub = bin(o.subboxBitmap).count("1")
  132. for b in range(numsub):
  133. data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
  134. else:
  135. data = ""
  136. return data + self.compileAttributes12(attrs, Glat_format_23_entry)
  137. def toXML(self, writer, ttFont):
  138. writer.simpletag("version", version=self.version, compressionScheme=self.scheme)
  139. writer.newline()
  140. for n, a in sorted(
  141. self.attributes.items(), key=lambda x: ttFont.getGlyphID(x[0])
  142. ):
  143. writer.begintag("glyph", name=n)
  144. writer.newline()
  145. if hasattr(a, "octabox"):
  146. o = a.octabox
  147. formatstring, names, fixes = sstruct.getformat(
  148. Glat_format_3_octabox_metrics
  149. )
  150. vals = {}
  151. for k in names:
  152. if k == "subboxBitmap":
  153. continue
  154. vals[k] = "{:.3f}%".format(getattr(o, k) * 100.0 / 255)
  155. vals["bitmap"] = "{:0X}".format(o.subboxBitmap)
  156. writer.begintag("octaboxes", **vals)
  157. writer.newline()
  158. formatstring, names, fixes = sstruct.getformat(
  159. Glat_format_3_subbox_entry
  160. )
  161. for s in o.subboxes:
  162. vals = {}
  163. for k in names:
  164. vals[k] = "{:.3f}%".format(getattr(s, k) * 100.0 / 255)
  165. writer.simpletag("octabox", **vals)
  166. writer.newline()
  167. writer.endtag("octaboxes")
  168. writer.newline()
  169. for k, v in sorted(a.items()):
  170. writer.simpletag("attribute", index=k, value=v)
  171. writer.newline()
  172. writer.endtag("glyph")
  173. writer.newline()
  174. def fromXML(self, name, attrs, content, ttFont):
  175. if name == "version":
  176. self.version = float(safeEval(attrs["version"]))
  177. self.scheme = int(safeEval(attrs["compressionScheme"]))
  178. if name != "glyph":
  179. return
  180. if not hasattr(self, "attributes"):
  181. self.attributes = {}
  182. gname = attrs["name"]
  183. attributes = _Dict()
  184. for element in content:
  185. if not isinstance(element, tuple):
  186. continue
  187. tag, attrs, subcontent = element
  188. if tag == "attribute":
  189. k = int(safeEval(attrs["index"]))
  190. v = int(safeEval(attrs["value"]))
  191. attributes[k] = v
  192. elif tag == "octaboxes":
  193. self.hasOctaboxes = True
  194. o = _Object()
  195. o.subboxBitmap = int(attrs["bitmap"], 16)
  196. o.subboxes = []
  197. del attrs["bitmap"]
  198. for k, v in attrs.items():
  199. setattr(o, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
  200. for element in subcontent:
  201. if not isinstance(element, tuple):
  202. continue
  203. (tag, attrs, subcontent) = element
  204. so = _Object()
  205. for k, v in attrs.items():
  206. setattr(so, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
  207. o.subboxes.append(so)
  208. attributes.octabox = o
  209. self.attributes[gname] = attributes