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