• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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