aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/ttLib/tables/F__e_a_t.py
blob: fbcd6ca6e7bc0640263ddab74e1e1c89ea61bbfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
from . import DefaultTable
from . import grUtils
import struct

Feat_hdr_format = """
    >
    version:    16.16F
"""


class table_F__e_a_t(DefaultTable.DefaultTable):
    """The ``Feat`` table is used exclusively by the Graphite shaping engine
    to store features and possible settings specified in GDL. Graphite features
    determine what rules are applied to transform a glyph stream.

    Not to be confused with ``feat``, or the OpenType Layout tables
    ``GSUB``/``GPOS``."""

    def __init__(self, tag=None):
        DefaultTable.DefaultTable.__init__(self, tag)
        self.features = {}

    def decompile(self, data, ttFont):
        (_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
        self.version = float(floatToFixedToStr(self.version, precisionBits=16))
        (numFeats,) = struct.unpack(">H", data[:2])
        data = data[8:]
        allfeats = []
        maxsetting = 0
        for i in range(numFeats):
            if self.version >= 2.0:
                (fid, nums, _, offset, flags, lid) = struct.unpack(
                    ">LHHLHH", data[16 * i : 16 * (i + 1)]
                )
                offset = int((offset - 12 - 16 * numFeats) / 4)
            else:
                (fid, nums, offset, flags, lid) = struct.unpack(
                    ">HHLHH", data[12 * i : 12 * (i + 1)]
                )
                offset = int((offset - 12 - 12 * numFeats) / 4)
            allfeats.append((fid, nums, offset, flags, lid))
            maxsetting = max(maxsetting, offset + nums)
        data = data[16 * numFeats :]
        allsettings = []
        for i in range(maxsetting):
            if len(data) >= 4 * (i + 1):
                (val, lid) = struct.unpack(">HH", data[4 * i : 4 * (i + 1)])
                allsettings.append((val, lid))
        for i, f in enumerate(allfeats):
            (fid, nums, offset, flags, lid) = f
            fobj = Feature()
            fobj.flags = flags
            fobj.label = lid
            self.features[grUtils.num2tag(fid)] = fobj
            fobj.settings = {}
            fobj.default = None
            fobj.index = i
            for i in range(offset, offset + nums):
                if i >= len(allsettings):
                    continue
                (vid, vlid) = allsettings[i]
                fobj.settings[vid] = vlid
                if fobj.default is None:
                    fobj.default = vid

    def compile(self, ttFont):
        fdat = b""
        vdat = b""
        offset = 0
        for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
            fnum = grUtils.tag2num(f)
            if self.version >= 2.0:
                fdat += struct.pack(
                    ">LHHLHH",
                    grUtils.tag2num(f),
                    len(v.settings),
                    0,
                    offset * 4 + 12 + 16 * len(self.features),
                    v.flags,
                    v.label,
                )
            elif fnum > 65535:  # self healing for alphabetic ids
                self.version = 2.0
                return self.compile(ttFont)
            else:
                fdat += struct.pack(
                    ">HHLHH",
                    grUtils.tag2num(f),
                    len(v.settings),
                    offset * 4 + 12 + 12 * len(self.features),
                    v.flags,
                    v.label,
                )
            for s, l in sorted(
                v.settings.items(), key=lambda x: (-1, x[1]) if x[0] == v.default else x
            ):
                vdat += struct.pack(">HH", s, l)
            offset += len(v.settings)
        hdr = sstruct.pack(Feat_hdr_format, self)
        return hdr + struct.pack(">HHL", len(self.features), 0, 0) + fdat + vdat

    def toXML(self, writer, ttFont):
        writer.simpletag("version", version=self.version)
        writer.newline()
        for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
            writer.begintag(
                "feature",
                fid=f,
                label=v.label,
                flags=v.flags,
                default=(v.default if v.default else 0),
            )
            writer.newline()
            for s, l in sorted(v.settings.items()):
                writer.simpletag("setting", value=s, label=l)
                writer.newline()
            writer.endtag("feature")
            writer.newline()

    def fromXML(self, name, attrs, content, ttFont):
        if name == "version":
            self.version = float(safeEval(attrs["version"]))
        elif name == "feature":
            fid = attrs["fid"]
            fobj = Feature()
            fobj.flags = int(safeEval(attrs["flags"]))
            fobj.label = int(safeEval(attrs["label"]))
            fobj.default = int(safeEval(attrs.get("default", "0")))
            fobj.index = len(self.features)
            self.features[fid] = fobj
            fobj.settings = {}
            for element in content:
                if not isinstance(element, tuple):
                    continue
                tag, a, c = element
                if tag == "setting":
                    fobj.settings[int(safeEval(a["value"]))] = int(safeEval(a["label"]))


class Feature(object):
    pass