xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/tables/otTables.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes# coding: utf-8
2*e1fe3e4aSElliott Hughes"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
3*e1fe3e4aSElliott HughesOpenType subtables.
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott HughesMost are constructed upon import from data in otData.py, all are populated with
6*e1fe3e4aSElliott Hughesconverter objects from otConverters.py.
7*e1fe3e4aSElliott Hughes"""
8*e1fe3e4aSElliott Hughesimport copy
9*e1fe3e4aSElliott Hughesfrom enum import IntEnum
10*e1fe3e4aSElliott Hughesfrom functools import reduce
11*e1fe3e4aSElliott Hughesfrom math import radians
12*e1fe3e4aSElliott Hughesimport itertools
13*e1fe3e4aSElliott Hughesfrom collections import defaultdict, namedtuple
14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otTraverse import dfs_base_table
15*e1fe3e4aSElliott Hughesfrom fontTools.misc.arrayTools import quantizeRect
16*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound
17*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import Transform, Identity
18*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytesjoin, pad, safeEval
19*e1fe3e4aSElliott Hughesfrom fontTools.pens.boundsPen import ControlBoundsPen
20*e1fe3e4aSElliott Hughesfrom fontTools.pens.transformPen import TransformPen
21*e1fe3e4aSElliott Hughesfrom .otBase import (
22*e1fe3e4aSElliott Hughes    BaseTable,
23*e1fe3e4aSElliott Hughes    FormatSwitchingBaseTable,
24*e1fe3e4aSElliott Hughes    ValueRecord,
25*e1fe3e4aSElliott Hughes    CountReference,
26*e1fe3e4aSElliott Hughes    getFormatSwitchingBaseTableClass,
27*e1fe3e4aSElliott Hughes)
28*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
29*e1fe3e4aSElliott Hughesimport logging
30*e1fe3e4aSElliott Hughesimport struct
31*e1fe3e4aSElliott Hughesfrom typing import TYPE_CHECKING, Iterator, List, Optional, Set
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughesif TYPE_CHECKING:
34*e1fe3e4aSElliott Hughes    from fontTools.ttLib.ttGlyphSet import _TTGlyphSet
35*e1fe3e4aSElliott Hughes
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__)
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughesclass AATStateTable(object):
41*e1fe3e4aSElliott Hughes    def __init__(self):
42*e1fe3e4aSElliott Hughes        self.GlyphClasses = {}  # GlyphID --> GlyphClass
43*e1fe3e4aSElliott Hughes        self.States = []  # List of AATState, indexed by state number
44*e1fe3e4aSElliott Hughes        self.PerGlyphLookups = []  # [{GlyphID:GlyphID}, ...]
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughes
47*e1fe3e4aSElliott Hughesclass AATState(object):
48*e1fe3e4aSElliott Hughes    def __init__(self):
49*e1fe3e4aSElliott Hughes        self.Transitions = {}  # GlyphClass --> AATAction
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughesclass AATAction(object):
53*e1fe3e4aSElliott Hughes    _FLAGS = None
54*e1fe3e4aSElliott Hughes
55*e1fe3e4aSElliott Hughes    @staticmethod
56*e1fe3e4aSElliott Hughes    def compileActions(font, states):
57*e1fe3e4aSElliott Hughes        return (None, None)
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughes    def _writeFlagsToXML(self, xmlWriter):
60*e1fe3e4aSElliott Hughes        flags = [f for f in self._FLAGS if self.__dict__[f]]
61*e1fe3e4aSElliott Hughes        if flags:
62*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Flags", value=",".join(flags))
63*e1fe3e4aSElliott Hughes            xmlWriter.newline()
64*e1fe3e4aSElliott Hughes        if self.ReservedFlags != 0:
65*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("ReservedFlags", value="0x%04X" % self.ReservedFlags)
66*e1fe3e4aSElliott Hughes            xmlWriter.newline()
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    def _setFlag(self, flag):
69*e1fe3e4aSElliott Hughes        assert flag in self._FLAGS, "unsupported flag %s" % flag
70*e1fe3e4aSElliott Hughes        self.__dict__[flag] = True
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott Hughesclass RearrangementMorphAction(AATAction):
74*e1fe3e4aSElliott Hughes    staticSize = 4
75*e1fe3e4aSElliott Hughes    actionHeaderSize = 0
76*e1fe3e4aSElliott Hughes    _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"]
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes    _VERBS = {
79*e1fe3e4aSElliott Hughes        0: "no change",
80*e1fe3e4aSElliott Hughes        1: "Ax ⇒ xA",
81*e1fe3e4aSElliott Hughes        2: "xD ⇒ Dx",
82*e1fe3e4aSElliott Hughes        3: "AxD ⇒ DxA",
83*e1fe3e4aSElliott Hughes        4: "ABx ⇒ xAB",
84*e1fe3e4aSElliott Hughes        5: "ABx ⇒ xBA",
85*e1fe3e4aSElliott Hughes        6: "xCD ⇒ CDx",
86*e1fe3e4aSElliott Hughes        7: "xCD ⇒ DCx",
87*e1fe3e4aSElliott Hughes        8: "AxCD ⇒ CDxA",
88*e1fe3e4aSElliott Hughes        9: "AxCD ⇒ DCxA",
89*e1fe3e4aSElliott Hughes        10: "ABxD ⇒ DxAB",
90*e1fe3e4aSElliott Hughes        11: "ABxD ⇒ DxBA",
91*e1fe3e4aSElliott Hughes        12: "ABxCD ⇒ CDxAB",
92*e1fe3e4aSElliott Hughes        13: "ABxCD ⇒ CDxBA",
93*e1fe3e4aSElliott Hughes        14: "ABxCD ⇒ DCxAB",
94*e1fe3e4aSElliott Hughes        15: "ABxCD ⇒ DCxBA",
95*e1fe3e4aSElliott Hughes    }
96*e1fe3e4aSElliott Hughes
97*e1fe3e4aSElliott Hughes    def __init__(self):
98*e1fe3e4aSElliott Hughes        self.NewState = 0
99*e1fe3e4aSElliott Hughes        self.Verb = 0
100*e1fe3e4aSElliott Hughes        self.MarkFirst = False
101*e1fe3e4aSElliott Hughes        self.DontAdvance = False
102*e1fe3e4aSElliott Hughes        self.MarkLast = False
103*e1fe3e4aSElliott Hughes        self.ReservedFlags = 0
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes    def compile(self, writer, font, actionIndex):
106*e1fe3e4aSElliott Hughes        assert actionIndex is None
107*e1fe3e4aSElliott Hughes        writer.writeUShort(self.NewState)
108*e1fe3e4aSElliott Hughes        assert self.Verb >= 0 and self.Verb <= 15, self.Verb
109*e1fe3e4aSElliott Hughes        flags = self.Verb | self.ReservedFlags
110*e1fe3e4aSElliott Hughes        if self.MarkFirst:
111*e1fe3e4aSElliott Hughes            flags |= 0x8000
112*e1fe3e4aSElliott Hughes        if self.DontAdvance:
113*e1fe3e4aSElliott Hughes            flags |= 0x4000
114*e1fe3e4aSElliott Hughes        if self.MarkLast:
115*e1fe3e4aSElliott Hughes            flags |= 0x2000
116*e1fe3e4aSElliott Hughes        writer.writeUShort(flags)
117*e1fe3e4aSElliott Hughes
118*e1fe3e4aSElliott Hughes    def decompile(self, reader, font, actionReader):
119*e1fe3e4aSElliott Hughes        assert actionReader is None
120*e1fe3e4aSElliott Hughes        self.NewState = reader.readUShort()
121*e1fe3e4aSElliott Hughes        flags = reader.readUShort()
122*e1fe3e4aSElliott Hughes        self.Verb = flags & 0xF
123*e1fe3e4aSElliott Hughes        self.MarkFirst = bool(flags & 0x8000)
124*e1fe3e4aSElliott Hughes        self.DontAdvance = bool(flags & 0x4000)
125*e1fe3e4aSElliott Hughes        self.MarkLast = bool(flags & 0x2000)
126*e1fe3e4aSElliott Hughes        self.ReservedFlags = flags & 0x1FF0
127*e1fe3e4aSElliott Hughes
128*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs, name):
129*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, **attrs)
130*e1fe3e4aSElliott Hughes        xmlWriter.newline()
131*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("NewState", value=self.NewState)
132*e1fe3e4aSElliott Hughes        xmlWriter.newline()
133*e1fe3e4aSElliott Hughes        self._writeFlagsToXML(xmlWriter)
134*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("Verb", value=self.Verb)
135*e1fe3e4aSElliott Hughes        verbComment = self._VERBS.get(self.Verb)
136*e1fe3e4aSElliott Hughes        if verbComment is not None:
137*e1fe3e4aSElliott Hughes            xmlWriter.comment(verbComment)
138*e1fe3e4aSElliott Hughes        xmlWriter.newline()
139*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
140*e1fe3e4aSElliott Hughes        xmlWriter.newline()
141*e1fe3e4aSElliott Hughes
142*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
143*e1fe3e4aSElliott Hughes        self.NewState = self.Verb = self.ReservedFlags = 0
144*e1fe3e4aSElliott Hughes        self.MarkFirst = self.DontAdvance = self.MarkLast = False
145*e1fe3e4aSElliott Hughes        content = [t for t in content if isinstance(t, tuple)]
146*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in content:
147*e1fe3e4aSElliott Hughes            if eltName == "NewState":
148*e1fe3e4aSElliott Hughes                self.NewState = safeEval(eltAttrs["value"])
149*e1fe3e4aSElliott Hughes            elif eltName == "Verb":
150*e1fe3e4aSElliott Hughes                self.Verb = safeEval(eltAttrs["value"])
151*e1fe3e4aSElliott Hughes            elif eltName == "ReservedFlags":
152*e1fe3e4aSElliott Hughes                self.ReservedFlags = safeEval(eltAttrs["value"])
153*e1fe3e4aSElliott Hughes            elif eltName == "Flags":
154*e1fe3e4aSElliott Hughes                for flag in eltAttrs["value"].split(","):
155*e1fe3e4aSElliott Hughes                    self._setFlag(flag.strip())
156*e1fe3e4aSElliott Hughes
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughesclass ContextualMorphAction(AATAction):
159*e1fe3e4aSElliott Hughes    staticSize = 8
160*e1fe3e4aSElliott Hughes    actionHeaderSize = 0
161*e1fe3e4aSElliott Hughes    _FLAGS = ["SetMark", "DontAdvance"]
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes    def __init__(self):
164*e1fe3e4aSElliott Hughes        self.NewState = 0
165*e1fe3e4aSElliott Hughes        self.SetMark, self.DontAdvance = False, False
166*e1fe3e4aSElliott Hughes        self.ReservedFlags = 0
167*e1fe3e4aSElliott Hughes        self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes    def compile(self, writer, font, actionIndex):
170*e1fe3e4aSElliott Hughes        assert actionIndex is None
171*e1fe3e4aSElliott Hughes        writer.writeUShort(self.NewState)
172*e1fe3e4aSElliott Hughes        flags = self.ReservedFlags
173*e1fe3e4aSElliott Hughes        if self.SetMark:
174*e1fe3e4aSElliott Hughes            flags |= 0x8000
175*e1fe3e4aSElliott Hughes        if self.DontAdvance:
176*e1fe3e4aSElliott Hughes            flags |= 0x4000
177*e1fe3e4aSElliott Hughes        writer.writeUShort(flags)
178*e1fe3e4aSElliott Hughes        writer.writeUShort(self.MarkIndex)
179*e1fe3e4aSElliott Hughes        writer.writeUShort(self.CurrentIndex)
180*e1fe3e4aSElliott Hughes
181*e1fe3e4aSElliott Hughes    def decompile(self, reader, font, actionReader):
182*e1fe3e4aSElliott Hughes        assert actionReader is None
183*e1fe3e4aSElliott Hughes        self.NewState = reader.readUShort()
184*e1fe3e4aSElliott Hughes        flags = reader.readUShort()
185*e1fe3e4aSElliott Hughes        self.SetMark = bool(flags & 0x8000)
186*e1fe3e4aSElliott Hughes        self.DontAdvance = bool(flags & 0x4000)
187*e1fe3e4aSElliott Hughes        self.ReservedFlags = flags & 0x3FFF
188*e1fe3e4aSElliott Hughes        self.MarkIndex = reader.readUShort()
189*e1fe3e4aSElliott Hughes        self.CurrentIndex = reader.readUShort()
190*e1fe3e4aSElliott Hughes
191*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs, name):
192*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, **attrs)
193*e1fe3e4aSElliott Hughes        xmlWriter.newline()
194*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("NewState", value=self.NewState)
195*e1fe3e4aSElliott Hughes        xmlWriter.newline()
196*e1fe3e4aSElliott Hughes        self._writeFlagsToXML(xmlWriter)
197*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("MarkIndex", value=self.MarkIndex)
198*e1fe3e4aSElliott Hughes        xmlWriter.newline()
199*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("CurrentIndex", value=self.CurrentIndex)
200*e1fe3e4aSElliott Hughes        xmlWriter.newline()
201*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
202*e1fe3e4aSElliott Hughes        xmlWriter.newline()
203*e1fe3e4aSElliott Hughes
204*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
205*e1fe3e4aSElliott Hughes        self.NewState = self.ReservedFlags = 0
206*e1fe3e4aSElliott Hughes        self.SetMark = self.DontAdvance = False
207*e1fe3e4aSElliott Hughes        self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
208*e1fe3e4aSElliott Hughes        content = [t for t in content if isinstance(t, tuple)]
209*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in content:
210*e1fe3e4aSElliott Hughes            if eltName == "NewState":
211*e1fe3e4aSElliott Hughes                self.NewState = safeEval(eltAttrs["value"])
212*e1fe3e4aSElliott Hughes            elif eltName == "Flags":
213*e1fe3e4aSElliott Hughes                for flag in eltAttrs["value"].split(","):
214*e1fe3e4aSElliott Hughes                    self._setFlag(flag.strip())
215*e1fe3e4aSElliott Hughes            elif eltName == "ReservedFlags":
216*e1fe3e4aSElliott Hughes                self.ReservedFlags = safeEval(eltAttrs["value"])
217*e1fe3e4aSElliott Hughes            elif eltName == "MarkIndex":
218*e1fe3e4aSElliott Hughes                self.MarkIndex = safeEval(eltAttrs["value"])
219*e1fe3e4aSElliott Hughes            elif eltName == "CurrentIndex":
220*e1fe3e4aSElliott Hughes                self.CurrentIndex = safeEval(eltAttrs["value"])
221*e1fe3e4aSElliott Hughes
222*e1fe3e4aSElliott Hughes
223*e1fe3e4aSElliott Hughesclass LigAction(object):
224*e1fe3e4aSElliott Hughes    def __init__(self):
225*e1fe3e4aSElliott Hughes        self.Store = False
226*e1fe3e4aSElliott Hughes        # GlyphIndexDelta is a (possibly negative) delta that gets
227*e1fe3e4aSElliott Hughes        # added to the glyph ID at the top of the AAT runtime
228*e1fe3e4aSElliott Hughes        # execution stack. It is *not* a byte offset into the
229*e1fe3e4aSElliott Hughes        # morx table. The result of the addition, which is performed
230*e1fe3e4aSElliott Hughes        # at run time by the shaping engine, is an index into
231*e1fe3e4aSElliott Hughes        # the ligature components table. See 'morx' specification.
232*e1fe3e4aSElliott Hughes        # In the AAT specification, this field is called Offset;
233*e1fe3e4aSElliott Hughes        # but its meaning is quite different from other offsets
234*e1fe3e4aSElliott Hughes        # in either AAT or OpenType, so we use a different name.
235*e1fe3e4aSElliott Hughes        self.GlyphIndexDelta = 0
236*e1fe3e4aSElliott Hughes
237*e1fe3e4aSElliott Hughes
238*e1fe3e4aSElliott Hughesclass LigatureMorphAction(AATAction):
239*e1fe3e4aSElliott Hughes    staticSize = 6
240*e1fe3e4aSElliott Hughes
241*e1fe3e4aSElliott Hughes    # 4 bytes for each of {action,ligComponents,ligatures}Offset
242*e1fe3e4aSElliott Hughes    actionHeaderSize = 12
243*e1fe3e4aSElliott Hughes
244*e1fe3e4aSElliott Hughes    _FLAGS = ["SetComponent", "DontAdvance"]
245*e1fe3e4aSElliott Hughes
246*e1fe3e4aSElliott Hughes    def __init__(self):
247*e1fe3e4aSElliott Hughes        self.NewState = 0
248*e1fe3e4aSElliott Hughes        self.SetComponent, self.DontAdvance = False, False
249*e1fe3e4aSElliott Hughes        self.ReservedFlags = 0
250*e1fe3e4aSElliott Hughes        self.Actions = []
251*e1fe3e4aSElliott Hughes
252*e1fe3e4aSElliott Hughes    def compile(self, writer, font, actionIndex):
253*e1fe3e4aSElliott Hughes        assert actionIndex is not None
254*e1fe3e4aSElliott Hughes        writer.writeUShort(self.NewState)
255*e1fe3e4aSElliott Hughes        flags = self.ReservedFlags
256*e1fe3e4aSElliott Hughes        if self.SetComponent:
257*e1fe3e4aSElliott Hughes            flags |= 0x8000
258*e1fe3e4aSElliott Hughes        if self.DontAdvance:
259*e1fe3e4aSElliott Hughes            flags |= 0x4000
260*e1fe3e4aSElliott Hughes        if len(self.Actions) > 0:
261*e1fe3e4aSElliott Hughes            flags |= 0x2000
262*e1fe3e4aSElliott Hughes        writer.writeUShort(flags)
263*e1fe3e4aSElliott Hughes        if len(self.Actions) > 0:
264*e1fe3e4aSElliott Hughes            actions = self.compileLigActions()
265*e1fe3e4aSElliott Hughes            writer.writeUShort(actionIndex[actions])
266*e1fe3e4aSElliott Hughes        else:
267*e1fe3e4aSElliott Hughes            writer.writeUShort(0)
268*e1fe3e4aSElliott Hughes
269*e1fe3e4aSElliott Hughes    def decompile(self, reader, font, actionReader):
270*e1fe3e4aSElliott Hughes        assert actionReader is not None
271*e1fe3e4aSElliott Hughes        self.NewState = reader.readUShort()
272*e1fe3e4aSElliott Hughes        flags = reader.readUShort()
273*e1fe3e4aSElliott Hughes        self.SetComponent = bool(flags & 0x8000)
274*e1fe3e4aSElliott Hughes        self.DontAdvance = bool(flags & 0x4000)
275*e1fe3e4aSElliott Hughes        performAction = bool(flags & 0x2000)
276*e1fe3e4aSElliott Hughes        # As of 2017-09-12, the 'morx' specification says that
277*e1fe3e4aSElliott Hughes        # the reserved bitmask in ligature subtables is 0x3FFF.
278*e1fe3e4aSElliott Hughes        # However, the specification also defines a flag 0x2000,
279*e1fe3e4aSElliott Hughes        # so the reserved value should actually be 0x1FFF.
280*e1fe3e4aSElliott Hughes        # TODO: Report this specification bug to Apple.
281*e1fe3e4aSElliott Hughes        self.ReservedFlags = flags & 0x1FFF
282*e1fe3e4aSElliott Hughes        actionIndex = reader.readUShort()
283*e1fe3e4aSElliott Hughes        if performAction:
284*e1fe3e4aSElliott Hughes            self.Actions = self._decompileLigActions(actionReader, actionIndex)
285*e1fe3e4aSElliott Hughes        else:
286*e1fe3e4aSElliott Hughes            self.Actions = []
287*e1fe3e4aSElliott Hughes
288*e1fe3e4aSElliott Hughes    @staticmethod
289*e1fe3e4aSElliott Hughes    def compileActions(font, states):
290*e1fe3e4aSElliott Hughes        result, actions, actionIndex = b"", set(), {}
291*e1fe3e4aSElliott Hughes        for state in states:
292*e1fe3e4aSElliott Hughes            for _glyphClass, trans in state.Transitions.items():
293*e1fe3e4aSElliott Hughes                actions.add(trans.compileLigActions())
294*e1fe3e4aSElliott Hughes        # Sort the compiled actions in decreasing order of
295*e1fe3e4aSElliott Hughes        # length, so that the longer sequence come before the
296*e1fe3e4aSElliott Hughes        # shorter ones.  For each compiled action ABCD, its
297*e1fe3e4aSElliott Hughes        # suffixes BCD, CD, and D do not be encoded separately
298*e1fe3e4aSElliott Hughes        # (in case they occur); instead, we can just store an
299*e1fe3e4aSElliott Hughes        # index that points into the middle of the longer
300*e1fe3e4aSElliott Hughes        # sequence. Every compiled AAT ligature sequence is
301*e1fe3e4aSElliott Hughes        # terminated with an end-of-sequence flag, which can
302*e1fe3e4aSElliott Hughes        # only be set on the last element of the sequence.
303*e1fe3e4aSElliott Hughes        # Therefore, it is sufficient to consider just the
304*e1fe3e4aSElliott Hughes        # suffixes.
305*e1fe3e4aSElliott Hughes        for a in sorted(actions, key=lambda x: (-len(x), x)):
306*e1fe3e4aSElliott Hughes            if a not in actionIndex:
307*e1fe3e4aSElliott Hughes                for i in range(0, len(a), 4):
308*e1fe3e4aSElliott Hughes                    suffix = a[i:]
309*e1fe3e4aSElliott Hughes                    suffixIndex = (len(result) + i) // 4
310*e1fe3e4aSElliott Hughes                    actionIndex.setdefault(suffix, suffixIndex)
311*e1fe3e4aSElliott Hughes                result += a
312*e1fe3e4aSElliott Hughes        result = pad(result, 4)
313*e1fe3e4aSElliott Hughes        return (result, actionIndex)
314*e1fe3e4aSElliott Hughes
315*e1fe3e4aSElliott Hughes    def compileLigActions(self):
316*e1fe3e4aSElliott Hughes        result = []
317*e1fe3e4aSElliott Hughes        for i, action in enumerate(self.Actions):
318*e1fe3e4aSElliott Hughes            last = i == len(self.Actions) - 1
319*e1fe3e4aSElliott Hughes            value = action.GlyphIndexDelta & 0x3FFFFFFF
320*e1fe3e4aSElliott Hughes            value |= 0x80000000 if last else 0
321*e1fe3e4aSElliott Hughes            value |= 0x40000000 if action.Store else 0
322*e1fe3e4aSElliott Hughes            result.append(struct.pack(">L", value))
323*e1fe3e4aSElliott Hughes        return bytesjoin(result)
324*e1fe3e4aSElliott Hughes
325*e1fe3e4aSElliott Hughes    def _decompileLigActions(self, actionReader, actionIndex):
326*e1fe3e4aSElliott Hughes        actions = []
327*e1fe3e4aSElliott Hughes        last = False
328*e1fe3e4aSElliott Hughes        reader = actionReader.getSubReader(actionReader.pos + actionIndex * 4)
329*e1fe3e4aSElliott Hughes        while not last:
330*e1fe3e4aSElliott Hughes            value = reader.readULong()
331*e1fe3e4aSElliott Hughes            last = bool(value & 0x80000000)
332*e1fe3e4aSElliott Hughes            action = LigAction()
333*e1fe3e4aSElliott Hughes            actions.append(action)
334*e1fe3e4aSElliott Hughes            action.Store = bool(value & 0x40000000)
335*e1fe3e4aSElliott Hughes            delta = value & 0x3FFFFFFF
336*e1fe3e4aSElliott Hughes            if delta >= 0x20000000:  # sign-extend 30-bit value
337*e1fe3e4aSElliott Hughes                delta = -0x40000000 + delta
338*e1fe3e4aSElliott Hughes            action.GlyphIndexDelta = delta
339*e1fe3e4aSElliott Hughes        return actions
340*e1fe3e4aSElliott Hughes
341*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
342*e1fe3e4aSElliott Hughes        self.NewState = self.ReservedFlags = 0
343*e1fe3e4aSElliott Hughes        self.SetComponent = self.DontAdvance = False
344*e1fe3e4aSElliott Hughes        self.ReservedFlags = 0
345*e1fe3e4aSElliott Hughes        self.Actions = []
346*e1fe3e4aSElliott Hughes        content = [t for t in content if isinstance(t, tuple)]
347*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in content:
348*e1fe3e4aSElliott Hughes            if eltName == "NewState":
349*e1fe3e4aSElliott Hughes                self.NewState = safeEval(eltAttrs["value"])
350*e1fe3e4aSElliott Hughes            elif eltName == "Flags":
351*e1fe3e4aSElliott Hughes                for flag in eltAttrs["value"].split(","):
352*e1fe3e4aSElliott Hughes                    self._setFlag(flag.strip())
353*e1fe3e4aSElliott Hughes            elif eltName == "ReservedFlags":
354*e1fe3e4aSElliott Hughes                self.ReservedFlags = safeEval(eltAttrs["value"])
355*e1fe3e4aSElliott Hughes            elif eltName == "Action":
356*e1fe3e4aSElliott Hughes                action = LigAction()
357*e1fe3e4aSElliott Hughes                flags = eltAttrs.get("Flags", "").split(",")
358*e1fe3e4aSElliott Hughes                flags = [f.strip() for f in flags]
359*e1fe3e4aSElliott Hughes                action.Store = "Store" in flags
360*e1fe3e4aSElliott Hughes                action.GlyphIndexDelta = safeEval(eltAttrs["GlyphIndexDelta"])
361*e1fe3e4aSElliott Hughes                self.Actions.append(action)
362*e1fe3e4aSElliott Hughes
363*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs, name):
364*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, **attrs)
365*e1fe3e4aSElliott Hughes        xmlWriter.newline()
366*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("NewState", value=self.NewState)
367*e1fe3e4aSElliott Hughes        xmlWriter.newline()
368*e1fe3e4aSElliott Hughes        self._writeFlagsToXML(xmlWriter)
369*e1fe3e4aSElliott Hughes        for action in self.Actions:
370*e1fe3e4aSElliott Hughes            attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)]
371*e1fe3e4aSElliott Hughes            if action.Store:
372*e1fe3e4aSElliott Hughes                attribs.append(("Flags", "Store"))
373*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Action", attribs)
374*e1fe3e4aSElliott Hughes            xmlWriter.newline()
375*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
376*e1fe3e4aSElliott Hughes        xmlWriter.newline()
377*e1fe3e4aSElliott Hughes
378*e1fe3e4aSElliott Hughes
379*e1fe3e4aSElliott Hughesclass InsertionMorphAction(AATAction):
380*e1fe3e4aSElliott Hughes    staticSize = 8
381*e1fe3e4aSElliott Hughes    actionHeaderSize = 4  # 4 bytes for actionOffset
382*e1fe3e4aSElliott Hughes    _FLAGS = [
383*e1fe3e4aSElliott Hughes        "SetMark",
384*e1fe3e4aSElliott Hughes        "DontAdvance",
385*e1fe3e4aSElliott Hughes        "CurrentIsKashidaLike",
386*e1fe3e4aSElliott Hughes        "MarkedIsKashidaLike",
387*e1fe3e4aSElliott Hughes        "CurrentInsertBefore",
388*e1fe3e4aSElliott Hughes        "MarkedInsertBefore",
389*e1fe3e4aSElliott Hughes    ]
390*e1fe3e4aSElliott Hughes
391*e1fe3e4aSElliott Hughes    def __init__(self):
392*e1fe3e4aSElliott Hughes        self.NewState = 0
393*e1fe3e4aSElliott Hughes        for flag in self._FLAGS:
394*e1fe3e4aSElliott Hughes            setattr(self, flag, False)
395*e1fe3e4aSElliott Hughes        self.ReservedFlags = 0
396*e1fe3e4aSElliott Hughes        self.CurrentInsertionAction, self.MarkedInsertionAction = [], []
397*e1fe3e4aSElliott Hughes
398*e1fe3e4aSElliott Hughes    def compile(self, writer, font, actionIndex):
399*e1fe3e4aSElliott Hughes        assert actionIndex is not None
400*e1fe3e4aSElliott Hughes        writer.writeUShort(self.NewState)
401*e1fe3e4aSElliott Hughes        flags = self.ReservedFlags
402*e1fe3e4aSElliott Hughes        if self.SetMark:
403*e1fe3e4aSElliott Hughes            flags |= 0x8000
404*e1fe3e4aSElliott Hughes        if self.DontAdvance:
405*e1fe3e4aSElliott Hughes            flags |= 0x4000
406*e1fe3e4aSElliott Hughes        if self.CurrentIsKashidaLike:
407*e1fe3e4aSElliott Hughes            flags |= 0x2000
408*e1fe3e4aSElliott Hughes        if self.MarkedIsKashidaLike:
409*e1fe3e4aSElliott Hughes            flags |= 0x1000
410*e1fe3e4aSElliott Hughes        if self.CurrentInsertBefore:
411*e1fe3e4aSElliott Hughes            flags |= 0x0800
412*e1fe3e4aSElliott Hughes        if self.MarkedInsertBefore:
413*e1fe3e4aSElliott Hughes            flags |= 0x0400
414*e1fe3e4aSElliott Hughes        flags |= len(self.CurrentInsertionAction) << 5
415*e1fe3e4aSElliott Hughes        flags |= len(self.MarkedInsertionAction)
416*e1fe3e4aSElliott Hughes        writer.writeUShort(flags)
417*e1fe3e4aSElliott Hughes        if len(self.CurrentInsertionAction) > 0:
418*e1fe3e4aSElliott Hughes            currentIndex = actionIndex[tuple(self.CurrentInsertionAction)]
419*e1fe3e4aSElliott Hughes        else:
420*e1fe3e4aSElliott Hughes            currentIndex = 0xFFFF
421*e1fe3e4aSElliott Hughes        writer.writeUShort(currentIndex)
422*e1fe3e4aSElliott Hughes        if len(self.MarkedInsertionAction) > 0:
423*e1fe3e4aSElliott Hughes            markedIndex = actionIndex[tuple(self.MarkedInsertionAction)]
424*e1fe3e4aSElliott Hughes        else:
425*e1fe3e4aSElliott Hughes            markedIndex = 0xFFFF
426*e1fe3e4aSElliott Hughes        writer.writeUShort(markedIndex)
427*e1fe3e4aSElliott Hughes
428*e1fe3e4aSElliott Hughes    def decompile(self, reader, font, actionReader):
429*e1fe3e4aSElliott Hughes        assert actionReader is not None
430*e1fe3e4aSElliott Hughes        self.NewState = reader.readUShort()
431*e1fe3e4aSElliott Hughes        flags = reader.readUShort()
432*e1fe3e4aSElliott Hughes        self.SetMark = bool(flags & 0x8000)
433*e1fe3e4aSElliott Hughes        self.DontAdvance = bool(flags & 0x4000)
434*e1fe3e4aSElliott Hughes        self.CurrentIsKashidaLike = bool(flags & 0x2000)
435*e1fe3e4aSElliott Hughes        self.MarkedIsKashidaLike = bool(flags & 0x1000)
436*e1fe3e4aSElliott Hughes        self.CurrentInsertBefore = bool(flags & 0x0800)
437*e1fe3e4aSElliott Hughes        self.MarkedInsertBefore = bool(flags & 0x0400)
438*e1fe3e4aSElliott Hughes        self.CurrentInsertionAction = self._decompileInsertionAction(
439*e1fe3e4aSElliott Hughes            actionReader, font, index=reader.readUShort(), count=((flags & 0x03E0) >> 5)
440*e1fe3e4aSElliott Hughes        )
441*e1fe3e4aSElliott Hughes        self.MarkedInsertionAction = self._decompileInsertionAction(
442*e1fe3e4aSElliott Hughes            actionReader, font, index=reader.readUShort(), count=(flags & 0x001F)
443*e1fe3e4aSElliott Hughes        )
444*e1fe3e4aSElliott Hughes
445*e1fe3e4aSElliott Hughes    def _decompileInsertionAction(self, actionReader, font, index, count):
446*e1fe3e4aSElliott Hughes        if index == 0xFFFF or count == 0:
447*e1fe3e4aSElliott Hughes            return []
448*e1fe3e4aSElliott Hughes        reader = actionReader.getSubReader(actionReader.pos + index * 2)
449*e1fe3e4aSElliott Hughes        return font.getGlyphNameMany(reader.readUShortArray(count))
450*e1fe3e4aSElliott Hughes
451*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs, name):
452*e1fe3e4aSElliott Hughes        xmlWriter.begintag(name, **attrs)
453*e1fe3e4aSElliott Hughes        xmlWriter.newline()
454*e1fe3e4aSElliott Hughes        xmlWriter.simpletag("NewState", value=self.NewState)
455*e1fe3e4aSElliott Hughes        xmlWriter.newline()
456*e1fe3e4aSElliott Hughes        self._writeFlagsToXML(xmlWriter)
457*e1fe3e4aSElliott Hughes        for g in self.CurrentInsertionAction:
458*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("CurrentInsertionAction", glyph=g)
459*e1fe3e4aSElliott Hughes            xmlWriter.newline()
460*e1fe3e4aSElliott Hughes        for g in self.MarkedInsertionAction:
461*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("MarkedInsertionAction", glyph=g)
462*e1fe3e4aSElliott Hughes            xmlWriter.newline()
463*e1fe3e4aSElliott Hughes        xmlWriter.endtag(name)
464*e1fe3e4aSElliott Hughes        xmlWriter.newline()
465*e1fe3e4aSElliott Hughes
466*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
467*e1fe3e4aSElliott Hughes        self.__init__()
468*e1fe3e4aSElliott Hughes        content = [t for t in content if isinstance(t, tuple)]
469*e1fe3e4aSElliott Hughes        for eltName, eltAttrs, eltContent in content:
470*e1fe3e4aSElliott Hughes            if eltName == "NewState":
471*e1fe3e4aSElliott Hughes                self.NewState = safeEval(eltAttrs["value"])
472*e1fe3e4aSElliott Hughes            elif eltName == "Flags":
473*e1fe3e4aSElliott Hughes                for flag in eltAttrs["value"].split(","):
474*e1fe3e4aSElliott Hughes                    self._setFlag(flag.strip())
475*e1fe3e4aSElliott Hughes            elif eltName == "CurrentInsertionAction":
476*e1fe3e4aSElliott Hughes                self.CurrentInsertionAction.append(eltAttrs["glyph"])
477*e1fe3e4aSElliott Hughes            elif eltName == "MarkedInsertionAction":
478*e1fe3e4aSElliott Hughes                self.MarkedInsertionAction.append(eltAttrs["glyph"])
479*e1fe3e4aSElliott Hughes            else:
480*e1fe3e4aSElliott Hughes                assert False, eltName
481*e1fe3e4aSElliott Hughes
482*e1fe3e4aSElliott Hughes    @staticmethod
483*e1fe3e4aSElliott Hughes    def compileActions(font, states):
484*e1fe3e4aSElliott Hughes        actions, actionIndex, result = set(), {}, b""
485*e1fe3e4aSElliott Hughes        for state in states:
486*e1fe3e4aSElliott Hughes            for _glyphClass, trans in state.Transitions.items():
487*e1fe3e4aSElliott Hughes                if trans.CurrentInsertionAction is not None:
488*e1fe3e4aSElliott Hughes                    actions.add(tuple(trans.CurrentInsertionAction))
489*e1fe3e4aSElliott Hughes                if trans.MarkedInsertionAction is not None:
490*e1fe3e4aSElliott Hughes                    actions.add(tuple(trans.MarkedInsertionAction))
491*e1fe3e4aSElliott Hughes        # Sort the compiled actions in decreasing order of
492*e1fe3e4aSElliott Hughes        # length, so that the longer sequence come before the
493*e1fe3e4aSElliott Hughes        # shorter ones.
494*e1fe3e4aSElliott Hughes        for action in sorted(actions, key=lambda x: (-len(x), x)):
495*e1fe3e4aSElliott Hughes            # We insert all sub-sequences of the action glyph sequence
496*e1fe3e4aSElliott Hughes            # into actionIndex. For example, if one action triggers on
497*e1fe3e4aSElliott Hughes            # glyph sequence [A, B, C, D, E] and another action triggers
498*e1fe3e4aSElliott Hughes            # on [C, D], we return result=[A, B, C, D, E] (as list of
499*e1fe3e4aSElliott Hughes            # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0,
500*e1fe3e4aSElliott Hughes            # ('C','D'): 2}.
501*e1fe3e4aSElliott Hughes            if action in actionIndex:
502*e1fe3e4aSElliott Hughes                continue
503*e1fe3e4aSElliott Hughes            for start in range(0, len(action)):
504*e1fe3e4aSElliott Hughes                startIndex = (len(result) // 2) + start
505*e1fe3e4aSElliott Hughes                for limit in range(start, len(action)):
506*e1fe3e4aSElliott Hughes                    glyphs = action[start : limit + 1]
507*e1fe3e4aSElliott Hughes                    actionIndex.setdefault(glyphs, startIndex)
508*e1fe3e4aSElliott Hughes            for glyph in action:
509*e1fe3e4aSElliott Hughes                glyphID = font.getGlyphID(glyph)
510*e1fe3e4aSElliott Hughes                result += struct.pack(">H", glyphID)
511*e1fe3e4aSElliott Hughes        return result, actionIndex
512*e1fe3e4aSElliott Hughes
513*e1fe3e4aSElliott Hughes
514*e1fe3e4aSElliott Hughesclass FeatureParams(BaseTable):
515*e1fe3e4aSElliott Hughes    def compile(self, writer, font):
516*e1fe3e4aSElliott Hughes        assert (
517*e1fe3e4aSElliott Hughes            featureParamTypes.get(writer["FeatureTag"]) == self.__class__
518*e1fe3e4aSElliott Hughes        ), "Wrong FeatureParams type for feature '%s': %s" % (
519*e1fe3e4aSElliott Hughes            writer["FeatureTag"],
520*e1fe3e4aSElliott Hughes            self.__class__.__name__,
521*e1fe3e4aSElliott Hughes        )
522*e1fe3e4aSElliott Hughes        BaseTable.compile(self, writer, font)
523*e1fe3e4aSElliott Hughes
524*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs=None, name=None):
525*e1fe3e4aSElliott Hughes        BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__)
526*e1fe3e4aSElliott Hughes
527*e1fe3e4aSElliott Hughes
528*e1fe3e4aSElliott Hughesclass FeatureParamsSize(FeatureParams):
529*e1fe3e4aSElliott Hughes    pass
530*e1fe3e4aSElliott Hughes
531*e1fe3e4aSElliott Hughes
532*e1fe3e4aSElliott Hughesclass FeatureParamsStylisticSet(FeatureParams):
533*e1fe3e4aSElliott Hughes    pass
534*e1fe3e4aSElliott Hughes
535*e1fe3e4aSElliott Hughes
536*e1fe3e4aSElliott Hughesclass FeatureParamsCharacterVariants(FeatureParams):
537*e1fe3e4aSElliott Hughes    pass
538*e1fe3e4aSElliott Hughes
539*e1fe3e4aSElliott Hughes
540*e1fe3e4aSElliott Hughesclass Coverage(FormatSwitchingBaseTable):
541*e1fe3e4aSElliott Hughes    # manual implementation to get rid of glyphID dependencies
542*e1fe3e4aSElliott Hughes
543*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
544*e1fe3e4aSElliott Hughes        if not hasattr(self, "glyphs"):
545*e1fe3e4aSElliott Hughes            self.glyphs = []
546*e1fe3e4aSElliott Hughes
547*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
548*e1fe3e4aSElliott Hughes        if self.Format == 1:
549*e1fe3e4aSElliott Hughes            self.glyphs = rawTable["GlyphArray"]
550*e1fe3e4aSElliott Hughes        elif self.Format == 2:
551*e1fe3e4aSElliott Hughes            glyphs = self.glyphs = []
552*e1fe3e4aSElliott Hughes            ranges = rawTable["RangeRecord"]
553*e1fe3e4aSElliott Hughes            # Some SIL fonts have coverage entries that don't have sorted
554*e1fe3e4aSElliott Hughes            # StartCoverageIndex.  If it is so, fixup and warn.  We undo
555*e1fe3e4aSElliott Hughes            # this when writing font out.
556*e1fe3e4aSElliott Hughes            sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
557*e1fe3e4aSElliott Hughes            if ranges != sorted_ranges:
558*e1fe3e4aSElliott Hughes                log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
559*e1fe3e4aSElliott Hughes                ranges = sorted_ranges
560*e1fe3e4aSElliott Hughes            del sorted_ranges
561*e1fe3e4aSElliott Hughes            for r in ranges:
562*e1fe3e4aSElliott Hughes                start = r.Start
563*e1fe3e4aSElliott Hughes                end = r.End
564*e1fe3e4aSElliott Hughes                startID = font.getGlyphID(start)
565*e1fe3e4aSElliott Hughes                endID = font.getGlyphID(end) + 1
566*e1fe3e4aSElliott Hughes                glyphs.extend(font.getGlyphNameMany(range(startID, endID)))
567*e1fe3e4aSElliott Hughes        else:
568*e1fe3e4aSElliott Hughes            self.glyphs = []
569*e1fe3e4aSElliott Hughes            log.warning("Unknown Coverage format: %s", self.Format)
570*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
571*e1fe3e4aSElliott Hughes
572*e1fe3e4aSElliott Hughes    def preWrite(self, font):
573*e1fe3e4aSElliott Hughes        glyphs = getattr(self, "glyphs", None)
574*e1fe3e4aSElliott Hughes        if glyphs is None:
575*e1fe3e4aSElliott Hughes            glyphs = self.glyphs = []
576*e1fe3e4aSElliott Hughes        format = 1
577*e1fe3e4aSElliott Hughes        rawTable = {"GlyphArray": glyphs}
578*e1fe3e4aSElliott Hughes        if glyphs:
579*e1fe3e4aSElliott Hughes            # find out whether Format 2 is more compact or not
580*e1fe3e4aSElliott Hughes            glyphIDs = font.getGlyphIDMany(glyphs)
581*e1fe3e4aSElliott Hughes            brokenOrder = sorted(glyphIDs) != glyphIDs
582*e1fe3e4aSElliott Hughes
583*e1fe3e4aSElliott Hughes            last = glyphIDs[0]
584*e1fe3e4aSElliott Hughes            ranges = [[last]]
585*e1fe3e4aSElliott Hughes            for glyphID in glyphIDs[1:]:
586*e1fe3e4aSElliott Hughes                if glyphID != last + 1:
587*e1fe3e4aSElliott Hughes                    ranges[-1].append(last)
588*e1fe3e4aSElliott Hughes                    ranges.append([glyphID])
589*e1fe3e4aSElliott Hughes                last = glyphID
590*e1fe3e4aSElliott Hughes            ranges[-1].append(last)
591*e1fe3e4aSElliott Hughes
592*e1fe3e4aSElliott Hughes            if brokenOrder or len(ranges) * 3 < len(glyphs):  # 3 words vs. 1 word
593*e1fe3e4aSElliott Hughes                # Format 2 is more compact
594*e1fe3e4aSElliott Hughes                index = 0
595*e1fe3e4aSElliott Hughes                for i in range(len(ranges)):
596*e1fe3e4aSElliott Hughes                    start, end = ranges[i]
597*e1fe3e4aSElliott Hughes                    r = RangeRecord()
598*e1fe3e4aSElliott Hughes                    r.StartID = start
599*e1fe3e4aSElliott Hughes                    r.Start = font.getGlyphName(start)
600*e1fe3e4aSElliott Hughes                    r.End = font.getGlyphName(end)
601*e1fe3e4aSElliott Hughes                    r.StartCoverageIndex = index
602*e1fe3e4aSElliott Hughes                    ranges[i] = r
603*e1fe3e4aSElliott Hughes                    index = index + end - start + 1
604*e1fe3e4aSElliott Hughes                if brokenOrder:
605*e1fe3e4aSElliott Hughes                    log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
606*e1fe3e4aSElliott Hughes                    ranges.sort(key=lambda a: a.StartID)
607*e1fe3e4aSElliott Hughes                for r in ranges:
608*e1fe3e4aSElliott Hughes                    del r.StartID
609*e1fe3e4aSElliott Hughes                format = 2
610*e1fe3e4aSElliott Hughes                rawTable = {"RangeRecord": ranges}
611*e1fe3e4aSElliott Hughes            # else:
612*e1fe3e4aSElliott Hughes            # 	fallthrough; Format 1 is more compact
613*e1fe3e4aSElliott Hughes        self.Format = format
614*e1fe3e4aSElliott Hughes        return rawTable
615*e1fe3e4aSElliott Hughes
616*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
617*e1fe3e4aSElliott Hughes        for glyphName in getattr(self, "glyphs", []):
618*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Glyph", value=glyphName)
619*e1fe3e4aSElliott Hughes            xmlWriter.newline()
620*e1fe3e4aSElliott Hughes
621*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
622*e1fe3e4aSElliott Hughes        glyphs = getattr(self, "glyphs", None)
623*e1fe3e4aSElliott Hughes        if glyphs is None:
624*e1fe3e4aSElliott Hughes            glyphs = []
625*e1fe3e4aSElliott Hughes            self.glyphs = glyphs
626*e1fe3e4aSElliott Hughes        glyphs.append(attrs["value"])
627*e1fe3e4aSElliott Hughes
628*e1fe3e4aSElliott Hughes
629*e1fe3e4aSElliott Hughes# The special 0xFFFFFFFF delta-set index is used to indicate that there
630*e1fe3e4aSElliott Hughes# is no variation data in the ItemVariationStore for a given variable field
631*e1fe3e4aSElliott HughesNO_VARIATION_INDEX = 0xFFFFFFFF
632*e1fe3e4aSElliott Hughes
633*e1fe3e4aSElliott Hughes
634*e1fe3e4aSElliott Hughesclass DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
635*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
636*e1fe3e4aSElliott Hughes        if not hasattr(self, "mapping"):
637*e1fe3e4aSElliott Hughes            self.mapping = []
638*e1fe3e4aSElliott Hughes
639*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
640*e1fe3e4aSElliott Hughes        assert (rawTable["EntryFormat"] & 0xFFC0) == 0
641*e1fe3e4aSElliott Hughes        self.mapping = rawTable["mapping"]
642*e1fe3e4aSElliott Hughes
643*e1fe3e4aSElliott Hughes    @staticmethod
644*e1fe3e4aSElliott Hughes    def getEntryFormat(mapping):
645*e1fe3e4aSElliott Hughes        ored = 0
646*e1fe3e4aSElliott Hughes        for idx in mapping:
647*e1fe3e4aSElliott Hughes            ored |= idx
648*e1fe3e4aSElliott Hughes
649*e1fe3e4aSElliott Hughes        inner = ored & 0xFFFF
650*e1fe3e4aSElliott Hughes        innerBits = 0
651*e1fe3e4aSElliott Hughes        while inner:
652*e1fe3e4aSElliott Hughes            innerBits += 1
653*e1fe3e4aSElliott Hughes            inner >>= 1
654*e1fe3e4aSElliott Hughes        innerBits = max(innerBits, 1)
655*e1fe3e4aSElliott Hughes        assert innerBits <= 16
656*e1fe3e4aSElliott Hughes
657*e1fe3e4aSElliott Hughes        ored = (ored >> (16 - innerBits)) | (ored & ((1 << innerBits) - 1))
658*e1fe3e4aSElliott Hughes        if ored <= 0x000000FF:
659*e1fe3e4aSElliott Hughes            entrySize = 1
660*e1fe3e4aSElliott Hughes        elif ored <= 0x0000FFFF:
661*e1fe3e4aSElliott Hughes            entrySize = 2
662*e1fe3e4aSElliott Hughes        elif ored <= 0x00FFFFFF:
663*e1fe3e4aSElliott Hughes            entrySize = 3
664*e1fe3e4aSElliott Hughes        else:
665*e1fe3e4aSElliott Hughes            entrySize = 4
666*e1fe3e4aSElliott Hughes
667*e1fe3e4aSElliott Hughes        return ((entrySize - 1) << 4) | (innerBits - 1)
668*e1fe3e4aSElliott Hughes
669*e1fe3e4aSElliott Hughes    def preWrite(self, font):
670*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
671*e1fe3e4aSElliott Hughes        if mapping is None:
672*e1fe3e4aSElliott Hughes            mapping = self.mapping = []
673*e1fe3e4aSElliott Hughes        self.Format = 1 if len(mapping) > 0xFFFF else 0
674*e1fe3e4aSElliott Hughes        rawTable = self.__dict__.copy()
675*e1fe3e4aSElliott Hughes        rawTable["MappingCount"] = len(mapping)
676*e1fe3e4aSElliott Hughes        rawTable["EntryFormat"] = self.getEntryFormat(mapping)
677*e1fe3e4aSElliott Hughes        return rawTable
678*e1fe3e4aSElliott Hughes
679*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
680*e1fe3e4aSElliott Hughes        # Make xml dump less verbose, by omitting no-op entries like:
681*e1fe3e4aSElliott Hughes        #   <Map index="..." outer="65535" inner="65535"/>
682*e1fe3e4aSElliott Hughes        xmlWriter.comment("Omitted values default to 0xFFFF/0xFFFF (no variations)")
683*e1fe3e4aSElliott Hughes        xmlWriter.newline()
684*e1fe3e4aSElliott Hughes        for i, value in enumerate(getattr(self, "mapping", [])):
685*e1fe3e4aSElliott Hughes            attrs = [("index", i)]
686*e1fe3e4aSElliott Hughes            if value != NO_VARIATION_INDEX:
687*e1fe3e4aSElliott Hughes                attrs.extend(
688*e1fe3e4aSElliott Hughes                    [
689*e1fe3e4aSElliott Hughes                        ("outer", value >> 16),
690*e1fe3e4aSElliott Hughes                        ("inner", value & 0xFFFF),
691*e1fe3e4aSElliott Hughes                    ]
692*e1fe3e4aSElliott Hughes                )
693*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Map", attrs)
694*e1fe3e4aSElliott Hughes            xmlWriter.newline()
695*e1fe3e4aSElliott Hughes
696*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
697*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
698*e1fe3e4aSElliott Hughes        if mapping is None:
699*e1fe3e4aSElliott Hughes            self.mapping = mapping = []
700*e1fe3e4aSElliott Hughes        index = safeEval(attrs["index"])
701*e1fe3e4aSElliott Hughes        outer = safeEval(attrs.get("outer", "0xFFFF"))
702*e1fe3e4aSElliott Hughes        inner = safeEval(attrs.get("inner", "0xFFFF"))
703*e1fe3e4aSElliott Hughes        assert inner <= 0xFFFF
704*e1fe3e4aSElliott Hughes        mapping.insert(index, (outer << 16) | inner)
705*e1fe3e4aSElliott Hughes
706*e1fe3e4aSElliott Hughes
707*e1fe3e4aSElliott Hughesclass VarIdxMap(BaseTable):
708*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
709*e1fe3e4aSElliott Hughes        if not hasattr(self, "mapping"):
710*e1fe3e4aSElliott Hughes            self.mapping = {}
711*e1fe3e4aSElliott Hughes
712*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
713*e1fe3e4aSElliott Hughes        assert (rawTable["EntryFormat"] & 0xFFC0) == 0
714*e1fe3e4aSElliott Hughes        glyphOrder = font.getGlyphOrder()
715*e1fe3e4aSElliott Hughes        mapList = rawTable["mapping"]
716*e1fe3e4aSElliott Hughes        mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList)))
717*e1fe3e4aSElliott Hughes        self.mapping = dict(zip(glyphOrder, mapList))
718*e1fe3e4aSElliott Hughes
719*e1fe3e4aSElliott Hughes    def preWrite(self, font):
720*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
721*e1fe3e4aSElliott Hughes        if mapping is None:
722*e1fe3e4aSElliott Hughes            mapping = self.mapping = {}
723*e1fe3e4aSElliott Hughes
724*e1fe3e4aSElliott Hughes        glyphOrder = font.getGlyphOrder()
725*e1fe3e4aSElliott Hughes        mapping = [mapping[g] for g in glyphOrder]
726*e1fe3e4aSElliott Hughes        while len(mapping) > 1 and mapping[-2] == mapping[-1]:
727*e1fe3e4aSElliott Hughes            del mapping[-1]
728*e1fe3e4aSElliott Hughes
729*e1fe3e4aSElliott Hughes        rawTable = {"mapping": mapping}
730*e1fe3e4aSElliott Hughes        rawTable["MappingCount"] = len(mapping)
731*e1fe3e4aSElliott Hughes        rawTable["EntryFormat"] = DeltaSetIndexMap.getEntryFormat(mapping)
732*e1fe3e4aSElliott Hughes        return rawTable
733*e1fe3e4aSElliott Hughes
734*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
735*e1fe3e4aSElliott Hughes        for glyph, value in sorted(getattr(self, "mapping", {}).items()):
736*e1fe3e4aSElliott Hughes            attrs = (
737*e1fe3e4aSElliott Hughes                ("glyph", glyph),
738*e1fe3e4aSElliott Hughes                ("outer", value >> 16),
739*e1fe3e4aSElliott Hughes                ("inner", value & 0xFFFF),
740*e1fe3e4aSElliott Hughes            )
741*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Map", attrs)
742*e1fe3e4aSElliott Hughes            xmlWriter.newline()
743*e1fe3e4aSElliott Hughes
744*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
745*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
746*e1fe3e4aSElliott Hughes        if mapping is None:
747*e1fe3e4aSElliott Hughes            mapping = {}
748*e1fe3e4aSElliott Hughes            self.mapping = mapping
749*e1fe3e4aSElliott Hughes        try:
750*e1fe3e4aSElliott Hughes            glyph = attrs["glyph"]
751*e1fe3e4aSElliott Hughes        except:  # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
752*e1fe3e4aSElliott Hughes            glyph = font.getGlyphOrder()[attrs["index"]]
753*e1fe3e4aSElliott Hughes        outer = safeEval(attrs["outer"])
754*e1fe3e4aSElliott Hughes        inner = safeEval(attrs["inner"])
755*e1fe3e4aSElliott Hughes        assert inner <= 0xFFFF
756*e1fe3e4aSElliott Hughes        mapping[glyph] = (outer << 16) | inner
757*e1fe3e4aSElliott Hughes
758*e1fe3e4aSElliott Hughes
759*e1fe3e4aSElliott Hughesclass VarRegionList(BaseTable):
760*e1fe3e4aSElliott Hughes    def preWrite(self, font):
761*e1fe3e4aSElliott Hughes        # The OT spec says VarStore.VarRegionList.RegionAxisCount should always
762*e1fe3e4aSElliott Hughes        # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
763*e1fe3e4aSElliott Hughes        # even when the VarRegionList is empty. We can't treat RegionAxisCount
764*e1fe3e4aSElliott Hughes        # like a normal propagated count (== len(Region[i].VarRegionAxis)),
765*e1fe3e4aSElliott Hughes        # otherwise it would default to 0 if VarRegionList is empty.
766*e1fe3e4aSElliott Hughes        # Thus, we force it to always be equal to fvar.axisCount.
767*e1fe3e4aSElliott Hughes        # https://github.com/khaledhosny/ots/pull/192
768*e1fe3e4aSElliott Hughes        fvarTable = font.get("fvar")
769*e1fe3e4aSElliott Hughes        if fvarTable:
770*e1fe3e4aSElliott Hughes            self.RegionAxisCount = len(fvarTable.axes)
771*e1fe3e4aSElliott Hughes        return {
772*e1fe3e4aSElliott Hughes            **self.__dict__,
773*e1fe3e4aSElliott Hughes            "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount"),
774*e1fe3e4aSElliott Hughes        }
775*e1fe3e4aSElliott Hughes
776*e1fe3e4aSElliott Hughes
777*e1fe3e4aSElliott Hughesclass SingleSubst(FormatSwitchingBaseTable):
778*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
779*e1fe3e4aSElliott Hughes        if not hasattr(self, "mapping"):
780*e1fe3e4aSElliott Hughes            self.mapping = {}
781*e1fe3e4aSElliott Hughes
782*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
783*e1fe3e4aSElliott Hughes        mapping = {}
784*e1fe3e4aSElliott Hughes        input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
785*e1fe3e4aSElliott Hughes        if self.Format == 1:
786*e1fe3e4aSElliott Hughes            delta = rawTable["DeltaGlyphID"]
787*e1fe3e4aSElliott Hughes            inputGIDS = font.getGlyphIDMany(input)
788*e1fe3e4aSElliott Hughes            outGIDS = [(glyphID + delta) % 65536 for glyphID in inputGIDS]
789*e1fe3e4aSElliott Hughes            outNames = font.getGlyphNameMany(outGIDS)
790*e1fe3e4aSElliott Hughes            for inp, out in zip(input, outNames):
791*e1fe3e4aSElliott Hughes                mapping[inp] = out
792*e1fe3e4aSElliott Hughes        elif self.Format == 2:
793*e1fe3e4aSElliott Hughes            assert (
794*e1fe3e4aSElliott Hughes                len(input) == rawTable["GlyphCount"]
795*e1fe3e4aSElliott Hughes            ), "invalid SingleSubstFormat2 table"
796*e1fe3e4aSElliott Hughes            subst = rawTable["Substitute"]
797*e1fe3e4aSElliott Hughes            for inp, sub in zip(input, subst):
798*e1fe3e4aSElliott Hughes                mapping[inp] = sub
799*e1fe3e4aSElliott Hughes        else:
800*e1fe3e4aSElliott Hughes            assert 0, "unknown format: %s" % self.Format
801*e1fe3e4aSElliott Hughes        self.mapping = mapping
802*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
803*e1fe3e4aSElliott Hughes
804*e1fe3e4aSElliott Hughes    def preWrite(self, font):
805*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
806*e1fe3e4aSElliott Hughes        if mapping is None:
807*e1fe3e4aSElliott Hughes            mapping = self.mapping = {}
808*e1fe3e4aSElliott Hughes        items = list(mapping.items())
809*e1fe3e4aSElliott Hughes        getGlyphID = font.getGlyphID
810*e1fe3e4aSElliott Hughes        gidItems = [(getGlyphID(a), getGlyphID(b)) for a, b in items]
811*e1fe3e4aSElliott Hughes        sortableItems = sorted(zip(gidItems, items))
812*e1fe3e4aSElliott Hughes
813*e1fe3e4aSElliott Hughes        # figure out format
814*e1fe3e4aSElliott Hughes        format = 2
815*e1fe3e4aSElliott Hughes        delta = None
816*e1fe3e4aSElliott Hughes        for inID, outID in gidItems:
817*e1fe3e4aSElliott Hughes            if delta is None:
818*e1fe3e4aSElliott Hughes                delta = (outID - inID) % 65536
819*e1fe3e4aSElliott Hughes
820*e1fe3e4aSElliott Hughes            if (inID + delta) % 65536 != outID:
821*e1fe3e4aSElliott Hughes                break
822*e1fe3e4aSElliott Hughes        else:
823*e1fe3e4aSElliott Hughes            if delta is None:
824*e1fe3e4aSElliott Hughes                # the mapping is empty, better use format 2
825*e1fe3e4aSElliott Hughes                format = 2
826*e1fe3e4aSElliott Hughes            else:
827*e1fe3e4aSElliott Hughes                format = 1
828*e1fe3e4aSElliott Hughes
829*e1fe3e4aSElliott Hughes        rawTable = {}
830*e1fe3e4aSElliott Hughes        self.Format = format
831*e1fe3e4aSElliott Hughes        cov = Coverage()
832*e1fe3e4aSElliott Hughes        input = [item[1][0] for item in sortableItems]
833*e1fe3e4aSElliott Hughes        subst = [item[1][1] for item in sortableItems]
834*e1fe3e4aSElliott Hughes        cov.glyphs = input
835*e1fe3e4aSElliott Hughes        rawTable["Coverage"] = cov
836*e1fe3e4aSElliott Hughes        if format == 1:
837*e1fe3e4aSElliott Hughes            assert delta is not None
838*e1fe3e4aSElliott Hughes            rawTable["DeltaGlyphID"] = delta
839*e1fe3e4aSElliott Hughes        else:
840*e1fe3e4aSElliott Hughes            rawTable["Substitute"] = subst
841*e1fe3e4aSElliott Hughes        return rawTable
842*e1fe3e4aSElliott Hughes
843*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
844*e1fe3e4aSElliott Hughes        items = sorted(self.mapping.items())
845*e1fe3e4aSElliott Hughes        for inGlyph, outGlyph in items:
846*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", outGlyph)])
847*e1fe3e4aSElliott Hughes            xmlWriter.newline()
848*e1fe3e4aSElliott Hughes
849*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
850*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
851*e1fe3e4aSElliott Hughes        if mapping is None:
852*e1fe3e4aSElliott Hughes            mapping = {}
853*e1fe3e4aSElliott Hughes            self.mapping = mapping
854*e1fe3e4aSElliott Hughes        mapping[attrs["in"]] = attrs["out"]
855*e1fe3e4aSElliott Hughes
856*e1fe3e4aSElliott Hughes
857*e1fe3e4aSElliott Hughesclass MultipleSubst(FormatSwitchingBaseTable):
858*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
859*e1fe3e4aSElliott Hughes        if not hasattr(self, "mapping"):
860*e1fe3e4aSElliott Hughes            self.mapping = {}
861*e1fe3e4aSElliott Hughes
862*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
863*e1fe3e4aSElliott Hughes        mapping = {}
864*e1fe3e4aSElliott Hughes        if self.Format == 1:
865*e1fe3e4aSElliott Hughes            glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"])
866*e1fe3e4aSElliott Hughes            subst = [s.Substitute for s in rawTable["Sequence"]]
867*e1fe3e4aSElliott Hughes            mapping = dict(zip(glyphs, subst))
868*e1fe3e4aSElliott Hughes        else:
869*e1fe3e4aSElliott Hughes            assert 0, "unknown format: %s" % self.Format
870*e1fe3e4aSElliott Hughes        self.mapping = mapping
871*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
872*e1fe3e4aSElliott Hughes
873*e1fe3e4aSElliott Hughes    def preWrite(self, font):
874*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
875*e1fe3e4aSElliott Hughes        if mapping is None:
876*e1fe3e4aSElliott Hughes            mapping = self.mapping = {}
877*e1fe3e4aSElliott Hughes        cov = Coverage()
878*e1fe3e4aSElliott Hughes        cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID)
879*e1fe3e4aSElliott Hughes        self.Format = 1
880*e1fe3e4aSElliott Hughes        rawTable = {
881*e1fe3e4aSElliott Hughes            "Coverage": cov,
882*e1fe3e4aSElliott Hughes            "Sequence": [self.makeSequence_(mapping[glyph]) for glyph in cov.glyphs],
883*e1fe3e4aSElliott Hughes        }
884*e1fe3e4aSElliott Hughes        return rawTable
885*e1fe3e4aSElliott Hughes
886*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
887*e1fe3e4aSElliott Hughes        items = sorted(self.mapping.items())
888*e1fe3e4aSElliott Hughes        for inGlyph, outGlyphs in items:
889*e1fe3e4aSElliott Hughes            out = ",".join(outGlyphs)
890*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", out)])
891*e1fe3e4aSElliott Hughes            xmlWriter.newline()
892*e1fe3e4aSElliott Hughes
893*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
894*e1fe3e4aSElliott Hughes        mapping = getattr(self, "mapping", None)
895*e1fe3e4aSElliott Hughes        if mapping is None:
896*e1fe3e4aSElliott Hughes            mapping = {}
897*e1fe3e4aSElliott Hughes            self.mapping = mapping
898*e1fe3e4aSElliott Hughes
899*e1fe3e4aSElliott Hughes        # TTX v3.0 and earlier.
900*e1fe3e4aSElliott Hughes        if name == "Coverage":
901*e1fe3e4aSElliott Hughes            self.old_coverage_ = []
902*e1fe3e4aSElliott Hughes            for element in content:
903*e1fe3e4aSElliott Hughes                if not isinstance(element, tuple):
904*e1fe3e4aSElliott Hughes                    continue
905*e1fe3e4aSElliott Hughes                element_name, element_attrs, _ = element
906*e1fe3e4aSElliott Hughes                if element_name == "Glyph":
907*e1fe3e4aSElliott Hughes                    self.old_coverage_.append(element_attrs["value"])
908*e1fe3e4aSElliott Hughes            return
909*e1fe3e4aSElliott Hughes        if name == "Sequence":
910*e1fe3e4aSElliott Hughes            index = int(attrs.get("index", len(mapping)))
911*e1fe3e4aSElliott Hughes            glyph = self.old_coverage_[index]
912*e1fe3e4aSElliott Hughes            glyph_mapping = mapping[glyph] = []
913*e1fe3e4aSElliott Hughes            for element in content:
914*e1fe3e4aSElliott Hughes                if not isinstance(element, tuple):
915*e1fe3e4aSElliott Hughes                    continue
916*e1fe3e4aSElliott Hughes                element_name, element_attrs, _ = element
917*e1fe3e4aSElliott Hughes                if element_name == "Substitute":
918*e1fe3e4aSElliott Hughes                    glyph_mapping.append(element_attrs["value"])
919*e1fe3e4aSElliott Hughes            return
920*e1fe3e4aSElliott Hughes
921*e1fe3e4aSElliott Hughes            # TTX v3.1 and later.
922*e1fe3e4aSElliott Hughes        outGlyphs = attrs["out"].split(",") if attrs["out"] else []
923*e1fe3e4aSElliott Hughes        mapping[attrs["in"]] = [g.strip() for g in outGlyphs]
924*e1fe3e4aSElliott Hughes
925*e1fe3e4aSElliott Hughes    @staticmethod
926*e1fe3e4aSElliott Hughes    def makeSequence_(g):
927*e1fe3e4aSElliott Hughes        seq = Sequence()
928*e1fe3e4aSElliott Hughes        seq.Substitute = g
929*e1fe3e4aSElliott Hughes        return seq
930*e1fe3e4aSElliott Hughes
931*e1fe3e4aSElliott Hughes
932*e1fe3e4aSElliott Hughesclass ClassDef(FormatSwitchingBaseTable):
933*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
934*e1fe3e4aSElliott Hughes        if not hasattr(self, "classDefs"):
935*e1fe3e4aSElliott Hughes            self.classDefs = {}
936*e1fe3e4aSElliott Hughes
937*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
938*e1fe3e4aSElliott Hughes        classDefs = {}
939*e1fe3e4aSElliott Hughes
940*e1fe3e4aSElliott Hughes        if self.Format == 1:
941*e1fe3e4aSElliott Hughes            start = rawTable["StartGlyph"]
942*e1fe3e4aSElliott Hughes            classList = rawTable["ClassValueArray"]
943*e1fe3e4aSElliott Hughes            startID = font.getGlyphID(start)
944*e1fe3e4aSElliott Hughes            endID = startID + len(classList)
945*e1fe3e4aSElliott Hughes            glyphNames = font.getGlyphNameMany(range(startID, endID))
946*e1fe3e4aSElliott Hughes            for glyphName, cls in zip(glyphNames, classList):
947*e1fe3e4aSElliott Hughes                if cls:
948*e1fe3e4aSElliott Hughes                    classDefs[glyphName] = cls
949*e1fe3e4aSElliott Hughes
950*e1fe3e4aSElliott Hughes        elif self.Format == 2:
951*e1fe3e4aSElliott Hughes            records = rawTable["ClassRangeRecord"]
952*e1fe3e4aSElliott Hughes            for rec in records:
953*e1fe3e4aSElliott Hughes                cls = rec.Class
954*e1fe3e4aSElliott Hughes                if not cls:
955*e1fe3e4aSElliott Hughes                    continue
956*e1fe3e4aSElliott Hughes                start = rec.Start
957*e1fe3e4aSElliott Hughes                end = rec.End
958*e1fe3e4aSElliott Hughes                startID = font.getGlyphID(start)
959*e1fe3e4aSElliott Hughes                endID = font.getGlyphID(end) + 1
960*e1fe3e4aSElliott Hughes                glyphNames = font.getGlyphNameMany(range(startID, endID))
961*e1fe3e4aSElliott Hughes                for glyphName in glyphNames:
962*e1fe3e4aSElliott Hughes                    classDefs[glyphName] = cls
963*e1fe3e4aSElliott Hughes        else:
964*e1fe3e4aSElliott Hughes            log.warning("Unknown ClassDef format: %s", self.Format)
965*e1fe3e4aSElliott Hughes        self.classDefs = classDefs
966*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
967*e1fe3e4aSElliott Hughes
968*e1fe3e4aSElliott Hughes    def _getClassRanges(self, font):
969*e1fe3e4aSElliott Hughes        classDefs = getattr(self, "classDefs", None)
970*e1fe3e4aSElliott Hughes        if classDefs is None:
971*e1fe3e4aSElliott Hughes            self.classDefs = {}
972*e1fe3e4aSElliott Hughes            return
973*e1fe3e4aSElliott Hughes        getGlyphID = font.getGlyphID
974*e1fe3e4aSElliott Hughes        items = []
975*e1fe3e4aSElliott Hughes        for glyphName, cls in classDefs.items():
976*e1fe3e4aSElliott Hughes            if not cls:
977*e1fe3e4aSElliott Hughes                continue
978*e1fe3e4aSElliott Hughes            items.append((getGlyphID(glyphName), glyphName, cls))
979*e1fe3e4aSElliott Hughes        if items:
980*e1fe3e4aSElliott Hughes            items.sort()
981*e1fe3e4aSElliott Hughes            last, lastName, lastCls = items[0]
982*e1fe3e4aSElliott Hughes            ranges = [[lastCls, last, lastName]]
983*e1fe3e4aSElliott Hughes            for glyphID, glyphName, cls in items[1:]:
984*e1fe3e4aSElliott Hughes                if glyphID != last + 1 or cls != lastCls:
985*e1fe3e4aSElliott Hughes                    ranges[-1].extend([last, lastName])
986*e1fe3e4aSElliott Hughes                    ranges.append([cls, glyphID, glyphName])
987*e1fe3e4aSElliott Hughes                last = glyphID
988*e1fe3e4aSElliott Hughes                lastName = glyphName
989*e1fe3e4aSElliott Hughes                lastCls = cls
990*e1fe3e4aSElliott Hughes            ranges[-1].extend([last, lastName])
991*e1fe3e4aSElliott Hughes            return ranges
992*e1fe3e4aSElliott Hughes
993*e1fe3e4aSElliott Hughes    def preWrite(self, font):
994*e1fe3e4aSElliott Hughes        format = 2
995*e1fe3e4aSElliott Hughes        rawTable = {"ClassRangeRecord": []}
996*e1fe3e4aSElliott Hughes        ranges = self._getClassRanges(font)
997*e1fe3e4aSElliott Hughes        if ranges:
998*e1fe3e4aSElliott Hughes            startGlyph = ranges[0][1]
999*e1fe3e4aSElliott Hughes            endGlyph = ranges[-1][3]
1000*e1fe3e4aSElliott Hughes            glyphCount = endGlyph - startGlyph + 1
1001*e1fe3e4aSElliott Hughes            if len(ranges) * 3 < glyphCount + 1:
1002*e1fe3e4aSElliott Hughes                # Format 2 is more compact
1003*e1fe3e4aSElliott Hughes                for i in range(len(ranges)):
1004*e1fe3e4aSElliott Hughes                    cls, start, startName, end, endName = ranges[i]
1005*e1fe3e4aSElliott Hughes                    rec = ClassRangeRecord()
1006*e1fe3e4aSElliott Hughes                    rec.Start = startName
1007*e1fe3e4aSElliott Hughes                    rec.End = endName
1008*e1fe3e4aSElliott Hughes                    rec.Class = cls
1009*e1fe3e4aSElliott Hughes                    ranges[i] = rec
1010*e1fe3e4aSElliott Hughes                format = 2
1011*e1fe3e4aSElliott Hughes                rawTable = {"ClassRangeRecord": ranges}
1012*e1fe3e4aSElliott Hughes            else:
1013*e1fe3e4aSElliott Hughes                # Format 1 is more compact
1014*e1fe3e4aSElliott Hughes                startGlyphName = ranges[0][2]
1015*e1fe3e4aSElliott Hughes                classes = [0] * glyphCount
1016*e1fe3e4aSElliott Hughes                for cls, start, startName, end, endName in ranges:
1017*e1fe3e4aSElliott Hughes                    for g in range(start - startGlyph, end - startGlyph + 1):
1018*e1fe3e4aSElliott Hughes                        classes[g] = cls
1019*e1fe3e4aSElliott Hughes                format = 1
1020*e1fe3e4aSElliott Hughes                rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes}
1021*e1fe3e4aSElliott Hughes        self.Format = format
1022*e1fe3e4aSElliott Hughes        return rawTable
1023*e1fe3e4aSElliott Hughes
1024*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
1025*e1fe3e4aSElliott Hughes        items = sorted(self.classDefs.items())
1026*e1fe3e4aSElliott Hughes        for glyphName, cls in items:
1027*e1fe3e4aSElliott Hughes            xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
1028*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1029*e1fe3e4aSElliott Hughes
1030*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
1031*e1fe3e4aSElliott Hughes        classDefs = getattr(self, "classDefs", None)
1032*e1fe3e4aSElliott Hughes        if classDefs is None:
1033*e1fe3e4aSElliott Hughes            classDefs = {}
1034*e1fe3e4aSElliott Hughes            self.classDefs = classDefs
1035*e1fe3e4aSElliott Hughes        classDefs[attrs["glyph"]] = int(attrs["class"])
1036*e1fe3e4aSElliott Hughes
1037*e1fe3e4aSElliott Hughes
1038*e1fe3e4aSElliott Hughesclass AlternateSubst(FormatSwitchingBaseTable):
1039*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
1040*e1fe3e4aSElliott Hughes        if not hasattr(self, "alternates"):
1041*e1fe3e4aSElliott Hughes            self.alternates = {}
1042*e1fe3e4aSElliott Hughes
1043*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
1044*e1fe3e4aSElliott Hughes        alternates = {}
1045*e1fe3e4aSElliott Hughes        if self.Format == 1:
1046*e1fe3e4aSElliott Hughes            input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1047*e1fe3e4aSElliott Hughes            alts = rawTable["AlternateSet"]
1048*e1fe3e4aSElliott Hughes            assert len(input) == len(alts)
1049*e1fe3e4aSElliott Hughes            for inp, alt in zip(input, alts):
1050*e1fe3e4aSElliott Hughes                alternates[inp] = alt.Alternate
1051*e1fe3e4aSElliott Hughes        else:
1052*e1fe3e4aSElliott Hughes            assert 0, "unknown format: %s" % self.Format
1053*e1fe3e4aSElliott Hughes        self.alternates = alternates
1054*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
1055*e1fe3e4aSElliott Hughes
1056*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1057*e1fe3e4aSElliott Hughes        self.Format = 1
1058*e1fe3e4aSElliott Hughes        alternates = getattr(self, "alternates", None)
1059*e1fe3e4aSElliott Hughes        if alternates is None:
1060*e1fe3e4aSElliott Hughes            alternates = self.alternates = {}
1061*e1fe3e4aSElliott Hughes        items = list(alternates.items())
1062*e1fe3e4aSElliott Hughes        for i in range(len(items)):
1063*e1fe3e4aSElliott Hughes            glyphName, set = items[i]
1064*e1fe3e4aSElliott Hughes            items[i] = font.getGlyphID(glyphName), glyphName, set
1065*e1fe3e4aSElliott Hughes        items.sort()
1066*e1fe3e4aSElliott Hughes        cov = Coverage()
1067*e1fe3e4aSElliott Hughes        cov.glyphs = [item[1] for item in items]
1068*e1fe3e4aSElliott Hughes        alternates = []
1069*e1fe3e4aSElliott Hughes        setList = [item[-1] for item in items]
1070*e1fe3e4aSElliott Hughes        for set in setList:
1071*e1fe3e4aSElliott Hughes            alts = AlternateSet()
1072*e1fe3e4aSElliott Hughes            alts.Alternate = set
1073*e1fe3e4aSElliott Hughes            alternates.append(alts)
1074*e1fe3e4aSElliott Hughes        # a special case to deal with the fact that several hundred Adobe Japan1-5
1075*e1fe3e4aSElliott Hughes        # CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
1076*e1fe3e4aSElliott Hughes        # Also useful in that when splitting a sub-table because of an offset overflow
1077*e1fe3e4aSElliott Hughes        # I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
1078*e1fe3e4aSElliott Hughes        # Allows packing more rules in subtable.
1079*e1fe3e4aSElliott Hughes        self.sortCoverageLast = 1
1080*e1fe3e4aSElliott Hughes        return {"Coverage": cov, "AlternateSet": alternates}
1081*e1fe3e4aSElliott Hughes
1082*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
1083*e1fe3e4aSElliott Hughes        items = sorted(self.alternates.items())
1084*e1fe3e4aSElliott Hughes        for glyphName, alternates in items:
1085*e1fe3e4aSElliott Hughes            xmlWriter.begintag("AlternateSet", glyph=glyphName)
1086*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1087*e1fe3e4aSElliott Hughes            for alt in alternates:
1088*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("Alternate", glyph=alt)
1089*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1090*e1fe3e4aSElliott Hughes            xmlWriter.endtag("AlternateSet")
1091*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1092*e1fe3e4aSElliott Hughes
1093*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
1094*e1fe3e4aSElliott Hughes        alternates = getattr(self, "alternates", None)
1095*e1fe3e4aSElliott Hughes        if alternates is None:
1096*e1fe3e4aSElliott Hughes            alternates = {}
1097*e1fe3e4aSElliott Hughes            self.alternates = alternates
1098*e1fe3e4aSElliott Hughes        glyphName = attrs["glyph"]
1099*e1fe3e4aSElliott Hughes        set = []
1100*e1fe3e4aSElliott Hughes        alternates[glyphName] = set
1101*e1fe3e4aSElliott Hughes        for element in content:
1102*e1fe3e4aSElliott Hughes            if not isinstance(element, tuple):
1103*e1fe3e4aSElliott Hughes                continue
1104*e1fe3e4aSElliott Hughes            name, attrs, content = element
1105*e1fe3e4aSElliott Hughes            set.append(attrs["glyph"])
1106*e1fe3e4aSElliott Hughes
1107*e1fe3e4aSElliott Hughes
1108*e1fe3e4aSElliott Hughesclass LigatureSubst(FormatSwitchingBaseTable):
1109*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
1110*e1fe3e4aSElliott Hughes        if not hasattr(self, "ligatures"):
1111*e1fe3e4aSElliott Hughes            self.ligatures = {}
1112*e1fe3e4aSElliott Hughes
1113*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
1114*e1fe3e4aSElliott Hughes        ligatures = {}
1115*e1fe3e4aSElliott Hughes        if self.Format == 1:
1116*e1fe3e4aSElliott Hughes            input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
1117*e1fe3e4aSElliott Hughes            ligSets = rawTable["LigatureSet"]
1118*e1fe3e4aSElliott Hughes            assert len(input) == len(ligSets)
1119*e1fe3e4aSElliott Hughes            for i in range(len(input)):
1120*e1fe3e4aSElliott Hughes                ligatures[input[i]] = ligSets[i].Ligature
1121*e1fe3e4aSElliott Hughes        else:
1122*e1fe3e4aSElliott Hughes            assert 0, "unknown format: %s" % self.Format
1123*e1fe3e4aSElliott Hughes        self.ligatures = ligatures
1124*e1fe3e4aSElliott Hughes        del self.Format  # Don't need this anymore
1125*e1fe3e4aSElliott Hughes
1126*e1fe3e4aSElliott Hughes    @staticmethod
1127*e1fe3e4aSElliott Hughes    def _getLigatureSortKey(components):
1128*e1fe3e4aSElliott Hughes        # Computes a key for ordering ligatures in a GSUB Type-4 lookup.
1129*e1fe3e4aSElliott Hughes
1130*e1fe3e4aSElliott Hughes        # When building the OpenType lookup, we need to make sure that
1131*e1fe3e4aSElliott Hughes        # the longest sequence of components is listed first, so we
1132*e1fe3e4aSElliott Hughes        # use the negative length as the key for sorting.
1133*e1fe3e4aSElliott Hughes        # Note, we no longer need to worry about deterministic order because the
1134*e1fe3e4aSElliott Hughes        # ligature mapping `dict` remembers the insertion order, and this in
1135*e1fe3e4aSElliott Hughes        # turn depends on the order in which the ligatures are written in the FEA.
1136*e1fe3e4aSElliott Hughes        # Since python sort algorithm is stable, the ligatures of equal length
1137*e1fe3e4aSElliott Hughes        # will keep the relative order in which they appear in the feature file.
1138*e1fe3e4aSElliott Hughes        # For example, given the following ligatures (all starting with 'f' and
1139*e1fe3e4aSElliott Hughes        # thus belonging to the same LigatureSet):
1140*e1fe3e4aSElliott Hughes        #
1141*e1fe3e4aSElliott Hughes        #   feature liga {
1142*e1fe3e4aSElliott Hughes        #     sub f i by f_i;
1143*e1fe3e4aSElliott Hughes        #     sub f f f by f_f_f;
1144*e1fe3e4aSElliott Hughes        #     sub f f by f_f;
1145*e1fe3e4aSElliott Hughes        #     sub f f i by f_f_i;
1146*e1fe3e4aSElliott Hughes        #   } liga;
1147*e1fe3e4aSElliott Hughes        #
1148*e1fe3e4aSElliott Hughes        # this should sort to: f_f_f, f_f_i, f_i, f_f
1149*e1fe3e4aSElliott Hughes        # This is also what fea-rs does, see:
1150*e1fe3e4aSElliott Hughes        # https://github.com/adobe-type-tools/afdko/issues/1727
1151*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/3428
1152*e1fe3e4aSElliott Hughes        # https://github.com/googlefonts/fontc/pull/680
1153*e1fe3e4aSElliott Hughes        return -len(components)
1154*e1fe3e4aSElliott Hughes
1155*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1156*e1fe3e4aSElliott Hughes        self.Format = 1
1157*e1fe3e4aSElliott Hughes        ligatures = getattr(self, "ligatures", None)
1158*e1fe3e4aSElliott Hughes        if ligatures is None:
1159*e1fe3e4aSElliott Hughes            ligatures = self.ligatures = {}
1160*e1fe3e4aSElliott Hughes
1161*e1fe3e4aSElliott Hughes        if ligatures and isinstance(next(iter(ligatures)), tuple):
1162*e1fe3e4aSElliott Hughes            # New high-level API in v3.1 and later.  Note that we just support compiling this
1163*e1fe3e4aSElliott Hughes            # for now.  We don't load to this API, and don't do XML with it.
1164*e1fe3e4aSElliott Hughes
1165*e1fe3e4aSElliott Hughes            # ligatures is map from components-sequence to lig-glyph
1166*e1fe3e4aSElliott Hughes            newLigatures = dict()
1167*e1fe3e4aSElliott Hughes            for comps in sorted(ligatures.keys(), key=self._getLigatureSortKey):
1168*e1fe3e4aSElliott Hughes                ligature = Ligature()
1169*e1fe3e4aSElliott Hughes                ligature.Component = comps[1:]
1170*e1fe3e4aSElliott Hughes                ligature.CompCount = len(comps)
1171*e1fe3e4aSElliott Hughes                ligature.LigGlyph = ligatures[comps]
1172*e1fe3e4aSElliott Hughes                newLigatures.setdefault(comps[0], []).append(ligature)
1173*e1fe3e4aSElliott Hughes            ligatures = newLigatures
1174*e1fe3e4aSElliott Hughes
1175*e1fe3e4aSElliott Hughes        items = list(ligatures.items())
1176*e1fe3e4aSElliott Hughes        for i in range(len(items)):
1177*e1fe3e4aSElliott Hughes            glyphName, set = items[i]
1178*e1fe3e4aSElliott Hughes            items[i] = font.getGlyphID(glyphName), glyphName, set
1179*e1fe3e4aSElliott Hughes        items.sort()
1180*e1fe3e4aSElliott Hughes        cov = Coverage()
1181*e1fe3e4aSElliott Hughes        cov.glyphs = [item[1] for item in items]
1182*e1fe3e4aSElliott Hughes
1183*e1fe3e4aSElliott Hughes        ligSets = []
1184*e1fe3e4aSElliott Hughes        setList = [item[-1] for item in items]
1185*e1fe3e4aSElliott Hughes        for set in setList:
1186*e1fe3e4aSElliott Hughes            ligSet = LigatureSet()
1187*e1fe3e4aSElliott Hughes            ligs = ligSet.Ligature = []
1188*e1fe3e4aSElliott Hughes            for lig in set:
1189*e1fe3e4aSElliott Hughes                ligs.append(lig)
1190*e1fe3e4aSElliott Hughes            ligSets.append(ligSet)
1191*e1fe3e4aSElliott Hughes        # Useful in that when splitting a sub-table because of an offset overflow
1192*e1fe3e4aSElliott Hughes        # I don't need to calculate the change in subtabl offset due to the coverage table size.
1193*e1fe3e4aSElliott Hughes        # Allows packing more rules in subtable.
1194*e1fe3e4aSElliott Hughes        self.sortCoverageLast = 1
1195*e1fe3e4aSElliott Hughes        return {"Coverage": cov, "LigatureSet": ligSets}
1196*e1fe3e4aSElliott Hughes
1197*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
1198*e1fe3e4aSElliott Hughes        items = sorted(self.ligatures.items())
1199*e1fe3e4aSElliott Hughes        for glyphName, ligSets in items:
1200*e1fe3e4aSElliott Hughes            xmlWriter.begintag("LigatureSet", glyph=glyphName)
1201*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1202*e1fe3e4aSElliott Hughes            for lig in ligSets:
1203*e1fe3e4aSElliott Hughes                xmlWriter.simpletag(
1204*e1fe3e4aSElliott Hughes                    "Ligature", glyph=lig.LigGlyph, components=",".join(lig.Component)
1205*e1fe3e4aSElliott Hughes                )
1206*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1207*e1fe3e4aSElliott Hughes            xmlWriter.endtag("LigatureSet")
1208*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1209*e1fe3e4aSElliott Hughes
1210*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
1211*e1fe3e4aSElliott Hughes        ligatures = getattr(self, "ligatures", None)
1212*e1fe3e4aSElliott Hughes        if ligatures is None:
1213*e1fe3e4aSElliott Hughes            ligatures = {}
1214*e1fe3e4aSElliott Hughes            self.ligatures = ligatures
1215*e1fe3e4aSElliott Hughes        glyphName = attrs["glyph"]
1216*e1fe3e4aSElliott Hughes        ligs = []
1217*e1fe3e4aSElliott Hughes        ligatures[glyphName] = ligs
1218*e1fe3e4aSElliott Hughes        for element in content:
1219*e1fe3e4aSElliott Hughes            if not isinstance(element, tuple):
1220*e1fe3e4aSElliott Hughes                continue
1221*e1fe3e4aSElliott Hughes            name, attrs, content = element
1222*e1fe3e4aSElliott Hughes            lig = Ligature()
1223*e1fe3e4aSElliott Hughes            lig.LigGlyph = attrs["glyph"]
1224*e1fe3e4aSElliott Hughes            components = attrs["components"]
1225*e1fe3e4aSElliott Hughes            lig.Component = components.split(",") if components else []
1226*e1fe3e4aSElliott Hughes            lig.CompCount = len(lig.Component)
1227*e1fe3e4aSElliott Hughes            ligs.append(lig)
1228*e1fe3e4aSElliott Hughes
1229*e1fe3e4aSElliott Hughes
1230*e1fe3e4aSElliott Hughesclass COLR(BaseTable):
1231*e1fe3e4aSElliott Hughes    def decompile(self, reader, font):
1232*e1fe3e4aSElliott Hughes        # COLRv0 is exceptional in that LayerRecordCount appears *after* the
1233*e1fe3e4aSElliott Hughes        # LayerRecordArray it counts, but the parser logic expects Count fields
1234*e1fe3e4aSElliott Hughes        # to always precede the arrays. Here we work around this by parsing the
1235*e1fe3e4aSElliott Hughes        # LayerRecordCount before the rest of the table, and storing it in
1236*e1fe3e4aSElliott Hughes        # the reader's local state.
1237*e1fe3e4aSElliott Hughes        subReader = reader.getSubReader(offset=0)
1238*e1fe3e4aSElliott Hughes        for conv in self.getConverters():
1239*e1fe3e4aSElliott Hughes            if conv.name != "LayerRecordCount":
1240*e1fe3e4aSElliott Hughes                subReader.advance(conv.staticSize)
1241*e1fe3e4aSElliott Hughes                continue
1242*e1fe3e4aSElliott Hughes            reader[conv.name] = conv.read(subReader, font, tableDict={})
1243*e1fe3e4aSElliott Hughes            break
1244*e1fe3e4aSElliott Hughes        else:
1245*e1fe3e4aSElliott Hughes            raise AssertionError("LayerRecordCount converter not found")
1246*e1fe3e4aSElliott Hughes        return BaseTable.decompile(self, reader, font)
1247*e1fe3e4aSElliott Hughes
1248*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1249*e1fe3e4aSElliott Hughes        # The writer similarly assumes Count values precede the things counted,
1250*e1fe3e4aSElliott Hughes        # thus here we pre-initialize a CountReference; the actual count value
1251*e1fe3e4aSElliott Hughes        # will be set to the lenght of the array by the time this is assembled.
1252*e1fe3e4aSElliott Hughes        self.LayerRecordCount = None
1253*e1fe3e4aSElliott Hughes        return {
1254*e1fe3e4aSElliott Hughes            **self.__dict__,
1255*e1fe3e4aSElliott Hughes            "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount"),
1256*e1fe3e4aSElliott Hughes        }
1257*e1fe3e4aSElliott Hughes
1258*e1fe3e4aSElliott Hughes    def computeClipBoxes(self, glyphSet: "_TTGlyphSet", quantization: int = 1):
1259*e1fe3e4aSElliott Hughes        if self.Version == 0:
1260*e1fe3e4aSElliott Hughes            return
1261*e1fe3e4aSElliott Hughes
1262*e1fe3e4aSElliott Hughes        clips = {}
1263*e1fe3e4aSElliott Hughes        for rec in self.BaseGlyphList.BaseGlyphPaintRecord:
1264*e1fe3e4aSElliott Hughes            try:
1265*e1fe3e4aSElliott Hughes                clipBox = rec.Paint.computeClipBox(self, glyphSet, quantization)
1266*e1fe3e4aSElliott Hughes            except Exception as e:
1267*e1fe3e4aSElliott Hughes                from fontTools.ttLib import TTLibError
1268*e1fe3e4aSElliott Hughes
1269*e1fe3e4aSElliott Hughes                raise TTLibError(
1270*e1fe3e4aSElliott Hughes                    f"Failed to compute COLR ClipBox for {rec.BaseGlyph!r}"
1271*e1fe3e4aSElliott Hughes                ) from e
1272*e1fe3e4aSElliott Hughes
1273*e1fe3e4aSElliott Hughes            if clipBox is not None:
1274*e1fe3e4aSElliott Hughes                clips[rec.BaseGlyph] = clipBox
1275*e1fe3e4aSElliott Hughes
1276*e1fe3e4aSElliott Hughes        hasClipList = hasattr(self, "ClipList") and self.ClipList is not None
1277*e1fe3e4aSElliott Hughes        if not clips:
1278*e1fe3e4aSElliott Hughes            if hasClipList:
1279*e1fe3e4aSElliott Hughes                self.ClipList = None
1280*e1fe3e4aSElliott Hughes        else:
1281*e1fe3e4aSElliott Hughes            if not hasClipList:
1282*e1fe3e4aSElliott Hughes                self.ClipList = ClipList()
1283*e1fe3e4aSElliott Hughes                self.ClipList.Format = 1
1284*e1fe3e4aSElliott Hughes            self.ClipList.clips = clips
1285*e1fe3e4aSElliott Hughes
1286*e1fe3e4aSElliott Hughes
1287*e1fe3e4aSElliott Hughesclass LookupList(BaseTable):
1288*e1fe3e4aSElliott Hughes    @property
1289*e1fe3e4aSElliott Hughes    def table(self):
1290*e1fe3e4aSElliott Hughes        for l in self.Lookup:
1291*e1fe3e4aSElliott Hughes            for st in l.SubTable:
1292*e1fe3e4aSElliott Hughes                if type(st).__name__.endswith("Subst"):
1293*e1fe3e4aSElliott Hughes                    return "GSUB"
1294*e1fe3e4aSElliott Hughes                if type(st).__name__.endswith("Pos"):
1295*e1fe3e4aSElliott Hughes                    return "GPOS"
1296*e1fe3e4aSElliott Hughes        raise ValueError
1297*e1fe3e4aSElliott Hughes
1298*e1fe3e4aSElliott Hughes    def toXML2(self, xmlWriter, font):
1299*e1fe3e4aSElliott Hughes        if (
1300*e1fe3e4aSElliott Hughes            not font
1301*e1fe3e4aSElliott Hughes            or "Debg" not in font
1302*e1fe3e4aSElliott Hughes            or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data
1303*e1fe3e4aSElliott Hughes        ):
1304*e1fe3e4aSElliott Hughes            return super().toXML2(xmlWriter, font)
1305*e1fe3e4aSElliott Hughes        debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
1306*e1fe3e4aSElliott Hughes        for conv in self.getConverters():
1307*e1fe3e4aSElliott Hughes            if conv.repeat:
1308*e1fe3e4aSElliott Hughes                value = getattr(self, conv.name, [])
1309*e1fe3e4aSElliott Hughes                for lookupIndex, item in enumerate(value):
1310*e1fe3e4aSElliott Hughes                    if str(lookupIndex) in debugData:
1311*e1fe3e4aSElliott Hughes                        info = LookupDebugInfo(*debugData[str(lookupIndex)])
1312*e1fe3e4aSElliott Hughes                        tag = info.location
1313*e1fe3e4aSElliott Hughes                        if info.name:
1314*e1fe3e4aSElliott Hughes                            tag = f"{info.name}: {tag}"
1315*e1fe3e4aSElliott Hughes                        if info.feature:
1316*e1fe3e4aSElliott Hughes                            script, language, feature = info.feature
1317*e1fe3e4aSElliott Hughes                            tag = f"{tag} in {feature} ({script}/{language})"
1318*e1fe3e4aSElliott Hughes                        xmlWriter.comment(tag)
1319*e1fe3e4aSElliott Hughes                        xmlWriter.newline()
1320*e1fe3e4aSElliott Hughes
1321*e1fe3e4aSElliott Hughes                    conv.xmlWrite(
1322*e1fe3e4aSElliott Hughes                        xmlWriter, font, item, conv.name, [("index", lookupIndex)]
1323*e1fe3e4aSElliott Hughes                    )
1324*e1fe3e4aSElliott Hughes            else:
1325*e1fe3e4aSElliott Hughes                if conv.aux and not eval(conv.aux, None, vars(self)):
1326*e1fe3e4aSElliott Hughes                    continue
1327*e1fe3e4aSElliott Hughes                value = getattr(
1328*e1fe3e4aSElliott Hughes                    self, conv.name, None
1329*e1fe3e4aSElliott Hughes                )  # TODO Handle defaults instead of defaulting to None!
1330*e1fe3e4aSElliott Hughes                conv.xmlWrite(xmlWriter, font, value, conv.name, [])
1331*e1fe3e4aSElliott Hughes
1332*e1fe3e4aSElliott Hughes
1333*e1fe3e4aSElliott Hughesclass BaseGlyphRecordArray(BaseTable):
1334*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1335*e1fe3e4aSElliott Hughes        self.BaseGlyphRecord = sorted(
1336*e1fe3e4aSElliott Hughes            self.BaseGlyphRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1337*e1fe3e4aSElliott Hughes        )
1338*e1fe3e4aSElliott Hughes        return self.__dict__.copy()
1339*e1fe3e4aSElliott Hughes
1340*e1fe3e4aSElliott Hughes
1341*e1fe3e4aSElliott Hughesclass BaseGlyphList(BaseTable):
1342*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1343*e1fe3e4aSElliott Hughes        self.BaseGlyphPaintRecord = sorted(
1344*e1fe3e4aSElliott Hughes            self.BaseGlyphPaintRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
1345*e1fe3e4aSElliott Hughes        )
1346*e1fe3e4aSElliott Hughes        return self.__dict__.copy()
1347*e1fe3e4aSElliott Hughes
1348*e1fe3e4aSElliott Hughes
1349*e1fe3e4aSElliott Hughesclass ClipBoxFormat(IntEnum):
1350*e1fe3e4aSElliott Hughes    Static = 1
1351*e1fe3e4aSElliott Hughes    Variable = 2
1352*e1fe3e4aSElliott Hughes
1353*e1fe3e4aSElliott Hughes    def is_variable(self):
1354*e1fe3e4aSElliott Hughes        return self is self.Variable
1355*e1fe3e4aSElliott Hughes
1356*e1fe3e4aSElliott Hughes    def as_variable(self):
1357*e1fe3e4aSElliott Hughes        return self.Variable
1358*e1fe3e4aSElliott Hughes
1359*e1fe3e4aSElliott Hughes
1360*e1fe3e4aSElliott Hughesclass ClipBox(getFormatSwitchingBaseTableClass("uint8")):
1361*e1fe3e4aSElliott Hughes    formatEnum = ClipBoxFormat
1362*e1fe3e4aSElliott Hughes
1363*e1fe3e4aSElliott Hughes    def as_tuple(self):
1364*e1fe3e4aSElliott Hughes        return tuple(getattr(self, conv.name) for conv in self.getConverters())
1365*e1fe3e4aSElliott Hughes
1366*e1fe3e4aSElliott Hughes    def __repr__(self):
1367*e1fe3e4aSElliott Hughes        return f"{self.__class__.__name__}{self.as_tuple()}"
1368*e1fe3e4aSElliott Hughes
1369*e1fe3e4aSElliott Hughes
1370*e1fe3e4aSElliott Hughesclass ClipList(getFormatSwitchingBaseTableClass("uint8")):
1371*e1fe3e4aSElliott Hughes    def populateDefaults(self, propagator=None):
1372*e1fe3e4aSElliott Hughes        if not hasattr(self, "clips"):
1373*e1fe3e4aSElliott Hughes            self.clips = {}
1374*e1fe3e4aSElliott Hughes
1375*e1fe3e4aSElliott Hughes    def postRead(self, rawTable, font):
1376*e1fe3e4aSElliott Hughes        clips = {}
1377*e1fe3e4aSElliott Hughes        glyphOrder = font.getGlyphOrder()
1378*e1fe3e4aSElliott Hughes        for i, rec in enumerate(rawTable["ClipRecord"]):
1379*e1fe3e4aSElliott Hughes            if rec.StartGlyphID > rec.EndGlyphID:
1380*e1fe3e4aSElliott Hughes                log.warning(
1381*e1fe3e4aSElliott Hughes                    "invalid ClipRecord[%i].StartGlyphID (%i) > "
1382*e1fe3e4aSElliott Hughes                    "EndGlyphID (%i); skipped",
1383*e1fe3e4aSElliott Hughes                    i,
1384*e1fe3e4aSElliott Hughes                    rec.StartGlyphID,
1385*e1fe3e4aSElliott Hughes                    rec.EndGlyphID,
1386*e1fe3e4aSElliott Hughes                )
1387*e1fe3e4aSElliott Hughes                continue
1388*e1fe3e4aSElliott Hughes            redefinedGlyphs = []
1389*e1fe3e4aSElliott Hughes            missingGlyphs = []
1390*e1fe3e4aSElliott Hughes            for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1):
1391*e1fe3e4aSElliott Hughes                try:
1392*e1fe3e4aSElliott Hughes                    glyph = glyphOrder[glyphID]
1393*e1fe3e4aSElliott Hughes                except IndexError:
1394*e1fe3e4aSElliott Hughes                    missingGlyphs.append(glyphID)
1395*e1fe3e4aSElliott Hughes                    continue
1396*e1fe3e4aSElliott Hughes                if glyph not in clips:
1397*e1fe3e4aSElliott Hughes                    clips[glyph] = copy.copy(rec.ClipBox)
1398*e1fe3e4aSElliott Hughes                else:
1399*e1fe3e4aSElliott Hughes                    redefinedGlyphs.append(glyphID)
1400*e1fe3e4aSElliott Hughes            if redefinedGlyphs:
1401*e1fe3e4aSElliott Hughes                log.warning(
1402*e1fe3e4aSElliott Hughes                    "ClipRecord[%i] overlaps previous records; "
1403*e1fe3e4aSElliott Hughes                    "ignoring redefined clip boxes for the "
1404*e1fe3e4aSElliott Hughes                    "following glyph ID range: [%i-%i]",
1405*e1fe3e4aSElliott Hughes                    i,
1406*e1fe3e4aSElliott Hughes                    min(redefinedGlyphs),
1407*e1fe3e4aSElliott Hughes                    max(redefinedGlyphs),
1408*e1fe3e4aSElliott Hughes                )
1409*e1fe3e4aSElliott Hughes            if missingGlyphs:
1410*e1fe3e4aSElliott Hughes                log.warning(
1411*e1fe3e4aSElliott Hughes                    "ClipRecord[%i] range references missing " "glyph IDs: [%i-%i]",
1412*e1fe3e4aSElliott Hughes                    i,
1413*e1fe3e4aSElliott Hughes                    min(missingGlyphs),
1414*e1fe3e4aSElliott Hughes                    max(missingGlyphs),
1415*e1fe3e4aSElliott Hughes                )
1416*e1fe3e4aSElliott Hughes        self.clips = clips
1417*e1fe3e4aSElliott Hughes
1418*e1fe3e4aSElliott Hughes    def groups(self):
1419*e1fe3e4aSElliott Hughes        glyphsByClip = defaultdict(list)
1420*e1fe3e4aSElliott Hughes        uniqueClips = {}
1421*e1fe3e4aSElliott Hughes        for glyphName, clipBox in self.clips.items():
1422*e1fe3e4aSElliott Hughes            key = clipBox.as_tuple()
1423*e1fe3e4aSElliott Hughes            glyphsByClip[key].append(glyphName)
1424*e1fe3e4aSElliott Hughes            if key not in uniqueClips:
1425*e1fe3e4aSElliott Hughes                uniqueClips[key] = clipBox
1426*e1fe3e4aSElliott Hughes        return {
1427*e1fe3e4aSElliott Hughes            frozenset(glyphs): uniqueClips[key] for key, glyphs in glyphsByClip.items()
1428*e1fe3e4aSElliott Hughes        }
1429*e1fe3e4aSElliott Hughes
1430*e1fe3e4aSElliott Hughes    def preWrite(self, font):
1431*e1fe3e4aSElliott Hughes        if not hasattr(self, "clips"):
1432*e1fe3e4aSElliott Hughes            self.clips = {}
1433*e1fe3e4aSElliott Hughes        clipBoxRanges = {}
1434*e1fe3e4aSElliott Hughes        glyphMap = font.getReverseGlyphMap()
1435*e1fe3e4aSElliott Hughes        for glyphs, clipBox in self.groups().items():
1436*e1fe3e4aSElliott Hughes            glyphIDs = sorted(
1437*e1fe3e4aSElliott Hughes                glyphMap[glyphName] for glyphName in glyphs if glyphName in glyphMap
1438*e1fe3e4aSElliott Hughes            )
1439*e1fe3e4aSElliott Hughes            if not glyphIDs:
1440*e1fe3e4aSElliott Hughes                continue
1441*e1fe3e4aSElliott Hughes            last = glyphIDs[0]
1442*e1fe3e4aSElliott Hughes            ranges = [[last]]
1443*e1fe3e4aSElliott Hughes            for glyphID in glyphIDs[1:]:
1444*e1fe3e4aSElliott Hughes                if glyphID != last + 1:
1445*e1fe3e4aSElliott Hughes                    ranges[-1].append(last)
1446*e1fe3e4aSElliott Hughes                    ranges.append([glyphID])
1447*e1fe3e4aSElliott Hughes                last = glyphID
1448*e1fe3e4aSElliott Hughes            ranges[-1].append(last)
1449*e1fe3e4aSElliott Hughes            for start, end in ranges:
1450*e1fe3e4aSElliott Hughes                assert (start, end) not in clipBoxRanges
1451*e1fe3e4aSElliott Hughes                clipBoxRanges[(start, end)] = clipBox
1452*e1fe3e4aSElliott Hughes
1453*e1fe3e4aSElliott Hughes        clipRecords = []
1454*e1fe3e4aSElliott Hughes        for (start, end), clipBox in sorted(clipBoxRanges.items()):
1455*e1fe3e4aSElliott Hughes            record = ClipRecord()
1456*e1fe3e4aSElliott Hughes            record.StartGlyphID = start
1457*e1fe3e4aSElliott Hughes            record.EndGlyphID = end
1458*e1fe3e4aSElliott Hughes            record.ClipBox = clipBox
1459*e1fe3e4aSElliott Hughes            clipRecords.append(record)
1460*e1fe3e4aSElliott Hughes        rawTable = {
1461*e1fe3e4aSElliott Hughes            "ClipCount": len(clipRecords),
1462*e1fe3e4aSElliott Hughes            "ClipRecord": clipRecords,
1463*e1fe3e4aSElliott Hughes        }
1464*e1fe3e4aSElliott Hughes        return rawTable
1465*e1fe3e4aSElliott Hughes
1466*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs=None, name=None):
1467*e1fe3e4aSElliott Hughes        tableName = name if name else self.__class__.__name__
1468*e1fe3e4aSElliott Hughes        if attrs is None:
1469*e1fe3e4aSElliott Hughes            attrs = []
1470*e1fe3e4aSElliott Hughes        if hasattr(self, "Format"):
1471*e1fe3e4aSElliott Hughes            attrs.append(("Format", self.Format))
1472*e1fe3e4aSElliott Hughes        xmlWriter.begintag(tableName, attrs)
1473*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1474*e1fe3e4aSElliott Hughes        # sort clips alphabetically to ensure deterministic XML dump
1475*e1fe3e4aSElliott Hughes        for glyphs, clipBox in sorted(
1476*e1fe3e4aSElliott Hughes            self.groups().items(), key=lambda item: min(item[0])
1477*e1fe3e4aSElliott Hughes        ):
1478*e1fe3e4aSElliott Hughes            xmlWriter.begintag("Clip")
1479*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1480*e1fe3e4aSElliott Hughes            for glyphName in sorted(glyphs):
1481*e1fe3e4aSElliott Hughes                xmlWriter.simpletag("Glyph", value=glyphName)
1482*e1fe3e4aSElliott Hughes                xmlWriter.newline()
1483*e1fe3e4aSElliott Hughes            xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)])
1484*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1485*e1fe3e4aSElliott Hughes            clipBox.toXML2(xmlWriter, font)
1486*e1fe3e4aSElliott Hughes            xmlWriter.endtag("ClipBox")
1487*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1488*e1fe3e4aSElliott Hughes            xmlWriter.endtag("Clip")
1489*e1fe3e4aSElliott Hughes            xmlWriter.newline()
1490*e1fe3e4aSElliott Hughes        xmlWriter.endtag(tableName)
1491*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1492*e1fe3e4aSElliott Hughes
1493*e1fe3e4aSElliott Hughes    def fromXML(self, name, attrs, content, font):
1494*e1fe3e4aSElliott Hughes        clips = getattr(self, "clips", None)
1495*e1fe3e4aSElliott Hughes        if clips is None:
1496*e1fe3e4aSElliott Hughes            self.clips = clips = {}
1497*e1fe3e4aSElliott Hughes        assert name == "Clip"
1498*e1fe3e4aSElliott Hughes        glyphs = []
1499*e1fe3e4aSElliott Hughes        clipBox = None
1500*e1fe3e4aSElliott Hughes        for elem in content:
1501*e1fe3e4aSElliott Hughes            if not isinstance(elem, tuple):
1502*e1fe3e4aSElliott Hughes                continue
1503*e1fe3e4aSElliott Hughes            name, attrs, content = elem
1504*e1fe3e4aSElliott Hughes            if name == "Glyph":
1505*e1fe3e4aSElliott Hughes                glyphs.append(attrs["value"])
1506*e1fe3e4aSElliott Hughes            elif name == "ClipBox":
1507*e1fe3e4aSElliott Hughes                clipBox = ClipBox()
1508*e1fe3e4aSElliott Hughes                clipBox.Format = safeEval(attrs["Format"])
1509*e1fe3e4aSElliott Hughes                for elem in content:
1510*e1fe3e4aSElliott Hughes                    if not isinstance(elem, tuple):
1511*e1fe3e4aSElliott Hughes                        continue
1512*e1fe3e4aSElliott Hughes                    name, attrs, content = elem
1513*e1fe3e4aSElliott Hughes                    clipBox.fromXML(name, attrs, content, font)
1514*e1fe3e4aSElliott Hughes        if clipBox:
1515*e1fe3e4aSElliott Hughes            for glyphName in glyphs:
1516*e1fe3e4aSElliott Hughes                clips[glyphName] = clipBox
1517*e1fe3e4aSElliott Hughes
1518*e1fe3e4aSElliott Hughes
1519*e1fe3e4aSElliott Hughesclass ExtendMode(IntEnum):
1520*e1fe3e4aSElliott Hughes    PAD = 0
1521*e1fe3e4aSElliott Hughes    REPEAT = 1
1522*e1fe3e4aSElliott Hughes    REFLECT = 2
1523*e1fe3e4aSElliott Hughes
1524*e1fe3e4aSElliott Hughes
1525*e1fe3e4aSElliott Hughes# Porter-Duff modes for COLRv1 PaintComposite:
1526*e1fe3e4aSElliott Hughes# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
1527*e1fe3e4aSElliott Hughesclass CompositeMode(IntEnum):
1528*e1fe3e4aSElliott Hughes    CLEAR = 0
1529*e1fe3e4aSElliott Hughes    SRC = 1
1530*e1fe3e4aSElliott Hughes    DEST = 2
1531*e1fe3e4aSElliott Hughes    SRC_OVER = 3
1532*e1fe3e4aSElliott Hughes    DEST_OVER = 4
1533*e1fe3e4aSElliott Hughes    SRC_IN = 5
1534*e1fe3e4aSElliott Hughes    DEST_IN = 6
1535*e1fe3e4aSElliott Hughes    SRC_OUT = 7
1536*e1fe3e4aSElliott Hughes    DEST_OUT = 8
1537*e1fe3e4aSElliott Hughes    SRC_ATOP = 9
1538*e1fe3e4aSElliott Hughes    DEST_ATOP = 10
1539*e1fe3e4aSElliott Hughes    XOR = 11
1540*e1fe3e4aSElliott Hughes    PLUS = 12
1541*e1fe3e4aSElliott Hughes    SCREEN = 13
1542*e1fe3e4aSElliott Hughes    OVERLAY = 14
1543*e1fe3e4aSElliott Hughes    DARKEN = 15
1544*e1fe3e4aSElliott Hughes    LIGHTEN = 16
1545*e1fe3e4aSElliott Hughes    COLOR_DODGE = 17
1546*e1fe3e4aSElliott Hughes    COLOR_BURN = 18
1547*e1fe3e4aSElliott Hughes    HARD_LIGHT = 19
1548*e1fe3e4aSElliott Hughes    SOFT_LIGHT = 20
1549*e1fe3e4aSElliott Hughes    DIFFERENCE = 21
1550*e1fe3e4aSElliott Hughes    EXCLUSION = 22
1551*e1fe3e4aSElliott Hughes    MULTIPLY = 23
1552*e1fe3e4aSElliott Hughes    HSL_HUE = 24
1553*e1fe3e4aSElliott Hughes    HSL_SATURATION = 25
1554*e1fe3e4aSElliott Hughes    HSL_COLOR = 26
1555*e1fe3e4aSElliott Hughes    HSL_LUMINOSITY = 27
1556*e1fe3e4aSElliott Hughes
1557*e1fe3e4aSElliott Hughes
1558*e1fe3e4aSElliott Hughesclass PaintFormat(IntEnum):
1559*e1fe3e4aSElliott Hughes    PaintColrLayers = 1
1560*e1fe3e4aSElliott Hughes    PaintSolid = 2
1561*e1fe3e4aSElliott Hughes    PaintVarSolid = 3
1562*e1fe3e4aSElliott Hughes    PaintLinearGradient = 4
1563*e1fe3e4aSElliott Hughes    PaintVarLinearGradient = 5
1564*e1fe3e4aSElliott Hughes    PaintRadialGradient = 6
1565*e1fe3e4aSElliott Hughes    PaintVarRadialGradient = 7
1566*e1fe3e4aSElliott Hughes    PaintSweepGradient = 8
1567*e1fe3e4aSElliott Hughes    PaintVarSweepGradient = 9
1568*e1fe3e4aSElliott Hughes    PaintGlyph = 10
1569*e1fe3e4aSElliott Hughes    PaintColrGlyph = 11
1570*e1fe3e4aSElliott Hughes    PaintTransform = 12
1571*e1fe3e4aSElliott Hughes    PaintVarTransform = 13
1572*e1fe3e4aSElliott Hughes    PaintTranslate = 14
1573*e1fe3e4aSElliott Hughes    PaintVarTranslate = 15
1574*e1fe3e4aSElliott Hughes    PaintScale = 16
1575*e1fe3e4aSElliott Hughes    PaintVarScale = 17
1576*e1fe3e4aSElliott Hughes    PaintScaleAroundCenter = 18
1577*e1fe3e4aSElliott Hughes    PaintVarScaleAroundCenter = 19
1578*e1fe3e4aSElliott Hughes    PaintScaleUniform = 20
1579*e1fe3e4aSElliott Hughes    PaintVarScaleUniform = 21
1580*e1fe3e4aSElliott Hughes    PaintScaleUniformAroundCenter = 22
1581*e1fe3e4aSElliott Hughes    PaintVarScaleUniformAroundCenter = 23
1582*e1fe3e4aSElliott Hughes    PaintRotate = 24
1583*e1fe3e4aSElliott Hughes    PaintVarRotate = 25
1584*e1fe3e4aSElliott Hughes    PaintRotateAroundCenter = 26
1585*e1fe3e4aSElliott Hughes    PaintVarRotateAroundCenter = 27
1586*e1fe3e4aSElliott Hughes    PaintSkew = 28
1587*e1fe3e4aSElliott Hughes    PaintVarSkew = 29
1588*e1fe3e4aSElliott Hughes    PaintSkewAroundCenter = 30
1589*e1fe3e4aSElliott Hughes    PaintVarSkewAroundCenter = 31
1590*e1fe3e4aSElliott Hughes    PaintComposite = 32
1591*e1fe3e4aSElliott Hughes
1592*e1fe3e4aSElliott Hughes    def is_variable(self):
1593*e1fe3e4aSElliott Hughes        return self.name.startswith("PaintVar")
1594*e1fe3e4aSElliott Hughes
1595*e1fe3e4aSElliott Hughes    def as_variable(self):
1596*e1fe3e4aSElliott Hughes        if self.is_variable():
1597*e1fe3e4aSElliott Hughes            return self
1598*e1fe3e4aSElliott Hughes        try:
1599*e1fe3e4aSElliott Hughes            return PaintFormat.__members__[f"PaintVar{self.name[5:]}"]
1600*e1fe3e4aSElliott Hughes        except KeyError:
1601*e1fe3e4aSElliott Hughes            return None
1602*e1fe3e4aSElliott Hughes
1603*e1fe3e4aSElliott Hughes
1604*e1fe3e4aSElliott Hughesclass Paint(getFormatSwitchingBaseTableClass("uint8")):
1605*e1fe3e4aSElliott Hughes    formatEnum = PaintFormat
1606*e1fe3e4aSElliott Hughes
1607*e1fe3e4aSElliott Hughes    def getFormatName(self):
1608*e1fe3e4aSElliott Hughes        try:
1609*e1fe3e4aSElliott Hughes            return self.formatEnum(self.Format).name
1610*e1fe3e4aSElliott Hughes        except ValueError:
1611*e1fe3e4aSElliott Hughes            raise NotImplementedError(f"Unknown Paint format: {self.Format}")
1612*e1fe3e4aSElliott Hughes
1613*e1fe3e4aSElliott Hughes    def toXML(self, xmlWriter, font, attrs=None, name=None):
1614*e1fe3e4aSElliott Hughes        tableName = name if name else self.__class__.__name__
1615*e1fe3e4aSElliott Hughes        if attrs is None:
1616*e1fe3e4aSElliott Hughes            attrs = []
1617*e1fe3e4aSElliott Hughes        attrs.append(("Format", self.Format))
1618*e1fe3e4aSElliott Hughes        xmlWriter.begintag(tableName, attrs)
1619*e1fe3e4aSElliott Hughes        xmlWriter.comment(self.getFormatName())
1620*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1621*e1fe3e4aSElliott Hughes        self.toXML2(xmlWriter, font)
1622*e1fe3e4aSElliott Hughes        xmlWriter.endtag(tableName)
1623*e1fe3e4aSElliott Hughes        xmlWriter.newline()
1624*e1fe3e4aSElliott Hughes
1625*e1fe3e4aSElliott Hughes    def iterPaintSubTables(self, colr: COLR) -> Iterator[BaseTable.SubTableEntry]:
1626*e1fe3e4aSElliott Hughes        if self.Format == PaintFormat.PaintColrLayers:
1627*e1fe3e4aSElliott Hughes            # https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists
1628*e1fe3e4aSElliott Hughes            layers = []
1629*e1fe3e4aSElliott Hughes            if colr.LayerList is not None:
1630*e1fe3e4aSElliott Hughes                layers = colr.LayerList.Paint
1631*e1fe3e4aSElliott Hughes            yield from (
1632*e1fe3e4aSElliott Hughes                BaseTable.SubTableEntry(name="Layers", value=v, index=i)
1633*e1fe3e4aSElliott Hughes                for i, v in enumerate(
1634*e1fe3e4aSElliott Hughes                    layers[self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers]
1635*e1fe3e4aSElliott Hughes                )
1636*e1fe3e4aSElliott Hughes            )
1637*e1fe3e4aSElliott Hughes            return
1638*e1fe3e4aSElliott Hughes
1639*e1fe3e4aSElliott Hughes        if self.Format == PaintFormat.PaintColrGlyph:
1640*e1fe3e4aSElliott Hughes            for record in colr.BaseGlyphList.BaseGlyphPaintRecord:
1641*e1fe3e4aSElliott Hughes                if record.BaseGlyph == self.Glyph:
1642*e1fe3e4aSElliott Hughes                    yield BaseTable.SubTableEntry(name="BaseGlyph", value=record.Paint)
1643*e1fe3e4aSElliott Hughes                    return
1644*e1fe3e4aSElliott Hughes            else:
1645*e1fe3e4aSElliott Hughes                raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList")
1646*e1fe3e4aSElliott Hughes
1647*e1fe3e4aSElliott Hughes        for conv in self.getConverters():
1648*e1fe3e4aSElliott Hughes            if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
1649*e1fe3e4aSElliott Hughes                value = getattr(self, conv.name)
1650*e1fe3e4aSElliott Hughes                yield BaseTable.SubTableEntry(name=conv.name, value=value)
1651*e1fe3e4aSElliott Hughes
1652*e1fe3e4aSElliott Hughes    def getChildren(self, colr) -> List["Paint"]:
1653*e1fe3e4aSElliott Hughes        # this is kept for backward compatibility (e.g. it's used by the subsetter)
1654*e1fe3e4aSElliott Hughes        return [p.value for p in self.iterPaintSubTables(colr)]
1655*e1fe3e4aSElliott Hughes
1656*e1fe3e4aSElliott Hughes    def traverse(self, colr: COLR, callback):
1657*e1fe3e4aSElliott Hughes        """Depth-first traversal of graph rooted at self, callback on each node."""
1658*e1fe3e4aSElliott Hughes        if not callable(callback):
1659*e1fe3e4aSElliott Hughes            raise TypeError("callback must be callable")
1660*e1fe3e4aSElliott Hughes
1661*e1fe3e4aSElliott Hughes        for path in dfs_base_table(
1662*e1fe3e4aSElliott Hughes            self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr)
1663*e1fe3e4aSElliott Hughes        ):
1664*e1fe3e4aSElliott Hughes            paint = path[-1].value
1665*e1fe3e4aSElliott Hughes            callback(paint)
1666*e1fe3e4aSElliott Hughes
1667*e1fe3e4aSElliott Hughes    def getTransform(self) -> Transform:
1668*e1fe3e4aSElliott Hughes        if self.Format == PaintFormat.PaintTransform:
1669*e1fe3e4aSElliott Hughes            t = self.Transform
1670*e1fe3e4aSElliott Hughes            return Transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy)
1671*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintTranslate:
1672*e1fe3e4aSElliott Hughes            return Identity.translate(self.dx, self.dy)
1673*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintScale:
1674*e1fe3e4aSElliott Hughes            return Identity.scale(self.scaleX, self.scaleY)
1675*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintScaleAroundCenter:
1676*e1fe3e4aSElliott Hughes            return (
1677*e1fe3e4aSElliott Hughes                Identity.translate(self.centerX, self.centerY)
1678*e1fe3e4aSElliott Hughes                .scale(self.scaleX, self.scaleY)
1679*e1fe3e4aSElliott Hughes                .translate(-self.centerX, -self.centerY)
1680*e1fe3e4aSElliott Hughes            )
1681*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintScaleUniform:
1682*e1fe3e4aSElliott Hughes            return Identity.scale(self.scale)
1683*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintScaleUniformAroundCenter:
1684*e1fe3e4aSElliott Hughes            return (
1685*e1fe3e4aSElliott Hughes                Identity.translate(self.centerX, self.centerY)
1686*e1fe3e4aSElliott Hughes                .scale(self.scale)
1687*e1fe3e4aSElliott Hughes                .translate(-self.centerX, -self.centerY)
1688*e1fe3e4aSElliott Hughes            )
1689*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintRotate:
1690*e1fe3e4aSElliott Hughes            return Identity.rotate(radians(self.angle))
1691*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintRotateAroundCenter:
1692*e1fe3e4aSElliott Hughes            return (
1693*e1fe3e4aSElliott Hughes                Identity.translate(self.centerX, self.centerY)
1694*e1fe3e4aSElliott Hughes                .rotate(radians(self.angle))
1695*e1fe3e4aSElliott Hughes                .translate(-self.centerX, -self.centerY)
1696*e1fe3e4aSElliott Hughes            )
1697*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintSkew:
1698*e1fe3e4aSElliott Hughes            return Identity.skew(radians(-self.xSkewAngle), radians(self.ySkewAngle))
1699*e1fe3e4aSElliott Hughes        elif self.Format == PaintFormat.PaintSkewAroundCenter:
1700*e1fe3e4aSElliott Hughes            return (
1701*e1fe3e4aSElliott Hughes                Identity.translate(self.centerX, self.centerY)
1702*e1fe3e4aSElliott Hughes                .skew(radians(-self.xSkewAngle), radians(self.ySkewAngle))
1703*e1fe3e4aSElliott Hughes                .translate(-self.centerX, -self.centerY)
1704*e1fe3e4aSElliott Hughes            )
1705*e1fe3e4aSElliott Hughes        if PaintFormat(self.Format).is_variable():
1706*e1fe3e4aSElliott Hughes            raise NotImplementedError(f"Variable Paints not supported: {self.Format}")
1707*e1fe3e4aSElliott Hughes
1708*e1fe3e4aSElliott Hughes        return Identity
1709*e1fe3e4aSElliott Hughes
1710*e1fe3e4aSElliott Hughes    def computeClipBox(
1711*e1fe3e4aSElliott Hughes        self, colr: COLR, glyphSet: "_TTGlyphSet", quantization: int = 1
1712*e1fe3e4aSElliott Hughes    ) -> Optional[ClipBox]:
1713*e1fe3e4aSElliott Hughes        pen = ControlBoundsPen(glyphSet)
1714*e1fe3e4aSElliott Hughes        for path in dfs_base_table(
1715*e1fe3e4aSElliott Hughes            self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr)
1716*e1fe3e4aSElliott Hughes        ):
1717*e1fe3e4aSElliott Hughes            paint = path[-1].value
1718*e1fe3e4aSElliott Hughes            if paint.Format == PaintFormat.PaintGlyph:
1719*e1fe3e4aSElliott Hughes                transformation = reduce(
1720*e1fe3e4aSElliott Hughes                    Transform.transform,
1721*e1fe3e4aSElliott Hughes                    (st.value.getTransform() for st in path),
1722*e1fe3e4aSElliott Hughes                    Identity,
1723*e1fe3e4aSElliott Hughes                )
1724*e1fe3e4aSElliott Hughes                glyphSet[paint.Glyph].draw(TransformPen(pen, transformation))
1725*e1fe3e4aSElliott Hughes
1726*e1fe3e4aSElliott Hughes        if pen.bounds is None:
1727*e1fe3e4aSElliott Hughes            return None
1728*e1fe3e4aSElliott Hughes
1729*e1fe3e4aSElliott Hughes        cb = ClipBox()
1730*e1fe3e4aSElliott Hughes        cb.Format = int(ClipBoxFormat.Static)
1731*e1fe3e4aSElliott Hughes        cb.xMin, cb.yMin, cb.xMax, cb.yMax = quantizeRect(pen.bounds, quantization)
1732*e1fe3e4aSElliott Hughes        return cb
1733*e1fe3e4aSElliott Hughes
1734*e1fe3e4aSElliott Hughes
1735*e1fe3e4aSElliott Hughes# For each subtable format there is a class. However, we don't really distinguish
1736*e1fe3e4aSElliott Hughes# between "field name" and "format name": often these are the same. Yet there's
1737*e1fe3e4aSElliott Hughes# a whole bunch of fields with different names. The following dict is a mapping
1738*e1fe3e4aSElliott Hughes# from "format name" to "field name". _buildClasses() uses this to create a
1739*e1fe3e4aSElliott Hughes# subclass for each alternate field name.
1740*e1fe3e4aSElliott Hughes#
1741*e1fe3e4aSElliott Hughes_equivalents = {
1742*e1fe3e4aSElliott Hughes    "MarkArray": ("Mark1Array",),
1743*e1fe3e4aSElliott Hughes    "LangSys": ("DefaultLangSys",),
1744*e1fe3e4aSElliott Hughes    "Coverage": (
1745*e1fe3e4aSElliott Hughes        "MarkCoverage",
1746*e1fe3e4aSElliott Hughes        "BaseCoverage",
1747*e1fe3e4aSElliott Hughes        "LigatureCoverage",
1748*e1fe3e4aSElliott Hughes        "Mark1Coverage",
1749*e1fe3e4aSElliott Hughes        "Mark2Coverage",
1750*e1fe3e4aSElliott Hughes        "BacktrackCoverage",
1751*e1fe3e4aSElliott Hughes        "InputCoverage",
1752*e1fe3e4aSElliott Hughes        "LookAheadCoverage",
1753*e1fe3e4aSElliott Hughes        "VertGlyphCoverage",
1754*e1fe3e4aSElliott Hughes        "HorizGlyphCoverage",
1755*e1fe3e4aSElliott Hughes        "TopAccentCoverage",
1756*e1fe3e4aSElliott Hughes        "ExtendedShapeCoverage",
1757*e1fe3e4aSElliott Hughes        "MathKernCoverage",
1758*e1fe3e4aSElliott Hughes    ),
1759*e1fe3e4aSElliott Hughes    "ClassDef": (
1760*e1fe3e4aSElliott Hughes        "ClassDef1",
1761*e1fe3e4aSElliott Hughes        "ClassDef2",
1762*e1fe3e4aSElliott Hughes        "BacktrackClassDef",
1763*e1fe3e4aSElliott Hughes        "InputClassDef",
1764*e1fe3e4aSElliott Hughes        "LookAheadClassDef",
1765*e1fe3e4aSElliott Hughes        "GlyphClassDef",
1766*e1fe3e4aSElliott Hughes        "MarkAttachClassDef",
1767*e1fe3e4aSElliott Hughes    ),
1768*e1fe3e4aSElliott Hughes    "Anchor": (
1769*e1fe3e4aSElliott Hughes        "EntryAnchor",
1770*e1fe3e4aSElliott Hughes        "ExitAnchor",
1771*e1fe3e4aSElliott Hughes        "BaseAnchor",
1772*e1fe3e4aSElliott Hughes        "LigatureAnchor",
1773*e1fe3e4aSElliott Hughes        "Mark2Anchor",
1774*e1fe3e4aSElliott Hughes        "MarkAnchor",
1775*e1fe3e4aSElliott Hughes    ),
1776*e1fe3e4aSElliott Hughes    "Device": (
1777*e1fe3e4aSElliott Hughes        "XPlaDevice",
1778*e1fe3e4aSElliott Hughes        "YPlaDevice",
1779*e1fe3e4aSElliott Hughes        "XAdvDevice",
1780*e1fe3e4aSElliott Hughes        "YAdvDevice",
1781*e1fe3e4aSElliott Hughes        "XDeviceTable",
1782*e1fe3e4aSElliott Hughes        "YDeviceTable",
1783*e1fe3e4aSElliott Hughes        "DeviceTable",
1784*e1fe3e4aSElliott Hughes    ),
1785*e1fe3e4aSElliott Hughes    "Axis": (
1786*e1fe3e4aSElliott Hughes        "HorizAxis",
1787*e1fe3e4aSElliott Hughes        "VertAxis",
1788*e1fe3e4aSElliott Hughes    ),
1789*e1fe3e4aSElliott Hughes    "MinMax": ("DefaultMinMax",),
1790*e1fe3e4aSElliott Hughes    "BaseCoord": (
1791*e1fe3e4aSElliott Hughes        "MinCoord",
1792*e1fe3e4aSElliott Hughes        "MaxCoord",
1793*e1fe3e4aSElliott Hughes    ),
1794*e1fe3e4aSElliott Hughes    "JstfLangSys": ("DefJstfLangSys",),
1795*e1fe3e4aSElliott Hughes    "JstfGSUBModList": (
1796*e1fe3e4aSElliott Hughes        "ShrinkageEnableGSUB",
1797*e1fe3e4aSElliott Hughes        "ShrinkageDisableGSUB",
1798*e1fe3e4aSElliott Hughes        "ExtensionEnableGSUB",
1799*e1fe3e4aSElliott Hughes        "ExtensionDisableGSUB",
1800*e1fe3e4aSElliott Hughes    ),
1801*e1fe3e4aSElliott Hughes    "JstfGPOSModList": (
1802*e1fe3e4aSElliott Hughes        "ShrinkageEnableGPOS",
1803*e1fe3e4aSElliott Hughes        "ShrinkageDisableGPOS",
1804*e1fe3e4aSElliott Hughes        "ExtensionEnableGPOS",
1805*e1fe3e4aSElliott Hughes        "ExtensionDisableGPOS",
1806*e1fe3e4aSElliott Hughes    ),
1807*e1fe3e4aSElliott Hughes    "JstfMax": (
1808*e1fe3e4aSElliott Hughes        "ShrinkageJstfMax",
1809*e1fe3e4aSElliott Hughes        "ExtensionJstfMax",
1810*e1fe3e4aSElliott Hughes    ),
1811*e1fe3e4aSElliott Hughes    "MathKern": (
1812*e1fe3e4aSElliott Hughes        "TopRightMathKern",
1813*e1fe3e4aSElliott Hughes        "TopLeftMathKern",
1814*e1fe3e4aSElliott Hughes        "BottomRightMathKern",
1815*e1fe3e4aSElliott Hughes        "BottomLeftMathKern",
1816*e1fe3e4aSElliott Hughes    ),
1817*e1fe3e4aSElliott Hughes    "MathGlyphConstruction": ("VertGlyphConstruction", "HorizGlyphConstruction"),
1818*e1fe3e4aSElliott Hughes}
1819*e1fe3e4aSElliott Hughes
1820*e1fe3e4aSElliott Hughes#
1821*e1fe3e4aSElliott Hughes# OverFlow logic, to automatically create ExtensionLookups
1822*e1fe3e4aSElliott Hughes# XXX This should probably move to otBase.py
1823*e1fe3e4aSElliott Hughes#
1824*e1fe3e4aSElliott Hughes
1825*e1fe3e4aSElliott Hughes
1826*e1fe3e4aSElliott Hughesdef fixLookupOverFlows(ttf, overflowRecord):
1827*e1fe3e4aSElliott Hughes    """Either the offset from the LookupList to a lookup overflowed, or
1828*e1fe3e4aSElliott Hughes    an offset from a lookup to a subtable overflowed.
1829*e1fe3e4aSElliott Hughes    The table layout is:
1830*e1fe3e4aSElliott Hughes    GPSO/GUSB
1831*e1fe3e4aSElliott Hughes            Script List
1832*e1fe3e4aSElliott Hughes            Feature List
1833*e1fe3e4aSElliott Hughes            LookUpList
1834*e1fe3e4aSElliott Hughes                    Lookup[0] and contents
1835*e1fe3e4aSElliott Hughes                            SubTable offset list
1836*e1fe3e4aSElliott Hughes                                    SubTable[0] and contents
1837*e1fe3e4aSElliott Hughes                                    ...
1838*e1fe3e4aSElliott Hughes                                    SubTable[n] and contents
1839*e1fe3e4aSElliott Hughes                    ...
1840*e1fe3e4aSElliott Hughes                    Lookup[n] and contents
1841*e1fe3e4aSElliott Hughes                            SubTable offset list
1842*e1fe3e4aSElliott Hughes                                    SubTable[0] and contents
1843*e1fe3e4aSElliott Hughes                                    ...
1844*e1fe3e4aSElliott Hughes                                    SubTable[n] and contents
1845*e1fe3e4aSElliott Hughes    If the offset to a lookup overflowed (SubTableIndex is None)
1846*e1fe3e4aSElliott Hughes            we must promote the *previous*	lookup to an Extension type.
1847*e1fe3e4aSElliott Hughes    If the offset from a lookup to subtable overflowed, then we must promote it
1848*e1fe3e4aSElliott Hughes            to an Extension Lookup type.
1849*e1fe3e4aSElliott Hughes    """
1850*e1fe3e4aSElliott Hughes    ok = 0
1851*e1fe3e4aSElliott Hughes    lookupIndex = overflowRecord.LookupListIndex
1852*e1fe3e4aSElliott Hughes    if overflowRecord.SubTableIndex is None:
1853*e1fe3e4aSElliott Hughes        lookupIndex = lookupIndex - 1
1854*e1fe3e4aSElliott Hughes    if lookupIndex < 0:
1855*e1fe3e4aSElliott Hughes        return ok
1856*e1fe3e4aSElliott Hughes    if overflowRecord.tableType == "GSUB":
1857*e1fe3e4aSElliott Hughes        extType = 7
1858*e1fe3e4aSElliott Hughes    elif overflowRecord.tableType == "GPOS":
1859*e1fe3e4aSElliott Hughes        extType = 9
1860*e1fe3e4aSElliott Hughes
1861*e1fe3e4aSElliott Hughes    lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
1862*e1fe3e4aSElliott Hughes    lookup = lookups[lookupIndex]
1863*e1fe3e4aSElliott Hughes    # If the previous lookup is an extType, look further back. Very unlikely, but possible.
1864*e1fe3e4aSElliott Hughes    while lookup.SubTable[0].__class__.LookupType == extType:
1865*e1fe3e4aSElliott Hughes        lookupIndex = lookupIndex - 1
1866*e1fe3e4aSElliott Hughes        if lookupIndex < 0:
1867*e1fe3e4aSElliott Hughes            return ok
1868*e1fe3e4aSElliott Hughes        lookup = lookups[lookupIndex]
1869*e1fe3e4aSElliott Hughes
1870*e1fe3e4aSElliott Hughes    for lookupIndex in range(lookupIndex, len(lookups)):
1871*e1fe3e4aSElliott Hughes        lookup = lookups[lookupIndex]
1872*e1fe3e4aSElliott Hughes        if lookup.LookupType != extType:
1873*e1fe3e4aSElliott Hughes            lookup.LookupType = extType
1874*e1fe3e4aSElliott Hughes            for si in range(len(lookup.SubTable)):
1875*e1fe3e4aSElliott Hughes                subTable = lookup.SubTable[si]
1876*e1fe3e4aSElliott Hughes                extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
1877*e1fe3e4aSElliott Hughes                extSubTable = extSubTableClass()
1878*e1fe3e4aSElliott Hughes                extSubTable.Format = 1
1879*e1fe3e4aSElliott Hughes                extSubTable.ExtSubTable = subTable
1880*e1fe3e4aSElliott Hughes                lookup.SubTable[si] = extSubTable
1881*e1fe3e4aSElliott Hughes    ok = 1
1882*e1fe3e4aSElliott Hughes    return ok
1883*e1fe3e4aSElliott Hughes
1884*e1fe3e4aSElliott Hughes
1885*e1fe3e4aSElliott Hughesdef splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
1886*e1fe3e4aSElliott Hughes    ok = 1
1887*e1fe3e4aSElliott Hughes    oldMapping = sorted(oldSubTable.mapping.items())
1888*e1fe3e4aSElliott Hughes    oldLen = len(oldMapping)
1889*e1fe3e4aSElliott Hughes
1890*e1fe3e4aSElliott Hughes    if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
1891*e1fe3e4aSElliott Hughes        # Coverage table is written last. Overflow is to or within the
1892*e1fe3e4aSElliott Hughes        # the coverage table. We will just cut the subtable in half.
1893*e1fe3e4aSElliott Hughes        newLen = oldLen // 2
1894*e1fe3e4aSElliott Hughes
1895*e1fe3e4aSElliott Hughes    elif overflowRecord.itemName == "Sequence":
1896*e1fe3e4aSElliott Hughes        # We just need to back up by two items from the overflowed
1897*e1fe3e4aSElliott Hughes        # Sequence index to make sure the offset to the Coverage table
1898*e1fe3e4aSElliott Hughes        # doesn't overflow.
1899*e1fe3e4aSElliott Hughes        newLen = overflowRecord.itemIndex - 1
1900*e1fe3e4aSElliott Hughes
1901*e1fe3e4aSElliott Hughes    newSubTable.mapping = {}
1902*e1fe3e4aSElliott Hughes    for i in range(newLen, oldLen):
1903*e1fe3e4aSElliott Hughes        item = oldMapping[i]
1904*e1fe3e4aSElliott Hughes        key = item[0]
1905*e1fe3e4aSElliott Hughes        newSubTable.mapping[key] = item[1]
1906*e1fe3e4aSElliott Hughes        del oldSubTable.mapping[key]
1907*e1fe3e4aSElliott Hughes
1908*e1fe3e4aSElliott Hughes    return ok
1909*e1fe3e4aSElliott Hughes
1910*e1fe3e4aSElliott Hughes
1911*e1fe3e4aSElliott Hughesdef splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
1912*e1fe3e4aSElliott Hughes    ok = 1
1913*e1fe3e4aSElliott Hughes    if hasattr(oldSubTable, "sortCoverageLast"):
1914*e1fe3e4aSElliott Hughes        newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
1915*e1fe3e4aSElliott Hughes
1916*e1fe3e4aSElliott Hughes    oldAlts = sorted(oldSubTable.alternates.items())
1917*e1fe3e4aSElliott Hughes    oldLen = len(oldAlts)
1918*e1fe3e4aSElliott Hughes
1919*e1fe3e4aSElliott Hughes    if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
1920*e1fe3e4aSElliott Hughes        # Coverage table is written last. overflow is to or within the
1921*e1fe3e4aSElliott Hughes        # the coverage table. We will just cut the subtable in half.
1922*e1fe3e4aSElliott Hughes        newLen = oldLen // 2
1923*e1fe3e4aSElliott Hughes
1924*e1fe3e4aSElliott Hughes    elif overflowRecord.itemName == "AlternateSet":
1925*e1fe3e4aSElliott Hughes        # We just need to back up by two items
1926*e1fe3e4aSElliott Hughes        # from the overflowed AlternateSet index to make sure the offset
1927*e1fe3e4aSElliott Hughes        # to the Coverage table doesn't overflow.
1928*e1fe3e4aSElliott Hughes        newLen = overflowRecord.itemIndex - 1
1929*e1fe3e4aSElliott Hughes
1930*e1fe3e4aSElliott Hughes    newSubTable.alternates = {}
1931*e1fe3e4aSElliott Hughes    for i in range(newLen, oldLen):
1932*e1fe3e4aSElliott Hughes        item = oldAlts[i]
1933*e1fe3e4aSElliott Hughes        key = item[0]
1934*e1fe3e4aSElliott Hughes        newSubTable.alternates[key] = item[1]
1935*e1fe3e4aSElliott Hughes        del oldSubTable.alternates[key]
1936*e1fe3e4aSElliott Hughes
1937*e1fe3e4aSElliott Hughes    return ok
1938*e1fe3e4aSElliott Hughes
1939*e1fe3e4aSElliott Hughes
1940*e1fe3e4aSElliott Hughesdef splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
1941*e1fe3e4aSElliott Hughes    ok = 1
1942*e1fe3e4aSElliott Hughes    oldLigs = sorted(oldSubTable.ligatures.items())
1943*e1fe3e4aSElliott Hughes    oldLen = len(oldLigs)
1944*e1fe3e4aSElliott Hughes
1945*e1fe3e4aSElliott Hughes    if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
1946*e1fe3e4aSElliott Hughes        # Coverage table is written last. overflow is to or within the
1947*e1fe3e4aSElliott Hughes        # the coverage table. We will just cut the subtable in half.
1948*e1fe3e4aSElliott Hughes        newLen = oldLen // 2
1949*e1fe3e4aSElliott Hughes
1950*e1fe3e4aSElliott Hughes    elif overflowRecord.itemName == "LigatureSet":
1951*e1fe3e4aSElliott Hughes        # We just need to back up by two items
1952*e1fe3e4aSElliott Hughes        # from the overflowed AlternateSet index to make sure the offset
1953*e1fe3e4aSElliott Hughes        # to the Coverage table doesn't overflow.
1954*e1fe3e4aSElliott Hughes        newLen = overflowRecord.itemIndex - 1
1955*e1fe3e4aSElliott Hughes
1956*e1fe3e4aSElliott Hughes    newSubTable.ligatures = {}
1957*e1fe3e4aSElliott Hughes    for i in range(newLen, oldLen):
1958*e1fe3e4aSElliott Hughes        item = oldLigs[i]
1959*e1fe3e4aSElliott Hughes        key = item[0]
1960*e1fe3e4aSElliott Hughes        newSubTable.ligatures[key] = item[1]
1961*e1fe3e4aSElliott Hughes        del oldSubTable.ligatures[key]
1962*e1fe3e4aSElliott Hughes
1963*e1fe3e4aSElliott Hughes    return ok
1964*e1fe3e4aSElliott Hughes
1965*e1fe3e4aSElliott Hughes
1966*e1fe3e4aSElliott Hughesdef splitPairPos(oldSubTable, newSubTable, overflowRecord):
1967*e1fe3e4aSElliott Hughes    st = oldSubTable
1968*e1fe3e4aSElliott Hughes    ok = False
1969*e1fe3e4aSElliott Hughes    newSubTable.Format = oldSubTable.Format
1970*e1fe3e4aSElliott Hughes    if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1:
1971*e1fe3e4aSElliott Hughes        for name in "ValueFormat1", "ValueFormat2":
1972*e1fe3e4aSElliott Hughes            setattr(newSubTable, name, getattr(oldSubTable, name))
1973*e1fe3e4aSElliott Hughes
1974*e1fe3e4aSElliott Hughes        # Move top half of coverage to new subtable
1975*e1fe3e4aSElliott Hughes
1976*e1fe3e4aSElliott Hughes        newSubTable.Coverage = oldSubTable.Coverage.__class__()
1977*e1fe3e4aSElliott Hughes
1978*e1fe3e4aSElliott Hughes        coverage = oldSubTable.Coverage.glyphs
1979*e1fe3e4aSElliott Hughes        records = oldSubTable.PairSet
1980*e1fe3e4aSElliott Hughes
1981*e1fe3e4aSElliott Hughes        oldCount = len(oldSubTable.PairSet) // 2
1982*e1fe3e4aSElliott Hughes
1983*e1fe3e4aSElliott Hughes        oldSubTable.Coverage.glyphs = coverage[:oldCount]
1984*e1fe3e4aSElliott Hughes        oldSubTable.PairSet = records[:oldCount]
1985*e1fe3e4aSElliott Hughes
1986*e1fe3e4aSElliott Hughes        newSubTable.Coverage.glyphs = coverage[oldCount:]
1987*e1fe3e4aSElliott Hughes        newSubTable.PairSet = records[oldCount:]
1988*e1fe3e4aSElliott Hughes
1989*e1fe3e4aSElliott Hughes        oldSubTable.PairSetCount = len(oldSubTable.PairSet)
1990*e1fe3e4aSElliott Hughes        newSubTable.PairSetCount = len(newSubTable.PairSet)
1991*e1fe3e4aSElliott Hughes
1992*e1fe3e4aSElliott Hughes        ok = True
1993*e1fe3e4aSElliott Hughes
1994*e1fe3e4aSElliott Hughes    elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1:
1995*e1fe3e4aSElliott Hughes        if not hasattr(oldSubTable, "Class2Count"):
1996*e1fe3e4aSElliott Hughes            oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record)
1997*e1fe3e4aSElliott Hughes        for name in "Class2Count", "ClassDef2", "ValueFormat1", "ValueFormat2":
1998*e1fe3e4aSElliott Hughes            setattr(newSubTable, name, getattr(oldSubTable, name))
1999*e1fe3e4aSElliott Hughes
2000*e1fe3e4aSElliott Hughes        # The two subtables will still have the same ClassDef2 and the table
2001*e1fe3e4aSElliott Hughes        # sharing will still cause the sharing to overflow.  As such, disable
2002*e1fe3e4aSElliott Hughes        # sharing on the one that is serialized second (that's oldSubTable).
2003*e1fe3e4aSElliott Hughes        oldSubTable.DontShare = True
2004*e1fe3e4aSElliott Hughes
2005*e1fe3e4aSElliott Hughes        # Move top half of class numbers to new subtable
2006*e1fe3e4aSElliott Hughes
2007*e1fe3e4aSElliott Hughes        newSubTable.Coverage = oldSubTable.Coverage.__class__()
2008*e1fe3e4aSElliott Hughes        newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__()
2009*e1fe3e4aSElliott Hughes
2010*e1fe3e4aSElliott Hughes        coverage = oldSubTable.Coverage.glyphs
2011*e1fe3e4aSElliott Hughes        classDefs = oldSubTable.ClassDef1.classDefs
2012*e1fe3e4aSElliott Hughes        records = oldSubTable.Class1Record
2013*e1fe3e4aSElliott Hughes
2014*e1fe3e4aSElliott Hughes        oldCount = len(oldSubTable.Class1Record) // 2
2015*e1fe3e4aSElliott Hughes        newGlyphs = set(k for k, v in classDefs.items() if v >= oldCount)
2016*e1fe3e4aSElliott Hughes
2017*e1fe3e4aSElliott Hughes        oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs]
2018*e1fe3e4aSElliott Hughes        oldSubTable.ClassDef1.classDefs = {
2019*e1fe3e4aSElliott Hughes            k: v for k, v in classDefs.items() if v < oldCount
2020*e1fe3e4aSElliott Hughes        }
2021*e1fe3e4aSElliott Hughes        oldSubTable.Class1Record = records[:oldCount]
2022*e1fe3e4aSElliott Hughes
2023*e1fe3e4aSElliott Hughes        newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs]
2024*e1fe3e4aSElliott Hughes        newSubTable.ClassDef1.classDefs = {
2025*e1fe3e4aSElliott Hughes            k: (v - oldCount) for k, v in classDefs.items() if v > oldCount
2026*e1fe3e4aSElliott Hughes        }
2027*e1fe3e4aSElliott Hughes        newSubTable.Class1Record = records[oldCount:]
2028*e1fe3e4aSElliott Hughes
2029*e1fe3e4aSElliott Hughes        oldSubTable.Class1Count = len(oldSubTable.Class1Record)
2030*e1fe3e4aSElliott Hughes        newSubTable.Class1Count = len(newSubTable.Class1Record)
2031*e1fe3e4aSElliott Hughes
2032*e1fe3e4aSElliott Hughes        ok = True
2033*e1fe3e4aSElliott Hughes
2034*e1fe3e4aSElliott Hughes    return ok
2035*e1fe3e4aSElliott Hughes
2036*e1fe3e4aSElliott Hughes
2037*e1fe3e4aSElliott Hughesdef splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
2038*e1fe3e4aSElliott Hughes    # split half of the mark classes to the new subtable
2039*e1fe3e4aSElliott Hughes    classCount = oldSubTable.ClassCount
2040*e1fe3e4aSElliott Hughes    if classCount < 2:
2041*e1fe3e4aSElliott Hughes        # oh well, not much left to split...
2042*e1fe3e4aSElliott Hughes        return False
2043*e1fe3e4aSElliott Hughes
2044*e1fe3e4aSElliott Hughes    oldClassCount = classCount // 2
2045*e1fe3e4aSElliott Hughes    newClassCount = classCount - oldClassCount
2046*e1fe3e4aSElliott Hughes
2047*e1fe3e4aSElliott Hughes    oldMarkCoverage, oldMarkRecords = [], []
2048*e1fe3e4aSElliott Hughes    newMarkCoverage, newMarkRecords = [], []
2049*e1fe3e4aSElliott Hughes    for glyphName, markRecord in zip(
2050*e1fe3e4aSElliott Hughes        oldSubTable.MarkCoverage.glyphs, oldSubTable.MarkArray.MarkRecord
2051*e1fe3e4aSElliott Hughes    ):
2052*e1fe3e4aSElliott Hughes        if markRecord.Class < oldClassCount:
2053*e1fe3e4aSElliott Hughes            oldMarkCoverage.append(glyphName)
2054*e1fe3e4aSElliott Hughes            oldMarkRecords.append(markRecord)
2055*e1fe3e4aSElliott Hughes        else:
2056*e1fe3e4aSElliott Hughes            markRecord.Class -= oldClassCount
2057*e1fe3e4aSElliott Hughes            newMarkCoverage.append(glyphName)
2058*e1fe3e4aSElliott Hughes            newMarkRecords.append(markRecord)
2059*e1fe3e4aSElliott Hughes
2060*e1fe3e4aSElliott Hughes    oldBaseRecords, newBaseRecords = [], []
2061*e1fe3e4aSElliott Hughes    for rec in oldSubTable.BaseArray.BaseRecord:
2062*e1fe3e4aSElliott Hughes        oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__()
2063*e1fe3e4aSElliott Hughes        oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount]
2064*e1fe3e4aSElliott Hughes        newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:]
2065*e1fe3e4aSElliott Hughes        oldBaseRecords.append(oldBaseRecord)
2066*e1fe3e4aSElliott Hughes        newBaseRecords.append(newBaseRecord)
2067*e1fe3e4aSElliott Hughes
2068*e1fe3e4aSElliott Hughes    newSubTable.Format = oldSubTable.Format
2069*e1fe3e4aSElliott Hughes
2070*e1fe3e4aSElliott Hughes    oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
2071*e1fe3e4aSElliott Hughes    newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
2072*e1fe3e4aSElliott Hughes    newSubTable.MarkCoverage.glyphs = newMarkCoverage
2073*e1fe3e4aSElliott Hughes
2074*e1fe3e4aSElliott Hughes    # share the same BaseCoverage in both halves
2075*e1fe3e4aSElliott Hughes    newSubTable.BaseCoverage = oldSubTable.BaseCoverage
2076*e1fe3e4aSElliott Hughes
2077*e1fe3e4aSElliott Hughes    oldSubTable.ClassCount = oldClassCount
2078*e1fe3e4aSElliott Hughes    newSubTable.ClassCount = newClassCount
2079*e1fe3e4aSElliott Hughes
2080*e1fe3e4aSElliott Hughes    oldSubTable.MarkArray.MarkRecord = oldMarkRecords
2081*e1fe3e4aSElliott Hughes    newSubTable.MarkArray = oldSubTable.MarkArray.__class__()
2082*e1fe3e4aSElliott Hughes    newSubTable.MarkArray.MarkRecord = newMarkRecords
2083*e1fe3e4aSElliott Hughes
2084*e1fe3e4aSElliott Hughes    oldSubTable.MarkArray.MarkCount = len(oldMarkRecords)
2085*e1fe3e4aSElliott Hughes    newSubTable.MarkArray.MarkCount = len(newMarkRecords)
2086*e1fe3e4aSElliott Hughes
2087*e1fe3e4aSElliott Hughes    oldSubTable.BaseArray.BaseRecord = oldBaseRecords
2088*e1fe3e4aSElliott Hughes    newSubTable.BaseArray = oldSubTable.BaseArray.__class__()
2089*e1fe3e4aSElliott Hughes    newSubTable.BaseArray.BaseRecord = newBaseRecords
2090*e1fe3e4aSElliott Hughes
2091*e1fe3e4aSElliott Hughes    oldSubTable.BaseArray.BaseCount = len(oldBaseRecords)
2092*e1fe3e4aSElliott Hughes    newSubTable.BaseArray.BaseCount = len(newBaseRecords)
2093*e1fe3e4aSElliott Hughes
2094*e1fe3e4aSElliott Hughes    return True
2095*e1fe3e4aSElliott Hughes
2096*e1fe3e4aSElliott Hughes
2097*e1fe3e4aSElliott HughessplitTable = {
2098*e1fe3e4aSElliott Hughes    "GSUB": {
2099*e1fe3e4aSElliott Hughes        # 					1: splitSingleSubst,
2100*e1fe3e4aSElliott Hughes        2: splitMultipleSubst,
2101*e1fe3e4aSElliott Hughes        3: splitAlternateSubst,
2102*e1fe3e4aSElliott Hughes        4: splitLigatureSubst,
2103*e1fe3e4aSElliott Hughes        # 					5: splitContextSubst,
2104*e1fe3e4aSElliott Hughes        # 					6: splitChainContextSubst,
2105*e1fe3e4aSElliott Hughes        # 					7: splitExtensionSubst,
2106*e1fe3e4aSElliott Hughes        # 					8: splitReverseChainSingleSubst,
2107*e1fe3e4aSElliott Hughes    },
2108*e1fe3e4aSElliott Hughes    "GPOS": {
2109*e1fe3e4aSElliott Hughes        # 					1: splitSinglePos,
2110*e1fe3e4aSElliott Hughes        2: splitPairPos,
2111*e1fe3e4aSElliott Hughes        # 					3: splitCursivePos,
2112*e1fe3e4aSElliott Hughes        4: splitMarkBasePos,
2113*e1fe3e4aSElliott Hughes        # 					5: splitMarkLigPos,
2114*e1fe3e4aSElliott Hughes        # 					6: splitMarkMarkPos,
2115*e1fe3e4aSElliott Hughes        # 					7: splitContextPos,
2116*e1fe3e4aSElliott Hughes        # 					8: splitChainContextPos,
2117*e1fe3e4aSElliott Hughes        # 					9: splitExtensionPos,
2118*e1fe3e4aSElliott Hughes    },
2119*e1fe3e4aSElliott Hughes}
2120*e1fe3e4aSElliott Hughes
2121*e1fe3e4aSElliott Hughes
2122*e1fe3e4aSElliott Hughesdef fixSubTableOverFlows(ttf, overflowRecord):
2123*e1fe3e4aSElliott Hughes    """
2124*e1fe3e4aSElliott Hughes    An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
2125*e1fe3e4aSElliott Hughes    """
2126*e1fe3e4aSElliott Hughes    table = ttf[overflowRecord.tableType].table
2127*e1fe3e4aSElliott Hughes    lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
2128*e1fe3e4aSElliott Hughes    subIndex = overflowRecord.SubTableIndex
2129*e1fe3e4aSElliott Hughes    subtable = lookup.SubTable[subIndex]
2130*e1fe3e4aSElliott Hughes
2131*e1fe3e4aSElliott Hughes    # First, try not sharing anything for this subtable...
2132*e1fe3e4aSElliott Hughes    if not hasattr(subtable, "DontShare"):
2133*e1fe3e4aSElliott Hughes        subtable.DontShare = True
2134*e1fe3e4aSElliott Hughes        return True
2135*e1fe3e4aSElliott Hughes
2136*e1fe3e4aSElliott Hughes    if hasattr(subtable, "ExtSubTable"):
2137*e1fe3e4aSElliott Hughes        # We split the subtable of the Extension table, and add a new Extension table
2138*e1fe3e4aSElliott Hughes        # to contain the new subtable.
2139*e1fe3e4aSElliott Hughes
2140*e1fe3e4aSElliott Hughes        subTableType = subtable.ExtSubTable.__class__.LookupType
2141*e1fe3e4aSElliott Hughes        extSubTable = subtable
2142*e1fe3e4aSElliott Hughes        subtable = extSubTable.ExtSubTable
2143*e1fe3e4aSElliott Hughes        newExtSubTableClass = lookupTypes[overflowRecord.tableType][
2144*e1fe3e4aSElliott Hughes            extSubTable.__class__.LookupType
2145*e1fe3e4aSElliott Hughes        ]
2146*e1fe3e4aSElliott Hughes        newExtSubTable = newExtSubTableClass()
2147*e1fe3e4aSElliott Hughes        newExtSubTable.Format = extSubTable.Format
2148*e1fe3e4aSElliott Hughes        toInsert = newExtSubTable
2149*e1fe3e4aSElliott Hughes
2150*e1fe3e4aSElliott Hughes        newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
2151*e1fe3e4aSElliott Hughes        newSubTable = newSubTableClass()
2152*e1fe3e4aSElliott Hughes        newExtSubTable.ExtSubTable = newSubTable
2153*e1fe3e4aSElliott Hughes    else:
2154*e1fe3e4aSElliott Hughes        subTableType = subtable.__class__.LookupType
2155*e1fe3e4aSElliott Hughes        newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
2156*e1fe3e4aSElliott Hughes        newSubTable = newSubTableClass()
2157*e1fe3e4aSElliott Hughes        toInsert = newSubTable
2158*e1fe3e4aSElliott Hughes
2159*e1fe3e4aSElliott Hughes    if hasattr(lookup, "SubTableCount"):  # may not be defined yet.
2160*e1fe3e4aSElliott Hughes        lookup.SubTableCount = lookup.SubTableCount + 1
2161*e1fe3e4aSElliott Hughes
2162*e1fe3e4aSElliott Hughes    try:
2163*e1fe3e4aSElliott Hughes        splitFunc = splitTable[overflowRecord.tableType][subTableType]
2164*e1fe3e4aSElliott Hughes    except KeyError:
2165*e1fe3e4aSElliott Hughes        log.error(
2166*e1fe3e4aSElliott Hughes            "Don't know how to split %s lookup type %s",
2167*e1fe3e4aSElliott Hughes            overflowRecord.tableType,
2168*e1fe3e4aSElliott Hughes            subTableType,
2169*e1fe3e4aSElliott Hughes        )
2170*e1fe3e4aSElliott Hughes        return False
2171*e1fe3e4aSElliott Hughes
2172*e1fe3e4aSElliott Hughes    ok = splitFunc(subtable, newSubTable, overflowRecord)
2173*e1fe3e4aSElliott Hughes    if ok:
2174*e1fe3e4aSElliott Hughes        lookup.SubTable.insert(subIndex + 1, toInsert)
2175*e1fe3e4aSElliott Hughes    return ok
2176*e1fe3e4aSElliott Hughes
2177*e1fe3e4aSElliott Hughes
2178*e1fe3e4aSElliott Hughes# End of OverFlow logic
2179*e1fe3e4aSElliott Hughes
2180*e1fe3e4aSElliott Hughes
2181*e1fe3e4aSElliott Hughesdef _buildClasses():
2182*e1fe3e4aSElliott Hughes    import re
2183*e1fe3e4aSElliott Hughes    from .otData import otData
2184*e1fe3e4aSElliott Hughes
2185*e1fe3e4aSElliott Hughes    formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$")
2186*e1fe3e4aSElliott Hughes    namespace = globals()
2187*e1fe3e4aSElliott Hughes
2188*e1fe3e4aSElliott Hughes    # populate module with classes
2189*e1fe3e4aSElliott Hughes    for name, table in otData:
2190*e1fe3e4aSElliott Hughes        baseClass = BaseTable
2191*e1fe3e4aSElliott Hughes        m = formatPat.match(name)
2192*e1fe3e4aSElliott Hughes        if m:
2193*e1fe3e4aSElliott Hughes            # XxxFormatN subtable, we only add the "base" table
2194*e1fe3e4aSElliott Hughes            name = m.group(1)
2195*e1fe3e4aSElliott Hughes            # the first row of a format-switching otData table describes the Format;
2196*e1fe3e4aSElliott Hughes            # the first column defines the type of the Format field.
2197*e1fe3e4aSElliott Hughes            # Currently this can be either 'uint16' or 'uint8'.
2198*e1fe3e4aSElliott Hughes            formatType = table[0][0]
2199*e1fe3e4aSElliott Hughes            baseClass = getFormatSwitchingBaseTableClass(formatType)
2200*e1fe3e4aSElliott Hughes        if name not in namespace:
2201*e1fe3e4aSElliott Hughes            # the class doesn't exist yet, so the base implementation is used.
2202*e1fe3e4aSElliott Hughes            cls = type(name, (baseClass,), {})
2203*e1fe3e4aSElliott Hughes            if name in ("GSUB", "GPOS"):
2204*e1fe3e4aSElliott Hughes                cls.DontShare = True
2205*e1fe3e4aSElliott Hughes            namespace[name] = cls
2206*e1fe3e4aSElliott Hughes
2207*e1fe3e4aSElliott Hughes    # link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.)
2208*e1fe3e4aSElliott Hughes    for name, _ in otData:
2209*e1fe3e4aSElliott Hughes        if name.startswith("Var") and len(name) > 3 and name[3:] in namespace:
2210*e1fe3e4aSElliott Hughes            varType = namespace[name]
2211*e1fe3e4aSElliott Hughes            noVarType = namespace[name[3:]]
2212*e1fe3e4aSElliott Hughes            varType.NoVarType = noVarType
2213*e1fe3e4aSElliott Hughes            noVarType.VarType = varType
2214*e1fe3e4aSElliott Hughes
2215*e1fe3e4aSElliott Hughes    for base, alts in _equivalents.items():
2216*e1fe3e4aSElliott Hughes        base = namespace[base]
2217*e1fe3e4aSElliott Hughes        for alt in alts:
2218*e1fe3e4aSElliott Hughes            namespace[alt] = base
2219*e1fe3e4aSElliott Hughes
2220*e1fe3e4aSElliott Hughes    global lookupTypes
2221*e1fe3e4aSElliott Hughes    lookupTypes = {
2222*e1fe3e4aSElliott Hughes        "GSUB": {
2223*e1fe3e4aSElliott Hughes            1: SingleSubst,
2224*e1fe3e4aSElliott Hughes            2: MultipleSubst,
2225*e1fe3e4aSElliott Hughes            3: AlternateSubst,
2226*e1fe3e4aSElliott Hughes            4: LigatureSubst,
2227*e1fe3e4aSElliott Hughes            5: ContextSubst,
2228*e1fe3e4aSElliott Hughes            6: ChainContextSubst,
2229*e1fe3e4aSElliott Hughes            7: ExtensionSubst,
2230*e1fe3e4aSElliott Hughes            8: ReverseChainSingleSubst,
2231*e1fe3e4aSElliott Hughes        },
2232*e1fe3e4aSElliott Hughes        "GPOS": {
2233*e1fe3e4aSElliott Hughes            1: SinglePos,
2234*e1fe3e4aSElliott Hughes            2: PairPos,
2235*e1fe3e4aSElliott Hughes            3: CursivePos,
2236*e1fe3e4aSElliott Hughes            4: MarkBasePos,
2237*e1fe3e4aSElliott Hughes            5: MarkLigPos,
2238*e1fe3e4aSElliott Hughes            6: MarkMarkPos,
2239*e1fe3e4aSElliott Hughes            7: ContextPos,
2240*e1fe3e4aSElliott Hughes            8: ChainContextPos,
2241*e1fe3e4aSElliott Hughes            9: ExtensionPos,
2242*e1fe3e4aSElliott Hughes        },
2243*e1fe3e4aSElliott Hughes        "mort": {
2244*e1fe3e4aSElliott Hughes            4: NoncontextualMorph,
2245*e1fe3e4aSElliott Hughes        },
2246*e1fe3e4aSElliott Hughes        "morx": {
2247*e1fe3e4aSElliott Hughes            0: RearrangementMorph,
2248*e1fe3e4aSElliott Hughes            1: ContextualMorph,
2249*e1fe3e4aSElliott Hughes            2: LigatureMorph,
2250*e1fe3e4aSElliott Hughes            # 3: Reserved,
2251*e1fe3e4aSElliott Hughes            4: NoncontextualMorph,
2252*e1fe3e4aSElliott Hughes            5: InsertionMorph,
2253*e1fe3e4aSElliott Hughes        },
2254*e1fe3e4aSElliott Hughes    }
2255*e1fe3e4aSElliott Hughes    lookupTypes["JSTF"] = lookupTypes["GPOS"]  # JSTF contains GPOS
2256*e1fe3e4aSElliott Hughes    for lookupEnum in lookupTypes.values():
2257*e1fe3e4aSElliott Hughes        for enum, cls in lookupEnum.items():
2258*e1fe3e4aSElliott Hughes            cls.LookupType = enum
2259*e1fe3e4aSElliott Hughes
2260*e1fe3e4aSElliott Hughes    global featureParamTypes
2261*e1fe3e4aSElliott Hughes    featureParamTypes = {
2262*e1fe3e4aSElliott Hughes        "size": FeatureParamsSize,
2263*e1fe3e4aSElliott Hughes    }
2264*e1fe3e4aSElliott Hughes    for i in range(1, 20 + 1):
2265*e1fe3e4aSElliott Hughes        featureParamTypes["ss%02d" % i] = FeatureParamsStylisticSet
2266*e1fe3e4aSElliott Hughes    for i in range(1, 99 + 1):
2267*e1fe3e4aSElliott Hughes        featureParamTypes["cv%02d" % i] = FeatureParamsCharacterVariants
2268*e1fe3e4aSElliott Hughes
2269*e1fe3e4aSElliott Hughes    # add converters to classes
2270*e1fe3e4aSElliott Hughes    from .otConverters import buildConverters
2271*e1fe3e4aSElliott Hughes
2272*e1fe3e4aSElliott Hughes    for name, table in otData:
2273*e1fe3e4aSElliott Hughes        m = formatPat.match(name)
2274*e1fe3e4aSElliott Hughes        if m:
2275*e1fe3e4aSElliott Hughes            # XxxFormatN subtable, add converter to "base" table
2276*e1fe3e4aSElliott Hughes            name, format = m.groups()
2277*e1fe3e4aSElliott Hughes            format = int(format)
2278*e1fe3e4aSElliott Hughes            cls = namespace[name]
2279*e1fe3e4aSElliott Hughes            if not hasattr(cls, "converters"):
2280*e1fe3e4aSElliott Hughes                cls.converters = {}
2281*e1fe3e4aSElliott Hughes                cls.convertersByName = {}
2282*e1fe3e4aSElliott Hughes            converters, convertersByName = buildConverters(table[1:], namespace)
2283*e1fe3e4aSElliott Hughes            cls.converters[format] = converters
2284*e1fe3e4aSElliott Hughes            cls.convertersByName[format] = convertersByName
2285*e1fe3e4aSElliott Hughes            # XXX Add staticSize?
2286*e1fe3e4aSElliott Hughes        else:
2287*e1fe3e4aSElliott Hughes            cls = namespace[name]
2288*e1fe3e4aSElliott Hughes            cls.converters, cls.convertersByName = buildConverters(table, namespace)
2289*e1fe3e4aSElliott Hughes            # XXX Add staticSize?
2290*e1fe3e4aSElliott Hughes
2291*e1fe3e4aSElliott Hughes
2292*e1fe3e4aSElliott Hughes_buildClasses()
2293*e1fe3e4aSElliott Hughes
2294*e1fe3e4aSElliott Hughes
2295*e1fe3e4aSElliott Hughesdef _getGlyphsFromCoverageTable(coverage):
2296*e1fe3e4aSElliott Hughes    if coverage is None:
2297*e1fe3e4aSElliott Hughes        # empty coverage table
2298*e1fe3e4aSElliott Hughes        return []
2299*e1fe3e4aSElliott Hughes    else:
2300*e1fe3e4aSElliott Hughes        return coverage.glyphs
2301