F__e_a_t.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.fixedTools import floatToFixedToStr
  3. from fontTools.misc.textTools import safeEval
  4. from . import DefaultTable
  5. from . import grUtils
  6. import struct
  7. Feat_hdr_format = """
  8. >
  9. version: 16.16F
  10. """
  11. class table_F__e_a_t(DefaultTable.DefaultTable):
  12. """Feature table
  13. The ``Feat`` table is used exclusively by the Graphite shaping engine
  14. to store features and possible settings specified in GDL. Graphite features
  15. determine what rules are applied to transform a glyph stream.
  16. Not to be confused with ``feat``, or the OpenType Layout tables
  17. ``GSUB``/``GPOS``.
  18. See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
  19. """
  20. def __init__(self, tag=None):
  21. DefaultTable.DefaultTable.__init__(self, tag)
  22. self.features = {}
  23. def decompile(self, data, ttFont):
  24. (_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
  25. self.version = float(floatToFixedToStr(self.version, precisionBits=16))
  26. (numFeats,) = struct.unpack(">H", data[:2])
  27. data = data[8:]
  28. allfeats = []
  29. maxsetting = 0
  30. for i in range(numFeats):
  31. if self.version >= 2.0:
  32. (fid, nums, _, offset, flags, lid) = struct.unpack(
  33. ">LHHLHH", data[16 * i : 16 * (i + 1)]
  34. )
  35. offset = int((offset - 12 - 16 * numFeats) / 4)
  36. else:
  37. (fid, nums, offset, flags, lid) = struct.unpack(
  38. ">HHLHH", data[12 * i : 12 * (i + 1)]
  39. )
  40. offset = int((offset - 12 - 12 * numFeats) / 4)
  41. allfeats.append((fid, nums, offset, flags, lid))
  42. maxsetting = max(maxsetting, offset + nums)
  43. data = data[16 * numFeats :]
  44. allsettings = []
  45. for i in range(maxsetting):
  46. if len(data) >= 4 * (i + 1):
  47. (val, lid) = struct.unpack(">HH", data[4 * i : 4 * (i + 1)])
  48. allsettings.append((val, lid))
  49. for i, f in enumerate(allfeats):
  50. (fid, nums, offset, flags, lid) = f
  51. fobj = Feature()
  52. fobj.flags = flags
  53. fobj.label = lid
  54. self.features[grUtils.num2tag(fid)] = fobj
  55. fobj.settings = {}
  56. fobj.default = None
  57. fobj.index = i
  58. for i in range(offset, offset + nums):
  59. if i >= len(allsettings):
  60. continue
  61. (vid, vlid) = allsettings[i]
  62. fobj.settings[vid] = vlid
  63. if fobj.default is None:
  64. fobj.default = vid
  65. def compile(self, ttFont):
  66. fdat = b""
  67. vdat = b""
  68. offset = 0
  69. for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
  70. fnum = grUtils.tag2num(f)
  71. if self.version >= 2.0:
  72. fdat += struct.pack(
  73. ">LHHLHH",
  74. grUtils.tag2num(f),
  75. len(v.settings),
  76. 0,
  77. offset * 4 + 12 + 16 * len(self.features),
  78. v.flags,
  79. v.label,
  80. )
  81. elif fnum > 65535: # self healing for alphabetic ids
  82. self.version = 2.0
  83. return self.compile(ttFont)
  84. else:
  85. fdat += struct.pack(
  86. ">HHLHH",
  87. grUtils.tag2num(f),
  88. len(v.settings),
  89. offset * 4 + 12 + 12 * len(self.features),
  90. v.flags,
  91. v.label,
  92. )
  93. for s, l in sorted(
  94. v.settings.items(), key=lambda x: (-1, x[1]) if x[0] == v.default else x
  95. ):
  96. vdat += struct.pack(">HH", s, l)
  97. offset += len(v.settings)
  98. hdr = sstruct.pack(Feat_hdr_format, self)
  99. return hdr + struct.pack(">HHL", len(self.features), 0, 0) + fdat + vdat
  100. def toXML(self, writer, ttFont):
  101. writer.simpletag("version", version=self.version)
  102. writer.newline()
  103. for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
  104. writer.begintag(
  105. "feature",
  106. fid=f,
  107. label=v.label,
  108. flags=v.flags,
  109. default=(v.default if v.default else 0),
  110. )
  111. writer.newline()
  112. for s, l in sorted(v.settings.items()):
  113. writer.simpletag("setting", value=s, label=l)
  114. writer.newline()
  115. writer.endtag("feature")
  116. writer.newline()
  117. def fromXML(self, name, attrs, content, ttFont):
  118. if name == "version":
  119. self.version = float(safeEval(attrs["version"]))
  120. elif name == "feature":
  121. fid = attrs["fid"]
  122. fobj = Feature()
  123. fobj.flags = int(safeEval(attrs["flags"]))
  124. fobj.label = int(safeEval(attrs["label"]))
  125. fobj.default = int(safeEval(attrs.get("default", "0")))
  126. fobj.index = len(self.features)
  127. self.features[fid] = fobj
  128. fobj.settings = {}
  129. for element in content:
  130. if not isinstance(element, tuple):
  131. continue
  132. tag, a, c = element
  133. if tag == "setting":
  134. fobj.settings[int(safeEval(a["value"]))] = int(safeEval(a["label"]))
  135. class Feature(object):
  136. pass