xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/tables/otConverters.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import (
2*e1fe3e4aSElliott Hughes    fixedToFloat as fi2fl,
3*e1fe3e4aSElliott Hughes    floatToFixed as fl2fi,
4*e1fe3e4aSElliott Hughes    floatToFixedToStr as fl2str,
5*e1fe3e4aSElliott Hughes    strToFixedToFloat as str2fl,
6*e1fe3e4aSElliott Hughes    ensureVersionIsLong as fi2ve,
7*e1fe3e4aSElliott Hughes    versionToFixed as ve2fi,
8*e1fe3e4aSElliott Hughes)
9*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound
10*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval
11*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import getSearchRange
12*e1fe3e4aSElliott Hughesfrom .otBase import (
13*e1fe3e4aSElliott Hughes    CountReference,
14*e1fe3e4aSElliott Hughes    FormatSwitchingBaseTable,
15*e1fe3e4aSElliott Hughes    OTTableReader,
16*e1fe3e4aSElliott Hughes    OTTableWriter,
17*e1fe3e4aSElliott Hughes    ValueRecordFactory,
18*e1fe3e4aSElliott Hughes)
19*e1fe3e4aSElliott Hughesfrom .otTables import (
20*e1fe3e4aSElliott Hughes    lookupTypes,
21*e1fe3e4aSElliott Hughes    AATStateTable,
22*e1fe3e4aSElliott Hughes    AATState,
23*e1fe3e4aSElliott Hughes    AATAction,
24*e1fe3e4aSElliott Hughes    ContextualMorphAction,
25*e1fe3e4aSElliott Hughes    LigatureMorphAction,
26*e1fe3e4aSElliott Hughes    InsertionMorphAction,
27*e1fe3e4aSElliott Hughes    MorxSubtable,
28*e1fe3e4aSElliott Hughes    ExtendMode as _ExtendMode,
29*e1fe3e4aSElliott Hughes    CompositeMode as _CompositeMode,
30*e1fe3e4aSElliott Hughes    NO_VARIATION_INDEX,
31*e1fe3e4aSElliott Hughes)
32*e1fe3e4aSElliott Hughesfrom itertools import zip_longest
33*e1fe3e4aSElliott Hughesfrom functools import partial
34*e1fe3e4aSElliott Hughesimport re
35*e1fe3e4aSElliott Hughesimport struct
36*e1fe3e4aSElliott Hughesfrom typing import Optional
37*e1fe3e4aSElliott Hughesimport logging
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__)
41*e1fe3e4aSElliott Hughesistuple = lambda t: isinstance(t, tuple)
42*e1fe3e4aSElliott Hughes
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughesdef buildConverters(tableSpec, tableNamespace):
45*e1fe3e4aSElliott Hughes    """Given a table spec from otData.py, build a converter object for each
46*e1fe3e4aSElliott Hughes    field of the table. This is called for each table in otData.py, and
47*e1fe3e4aSElliott Hughes    the results are assigned to the corresponding class in otTables.py."""
48*e1fe3e4aSElliott Hughes    converters = []
49*e1fe3e4aSElliott Hughes    convertersByName = {}
50*e1fe3e4aSElliott Hughes    for tp, name, repeat, aux, descr in tableSpec:
51*e1fe3e4aSElliott Hughes        tableName = name
52*e1fe3e4aSElliott Hughes        if name.startswith("ValueFormat"):
53*e1fe3e4aSElliott Hughes            assert tp == "uint16"
54*e1fe3e4aSElliott Hughes            converterClass = ValueFormat
55*e1fe3e4aSElliott Hughes        elif name.endswith("Count") or name in ("StructLength", "MorphType"):
56*e1fe3e4aSElliott Hughes            converterClass = {
57*e1fe3e4aSElliott Hughes                "uint8": ComputedUInt8,
58*e1fe3e4aSElliott Hughes                "uint16": ComputedUShort,
59*e1fe3e4aSElliott Hughes                "uint32": ComputedULong,
60*e1fe3e4aSElliott Hughes            }[tp]
61*e1fe3e4aSElliott Hughes        elif name == "SubTable":
62*e1fe3e4aSElliott Hughes            converterClass = SubTable
63*e1fe3e4aSElliott Hughes        elif name == "ExtSubTable":
64*e1fe3e4aSElliott Hughes            converterClass = ExtSubTable
65*e1fe3e4aSElliott Hughes        elif name == "SubStruct":
66*e1fe3e4aSElliott Hughes            converterClass = SubStruct
67*e1fe3e4aSElliott Hughes        elif name == "FeatureParams":
68*e1fe3e4aSElliott Hughes            converterClass = FeatureParams
69*e1fe3e4aSElliott Hughes        elif name in ("CIDGlyphMapping", "GlyphCIDMapping"):
70*e1fe3e4aSElliott Hughes            converterClass = StructWithLength
71*e1fe3e4aSElliott Hughes        else:
72*e1fe3e4aSElliott Hughes            if not tp in converterMapping and "(" not in tp:
73*e1fe3e4aSElliott Hughes                tableName = tp
74*e1fe3e4aSElliott Hughes                converterClass = Struct
75*e1fe3e4aSElliott Hughes            else:
76*e1fe3e4aSElliott Hughes                converterClass = eval(tp, tableNamespace, converterMapping)
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes        conv = converterClass(name, repeat, aux, description=descr)
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughes        if conv.tableClass:
81*e1fe3e4aSElliott Hughes            # A "template" such as OffsetTo(AType) knowss the table class already
82*e1fe3e4aSElliott Hughes            tableClass = conv.tableClass
83*e1fe3e4aSElliott Hughes        elif tp in ("MortChain", "MortSubtable", "MorxChain"):
84*e1fe3e4aSElliott Hughes            tableClass = tableNamespace.get(tp)
85*e1fe3e4aSElliott Hughes        else:
86*e1fe3e4aSElliott Hughes            tableClass = tableNamespace.get(tableName)
87*e1fe3e4aSElliott Hughes
88*e1fe3e4aSElliott Hughes        if not conv.tableClass:
89*e1fe3e4aSElliott Hughes            conv.tableClass = tableClass
90*e1fe3e4aSElliott Hughes
91*e1fe3e4aSElliott Hughes        if name in ["SubTable", "ExtSubTable", "SubStruct"]:
92*e1fe3e4aSElliott Hughes            conv.lookupTypes = tableNamespace["lookupTypes"]
93*e1fe3e4aSElliott Hughes            # also create reverse mapping
94*e1fe3e4aSElliott Hughes            for t in conv.lookupTypes.values():
95*e1fe3e4aSElliott Hughes                for cls in t.values():
96*e1fe3e4aSElliott Hughes                    convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
97*e1fe3e4aSElliott Hughes        if name == "FeatureParams":
98*e1fe3e4aSElliott Hughes            conv.featureParamTypes = tableNamespace["featureParamTypes"]
99*e1fe3e4aSElliott Hughes            conv.defaultFeatureParams = tableNamespace["FeatureParams"]
100*e1fe3e4aSElliott Hughes            for cls in conv.featureParamTypes.values():
101*e1fe3e4aSElliott Hughes                convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
102*e1fe3e4aSElliott Hughes        converters.append(conv)
103*e1fe3e4aSElliott Hughes        assert name not in convertersByName, name
104*e1fe3e4aSElliott Hughes        convertersByName[name] = conv
105*e1fe3e4aSElliott Hughes    return converters, convertersByName
106*e1fe3e4aSElliott Hughes
107*e1fe3e4aSElliott Hughes
108*e1fe3e4aSElliott Hughesclass _MissingItem(tuple):
109*e1fe3e4aSElliott Hughes    __slots__ = ()
110*e1fe3e4aSElliott Hughes
111*e1fe3e4aSElliott Hughes
112*e1fe3e4aSElliott Hughestry:
113*e1fe3e4aSElliott Hughes    from collections import UserList
114*e1fe3e4aSElliott Hughesexcept ImportError:
115*e1fe3e4aSElliott Hughes    from UserList import UserList
116*e1fe3e4aSElliott Hughes
117*e1fe3e4aSElliott Hughes
118*e1fe3e4aSElliott Hughesclass _LazyList(UserList):
119*e1fe3e4aSElliott Hughes    def __getslice__(self, i, j):
120*e1fe3e4aSElliott Hughes        return self.__getitem__(slice(i, j))
121*e1fe3e4aSElliott Hughes
122*e1fe3e4aSElliott Hughes    def __getitem__(self, k):
123*e1fe3e4aSElliott Hughes        if isinstance(k, slice):
124*e1fe3e4aSElliott Hughes            indices = range(*k.indices(len(self)))
125*e1fe3e4aSElliott Hughes            return [self[i] for i in indices]
126*e1fe3e4aSElliott Hughes        item = self.data[k]
127*e1fe3e4aSElliott Hughes        if isinstance(item, _MissingItem):
128*e1fe3e4aSElliott Hughes            self.reader.seek(self.pos + item[0] * self.recordSize)
129*e1fe3e4aSElliott Hughes            item = self.conv.read(self.reader, self.font, {})
130*e1fe3e4aSElliott Hughes            self.data[k] = item
131*e1fe3e4aSElliott Hughes        return item
132*e1fe3e4aSElliott Hughes
133*e1fe3e4aSElliott Hughes    def __add__(self, other):
134*e1fe3e4aSElliott Hughes        if isinstance(other, _LazyList):
135*e1fe3e4aSElliott Hughes            other = list(other)
136*e1fe3e4aSElliott Hughes        elif isinstance(other, list):
137*e1fe3e4aSElliott Hughes            pass
138*e1fe3e4aSElliott Hughes        else:
139*e1fe3e4aSElliott Hughes            return NotImplemented
140*e1fe3e4aSElliott Hughes        return list(self) + other
141*e1fe3e4aSElliott Hughes
142*e1fe3e4aSElliott Hughes    def __radd__(self, other):
143*e1fe3e4aSElliott Hughes        if not isinstance(other, list):
144*e1fe3e4aSElliott Hughes            return NotImplemented
145*e1fe3e4aSElliott Hughes        return other + list(self)
146*e1fe3e4aSElliott Hughes
147*e1fe3e4aSElliott Hughes
148*e1fe3e4aSElliott Hughesclass BaseConverter(object):
149*e1fe3e4aSElliott Hughes    """Base class for converter objects. Apart from the constructor, this
150*e1fe3e4aSElliott Hughes    is an abstract class."""
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes    def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
153*e1fe3e4aSElliott Hughes        self.name = name
154*e1fe3e4aSElliott Hughes        self.repeat = repeat
155*e1fe3e4aSElliott Hughes        self.aux = aux
156*e1fe3e4aSElliott Hughes        self.tableClass = tableClass
157*e1fe3e4aSElliott Hughes        self.isCount = name.endswith("Count") or name in [
158*e1fe3e4aSElliott Hughes            "DesignAxisRecordSize",
159*e1fe3e4aSElliott Hughes            "ValueRecordSize",
160*e1fe3e4aSElliott Hughes        ]
161*e1fe3e4aSElliott Hughes        self.isLookupType = name.endswith("LookupType") or name == "MorphType"
162*e1fe3e4aSElliott Hughes        self.isPropagated = name in [
163*e1fe3e4aSElliott Hughes            "ClassCount",
164*e1fe3e4aSElliott Hughes            "Class2Count",
165*e1fe3e4aSElliott Hughes            "FeatureTag",
166*e1fe3e4aSElliott Hughes            "SettingsCount",
167*e1fe3e4aSElliott Hughes            "VarRegionCount",
168*e1fe3e4aSElliott Hughes            "MappingCount",
169*e1fe3e4aSElliott Hughes            "RegionAxisCount",
170*e1fe3e4aSElliott Hughes            "DesignAxisCount",
171*e1fe3e4aSElliott Hughes            "DesignAxisRecordSize",
172*e1fe3e4aSElliott Hughes            "AxisValueCount",
173*e1fe3e4aSElliott Hughes            "ValueRecordSize",
174*e1fe3e4aSElliott Hughes            "AxisCount",
175*e1fe3e4aSElliott Hughes            "BaseGlyphRecordCount",
176*e1fe3e4aSElliott Hughes            "LayerRecordCount",
177*e1fe3e4aSElliott Hughes        ]
178*e1fe3e4aSElliott Hughes        self.description = description
179*e1fe3e4aSElliott Hughes
180*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
181*e1fe3e4aSElliott Hughes        """Read an array of values from the reader."""
182*e1fe3e4aSElliott Hughes        lazy = font.lazy and count > 8
183*e1fe3e4aSElliott Hughes        if lazy:
184*e1fe3e4aSElliott Hughes            recordSize = self.getRecordSize(reader)
185*e1fe3e4aSElliott Hughes            if recordSize is NotImplemented:
186*e1fe3e4aSElliott Hughes                lazy = False
187*e1fe3e4aSElliott Hughes        if not lazy:
188*e1fe3e4aSElliott Hughes            l = []
189*e1fe3e4aSElliott Hughes            for i in range(count):
190*e1fe3e4aSElliott Hughes                l.append(self.read(reader, font, tableDict))
191*e1fe3e4aSElliott Hughes            return l
192*e1fe3e4aSElliott Hughes        else:
193*e1fe3e4aSElliott Hughes            l = _LazyList()
194*e1fe3e4aSElliott Hughes            l.reader = reader.copy()
195*e1fe3e4aSElliott Hughes            l.pos = l.reader.pos
196*e1fe3e4aSElliott Hughes            l.font = font
197*e1fe3e4aSElliott Hughes            l.conv = self
198*e1fe3e4aSElliott Hughes            l.recordSize = recordSize
199*e1fe3e4aSElliott Hughes            l.extend(_MissingItem([i]) for i in range(count))
200*e1fe3e4aSElliott Hughes            reader.advance(count * recordSize)
201*e1fe3e4aSElliott Hughes            return l
202*e1fe3e4aSElliott Hughes
203*e1fe3e4aSElliott Hughes    def getRecordSize(self, reader):
204*e1fe3e4aSElliott Hughes        if hasattr(self, "staticSize"):
205*e1fe3e4aSElliott Hughes            return self.staticSize
206*e1fe3e4aSElliott Hughes        return NotImplemented
207*e1fe3e4aSElliott Hughes
208*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
209*e1fe3e4aSElliott Hughes        """Read a value from the reader."""
210*e1fe3e4aSElliott Hughes        raise NotImplementedError(self)
211*e1fe3e4aSElliott Hughes
212*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
213*e1fe3e4aSElliott Hughes        try:
214*e1fe3e4aSElliott Hughes            for i, value in enumerate(values):
215*e1fe3e4aSElliott Hughes                self.write(writer, font, tableDict, value, i)
216*e1fe3e4aSElliott Hughes        except Exception as e:
217*e1fe3e4aSElliott Hughes            e.args = e.args + (i,)
218*e1fe3e4aSElliott Hughes            raise
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
221*e1fe3e4aSElliott Hughes        """Write a value to the writer."""
222*e1fe3e4aSElliott Hughes        raise NotImplementedError(self)
223*e1fe3e4aSElliott Hughes
224*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
225*e1fe3e4aSElliott Hughes        """Read a value from XML."""
226*e1fe3e4aSElliott Hughes        raise NotImplementedError(self)
227*e1fe3e4aSElliott Hughes
228*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
229*e1fe3e4aSElliott Hughes        """Write a value to XML."""
230*e1fe3e4aSElliott Hughes        raise NotImplementedError(self)
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes    varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)")
233*e1fe3e4aSElliott Hughes
234*e1fe3e4aSElliott Hughes    def getVarIndexOffset(self) -> Optional[int]:
235*e1fe3e4aSElliott Hughes        """If description has `VarIndexBase + {offset}`, return the offset else None."""
236*e1fe3e4aSElliott Hughes        m = self.varIndexBasePlusOffsetRE.search(self.description)
237*e1fe3e4aSElliott Hughes        if not m:
238*e1fe3e4aSElliott Hughes            return None
239*e1fe3e4aSElliott Hughes        return int(m.group(1))
240*e1fe3e4aSElliott Hughes
241*e1fe3e4aSElliott Hughes
242*e1fe3e4aSElliott Hughesclass SimpleValue(BaseConverter):
243*e1fe3e4aSElliott Hughes    @staticmethod
244*e1fe3e4aSElliott Hughes    def toString(value):
245*e1fe3e4aSElliott Hughes        return value
246*e1fe3e4aSElliott Hughes
247*e1fe3e4aSElliott Hughes    @staticmethod
248*e1fe3e4aSElliott Hughes    def fromString(value):
249*e1fe3e4aSElliott Hughes        return value
250*e1fe3e4aSElliott Hughes
251*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
252*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", self.toString(value))])
253*e1fe3e4aSElliott Hughes        xmlWriter.newline()
254*e1fe3e4aSElliott Hughes
255*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
256*e1fe3e4aSElliott Hughes        return self.fromString(attrs["value"])
257*e1fe3e4aSElliott Hughes
258*e1fe3e4aSElliott Hughes
259*e1fe3e4aSElliott Hughesclass OptionalValue(SimpleValue):
260*e1fe3e4aSElliott Hughes    DEFAULT = None
261*e1fe3e4aSElliott Hughes
262*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
263*e1fe3e4aSElliott Hughes        if value != self.DEFAULT:
264*e1fe3e4aSElliott Hughes            attrs.append(("value", self.toString(value)))
265*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs)
266*e1fe3e4aSElliott Hughes        xmlWriter.newline()
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
269*e1fe3e4aSElliott Hughes        if "value" in attrs:
270*e1fe3e4aSElliott Hughes            return self.fromString(attrs["value"])
271*e1fe3e4aSElliott Hughes        return self.DEFAULT
272*e1fe3e4aSElliott Hughes
273*e1fe3e4aSElliott Hughes
274*e1fe3e4aSElliott Hughesclass IntValue(SimpleValue):
275*e1fe3e4aSElliott Hughes    @staticmethod
276*e1fe3e4aSElliott Hughes    def fromString(value):
277*e1fe3e4aSElliott Hughes        return int(value, 0)
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughesclass Long(IntValue):
281*e1fe3e4aSElliott Hughes    staticSize = 4
282*e1fe3e4aSElliott Hughes
283*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
284*e1fe3e4aSElliott Hughes        return reader.readLong()
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
287*e1fe3e4aSElliott Hughes        return reader.readLongArray(count)
288*e1fe3e4aSElliott Hughes
289*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
290*e1fe3e4aSElliott Hughes        writer.writeLong(value)
291*e1fe3e4aSElliott Hughes
292*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
293*e1fe3e4aSElliott Hughes        writer.writeLongArray(values)
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes
296*e1fe3e4aSElliott Hughesclass ULong(IntValue):
297*e1fe3e4aSElliott Hughes    staticSize = 4
298*e1fe3e4aSElliott Hughes
299*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
300*e1fe3e4aSElliott Hughes        return reader.readULong()
301*e1fe3e4aSElliott Hughes
302*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
303*e1fe3e4aSElliott Hughes        return reader.readULongArray(count)
304*e1fe3e4aSElliott Hughes
305*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
306*e1fe3e4aSElliott Hughes        writer.writeULong(value)
307*e1fe3e4aSElliott Hughes
308*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
309*e1fe3e4aSElliott Hughes        writer.writeULongArray(values)
310*e1fe3e4aSElliott Hughes
311*e1fe3e4aSElliott Hughes
312*e1fe3e4aSElliott Hughesclass Flags32(ULong):
313*e1fe3e4aSElliott Hughes    @staticmethod
314*e1fe3e4aSElliott Hughes    def toString(value):
315*e1fe3e4aSElliott Hughes        return "0x%08X" % value
316*e1fe3e4aSElliott Hughes
317*e1fe3e4aSElliott Hughes
318*e1fe3e4aSElliott Hughesclass VarIndex(OptionalValue, ULong):
319*e1fe3e4aSElliott Hughes    DEFAULT = NO_VARIATION_INDEX
320*e1fe3e4aSElliott Hughes
321*e1fe3e4aSElliott Hughes
322*e1fe3e4aSElliott Hughesclass Short(IntValue):
323*e1fe3e4aSElliott Hughes    staticSize = 2
324*e1fe3e4aSElliott Hughes
325*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
326*e1fe3e4aSElliott Hughes        return reader.readShort()
327*e1fe3e4aSElliott Hughes
328*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
329*e1fe3e4aSElliott Hughes        return reader.readShortArray(count)
330*e1fe3e4aSElliott Hughes
331*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
332*e1fe3e4aSElliott Hughes        writer.writeShort(value)
333*e1fe3e4aSElliott Hughes
334*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
335*e1fe3e4aSElliott Hughes        writer.writeShortArray(values)
336*e1fe3e4aSElliott Hughes
337*e1fe3e4aSElliott Hughes
338*e1fe3e4aSElliott Hughesclass UShort(IntValue):
339*e1fe3e4aSElliott Hughes    staticSize = 2
340*e1fe3e4aSElliott Hughes
341*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
342*e1fe3e4aSElliott Hughes        return reader.readUShort()
343*e1fe3e4aSElliott Hughes
344*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
345*e1fe3e4aSElliott Hughes        return reader.readUShortArray(count)
346*e1fe3e4aSElliott Hughes
347*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
348*e1fe3e4aSElliott Hughes        writer.writeUShort(value)
349*e1fe3e4aSElliott Hughes
350*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
351*e1fe3e4aSElliott Hughes        writer.writeUShortArray(values)
352*e1fe3e4aSElliott Hughes
353*e1fe3e4aSElliott Hughes
354*e1fe3e4aSElliott Hughesclass Int8(IntValue):
355*e1fe3e4aSElliott Hughes    staticSize = 1
356*e1fe3e4aSElliott Hughes
357*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
358*e1fe3e4aSElliott Hughes        return reader.readInt8()
359*e1fe3e4aSElliott Hughes
360*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
361*e1fe3e4aSElliott Hughes        return reader.readInt8Array(count)
362*e1fe3e4aSElliott Hughes
363*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
364*e1fe3e4aSElliott Hughes        writer.writeInt8(value)
365*e1fe3e4aSElliott Hughes
366*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
367*e1fe3e4aSElliott Hughes        writer.writeInt8Array(values)
368*e1fe3e4aSElliott Hughes
369*e1fe3e4aSElliott Hughes
370*e1fe3e4aSElliott Hughesclass UInt8(IntValue):
371*e1fe3e4aSElliott Hughes    staticSize = 1
372*e1fe3e4aSElliott Hughes
373*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
374*e1fe3e4aSElliott Hughes        return reader.readUInt8()
375*e1fe3e4aSElliott Hughes
376*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
377*e1fe3e4aSElliott Hughes        return reader.readUInt8Array(count)
378*e1fe3e4aSElliott Hughes
379*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
380*e1fe3e4aSElliott Hughes        writer.writeUInt8(value)
381*e1fe3e4aSElliott Hughes
382*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
383*e1fe3e4aSElliott Hughes        writer.writeUInt8Array(values)
384*e1fe3e4aSElliott Hughes
385*e1fe3e4aSElliott Hughes
386*e1fe3e4aSElliott Hughesclass UInt24(IntValue):
387*e1fe3e4aSElliott Hughes    staticSize = 3
388*e1fe3e4aSElliott Hughes
389*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
390*e1fe3e4aSElliott Hughes        return reader.readUInt24()
391*e1fe3e4aSElliott Hughes
392*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
393*e1fe3e4aSElliott Hughes        writer.writeUInt24(value)
394*e1fe3e4aSElliott Hughes
395*e1fe3e4aSElliott Hughes
396*e1fe3e4aSElliott Hughesclass ComputedInt(IntValue):
397*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
398*e1fe3e4aSElliott Hughes        if value is not None:
399*e1fe3e4aSElliott Hughes            xmlWriter.comment("%s=%s" % (name, value))
400*e1fe3e4aSElliott Hughes            xmlWriter.newline()
401*e1fe3e4aSElliott Hughes
402*e1fe3e4aSElliott Hughes
403*e1fe3e4aSElliott Hughesclass ComputedUInt8(ComputedInt, UInt8):
404*e1fe3e4aSElliott Hughes    pass
405*e1fe3e4aSElliott Hughes
406*e1fe3e4aSElliott Hughes
407*e1fe3e4aSElliott Hughesclass ComputedUShort(ComputedInt, UShort):
408*e1fe3e4aSElliott Hughes    pass
409*e1fe3e4aSElliott Hughes
410*e1fe3e4aSElliott Hughes
411*e1fe3e4aSElliott Hughesclass ComputedULong(ComputedInt, ULong):
412*e1fe3e4aSElliott Hughes    pass
413*e1fe3e4aSElliott Hughes
414*e1fe3e4aSElliott Hughes
415*e1fe3e4aSElliott Hughesclass Tag(SimpleValue):
416*e1fe3e4aSElliott Hughes    staticSize = 4
417*e1fe3e4aSElliott Hughes
418*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
419*e1fe3e4aSElliott Hughes        return reader.readTag()
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
422*e1fe3e4aSElliott Hughes        writer.writeTag(value)
423*e1fe3e4aSElliott Hughes
424*e1fe3e4aSElliott Hughes
425*e1fe3e4aSElliott Hughesclass GlyphID(SimpleValue):
426*e1fe3e4aSElliott Hughes    staticSize = 2
427*e1fe3e4aSElliott Hughes    typecode = "H"
428*e1fe3e4aSElliott Hughes
429*e1fe3e4aSElliott Hughes    def readArray(self, reader, font, tableDict, count):
430*e1fe3e4aSElliott Hughes        return font.getGlyphNameMany(
431*e1fe3e4aSElliott Hughes            reader.readArray(self.typecode, self.staticSize, count)
432*e1fe3e4aSElliott Hughes        )
433*e1fe3e4aSElliott Hughes
434*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
435*e1fe3e4aSElliott Hughes        return font.getGlyphName(reader.readValue(self.typecode, self.staticSize))
436*e1fe3e4aSElliott Hughes
437*e1fe3e4aSElliott Hughes    def writeArray(self, writer, font, tableDict, values):
438*e1fe3e4aSElliott Hughes        writer.writeArray(self.typecode, font.getGlyphIDMany(values))
439*e1fe3e4aSElliott Hughes
440*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
441*e1fe3e4aSElliott Hughes        writer.writeValue(self.typecode, font.getGlyphID(value))
442*e1fe3e4aSElliott Hughes
443*e1fe3e4aSElliott Hughes
444*e1fe3e4aSElliott Hughesclass GlyphID32(GlyphID):
445*e1fe3e4aSElliott Hughes    staticSize = 4
446*e1fe3e4aSElliott Hughes    typecode = "L"
447*e1fe3e4aSElliott Hughes
448*e1fe3e4aSElliott Hughes
449*e1fe3e4aSElliott Hughesclass NameID(UShort):
450*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
451*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", value)])
452*e1fe3e4aSElliott Hughes        if font and value:
453*e1fe3e4aSElliott Hughes            nameTable = font.get("name")
454*e1fe3e4aSElliott Hughes            if nameTable:
455*e1fe3e4aSElliott Hughes                name = nameTable.getDebugName(value)
456*e1fe3e4aSElliott Hughes                xmlWriter.write("  ")
457*e1fe3e4aSElliott Hughes                if name:
458*e1fe3e4aSElliott Hughes                    xmlWriter.comment(name)
459*e1fe3e4aSElliott Hughes                else:
460*e1fe3e4aSElliott Hughes                    xmlWriter.comment("missing from name table")
461*e1fe3e4aSElliott Hughes                    log.warning("name id %d missing from name table" % value)
462*e1fe3e4aSElliott Hughes        xmlWriter.newline()
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes
465*e1fe3e4aSElliott Hughesclass STATFlags(UShort):
466*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
467*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", value)])
468*e1fe3e4aSElliott Hughes        flags = []
469*e1fe3e4aSElliott Hughes        if value & 0x01:
470*e1fe3e4aSElliott Hughes            flags.append("OlderSiblingFontAttribute")
471*e1fe3e4aSElliott Hughes        if value & 0x02:
472*e1fe3e4aSElliott Hughes            flags.append("ElidableAxisValueName")
473*e1fe3e4aSElliott Hughes        if flags:
474*e1fe3e4aSElliott Hughes            xmlWriter.write("  ")
475*e1fe3e4aSElliott Hughes            xmlWriter.comment(" ".join(flags))
476*e1fe3e4aSElliott Hughes        xmlWriter.newline()
477*e1fe3e4aSElliott Hughes
478*e1fe3e4aSElliott Hughes
479*e1fe3e4aSElliott Hughesclass FloatValue(SimpleValue):
480*e1fe3e4aSElliott Hughes    @staticmethod
481*e1fe3e4aSElliott Hughes    def fromString(value):
482*e1fe3e4aSElliott Hughes        return float(value)
483*e1fe3e4aSElliott Hughes
484*e1fe3e4aSElliott Hughes
485*e1fe3e4aSElliott Hughesclass DeciPoints(FloatValue):
486*e1fe3e4aSElliott Hughes    staticSize = 2
487*e1fe3e4aSElliott Hughes
488*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
489*e1fe3e4aSElliott Hughes        return reader.readUShort() / 10
490*e1fe3e4aSElliott Hughes
491*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
492*e1fe3e4aSElliott Hughes        writer.writeUShort(round(value * 10))
493*e1fe3e4aSElliott Hughes
494*e1fe3e4aSElliott Hughes
495*e1fe3e4aSElliott Hughesclass BaseFixedValue(FloatValue):
496*e1fe3e4aSElliott Hughes    staticSize = NotImplemented
497*e1fe3e4aSElliott Hughes    precisionBits = NotImplemented
498*e1fe3e4aSElliott Hughes    readerMethod = NotImplemented
499*e1fe3e4aSElliott Hughes    writerMethod = NotImplemented
500*e1fe3e4aSElliott Hughes
501*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
502*e1fe3e4aSElliott Hughes        return self.fromInt(getattr(reader, self.readerMethod)())
503*e1fe3e4aSElliott Hughes
504*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
505*e1fe3e4aSElliott Hughes        getattr(writer, self.writerMethod)(self.toInt(value))
506*e1fe3e4aSElliott Hughes
507*e1fe3e4aSElliott Hughes    @classmethod
508*e1fe3e4aSElliott Hughes    def fromInt(cls, value):
509*e1fe3e4aSElliott Hughes        return fi2fl(value, cls.precisionBits)
510*e1fe3e4aSElliott Hughes
511*e1fe3e4aSElliott Hughes    @classmethod
512*e1fe3e4aSElliott Hughes    def toInt(cls, value):
513*e1fe3e4aSElliott Hughes        return fl2fi(value, cls.precisionBits)
514*e1fe3e4aSElliott Hughes
515*e1fe3e4aSElliott Hughes    @classmethod
516*e1fe3e4aSElliott Hughes    def fromString(cls, value):
517*e1fe3e4aSElliott Hughes        return str2fl(value, cls.precisionBits)
518*e1fe3e4aSElliott Hughes
519*e1fe3e4aSElliott Hughes    @classmethod
520*e1fe3e4aSElliott Hughes    def toString(cls, value):
521*e1fe3e4aSElliott Hughes        return fl2str(value, cls.precisionBits)
522*e1fe3e4aSElliott Hughes
523*e1fe3e4aSElliott Hughes
524*e1fe3e4aSElliott Hughesclass Fixed(BaseFixedValue):
525*e1fe3e4aSElliott Hughes    staticSize = 4
526*e1fe3e4aSElliott Hughes    precisionBits = 16
527*e1fe3e4aSElliott Hughes    readerMethod = "readLong"
528*e1fe3e4aSElliott Hughes    writerMethod = "writeLong"
529*e1fe3e4aSElliott Hughes
530*e1fe3e4aSElliott Hughes
531*e1fe3e4aSElliott Hughesclass F2Dot14(BaseFixedValue):
532*e1fe3e4aSElliott Hughes    staticSize = 2
533*e1fe3e4aSElliott Hughes    precisionBits = 14
534*e1fe3e4aSElliott Hughes    readerMethod = "readShort"
535*e1fe3e4aSElliott Hughes    writerMethod = "writeShort"
536*e1fe3e4aSElliott Hughes
537*e1fe3e4aSElliott Hughes
538*e1fe3e4aSElliott Hughesclass Angle(F2Dot14):
539*e1fe3e4aSElliott Hughes    # angles are specified in degrees, and encoded as F2Dot14 fractions of half
540*e1fe3e4aSElliott Hughes    # circle: e.g. 1.0 => 180, -0.5 => -90, -2.0 => -360, etc.
541*e1fe3e4aSElliott Hughes    bias = 0.0
542*e1fe3e4aSElliott Hughes    factor = 1.0 / (1 << 14) * 180  # 0.010986328125
543*e1fe3e4aSElliott Hughes
544*e1fe3e4aSElliott Hughes    @classmethod
545*e1fe3e4aSElliott Hughes    def fromInt(cls, value):
546*e1fe3e4aSElliott Hughes        return (super().fromInt(value) + cls.bias) * 180
547*e1fe3e4aSElliott Hughes
548*e1fe3e4aSElliott Hughes    @classmethod
549*e1fe3e4aSElliott Hughes    def toInt(cls, value):
550*e1fe3e4aSElliott Hughes        return super().toInt((value / 180) - cls.bias)
551*e1fe3e4aSElliott Hughes
552*e1fe3e4aSElliott Hughes    @classmethod
553*e1fe3e4aSElliott Hughes    def fromString(cls, value):
554*e1fe3e4aSElliott Hughes        # quantize to nearest multiples of minimum fixed-precision angle
555*e1fe3e4aSElliott Hughes        return otRound(float(value) / cls.factor) * cls.factor
556*e1fe3e4aSElliott Hughes
557*e1fe3e4aSElliott Hughes    @classmethod
558*e1fe3e4aSElliott Hughes    def toString(cls, value):
559*e1fe3e4aSElliott Hughes        return nearestMultipleShortestRepr(value, cls.factor)
560*e1fe3e4aSElliott Hughes
561*e1fe3e4aSElliott Hughes
562*e1fe3e4aSElliott Hughesclass BiasedAngle(Angle):
563*e1fe3e4aSElliott Hughes    # A bias of 1.0 is used in the representation of start and end angles
564*e1fe3e4aSElliott Hughes    # of COLRv1 PaintSweepGradients to allow for encoding +360deg
565*e1fe3e4aSElliott Hughes    bias = 1.0
566*e1fe3e4aSElliott Hughes
567*e1fe3e4aSElliott Hughes
568*e1fe3e4aSElliott Hughesclass Version(SimpleValue):
569*e1fe3e4aSElliott Hughes    staticSize = 4
570*e1fe3e4aSElliott Hughes
571*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
572*e1fe3e4aSElliott Hughes        value = reader.readLong()
573*e1fe3e4aSElliott Hughes        return value
574*e1fe3e4aSElliott Hughes
575*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
576*e1fe3e4aSElliott Hughes        value = fi2ve(value)
577*e1fe3e4aSElliott Hughes        writer.writeLong(value)
578*e1fe3e4aSElliott Hughes
579*e1fe3e4aSElliott Hughes    @staticmethod
580*e1fe3e4aSElliott Hughes    def fromString(value):
581*e1fe3e4aSElliott Hughes        return ve2fi(value)
582*e1fe3e4aSElliott Hughes
583*e1fe3e4aSElliott Hughes    @staticmethod
584*e1fe3e4aSElliott Hughes    def toString(value):
585*e1fe3e4aSElliott Hughes        return "0x%08x" % value
586*e1fe3e4aSElliott Hughes
587*e1fe3e4aSElliott Hughes    @staticmethod
588*e1fe3e4aSElliott Hughes    def fromFloat(v):
589*e1fe3e4aSElliott Hughes        return fl2fi(v, 16)
590*e1fe3e4aSElliott Hughes
591*e1fe3e4aSElliott Hughes
592*e1fe3e4aSElliott Hughesclass Char64(SimpleValue):
593*e1fe3e4aSElliott Hughes    """An ASCII string with up to 64 characters.
594*e1fe3e4aSElliott Hughes
595*e1fe3e4aSElliott Hughes    Unused character positions are filled with 0x00 bytes.
596*e1fe3e4aSElliott Hughes    Used in Apple AAT fonts in the `gcid` table.
597*e1fe3e4aSElliott Hughes    """
598*e1fe3e4aSElliott Hughes
599*e1fe3e4aSElliott Hughes    staticSize = 64
600*e1fe3e4aSElliott Hughes
601*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
602*e1fe3e4aSElliott Hughes        data = reader.readData(self.staticSize)
603*e1fe3e4aSElliott Hughes        zeroPos = data.find(b"\0")
604*e1fe3e4aSElliott Hughes        if zeroPos >= 0:
605*e1fe3e4aSElliott Hughes            data = data[:zeroPos]
606*e1fe3e4aSElliott Hughes        s = tostr(data, encoding="ascii", errors="replace")
607*e1fe3e4aSElliott Hughes        if s != tostr(data, encoding="ascii", errors="ignore"):
608*e1fe3e4aSElliott Hughes            log.warning('replaced non-ASCII characters in "%s"' % s)
609*e1fe3e4aSElliott Hughes        return s
610*e1fe3e4aSElliott Hughes
611*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
612*e1fe3e4aSElliott Hughes        data = tobytes(value, encoding="ascii", errors="replace")
613*e1fe3e4aSElliott Hughes        if data != tobytes(value, encoding="ascii", errors="ignore"):
614*e1fe3e4aSElliott Hughes            log.warning('replacing non-ASCII characters in "%s"' % value)
615*e1fe3e4aSElliott Hughes        if len(data) > self.staticSize:
616*e1fe3e4aSElliott Hughes            log.warning(
617*e1fe3e4aSElliott Hughes                'truncating overlong "%s" to %d bytes' % (value, self.staticSize)
618*e1fe3e4aSElliott Hughes            )
619*e1fe3e4aSElliott Hughes        data = (data + b"\0" * self.staticSize)[: self.staticSize]
620*e1fe3e4aSElliott Hughes        writer.writeData(data)
621*e1fe3e4aSElliott Hughes
622*e1fe3e4aSElliott Hughes
623*e1fe3e4aSElliott Hughesclass Struct(BaseConverter):
624*e1fe3e4aSElliott Hughes    def getRecordSize(self, reader):
625*e1fe3e4aSElliott Hughes        return self.tableClass and self.tableClass.getRecordSize(reader)
626*e1fe3e4aSElliott Hughes
627*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
628*e1fe3e4aSElliott Hughes        table = self.tableClass()
629*e1fe3e4aSElliott Hughes        table.decompile(reader, font)
630*e1fe3e4aSElliott Hughes        return table
631*e1fe3e4aSElliott Hughes
632*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
633*e1fe3e4aSElliott Hughes        value.compile(writer, font)
634*e1fe3e4aSElliott Hughes
635*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
636*e1fe3e4aSElliott Hughes        if value is None:
637*e1fe3e4aSElliott Hughes            if attrs:
638*e1fe3e4aSElliott Hughes                # If there are attributes (probably index), then
639*e1fe3e4aSElliott Hughes                # don't drop this even if it's NULL.  It will mess
640*e1fe3e4aSElliott Hughes                # up the array indices of the containing element.
641*e1fe3e4aSElliott Hughes                xmlWriter.simpletag(name, attrs + [("empty", 1)])
642*e1fe3e4aSElliott Hughes                xmlWriter.newline()
643*e1fe3e4aSElliott Hughes            else:
644*e1fe3e4aSElliott Hughes                pass  # NULL table, ignore
645*e1fe3e4aSElliott Hughes        else:
646*e1fe3e4aSElliott Hughes            value.toXML(xmlWriter, font, attrs, name=name)
647*e1fe3e4aSElliott Hughes
648*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
649*e1fe3e4aSElliott Hughes        if "empty" in attrs and safeEval(attrs["empty"]):
650*e1fe3e4aSElliott Hughes            return None
651*e1fe3e4aSElliott Hughes        table = self.tableClass()
652*e1fe3e4aSElliott Hughes        Format = attrs.get("Format")
653*e1fe3e4aSElliott Hughes        if Format is not None:
654*e1fe3e4aSElliott Hughes            table.Format = int(Format)
655*e1fe3e4aSElliott Hughes
656*e1fe3e4aSElliott Hughes        noPostRead = not hasattr(table, "postRead")
657*e1fe3e4aSElliott Hughes        if noPostRead:
658*e1fe3e4aSElliott Hughes            # TODO Cache table.hasPropagated.
659*e1fe3e4aSElliott Hughes            cleanPropagation = False
660*e1fe3e4aSElliott Hughes            for conv in table.getConverters():
661*e1fe3e4aSElliott Hughes                if conv.isPropagated:
662*e1fe3e4aSElliott Hughes                    cleanPropagation = True
663*e1fe3e4aSElliott Hughes                    if not hasattr(font, "_propagator"):
664*e1fe3e4aSElliott Hughes                        font._propagator = {}
665*e1fe3e4aSElliott Hughes                    propagator = font._propagator
666*e1fe3e4aSElliott Hughes                    assert conv.name not in propagator, (conv.name, propagator)
667*e1fe3e4aSElliott Hughes                    setattr(table, conv.name, None)
668*e1fe3e4aSElliott Hughes                    propagator[conv.name] = CountReference(table.__dict__, conv.name)
669*e1fe3e4aSElliott Hughes
670*e1fe3e4aSElliott Hughes        for element in content:
671*e1fe3e4aSElliott Hughes            if isinstance(element, tuple):
672*e1fe3e4aSElliott Hughes                name, attrs, content = element
673*e1fe3e4aSElliott Hughes                table.fromXML(name, attrs, content, font)
674*e1fe3e4aSElliott Hughes            else:
675*e1fe3e4aSElliott Hughes                pass
676*e1fe3e4aSElliott Hughes
677*e1fe3e4aSElliott Hughes        table.populateDefaults(propagator=getattr(font, "_propagator", None))
678*e1fe3e4aSElliott Hughes
679*e1fe3e4aSElliott Hughes        if noPostRead:
680*e1fe3e4aSElliott Hughes            if cleanPropagation:
681*e1fe3e4aSElliott Hughes                for conv in table.getConverters():
682*e1fe3e4aSElliott Hughes                    if conv.isPropagated:
683*e1fe3e4aSElliott Hughes                        propagator = font._propagator
684*e1fe3e4aSElliott Hughes                        del propagator[conv.name]
685*e1fe3e4aSElliott Hughes                        if not propagator:
686*e1fe3e4aSElliott Hughes                            del font._propagator
687*e1fe3e4aSElliott Hughes
688*e1fe3e4aSElliott Hughes        return table
689*e1fe3e4aSElliott Hughes
690*e1fe3e4aSElliott Hughes    def __repr__(self):
691*e1fe3e4aSElliott Hughes        return "Struct of " + repr(self.tableClass)
692*e1fe3e4aSElliott Hughes
693*e1fe3e4aSElliott Hughes
694*e1fe3e4aSElliott Hughesclass StructWithLength(Struct):
695*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
696*e1fe3e4aSElliott Hughes        pos = reader.pos
697*e1fe3e4aSElliott Hughes        table = self.tableClass()
698*e1fe3e4aSElliott Hughes        table.decompile(reader, font)
699*e1fe3e4aSElliott Hughes        reader.seek(pos + table.StructLength)
700*e1fe3e4aSElliott Hughes        return table
701*e1fe3e4aSElliott Hughes
702*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
703*e1fe3e4aSElliott Hughes        for convIndex, conv in enumerate(value.getConverters()):
704*e1fe3e4aSElliott Hughes            if conv.name == "StructLength":
705*e1fe3e4aSElliott Hughes                break
706*e1fe3e4aSElliott Hughes        lengthIndex = len(writer.items) + convIndex
707*e1fe3e4aSElliott Hughes        if isinstance(value, FormatSwitchingBaseTable):
708*e1fe3e4aSElliott Hughes            lengthIndex += 1  # implicit Format field
709*e1fe3e4aSElliott Hughes        deadbeef = {1: 0xDE, 2: 0xDEAD, 4: 0xDEADBEEF}[conv.staticSize]
710*e1fe3e4aSElliott Hughes
711*e1fe3e4aSElliott Hughes        before = writer.getDataLength()
712*e1fe3e4aSElliott Hughes        value.StructLength = deadbeef
713*e1fe3e4aSElliott Hughes        value.compile(writer, font)
714*e1fe3e4aSElliott Hughes        length = writer.getDataLength() - before
715*e1fe3e4aSElliott Hughes        lengthWriter = writer.getSubWriter()
716*e1fe3e4aSElliott Hughes        conv.write(lengthWriter, font, tableDict, length)
717*e1fe3e4aSElliott Hughes        assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"[: conv.staticSize]
718*e1fe3e4aSElliott Hughes        writer.items[lengthIndex] = lengthWriter.getAllData()
719*e1fe3e4aSElliott Hughes
720*e1fe3e4aSElliott Hughes
721*e1fe3e4aSElliott Hughesclass Table(Struct):
722*e1fe3e4aSElliott Hughes    staticSize = 2
723*e1fe3e4aSElliott Hughes
724*e1fe3e4aSElliott Hughes    def readOffset(self, reader):
725*e1fe3e4aSElliott Hughes        return reader.readUShort()
726*e1fe3e4aSElliott Hughes
727*e1fe3e4aSElliott Hughes    def writeNullOffset(self, writer):
728*e1fe3e4aSElliott Hughes        writer.writeUShort(0)
729*e1fe3e4aSElliott Hughes
730*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
731*e1fe3e4aSElliott Hughes        offset = self.readOffset(reader)
732*e1fe3e4aSElliott Hughes        if offset == 0:
733*e1fe3e4aSElliott Hughes            return None
734*e1fe3e4aSElliott Hughes        table = self.tableClass()
735*e1fe3e4aSElliott Hughes        reader = reader.getSubReader(offset)
736*e1fe3e4aSElliott Hughes        if font.lazy:
737*e1fe3e4aSElliott Hughes            table.reader = reader
738*e1fe3e4aSElliott Hughes            table.font = font
739*e1fe3e4aSElliott Hughes        else:
740*e1fe3e4aSElliott Hughes            table.decompile(reader, font)
741*e1fe3e4aSElliott Hughes        return table
742*e1fe3e4aSElliott Hughes
743*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
744*e1fe3e4aSElliott Hughes        if value is None:
745*e1fe3e4aSElliott Hughes            self.writeNullOffset(writer)
746*e1fe3e4aSElliott Hughes        else:
747*e1fe3e4aSElliott Hughes            subWriter = writer.getSubWriter()
748*e1fe3e4aSElliott Hughes            subWriter.name = self.name
749*e1fe3e4aSElliott Hughes            if repeatIndex is not None:
750*e1fe3e4aSElliott Hughes                subWriter.repeatIndex = repeatIndex
751*e1fe3e4aSElliott Hughes            writer.writeSubTable(subWriter, offsetSize=self.staticSize)
752*e1fe3e4aSElliott Hughes            value.compile(subWriter, font)
753*e1fe3e4aSElliott Hughes
754*e1fe3e4aSElliott Hughes
755*e1fe3e4aSElliott Hughesclass LTable(Table):
756*e1fe3e4aSElliott Hughes    staticSize = 4
757*e1fe3e4aSElliott Hughes
758*e1fe3e4aSElliott Hughes    def readOffset(self, reader):
759*e1fe3e4aSElliott Hughes        return reader.readULong()
760*e1fe3e4aSElliott Hughes
761*e1fe3e4aSElliott Hughes    def writeNullOffset(self, writer):
762*e1fe3e4aSElliott Hughes        writer.writeULong(0)
763*e1fe3e4aSElliott Hughes
764*e1fe3e4aSElliott Hughes
765*e1fe3e4aSElliott Hughes# Table pointed to by a 24-bit, 3-byte long offset
766*e1fe3e4aSElliott Hughesclass Table24(Table):
767*e1fe3e4aSElliott Hughes    staticSize = 3
768*e1fe3e4aSElliott Hughes
769*e1fe3e4aSElliott Hughes    def readOffset(self, reader):
770*e1fe3e4aSElliott Hughes        return reader.readUInt24()
771*e1fe3e4aSElliott Hughes
772*e1fe3e4aSElliott Hughes    def writeNullOffset(self, writer):
773*e1fe3e4aSElliott Hughes        writer.writeUInt24(0)
774*e1fe3e4aSElliott Hughes
775*e1fe3e4aSElliott Hughes
776*e1fe3e4aSElliott Hughes# TODO Clean / merge the SubTable and SubStruct
777*e1fe3e4aSElliott Hughes
778*e1fe3e4aSElliott Hughes
779*e1fe3e4aSElliott Hughesclass SubStruct(Struct):
780*e1fe3e4aSElliott Hughes    def getConverter(self, tableType, lookupType):
781*e1fe3e4aSElliott Hughes        tableClass = self.lookupTypes[tableType][lookupType]
782*e1fe3e4aSElliott Hughes        return self.__class__(self.name, self.repeat, self.aux, tableClass)
783*e1fe3e4aSElliott Hughes
784*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
785*e1fe3e4aSElliott Hughes        super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
786*e1fe3e4aSElliott Hughes
787*e1fe3e4aSElliott Hughes
788*e1fe3e4aSElliott Hughesclass SubTable(Table):
789*e1fe3e4aSElliott Hughes    def getConverter(self, tableType, lookupType):
790*e1fe3e4aSElliott Hughes        tableClass = self.lookupTypes[tableType][lookupType]
791*e1fe3e4aSElliott Hughes        return self.__class__(self.name, self.repeat, self.aux, tableClass)
792*e1fe3e4aSElliott Hughes
793*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
794*e1fe3e4aSElliott Hughes        super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
795*e1fe3e4aSElliott Hughes
796*e1fe3e4aSElliott Hughes
797*e1fe3e4aSElliott Hughesclass ExtSubTable(LTable, SubTable):
798*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
799*e1fe3e4aSElliott Hughes        writer.Extension = True  # actually, mere presence of the field flags it as an Ext Subtable writer.
800*e1fe3e4aSElliott Hughes        Table.write(self, writer, font, tableDict, value, repeatIndex)
801*e1fe3e4aSElliott Hughes
802*e1fe3e4aSElliott Hughes
803*e1fe3e4aSElliott Hughesclass FeatureParams(Table):
804*e1fe3e4aSElliott Hughes    def getConverter(self, featureTag):
805*e1fe3e4aSElliott Hughes        tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
806*e1fe3e4aSElliott Hughes        return self.__class__(self.name, self.repeat, self.aux, tableClass)
807*e1fe3e4aSElliott Hughes
808*e1fe3e4aSElliott Hughes
809*e1fe3e4aSElliott Hughesclass ValueFormat(IntValue):
810*e1fe3e4aSElliott Hughes    staticSize = 2
811*e1fe3e4aSElliott Hughes
812*e1fe3e4aSElliott Hughes    def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
813*e1fe3e4aSElliott Hughes        BaseConverter.__init__(
814*e1fe3e4aSElliott Hughes            self, name, repeat, aux, tableClass, description=description
815*e1fe3e4aSElliott Hughes        )
816*e1fe3e4aSElliott Hughes        self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
817*e1fe3e4aSElliott Hughes
818*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
819*e1fe3e4aSElliott Hughes        format = reader.readUShort()
820*e1fe3e4aSElliott Hughes        reader[self.which] = ValueRecordFactory(format)
821*e1fe3e4aSElliott Hughes        return format
822*e1fe3e4aSElliott Hughes
823*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, format, repeatIndex=None):
824*e1fe3e4aSElliott Hughes        writer.writeUShort(format)
825*e1fe3e4aSElliott Hughes        writer[self.which] = ValueRecordFactory(format)
826*e1fe3e4aSElliott Hughes
827*e1fe3e4aSElliott Hughes
828*e1fe3e4aSElliott Hughesclass ValueRecord(ValueFormat):
829*e1fe3e4aSElliott Hughes    def getRecordSize(self, reader):
830*e1fe3e4aSElliott Hughes        return 2 * len(reader[self.which])
831*e1fe3e4aSElliott Hughes
832*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
833*e1fe3e4aSElliott Hughes        return reader[self.which].readValueRecord(reader, font)
834*e1fe3e4aSElliott Hughes
835*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
836*e1fe3e4aSElliott Hughes        writer[self.which].writeValueRecord(writer, font, value)
837*e1fe3e4aSElliott Hughes
838*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
839*e1fe3e4aSElliott Hughes        if value is None:
840*e1fe3e4aSElliott Hughes            pass  # NULL table, ignore
841*e1fe3e4aSElliott Hughes        else:
842*e1fe3e4aSElliott Hughes            value.toXML(xmlWriter, font, self.name, attrs)
843*e1fe3e4aSElliott Hughes
844*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
845*e1fe3e4aSElliott Hughes        from .otBase import ValueRecord
846*e1fe3e4aSElliott Hughes
847*e1fe3e4aSElliott Hughes        value = ValueRecord()
848*e1fe3e4aSElliott Hughes        value.fromXML(None, attrs, content, font)
849*e1fe3e4aSElliott Hughes        return value
850*e1fe3e4aSElliott Hughes
851*e1fe3e4aSElliott Hughes
852*e1fe3e4aSElliott Hughesclass AATLookup(BaseConverter):
853*e1fe3e4aSElliott Hughes    BIN_SEARCH_HEADER_SIZE = 10
854*e1fe3e4aSElliott Hughes
855*e1fe3e4aSElliott Hughes    def __init__(self, name, repeat, aux, tableClass, *, description=""):
856*e1fe3e4aSElliott Hughes        BaseConverter.__init__(
857*e1fe3e4aSElliott Hughes            self, name, repeat, aux, tableClass, description=description
858*e1fe3e4aSElliott Hughes        )
859*e1fe3e4aSElliott Hughes        if issubclass(self.tableClass, SimpleValue):
860*e1fe3e4aSElliott Hughes            self.converter = self.tableClass(name="Value", repeat=None, aux=None)
861*e1fe3e4aSElliott Hughes        else:
862*e1fe3e4aSElliott Hughes            self.converter = Table(
863*e1fe3e4aSElliott Hughes                name="Value", repeat=None, aux=None, tableClass=self.tableClass
864*e1fe3e4aSElliott Hughes            )
865*e1fe3e4aSElliott Hughes
866*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
867*e1fe3e4aSElliott Hughes        format = reader.readUShort()
868*e1fe3e4aSElliott Hughes        if format == 0:
869*e1fe3e4aSElliott Hughes            return self.readFormat0(reader, font)
870*e1fe3e4aSElliott Hughes        elif format == 2:
871*e1fe3e4aSElliott Hughes            return self.readFormat2(reader, font)
872*e1fe3e4aSElliott Hughes        elif format == 4:
873*e1fe3e4aSElliott Hughes            return self.readFormat4(reader, font)
874*e1fe3e4aSElliott Hughes        elif format == 6:
875*e1fe3e4aSElliott Hughes            return self.readFormat6(reader, font)
876*e1fe3e4aSElliott Hughes        elif format == 8:
877*e1fe3e4aSElliott Hughes            return self.readFormat8(reader, font)
878*e1fe3e4aSElliott Hughes        else:
879*e1fe3e4aSElliott Hughes            assert False, "unsupported lookup format: %d" % format
880*e1fe3e4aSElliott Hughes
881*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
882*e1fe3e4aSElliott Hughes        values = list(
883*e1fe3e4aSElliott Hughes            sorted([(font.getGlyphID(glyph), val) for glyph, val in value.items()])
884*e1fe3e4aSElliott Hughes        )
885*e1fe3e4aSElliott Hughes        # TODO: Also implement format 4.
886*e1fe3e4aSElliott Hughes        formats = list(
887*e1fe3e4aSElliott Hughes            sorted(
888*e1fe3e4aSElliott Hughes                filter(
889*e1fe3e4aSElliott Hughes                    None,
890*e1fe3e4aSElliott Hughes                    [
891*e1fe3e4aSElliott Hughes                        self.buildFormat0(writer, font, values),
892*e1fe3e4aSElliott Hughes                        self.buildFormat2(writer, font, values),
893*e1fe3e4aSElliott Hughes                        self.buildFormat6(writer, font, values),
894*e1fe3e4aSElliott Hughes                        self.buildFormat8(writer, font, values),
895*e1fe3e4aSElliott Hughes                    ],
896*e1fe3e4aSElliott Hughes                )
897*e1fe3e4aSElliott Hughes            )
898*e1fe3e4aSElliott Hughes        )
899*e1fe3e4aSElliott Hughes        # We use the format ID as secondary sort key to make the output
900*e1fe3e4aSElliott Hughes        # deterministic when multiple formats have same encoded size.
901*e1fe3e4aSElliott Hughes        dataSize, lookupFormat, writeMethod = formats[0]
902*e1fe3e4aSElliott Hughes        pos = writer.getDataLength()
903*e1fe3e4aSElliott Hughes        writeMethod()
904*e1fe3e4aSElliott Hughes        actualSize = writer.getDataLength() - pos
905*e1fe3e4aSElliott Hughes        assert (
906*e1fe3e4aSElliott Hughes            actualSize == dataSize
907*e1fe3e4aSElliott Hughes        ), "AATLookup format %d claimed to write %d bytes, but wrote %d" % (
908*e1fe3e4aSElliott Hughes            lookupFormat,
909*e1fe3e4aSElliott Hughes            dataSize,
910*e1fe3e4aSElliott Hughes            actualSize,
911*e1fe3e4aSElliott Hughes        )
912*e1fe3e4aSElliott Hughes
913*e1fe3e4aSElliott Hughes    @staticmethod
914*e1fe3e4aSElliott Hughes    def writeBinSearchHeader(writer, numUnits, unitSize):
915*e1fe3e4aSElliott Hughes        writer.writeUShort(unitSize)
916*e1fe3e4aSElliott Hughes        writer.writeUShort(numUnits)
917*e1fe3e4aSElliott Hughes        searchRange, entrySelector, rangeShift = getSearchRange(
918*e1fe3e4aSElliott Hughes            n=numUnits, itemSize=unitSize
919*e1fe3e4aSElliott Hughes        )
920*e1fe3e4aSElliott Hughes        writer.writeUShort(searchRange)
921*e1fe3e4aSElliott Hughes        writer.writeUShort(entrySelector)
922*e1fe3e4aSElliott Hughes        writer.writeUShort(rangeShift)
923*e1fe3e4aSElliott Hughes
924*e1fe3e4aSElliott Hughes    def buildFormat0(self, writer, font, values):
925*e1fe3e4aSElliott Hughes        numGlyphs = len(font.getGlyphOrder())
926*e1fe3e4aSElliott Hughes        if len(values) != numGlyphs:
927*e1fe3e4aSElliott Hughes            return None
928*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
929*e1fe3e4aSElliott Hughes        return (
930*e1fe3e4aSElliott Hughes            2 + numGlyphs * valueSize,
931*e1fe3e4aSElliott Hughes            0,
932*e1fe3e4aSElliott Hughes            lambda: self.writeFormat0(writer, font, values),
933*e1fe3e4aSElliott Hughes        )
934*e1fe3e4aSElliott Hughes
935*e1fe3e4aSElliott Hughes    def writeFormat0(self, writer, font, values):
936*e1fe3e4aSElliott Hughes        writer.writeUShort(0)
937*e1fe3e4aSElliott Hughes        for glyphID_, value in values:
938*e1fe3e4aSElliott Hughes            self.converter.write(
939*e1fe3e4aSElliott Hughes                writer, font, tableDict=None, value=value, repeatIndex=None
940*e1fe3e4aSElliott Hughes            )
941*e1fe3e4aSElliott Hughes
942*e1fe3e4aSElliott Hughes    def buildFormat2(self, writer, font, values):
943*e1fe3e4aSElliott Hughes        segStart, segValue = values[0]
944*e1fe3e4aSElliott Hughes        segEnd = segStart
945*e1fe3e4aSElliott Hughes        segments = []
946*e1fe3e4aSElliott Hughes        for glyphID, curValue in values[1:]:
947*e1fe3e4aSElliott Hughes            if glyphID != segEnd + 1 or curValue != segValue:
948*e1fe3e4aSElliott Hughes                segments.append((segStart, segEnd, segValue))
949*e1fe3e4aSElliott Hughes                segStart = segEnd = glyphID
950*e1fe3e4aSElliott Hughes                segValue = curValue
951*e1fe3e4aSElliott Hughes            else:
952*e1fe3e4aSElliott Hughes                segEnd = glyphID
953*e1fe3e4aSElliott Hughes        segments.append((segStart, segEnd, segValue))
954*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
955*e1fe3e4aSElliott Hughes        numUnits, unitSize = len(segments) + 1, valueSize + 4
956*e1fe3e4aSElliott Hughes        return (
957*e1fe3e4aSElliott Hughes            2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize,
958*e1fe3e4aSElliott Hughes            2,
959*e1fe3e4aSElliott Hughes            lambda: self.writeFormat2(writer, font, segments),
960*e1fe3e4aSElliott Hughes        )
961*e1fe3e4aSElliott Hughes
962*e1fe3e4aSElliott Hughes    def writeFormat2(self, writer, font, segments):
963*e1fe3e4aSElliott Hughes        writer.writeUShort(2)
964*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
965*e1fe3e4aSElliott Hughes        numUnits, unitSize = len(segments), valueSize + 4
966*e1fe3e4aSElliott Hughes        self.writeBinSearchHeader(writer, numUnits, unitSize)
967*e1fe3e4aSElliott Hughes        for firstGlyph, lastGlyph, value in segments:
968*e1fe3e4aSElliott Hughes            writer.writeUShort(lastGlyph)
969*e1fe3e4aSElliott Hughes            writer.writeUShort(firstGlyph)
970*e1fe3e4aSElliott Hughes            self.converter.write(
971*e1fe3e4aSElliott Hughes                writer, font, tableDict=None, value=value, repeatIndex=None
972*e1fe3e4aSElliott Hughes            )
973*e1fe3e4aSElliott Hughes        writer.writeUShort(0xFFFF)
974*e1fe3e4aSElliott Hughes        writer.writeUShort(0xFFFF)
975*e1fe3e4aSElliott Hughes        writer.writeData(b"\x00" * valueSize)
976*e1fe3e4aSElliott Hughes
977*e1fe3e4aSElliott Hughes    def buildFormat6(self, writer, font, values):
978*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
979*e1fe3e4aSElliott Hughes        numUnits, unitSize = len(values), valueSize + 2
980*e1fe3e4aSElliott Hughes        return (
981*e1fe3e4aSElliott Hughes            2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize,
982*e1fe3e4aSElliott Hughes            6,
983*e1fe3e4aSElliott Hughes            lambda: self.writeFormat6(writer, font, values),
984*e1fe3e4aSElliott Hughes        )
985*e1fe3e4aSElliott Hughes
986*e1fe3e4aSElliott Hughes    def writeFormat6(self, writer, font, values):
987*e1fe3e4aSElliott Hughes        writer.writeUShort(6)
988*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
989*e1fe3e4aSElliott Hughes        numUnits, unitSize = len(values), valueSize + 2
990*e1fe3e4aSElliott Hughes        self.writeBinSearchHeader(writer, numUnits, unitSize)
991*e1fe3e4aSElliott Hughes        for glyphID, value in values:
992*e1fe3e4aSElliott Hughes            writer.writeUShort(glyphID)
993*e1fe3e4aSElliott Hughes            self.converter.write(
994*e1fe3e4aSElliott Hughes                writer, font, tableDict=None, value=value, repeatIndex=None
995*e1fe3e4aSElliott Hughes            )
996*e1fe3e4aSElliott Hughes        writer.writeUShort(0xFFFF)
997*e1fe3e4aSElliott Hughes        writer.writeData(b"\x00" * valueSize)
998*e1fe3e4aSElliott Hughes
999*e1fe3e4aSElliott Hughes    def buildFormat8(self, writer, font, values):
1000*e1fe3e4aSElliott Hughes        minGlyphID, maxGlyphID = values[0][0], values[-1][0]
1001*e1fe3e4aSElliott Hughes        if len(values) != maxGlyphID - minGlyphID + 1:
1002*e1fe3e4aSElliott Hughes            return None
1003*e1fe3e4aSElliott Hughes        valueSize = self.converter.staticSize
1004*e1fe3e4aSElliott Hughes        return (
1005*e1fe3e4aSElliott Hughes            6 + len(values) * valueSize,
1006*e1fe3e4aSElliott Hughes            8,
1007*e1fe3e4aSElliott Hughes            lambda: self.writeFormat8(writer, font, values),
1008*e1fe3e4aSElliott Hughes        )
1009*e1fe3e4aSElliott Hughes
1010*e1fe3e4aSElliott Hughes    def writeFormat8(self, writer, font, values):
1011*e1fe3e4aSElliott Hughes        firstGlyphID = values[0][0]
1012*e1fe3e4aSElliott Hughes        writer.writeUShort(8)
1013*e1fe3e4aSElliott Hughes        writer.writeUShort(firstGlyphID)
1014*e1fe3e4aSElliott Hughes        writer.writeUShort(len(values))
1015*e1fe3e4aSElliott Hughes        for _, value in values:
1016*e1fe3e4aSElliott Hughes            self.converter.write(
1017*e1fe3e4aSElliott Hughes                writer, font, tableDict=None, value=value, repeatIndex=None
1018*e1fe3e4aSElliott Hughes            )
1019*e1fe3e4aSElliott Hughes
1020*e1fe3e4aSElliott Hughes    def readFormat0(self, reader, font):
1021*e1fe3e4aSElliott Hughes        numGlyphs = len(font.getGlyphOrder())
1022*e1fe3e4aSElliott Hughes        data = self.converter.readArray(reader, font, tableDict=None, count=numGlyphs)
1023*e1fe3e4aSElliott Hughes        return {font.getGlyphName(k): value for k, value in enumerate(data)}
1024*e1fe3e4aSElliott Hughes
1025*e1fe3e4aSElliott Hughes    def readFormat2(self, reader, font):
1026*e1fe3e4aSElliott Hughes        mapping = {}
1027*e1fe3e4aSElliott Hughes        pos = reader.pos - 2  # start of table is at UShort for format
1028*e1fe3e4aSElliott Hughes        unitSize, numUnits = reader.readUShort(), reader.readUShort()
1029*e1fe3e4aSElliott Hughes        assert unitSize >= 4 + self.converter.staticSize, unitSize
1030*e1fe3e4aSElliott Hughes        for i in range(numUnits):
1031*e1fe3e4aSElliott Hughes            reader.seek(pos + i * unitSize + 12)
1032*e1fe3e4aSElliott Hughes            last = reader.readUShort()
1033*e1fe3e4aSElliott Hughes            first = reader.readUShort()
1034*e1fe3e4aSElliott Hughes            value = self.converter.read(reader, font, tableDict=None)
1035*e1fe3e4aSElliott Hughes            if last != 0xFFFF:
1036*e1fe3e4aSElliott Hughes                for k in range(first, last + 1):
1037*e1fe3e4aSElliott Hughes                    mapping[font.getGlyphName(k)] = value
1038*e1fe3e4aSElliott Hughes        return mapping
1039*e1fe3e4aSElliott Hughes
1040*e1fe3e4aSElliott Hughes    def readFormat4(self, reader, font):
1041*e1fe3e4aSElliott Hughes        mapping = {}
1042*e1fe3e4aSElliott Hughes        pos = reader.pos - 2  # start of table is at UShort for format
1043*e1fe3e4aSElliott Hughes        unitSize = reader.readUShort()
1044*e1fe3e4aSElliott Hughes        assert unitSize >= 6, unitSize
1045*e1fe3e4aSElliott Hughes        for i in range(reader.readUShort()):
1046*e1fe3e4aSElliott Hughes            reader.seek(pos + i * unitSize + 12)
1047*e1fe3e4aSElliott Hughes            last = reader.readUShort()
1048*e1fe3e4aSElliott Hughes            first = reader.readUShort()
1049*e1fe3e4aSElliott Hughes            offset = reader.readUShort()
1050*e1fe3e4aSElliott Hughes            if last != 0xFFFF:
1051*e1fe3e4aSElliott Hughes                dataReader = reader.getSubReader(0)  # relative to current position
1052*e1fe3e4aSElliott Hughes                dataReader.seek(pos + offset)  # relative to start of table
1053*e1fe3e4aSElliott Hughes                data = self.converter.readArray(
1054*e1fe3e4aSElliott Hughes                    dataReader, font, tableDict=None, count=last - first + 1
1055*e1fe3e4aSElliott Hughes                )
1056*e1fe3e4aSElliott Hughes                for k, v in enumerate(data):
1057*e1fe3e4aSElliott Hughes                    mapping[font.getGlyphName(first + k)] = v
1058*e1fe3e4aSElliott Hughes        return mapping
1059*e1fe3e4aSElliott Hughes
1060*e1fe3e4aSElliott Hughes    def readFormat6(self, reader, font):
1061*e1fe3e4aSElliott Hughes        mapping = {}
1062*e1fe3e4aSElliott Hughes        pos = reader.pos - 2  # start of table is at UShort for format
1063*e1fe3e4aSElliott Hughes        unitSize = reader.readUShort()
1064*e1fe3e4aSElliott Hughes        assert unitSize >= 2 + self.converter.staticSize, unitSize
1065*e1fe3e4aSElliott Hughes        for i in range(reader.readUShort()):
1066*e1fe3e4aSElliott Hughes            reader.seek(pos + i * unitSize + 12)
1067*e1fe3e4aSElliott Hughes            glyphID = reader.readUShort()
1068*e1fe3e4aSElliott Hughes            value = self.converter.read(reader, font, tableDict=None)
1069*e1fe3e4aSElliott Hughes            if glyphID != 0xFFFF:
1070*e1fe3e4aSElliott Hughes                mapping[font.getGlyphName(glyphID)] = value
1071*e1fe3e4aSElliott Hughes        return mapping
1072*e1fe3e4aSElliott Hughes
1073*e1fe3e4aSElliott Hughes    def readFormat8(self, reader, font):
1074*e1fe3e4aSElliott Hughes        first = reader.readUShort()
1075*e1fe3e4aSElliott Hughes        count = reader.readUShort()
1076*e1fe3e4aSElliott Hughes        data = self.converter.readArray(reader, font, tableDict=None, count=count)
1077*e1fe3e4aSElliott Hughes        return {font.getGlyphName(first + k): value for (k, value) in enumerate(data)}
1078*e1fe3e4aSElliott Hughes
1079*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1080*e1fe3e4aSElliott Hughes        value = {}
1081*e1fe3e4aSElliott Hughes        for element in content:
1082*e1fe3e4aSElliott Hughes            if isinstance(element, tuple):
1083*e1fe3e4aSElliott Hughes                name, a, eltContent = element
1084*e1fe3e4aSElliott Hughes                if name == "Lookup":
1085*e1fe3e4aSElliott Hughes                    value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font)
1086*e1fe3e4aSElliott Hughes        return value
1087*e1fe3e4aSElliott Hughes
1088*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1089*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, attrs)
1090*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1091*e1fe3e4aSElliott Hughes        for glyph, value in sorted(value.items()):
1092*e1fe3e4aSElliott Hughes            self.converter.xmlWrite(
1093*e1fe3e4aSElliott Hughes                xmlWriter, font, value=value, name="Lookup", attrs=[("glyph", glyph)]
1094*e1fe3e4aSElliott Hughes            )
1095*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
1096*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1097*e1fe3e4aSElliott Hughes
1098*e1fe3e4aSElliott Hughes
1099*e1fe3e4aSElliott Hughes# The AAT 'ankr' table has an unusual structure: An offset to an AATLookup
1100*e1fe3e4aSElliott Hughes# followed by an offset to a glyph data table. Other than usual, the
1101*e1fe3e4aSElliott Hughes# offsets in the AATLookup are not relative to the beginning of
1102*e1fe3e4aSElliott Hughes# the beginning of the 'ankr' table, but relative to the glyph data table.
1103*e1fe3e4aSElliott Hughes# So, to find the anchor data for a glyph, one needs to add the offset
1104*e1fe3e4aSElliott Hughes# to the data table to the offset found in the AATLookup, and then use
1105*e1fe3e4aSElliott Hughes# the sum of these two offsets to find the actual data.
1106*e1fe3e4aSElliott Hughesclass AATLookupWithDataOffset(BaseConverter):
1107*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1108*e1fe3e4aSElliott Hughes        lookupOffset = reader.readULong()
1109*e1fe3e4aSElliott Hughes        dataOffset = reader.readULong()
1110*e1fe3e4aSElliott Hughes        lookupReader = reader.getSubReader(lookupOffset)
1111*e1fe3e4aSElliott Hughes        lookup = AATLookup("DataOffsets", None, None, UShort)
1112*e1fe3e4aSElliott Hughes        offsets = lookup.read(lookupReader, font, tableDict)
1113*e1fe3e4aSElliott Hughes        result = {}
1114*e1fe3e4aSElliott Hughes        for glyph, offset in offsets.items():
1115*e1fe3e4aSElliott Hughes            dataReader = reader.getSubReader(offset + dataOffset)
1116*e1fe3e4aSElliott Hughes            item = self.tableClass()
1117*e1fe3e4aSElliott Hughes            item.decompile(dataReader, font)
1118*e1fe3e4aSElliott Hughes            result[glyph] = item
1119*e1fe3e4aSElliott Hughes        return result
1120*e1fe3e4aSElliott Hughes
1121*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1122*e1fe3e4aSElliott Hughes        # We do not work with OTTableWriter sub-writers because
1123*e1fe3e4aSElliott Hughes        # the offsets in our AATLookup are relative to our data
1124*e1fe3e4aSElliott Hughes        # table, for which we need to provide an offset value itself.
1125*e1fe3e4aSElliott Hughes        # It might have been possible to somehow make a kludge for
1126*e1fe3e4aSElliott Hughes        # performing this indirect offset computation directly inside
1127*e1fe3e4aSElliott Hughes        # OTTableWriter. But this would have made the internal logic
1128*e1fe3e4aSElliott Hughes        # of OTTableWriter even more complex than it already is,
1129*e1fe3e4aSElliott Hughes        # so we decided to roll our own offset computation for the
1130*e1fe3e4aSElliott Hughes        # contents of the AATLookup and associated data table.
1131*e1fe3e4aSElliott Hughes        offsetByGlyph, offsetByData, dataLen = {}, {}, 0
1132*e1fe3e4aSElliott Hughes        compiledData = []
1133*e1fe3e4aSElliott Hughes        for glyph in sorted(value, key=font.getGlyphID):
1134*e1fe3e4aSElliott Hughes            subWriter = OTTableWriter()
1135*e1fe3e4aSElliott Hughes            value[glyph].compile(subWriter, font)
1136*e1fe3e4aSElliott Hughes            data = subWriter.getAllData()
1137*e1fe3e4aSElliott Hughes            offset = offsetByData.get(data, None)
1138*e1fe3e4aSElliott Hughes            if offset == None:
1139*e1fe3e4aSElliott Hughes                offset = dataLen
1140*e1fe3e4aSElliott Hughes                dataLen = dataLen + len(data)
1141*e1fe3e4aSElliott Hughes                offsetByData[data] = offset
1142*e1fe3e4aSElliott Hughes                compiledData.append(data)
1143*e1fe3e4aSElliott Hughes            offsetByGlyph[glyph] = offset
1144*e1fe3e4aSElliott Hughes        # For calculating the offsets to our AATLookup and data table,
1145*e1fe3e4aSElliott Hughes        # we can use the regular OTTableWriter infrastructure.
1146*e1fe3e4aSElliott Hughes        lookupWriter = writer.getSubWriter()
1147*e1fe3e4aSElliott Hughes        lookup = AATLookup("DataOffsets", None, None, UShort)
1148*e1fe3e4aSElliott Hughes        lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
1149*e1fe3e4aSElliott Hughes
1150*e1fe3e4aSElliott Hughes        dataWriter = writer.getSubWriter()
1151*e1fe3e4aSElliott Hughes        writer.writeSubTable(lookupWriter, offsetSize=4)
1152*e1fe3e4aSElliott Hughes        writer.writeSubTable(dataWriter, offsetSize=4)
1153*e1fe3e4aSElliott Hughes        for d in compiledData:
1154*e1fe3e4aSElliott Hughes            dataWriter.writeData(d)
1155*e1fe3e4aSElliott Hughes
1156*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1157*e1fe3e4aSElliott Hughes        lookup = AATLookup("DataOffsets", None, None, self.tableClass)
1158*e1fe3e4aSElliott Hughes        return lookup.xmlRead(attrs, content, font)
1159*e1fe3e4aSElliott Hughes
1160*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1161*e1fe3e4aSElliott Hughes        lookup = AATLookup("DataOffsets", None, None, self.tableClass)
1162*e1fe3e4aSElliott Hughes        lookup.xmlWrite(xmlWriter, font, value, name, attrs)
1163*e1fe3e4aSElliott Hughes
1164*e1fe3e4aSElliott Hughes
1165*e1fe3e4aSElliott Hughesclass MorxSubtableConverter(BaseConverter):
1166*e1fe3e4aSElliott Hughes    _PROCESSING_ORDERS = {
1167*e1fe3e4aSElliott Hughes        # bits 30 and 28 of morx.CoverageFlags; see morx spec
1168*e1fe3e4aSElliott Hughes        (False, False): "LayoutOrder",
1169*e1fe3e4aSElliott Hughes        (True, False): "ReversedLayoutOrder",
1170*e1fe3e4aSElliott Hughes        (False, True): "LogicalOrder",
1171*e1fe3e4aSElliott Hughes        (True, True): "ReversedLogicalOrder",
1172*e1fe3e4aSElliott Hughes    }
1173*e1fe3e4aSElliott Hughes
1174*e1fe3e4aSElliott Hughes    _PROCESSING_ORDERS_REVERSED = {val: key for key, val in _PROCESSING_ORDERS.items()}
1175*e1fe3e4aSElliott Hughes
1176*e1fe3e4aSElliott Hughes    def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
1177*e1fe3e4aSElliott Hughes        BaseConverter.__init__(
1178*e1fe3e4aSElliott Hughes            self, name, repeat, aux, tableClass, description=description
1179*e1fe3e4aSElliott Hughes        )
1180*e1fe3e4aSElliott Hughes
1181*e1fe3e4aSElliott Hughes    def _setTextDirectionFromCoverageFlags(self, flags, subtable):
1182*e1fe3e4aSElliott Hughes        if (flags & 0x20) != 0:
1183*e1fe3e4aSElliott Hughes            subtable.TextDirection = "Any"
1184*e1fe3e4aSElliott Hughes        elif (flags & 0x80) != 0:
1185*e1fe3e4aSElliott Hughes            subtable.TextDirection = "Vertical"
1186*e1fe3e4aSElliott Hughes        else:
1187*e1fe3e4aSElliott Hughes            subtable.TextDirection = "Horizontal"
1188*e1fe3e4aSElliott Hughes
1189*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1190*e1fe3e4aSElliott Hughes        pos = reader.pos
1191*e1fe3e4aSElliott Hughes        m = MorxSubtable()
1192*e1fe3e4aSElliott Hughes        m.StructLength = reader.readULong()
1193*e1fe3e4aSElliott Hughes        flags = reader.readUInt8()
1194*e1fe3e4aSElliott Hughes        orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0)
1195*e1fe3e4aSElliott Hughes        m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
1196*e1fe3e4aSElliott Hughes        self._setTextDirectionFromCoverageFlags(flags, m)
1197*e1fe3e4aSElliott Hughes        m.Reserved = reader.readUShort()
1198*e1fe3e4aSElliott Hughes        m.Reserved |= (flags & 0xF) << 16
1199*e1fe3e4aSElliott Hughes        m.MorphType = reader.readUInt8()
1200*e1fe3e4aSElliott Hughes        m.SubFeatureFlags = reader.readULong()
1201*e1fe3e4aSElliott Hughes        tableClass = lookupTypes["morx"].get(m.MorphType)
1202*e1fe3e4aSElliott Hughes        if tableClass is None:
1203*e1fe3e4aSElliott Hughes            assert False, "unsupported 'morx' lookup type %s" % m.MorphType
1204*e1fe3e4aSElliott Hughes        # To decode AAT ligatures, we need to know the subtable size.
1205*e1fe3e4aSElliott Hughes        # The easiest way to pass this along is to create a new reader
1206*e1fe3e4aSElliott Hughes        # that works on just the subtable as its data.
1207*e1fe3e4aSElliott Hughes        headerLength = reader.pos - pos
1208*e1fe3e4aSElliott Hughes        data = reader.data[reader.pos : reader.pos + m.StructLength - headerLength]
1209*e1fe3e4aSElliott Hughes        assert len(data) == m.StructLength - headerLength
1210*e1fe3e4aSElliott Hughes        subReader = OTTableReader(data=data, tableTag=reader.tableTag)
1211*e1fe3e4aSElliott Hughes        m.SubStruct = tableClass()
1212*e1fe3e4aSElliott Hughes        m.SubStruct.decompile(subReader, font)
1213*e1fe3e4aSElliott Hughes        reader.seek(pos + m.StructLength)
1214*e1fe3e4aSElliott Hughes        return m
1215*e1fe3e4aSElliott Hughes
1216*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1217*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, attrs)
1218*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1219*e1fe3e4aSElliott Hughes        xmlWriter.comment("StructLength=%d" % value.StructLength)
1220*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1221*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("TextDirection", value=value.TextDirection)
1222*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1223*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("ProcessingOrder", value=value.ProcessingOrder)
1224*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1225*e1fe3e4aSElliott Hughes        if value.Reserved != 0:
1226*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Reserved", value="0x%04x" % value.Reserved)
1227*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1228*e1fe3e4aSElliott Hughes        xmlWriter.comment("MorphType=%d" % value.MorphType)
1229*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1230*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("SubFeatureFlags", value="0x%08x" % value.SubFeatureFlags)
1231*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1232*e1fe3e4aSElliott Hughes        value.SubStruct.toXML(xmlWriter, font)
1233*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
1234*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1235*e1fe3e4aSElliott Hughes
1236*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1237*e1fe3e4aSElliott Hughes        m = MorxSubtable()
1238*e1fe3e4aSElliott Hughes        covFlags = 0
1239*e1fe3e4aSElliott Hughes        m.Reserved = 0
1240*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in filter(istuple, content):
1241*e1fe3e4aSElliott Hughes            if eltName == "CoverageFlags":
1242*e1fe3e4aSElliott Hughes                # Only in XML from old versions of fonttools.
1243*e1fe3e4aSElliott Hughes                covFlags = safeEval(eltAttrs["value"])
1244*e1fe3e4aSElliott Hughes                orderKey = ((covFlags & 0x40) != 0, (covFlags & 0x10) != 0)
1245*e1fe3e4aSElliott Hughes                m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
1246*e1fe3e4aSElliott Hughes                self._setTextDirectionFromCoverageFlags(covFlags, m)
1247*e1fe3e4aSElliott Hughes            elif eltName == "ProcessingOrder":
1248*e1fe3e4aSElliott Hughes                m.ProcessingOrder = eltAttrs["value"]
1249*e1fe3e4aSElliott Hughes                assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, (
1250*e1fe3e4aSElliott Hughes                    "unknown ProcessingOrder: %s" % m.ProcessingOrder
1251*e1fe3e4aSElliott Hughes                )
1252*e1fe3e4aSElliott Hughes            elif eltName == "TextDirection":
1253*e1fe3e4aSElliott Hughes                m.TextDirection = eltAttrs["value"]
1254*e1fe3e4aSElliott Hughes                assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, (
1255*e1fe3e4aSElliott Hughes                    "unknown TextDirection %s" % m.TextDirection
1256*e1fe3e4aSElliott Hughes                )
1257*e1fe3e4aSElliott Hughes            elif eltName == "Reserved":
1258*e1fe3e4aSElliott Hughes                m.Reserved = safeEval(eltAttrs["value"])
1259*e1fe3e4aSElliott Hughes            elif eltName == "SubFeatureFlags":
1260*e1fe3e4aSElliott Hughes                m.SubFeatureFlags = safeEval(eltAttrs["value"])
1261*e1fe3e4aSElliott Hughes            elif eltName.endswith("Morph"):
1262*e1fe3e4aSElliott Hughes                m.fromXML(eltName, eltAttrs, eltContent, font)
1263*e1fe3e4aSElliott Hughes            else:
1264*e1fe3e4aSElliott Hughes                assert False, eltName
1265*e1fe3e4aSElliott Hughes        m.Reserved = (covFlags & 0xF) << 16 | m.Reserved
1266*e1fe3e4aSElliott Hughes        return m
1267*e1fe3e4aSElliott Hughes
1268*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1269*e1fe3e4aSElliott Hughes        covFlags = (value.Reserved & 0x000F0000) >> 16
1270*e1fe3e4aSElliott Hughes        reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[
1271*e1fe3e4aSElliott Hughes            value.ProcessingOrder
1272*e1fe3e4aSElliott Hughes        ]
1273*e1fe3e4aSElliott Hughes        covFlags |= 0x80 if value.TextDirection == "Vertical" else 0
1274*e1fe3e4aSElliott Hughes        covFlags |= 0x40 if reverseOrder else 0
1275*e1fe3e4aSElliott Hughes        covFlags |= 0x20 if value.TextDirection == "Any" else 0
1276*e1fe3e4aSElliott Hughes        covFlags |= 0x10 if logicalOrder else 0
1277*e1fe3e4aSElliott Hughes        value.CoverageFlags = covFlags
1278*e1fe3e4aSElliott Hughes        lengthIndex = len(writer.items)
1279*e1fe3e4aSElliott Hughes        before = writer.getDataLength()
1280*e1fe3e4aSElliott Hughes        value.StructLength = 0xDEADBEEF
1281*e1fe3e4aSElliott Hughes        # The high nibble of value.Reserved is actuallly encoded
1282*e1fe3e4aSElliott Hughes        # into coverageFlags, so we need to clear it here.
1283*e1fe3e4aSElliott Hughes        origReserved = value.Reserved  # including high nibble
1284*e1fe3e4aSElliott Hughes        value.Reserved = value.Reserved & 0xFFFF  # without high nibble
1285*e1fe3e4aSElliott Hughes        value.compile(writer, font)
1286*e1fe3e4aSElliott Hughes        value.Reserved = origReserved  # restore original value
1287*e1fe3e4aSElliott Hughes        assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
1288*e1fe3e4aSElliott Hughes        length = writer.getDataLength() - before
1289*e1fe3e4aSElliott Hughes        writer.items[lengthIndex] = struct.pack(">L", length)
1290*e1fe3e4aSElliott Hughes
1291*e1fe3e4aSElliott Hughes
1292*e1fe3e4aSElliott Hughes# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
1293*e1fe3e4aSElliott Hughes# TODO: Untangle the implementation of the various lookup-specific formats.
1294*e1fe3e4aSElliott Hughesclass STXHeader(BaseConverter):
1295*e1fe3e4aSElliott Hughes    def __init__(self, name, repeat, aux, tableClass, *, description=""):
1296*e1fe3e4aSElliott Hughes        BaseConverter.__init__(
1297*e1fe3e4aSElliott Hughes            self, name, repeat, aux, tableClass, description=description
1298*e1fe3e4aSElliott Hughes        )
1299*e1fe3e4aSElliott Hughes        assert issubclass(self.tableClass, AATAction)
1300*e1fe3e4aSElliott Hughes        self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
1301*e1fe3e4aSElliott Hughes        if issubclass(self.tableClass, ContextualMorphAction):
1302*e1fe3e4aSElliott Hughes            self.perGlyphLookup = AATLookup("PerGlyphLookup", None, None, GlyphID)
1303*e1fe3e4aSElliott Hughes        else:
1304*e1fe3e4aSElliott Hughes            self.perGlyphLookup = None
1305*e1fe3e4aSElliott Hughes
1306*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1307*e1fe3e4aSElliott Hughes        table = AATStateTable()
1308*e1fe3e4aSElliott Hughes        pos = reader.pos
1309*e1fe3e4aSElliott Hughes        classTableReader = reader.getSubReader(0)
1310*e1fe3e4aSElliott Hughes        stateArrayReader = reader.getSubReader(0)
1311*e1fe3e4aSElliott Hughes        entryTableReader = reader.getSubReader(0)
1312*e1fe3e4aSElliott Hughes        actionReader = None
1313*e1fe3e4aSElliott Hughes        ligaturesReader = None
1314*e1fe3e4aSElliott Hughes        table.GlyphClassCount = reader.readULong()
1315*e1fe3e4aSElliott Hughes        classTableReader.seek(pos + reader.readULong())
1316*e1fe3e4aSElliott Hughes        stateArrayReader.seek(pos + reader.readULong())
1317*e1fe3e4aSElliott Hughes        entryTableReader.seek(pos + reader.readULong())
1318*e1fe3e4aSElliott Hughes        if self.perGlyphLookup is not None:
1319*e1fe3e4aSElliott Hughes            perGlyphTableReader = reader.getSubReader(0)
1320*e1fe3e4aSElliott Hughes            perGlyphTableReader.seek(pos + reader.readULong())
1321*e1fe3e4aSElliott Hughes        if issubclass(self.tableClass, LigatureMorphAction):
1322*e1fe3e4aSElliott Hughes            actionReader = reader.getSubReader(0)
1323*e1fe3e4aSElliott Hughes            actionReader.seek(pos + reader.readULong())
1324*e1fe3e4aSElliott Hughes            ligComponentReader = reader.getSubReader(0)
1325*e1fe3e4aSElliott Hughes            ligComponentReader.seek(pos + reader.readULong())
1326*e1fe3e4aSElliott Hughes            ligaturesReader = reader.getSubReader(0)
1327*e1fe3e4aSElliott Hughes            ligaturesReader.seek(pos + reader.readULong())
1328*e1fe3e4aSElliott Hughes            numLigComponents = (ligaturesReader.pos - ligComponentReader.pos) // 2
1329*e1fe3e4aSElliott Hughes            assert numLigComponents >= 0
1330*e1fe3e4aSElliott Hughes            table.LigComponents = ligComponentReader.readUShortArray(numLigComponents)
1331*e1fe3e4aSElliott Hughes            table.Ligatures = self._readLigatures(ligaturesReader, font)
1332*e1fe3e4aSElliott Hughes        elif issubclass(self.tableClass, InsertionMorphAction):
1333*e1fe3e4aSElliott Hughes            actionReader = reader.getSubReader(0)
1334*e1fe3e4aSElliott Hughes            actionReader.seek(pos + reader.readULong())
1335*e1fe3e4aSElliott Hughes        table.GlyphClasses = self.classLookup.read(classTableReader, font, tableDict)
1336*e1fe3e4aSElliott Hughes        numStates = int(
1337*e1fe3e4aSElliott Hughes            (entryTableReader.pos - stateArrayReader.pos) / (table.GlyphClassCount * 2)
1338*e1fe3e4aSElliott Hughes        )
1339*e1fe3e4aSElliott Hughes        for stateIndex in range(numStates):
1340*e1fe3e4aSElliott Hughes            state = AATState()
1341*e1fe3e4aSElliott Hughes            table.States.append(state)
1342*e1fe3e4aSElliott Hughes            for glyphClass in range(table.GlyphClassCount):
1343*e1fe3e4aSElliott Hughes                entryIndex = stateArrayReader.readUShort()
1344*e1fe3e4aSElliott Hughes                state.Transitions[glyphClass] = self._readTransition(
1345*e1fe3e4aSElliott Hughes                    entryTableReader, entryIndex, font, actionReader
1346*e1fe3e4aSElliott Hughes                )
1347*e1fe3e4aSElliott Hughes        if self.perGlyphLookup is not None:
1348*e1fe3e4aSElliott Hughes            table.PerGlyphLookups = self._readPerGlyphLookups(
1349*e1fe3e4aSElliott Hughes                table, perGlyphTableReader, font
1350*e1fe3e4aSElliott Hughes            )
1351*e1fe3e4aSElliott Hughes        return table
1352*e1fe3e4aSElliott Hughes
1353*e1fe3e4aSElliott Hughes    def _readTransition(self, reader, entryIndex, font, actionReader):
1354*e1fe3e4aSElliott Hughes        transition = self.tableClass()
1355*e1fe3e4aSElliott Hughes        entryReader = reader.getSubReader(
1356*e1fe3e4aSElliott Hughes            reader.pos + entryIndex * transition.staticSize
1357*e1fe3e4aSElliott Hughes        )
1358*e1fe3e4aSElliott Hughes        transition.decompile(entryReader, font, actionReader)
1359*e1fe3e4aSElliott Hughes        return transition
1360*e1fe3e4aSElliott Hughes
1361*e1fe3e4aSElliott Hughes    def _readLigatures(self, reader, font):
1362*e1fe3e4aSElliott Hughes        limit = len(reader.data)
1363*e1fe3e4aSElliott Hughes        numLigatureGlyphs = (limit - reader.pos) // 2
1364*e1fe3e4aSElliott Hughes        return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs))
1365*e1fe3e4aSElliott Hughes
1366*e1fe3e4aSElliott Hughes    def _countPerGlyphLookups(self, table):
1367*e1fe3e4aSElliott Hughes        # Somewhat annoyingly, the morx table does not encode
1368*e1fe3e4aSElliott Hughes        # the size of the per-glyph table. So we need to find
1369*e1fe3e4aSElliott Hughes        # the maximum value that MorphActions use as index
1370*e1fe3e4aSElliott Hughes        # into this table.
1371*e1fe3e4aSElliott Hughes        numLookups = 0
1372*e1fe3e4aSElliott Hughes        for state in table.States:
1373*e1fe3e4aSElliott Hughes            for t in state.Transitions.values():
1374*e1fe3e4aSElliott Hughes                if isinstance(t, ContextualMorphAction):
1375*e1fe3e4aSElliott Hughes                    if t.MarkIndex != 0xFFFF:
1376*e1fe3e4aSElliott Hughes                        numLookups = max(numLookups, t.MarkIndex + 1)
1377*e1fe3e4aSElliott Hughes                    if t.CurrentIndex != 0xFFFF:
1378*e1fe3e4aSElliott Hughes                        numLookups = max(numLookups, t.CurrentIndex + 1)
1379*e1fe3e4aSElliott Hughes        return numLookups
1380*e1fe3e4aSElliott Hughes
1381*e1fe3e4aSElliott Hughes    def _readPerGlyphLookups(self, table, reader, font):
1382*e1fe3e4aSElliott Hughes        pos = reader.pos
1383*e1fe3e4aSElliott Hughes        lookups = []
1384*e1fe3e4aSElliott Hughes        for _ in range(self._countPerGlyphLookups(table)):
1385*e1fe3e4aSElliott Hughes            lookupReader = reader.getSubReader(0)
1386*e1fe3e4aSElliott Hughes            lookupReader.seek(pos + reader.readULong())
1387*e1fe3e4aSElliott Hughes            lookups.append(self.perGlyphLookup.read(lookupReader, font, {}))
1388*e1fe3e4aSElliott Hughes        return lookups
1389*e1fe3e4aSElliott Hughes
1390*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1391*e1fe3e4aSElliott Hughes        glyphClassWriter = OTTableWriter()
1392*e1fe3e4aSElliott Hughes        self.classLookup.write(
1393*e1fe3e4aSElliott Hughes            glyphClassWriter, font, tableDict, value.GlyphClasses, repeatIndex=None
1394*e1fe3e4aSElliott Hughes        )
1395*e1fe3e4aSElliott Hughes        glyphClassData = pad(glyphClassWriter.getAllData(), 2)
1396*e1fe3e4aSElliott Hughes        glyphClassCount = max(value.GlyphClasses.values()) + 1
1397*e1fe3e4aSElliott Hughes        glyphClassTableOffset = 16  # size of STXHeader
1398*e1fe3e4aSElliott Hughes        if self.perGlyphLookup is not None:
1399*e1fe3e4aSElliott Hughes            glyphClassTableOffset += 4
1400*e1fe3e4aSElliott Hughes
1401*e1fe3e4aSElliott Hughes        glyphClassTableOffset += self.tableClass.actionHeaderSize
1402*e1fe3e4aSElliott Hughes        actionData, actionIndex = self.tableClass.compileActions(font, value.States)
1403*e1fe3e4aSElliott Hughes        stateArrayData, entryTableData = self._compileStates(
1404*e1fe3e4aSElliott Hughes            font, value.States, glyphClassCount, actionIndex
1405*e1fe3e4aSElliott Hughes        )
1406*e1fe3e4aSElliott Hughes        stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
1407*e1fe3e4aSElliott Hughes        entryTableOffset = stateArrayOffset + len(stateArrayData)
1408*e1fe3e4aSElliott Hughes        perGlyphOffset = entryTableOffset + len(entryTableData)
1409*e1fe3e4aSElliott Hughes        perGlyphData = pad(self._compilePerGlyphLookups(value, font), 4)
1410*e1fe3e4aSElliott Hughes        if actionData is not None:
1411*e1fe3e4aSElliott Hughes            actionOffset = entryTableOffset + len(entryTableData)
1412*e1fe3e4aSElliott Hughes        else:
1413*e1fe3e4aSElliott Hughes            actionOffset = None
1414*e1fe3e4aSElliott Hughes
1415*e1fe3e4aSElliott Hughes        ligaturesOffset, ligComponentsOffset = None, None
1416*e1fe3e4aSElliott Hughes        ligComponentsData = self._compileLigComponents(value, font)
1417*e1fe3e4aSElliott Hughes        ligaturesData = self._compileLigatures(value, font)
1418*e1fe3e4aSElliott Hughes        if ligComponentsData is not None:
1419*e1fe3e4aSElliott Hughes            assert len(perGlyphData) == 0
1420*e1fe3e4aSElliott Hughes            ligComponentsOffset = actionOffset + len(actionData)
1421*e1fe3e4aSElliott Hughes            ligaturesOffset = ligComponentsOffset + len(ligComponentsData)
1422*e1fe3e4aSElliott Hughes
1423*e1fe3e4aSElliott Hughes        writer.writeULong(glyphClassCount)
1424*e1fe3e4aSElliott Hughes        writer.writeULong(glyphClassTableOffset)
1425*e1fe3e4aSElliott Hughes        writer.writeULong(stateArrayOffset)
1426*e1fe3e4aSElliott Hughes        writer.writeULong(entryTableOffset)
1427*e1fe3e4aSElliott Hughes        if self.perGlyphLookup is not None:
1428*e1fe3e4aSElliott Hughes            writer.writeULong(perGlyphOffset)
1429*e1fe3e4aSElliott Hughes        if actionOffset is not None:
1430*e1fe3e4aSElliott Hughes            writer.writeULong(actionOffset)
1431*e1fe3e4aSElliott Hughes        if ligComponentsOffset is not None:
1432*e1fe3e4aSElliott Hughes            writer.writeULong(ligComponentsOffset)
1433*e1fe3e4aSElliott Hughes            writer.writeULong(ligaturesOffset)
1434*e1fe3e4aSElliott Hughes        writer.writeData(glyphClassData)
1435*e1fe3e4aSElliott Hughes        writer.writeData(stateArrayData)
1436*e1fe3e4aSElliott Hughes        writer.writeData(entryTableData)
1437*e1fe3e4aSElliott Hughes        writer.writeData(perGlyphData)
1438*e1fe3e4aSElliott Hughes        if actionData is not None:
1439*e1fe3e4aSElliott Hughes            writer.writeData(actionData)
1440*e1fe3e4aSElliott Hughes        if ligComponentsData is not None:
1441*e1fe3e4aSElliott Hughes            writer.writeData(ligComponentsData)
1442*e1fe3e4aSElliott Hughes        if ligaturesData is not None:
1443*e1fe3e4aSElliott Hughes            writer.writeData(ligaturesData)
1444*e1fe3e4aSElliott Hughes
1445*e1fe3e4aSElliott Hughes    def _compileStates(self, font, states, glyphClassCount, actionIndex):
1446*e1fe3e4aSElliott Hughes        stateArrayWriter = OTTableWriter()
1447*e1fe3e4aSElliott Hughes        entries, entryIDs = [], {}
1448*e1fe3e4aSElliott Hughes        for state in states:
1449*e1fe3e4aSElliott Hughes            for glyphClass in range(glyphClassCount):
1450*e1fe3e4aSElliott Hughes                transition = state.Transitions[glyphClass]
1451*e1fe3e4aSElliott Hughes                entryWriter = OTTableWriter()
1452*e1fe3e4aSElliott Hughes                transition.compile(entryWriter, font, actionIndex)
1453*e1fe3e4aSElliott Hughes                entryData = entryWriter.getAllData()
1454*e1fe3e4aSElliott Hughes                assert (
1455*e1fe3e4aSElliott Hughes                    len(entryData) == transition.staticSize
1456*e1fe3e4aSElliott Hughes                ), "%s has staticSize %d, " "but actually wrote %d bytes" % (
1457*e1fe3e4aSElliott Hughes                    repr(transition),
1458*e1fe3e4aSElliott Hughes                    transition.staticSize,
1459*e1fe3e4aSElliott Hughes                    len(entryData),
1460*e1fe3e4aSElliott Hughes                )
1461*e1fe3e4aSElliott Hughes                entryIndex = entryIDs.get(entryData)
1462*e1fe3e4aSElliott Hughes                if entryIndex is None:
1463*e1fe3e4aSElliott Hughes                    entryIndex = len(entries)
1464*e1fe3e4aSElliott Hughes                    entryIDs[entryData] = entryIndex
1465*e1fe3e4aSElliott Hughes                    entries.append(entryData)
1466*e1fe3e4aSElliott Hughes                stateArrayWriter.writeUShort(entryIndex)
1467*e1fe3e4aSElliott Hughes        stateArrayData = pad(stateArrayWriter.getAllData(), 4)
1468*e1fe3e4aSElliott Hughes        entryTableData = pad(bytesjoin(entries), 4)
1469*e1fe3e4aSElliott Hughes        return stateArrayData, entryTableData
1470*e1fe3e4aSElliott Hughes
1471*e1fe3e4aSElliott Hughes    def _compilePerGlyphLookups(self, table, font):
1472*e1fe3e4aSElliott Hughes        if self.perGlyphLookup is None:
1473*e1fe3e4aSElliott Hughes            return b""
1474*e1fe3e4aSElliott Hughes        numLookups = self._countPerGlyphLookups(table)
1475*e1fe3e4aSElliott Hughes        assert len(table.PerGlyphLookups) == numLookups, (
1476*e1fe3e4aSElliott Hughes            "len(AATStateTable.PerGlyphLookups) is %d, "
1477*e1fe3e4aSElliott Hughes            "but the actions inside the table refer to %d"
1478*e1fe3e4aSElliott Hughes            % (len(table.PerGlyphLookups), numLookups)
1479*e1fe3e4aSElliott Hughes        )
1480*e1fe3e4aSElliott Hughes        writer = OTTableWriter()
1481*e1fe3e4aSElliott Hughes        for lookup in table.PerGlyphLookups:
1482*e1fe3e4aSElliott Hughes            lookupWriter = writer.getSubWriter()
1483*e1fe3e4aSElliott Hughes            self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None)
1484*e1fe3e4aSElliott Hughes            writer.writeSubTable(lookupWriter, offsetSize=4)
1485*e1fe3e4aSElliott Hughes        return writer.getAllData()
1486*e1fe3e4aSElliott Hughes
1487*e1fe3e4aSElliott Hughes    def _compileLigComponents(self, table, font):
1488*e1fe3e4aSElliott Hughes        if not hasattr(table, "LigComponents"):
1489*e1fe3e4aSElliott Hughes            return None
1490*e1fe3e4aSElliott Hughes        writer = OTTableWriter()
1491*e1fe3e4aSElliott Hughes        for component in table.LigComponents:
1492*e1fe3e4aSElliott Hughes            writer.writeUShort(component)
1493*e1fe3e4aSElliott Hughes        return writer.getAllData()
1494*e1fe3e4aSElliott Hughes
1495*e1fe3e4aSElliott Hughes    def _compileLigatures(self, table, font):
1496*e1fe3e4aSElliott Hughes        if not hasattr(table, "Ligatures"):
1497*e1fe3e4aSElliott Hughes            return None
1498*e1fe3e4aSElliott Hughes        writer = OTTableWriter()
1499*e1fe3e4aSElliott Hughes        for glyphName in table.Ligatures:
1500*e1fe3e4aSElliott Hughes            writer.writeUShort(font.getGlyphID(glyphName))
1501*e1fe3e4aSElliott Hughes        return writer.getAllData()
1502*e1fe3e4aSElliott Hughes
1503*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1504*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, attrs)
1505*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1506*e1fe3e4aSElliott Hughes        xmlWriter.comment("GlyphClassCount=%s" % value.GlyphClassCount)
1507*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1508*e1fe3e4aSElliott Hughes        for g, klass in sorted(value.GlyphClasses.items()):
1509*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
1510*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1511*e1fe3e4aSElliott Hughes        for stateIndex, state in enumerate(value.States):
1512*e1fe3e4aSElliott Hughes            xmlWriter.begintag("State", index=stateIndex)
1513*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1514*e1fe3e4aSElliott Hughes            for glyphClass, trans in sorted(state.Transitions.items()):
1515*e1fe3e4aSElliott Hughes                trans.toXML(
1516*e1fe3e4aSElliott Hughes                    xmlWriter,
1517*e1fe3e4aSElliott Hughes                    font=font,
1518*e1fe3e4aSElliott Hughes                    attrs={"onGlyphClass": glyphClass},
1519*e1fe3e4aSElliott Hughes                    name="Transition",
1520*e1fe3e4aSElliott Hughes                )
1521*e1fe3e4aSElliott Hughes            xmlWriter.endtag("State")
1522*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1523*e1fe3e4aSElliott Hughes        for i, lookup in enumerate(value.PerGlyphLookups):
1524*e1fe3e4aSElliott Hughes            xmlWriter.begintag("PerGlyphLookup", index=i)
1525*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1526*e1fe3e4aSElliott Hughes            for glyph, val in sorted(lookup.items()):
1527*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("Lookup", glyph=glyph, value=val)
1528*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1529*e1fe3e4aSElliott Hughes            xmlWriter.endtag("PerGlyphLookup")
1530*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1531*e1fe3e4aSElliott Hughes        if hasattr(value, "LigComponents"):
1532*e1fe3e4aSElliott Hughes            xmlWriter.begintag("LigComponents")
1533*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1534*e1fe3e4aSElliott Hughes            for i, val in enumerate(getattr(value, "LigComponents")):
1535*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("LigComponent", index=i, value=val)
1536*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1537*e1fe3e4aSElliott Hughes            xmlWriter.endtag("LigComponents")
1538*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1539*e1fe3e4aSElliott Hughes        self._xmlWriteLigatures(xmlWriter, font, value, name, attrs)
1540*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
1541*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1542*e1fe3e4aSElliott Hughes
1543*e1fe3e4aSElliott Hughes    def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs):
1544*e1fe3e4aSElliott Hughes        if not hasattr(value, "Ligatures"):
1545*e1fe3e4aSElliott Hughes            return
1546*e1fe3e4aSElliott Hughes        xmlWriter.begintag("Ligatures")
1547*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1548*e1fe3e4aSElliott Hughes        for i, g in enumerate(getattr(value, "Ligatures")):
1549*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Ligature", index=i, glyph=g)
1550*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1551*e1fe3e4aSElliott Hughes        xmlWriter.endtag("Ligatures")
1552*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1553*e1fe3e4aSElliott Hughes
1554*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1555*e1fe3e4aSElliott Hughes        table = AATStateTable()
1556*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in filter(istuple, content):
1557*e1fe3e4aSElliott Hughes            if eltName == "GlyphClass":
1558*e1fe3e4aSElliott Hughes                glyph = eltAttrs["glyph"]
1559*e1fe3e4aSElliott Hughes                value = eltAttrs["value"]
1560*e1fe3e4aSElliott Hughes                table.GlyphClasses[glyph] = safeEval(value)
1561*e1fe3e4aSElliott Hughes            elif eltName == "State":
1562*e1fe3e4aSElliott Hughes                state = self._xmlReadState(eltAttrs, eltContent, font)
1563*e1fe3e4aSElliott Hughes                table.States.append(state)
1564*e1fe3e4aSElliott Hughes            elif eltName == "PerGlyphLookup":
1565*e1fe3e4aSElliott Hughes                lookup = self.perGlyphLookup.xmlRead(eltAttrs, eltContent, font)
1566*e1fe3e4aSElliott Hughes                table.PerGlyphLookups.append(lookup)
1567*e1fe3e4aSElliott Hughes            elif eltName == "LigComponents":
1568*e1fe3e4aSElliott Hughes                table.LigComponents = self._xmlReadLigComponents(
1569*e1fe3e4aSElliott Hughes                    eltAttrs, eltContent, font
1570*e1fe3e4aSElliott Hughes                )
1571*e1fe3e4aSElliott Hughes            elif eltName == "Ligatures":
1572*e1fe3e4aSElliott Hughes                table.Ligatures = self._xmlReadLigatures(eltAttrs, eltContent, font)
1573*e1fe3e4aSElliott Hughes        table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
1574*e1fe3e4aSElliott Hughes        return table
1575*e1fe3e4aSElliott Hughes
1576*e1fe3e4aSElliott Hughes    def _xmlReadState(self, attrs, content, font):
1577*e1fe3e4aSElliott Hughes        state = AATState()
1578*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in filter(istuple, content):
1579*e1fe3e4aSElliott Hughes            if eltName == "Transition":
1580*e1fe3e4aSElliott Hughes                glyphClass = safeEval(eltAttrs["onGlyphClass"])
1581*e1fe3e4aSElliott Hughes                transition = self.tableClass()
1582*e1fe3e4aSElliott Hughes                transition.fromXML(eltName, eltAttrs, eltContent, font)
1583*e1fe3e4aSElliott Hughes                state.Transitions[glyphClass] = transition
1584*e1fe3e4aSElliott Hughes        return state
1585*e1fe3e4aSElliott Hughes
1586*e1fe3e4aSElliott Hughes    def _xmlReadLigComponents(self, attrs, content, font):
1587*e1fe3e4aSElliott Hughes        ligComponents = []
1588*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, _eltContent in filter(istuple, content):
1589*e1fe3e4aSElliott Hughes            if eltName == "LigComponent":
1590*e1fe3e4aSElliott Hughes                ligComponents.append(safeEval(eltAttrs["value"]))
1591*e1fe3e4aSElliott Hughes        return ligComponents
1592*e1fe3e4aSElliott Hughes
1593*e1fe3e4aSElliott Hughes    def _xmlReadLigatures(self, attrs, content, font):
1594*e1fe3e4aSElliott Hughes        ligs = []
1595*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, _eltContent in filter(istuple, content):
1596*e1fe3e4aSElliott Hughes            if eltName == "Ligature":
1597*e1fe3e4aSElliott Hughes                ligs.append(eltAttrs["glyph"])
1598*e1fe3e4aSElliott Hughes        return ligs
1599*e1fe3e4aSElliott Hughes
1600*e1fe3e4aSElliott Hughes
1601*e1fe3e4aSElliott Hughesclass CIDGlyphMap(BaseConverter):
1602*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1603*e1fe3e4aSElliott Hughes        numCIDs = reader.readUShort()
1604*e1fe3e4aSElliott Hughes        result = {}
1605*e1fe3e4aSElliott Hughes        for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)):
1606*e1fe3e4aSElliott Hughes            if glyphID != 0xFFFF:
1607*e1fe3e4aSElliott Hughes                result[cid] = font.getGlyphName(glyphID)
1608*e1fe3e4aSElliott Hughes        return result
1609*e1fe3e4aSElliott Hughes
1610*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1611*e1fe3e4aSElliott Hughes        items = {cid: font.getGlyphID(glyph) for cid, glyph in value.items()}
1612*e1fe3e4aSElliott Hughes        count = max(items) + 1 if items else 0
1613*e1fe3e4aSElliott Hughes        writer.writeUShort(count)
1614*e1fe3e4aSElliott Hughes        for cid in range(count):
1615*e1fe3e4aSElliott Hughes            writer.writeUShort(items.get(cid, 0xFFFF))
1616*e1fe3e4aSElliott Hughes
1617*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1618*e1fe3e4aSElliott Hughes        result = {}
1619*e1fe3e4aSElliott Hughes        for eName, eAttrs, _eContent in filter(istuple, content):
1620*e1fe3e4aSElliott Hughes            if eName == "CID":
1621*e1fe3e4aSElliott Hughes                result[safeEval(eAttrs["cid"])] = eAttrs["glyph"].strip()
1622*e1fe3e4aSElliott Hughes        return result
1623*e1fe3e4aSElliott Hughes
1624*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1625*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, attrs)
1626*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1627*e1fe3e4aSElliott Hughes        for cid, glyph in sorted(value.items()):
1628*e1fe3e4aSElliott Hughes            if glyph is not None and glyph != 0xFFFF:
1629*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("CID", cid=cid, glyph=glyph)
1630*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1631*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
1632*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1633*e1fe3e4aSElliott Hughes
1634*e1fe3e4aSElliott Hughes
1635*e1fe3e4aSElliott Hughesclass GlyphCIDMap(BaseConverter):
1636*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1637*e1fe3e4aSElliott Hughes        glyphOrder = font.getGlyphOrder()
1638*e1fe3e4aSElliott Hughes        count = reader.readUShort()
1639*e1fe3e4aSElliott Hughes        cids = reader.readUShortArray(count)
1640*e1fe3e4aSElliott Hughes        if count > len(glyphOrder):
1641*e1fe3e4aSElliott Hughes            log.warning(
1642*e1fe3e4aSElliott Hughes                "GlyphCIDMap has %d elements, "
1643*e1fe3e4aSElliott Hughes                "but the font has only %d glyphs; "
1644*e1fe3e4aSElliott Hughes                "ignoring the rest" % (count, len(glyphOrder))
1645*e1fe3e4aSElliott Hughes            )
1646*e1fe3e4aSElliott Hughes        result = {}
1647*e1fe3e4aSElliott Hughes        for glyphID in range(min(len(cids), len(glyphOrder))):
1648*e1fe3e4aSElliott Hughes            cid = cids[glyphID]
1649*e1fe3e4aSElliott Hughes            if cid != 0xFFFF:
1650*e1fe3e4aSElliott Hughes                result[glyphOrder[glyphID]] = cid
1651*e1fe3e4aSElliott Hughes        return result
1652*e1fe3e4aSElliott Hughes
1653*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1654*e1fe3e4aSElliott Hughes        items = {
1655*e1fe3e4aSElliott Hughes            font.getGlyphID(g): cid
1656*e1fe3e4aSElliott Hughes            for g, cid in value.items()
1657*e1fe3e4aSElliott Hughes            if cid is not None and cid != 0xFFFF
1658*e1fe3e4aSElliott Hughes        }
1659*e1fe3e4aSElliott Hughes        count = max(items) + 1 if items else 0
1660*e1fe3e4aSElliott Hughes        writer.writeUShort(count)
1661*e1fe3e4aSElliott Hughes        for glyphID in range(count):
1662*e1fe3e4aSElliott Hughes            writer.writeUShort(items.get(glyphID, 0xFFFF))
1663*e1fe3e4aSElliott Hughes
1664*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1665*e1fe3e4aSElliott Hughes        result = {}
1666*e1fe3e4aSElliott Hughes        for eName, eAttrs, _eContent in filter(istuple, content):
1667*e1fe3e4aSElliott Hughes            if eName == "CID":
1668*e1fe3e4aSElliott Hughes                result[eAttrs["glyph"]] = safeEval(eAttrs["value"])
1669*e1fe3e4aSElliott Hughes        return result
1670*e1fe3e4aSElliott Hughes
1671*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1672*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, attrs)
1673*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1674*e1fe3e4aSElliott Hughes        for glyph, cid in sorted(value.items()):
1675*e1fe3e4aSElliott Hughes            if cid is not None and cid != 0xFFFF:
1676*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("CID", glyph=glyph, value=cid)
1677*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1678*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
1679*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1680*e1fe3e4aSElliott Hughes
1681*e1fe3e4aSElliott Hughes
1682*e1fe3e4aSElliott Hughesclass DeltaValue(BaseConverter):
1683*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1684*e1fe3e4aSElliott Hughes        StartSize = tableDict["StartSize"]
1685*e1fe3e4aSElliott Hughes        EndSize = tableDict["EndSize"]
1686*e1fe3e4aSElliott Hughes        DeltaFormat = tableDict["DeltaFormat"]
1687*e1fe3e4aSElliott Hughes        assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
1688*e1fe3e4aSElliott Hughes        nItems = EndSize - StartSize + 1
1689*e1fe3e4aSElliott Hughes        nBits = 1 << DeltaFormat
1690*e1fe3e4aSElliott Hughes        minusOffset = 1 << nBits
1691*e1fe3e4aSElliott Hughes        mask = (1 << nBits) - 1
1692*e1fe3e4aSElliott Hughes        signMask = 1 << (nBits - 1)
1693*e1fe3e4aSElliott Hughes
1694*e1fe3e4aSElliott Hughes        DeltaValue = []
1695*e1fe3e4aSElliott Hughes        tmp, shift = 0, 0
1696*e1fe3e4aSElliott Hughes        for i in range(nItems):
1697*e1fe3e4aSElliott Hughes            if shift == 0:
1698*e1fe3e4aSElliott Hughes                tmp, shift = reader.readUShort(), 16
1699*e1fe3e4aSElliott Hughes            shift = shift - nBits
1700*e1fe3e4aSElliott Hughes            value = (tmp >> shift) & mask
1701*e1fe3e4aSElliott Hughes            if value & signMask:
1702*e1fe3e4aSElliott Hughes                value = value - minusOffset
1703*e1fe3e4aSElliott Hughes            DeltaValue.append(value)
1704*e1fe3e4aSElliott Hughes        return DeltaValue
1705*e1fe3e4aSElliott Hughes
1706*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1707*e1fe3e4aSElliott Hughes        StartSize = tableDict["StartSize"]
1708*e1fe3e4aSElliott Hughes        EndSize = tableDict["EndSize"]
1709*e1fe3e4aSElliott Hughes        DeltaFormat = tableDict["DeltaFormat"]
1710*e1fe3e4aSElliott Hughes        DeltaValue = value
1711*e1fe3e4aSElliott Hughes        assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
1712*e1fe3e4aSElliott Hughes        nItems = EndSize - StartSize + 1
1713*e1fe3e4aSElliott Hughes        nBits = 1 << DeltaFormat
1714*e1fe3e4aSElliott Hughes        assert len(DeltaValue) == nItems
1715*e1fe3e4aSElliott Hughes        mask = (1 << nBits) - 1
1716*e1fe3e4aSElliott Hughes
1717*e1fe3e4aSElliott Hughes        tmp, shift = 0, 16
1718*e1fe3e4aSElliott Hughes        for value in DeltaValue:
1719*e1fe3e4aSElliott Hughes            shift = shift - nBits
1720*e1fe3e4aSElliott Hughes            tmp = tmp | ((value & mask) << shift)
1721*e1fe3e4aSElliott Hughes            if shift == 0:
1722*e1fe3e4aSElliott Hughes                writer.writeUShort(tmp)
1723*e1fe3e4aSElliott Hughes                tmp, shift = 0, 16
1724*e1fe3e4aSElliott Hughes        if shift != 16:
1725*e1fe3e4aSElliott Hughes            writer.writeUShort(tmp)
1726*e1fe3e4aSElliott Hughes
1727*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1728*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", value)])
1729*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1730*e1fe3e4aSElliott Hughes
1731*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1732*e1fe3e4aSElliott Hughes        return safeEval(attrs["value"])
1733*e1fe3e4aSElliott Hughes
1734*e1fe3e4aSElliott Hughes
1735*e1fe3e4aSElliott Hughesclass VarIdxMapValue(BaseConverter):
1736*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1737*e1fe3e4aSElliott Hughes        fmt = tableDict["EntryFormat"]
1738*e1fe3e4aSElliott Hughes        nItems = tableDict["MappingCount"]
1739*e1fe3e4aSElliott Hughes
1740*e1fe3e4aSElliott Hughes        innerBits = 1 + (fmt & 0x000F)
1741*e1fe3e4aSElliott Hughes        innerMask = (1 << innerBits) - 1
1742*e1fe3e4aSElliott Hughes        outerMask = 0xFFFFFFFF - innerMask
1743*e1fe3e4aSElliott Hughes        outerShift = 16 - innerBits
1744*e1fe3e4aSElliott Hughes
1745*e1fe3e4aSElliott Hughes        entrySize = 1 + ((fmt & 0x0030) >> 4)
1746*e1fe3e4aSElliott Hughes        readArray = {
1747*e1fe3e4aSElliott Hughes            1: reader.readUInt8Array,
1748*e1fe3e4aSElliott Hughes            2: reader.readUShortArray,
1749*e1fe3e4aSElliott Hughes            3: reader.readUInt24Array,
1750*e1fe3e4aSElliott Hughes            4: reader.readULongArray,
1751*e1fe3e4aSElliott Hughes        }[entrySize]
1752*e1fe3e4aSElliott Hughes
1753*e1fe3e4aSElliott Hughes        return [
1754*e1fe3e4aSElliott Hughes            (((raw & outerMask) << outerShift) | (raw & innerMask))
1755*e1fe3e4aSElliott Hughes            for raw in readArray(nItems)
1756*e1fe3e4aSElliott Hughes        ]
1757*e1fe3e4aSElliott Hughes
1758*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, value, repeatIndex=None):
1759*e1fe3e4aSElliott Hughes        fmt = tableDict["EntryFormat"]
1760*e1fe3e4aSElliott Hughes        mapping = value
1761*e1fe3e4aSElliott Hughes        writer["MappingCount"].setValue(len(mapping))
1762*e1fe3e4aSElliott Hughes
1763*e1fe3e4aSElliott Hughes        innerBits = 1 + (fmt & 0x000F)
1764*e1fe3e4aSElliott Hughes        innerMask = (1 << innerBits) - 1
1765*e1fe3e4aSElliott Hughes        outerShift = 16 - innerBits
1766*e1fe3e4aSElliott Hughes
1767*e1fe3e4aSElliott Hughes        entrySize = 1 + ((fmt & 0x0030) >> 4)
1768*e1fe3e4aSElliott Hughes        writeArray = {
1769*e1fe3e4aSElliott Hughes            1: writer.writeUInt8Array,
1770*e1fe3e4aSElliott Hughes            2: writer.writeUShortArray,
1771*e1fe3e4aSElliott Hughes            3: writer.writeUInt24Array,
1772*e1fe3e4aSElliott Hughes            4: writer.writeULongArray,
1773*e1fe3e4aSElliott Hughes        }[entrySize]
1774*e1fe3e4aSElliott Hughes
1775*e1fe3e4aSElliott Hughes        writeArray(
1776*e1fe3e4aSElliott Hughes            [
1777*e1fe3e4aSElliott Hughes                (((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
1778*e1fe3e4aSElliott Hughes                for idx in mapping
1779*e1fe3e4aSElliott Hughes            ]
1780*e1fe3e4aSElliott Hughes        )
1781*e1fe3e4aSElliott Hughes
1782*e1fe3e4aSElliott Hughes
1783*e1fe3e4aSElliott Hughesclass VarDataValue(BaseConverter):
1784*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1785*e1fe3e4aSElliott Hughes        values = []
1786*e1fe3e4aSElliott Hughes
1787*e1fe3e4aSElliott Hughes        regionCount = tableDict["VarRegionCount"]
1788*e1fe3e4aSElliott Hughes        wordCount = tableDict["NumShorts"]
1789*e1fe3e4aSElliott Hughes
1790*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/2279
1791*e1fe3e4aSElliott Hughes        longWords = bool(wordCount & 0x8000)
1792*e1fe3e4aSElliott Hughes        wordCount = wordCount & 0x7FFF
1793*e1fe3e4aSElliott Hughes
1794*e1fe3e4aSElliott Hughes        if longWords:
1795*e1fe3e4aSElliott Hughes            readBigArray, readSmallArray = reader.readLongArray, reader.readShortArray
1796*e1fe3e4aSElliott Hughes        else:
1797*e1fe3e4aSElliott Hughes            readBigArray, readSmallArray = reader.readShortArray, reader.readInt8Array
1798*e1fe3e4aSElliott Hughes
1799*e1fe3e4aSElliott Hughes        n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
1800*e1fe3e4aSElliott Hughes        values.extend(readBigArray(n1))
1801*e1fe3e4aSElliott Hughes        values.extend(readSmallArray(n2 - n1))
1802*e1fe3e4aSElliott Hughes        if n2 > regionCount:  # Padding
1803*e1fe3e4aSElliott Hughes            del values[regionCount:]
1804*e1fe3e4aSElliott Hughes
1805*e1fe3e4aSElliott Hughes        return values
1806*e1fe3e4aSElliott Hughes
1807*e1fe3e4aSElliott Hughes    def write(self, writer, font, tableDict, values, repeatIndex=None):
1808*e1fe3e4aSElliott Hughes        regionCount = tableDict["VarRegionCount"]
1809*e1fe3e4aSElliott Hughes        wordCount = tableDict["NumShorts"]
1810*e1fe3e4aSElliott Hughes
1811*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/2279
1812*e1fe3e4aSElliott Hughes        longWords = bool(wordCount & 0x8000)
1813*e1fe3e4aSElliott Hughes        wordCount = wordCount & 0x7FFF
1814*e1fe3e4aSElliott Hughes
1815*e1fe3e4aSElliott Hughes        (writeBigArray, writeSmallArray) = {
1816*e1fe3e4aSElliott Hughes            False: (writer.writeShortArray, writer.writeInt8Array),
1817*e1fe3e4aSElliott Hughes            True: (writer.writeLongArray, writer.writeShortArray),
1818*e1fe3e4aSElliott Hughes        }[longWords]
1819*e1fe3e4aSElliott Hughes
1820*e1fe3e4aSElliott Hughes        n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
1821*e1fe3e4aSElliott Hughes        writeBigArray(values[:n1])
1822*e1fe3e4aSElliott Hughes        writeSmallArray(values[n1:regionCount])
1823*e1fe3e4aSElliott Hughes        if n2 > regionCount:  # Padding
1824*e1fe3e4aSElliott Hughes            writer.writeSmallArray([0] * (n2 - regionCount))
1825*e1fe3e4aSElliott Hughes
1826*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1827*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", value)])
1828*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1829*e1fe3e4aSElliott Hughes
1830*e1fe3e4aSElliott Hughes    def xmlRead(self, attrs, content, font):
1831*e1fe3e4aSElliott Hughes        return safeEval(attrs["value"])
1832*e1fe3e4aSElliott Hughes
1833*e1fe3e4aSElliott Hughes
1834*e1fe3e4aSElliott Hughesclass LookupFlag(UShort):
1835*e1fe3e4aSElliott Hughes    def xmlWrite(self, xmlWriter, font, value, name, attrs):
1836*e1fe3e4aSElliott Hughes        xmlWriter.simpletag(name, attrs + [("value", value)])
1837*e1fe3e4aSElliott Hughes        flags = []
1838*e1fe3e4aSElliott Hughes        if value & 0x01:
1839*e1fe3e4aSElliott Hughes            flags.append("rightToLeft")
1840*e1fe3e4aSElliott Hughes        if value & 0x02:
1841*e1fe3e4aSElliott Hughes            flags.append("ignoreBaseGlyphs")
1842*e1fe3e4aSElliott Hughes        if value & 0x04:
1843*e1fe3e4aSElliott Hughes            flags.append("ignoreLigatures")
1844*e1fe3e4aSElliott Hughes        if value & 0x08:
1845*e1fe3e4aSElliott Hughes            flags.append("ignoreMarks")
1846*e1fe3e4aSElliott Hughes        if value & 0x10:
1847*e1fe3e4aSElliott Hughes            flags.append("useMarkFilteringSet")
1848*e1fe3e4aSElliott Hughes        if value & 0xFF00:
1849*e1fe3e4aSElliott Hughes            flags.append("markAttachmentType[%i]" % (value >> 8))
1850*e1fe3e4aSElliott Hughes        if flags:
1851*e1fe3e4aSElliott Hughes            xmlWriter.comment(" ".join(flags))
1852*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1853*e1fe3e4aSElliott Hughes
1854*e1fe3e4aSElliott Hughes
1855*e1fe3e4aSElliott Hughesclass _UInt8Enum(UInt8):
1856*e1fe3e4aSElliott Hughes    enumClass = NotImplemented
1857*e1fe3e4aSElliott Hughes
1858*e1fe3e4aSElliott Hughes    def read(self, reader, font, tableDict):
1859*e1fe3e4aSElliott Hughes        return self.enumClass(super().read(reader, font, tableDict))
1860*e1fe3e4aSElliott Hughes
1861*e1fe3e4aSElliott Hughes    @classmethod
1862*e1fe3e4aSElliott Hughes    def fromString(cls, value):
1863*e1fe3e4aSElliott Hughes        return getattr(cls.enumClass, value.upper())
1864*e1fe3e4aSElliott Hughes
1865*e1fe3e4aSElliott Hughes    @classmethod
1866*e1fe3e4aSElliott Hughes    def toString(cls, value):
1867*e1fe3e4aSElliott Hughes        return cls.enumClass(value).name.lower()
1868*e1fe3e4aSElliott Hughes
1869*e1fe3e4aSElliott Hughes
1870*e1fe3e4aSElliott Hughesclass ExtendMode(_UInt8Enum):
1871*e1fe3e4aSElliott Hughes    enumClass = _ExtendMode
1872*e1fe3e4aSElliott Hughes
1873*e1fe3e4aSElliott Hughes
1874*e1fe3e4aSElliott Hughesclass CompositeMode(_UInt8Enum):
1875*e1fe3e4aSElliott Hughes    enumClass = _CompositeMode
1876*e1fe3e4aSElliott Hughes
1877*e1fe3e4aSElliott Hughes
1878*e1fe3e4aSElliott HughesconverterMapping = {
1879*e1fe3e4aSElliott Hughes    # type		class
1880*e1fe3e4aSElliott Hughes    "int8": Int8,
1881*e1fe3e4aSElliott Hughes    "int16": Short,
1882*e1fe3e4aSElliott Hughes    "uint8": UInt8,
1883*e1fe3e4aSElliott Hughes    "uint16": UShort,
1884*e1fe3e4aSElliott Hughes    "uint24": UInt24,
1885*e1fe3e4aSElliott Hughes    "uint32": ULong,
1886*e1fe3e4aSElliott Hughes    "char64": Char64,
1887*e1fe3e4aSElliott Hughes    "Flags32": Flags32,
1888*e1fe3e4aSElliott Hughes    "VarIndex": VarIndex,
1889*e1fe3e4aSElliott Hughes    "Version": Version,
1890*e1fe3e4aSElliott Hughes    "Tag": Tag,
1891*e1fe3e4aSElliott Hughes    "GlyphID": GlyphID,
1892*e1fe3e4aSElliott Hughes    "GlyphID32": GlyphID32,
1893*e1fe3e4aSElliott Hughes    "NameID": NameID,
1894*e1fe3e4aSElliott Hughes    "DeciPoints": DeciPoints,
1895*e1fe3e4aSElliott Hughes    "Fixed": Fixed,
1896*e1fe3e4aSElliott Hughes    "F2Dot14": F2Dot14,
1897*e1fe3e4aSElliott Hughes    "Angle": Angle,
1898*e1fe3e4aSElliott Hughes    "BiasedAngle": BiasedAngle,
1899*e1fe3e4aSElliott Hughes    "struct": Struct,
1900*e1fe3e4aSElliott Hughes    "Offset": Table,
1901*e1fe3e4aSElliott Hughes    "LOffset": LTable,
1902*e1fe3e4aSElliott Hughes    "Offset24": Table24,
1903*e1fe3e4aSElliott Hughes    "ValueRecord": ValueRecord,
1904*e1fe3e4aSElliott Hughes    "DeltaValue": DeltaValue,
1905*e1fe3e4aSElliott Hughes    "VarIdxMapValue": VarIdxMapValue,
1906*e1fe3e4aSElliott Hughes    "VarDataValue": VarDataValue,
1907*e1fe3e4aSElliott Hughes    "LookupFlag": LookupFlag,
1908*e1fe3e4aSElliott Hughes    "ExtendMode": ExtendMode,
1909*e1fe3e4aSElliott Hughes    "CompositeMode": CompositeMode,
1910*e1fe3e4aSElliott Hughes    "STATFlags": STATFlags,
1911*e1fe3e4aSElliott Hughes    # AAT
1912*e1fe3e4aSElliott Hughes    "CIDGlyphMap": CIDGlyphMap,
1913*e1fe3e4aSElliott Hughes    "GlyphCIDMap": GlyphCIDMap,
1914*e1fe3e4aSElliott Hughes    "MortChain": StructWithLength,
1915*e1fe3e4aSElliott Hughes    "MortSubtable": StructWithLength,
1916*e1fe3e4aSElliott Hughes    "MorxChain": StructWithLength,
1917*e1fe3e4aSElliott Hughes    "MorxSubtable": MorxSubtableConverter,
1918*e1fe3e4aSElliott Hughes    # "Template" types
1919*e1fe3e4aSElliott Hughes    "AATLookup": lambda C: partial(AATLookup, tableClass=C),
1920*e1fe3e4aSElliott Hughes    "AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C),
1921*e1fe3e4aSElliott Hughes    "STXHeader": lambda C: partial(STXHeader, tableClass=C),
1922*e1fe3e4aSElliott Hughes    "OffsetTo": lambda C: partial(Table, tableClass=C),
1923*e1fe3e4aSElliott Hughes    "LOffsetTo": lambda C: partial(LTable, tableClass=C),
1924*e1fe3e4aSElliott Hughes    "LOffset24To": lambda C: partial(Table24, tableClass=C),
1925*e1fe3e4aSElliott Hughes}
1926