xref: /aosp_15_r20/external/fonttools/Lib/fontTools/feaLib/ast.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.error import FeatureLibError
2*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.location import FeatureLibLocation
3*e1fe3e4aSElliott Hughesfrom fontTools.misc.encodingTools import getEncoding
4*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import byteord, tobytes
5*e1fe3e4aSElliott Hughesfrom collections import OrderedDict
6*e1fe3e4aSElliott Hughesimport itertools
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott HughesSHIFT = " " * 4
9*e1fe3e4aSElliott Hughes
10*e1fe3e4aSElliott Hughes__all__ = [
11*e1fe3e4aSElliott Hughes    "Element",
12*e1fe3e4aSElliott Hughes    "FeatureFile",
13*e1fe3e4aSElliott Hughes    "Comment",
14*e1fe3e4aSElliott Hughes    "GlyphName",
15*e1fe3e4aSElliott Hughes    "GlyphClass",
16*e1fe3e4aSElliott Hughes    "GlyphClassName",
17*e1fe3e4aSElliott Hughes    "MarkClassName",
18*e1fe3e4aSElliott Hughes    "AnonymousBlock",
19*e1fe3e4aSElliott Hughes    "Block",
20*e1fe3e4aSElliott Hughes    "FeatureBlock",
21*e1fe3e4aSElliott Hughes    "NestedBlock",
22*e1fe3e4aSElliott Hughes    "LookupBlock",
23*e1fe3e4aSElliott Hughes    "GlyphClassDefinition",
24*e1fe3e4aSElliott Hughes    "GlyphClassDefStatement",
25*e1fe3e4aSElliott Hughes    "MarkClass",
26*e1fe3e4aSElliott Hughes    "MarkClassDefinition",
27*e1fe3e4aSElliott Hughes    "AlternateSubstStatement",
28*e1fe3e4aSElliott Hughes    "Anchor",
29*e1fe3e4aSElliott Hughes    "AnchorDefinition",
30*e1fe3e4aSElliott Hughes    "AttachStatement",
31*e1fe3e4aSElliott Hughes    "AxisValueLocationStatement",
32*e1fe3e4aSElliott Hughes    "BaseAxis",
33*e1fe3e4aSElliott Hughes    "CVParametersNameStatement",
34*e1fe3e4aSElliott Hughes    "ChainContextPosStatement",
35*e1fe3e4aSElliott Hughes    "ChainContextSubstStatement",
36*e1fe3e4aSElliott Hughes    "CharacterStatement",
37*e1fe3e4aSElliott Hughes    "ConditionsetStatement",
38*e1fe3e4aSElliott Hughes    "CursivePosStatement",
39*e1fe3e4aSElliott Hughes    "ElidedFallbackName",
40*e1fe3e4aSElliott Hughes    "ElidedFallbackNameID",
41*e1fe3e4aSElliott Hughes    "Expression",
42*e1fe3e4aSElliott Hughes    "FeatureNameStatement",
43*e1fe3e4aSElliott Hughes    "FeatureReferenceStatement",
44*e1fe3e4aSElliott Hughes    "FontRevisionStatement",
45*e1fe3e4aSElliott Hughes    "HheaField",
46*e1fe3e4aSElliott Hughes    "IgnorePosStatement",
47*e1fe3e4aSElliott Hughes    "IgnoreSubstStatement",
48*e1fe3e4aSElliott Hughes    "IncludeStatement",
49*e1fe3e4aSElliott Hughes    "LanguageStatement",
50*e1fe3e4aSElliott Hughes    "LanguageSystemStatement",
51*e1fe3e4aSElliott Hughes    "LigatureCaretByIndexStatement",
52*e1fe3e4aSElliott Hughes    "LigatureCaretByPosStatement",
53*e1fe3e4aSElliott Hughes    "LigatureSubstStatement",
54*e1fe3e4aSElliott Hughes    "LookupFlagStatement",
55*e1fe3e4aSElliott Hughes    "LookupReferenceStatement",
56*e1fe3e4aSElliott Hughes    "MarkBasePosStatement",
57*e1fe3e4aSElliott Hughes    "MarkLigPosStatement",
58*e1fe3e4aSElliott Hughes    "MarkMarkPosStatement",
59*e1fe3e4aSElliott Hughes    "MultipleSubstStatement",
60*e1fe3e4aSElliott Hughes    "NameRecord",
61*e1fe3e4aSElliott Hughes    "OS2Field",
62*e1fe3e4aSElliott Hughes    "PairPosStatement",
63*e1fe3e4aSElliott Hughes    "ReverseChainSingleSubstStatement",
64*e1fe3e4aSElliott Hughes    "ScriptStatement",
65*e1fe3e4aSElliott Hughes    "SinglePosStatement",
66*e1fe3e4aSElliott Hughes    "SingleSubstStatement",
67*e1fe3e4aSElliott Hughes    "SizeParameters",
68*e1fe3e4aSElliott Hughes    "Statement",
69*e1fe3e4aSElliott Hughes    "STATAxisValueStatement",
70*e1fe3e4aSElliott Hughes    "STATDesignAxisStatement",
71*e1fe3e4aSElliott Hughes    "STATNameStatement",
72*e1fe3e4aSElliott Hughes    "SubtableStatement",
73*e1fe3e4aSElliott Hughes    "TableBlock",
74*e1fe3e4aSElliott Hughes    "ValueRecord",
75*e1fe3e4aSElliott Hughes    "ValueRecordDefinition",
76*e1fe3e4aSElliott Hughes    "VheaField",
77*e1fe3e4aSElliott Hughes]
78*e1fe3e4aSElliott Hughes
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughesdef deviceToString(device):
81*e1fe3e4aSElliott Hughes    if device is None:
82*e1fe3e4aSElliott Hughes        return "<device NULL>"
83*e1fe3e4aSElliott Hughes    else:
84*e1fe3e4aSElliott Hughes        return "<device %s>" % ", ".join("%d %d" % t for t in device)
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes
87*e1fe3e4aSElliott Hughesfea_keywords = set(
88*e1fe3e4aSElliott Hughes    [
89*e1fe3e4aSElliott Hughes        "anchor",
90*e1fe3e4aSElliott Hughes        "anchordef",
91*e1fe3e4aSElliott Hughes        "anon",
92*e1fe3e4aSElliott Hughes        "anonymous",
93*e1fe3e4aSElliott Hughes        "by",
94*e1fe3e4aSElliott Hughes        "contour",
95*e1fe3e4aSElliott Hughes        "cursive",
96*e1fe3e4aSElliott Hughes        "device",
97*e1fe3e4aSElliott Hughes        "enum",
98*e1fe3e4aSElliott Hughes        "enumerate",
99*e1fe3e4aSElliott Hughes        "excludedflt",
100*e1fe3e4aSElliott Hughes        "exclude_dflt",
101*e1fe3e4aSElliott Hughes        "feature",
102*e1fe3e4aSElliott Hughes        "from",
103*e1fe3e4aSElliott Hughes        "ignore",
104*e1fe3e4aSElliott Hughes        "ignorebaseglyphs",
105*e1fe3e4aSElliott Hughes        "ignoreligatures",
106*e1fe3e4aSElliott Hughes        "ignoremarks",
107*e1fe3e4aSElliott Hughes        "include",
108*e1fe3e4aSElliott Hughes        "includedflt",
109*e1fe3e4aSElliott Hughes        "include_dflt",
110*e1fe3e4aSElliott Hughes        "language",
111*e1fe3e4aSElliott Hughes        "languagesystem",
112*e1fe3e4aSElliott Hughes        "lookup",
113*e1fe3e4aSElliott Hughes        "lookupflag",
114*e1fe3e4aSElliott Hughes        "mark",
115*e1fe3e4aSElliott Hughes        "markattachmenttype",
116*e1fe3e4aSElliott Hughes        "markclass",
117*e1fe3e4aSElliott Hughes        "nameid",
118*e1fe3e4aSElliott Hughes        "null",
119*e1fe3e4aSElliott Hughes        "parameters",
120*e1fe3e4aSElliott Hughes        "pos",
121*e1fe3e4aSElliott Hughes        "position",
122*e1fe3e4aSElliott Hughes        "required",
123*e1fe3e4aSElliott Hughes        "righttoleft",
124*e1fe3e4aSElliott Hughes        "reversesub",
125*e1fe3e4aSElliott Hughes        "rsub",
126*e1fe3e4aSElliott Hughes        "script",
127*e1fe3e4aSElliott Hughes        "sub",
128*e1fe3e4aSElliott Hughes        "substitute",
129*e1fe3e4aSElliott Hughes        "subtable",
130*e1fe3e4aSElliott Hughes        "table",
131*e1fe3e4aSElliott Hughes        "usemarkfilteringset",
132*e1fe3e4aSElliott Hughes        "useextension",
133*e1fe3e4aSElliott Hughes        "valuerecorddef",
134*e1fe3e4aSElliott Hughes        "base",
135*e1fe3e4aSElliott Hughes        "gdef",
136*e1fe3e4aSElliott Hughes        "head",
137*e1fe3e4aSElliott Hughes        "hhea",
138*e1fe3e4aSElliott Hughes        "name",
139*e1fe3e4aSElliott Hughes        "vhea",
140*e1fe3e4aSElliott Hughes        "vmtx",
141*e1fe3e4aSElliott Hughes    ]
142*e1fe3e4aSElliott Hughes)
143*e1fe3e4aSElliott Hughes
144*e1fe3e4aSElliott Hughes
145*e1fe3e4aSElliott Hughesdef asFea(g):
146*e1fe3e4aSElliott Hughes    if hasattr(g, "asFea"):
147*e1fe3e4aSElliott Hughes        return g.asFea()
148*e1fe3e4aSElliott Hughes    elif isinstance(g, tuple) and len(g) == 2:
149*e1fe3e4aSElliott Hughes        return asFea(g[0]) + " - " + asFea(g[1])  # a range
150*e1fe3e4aSElliott Hughes    elif g.lower() in fea_keywords:
151*e1fe3e4aSElliott Hughes        return "\\" + g
152*e1fe3e4aSElliott Hughes    else:
153*e1fe3e4aSElliott Hughes        return g
154*e1fe3e4aSElliott Hughes
155*e1fe3e4aSElliott Hughes
156*e1fe3e4aSElliott Hughesclass Element(object):
157*e1fe3e4aSElliott Hughes    """A base class representing "something" in a feature file."""
158*e1fe3e4aSElliott Hughes
159*e1fe3e4aSElliott Hughes    def __init__(self, location=None):
160*e1fe3e4aSElliott Hughes        #: location of this element as a `FeatureLibLocation` object.
161*e1fe3e4aSElliott Hughes        if location and not isinstance(location, FeatureLibLocation):
162*e1fe3e4aSElliott Hughes            location = FeatureLibLocation(*location)
163*e1fe3e4aSElliott Hughes        self.location = location
164*e1fe3e4aSElliott Hughes
165*e1fe3e4aSElliott Hughes    def build(self, builder):
166*e1fe3e4aSElliott Hughes        pass
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
169*e1fe3e4aSElliott Hughes        """Returns this element as a string of feature code. For block-type
170*e1fe3e4aSElliott Hughes        elements (such as :class:`FeatureBlock`), the `indent` string is
171*e1fe3e4aSElliott Hughes        added to the start of each line in the output."""
172*e1fe3e4aSElliott Hughes        raise NotImplementedError
173*e1fe3e4aSElliott Hughes
174*e1fe3e4aSElliott Hughes    def __str__(self):
175*e1fe3e4aSElliott Hughes        return self.asFea()
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott Hughesclass Statement(Element):
179*e1fe3e4aSElliott Hughes    pass
180*e1fe3e4aSElliott Hughes
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughesclass Expression(Element):
183*e1fe3e4aSElliott Hughes    pass
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes
186*e1fe3e4aSElliott Hughesclass Comment(Element):
187*e1fe3e4aSElliott Hughes    """A comment in a feature file."""
188*e1fe3e4aSElliott Hughes
189*e1fe3e4aSElliott Hughes    def __init__(self, text, location=None):
190*e1fe3e4aSElliott Hughes        super(Comment, self).__init__(location)
191*e1fe3e4aSElliott Hughes        #: Text of the comment
192*e1fe3e4aSElliott Hughes        self.text = text
193*e1fe3e4aSElliott Hughes
194*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
195*e1fe3e4aSElliott Hughes        return self.text
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes
198*e1fe3e4aSElliott Hughesclass NullGlyph(Expression):
199*e1fe3e4aSElliott Hughes    """The NULL glyph, used in glyph deletion substitutions."""
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes    def __init__(self, location=None):
202*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
203*e1fe3e4aSElliott Hughes        #: The name itself as a string
204*e1fe3e4aSElliott Hughes
205*e1fe3e4aSElliott Hughes    def glyphSet(self):
206*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
207*e1fe3e4aSElliott Hughes        return ()
208*e1fe3e4aSElliott Hughes
209*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
210*e1fe3e4aSElliott Hughes        return "NULL"
211*e1fe3e4aSElliott Hughes
212*e1fe3e4aSElliott Hughes
213*e1fe3e4aSElliott Hughesclass GlyphName(Expression):
214*e1fe3e4aSElliott Hughes    """A single glyph name, such as ``cedilla``."""
215*e1fe3e4aSElliott Hughes
216*e1fe3e4aSElliott Hughes    def __init__(self, glyph, location=None):
217*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
218*e1fe3e4aSElliott Hughes        #: The name itself as a string
219*e1fe3e4aSElliott Hughes        self.glyph = glyph
220*e1fe3e4aSElliott Hughes
221*e1fe3e4aSElliott Hughes    def glyphSet(self):
222*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
223*e1fe3e4aSElliott Hughes        return (self.glyph,)
224*e1fe3e4aSElliott Hughes
225*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
226*e1fe3e4aSElliott Hughes        return asFea(self.glyph)
227*e1fe3e4aSElliott Hughes
228*e1fe3e4aSElliott Hughes
229*e1fe3e4aSElliott Hughesclass GlyphClass(Expression):
230*e1fe3e4aSElliott Hughes    """A glyph class, such as ``[acute cedilla grave]``."""
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes    def __init__(self, glyphs=None, location=None):
233*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
234*e1fe3e4aSElliott Hughes        #: The list of glyphs in this class, as :class:`GlyphName` objects.
235*e1fe3e4aSElliott Hughes        self.glyphs = glyphs if glyphs is not None else []
236*e1fe3e4aSElliott Hughes        self.original = []
237*e1fe3e4aSElliott Hughes        self.curr = 0
238*e1fe3e4aSElliott Hughes
239*e1fe3e4aSElliott Hughes    def glyphSet(self):
240*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
241*e1fe3e4aSElliott Hughes        return tuple(self.glyphs)
242*e1fe3e4aSElliott Hughes
243*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
244*e1fe3e4aSElliott Hughes        if len(self.original):
245*e1fe3e4aSElliott Hughes            if self.curr < len(self.glyphs):
246*e1fe3e4aSElliott Hughes                self.original.extend(self.glyphs[self.curr :])
247*e1fe3e4aSElliott Hughes                self.curr = len(self.glyphs)
248*e1fe3e4aSElliott Hughes            return "[" + " ".join(map(asFea, self.original)) + "]"
249*e1fe3e4aSElliott Hughes        else:
250*e1fe3e4aSElliott Hughes            return "[" + " ".join(map(asFea, self.glyphs)) + "]"
251*e1fe3e4aSElliott Hughes
252*e1fe3e4aSElliott Hughes    def extend(self, glyphs):
253*e1fe3e4aSElliott Hughes        """Add a list of :class:`GlyphName` objects to the class."""
254*e1fe3e4aSElliott Hughes        self.glyphs.extend(glyphs)
255*e1fe3e4aSElliott Hughes
256*e1fe3e4aSElliott Hughes    def append(self, glyph):
257*e1fe3e4aSElliott Hughes        """Add a single :class:`GlyphName` object to the class."""
258*e1fe3e4aSElliott Hughes        self.glyphs.append(glyph)
259*e1fe3e4aSElliott Hughes
260*e1fe3e4aSElliott Hughes    def add_range(self, start, end, glyphs):
261*e1fe3e4aSElliott Hughes        """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
262*e1fe3e4aSElliott Hughes        are either :class:`GlyphName` objects or strings representing the
263*e1fe3e4aSElliott Hughes        start and end glyphs in the class, and ``glyphs`` is the full list of
264*e1fe3e4aSElliott Hughes        :class:`GlyphName` objects in the range."""
265*e1fe3e4aSElliott Hughes        if self.curr < len(self.glyphs):
266*e1fe3e4aSElliott Hughes            self.original.extend(self.glyphs[self.curr :])
267*e1fe3e4aSElliott Hughes        self.original.append((start, end))
268*e1fe3e4aSElliott Hughes        self.glyphs.extend(glyphs)
269*e1fe3e4aSElliott Hughes        self.curr = len(self.glyphs)
270*e1fe3e4aSElliott Hughes
271*e1fe3e4aSElliott Hughes    def add_cid_range(self, start, end, glyphs):
272*e1fe3e4aSElliott Hughes        """Add a range to the class by glyph ID. ``start`` and ``end`` are the
273*e1fe3e4aSElliott Hughes        initial and final IDs, and ``glyphs`` is the full list of
274*e1fe3e4aSElliott Hughes        :class:`GlyphName` objects in the range."""
275*e1fe3e4aSElliott Hughes        if self.curr < len(self.glyphs):
276*e1fe3e4aSElliott Hughes            self.original.extend(self.glyphs[self.curr :])
277*e1fe3e4aSElliott Hughes        self.original.append(("\\{}".format(start), "\\{}".format(end)))
278*e1fe3e4aSElliott Hughes        self.glyphs.extend(glyphs)
279*e1fe3e4aSElliott Hughes        self.curr = len(self.glyphs)
280*e1fe3e4aSElliott Hughes
281*e1fe3e4aSElliott Hughes    def add_class(self, gc):
282*e1fe3e4aSElliott Hughes        """Add glyphs from the given :class:`GlyphClassName` object to the
283*e1fe3e4aSElliott Hughes        class."""
284*e1fe3e4aSElliott Hughes        if self.curr < len(self.glyphs):
285*e1fe3e4aSElliott Hughes            self.original.extend(self.glyphs[self.curr :])
286*e1fe3e4aSElliott Hughes        self.original.append(gc)
287*e1fe3e4aSElliott Hughes        self.glyphs.extend(gc.glyphSet())
288*e1fe3e4aSElliott Hughes        self.curr = len(self.glyphs)
289*e1fe3e4aSElliott Hughes
290*e1fe3e4aSElliott Hughes
291*e1fe3e4aSElliott Hughesclass GlyphClassName(Expression):
292*e1fe3e4aSElliott Hughes    """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
293*e1fe3e4aSElliott Hughes    with a :class:`GlyphClassDefinition` object."""
294*e1fe3e4aSElliott Hughes
295*e1fe3e4aSElliott Hughes    def __init__(self, glyphclass, location=None):
296*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
297*e1fe3e4aSElliott Hughes        assert isinstance(glyphclass, GlyphClassDefinition)
298*e1fe3e4aSElliott Hughes        self.glyphclass = glyphclass
299*e1fe3e4aSElliott Hughes
300*e1fe3e4aSElliott Hughes    def glyphSet(self):
301*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
302*e1fe3e4aSElliott Hughes        return tuple(self.glyphclass.glyphSet())
303*e1fe3e4aSElliott Hughes
304*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
305*e1fe3e4aSElliott Hughes        return "@" + self.glyphclass.name
306*e1fe3e4aSElliott Hughes
307*e1fe3e4aSElliott Hughes
308*e1fe3e4aSElliott Hughesclass MarkClassName(Expression):
309*e1fe3e4aSElliott Hughes    """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
310*e1fe3e4aSElliott Hughes    This must be instantiated with a :class:`MarkClass` object."""
311*e1fe3e4aSElliott Hughes
312*e1fe3e4aSElliott Hughes    def __init__(self, markClass, location=None):
313*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
314*e1fe3e4aSElliott Hughes        assert isinstance(markClass, MarkClass)
315*e1fe3e4aSElliott Hughes        self.markClass = markClass
316*e1fe3e4aSElliott Hughes
317*e1fe3e4aSElliott Hughes    def glyphSet(self):
318*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
319*e1fe3e4aSElliott Hughes        return self.markClass.glyphSet()
320*e1fe3e4aSElliott Hughes
321*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
322*e1fe3e4aSElliott Hughes        return "@" + self.markClass.name
323*e1fe3e4aSElliott Hughes
324*e1fe3e4aSElliott Hughes
325*e1fe3e4aSElliott Hughesclass AnonymousBlock(Statement):
326*e1fe3e4aSElliott Hughes    """An anonymous data block."""
327*e1fe3e4aSElliott Hughes
328*e1fe3e4aSElliott Hughes    def __init__(self, tag, content, location=None):
329*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
330*e1fe3e4aSElliott Hughes        self.tag = tag  #: string containing the block's "tag"
331*e1fe3e4aSElliott Hughes        self.content = content  #: block data as string
332*e1fe3e4aSElliott Hughes
333*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
334*e1fe3e4aSElliott Hughes        res = "anon {} {{\n".format(self.tag)
335*e1fe3e4aSElliott Hughes        res += self.content
336*e1fe3e4aSElliott Hughes        res += "}} {};\n\n".format(self.tag)
337*e1fe3e4aSElliott Hughes        return res
338*e1fe3e4aSElliott Hughes
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughesclass Block(Statement):
341*e1fe3e4aSElliott Hughes    """A block of statements: feature, lookup, etc."""
342*e1fe3e4aSElliott Hughes
343*e1fe3e4aSElliott Hughes    def __init__(self, location=None):
344*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
345*e1fe3e4aSElliott Hughes        self.statements = []  #: Statements contained in the block
346*e1fe3e4aSElliott Hughes
347*e1fe3e4aSElliott Hughes    def build(self, builder):
348*e1fe3e4aSElliott Hughes        """When handed a 'builder' object of comparable interface to
349*e1fe3e4aSElliott Hughes        :class:`fontTools.feaLib.builder`, walks the statements in this
350*e1fe3e4aSElliott Hughes        block, calling the builder callbacks."""
351*e1fe3e4aSElliott Hughes        for s in self.statements:
352*e1fe3e4aSElliott Hughes            s.build(builder)
353*e1fe3e4aSElliott Hughes
354*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
355*e1fe3e4aSElliott Hughes        indent += SHIFT
356*e1fe3e4aSElliott Hughes        return (
357*e1fe3e4aSElliott Hughes            indent
358*e1fe3e4aSElliott Hughes            + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
359*e1fe3e4aSElliott Hughes            + "\n"
360*e1fe3e4aSElliott Hughes        )
361*e1fe3e4aSElliott Hughes
362*e1fe3e4aSElliott Hughes
363*e1fe3e4aSElliott Hughesclass FeatureFile(Block):
364*e1fe3e4aSElliott Hughes    """The top-level element of the syntax tree, containing the whole feature
365*e1fe3e4aSElliott Hughes    file in its ``statements`` attribute."""
366*e1fe3e4aSElliott Hughes
367*e1fe3e4aSElliott Hughes    def __init__(self):
368*e1fe3e4aSElliott Hughes        Block.__init__(self, location=None)
369*e1fe3e4aSElliott Hughes        self.markClasses = {}  # name --> ast.MarkClass
370*e1fe3e4aSElliott Hughes
371*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
372*e1fe3e4aSElliott Hughes        return "\n".join(s.asFea(indent=indent) for s in self.statements)
373*e1fe3e4aSElliott Hughes
374*e1fe3e4aSElliott Hughes
375*e1fe3e4aSElliott Hughesclass FeatureBlock(Block):
376*e1fe3e4aSElliott Hughes    """A named feature block."""
377*e1fe3e4aSElliott Hughes
378*e1fe3e4aSElliott Hughes    def __init__(self, name, use_extension=False, location=None):
379*e1fe3e4aSElliott Hughes        Block.__init__(self, location)
380*e1fe3e4aSElliott Hughes        self.name, self.use_extension = name, use_extension
381*e1fe3e4aSElliott Hughes
382*e1fe3e4aSElliott Hughes    def build(self, builder):
383*e1fe3e4aSElliott Hughes        """Call the ``start_feature`` callback on the builder object, visit
384*e1fe3e4aSElliott Hughes        all the statements in this feature, and then call ``end_feature``."""
385*e1fe3e4aSElliott Hughes        # TODO(sascha): Handle use_extension.
386*e1fe3e4aSElliott Hughes        builder.start_feature(self.location, self.name)
387*e1fe3e4aSElliott Hughes        # language exclude_dflt statements modify builder.features_
388*e1fe3e4aSElliott Hughes        # limit them to this block with temporary builder.features_
389*e1fe3e4aSElliott Hughes        features = builder.features_
390*e1fe3e4aSElliott Hughes        builder.features_ = {}
391*e1fe3e4aSElliott Hughes        Block.build(self, builder)
392*e1fe3e4aSElliott Hughes        for key, value in builder.features_.items():
393*e1fe3e4aSElliott Hughes            features.setdefault(key, []).extend(value)
394*e1fe3e4aSElliott Hughes        builder.features_ = features
395*e1fe3e4aSElliott Hughes        builder.end_feature()
396*e1fe3e4aSElliott Hughes
397*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
398*e1fe3e4aSElliott Hughes        res = indent + "feature %s " % self.name.strip()
399*e1fe3e4aSElliott Hughes        if self.use_extension:
400*e1fe3e4aSElliott Hughes            res += "useExtension "
401*e1fe3e4aSElliott Hughes        res += "{\n"
402*e1fe3e4aSElliott Hughes        res += Block.asFea(self, indent=indent)
403*e1fe3e4aSElliott Hughes        res += indent + "} %s;\n" % self.name.strip()
404*e1fe3e4aSElliott Hughes        return res
405*e1fe3e4aSElliott Hughes
406*e1fe3e4aSElliott Hughes
407*e1fe3e4aSElliott Hughesclass NestedBlock(Block):
408*e1fe3e4aSElliott Hughes    """A block inside another block, for example when found inside a
409*e1fe3e4aSElliott Hughes    ``cvParameters`` block."""
410*e1fe3e4aSElliott Hughes
411*e1fe3e4aSElliott Hughes    def __init__(self, tag, block_name, location=None):
412*e1fe3e4aSElliott Hughes        Block.__init__(self, location)
413*e1fe3e4aSElliott Hughes        self.tag = tag
414*e1fe3e4aSElliott Hughes        self.block_name = block_name
415*e1fe3e4aSElliott Hughes
416*e1fe3e4aSElliott Hughes    def build(self, builder):
417*e1fe3e4aSElliott Hughes        Block.build(self, builder)
418*e1fe3e4aSElliott Hughes        if self.block_name == "ParamUILabelNameID":
419*e1fe3e4aSElliott Hughes            builder.add_to_cv_num_named_params(self.tag)
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
422*e1fe3e4aSElliott Hughes        res = "{}{} {{\n".format(indent, self.block_name)
423*e1fe3e4aSElliott Hughes        res += Block.asFea(self, indent=indent)
424*e1fe3e4aSElliott Hughes        res += "{}}};\n".format(indent)
425*e1fe3e4aSElliott Hughes        return res
426*e1fe3e4aSElliott Hughes
427*e1fe3e4aSElliott Hughes
428*e1fe3e4aSElliott Hughesclass LookupBlock(Block):
429*e1fe3e4aSElliott Hughes    """A named lookup, containing ``statements``."""
430*e1fe3e4aSElliott Hughes
431*e1fe3e4aSElliott Hughes    def __init__(self, name, use_extension=False, location=None):
432*e1fe3e4aSElliott Hughes        Block.__init__(self, location)
433*e1fe3e4aSElliott Hughes        self.name, self.use_extension = name, use_extension
434*e1fe3e4aSElliott Hughes
435*e1fe3e4aSElliott Hughes    def build(self, builder):
436*e1fe3e4aSElliott Hughes        # TODO(sascha): Handle use_extension.
437*e1fe3e4aSElliott Hughes        builder.start_lookup_block(self.location, self.name)
438*e1fe3e4aSElliott Hughes        Block.build(self, builder)
439*e1fe3e4aSElliott Hughes        builder.end_lookup_block()
440*e1fe3e4aSElliott Hughes
441*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
442*e1fe3e4aSElliott Hughes        res = "lookup {} ".format(self.name)
443*e1fe3e4aSElliott Hughes        if self.use_extension:
444*e1fe3e4aSElliott Hughes            res += "useExtension "
445*e1fe3e4aSElliott Hughes        res += "{\n"
446*e1fe3e4aSElliott Hughes        res += Block.asFea(self, indent=indent)
447*e1fe3e4aSElliott Hughes        res += "{}}} {};\n".format(indent, self.name)
448*e1fe3e4aSElliott Hughes        return res
449*e1fe3e4aSElliott Hughes
450*e1fe3e4aSElliott Hughes
451*e1fe3e4aSElliott Hughesclass TableBlock(Block):
452*e1fe3e4aSElliott Hughes    """A ``table ... { }`` block."""
453*e1fe3e4aSElliott Hughes
454*e1fe3e4aSElliott Hughes    def __init__(self, name, location=None):
455*e1fe3e4aSElliott Hughes        Block.__init__(self, location)
456*e1fe3e4aSElliott Hughes        self.name = name
457*e1fe3e4aSElliott Hughes
458*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
459*e1fe3e4aSElliott Hughes        res = "table {} {{\n".format(self.name.strip())
460*e1fe3e4aSElliott Hughes        res += super(TableBlock, self).asFea(indent=indent)
461*e1fe3e4aSElliott Hughes        res += "}} {};\n".format(self.name.strip())
462*e1fe3e4aSElliott Hughes        return res
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes
465*e1fe3e4aSElliott Hughesclass GlyphClassDefinition(Statement):
466*e1fe3e4aSElliott Hughes    """Example: ``@UPPERCASE = [A-Z];``."""
467*e1fe3e4aSElliott Hughes
468*e1fe3e4aSElliott Hughes    def __init__(self, name, glyphs, location=None):
469*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
470*e1fe3e4aSElliott Hughes        self.name = name  #: class name as a string, without initial ``@``
471*e1fe3e4aSElliott Hughes        self.glyphs = glyphs  #: a :class:`GlyphClass` object
472*e1fe3e4aSElliott Hughes
473*e1fe3e4aSElliott Hughes    def glyphSet(self):
474*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
475*e1fe3e4aSElliott Hughes        return tuple(self.glyphs.glyphSet())
476*e1fe3e4aSElliott Hughes
477*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
478*e1fe3e4aSElliott Hughes        return "@" + self.name + " = " + self.glyphs.asFea() + ";"
479*e1fe3e4aSElliott Hughes
480*e1fe3e4aSElliott Hughes
481*e1fe3e4aSElliott Hughesclass GlyphClassDefStatement(Statement):
482*e1fe3e4aSElliott Hughes    """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
483*e1fe3e4aSElliott Hughes    must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
484*e1fe3e4aSElliott Hughes    ``None``."""
485*e1fe3e4aSElliott Hughes
486*e1fe3e4aSElliott Hughes    def __init__(
487*e1fe3e4aSElliott Hughes        self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
488*e1fe3e4aSElliott Hughes    ):
489*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
490*e1fe3e4aSElliott Hughes        self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
491*e1fe3e4aSElliott Hughes        self.ligatureGlyphs = ligatureGlyphs
492*e1fe3e4aSElliott Hughes        self.componentGlyphs = componentGlyphs
493*e1fe3e4aSElliott Hughes
494*e1fe3e4aSElliott Hughes    def build(self, builder):
495*e1fe3e4aSElliott Hughes        """Calls the builder's ``add_glyphClassDef`` callback."""
496*e1fe3e4aSElliott Hughes        base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
497*e1fe3e4aSElliott Hughes        liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
498*e1fe3e4aSElliott Hughes        mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
499*e1fe3e4aSElliott Hughes        comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
500*e1fe3e4aSElliott Hughes        builder.add_glyphClassDef(self.location, base, liga, mark, comp)
501*e1fe3e4aSElliott Hughes
502*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
503*e1fe3e4aSElliott Hughes        return "GlyphClassDef {}, {}, {}, {};".format(
504*e1fe3e4aSElliott Hughes            self.baseGlyphs.asFea() if self.baseGlyphs else "",
505*e1fe3e4aSElliott Hughes            self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
506*e1fe3e4aSElliott Hughes            self.markGlyphs.asFea() if self.markGlyphs else "",
507*e1fe3e4aSElliott Hughes            self.componentGlyphs.asFea() if self.componentGlyphs else "",
508*e1fe3e4aSElliott Hughes        )
509*e1fe3e4aSElliott Hughes
510*e1fe3e4aSElliott Hughes
511*e1fe3e4aSElliott Hughesclass MarkClass(object):
512*e1fe3e4aSElliott Hughes    """One `or more` ``markClass`` statements for the same mark class.
513*e1fe3e4aSElliott Hughes
514*e1fe3e4aSElliott Hughes    While glyph classes can be defined only once, the feature file format
515*e1fe3e4aSElliott Hughes    allows expanding mark classes with multiple definitions, each using
516*e1fe3e4aSElliott Hughes    different glyphs and anchors. The following are two ``MarkClassDefinitions``
517*e1fe3e4aSElliott Hughes    for the same ``MarkClass``::
518*e1fe3e4aSElliott Hughes
519*e1fe3e4aSElliott Hughes        markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
520*e1fe3e4aSElliott Hughes        markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
521*e1fe3e4aSElliott Hughes
522*e1fe3e4aSElliott Hughes    The ``MarkClass`` object is therefore just a container for a list of
523*e1fe3e4aSElliott Hughes    :class:`MarkClassDefinition` statements.
524*e1fe3e4aSElliott Hughes    """
525*e1fe3e4aSElliott Hughes
526*e1fe3e4aSElliott Hughes    def __init__(self, name):
527*e1fe3e4aSElliott Hughes        self.name = name
528*e1fe3e4aSElliott Hughes        self.definitions = []
529*e1fe3e4aSElliott Hughes        self.glyphs = OrderedDict()  # glyph --> ast.MarkClassDefinitions
530*e1fe3e4aSElliott Hughes
531*e1fe3e4aSElliott Hughes    def addDefinition(self, definition):
532*e1fe3e4aSElliott Hughes        """Add a :class:`MarkClassDefinition` statement to this mark class."""
533*e1fe3e4aSElliott Hughes        assert isinstance(definition, MarkClassDefinition)
534*e1fe3e4aSElliott Hughes        self.definitions.append(definition)
535*e1fe3e4aSElliott Hughes        for glyph in definition.glyphSet():
536*e1fe3e4aSElliott Hughes            if glyph in self.glyphs:
537*e1fe3e4aSElliott Hughes                otherLoc = self.glyphs[glyph].location
538*e1fe3e4aSElliott Hughes                if otherLoc is None:
539*e1fe3e4aSElliott Hughes                    end = ""
540*e1fe3e4aSElliott Hughes                else:
541*e1fe3e4aSElliott Hughes                    end = f" at {otherLoc}"
542*e1fe3e4aSElliott Hughes                raise FeatureLibError(
543*e1fe3e4aSElliott Hughes                    "Glyph %s already defined%s" % (glyph, end), definition.location
544*e1fe3e4aSElliott Hughes                )
545*e1fe3e4aSElliott Hughes            self.glyphs[glyph] = definition
546*e1fe3e4aSElliott Hughes
547*e1fe3e4aSElliott Hughes    def glyphSet(self):
548*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
549*e1fe3e4aSElliott Hughes        return tuple(self.glyphs.keys())
550*e1fe3e4aSElliott Hughes
551*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
552*e1fe3e4aSElliott Hughes        res = "\n".join(d.asFea() for d in self.definitions)
553*e1fe3e4aSElliott Hughes        return res
554*e1fe3e4aSElliott Hughes
555*e1fe3e4aSElliott Hughes
556*e1fe3e4aSElliott Hughesclass MarkClassDefinition(Statement):
557*e1fe3e4aSElliott Hughes    """A single ``markClass`` statement. The ``markClass`` should be a
558*e1fe3e4aSElliott Hughes    :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
559*e1fe3e4aSElliott Hughes    and the ``glyphs`` parameter should be a `glyph-containing object`_ .
560*e1fe3e4aSElliott Hughes
561*e1fe3e4aSElliott Hughes    Example:
562*e1fe3e4aSElliott Hughes
563*e1fe3e4aSElliott Hughes        .. code:: python
564*e1fe3e4aSElliott Hughes
565*e1fe3e4aSElliott Hughes            mc = MarkClass("FRENCH_ACCENTS")
566*e1fe3e4aSElliott Hughes            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
567*e1fe3e4aSElliott Hughes                GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
568*e1fe3e4aSElliott Hughes            ) )
569*e1fe3e4aSElliott Hughes            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
570*e1fe3e4aSElliott Hughes                GlyphClass([ GlyphName("cedilla") ])
571*e1fe3e4aSElliott Hughes            ) )
572*e1fe3e4aSElliott Hughes
573*e1fe3e4aSElliott Hughes            mc.asFea()
574*e1fe3e4aSElliott Hughes            # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
575*e1fe3e4aSElliott Hughes            # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
576*e1fe3e4aSElliott Hughes
577*e1fe3e4aSElliott Hughes    """
578*e1fe3e4aSElliott Hughes
579*e1fe3e4aSElliott Hughes    def __init__(self, markClass, anchor, glyphs, location=None):
580*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
581*e1fe3e4aSElliott Hughes        assert isinstance(markClass, MarkClass)
582*e1fe3e4aSElliott Hughes        assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
583*e1fe3e4aSElliott Hughes        self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
584*e1fe3e4aSElliott Hughes
585*e1fe3e4aSElliott Hughes    def glyphSet(self):
586*e1fe3e4aSElliott Hughes        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
587*e1fe3e4aSElliott Hughes        return self.glyphs.glyphSet()
588*e1fe3e4aSElliott Hughes
589*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
590*e1fe3e4aSElliott Hughes        return "markClass {} {} @{};".format(
591*e1fe3e4aSElliott Hughes            self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
592*e1fe3e4aSElliott Hughes        )
593*e1fe3e4aSElliott Hughes
594*e1fe3e4aSElliott Hughes
595*e1fe3e4aSElliott Hughesclass AlternateSubstStatement(Statement):
596*e1fe3e4aSElliott Hughes    """A ``sub ... from ...`` statement.
597*e1fe3e4aSElliott Hughes
598*e1fe3e4aSElliott Hughes    ``prefix``, ``glyph``, ``suffix`` and ``replacement`` should be lists of
599*e1fe3e4aSElliott Hughes    `glyph-containing objects`_. ``glyph`` should be a `one element list`."""
600*e1fe3e4aSElliott Hughes
601*e1fe3e4aSElliott Hughes    def __init__(self, prefix, glyph, suffix, replacement, location=None):
602*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
603*e1fe3e4aSElliott Hughes        self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
604*e1fe3e4aSElliott Hughes        self.replacement = replacement
605*e1fe3e4aSElliott Hughes
606*e1fe3e4aSElliott Hughes    def build(self, builder):
607*e1fe3e4aSElliott Hughes        """Calls the builder's ``add_alternate_subst`` callback."""
608*e1fe3e4aSElliott Hughes        glyph = self.glyph.glyphSet()
609*e1fe3e4aSElliott Hughes        assert len(glyph) == 1, glyph
610*e1fe3e4aSElliott Hughes        glyph = list(glyph)[0]
611*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
612*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
613*e1fe3e4aSElliott Hughes        replacement = self.replacement.glyphSet()
614*e1fe3e4aSElliott Hughes        builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)
615*e1fe3e4aSElliott Hughes
616*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
617*e1fe3e4aSElliott Hughes        res = "sub "
618*e1fe3e4aSElliott Hughes        if len(self.prefix) or len(self.suffix):
619*e1fe3e4aSElliott Hughes            if len(self.prefix):
620*e1fe3e4aSElliott Hughes                res += " ".join(map(asFea, self.prefix)) + " "
621*e1fe3e4aSElliott Hughes            res += asFea(self.glyph) + "'"  # even though we really only use 1
622*e1fe3e4aSElliott Hughes            if len(self.suffix):
623*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, self.suffix))
624*e1fe3e4aSElliott Hughes        else:
625*e1fe3e4aSElliott Hughes            res += asFea(self.glyph)
626*e1fe3e4aSElliott Hughes        res += " from "
627*e1fe3e4aSElliott Hughes        res += asFea(self.replacement)
628*e1fe3e4aSElliott Hughes        res += ";"
629*e1fe3e4aSElliott Hughes        return res
630*e1fe3e4aSElliott Hughes
631*e1fe3e4aSElliott Hughes
632*e1fe3e4aSElliott Hughesclass Anchor(Expression):
633*e1fe3e4aSElliott Hughes    """An ``Anchor`` element, used inside a ``pos`` rule.
634*e1fe3e4aSElliott Hughes
635*e1fe3e4aSElliott Hughes    If a ``name`` is given, this will be used in preference to the coordinates.
636*e1fe3e4aSElliott Hughes    Other values should be integer.
637*e1fe3e4aSElliott Hughes    """
638*e1fe3e4aSElliott Hughes
639*e1fe3e4aSElliott Hughes    def __init__(
640*e1fe3e4aSElliott Hughes        self,
641*e1fe3e4aSElliott Hughes        x,
642*e1fe3e4aSElliott Hughes        y,
643*e1fe3e4aSElliott Hughes        name=None,
644*e1fe3e4aSElliott Hughes        contourpoint=None,
645*e1fe3e4aSElliott Hughes        xDeviceTable=None,
646*e1fe3e4aSElliott Hughes        yDeviceTable=None,
647*e1fe3e4aSElliott Hughes        location=None,
648*e1fe3e4aSElliott Hughes    ):
649*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
650*e1fe3e4aSElliott Hughes        self.name = name
651*e1fe3e4aSElliott Hughes        self.x, self.y, self.contourpoint = x, y, contourpoint
652*e1fe3e4aSElliott Hughes        self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable
653*e1fe3e4aSElliott Hughes
654*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
655*e1fe3e4aSElliott Hughes        if self.name is not None:
656*e1fe3e4aSElliott Hughes            return "<anchor {}>".format(self.name)
657*e1fe3e4aSElliott Hughes        res = "<anchor {} {}".format(self.x, self.y)
658*e1fe3e4aSElliott Hughes        if self.contourpoint:
659*e1fe3e4aSElliott Hughes            res += " contourpoint {}".format(self.contourpoint)
660*e1fe3e4aSElliott Hughes        if self.xDeviceTable or self.yDeviceTable:
661*e1fe3e4aSElliott Hughes            res += " "
662*e1fe3e4aSElliott Hughes            res += deviceToString(self.xDeviceTable)
663*e1fe3e4aSElliott Hughes            res += " "
664*e1fe3e4aSElliott Hughes            res += deviceToString(self.yDeviceTable)
665*e1fe3e4aSElliott Hughes        res += ">"
666*e1fe3e4aSElliott Hughes        return res
667*e1fe3e4aSElliott Hughes
668*e1fe3e4aSElliott Hughes
669*e1fe3e4aSElliott Hughesclass AnchorDefinition(Statement):
670*e1fe3e4aSElliott Hughes    """A named anchor definition. (2.e.viii). ``name`` should be a string."""
671*e1fe3e4aSElliott Hughes
672*e1fe3e4aSElliott Hughes    def __init__(self, name, x, y, contourpoint=None, location=None):
673*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
674*e1fe3e4aSElliott Hughes        self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
675*e1fe3e4aSElliott Hughes
676*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
677*e1fe3e4aSElliott Hughes        res = "anchorDef {} {}".format(self.x, self.y)
678*e1fe3e4aSElliott Hughes        if self.contourpoint:
679*e1fe3e4aSElliott Hughes            res += " contourpoint {}".format(self.contourpoint)
680*e1fe3e4aSElliott Hughes        res += " {};".format(self.name)
681*e1fe3e4aSElliott Hughes        return res
682*e1fe3e4aSElliott Hughes
683*e1fe3e4aSElliott Hughes
684*e1fe3e4aSElliott Hughesclass AttachStatement(Statement):
685*e1fe3e4aSElliott Hughes    """A ``GDEF`` table ``Attach`` statement."""
686*e1fe3e4aSElliott Hughes
687*e1fe3e4aSElliott Hughes    def __init__(self, glyphs, contourPoints, location=None):
688*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
689*e1fe3e4aSElliott Hughes        self.glyphs = glyphs  #: A `glyph-containing object`_
690*e1fe3e4aSElliott Hughes        self.contourPoints = contourPoints  #: A list of integer contour points
691*e1fe3e4aSElliott Hughes
692*e1fe3e4aSElliott Hughes    def build(self, builder):
693*e1fe3e4aSElliott Hughes        """Calls the builder's ``add_attach_points`` callback."""
694*e1fe3e4aSElliott Hughes        glyphs = self.glyphs.glyphSet()
695*e1fe3e4aSElliott Hughes        builder.add_attach_points(self.location, glyphs, self.contourPoints)
696*e1fe3e4aSElliott Hughes
697*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
698*e1fe3e4aSElliott Hughes        return "Attach {} {};".format(
699*e1fe3e4aSElliott Hughes            self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
700*e1fe3e4aSElliott Hughes        )
701*e1fe3e4aSElliott Hughes
702*e1fe3e4aSElliott Hughes
703*e1fe3e4aSElliott Hughesclass ChainContextPosStatement(Statement):
704*e1fe3e4aSElliott Hughes    r"""A chained contextual positioning statement.
705*e1fe3e4aSElliott Hughes
706*e1fe3e4aSElliott Hughes    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
707*e1fe3e4aSElliott Hughes    `glyph-containing objects`_ .
708*e1fe3e4aSElliott Hughes
709*e1fe3e4aSElliott Hughes    ``lookups`` should be a list of elements representing what lookups
710*e1fe3e4aSElliott Hughes    to apply at each glyph position. Each element should be a
711*e1fe3e4aSElliott Hughes    :class:`LookupBlock` to apply a single chaining lookup at the given
712*e1fe3e4aSElliott Hughes    position, a list of :class:`LookupBlock`\ s to apply multiple
713*e1fe3e4aSElliott Hughes    lookups, or ``None`` to apply no lookup. The length of the outer
714*e1fe3e4aSElliott Hughes    list should equal the length of ``glyphs``; the inner lists can be
715*e1fe3e4aSElliott Hughes    of variable length."""
716*e1fe3e4aSElliott Hughes
717*e1fe3e4aSElliott Hughes    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
718*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
719*e1fe3e4aSElliott Hughes        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
720*e1fe3e4aSElliott Hughes        self.lookups = list(lookups)
721*e1fe3e4aSElliott Hughes        for i, lookup in enumerate(lookups):
722*e1fe3e4aSElliott Hughes            if lookup:
723*e1fe3e4aSElliott Hughes                try:
724*e1fe3e4aSElliott Hughes                    (_ for _ in lookup)
725*e1fe3e4aSElliott Hughes                except TypeError:
726*e1fe3e4aSElliott Hughes                    self.lookups[i] = [lookup]
727*e1fe3e4aSElliott Hughes
728*e1fe3e4aSElliott Hughes    def build(self, builder):
729*e1fe3e4aSElliott Hughes        """Calls the builder's ``add_chain_context_pos`` callback."""
730*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
731*e1fe3e4aSElliott Hughes        glyphs = [g.glyphSet() for g in self.glyphs]
732*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
733*e1fe3e4aSElliott Hughes        builder.add_chain_context_pos(
734*e1fe3e4aSElliott Hughes            self.location, prefix, glyphs, suffix, self.lookups
735*e1fe3e4aSElliott Hughes        )
736*e1fe3e4aSElliott Hughes
737*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
738*e1fe3e4aSElliott Hughes        res = "pos "
739*e1fe3e4aSElliott Hughes        if (
740*e1fe3e4aSElliott Hughes            len(self.prefix)
741*e1fe3e4aSElliott Hughes            or len(self.suffix)
742*e1fe3e4aSElliott Hughes            or any([x is not None for x in self.lookups])
743*e1fe3e4aSElliott Hughes        ):
744*e1fe3e4aSElliott Hughes            if len(self.prefix):
745*e1fe3e4aSElliott Hughes                res += " ".join(g.asFea() for g in self.prefix) + " "
746*e1fe3e4aSElliott Hughes            for i, g in enumerate(self.glyphs):
747*e1fe3e4aSElliott Hughes                res += g.asFea() + "'"
748*e1fe3e4aSElliott Hughes                if self.lookups[i]:
749*e1fe3e4aSElliott Hughes                    for lu in self.lookups[i]:
750*e1fe3e4aSElliott Hughes                        res += " lookup " + lu.name
751*e1fe3e4aSElliott Hughes                if i < len(self.glyphs) - 1:
752*e1fe3e4aSElliott Hughes                    res += " "
753*e1fe3e4aSElliott Hughes            if len(self.suffix):
754*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, self.suffix))
755*e1fe3e4aSElliott Hughes        else:
756*e1fe3e4aSElliott Hughes            res += " ".join(map(asFea, self.glyph))
757*e1fe3e4aSElliott Hughes        res += ";"
758*e1fe3e4aSElliott Hughes        return res
759*e1fe3e4aSElliott Hughes
760*e1fe3e4aSElliott Hughes
761*e1fe3e4aSElliott Hughesclass ChainContextSubstStatement(Statement):
762*e1fe3e4aSElliott Hughes    r"""A chained contextual substitution statement.
763*e1fe3e4aSElliott Hughes
764*e1fe3e4aSElliott Hughes    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
765*e1fe3e4aSElliott Hughes    `glyph-containing objects`_ .
766*e1fe3e4aSElliott Hughes
767*e1fe3e4aSElliott Hughes    ``lookups`` should be a list of elements representing what lookups
768*e1fe3e4aSElliott Hughes    to apply at each glyph position. Each element should be a
769*e1fe3e4aSElliott Hughes    :class:`LookupBlock` to apply a single chaining lookup at the given
770*e1fe3e4aSElliott Hughes    position, a list of :class:`LookupBlock`\ s to apply multiple
771*e1fe3e4aSElliott Hughes    lookups, or ``None`` to apply no lookup. The length of the outer
772*e1fe3e4aSElliott Hughes    list should equal the length of ``glyphs``; the inner lists can be
773*e1fe3e4aSElliott Hughes    of variable length."""
774*e1fe3e4aSElliott Hughes
775*e1fe3e4aSElliott Hughes    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
776*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
777*e1fe3e4aSElliott Hughes        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
778*e1fe3e4aSElliott Hughes        self.lookups = list(lookups)
779*e1fe3e4aSElliott Hughes        for i, lookup in enumerate(lookups):
780*e1fe3e4aSElliott Hughes            if lookup:
781*e1fe3e4aSElliott Hughes                try:
782*e1fe3e4aSElliott Hughes                    (_ for _ in lookup)
783*e1fe3e4aSElliott Hughes                except TypeError:
784*e1fe3e4aSElliott Hughes                    self.lookups[i] = [lookup]
785*e1fe3e4aSElliott Hughes
786*e1fe3e4aSElliott Hughes    def build(self, builder):
787*e1fe3e4aSElliott Hughes        """Calls the builder's ``add_chain_context_subst`` callback."""
788*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
789*e1fe3e4aSElliott Hughes        glyphs = [g.glyphSet() for g in self.glyphs]
790*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
791*e1fe3e4aSElliott Hughes        builder.add_chain_context_subst(
792*e1fe3e4aSElliott Hughes            self.location, prefix, glyphs, suffix, self.lookups
793*e1fe3e4aSElliott Hughes        )
794*e1fe3e4aSElliott Hughes
795*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
796*e1fe3e4aSElliott Hughes        res = "sub "
797*e1fe3e4aSElliott Hughes        if (
798*e1fe3e4aSElliott Hughes            len(self.prefix)
799*e1fe3e4aSElliott Hughes            or len(self.suffix)
800*e1fe3e4aSElliott Hughes            or any([x is not None for x in self.lookups])
801*e1fe3e4aSElliott Hughes        ):
802*e1fe3e4aSElliott Hughes            if len(self.prefix):
803*e1fe3e4aSElliott Hughes                res += " ".join(g.asFea() for g in self.prefix) + " "
804*e1fe3e4aSElliott Hughes            for i, g in enumerate(self.glyphs):
805*e1fe3e4aSElliott Hughes                res += g.asFea() + "'"
806*e1fe3e4aSElliott Hughes                if self.lookups[i]:
807*e1fe3e4aSElliott Hughes                    for lu in self.lookups[i]:
808*e1fe3e4aSElliott Hughes                        res += " lookup " + lu.name
809*e1fe3e4aSElliott Hughes                if i < len(self.glyphs) - 1:
810*e1fe3e4aSElliott Hughes                    res += " "
811*e1fe3e4aSElliott Hughes            if len(self.suffix):
812*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, self.suffix))
813*e1fe3e4aSElliott Hughes        else:
814*e1fe3e4aSElliott Hughes            res += " ".join(map(asFea, self.glyph))
815*e1fe3e4aSElliott Hughes        res += ";"
816*e1fe3e4aSElliott Hughes        return res
817*e1fe3e4aSElliott Hughes
818*e1fe3e4aSElliott Hughes
819*e1fe3e4aSElliott Hughesclass CursivePosStatement(Statement):
820*e1fe3e4aSElliott Hughes    """A cursive positioning statement. Entry and exit anchors can either
821*e1fe3e4aSElliott Hughes    be :class:`Anchor` objects or ``None``."""
822*e1fe3e4aSElliott Hughes
823*e1fe3e4aSElliott Hughes    def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
824*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
825*e1fe3e4aSElliott Hughes        self.glyphclass = glyphclass
826*e1fe3e4aSElliott Hughes        self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
827*e1fe3e4aSElliott Hughes
828*e1fe3e4aSElliott Hughes    def build(self, builder):
829*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_cursive_pos`` callback."""
830*e1fe3e4aSElliott Hughes        builder.add_cursive_pos(
831*e1fe3e4aSElliott Hughes            self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
832*e1fe3e4aSElliott Hughes        )
833*e1fe3e4aSElliott Hughes
834*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
835*e1fe3e4aSElliott Hughes        entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
836*e1fe3e4aSElliott Hughes        exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
837*e1fe3e4aSElliott Hughes        return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)
838*e1fe3e4aSElliott Hughes
839*e1fe3e4aSElliott Hughes
840*e1fe3e4aSElliott Hughesclass FeatureReferenceStatement(Statement):
841*e1fe3e4aSElliott Hughes    """Example: ``feature salt;``"""
842*e1fe3e4aSElliott Hughes
843*e1fe3e4aSElliott Hughes    def __init__(self, featureName, location=None):
844*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
845*e1fe3e4aSElliott Hughes        self.location, self.featureName = (location, featureName)
846*e1fe3e4aSElliott Hughes
847*e1fe3e4aSElliott Hughes    def build(self, builder):
848*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_feature_reference`` callback."""
849*e1fe3e4aSElliott Hughes        builder.add_feature_reference(self.location, self.featureName)
850*e1fe3e4aSElliott Hughes
851*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
852*e1fe3e4aSElliott Hughes        return "feature {};".format(self.featureName)
853*e1fe3e4aSElliott Hughes
854*e1fe3e4aSElliott Hughes
855*e1fe3e4aSElliott Hughesclass IgnorePosStatement(Statement):
856*e1fe3e4aSElliott Hughes    """An ``ignore pos`` statement, containing `one or more` contexts to ignore.
857*e1fe3e4aSElliott Hughes
858*e1fe3e4aSElliott Hughes    ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
859*e1fe3e4aSElliott Hughes    with each of ``prefix``, ``glyphs`` and ``suffix`` being
860*e1fe3e4aSElliott Hughes    `glyph-containing objects`_ ."""
861*e1fe3e4aSElliott Hughes
862*e1fe3e4aSElliott Hughes    def __init__(self, chainContexts, location=None):
863*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
864*e1fe3e4aSElliott Hughes        self.chainContexts = chainContexts
865*e1fe3e4aSElliott Hughes
866*e1fe3e4aSElliott Hughes    def build(self, builder):
867*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_chain_context_pos`` callback on each
868*e1fe3e4aSElliott Hughes        rule context."""
869*e1fe3e4aSElliott Hughes        for prefix, glyphs, suffix in self.chainContexts:
870*e1fe3e4aSElliott Hughes            prefix = [p.glyphSet() for p in prefix]
871*e1fe3e4aSElliott Hughes            glyphs = [g.glyphSet() for g in glyphs]
872*e1fe3e4aSElliott Hughes            suffix = [s.glyphSet() for s in suffix]
873*e1fe3e4aSElliott Hughes            builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])
874*e1fe3e4aSElliott Hughes
875*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
876*e1fe3e4aSElliott Hughes        contexts = []
877*e1fe3e4aSElliott Hughes        for prefix, glyphs, suffix in self.chainContexts:
878*e1fe3e4aSElliott Hughes            res = ""
879*e1fe3e4aSElliott Hughes            if len(prefix) or len(suffix):
880*e1fe3e4aSElliott Hughes                if len(prefix):
881*e1fe3e4aSElliott Hughes                    res += " ".join(map(asFea, prefix)) + " "
882*e1fe3e4aSElliott Hughes                res += " ".join(g.asFea() + "'" for g in glyphs)
883*e1fe3e4aSElliott Hughes                if len(suffix):
884*e1fe3e4aSElliott Hughes                    res += " " + " ".join(map(asFea, suffix))
885*e1fe3e4aSElliott Hughes            else:
886*e1fe3e4aSElliott Hughes                res += " ".join(map(asFea, glyphs))
887*e1fe3e4aSElliott Hughes            contexts.append(res)
888*e1fe3e4aSElliott Hughes        return "ignore pos " + ", ".join(contexts) + ";"
889*e1fe3e4aSElliott Hughes
890*e1fe3e4aSElliott Hughes
891*e1fe3e4aSElliott Hughesclass IgnoreSubstStatement(Statement):
892*e1fe3e4aSElliott Hughes    """An ``ignore sub`` statement, containing `one or more` contexts to ignore.
893*e1fe3e4aSElliott Hughes
894*e1fe3e4aSElliott Hughes    ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
895*e1fe3e4aSElliott Hughes    with each of ``prefix``, ``glyphs`` and ``suffix`` being
896*e1fe3e4aSElliott Hughes    `glyph-containing objects`_ ."""
897*e1fe3e4aSElliott Hughes
898*e1fe3e4aSElliott Hughes    def __init__(self, chainContexts, location=None):
899*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
900*e1fe3e4aSElliott Hughes        self.chainContexts = chainContexts
901*e1fe3e4aSElliott Hughes
902*e1fe3e4aSElliott Hughes    def build(self, builder):
903*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_chain_context_subst`` callback on
904*e1fe3e4aSElliott Hughes        each rule context."""
905*e1fe3e4aSElliott Hughes        for prefix, glyphs, suffix in self.chainContexts:
906*e1fe3e4aSElliott Hughes            prefix = [p.glyphSet() for p in prefix]
907*e1fe3e4aSElliott Hughes            glyphs = [g.glyphSet() for g in glyphs]
908*e1fe3e4aSElliott Hughes            suffix = [s.glyphSet() for s in suffix]
909*e1fe3e4aSElliott Hughes            builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, [])
910*e1fe3e4aSElliott Hughes
911*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
912*e1fe3e4aSElliott Hughes        contexts = []
913*e1fe3e4aSElliott Hughes        for prefix, glyphs, suffix in self.chainContexts:
914*e1fe3e4aSElliott Hughes            res = ""
915*e1fe3e4aSElliott Hughes            if len(prefix):
916*e1fe3e4aSElliott Hughes                res += " ".join(map(asFea, prefix)) + " "
917*e1fe3e4aSElliott Hughes            res += " ".join(g.asFea() + "'" for g in glyphs)
918*e1fe3e4aSElliott Hughes            if len(suffix):
919*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, suffix))
920*e1fe3e4aSElliott Hughes            contexts.append(res)
921*e1fe3e4aSElliott Hughes        return "ignore sub " + ", ".join(contexts) + ";"
922*e1fe3e4aSElliott Hughes
923*e1fe3e4aSElliott Hughes
924*e1fe3e4aSElliott Hughesclass IncludeStatement(Statement):
925*e1fe3e4aSElliott Hughes    """An ``include()`` statement."""
926*e1fe3e4aSElliott Hughes
927*e1fe3e4aSElliott Hughes    def __init__(self, filename, location=None):
928*e1fe3e4aSElliott Hughes        super(IncludeStatement, self).__init__(location)
929*e1fe3e4aSElliott Hughes        self.filename = filename  #: String containing name of file to include
930*e1fe3e4aSElliott Hughes
931*e1fe3e4aSElliott Hughes    def build(self):
932*e1fe3e4aSElliott Hughes        # TODO: consider lazy-loading the including parser/lexer?
933*e1fe3e4aSElliott Hughes        raise FeatureLibError(
934*e1fe3e4aSElliott Hughes            "Building an include statement is not implemented yet. "
935*e1fe3e4aSElliott Hughes            "Instead, use Parser(..., followIncludes=True) for building.",
936*e1fe3e4aSElliott Hughes            self.location,
937*e1fe3e4aSElliott Hughes        )
938*e1fe3e4aSElliott Hughes
939*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
940*e1fe3e4aSElliott Hughes        return indent + "include(%s);" % self.filename
941*e1fe3e4aSElliott Hughes
942*e1fe3e4aSElliott Hughes
943*e1fe3e4aSElliott Hughesclass LanguageStatement(Statement):
944*e1fe3e4aSElliott Hughes    """A ``language`` statement within a feature."""
945*e1fe3e4aSElliott Hughes
946*e1fe3e4aSElliott Hughes    def __init__(self, language, include_default=True, required=False, location=None):
947*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
948*e1fe3e4aSElliott Hughes        assert len(language) == 4
949*e1fe3e4aSElliott Hughes        self.language = language  #: A four-character language tag
950*e1fe3e4aSElliott Hughes        self.include_default = include_default  #: If false, "exclude_dflt"
951*e1fe3e4aSElliott Hughes        self.required = required
952*e1fe3e4aSElliott Hughes
953*e1fe3e4aSElliott Hughes    def build(self, builder):
954*e1fe3e4aSElliott Hughes        """Call the builder object's ``set_language`` callback."""
955*e1fe3e4aSElliott Hughes        builder.set_language(
956*e1fe3e4aSElliott Hughes            location=self.location,
957*e1fe3e4aSElliott Hughes            language=self.language,
958*e1fe3e4aSElliott Hughes            include_default=self.include_default,
959*e1fe3e4aSElliott Hughes            required=self.required,
960*e1fe3e4aSElliott Hughes        )
961*e1fe3e4aSElliott Hughes
962*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
963*e1fe3e4aSElliott Hughes        res = "language {}".format(self.language.strip())
964*e1fe3e4aSElliott Hughes        if not self.include_default:
965*e1fe3e4aSElliott Hughes            res += " exclude_dflt"
966*e1fe3e4aSElliott Hughes        if self.required:
967*e1fe3e4aSElliott Hughes            res += " required"
968*e1fe3e4aSElliott Hughes        res += ";"
969*e1fe3e4aSElliott Hughes        return res
970*e1fe3e4aSElliott Hughes
971*e1fe3e4aSElliott Hughes
972*e1fe3e4aSElliott Hughesclass LanguageSystemStatement(Statement):
973*e1fe3e4aSElliott Hughes    """A top-level ``languagesystem`` statement."""
974*e1fe3e4aSElliott Hughes
975*e1fe3e4aSElliott Hughes    def __init__(self, script, language, location=None):
976*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
977*e1fe3e4aSElliott Hughes        self.script, self.language = (script, language)
978*e1fe3e4aSElliott Hughes
979*e1fe3e4aSElliott Hughes    def build(self, builder):
980*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_language_system`` callback."""
981*e1fe3e4aSElliott Hughes        builder.add_language_system(self.location, self.script, self.language)
982*e1fe3e4aSElliott Hughes
983*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
984*e1fe3e4aSElliott Hughes        return "languagesystem {} {};".format(self.script, self.language.strip())
985*e1fe3e4aSElliott Hughes
986*e1fe3e4aSElliott Hughes
987*e1fe3e4aSElliott Hughesclass FontRevisionStatement(Statement):
988*e1fe3e4aSElliott Hughes    """A ``head`` table ``FontRevision`` statement. ``revision`` should be a
989*e1fe3e4aSElliott Hughes    number, and will be formatted to three significant decimal places."""
990*e1fe3e4aSElliott Hughes
991*e1fe3e4aSElliott Hughes    def __init__(self, revision, location=None):
992*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
993*e1fe3e4aSElliott Hughes        self.revision = revision
994*e1fe3e4aSElliott Hughes
995*e1fe3e4aSElliott Hughes    def build(self, builder):
996*e1fe3e4aSElliott Hughes        builder.set_font_revision(self.location, self.revision)
997*e1fe3e4aSElliott Hughes
998*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
999*e1fe3e4aSElliott Hughes        return "FontRevision {:.3f};".format(self.revision)
1000*e1fe3e4aSElliott Hughes
1001*e1fe3e4aSElliott Hughes
1002*e1fe3e4aSElliott Hughesclass LigatureCaretByIndexStatement(Statement):
1003*e1fe3e4aSElliott Hughes    """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be
1004*e1fe3e4aSElliott Hughes    a `glyph-containing object`_, and ``carets`` should be a list of integers."""
1005*e1fe3e4aSElliott Hughes
1006*e1fe3e4aSElliott Hughes    def __init__(self, glyphs, carets, location=None):
1007*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1008*e1fe3e4aSElliott Hughes        self.glyphs, self.carets = (glyphs, carets)
1009*e1fe3e4aSElliott Hughes
1010*e1fe3e4aSElliott Hughes    def build(self, builder):
1011*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_ligatureCaretByIndex_`` callback."""
1012*e1fe3e4aSElliott Hughes        glyphs = self.glyphs.glyphSet()
1013*e1fe3e4aSElliott Hughes        builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
1014*e1fe3e4aSElliott Hughes
1015*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1016*e1fe3e4aSElliott Hughes        return "LigatureCaretByIndex {} {};".format(
1017*e1fe3e4aSElliott Hughes            self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
1018*e1fe3e4aSElliott Hughes        )
1019*e1fe3e4aSElliott Hughes
1020*e1fe3e4aSElliott Hughes
1021*e1fe3e4aSElliott Hughesclass LigatureCaretByPosStatement(Statement):
1022*e1fe3e4aSElliott Hughes    """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be
1023*e1fe3e4aSElliott Hughes    a `glyph-containing object`_, and ``carets`` should be a list of integers."""
1024*e1fe3e4aSElliott Hughes
1025*e1fe3e4aSElliott Hughes    def __init__(self, glyphs, carets, location=None):
1026*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1027*e1fe3e4aSElliott Hughes        self.glyphs, self.carets = (glyphs, carets)
1028*e1fe3e4aSElliott Hughes
1029*e1fe3e4aSElliott Hughes    def build(self, builder):
1030*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_ligatureCaretByPos_`` callback."""
1031*e1fe3e4aSElliott Hughes        glyphs = self.glyphs.glyphSet()
1032*e1fe3e4aSElliott Hughes        builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
1033*e1fe3e4aSElliott Hughes
1034*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1035*e1fe3e4aSElliott Hughes        return "LigatureCaretByPos {} {};".format(
1036*e1fe3e4aSElliott Hughes            self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
1037*e1fe3e4aSElliott Hughes        )
1038*e1fe3e4aSElliott Hughes
1039*e1fe3e4aSElliott Hughes
1040*e1fe3e4aSElliott Hughesclass LigatureSubstStatement(Statement):
1041*e1fe3e4aSElliott Hughes    """A chained contextual substitution statement.
1042*e1fe3e4aSElliott Hughes
1043*e1fe3e4aSElliott Hughes    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
1044*e1fe3e4aSElliott Hughes    `glyph-containing objects`_; ``replacement`` should be a single
1045*e1fe3e4aSElliott Hughes    `glyph-containing object`_.
1046*e1fe3e4aSElliott Hughes
1047*e1fe3e4aSElliott Hughes    If ``forceChain`` is True, this is expressed as a chaining rule
1048*e1fe3e4aSElliott Hughes    (e.g. ``sub f' i' by f_i``) even when no context is given."""
1049*e1fe3e4aSElliott Hughes
1050*e1fe3e4aSElliott Hughes    def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None):
1051*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1052*e1fe3e4aSElliott Hughes        self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
1053*e1fe3e4aSElliott Hughes        self.replacement, self.forceChain = replacement, forceChain
1054*e1fe3e4aSElliott Hughes
1055*e1fe3e4aSElliott Hughes    def build(self, builder):
1056*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
1057*e1fe3e4aSElliott Hughes        glyphs = [g.glyphSet() for g in self.glyphs]
1058*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
1059*e1fe3e4aSElliott Hughes        builder.add_ligature_subst(
1060*e1fe3e4aSElliott Hughes            self.location, prefix, glyphs, suffix, self.replacement, self.forceChain
1061*e1fe3e4aSElliott Hughes        )
1062*e1fe3e4aSElliott Hughes
1063*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1064*e1fe3e4aSElliott Hughes        res = "sub "
1065*e1fe3e4aSElliott Hughes        if len(self.prefix) or len(self.suffix) or self.forceChain:
1066*e1fe3e4aSElliott Hughes            if len(self.prefix):
1067*e1fe3e4aSElliott Hughes                res += " ".join(g.asFea() for g in self.prefix) + " "
1068*e1fe3e4aSElliott Hughes            res += " ".join(g.asFea() + "'" for g in self.glyphs)
1069*e1fe3e4aSElliott Hughes            if len(self.suffix):
1070*e1fe3e4aSElliott Hughes                res += " " + " ".join(g.asFea() for g in self.suffix)
1071*e1fe3e4aSElliott Hughes        else:
1072*e1fe3e4aSElliott Hughes            res += " ".join(g.asFea() for g in self.glyphs)
1073*e1fe3e4aSElliott Hughes        res += " by "
1074*e1fe3e4aSElliott Hughes        res += asFea(self.replacement)
1075*e1fe3e4aSElliott Hughes        res += ";"
1076*e1fe3e4aSElliott Hughes        return res
1077*e1fe3e4aSElliott Hughes
1078*e1fe3e4aSElliott Hughes
1079*e1fe3e4aSElliott Hughesclass LookupFlagStatement(Statement):
1080*e1fe3e4aSElliott Hughes    """A ``lookupflag`` statement. The ``value`` should be an integer value
1081*e1fe3e4aSElliott Hughes    representing the flags in use, but not including the ``markAttachment``
1082*e1fe3e4aSElliott Hughes    class and ``markFilteringSet`` values, which must be specified as
1083*e1fe3e4aSElliott Hughes    glyph-containing objects."""
1084*e1fe3e4aSElliott Hughes
1085*e1fe3e4aSElliott Hughes    def __init__(
1086*e1fe3e4aSElliott Hughes        self, value=0, markAttachment=None, markFilteringSet=None, location=None
1087*e1fe3e4aSElliott Hughes    ):
1088*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1089*e1fe3e4aSElliott Hughes        self.value = value
1090*e1fe3e4aSElliott Hughes        self.markAttachment = markAttachment
1091*e1fe3e4aSElliott Hughes        self.markFilteringSet = markFilteringSet
1092*e1fe3e4aSElliott Hughes
1093*e1fe3e4aSElliott Hughes    def build(self, builder):
1094*e1fe3e4aSElliott Hughes        """Calls the builder object's ``set_lookup_flag`` callback."""
1095*e1fe3e4aSElliott Hughes        markAttach = None
1096*e1fe3e4aSElliott Hughes        if self.markAttachment is not None:
1097*e1fe3e4aSElliott Hughes            markAttach = self.markAttachment.glyphSet()
1098*e1fe3e4aSElliott Hughes        markFilter = None
1099*e1fe3e4aSElliott Hughes        if self.markFilteringSet is not None:
1100*e1fe3e4aSElliott Hughes            markFilter = self.markFilteringSet.glyphSet()
1101*e1fe3e4aSElliott Hughes        builder.set_lookup_flag(self.location, self.value, markAttach, markFilter)
1102*e1fe3e4aSElliott Hughes
1103*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1104*e1fe3e4aSElliott Hughes        res = []
1105*e1fe3e4aSElliott Hughes        flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
1106*e1fe3e4aSElliott Hughes        curr = 1
1107*e1fe3e4aSElliott Hughes        for i in range(len(flags)):
1108*e1fe3e4aSElliott Hughes            if self.value & curr != 0:
1109*e1fe3e4aSElliott Hughes                res.append(flags[i])
1110*e1fe3e4aSElliott Hughes            curr = curr << 1
1111*e1fe3e4aSElliott Hughes        if self.markAttachment is not None:
1112*e1fe3e4aSElliott Hughes            res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
1113*e1fe3e4aSElliott Hughes        if self.markFilteringSet is not None:
1114*e1fe3e4aSElliott Hughes            res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
1115*e1fe3e4aSElliott Hughes        if not res:
1116*e1fe3e4aSElliott Hughes            res = ["0"]
1117*e1fe3e4aSElliott Hughes        return "lookupflag {};".format(" ".join(res))
1118*e1fe3e4aSElliott Hughes
1119*e1fe3e4aSElliott Hughes
1120*e1fe3e4aSElliott Hughesclass LookupReferenceStatement(Statement):
1121*e1fe3e4aSElliott Hughes    """Represents a ``lookup ...;`` statement to include a lookup in a feature.
1122*e1fe3e4aSElliott Hughes
1123*e1fe3e4aSElliott Hughes    The ``lookup`` should be a :class:`LookupBlock` object."""
1124*e1fe3e4aSElliott Hughes
1125*e1fe3e4aSElliott Hughes    def __init__(self, lookup, location=None):
1126*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1127*e1fe3e4aSElliott Hughes        self.location, self.lookup = (location, lookup)
1128*e1fe3e4aSElliott Hughes
1129*e1fe3e4aSElliott Hughes    def build(self, builder):
1130*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_lookup_call`` callback."""
1131*e1fe3e4aSElliott Hughes        builder.add_lookup_call(self.lookup.name)
1132*e1fe3e4aSElliott Hughes
1133*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1134*e1fe3e4aSElliott Hughes        return "lookup {};".format(self.lookup.name)
1135*e1fe3e4aSElliott Hughes
1136*e1fe3e4aSElliott Hughes
1137*e1fe3e4aSElliott Hughesclass MarkBasePosStatement(Statement):
1138*e1fe3e4aSElliott Hughes    """A mark-to-base positioning rule. The ``base`` should be a
1139*e1fe3e4aSElliott Hughes    `glyph-containing object`_. The ``marks`` should be a list of
1140*e1fe3e4aSElliott Hughes    (:class:`Anchor`, :class:`MarkClass`) tuples."""
1141*e1fe3e4aSElliott Hughes
1142*e1fe3e4aSElliott Hughes    def __init__(self, base, marks, location=None):
1143*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1144*e1fe3e4aSElliott Hughes        self.base, self.marks = base, marks
1145*e1fe3e4aSElliott Hughes
1146*e1fe3e4aSElliott Hughes    def build(self, builder):
1147*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_mark_base_pos`` callback."""
1148*e1fe3e4aSElliott Hughes        builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
1149*e1fe3e4aSElliott Hughes
1150*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1151*e1fe3e4aSElliott Hughes        res = "pos base {}".format(self.base.asFea())
1152*e1fe3e4aSElliott Hughes        for a, m in self.marks:
1153*e1fe3e4aSElliott Hughes            res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
1154*e1fe3e4aSElliott Hughes        res += ";"
1155*e1fe3e4aSElliott Hughes        return res
1156*e1fe3e4aSElliott Hughes
1157*e1fe3e4aSElliott Hughes
1158*e1fe3e4aSElliott Hughesclass MarkLigPosStatement(Statement):
1159*e1fe3e4aSElliott Hughes    """A mark-to-ligature positioning rule. The ``ligatures`` must be a
1160*e1fe3e4aSElliott Hughes    `glyph-containing object`_. The ``marks`` should be a list of lists: each
1161*e1fe3e4aSElliott Hughes    element in the top-level list represents a component glyph, and is made
1162*e1fe3e4aSElliott Hughes    up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing
1163*e1fe3e4aSElliott Hughes    mark attachment points for that position.
1164*e1fe3e4aSElliott Hughes
1165*e1fe3e4aSElliott Hughes    Example::
1166*e1fe3e4aSElliott Hughes
1167*e1fe3e4aSElliott Hughes        m1 = MarkClass("TOP_MARKS")
1168*e1fe3e4aSElliott Hughes        m2 = MarkClass("BOTTOM_MARKS")
1169*e1fe3e4aSElliott Hughes        # ... add definitions to mark classes...
1170*e1fe3e4aSElliott Hughes
1171*e1fe3e4aSElliott Hughes        glyph = GlyphName("lam_meem_jeem")
1172*e1fe3e4aSElliott Hughes        marks = [
1173*e1fe3e4aSElliott Hughes            [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
1174*e1fe3e4aSElliott Hughes            [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
1175*e1fe3e4aSElliott Hughes            [ ]                         # No attachments on the jeem
1176*e1fe3e4aSElliott Hughes        ]
1177*e1fe3e4aSElliott Hughes        mlp = MarkLigPosStatement(glyph, marks)
1178*e1fe3e4aSElliott Hughes
1179*e1fe3e4aSElliott Hughes        mlp.asFea()
1180*e1fe3e4aSElliott Hughes        # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS
1181*e1fe3e4aSElliott Hughes        # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS;
1182*e1fe3e4aSElliott Hughes
1183*e1fe3e4aSElliott Hughes    """
1184*e1fe3e4aSElliott Hughes
1185*e1fe3e4aSElliott Hughes    def __init__(self, ligatures, marks, location=None):
1186*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1187*e1fe3e4aSElliott Hughes        self.ligatures, self.marks = ligatures, marks
1188*e1fe3e4aSElliott Hughes
1189*e1fe3e4aSElliott Hughes    def build(self, builder):
1190*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_mark_lig_pos`` callback."""
1191*e1fe3e4aSElliott Hughes        builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
1192*e1fe3e4aSElliott Hughes
1193*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1194*e1fe3e4aSElliott Hughes        res = "pos ligature {}".format(self.ligatures.asFea())
1195*e1fe3e4aSElliott Hughes        ligs = []
1196*e1fe3e4aSElliott Hughes        for l in self.marks:
1197*e1fe3e4aSElliott Hughes            temp = ""
1198*e1fe3e4aSElliott Hughes            if l is None or not len(l):
1199*e1fe3e4aSElliott Hughes                temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
1200*e1fe3e4aSElliott Hughes            else:
1201*e1fe3e4aSElliott Hughes                for a, m in l:
1202*e1fe3e4aSElliott Hughes                    temp += (
1203*e1fe3e4aSElliott Hughes                        "\n"
1204*e1fe3e4aSElliott Hughes                        + indent
1205*e1fe3e4aSElliott Hughes                        + SHIFT * 2
1206*e1fe3e4aSElliott Hughes                        + "{} mark @{}".format(a.asFea(), m.name)
1207*e1fe3e4aSElliott Hughes                    )
1208*e1fe3e4aSElliott Hughes            ligs.append(temp)
1209*e1fe3e4aSElliott Hughes        res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
1210*e1fe3e4aSElliott Hughes        res += ";"
1211*e1fe3e4aSElliott Hughes        return res
1212*e1fe3e4aSElliott Hughes
1213*e1fe3e4aSElliott Hughes
1214*e1fe3e4aSElliott Hughesclass MarkMarkPosStatement(Statement):
1215*e1fe3e4aSElliott Hughes    """A mark-to-mark positioning rule. The ``baseMarks`` must be a
1216*e1fe3e4aSElliott Hughes    `glyph-containing object`_. The ``marks`` should be a list of
1217*e1fe3e4aSElliott Hughes    (:class:`Anchor`, :class:`MarkClass`) tuples."""
1218*e1fe3e4aSElliott Hughes
1219*e1fe3e4aSElliott Hughes    def __init__(self, baseMarks, marks, location=None):
1220*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1221*e1fe3e4aSElliott Hughes        self.baseMarks, self.marks = baseMarks, marks
1222*e1fe3e4aSElliott Hughes
1223*e1fe3e4aSElliott Hughes    def build(self, builder):
1224*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_mark_mark_pos`` callback."""
1225*e1fe3e4aSElliott Hughes        builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
1226*e1fe3e4aSElliott Hughes
1227*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1228*e1fe3e4aSElliott Hughes        res = "pos mark {}".format(self.baseMarks.asFea())
1229*e1fe3e4aSElliott Hughes        for a, m in self.marks:
1230*e1fe3e4aSElliott Hughes            res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
1231*e1fe3e4aSElliott Hughes        res += ";"
1232*e1fe3e4aSElliott Hughes        return res
1233*e1fe3e4aSElliott Hughes
1234*e1fe3e4aSElliott Hughes
1235*e1fe3e4aSElliott Hughesclass MultipleSubstStatement(Statement):
1236*e1fe3e4aSElliott Hughes    """A multiple substitution statement.
1237*e1fe3e4aSElliott Hughes
1238*e1fe3e4aSElliott Hughes    Args:
1239*e1fe3e4aSElliott Hughes        prefix: a list of `glyph-containing objects`_.
1240*e1fe3e4aSElliott Hughes        glyph: a single glyph-containing object.
1241*e1fe3e4aSElliott Hughes        suffix: a list of glyph-containing objects.
1242*e1fe3e4aSElliott Hughes        replacement: a list of glyph-containing objects.
1243*e1fe3e4aSElliott Hughes        forceChain: If true, the statement is expressed as a chaining rule
1244*e1fe3e4aSElliott Hughes            (e.g. ``sub f' i' by f_i``) even when no context is given.
1245*e1fe3e4aSElliott Hughes    """
1246*e1fe3e4aSElliott Hughes
1247*e1fe3e4aSElliott Hughes    def __init__(
1248*e1fe3e4aSElliott Hughes        self, prefix, glyph, suffix, replacement, forceChain=False, location=None
1249*e1fe3e4aSElliott Hughes    ):
1250*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1251*e1fe3e4aSElliott Hughes        self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
1252*e1fe3e4aSElliott Hughes        self.replacement = replacement
1253*e1fe3e4aSElliott Hughes        self.forceChain = forceChain
1254*e1fe3e4aSElliott Hughes
1255*e1fe3e4aSElliott Hughes    def build(self, builder):
1256*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_multiple_subst`` callback."""
1257*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
1258*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
1259*e1fe3e4aSElliott Hughes        if hasattr(self.glyph, "glyphSet"):
1260*e1fe3e4aSElliott Hughes            originals = self.glyph.glyphSet()
1261*e1fe3e4aSElliott Hughes        else:
1262*e1fe3e4aSElliott Hughes            originals = [self.glyph]
1263*e1fe3e4aSElliott Hughes        count = len(originals)
1264*e1fe3e4aSElliott Hughes        replaces = []
1265*e1fe3e4aSElliott Hughes        for r in self.replacement:
1266*e1fe3e4aSElliott Hughes            if hasattr(r, "glyphSet"):
1267*e1fe3e4aSElliott Hughes                replace = r.glyphSet()
1268*e1fe3e4aSElliott Hughes            else:
1269*e1fe3e4aSElliott Hughes                replace = [r]
1270*e1fe3e4aSElliott Hughes            if len(replace) == 1 and len(replace) != count:
1271*e1fe3e4aSElliott Hughes                replace = replace * count
1272*e1fe3e4aSElliott Hughes            replaces.append(replace)
1273*e1fe3e4aSElliott Hughes        replaces = list(zip(*replaces))
1274*e1fe3e4aSElliott Hughes
1275*e1fe3e4aSElliott Hughes        seen_originals = set()
1276*e1fe3e4aSElliott Hughes        for i, original in enumerate(originals):
1277*e1fe3e4aSElliott Hughes            if original not in seen_originals:
1278*e1fe3e4aSElliott Hughes                seen_originals.add(original)
1279*e1fe3e4aSElliott Hughes                builder.add_multiple_subst(
1280*e1fe3e4aSElliott Hughes                    self.location,
1281*e1fe3e4aSElliott Hughes                    prefix,
1282*e1fe3e4aSElliott Hughes                    original,
1283*e1fe3e4aSElliott Hughes                    suffix,
1284*e1fe3e4aSElliott Hughes                    replaces and replaces[i] or (),
1285*e1fe3e4aSElliott Hughes                    self.forceChain,
1286*e1fe3e4aSElliott Hughes                )
1287*e1fe3e4aSElliott Hughes
1288*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1289*e1fe3e4aSElliott Hughes        res = "sub "
1290*e1fe3e4aSElliott Hughes        if len(self.prefix) or len(self.suffix) or self.forceChain:
1291*e1fe3e4aSElliott Hughes            if len(self.prefix):
1292*e1fe3e4aSElliott Hughes                res += " ".join(map(asFea, self.prefix)) + " "
1293*e1fe3e4aSElliott Hughes            res += asFea(self.glyph) + "'"
1294*e1fe3e4aSElliott Hughes            if len(self.suffix):
1295*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, self.suffix))
1296*e1fe3e4aSElliott Hughes        else:
1297*e1fe3e4aSElliott Hughes            res += asFea(self.glyph)
1298*e1fe3e4aSElliott Hughes        replacement = self.replacement or [NullGlyph()]
1299*e1fe3e4aSElliott Hughes        res += " by "
1300*e1fe3e4aSElliott Hughes        res += " ".join(map(asFea, replacement))
1301*e1fe3e4aSElliott Hughes        res += ";"
1302*e1fe3e4aSElliott Hughes        return res
1303*e1fe3e4aSElliott Hughes
1304*e1fe3e4aSElliott Hughes
1305*e1fe3e4aSElliott Hughesclass PairPosStatement(Statement):
1306*e1fe3e4aSElliott Hughes    """A pair positioning statement.
1307*e1fe3e4aSElliott Hughes
1308*e1fe3e4aSElliott Hughes    ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_.
1309*e1fe3e4aSElliott Hughes    ``valuerecord1`` should be a :class:`ValueRecord` object;
1310*e1fe3e4aSElliott Hughes    ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``.
1311*e1fe3e4aSElliott Hughes    If ``enumerated`` is true, then this is expressed as an
1312*e1fe3e4aSElliott Hughes    `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_.
1313*e1fe3e4aSElliott Hughes    """
1314*e1fe3e4aSElliott Hughes
1315*e1fe3e4aSElliott Hughes    def __init__(
1316*e1fe3e4aSElliott Hughes        self,
1317*e1fe3e4aSElliott Hughes        glyphs1,
1318*e1fe3e4aSElliott Hughes        valuerecord1,
1319*e1fe3e4aSElliott Hughes        glyphs2,
1320*e1fe3e4aSElliott Hughes        valuerecord2,
1321*e1fe3e4aSElliott Hughes        enumerated=False,
1322*e1fe3e4aSElliott Hughes        location=None,
1323*e1fe3e4aSElliott Hughes    ):
1324*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1325*e1fe3e4aSElliott Hughes        self.enumerated = enumerated
1326*e1fe3e4aSElliott Hughes        self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
1327*e1fe3e4aSElliott Hughes        self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
1328*e1fe3e4aSElliott Hughes
1329*e1fe3e4aSElliott Hughes    def build(self, builder):
1330*e1fe3e4aSElliott Hughes        """Calls a callback on the builder object:
1331*e1fe3e4aSElliott Hughes
1332*e1fe3e4aSElliott Hughes        * If the rule is enumerated, calls ``add_specific_pair_pos`` on each
1333*e1fe3e4aSElliott Hughes          combination of first and second glyphs.
1334*e1fe3e4aSElliott Hughes        * If the glyphs are both single :class:`GlyphName` objects, calls
1335*e1fe3e4aSElliott Hughes          ``add_specific_pair_pos``.
1336*e1fe3e4aSElliott Hughes        * Else, calls ``add_class_pair_pos``.
1337*e1fe3e4aSElliott Hughes        """
1338*e1fe3e4aSElliott Hughes        if self.enumerated:
1339*e1fe3e4aSElliott Hughes            g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
1340*e1fe3e4aSElliott Hughes            seen_pair = False
1341*e1fe3e4aSElliott Hughes            for glyph1, glyph2 in itertools.product(*g):
1342*e1fe3e4aSElliott Hughes                seen_pair = True
1343*e1fe3e4aSElliott Hughes                builder.add_specific_pair_pos(
1344*e1fe3e4aSElliott Hughes                    self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2
1345*e1fe3e4aSElliott Hughes                )
1346*e1fe3e4aSElliott Hughes            if not seen_pair:
1347*e1fe3e4aSElliott Hughes                raise FeatureLibError(
1348*e1fe3e4aSElliott Hughes                    "Empty glyph class in positioning rule", self.location
1349*e1fe3e4aSElliott Hughes                )
1350*e1fe3e4aSElliott Hughes            return
1351*e1fe3e4aSElliott Hughes
1352*e1fe3e4aSElliott Hughes        is_specific = isinstance(self.glyphs1, GlyphName) and isinstance(
1353*e1fe3e4aSElliott Hughes            self.glyphs2, GlyphName
1354*e1fe3e4aSElliott Hughes        )
1355*e1fe3e4aSElliott Hughes        if is_specific:
1356*e1fe3e4aSElliott Hughes            builder.add_specific_pair_pos(
1357*e1fe3e4aSElliott Hughes                self.location,
1358*e1fe3e4aSElliott Hughes                self.glyphs1.glyph,
1359*e1fe3e4aSElliott Hughes                self.valuerecord1,
1360*e1fe3e4aSElliott Hughes                self.glyphs2.glyph,
1361*e1fe3e4aSElliott Hughes                self.valuerecord2,
1362*e1fe3e4aSElliott Hughes            )
1363*e1fe3e4aSElliott Hughes        else:
1364*e1fe3e4aSElliott Hughes            builder.add_class_pair_pos(
1365*e1fe3e4aSElliott Hughes                self.location,
1366*e1fe3e4aSElliott Hughes                self.glyphs1.glyphSet(),
1367*e1fe3e4aSElliott Hughes                self.valuerecord1,
1368*e1fe3e4aSElliott Hughes                self.glyphs2.glyphSet(),
1369*e1fe3e4aSElliott Hughes                self.valuerecord2,
1370*e1fe3e4aSElliott Hughes            )
1371*e1fe3e4aSElliott Hughes
1372*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1373*e1fe3e4aSElliott Hughes        res = "enum " if self.enumerated else ""
1374*e1fe3e4aSElliott Hughes        if self.valuerecord2:
1375*e1fe3e4aSElliott Hughes            res += "pos {} {} {} {};".format(
1376*e1fe3e4aSElliott Hughes                self.glyphs1.asFea(),
1377*e1fe3e4aSElliott Hughes                self.valuerecord1.asFea(),
1378*e1fe3e4aSElliott Hughes                self.glyphs2.asFea(),
1379*e1fe3e4aSElliott Hughes                self.valuerecord2.asFea(),
1380*e1fe3e4aSElliott Hughes            )
1381*e1fe3e4aSElliott Hughes        else:
1382*e1fe3e4aSElliott Hughes            res += "pos {} {} {};".format(
1383*e1fe3e4aSElliott Hughes                self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea()
1384*e1fe3e4aSElliott Hughes            )
1385*e1fe3e4aSElliott Hughes        return res
1386*e1fe3e4aSElliott Hughes
1387*e1fe3e4aSElliott Hughes
1388*e1fe3e4aSElliott Hughesclass ReverseChainSingleSubstStatement(Statement):
1389*e1fe3e4aSElliott Hughes    """A reverse chaining substitution statement. You don't see those every day.
1390*e1fe3e4aSElliott Hughes
1391*e1fe3e4aSElliott Hughes    Note the unusual argument order: ``suffix`` comes `before` ``glyphs``.
1392*e1fe3e4aSElliott Hughes    ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be
1393*e1fe3e4aSElliott Hughes    lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should
1394*e1fe3e4aSElliott Hughes    be one-item lists.
1395*e1fe3e4aSElliott Hughes    """
1396*e1fe3e4aSElliott Hughes
1397*e1fe3e4aSElliott Hughes    def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None):
1398*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1399*e1fe3e4aSElliott Hughes        self.old_prefix, self.old_suffix = old_prefix, old_suffix
1400*e1fe3e4aSElliott Hughes        self.glyphs = glyphs
1401*e1fe3e4aSElliott Hughes        self.replacements = replacements
1402*e1fe3e4aSElliott Hughes
1403*e1fe3e4aSElliott Hughes    def build(self, builder):
1404*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.old_prefix]
1405*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.old_suffix]
1406*e1fe3e4aSElliott Hughes        originals = self.glyphs[0].glyphSet()
1407*e1fe3e4aSElliott Hughes        replaces = self.replacements[0].glyphSet()
1408*e1fe3e4aSElliott Hughes        if len(replaces) == 1:
1409*e1fe3e4aSElliott Hughes            replaces = replaces * len(originals)
1410*e1fe3e4aSElliott Hughes        builder.add_reverse_chain_single_subst(
1411*e1fe3e4aSElliott Hughes            self.location, prefix, suffix, dict(zip(originals, replaces))
1412*e1fe3e4aSElliott Hughes        )
1413*e1fe3e4aSElliott Hughes
1414*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1415*e1fe3e4aSElliott Hughes        res = "rsub "
1416*e1fe3e4aSElliott Hughes        if len(self.old_prefix) or len(self.old_suffix):
1417*e1fe3e4aSElliott Hughes            if len(self.old_prefix):
1418*e1fe3e4aSElliott Hughes                res += " ".join(asFea(g) for g in self.old_prefix) + " "
1419*e1fe3e4aSElliott Hughes            res += " ".join(asFea(g) + "'" for g in self.glyphs)
1420*e1fe3e4aSElliott Hughes            if len(self.old_suffix):
1421*e1fe3e4aSElliott Hughes                res += " " + " ".join(asFea(g) for g in self.old_suffix)
1422*e1fe3e4aSElliott Hughes        else:
1423*e1fe3e4aSElliott Hughes            res += " ".join(map(asFea, self.glyphs))
1424*e1fe3e4aSElliott Hughes        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1425*e1fe3e4aSElliott Hughes        return res
1426*e1fe3e4aSElliott Hughes
1427*e1fe3e4aSElliott Hughes
1428*e1fe3e4aSElliott Hughesclass SingleSubstStatement(Statement):
1429*e1fe3e4aSElliott Hughes    """A single substitution statement.
1430*e1fe3e4aSElliott Hughes
1431*e1fe3e4aSElliott Hughes    Note the unusual argument order: ``prefix`` and suffix come `after`
1432*e1fe3e4aSElliott Hughes    the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and
1433*e1fe3e4aSElliott Hughes    ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and
1434*e1fe3e4aSElliott Hughes    ``replace`` should be one-item lists.
1435*e1fe3e4aSElliott Hughes    """
1436*e1fe3e4aSElliott Hughes
1437*e1fe3e4aSElliott Hughes    def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None):
1438*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1439*e1fe3e4aSElliott Hughes        self.prefix, self.suffix = prefix, suffix
1440*e1fe3e4aSElliott Hughes        self.forceChain = forceChain
1441*e1fe3e4aSElliott Hughes        self.glyphs = glyphs
1442*e1fe3e4aSElliott Hughes        self.replacements = replace
1443*e1fe3e4aSElliott Hughes
1444*e1fe3e4aSElliott Hughes    def build(self, builder):
1445*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_single_subst`` callback."""
1446*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
1447*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
1448*e1fe3e4aSElliott Hughes        originals = self.glyphs[0].glyphSet()
1449*e1fe3e4aSElliott Hughes        replaces = self.replacements[0].glyphSet()
1450*e1fe3e4aSElliott Hughes        if len(replaces) == 1:
1451*e1fe3e4aSElliott Hughes            replaces = replaces * len(originals)
1452*e1fe3e4aSElliott Hughes        builder.add_single_subst(
1453*e1fe3e4aSElliott Hughes            self.location,
1454*e1fe3e4aSElliott Hughes            prefix,
1455*e1fe3e4aSElliott Hughes            suffix,
1456*e1fe3e4aSElliott Hughes            OrderedDict(zip(originals, replaces)),
1457*e1fe3e4aSElliott Hughes            self.forceChain,
1458*e1fe3e4aSElliott Hughes        )
1459*e1fe3e4aSElliott Hughes
1460*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1461*e1fe3e4aSElliott Hughes        res = "sub "
1462*e1fe3e4aSElliott Hughes        if len(self.prefix) or len(self.suffix) or self.forceChain:
1463*e1fe3e4aSElliott Hughes            if len(self.prefix):
1464*e1fe3e4aSElliott Hughes                res += " ".join(asFea(g) for g in self.prefix) + " "
1465*e1fe3e4aSElliott Hughes            res += " ".join(asFea(g) + "'" for g in self.glyphs)
1466*e1fe3e4aSElliott Hughes            if len(self.suffix):
1467*e1fe3e4aSElliott Hughes                res += " " + " ".join(asFea(g) for g in self.suffix)
1468*e1fe3e4aSElliott Hughes        else:
1469*e1fe3e4aSElliott Hughes            res += " ".join(asFea(g) for g in self.glyphs)
1470*e1fe3e4aSElliott Hughes        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1471*e1fe3e4aSElliott Hughes        return res
1472*e1fe3e4aSElliott Hughes
1473*e1fe3e4aSElliott Hughes
1474*e1fe3e4aSElliott Hughesclass ScriptStatement(Statement):
1475*e1fe3e4aSElliott Hughes    """A ``script`` statement."""
1476*e1fe3e4aSElliott Hughes
1477*e1fe3e4aSElliott Hughes    def __init__(self, script, location=None):
1478*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1479*e1fe3e4aSElliott Hughes        self.script = script  #: the script code
1480*e1fe3e4aSElliott Hughes
1481*e1fe3e4aSElliott Hughes    def build(self, builder):
1482*e1fe3e4aSElliott Hughes        """Calls the builder's ``set_script`` callback."""
1483*e1fe3e4aSElliott Hughes        builder.set_script(self.location, self.script)
1484*e1fe3e4aSElliott Hughes
1485*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1486*e1fe3e4aSElliott Hughes        return "script {};".format(self.script.strip())
1487*e1fe3e4aSElliott Hughes
1488*e1fe3e4aSElliott Hughes
1489*e1fe3e4aSElliott Hughesclass SinglePosStatement(Statement):
1490*e1fe3e4aSElliott Hughes    """A single position statement. ``prefix`` and ``suffix`` should be
1491*e1fe3e4aSElliott Hughes    lists of `glyph-containing objects`_.
1492*e1fe3e4aSElliott Hughes
1493*e1fe3e4aSElliott Hughes    ``pos`` should be a one-element list containing a (`glyph-containing object`_,
1494*e1fe3e4aSElliott Hughes    :class:`ValueRecord`) tuple."""
1495*e1fe3e4aSElliott Hughes
1496*e1fe3e4aSElliott Hughes    def __init__(self, pos, prefix, suffix, forceChain, location=None):
1497*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1498*e1fe3e4aSElliott Hughes        self.pos, self.prefix, self.suffix = pos, prefix, suffix
1499*e1fe3e4aSElliott Hughes        self.forceChain = forceChain
1500*e1fe3e4aSElliott Hughes
1501*e1fe3e4aSElliott Hughes    def build(self, builder):
1502*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_single_pos`` callback."""
1503*e1fe3e4aSElliott Hughes        prefix = [p.glyphSet() for p in self.prefix]
1504*e1fe3e4aSElliott Hughes        suffix = [s.glyphSet() for s in self.suffix]
1505*e1fe3e4aSElliott Hughes        pos = [(g.glyphSet(), value) for g, value in self.pos]
1506*e1fe3e4aSElliott Hughes        builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain)
1507*e1fe3e4aSElliott Hughes
1508*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1509*e1fe3e4aSElliott Hughes        res = "pos "
1510*e1fe3e4aSElliott Hughes        if len(self.prefix) or len(self.suffix) or self.forceChain:
1511*e1fe3e4aSElliott Hughes            if len(self.prefix):
1512*e1fe3e4aSElliott Hughes                res += " ".join(map(asFea, self.prefix)) + " "
1513*e1fe3e4aSElliott Hughes            res += " ".join(
1514*e1fe3e4aSElliott Hughes                [
1515*e1fe3e4aSElliott Hughes                    asFea(x[0]) + "'" + ((" " + x[1].asFea()) if x[1] else "")
1516*e1fe3e4aSElliott Hughes                    for x in self.pos
1517*e1fe3e4aSElliott Hughes                ]
1518*e1fe3e4aSElliott Hughes            )
1519*e1fe3e4aSElliott Hughes            if len(self.suffix):
1520*e1fe3e4aSElliott Hughes                res += " " + " ".join(map(asFea, self.suffix))
1521*e1fe3e4aSElliott Hughes        else:
1522*e1fe3e4aSElliott Hughes            res += " ".join(
1523*e1fe3e4aSElliott Hughes                [asFea(x[0]) + " " + (x[1].asFea() if x[1] else "") for x in self.pos]
1524*e1fe3e4aSElliott Hughes            )
1525*e1fe3e4aSElliott Hughes        res += ";"
1526*e1fe3e4aSElliott Hughes        return res
1527*e1fe3e4aSElliott Hughes
1528*e1fe3e4aSElliott Hughes
1529*e1fe3e4aSElliott Hughesclass SubtableStatement(Statement):
1530*e1fe3e4aSElliott Hughes    """Represents a subtable break."""
1531*e1fe3e4aSElliott Hughes
1532*e1fe3e4aSElliott Hughes    def __init__(self, location=None):
1533*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1534*e1fe3e4aSElliott Hughes
1535*e1fe3e4aSElliott Hughes    def build(self, builder):
1536*e1fe3e4aSElliott Hughes        """Calls the builder objects's ``add_subtable_break`` callback."""
1537*e1fe3e4aSElliott Hughes        builder.add_subtable_break(self.location)
1538*e1fe3e4aSElliott Hughes
1539*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1540*e1fe3e4aSElliott Hughes        return "subtable;"
1541*e1fe3e4aSElliott Hughes
1542*e1fe3e4aSElliott Hughes
1543*e1fe3e4aSElliott Hughesclass ValueRecord(Expression):
1544*e1fe3e4aSElliott Hughes    """Represents a value record."""
1545*e1fe3e4aSElliott Hughes
1546*e1fe3e4aSElliott Hughes    def __init__(
1547*e1fe3e4aSElliott Hughes        self,
1548*e1fe3e4aSElliott Hughes        xPlacement=None,
1549*e1fe3e4aSElliott Hughes        yPlacement=None,
1550*e1fe3e4aSElliott Hughes        xAdvance=None,
1551*e1fe3e4aSElliott Hughes        yAdvance=None,
1552*e1fe3e4aSElliott Hughes        xPlaDevice=None,
1553*e1fe3e4aSElliott Hughes        yPlaDevice=None,
1554*e1fe3e4aSElliott Hughes        xAdvDevice=None,
1555*e1fe3e4aSElliott Hughes        yAdvDevice=None,
1556*e1fe3e4aSElliott Hughes        vertical=False,
1557*e1fe3e4aSElliott Hughes        location=None,
1558*e1fe3e4aSElliott Hughes    ):
1559*e1fe3e4aSElliott Hughes        Expression.__init__(self, location)
1560*e1fe3e4aSElliott Hughes        self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
1561*e1fe3e4aSElliott Hughes        self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
1562*e1fe3e4aSElliott Hughes        self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice)
1563*e1fe3e4aSElliott Hughes        self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice)
1564*e1fe3e4aSElliott Hughes        self.vertical = vertical
1565*e1fe3e4aSElliott Hughes
1566*e1fe3e4aSElliott Hughes    def __eq__(self, other):
1567*e1fe3e4aSElliott Hughes        return (
1568*e1fe3e4aSElliott Hughes            self.xPlacement == other.xPlacement
1569*e1fe3e4aSElliott Hughes            and self.yPlacement == other.yPlacement
1570*e1fe3e4aSElliott Hughes            and self.xAdvance == other.xAdvance
1571*e1fe3e4aSElliott Hughes            and self.yAdvance == other.yAdvance
1572*e1fe3e4aSElliott Hughes            and self.xPlaDevice == other.xPlaDevice
1573*e1fe3e4aSElliott Hughes            and self.xAdvDevice == other.xAdvDevice
1574*e1fe3e4aSElliott Hughes        )
1575*e1fe3e4aSElliott Hughes
1576*e1fe3e4aSElliott Hughes    def __ne__(self, other):
1577*e1fe3e4aSElliott Hughes        return not self.__eq__(other)
1578*e1fe3e4aSElliott Hughes
1579*e1fe3e4aSElliott Hughes    def __hash__(self):
1580*e1fe3e4aSElliott Hughes        return (
1581*e1fe3e4aSElliott Hughes            hash(self.xPlacement)
1582*e1fe3e4aSElliott Hughes            ^ hash(self.yPlacement)
1583*e1fe3e4aSElliott Hughes            ^ hash(self.xAdvance)
1584*e1fe3e4aSElliott Hughes            ^ hash(self.yAdvance)
1585*e1fe3e4aSElliott Hughes            ^ hash(self.xPlaDevice)
1586*e1fe3e4aSElliott Hughes            ^ hash(self.yPlaDevice)
1587*e1fe3e4aSElliott Hughes            ^ hash(self.xAdvDevice)
1588*e1fe3e4aSElliott Hughes            ^ hash(self.yAdvDevice)
1589*e1fe3e4aSElliott Hughes        )
1590*e1fe3e4aSElliott Hughes
1591*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1592*e1fe3e4aSElliott Hughes        if not self:
1593*e1fe3e4aSElliott Hughes            return "<NULL>"
1594*e1fe3e4aSElliott Hughes
1595*e1fe3e4aSElliott Hughes        x, y = self.xPlacement, self.yPlacement
1596*e1fe3e4aSElliott Hughes        xAdvance, yAdvance = self.xAdvance, self.yAdvance
1597*e1fe3e4aSElliott Hughes        xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice
1598*e1fe3e4aSElliott Hughes        xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice
1599*e1fe3e4aSElliott Hughes        vertical = self.vertical
1600*e1fe3e4aSElliott Hughes
1601*e1fe3e4aSElliott Hughes        # Try format A, if possible.
1602*e1fe3e4aSElliott Hughes        if x is None and y is None:
1603*e1fe3e4aSElliott Hughes            if xAdvance is None and vertical:
1604*e1fe3e4aSElliott Hughes                return str(yAdvance)
1605*e1fe3e4aSElliott Hughes            elif yAdvance is None and not vertical:
1606*e1fe3e4aSElliott Hughes                return str(xAdvance)
1607*e1fe3e4aSElliott Hughes
1608*e1fe3e4aSElliott Hughes        # Make any remaining None value 0 to avoid generating invalid records.
1609*e1fe3e4aSElliott Hughes        x = x or 0
1610*e1fe3e4aSElliott Hughes        y = y or 0
1611*e1fe3e4aSElliott Hughes        xAdvance = xAdvance or 0
1612*e1fe3e4aSElliott Hughes        yAdvance = yAdvance or 0
1613*e1fe3e4aSElliott Hughes
1614*e1fe3e4aSElliott Hughes        # Try format B, if possible.
1615*e1fe3e4aSElliott Hughes        if (
1616*e1fe3e4aSElliott Hughes            xPlaDevice is None
1617*e1fe3e4aSElliott Hughes            and yPlaDevice is None
1618*e1fe3e4aSElliott Hughes            and xAdvDevice is None
1619*e1fe3e4aSElliott Hughes            and yAdvDevice is None
1620*e1fe3e4aSElliott Hughes        ):
1621*e1fe3e4aSElliott Hughes            return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
1622*e1fe3e4aSElliott Hughes
1623*e1fe3e4aSElliott Hughes        # Last resort is format C.
1624*e1fe3e4aSElliott Hughes        return "<%s %s %s %s %s %s %s %s>" % (
1625*e1fe3e4aSElliott Hughes            x,
1626*e1fe3e4aSElliott Hughes            y,
1627*e1fe3e4aSElliott Hughes            xAdvance,
1628*e1fe3e4aSElliott Hughes            yAdvance,
1629*e1fe3e4aSElliott Hughes            deviceToString(xPlaDevice),
1630*e1fe3e4aSElliott Hughes            deviceToString(yPlaDevice),
1631*e1fe3e4aSElliott Hughes            deviceToString(xAdvDevice),
1632*e1fe3e4aSElliott Hughes            deviceToString(yAdvDevice),
1633*e1fe3e4aSElliott Hughes        )
1634*e1fe3e4aSElliott Hughes
1635*e1fe3e4aSElliott Hughes    def __bool__(self):
1636*e1fe3e4aSElliott Hughes        return any(
1637*e1fe3e4aSElliott Hughes            getattr(self, v) is not None
1638*e1fe3e4aSElliott Hughes            for v in [
1639*e1fe3e4aSElliott Hughes                "xPlacement",
1640*e1fe3e4aSElliott Hughes                "yPlacement",
1641*e1fe3e4aSElliott Hughes                "xAdvance",
1642*e1fe3e4aSElliott Hughes                "yAdvance",
1643*e1fe3e4aSElliott Hughes                "xPlaDevice",
1644*e1fe3e4aSElliott Hughes                "yPlaDevice",
1645*e1fe3e4aSElliott Hughes                "xAdvDevice",
1646*e1fe3e4aSElliott Hughes                "yAdvDevice",
1647*e1fe3e4aSElliott Hughes            ]
1648*e1fe3e4aSElliott Hughes        )
1649*e1fe3e4aSElliott Hughes
1650*e1fe3e4aSElliott Hughes    __nonzero__ = __bool__
1651*e1fe3e4aSElliott Hughes
1652*e1fe3e4aSElliott Hughes
1653*e1fe3e4aSElliott Hughesclass ValueRecordDefinition(Statement):
1654*e1fe3e4aSElliott Hughes    """Represents a named value record definition."""
1655*e1fe3e4aSElliott Hughes
1656*e1fe3e4aSElliott Hughes    def __init__(self, name, value, location=None):
1657*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1658*e1fe3e4aSElliott Hughes        self.name = name  #: Value record name as string
1659*e1fe3e4aSElliott Hughes        self.value = value  #: :class:`ValueRecord` object
1660*e1fe3e4aSElliott Hughes
1661*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1662*e1fe3e4aSElliott Hughes        return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
1663*e1fe3e4aSElliott Hughes
1664*e1fe3e4aSElliott Hughes
1665*e1fe3e4aSElliott Hughesdef simplify_name_attributes(pid, eid, lid):
1666*e1fe3e4aSElliott Hughes    if pid == 3 and eid == 1 and lid == 1033:
1667*e1fe3e4aSElliott Hughes        return ""
1668*e1fe3e4aSElliott Hughes    elif pid == 1 and eid == 0 and lid == 0:
1669*e1fe3e4aSElliott Hughes        return "1"
1670*e1fe3e4aSElliott Hughes    else:
1671*e1fe3e4aSElliott Hughes        return "{} {} {}".format(pid, eid, lid)
1672*e1fe3e4aSElliott Hughes
1673*e1fe3e4aSElliott Hughes
1674*e1fe3e4aSElliott Hughesclass NameRecord(Statement):
1675*e1fe3e4aSElliott Hughes    """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)"""
1676*e1fe3e4aSElliott Hughes
1677*e1fe3e4aSElliott Hughes    def __init__(self, nameID, platformID, platEncID, langID, string, location=None):
1678*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1679*e1fe3e4aSElliott Hughes        self.nameID = nameID  #: Name ID as integer (e.g. 9 for designer's name)
1680*e1fe3e4aSElliott Hughes        self.platformID = platformID  #: Platform ID as integer
1681*e1fe3e4aSElliott Hughes        self.platEncID = platEncID  #: Platform encoding ID as integer
1682*e1fe3e4aSElliott Hughes        self.langID = langID  #: Language ID as integer
1683*e1fe3e4aSElliott Hughes        self.string = string  #: Name record value
1684*e1fe3e4aSElliott Hughes
1685*e1fe3e4aSElliott Hughes    def build(self, builder):
1686*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_name_record`` callback."""
1687*e1fe3e4aSElliott Hughes        builder.add_name_record(
1688*e1fe3e4aSElliott Hughes            self.location,
1689*e1fe3e4aSElliott Hughes            self.nameID,
1690*e1fe3e4aSElliott Hughes            self.platformID,
1691*e1fe3e4aSElliott Hughes            self.platEncID,
1692*e1fe3e4aSElliott Hughes            self.langID,
1693*e1fe3e4aSElliott Hughes            self.string,
1694*e1fe3e4aSElliott Hughes        )
1695*e1fe3e4aSElliott Hughes
1696*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1697*e1fe3e4aSElliott Hughes        def escape(c, escape_pattern):
1698*e1fe3e4aSElliott Hughes            # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
1699*e1fe3e4aSElliott Hughes            if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
1700*e1fe3e4aSElliott Hughes                return chr(c)
1701*e1fe3e4aSElliott Hughes            else:
1702*e1fe3e4aSElliott Hughes                return escape_pattern % c
1703*e1fe3e4aSElliott Hughes
1704*e1fe3e4aSElliott Hughes        encoding = getEncoding(self.platformID, self.platEncID, self.langID)
1705*e1fe3e4aSElliott Hughes        if encoding is None:
1706*e1fe3e4aSElliott Hughes            raise FeatureLibError("Unsupported encoding", self.location)
1707*e1fe3e4aSElliott Hughes        s = tobytes(self.string, encoding=encoding)
1708*e1fe3e4aSElliott Hughes        if encoding == "utf_16_be":
1709*e1fe3e4aSElliott Hughes            escaped_string = "".join(
1710*e1fe3e4aSElliott Hughes                [
1711*e1fe3e4aSElliott Hughes                    escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
1712*e1fe3e4aSElliott Hughes                    for i in range(0, len(s), 2)
1713*e1fe3e4aSElliott Hughes                ]
1714*e1fe3e4aSElliott Hughes            )
1715*e1fe3e4aSElliott Hughes        else:
1716*e1fe3e4aSElliott Hughes            escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
1717*e1fe3e4aSElliott Hughes        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1718*e1fe3e4aSElliott Hughes        if plat != "":
1719*e1fe3e4aSElliott Hughes            plat += " "
1720*e1fe3e4aSElliott Hughes        return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string)
1721*e1fe3e4aSElliott Hughes
1722*e1fe3e4aSElliott Hughes
1723*e1fe3e4aSElliott Hughesclass FeatureNameStatement(NameRecord):
1724*e1fe3e4aSElliott Hughes    """Represents a ``sizemenuname`` or ``name`` statement."""
1725*e1fe3e4aSElliott Hughes
1726*e1fe3e4aSElliott Hughes    def build(self, builder):
1727*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_featureName`` callback."""
1728*e1fe3e4aSElliott Hughes        NameRecord.build(self, builder)
1729*e1fe3e4aSElliott Hughes        builder.add_featureName(self.nameID)
1730*e1fe3e4aSElliott Hughes
1731*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1732*e1fe3e4aSElliott Hughes        if self.nameID == "size":
1733*e1fe3e4aSElliott Hughes            tag = "sizemenuname"
1734*e1fe3e4aSElliott Hughes        else:
1735*e1fe3e4aSElliott Hughes            tag = "name"
1736*e1fe3e4aSElliott Hughes        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1737*e1fe3e4aSElliott Hughes        if plat != "":
1738*e1fe3e4aSElliott Hughes            plat += " "
1739*e1fe3e4aSElliott Hughes        return '{} {}"{}";'.format(tag, plat, self.string)
1740*e1fe3e4aSElliott Hughes
1741*e1fe3e4aSElliott Hughes
1742*e1fe3e4aSElliott Hughesclass STATNameStatement(NameRecord):
1743*e1fe3e4aSElliott Hughes    """Represents a STAT table ``name`` statement."""
1744*e1fe3e4aSElliott Hughes
1745*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1746*e1fe3e4aSElliott Hughes        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1747*e1fe3e4aSElliott Hughes        if plat != "":
1748*e1fe3e4aSElliott Hughes            plat += " "
1749*e1fe3e4aSElliott Hughes        return 'name {}"{}";'.format(plat, self.string)
1750*e1fe3e4aSElliott Hughes
1751*e1fe3e4aSElliott Hughes
1752*e1fe3e4aSElliott Hughesclass SizeParameters(Statement):
1753*e1fe3e4aSElliott Hughes    """A ``parameters`` statement."""
1754*e1fe3e4aSElliott Hughes
1755*e1fe3e4aSElliott Hughes    def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None):
1756*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1757*e1fe3e4aSElliott Hughes        self.DesignSize = DesignSize
1758*e1fe3e4aSElliott Hughes        self.SubfamilyID = SubfamilyID
1759*e1fe3e4aSElliott Hughes        self.RangeStart = RangeStart
1760*e1fe3e4aSElliott Hughes        self.RangeEnd = RangeEnd
1761*e1fe3e4aSElliott Hughes
1762*e1fe3e4aSElliott Hughes    def build(self, builder):
1763*e1fe3e4aSElliott Hughes        """Calls the builder object's ``set_size_parameters`` callback."""
1764*e1fe3e4aSElliott Hughes        builder.set_size_parameters(
1765*e1fe3e4aSElliott Hughes            self.location,
1766*e1fe3e4aSElliott Hughes            self.DesignSize,
1767*e1fe3e4aSElliott Hughes            self.SubfamilyID,
1768*e1fe3e4aSElliott Hughes            self.RangeStart,
1769*e1fe3e4aSElliott Hughes            self.RangeEnd,
1770*e1fe3e4aSElliott Hughes        )
1771*e1fe3e4aSElliott Hughes
1772*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1773*e1fe3e4aSElliott Hughes        res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
1774*e1fe3e4aSElliott Hughes        if self.RangeStart != 0 or self.RangeEnd != 0:
1775*e1fe3e4aSElliott Hughes            res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10))
1776*e1fe3e4aSElliott Hughes        return res + ";"
1777*e1fe3e4aSElliott Hughes
1778*e1fe3e4aSElliott Hughes
1779*e1fe3e4aSElliott Hughesclass CVParametersNameStatement(NameRecord):
1780*e1fe3e4aSElliott Hughes    """Represent a name statement inside a ``cvParameters`` block."""
1781*e1fe3e4aSElliott Hughes
1782*e1fe3e4aSElliott Hughes    def __init__(
1783*e1fe3e4aSElliott Hughes        self, nameID, platformID, platEncID, langID, string, block_name, location=None
1784*e1fe3e4aSElliott Hughes    ):
1785*e1fe3e4aSElliott Hughes        NameRecord.__init__(
1786*e1fe3e4aSElliott Hughes            self, nameID, platformID, platEncID, langID, string, location=location
1787*e1fe3e4aSElliott Hughes        )
1788*e1fe3e4aSElliott Hughes        self.block_name = block_name
1789*e1fe3e4aSElliott Hughes
1790*e1fe3e4aSElliott Hughes    def build(self, builder):
1791*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_cv_parameter`` callback."""
1792*e1fe3e4aSElliott Hughes        item = ""
1793*e1fe3e4aSElliott Hughes        if self.block_name == "ParamUILabelNameID":
1794*e1fe3e4aSElliott Hughes            item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
1795*e1fe3e4aSElliott Hughes        builder.add_cv_parameter(self.nameID)
1796*e1fe3e4aSElliott Hughes        self.nameID = (self.nameID, self.block_name + item)
1797*e1fe3e4aSElliott Hughes        NameRecord.build(self, builder)
1798*e1fe3e4aSElliott Hughes
1799*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1800*e1fe3e4aSElliott Hughes        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1801*e1fe3e4aSElliott Hughes        if plat != "":
1802*e1fe3e4aSElliott Hughes            plat += " "
1803*e1fe3e4aSElliott Hughes        return 'name {}"{}";'.format(plat, self.string)
1804*e1fe3e4aSElliott Hughes
1805*e1fe3e4aSElliott Hughes
1806*e1fe3e4aSElliott Hughesclass CharacterStatement(Statement):
1807*e1fe3e4aSElliott Hughes    """
1808*e1fe3e4aSElliott Hughes    Statement used in cvParameters blocks of Character Variant features (cvXX).
1809*e1fe3e4aSElliott Hughes    The Unicode value may be written with either decimal or hexadecimal
1810*e1fe3e4aSElliott Hughes    notation. The value must be preceded by '0x' if it is a hexadecimal value.
1811*e1fe3e4aSElliott Hughes    The largest Unicode value allowed is 0xFFFFFF.
1812*e1fe3e4aSElliott Hughes    """
1813*e1fe3e4aSElliott Hughes
1814*e1fe3e4aSElliott Hughes    def __init__(self, character, tag, location=None):
1815*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1816*e1fe3e4aSElliott Hughes        self.character = character
1817*e1fe3e4aSElliott Hughes        self.tag = tag
1818*e1fe3e4aSElliott Hughes
1819*e1fe3e4aSElliott Hughes    def build(self, builder):
1820*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_cv_character`` callback."""
1821*e1fe3e4aSElliott Hughes        builder.add_cv_character(self.character, self.tag)
1822*e1fe3e4aSElliott Hughes
1823*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1824*e1fe3e4aSElliott Hughes        return "Character {:#x};".format(self.character)
1825*e1fe3e4aSElliott Hughes
1826*e1fe3e4aSElliott Hughes
1827*e1fe3e4aSElliott Hughesclass BaseAxis(Statement):
1828*e1fe3e4aSElliott Hughes    """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList``
1829*e1fe3e4aSElliott Hughes    pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair."""
1830*e1fe3e4aSElliott Hughes
1831*e1fe3e4aSElliott Hughes    def __init__(self, bases, scripts, vertical, location=None):
1832*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1833*e1fe3e4aSElliott Hughes        self.bases = bases  #: A list of baseline tag names as strings
1834*e1fe3e4aSElliott Hughes        self.scripts = scripts  #: A list of script record tuplets (script tag, default baseline tag, base coordinate)
1835*e1fe3e4aSElliott Hughes        self.vertical = vertical  #: Boolean; VertAxis if True, HorizAxis if False
1836*e1fe3e4aSElliott Hughes
1837*e1fe3e4aSElliott Hughes    def build(self, builder):
1838*e1fe3e4aSElliott Hughes        """Calls the builder object's ``set_base_axis`` callback."""
1839*e1fe3e4aSElliott Hughes        builder.set_base_axis(self.bases, self.scripts, self.vertical)
1840*e1fe3e4aSElliott Hughes
1841*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1842*e1fe3e4aSElliott Hughes        direction = "Vert" if self.vertical else "Horiz"
1843*e1fe3e4aSElliott Hughes        scripts = [
1844*e1fe3e4aSElliott Hughes            "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2])))
1845*e1fe3e4aSElliott Hughes            for a in self.scripts
1846*e1fe3e4aSElliott Hughes        ]
1847*e1fe3e4aSElliott Hughes        return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
1848*e1fe3e4aSElliott Hughes            direction, " ".join(self.bases), indent, direction, ", ".join(scripts)
1849*e1fe3e4aSElliott Hughes        )
1850*e1fe3e4aSElliott Hughes
1851*e1fe3e4aSElliott Hughes
1852*e1fe3e4aSElliott Hughesclass OS2Field(Statement):
1853*e1fe3e4aSElliott Hughes    """An entry in the ``OS/2`` table. Most ``values`` should be numbers or
1854*e1fe3e4aSElliott Hughes    strings, apart from when the key is ``UnicodeRange``, ``CodePageRange``
1855*e1fe3e4aSElliott Hughes    or ``Panose``, in which case it should be an array of integers."""
1856*e1fe3e4aSElliott Hughes
1857*e1fe3e4aSElliott Hughes    def __init__(self, key, value, location=None):
1858*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1859*e1fe3e4aSElliott Hughes        self.key = key
1860*e1fe3e4aSElliott Hughes        self.value = value
1861*e1fe3e4aSElliott Hughes
1862*e1fe3e4aSElliott Hughes    def build(self, builder):
1863*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_os2_field`` callback."""
1864*e1fe3e4aSElliott Hughes        builder.add_os2_field(self.key, self.value)
1865*e1fe3e4aSElliott Hughes
1866*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1867*e1fe3e4aSElliott Hughes        def intarr2str(x):
1868*e1fe3e4aSElliott Hughes            return " ".join(map(str, x))
1869*e1fe3e4aSElliott Hughes
1870*e1fe3e4aSElliott Hughes        numbers = (
1871*e1fe3e4aSElliott Hughes            "FSType",
1872*e1fe3e4aSElliott Hughes            "TypoAscender",
1873*e1fe3e4aSElliott Hughes            "TypoDescender",
1874*e1fe3e4aSElliott Hughes            "TypoLineGap",
1875*e1fe3e4aSElliott Hughes            "winAscent",
1876*e1fe3e4aSElliott Hughes            "winDescent",
1877*e1fe3e4aSElliott Hughes            "XHeight",
1878*e1fe3e4aSElliott Hughes            "CapHeight",
1879*e1fe3e4aSElliott Hughes            "WeightClass",
1880*e1fe3e4aSElliott Hughes            "WidthClass",
1881*e1fe3e4aSElliott Hughes            "LowerOpSize",
1882*e1fe3e4aSElliott Hughes            "UpperOpSize",
1883*e1fe3e4aSElliott Hughes        )
1884*e1fe3e4aSElliott Hughes        ranges = ("UnicodeRange", "CodePageRange")
1885*e1fe3e4aSElliott Hughes        keywords = dict([(x.lower(), [x, str]) for x in numbers])
1886*e1fe3e4aSElliott Hughes        keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
1887*e1fe3e4aSElliott Hughes        keywords["panose"] = ["Panose", intarr2str]
1888*e1fe3e4aSElliott Hughes        keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
1889*e1fe3e4aSElliott Hughes        if self.key in keywords:
1890*e1fe3e4aSElliott Hughes            return "{} {};".format(
1891*e1fe3e4aSElliott Hughes                keywords[self.key][0], keywords[self.key][1](self.value)
1892*e1fe3e4aSElliott Hughes            )
1893*e1fe3e4aSElliott Hughes        return ""  # should raise exception
1894*e1fe3e4aSElliott Hughes
1895*e1fe3e4aSElliott Hughes
1896*e1fe3e4aSElliott Hughesclass HheaField(Statement):
1897*e1fe3e4aSElliott Hughes    """An entry in the ``hhea`` table."""
1898*e1fe3e4aSElliott Hughes
1899*e1fe3e4aSElliott Hughes    def __init__(self, key, value, location=None):
1900*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1901*e1fe3e4aSElliott Hughes        self.key = key
1902*e1fe3e4aSElliott Hughes        self.value = value
1903*e1fe3e4aSElliott Hughes
1904*e1fe3e4aSElliott Hughes    def build(self, builder):
1905*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_hhea_field`` callback."""
1906*e1fe3e4aSElliott Hughes        builder.add_hhea_field(self.key, self.value)
1907*e1fe3e4aSElliott Hughes
1908*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1909*e1fe3e4aSElliott Hughes        fields = ("CaretOffset", "Ascender", "Descender", "LineGap")
1910*e1fe3e4aSElliott Hughes        keywords = dict([(x.lower(), x) for x in fields])
1911*e1fe3e4aSElliott Hughes        return "{} {};".format(keywords[self.key], self.value)
1912*e1fe3e4aSElliott Hughes
1913*e1fe3e4aSElliott Hughes
1914*e1fe3e4aSElliott Hughesclass VheaField(Statement):
1915*e1fe3e4aSElliott Hughes    """An entry in the ``vhea`` table."""
1916*e1fe3e4aSElliott Hughes
1917*e1fe3e4aSElliott Hughes    def __init__(self, key, value, location=None):
1918*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1919*e1fe3e4aSElliott Hughes        self.key = key
1920*e1fe3e4aSElliott Hughes        self.value = value
1921*e1fe3e4aSElliott Hughes
1922*e1fe3e4aSElliott Hughes    def build(self, builder):
1923*e1fe3e4aSElliott Hughes        """Calls the builder object's ``add_vhea_field`` callback."""
1924*e1fe3e4aSElliott Hughes        builder.add_vhea_field(self.key, self.value)
1925*e1fe3e4aSElliott Hughes
1926*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1927*e1fe3e4aSElliott Hughes        fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
1928*e1fe3e4aSElliott Hughes        keywords = dict([(x.lower(), x) for x in fields])
1929*e1fe3e4aSElliott Hughes        return "{} {};".format(keywords[self.key], self.value)
1930*e1fe3e4aSElliott Hughes
1931*e1fe3e4aSElliott Hughes
1932*e1fe3e4aSElliott Hughesclass STATDesignAxisStatement(Statement):
1933*e1fe3e4aSElliott Hughes    """A STAT table Design Axis
1934*e1fe3e4aSElliott Hughes
1935*e1fe3e4aSElliott Hughes    Args:
1936*e1fe3e4aSElliott Hughes        tag (str): a 4 letter axis tag
1937*e1fe3e4aSElliott Hughes        axisOrder (int): an int
1938*e1fe3e4aSElliott Hughes        names (list): a list of :class:`STATNameStatement` objects
1939*e1fe3e4aSElliott Hughes    """
1940*e1fe3e4aSElliott Hughes
1941*e1fe3e4aSElliott Hughes    def __init__(self, tag, axisOrder, names, location=None):
1942*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1943*e1fe3e4aSElliott Hughes        self.tag = tag
1944*e1fe3e4aSElliott Hughes        self.axisOrder = axisOrder
1945*e1fe3e4aSElliott Hughes        self.names = names
1946*e1fe3e4aSElliott Hughes        self.location = location
1947*e1fe3e4aSElliott Hughes
1948*e1fe3e4aSElliott Hughes    def build(self, builder):
1949*e1fe3e4aSElliott Hughes        builder.addDesignAxis(self, self.location)
1950*e1fe3e4aSElliott Hughes
1951*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1952*e1fe3e4aSElliott Hughes        indent += SHIFT
1953*e1fe3e4aSElliott Hughes        res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
1954*e1fe3e4aSElliott Hughes        res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
1955*e1fe3e4aSElliott Hughes        res += "};"
1956*e1fe3e4aSElliott Hughes        return res
1957*e1fe3e4aSElliott Hughes
1958*e1fe3e4aSElliott Hughes
1959*e1fe3e4aSElliott Hughesclass ElidedFallbackName(Statement):
1960*e1fe3e4aSElliott Hughes    """STAT table ElidedFallbackName
1961*e1fe3e4aSElliott Hughes
1962*e1fe3e4aSElliott Hughes    Args:
1963*e1fe3e4aSElliott Hughes        names: a list of :class:`STATNameStatement` objects
1964*e1fe3e4aSElliott Hughes    """
1965*e1fe3e4aSElliott Hughes
1966*e1fe3e4aSElliott Hughes    def __init__(self, names, location=None):
1967*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1968*e1fe3e4aSElliott Hughes        self.names = names
1969*e1fe3e4aSElliott Hughes        self.location = location
1970*e1fe3e4aSElliott Hughes
1971*e1fe3e4aSElliott Hughes    def build(self, builder):
1972*e1fe3e4aSElliott Hughes        builder.setElidedFallbackName(self.names, self.location)
1973*e1fe3e4aSElliott Hughes
1974*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1975*e1fe3e4aSElliott Hughes        indent += SHIFT
1976*e1fe3e4aSElliott Hughes        res = "ElidedFallbackName { \n"
1977*e1fe3e4aSElliott Hughes        res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
1978*e1fe3e4aSElliott Hughes        res += "};"
1979*e1fe3e4aSElliott Hughes        return res
1980*e1fe3e4aSElliott Hughes
1981*e1fe3e4aSElliott Hughes
1982*e1fe3e4aSElliott Hughesclass ElidedFallbackNameID(Statement):
1983*e1fe3e4aSElliott Hughes    """STAT table ElidedFallbackNameID
1984*e1fe3e4aSElliott Hughes
1985*e1fe3e4aSElliott Hughes    Args:
1986*e1fe3e4aSElliott Hughes        value: an int pointing to an existing name table name ID
1987*e1fe3e4aSElliott Hughes    """
1988*e1fe3e4aSElliott Hughes
1989*e1fe3e4aSElliott Hughes    def __init__(self, value, location=None):
1990*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
1991*e1fe3e4aSElliott Hughes        self.value = value
1992*e1fe3e4aSElliott Hughes        self.location = location
1993*e1fe3e4aSElliott Hughes
1994*e1fe3e4aSElliott Hughes    def build(self, builder):
1995*e1fe3e4aSElliott Hughes        builder.setElidedFallbackName(self.value, self.location)
1996*e1fe3e4aSElliott Hughes
1997*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
1998*e1fe3e4aSElliott Hughes        return f"ElidedFallbackNameID {self.value};"
1999*e1fe3e4aSElliott Hughes
2000*e1fe3e4aSElliott Hughes
2001*e1fe3e4aSElliott Hughesclass STATAxisValueStatement(Statement):
2002*e1fe3e4aSElliott Hughes    """A STAT table Axis Value Record
2003*e1fe3e4aSElliott Hughes
2004*e1fe3e4aSElliott Hughes    Args:
2005*e1fe3e4aSElliott Hughes        names (list): a list of :class:`STATNameStatement` objects
2006*e1fe3e4aSElliott Hughes        locations (list): a list of :class:`AxisValueLocationStatement` objects
2007*e1fe3e4aSElliott Hughes        flags (int): an int
2008*e1fe3e4aSElliott Hughes    """
2009*e1fe3e4aSElliott Hughes
2010*e1fe3e4aSElliott Hughes    def __init__(self, names, locations, flags, location=None):
2011*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
2012*e1fe3e4aSElliott Hughes        self.names = names
2013*e1fe3e4aSElliott Hughes        self.locations = locations
2014*e1fe3e4aSElliott Hughes        self.flags = flags
2015*e1fe3e4aSElliott Hughes
2016*e1fe3e4aSElliott Hughes    def build(self, builder):
2017*e1fe3e4aSElliott Hughes        builder.addAxisValueRecord(self, self.location)
2018*e1fe3e4aSElliott Hughes
2019*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
2020*e1fe3e4aSElliott Hughes        res = "AxisValue {\n"
2021*e1fe3e4aSElliott Hughes        for location in self.locations:
2022*e1fe3e4aSElliott Hughes            res += location.asFea()
2023*e1fe3e4aSElliott Hughes
2024*e1fe3e4aSElliott Hughes        for nameRecord in self.names:
2025*e1fe3e4aSElliott Hughes            res += nameRecord.asFea()
2026*e1fe3e4aSElliott Hughes            res += "\n"
2027*e1fe3e4aSElliott Hughes
2028*e1fe3e4aSElliott Hughes        if self.flags:
2029*e1fe3e4aSElliott Hughes            flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
2030*e1fe3e4aSElliott Hughes            flagStrings = []
2031*e1fe3e4aSElliott Hughes            curr = 1
2032*e1fe3e4aSElliott Hughes            for i in range(len(flags)):
2033*e1fe3e4aSElliott Hughes                if self.flags & curr != 0:
2034*e1fe3e4aSElliott Hughes                    flagStrings.append(flags[i])
2035*e1fe3e4aSElliott Hughes                curr = curr << 1
2036*e1fe3e4aSElliott Hughes            res += f"flag {' '.join(flagStrings)};\n"
2037*e1fe3e4aSElliott Hughes        res += "};"
2038*e1fe3e4aSElliott Hughes        return res
2039*e1fe3e4aSElliott Hughes
2040*e1fe3e4aSElliott Hughes
2041*e1fe3e4aSElliott Hughesclass AxisValueLocationStatement(Statement):
2042*e1fe3e4aSElliott Hughes    """
2043*e1fe3e4aSElliott Hughes    A STAT table Axis Value Location
2044*e1fe3e4aSElliott Hughes
2045*e1fe3e4aSElliott Hughes    Args:
2046*e1fe3e4aSElliott Hughes        tag (str): a 4 letter axis tag
2047*e1fe3e4aSElliott Hughes        values (list): a list of ints and/or floats
2048*e1fe3e4aSElliott Hughes    """
2049*e1fe3e4aSElliott Hughes
2050*e1fe3e4aSElliott Hughes    def __init__(self, tag, values, location=None):
2051*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
2052*e1fe3e4aSElliott Hughes        self.tag = tag
2053*e1fe3e4aSElliott Hughes        self.values = values
2054*e1fe3e4aSElliott Hughes
2055*e1fe3e4aSElliott Hughes    def asFea(self, res=""):
2056*e1fe3e4aSElliott Hughes        res += f"location {self.tag} "
2057*e1fe3e4aSElliott Hughes        res += f"{' '.join(str(i) for i in self.values)};\n"
2058*e1fe3e4aSElliott Hughes        return res
2059*e1fe3e4aSElliott Hughes
2060*e1fe3e4aSElliott Hughes
2061*e1fe3e4aSElliott Hughesclass ConditionsetStatement(Statement):
2062*e1fe3e4aSElliott Hughes    """
2063*e1fe3e4aSElliott Hughes    A variable layout conditionset
2064*e1fe3e4aSElliott Hughes
2065*e1fe3e4aSElliott Hughes    Args:
2066*e1fe3e4aSElliott Hughes        name (str): the name of this conditionset
2067*e1fe3e4aSElliott Hughes        conditions (dict): a dictionary mapping axis tags to a
2068*e1fe3e4aSElliott Hughes            tuple of (min,max) userspace coordinates.
2069*e1fe3e4aSElliott Hughes    """
2070*e1fe3e4aSElliott Hughes
2071*e1fe3e4aSElliott Hughes    def __init__(self, name, conditions, location=None):
2072*e1fe3e4aSElliott Hughes        Statement.__init__(self, location)
2073*e1fe3e4aSElliott Hughes        self.name = name
2074*e1fe3e4aSElliott Hughes        self.conditions = conditions
2075*e1fe3e4aSElliott Hughes
2076*e1fe3e4aSElliott Hughes    def build(self, builder):
2077*e1fe3e4aSElliott Hughes        builder.add_conditionset(self.location, self.name, self.conditions)
2078*e1fe3e4aSElliott Hughes
2079*e1fe3e4aSElliott Hughes    def asFea(self, res="", indent=""):
2080*e1fe3e4aSElliott Hughes        res += indent + f"conditionset {self.name} " + "{\n"
2081*e1fe3e4aSElliott Hughes        for tag, (minvalue, maxvalue) in self.conditions.items():
2082*e1fe3e4aSElliott Hughes            res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n"
2083*e1fe3e4aSElliott Hughes        res += indent + "}" + f" {self.name};\n"
2084*e1fe3e4aSElliott Hughes        return res
2085*e1fe3e4aSElliott Hughes
2086*e1fe3e4aSElliott Hughes
2087*e1fe3e4aSElliott Hughesclass VariationBlock(Block):
2088*e1fe3e4aSElliott Hughes    """A variation feature block, applicable in a given set of conditions."""
2089*e1fe3e4aSElliott Hughes
2090*e1fe3e4aSElliott Hughes    def __init__(self, name, conditionset, use_extension=False, location=None):
2091*e1fe3e4aSElliott Hughes        Block.__init__(self, location)
2092*e1fe3e4aSElliott Hughes        self.name, self.conditionset, self.use_extension = (
2093*e1fe3e4aSElliott Hughes            name,
2094*e1fe3e4aSElliott Hughes            conditionset,
2095*e1fe3e4aSElliott Hughes            use_extension,
2096*e1fe3e4aSElliott Hughes        )
2097*e1fe3e4aSElliott Hughes
2098*e1fe3e4aSElliott Hughes    def build(self, builder):
2099*e1fe3e4aSElliott Hughes        """Call the ``start_feature`` callback on the builder object, visit
2100*e1fe3e4aSElliott Hughes        all the statements in this feature, and then call ``end_feature``."""
2101*e1fe3e4aSElliott Hughes        builder.start_feature(self.location, self.name)
2102*e1fe3e4aSElliott Hughes        if (
2103*e1fe3e4aSElliott Hughes            self.conditionset != "NULL"
2104*e1fe3e4aSElliott Hughes            and self.conditionset not in builder.conditionsets_
2105*e1fe3e4aSElliott Hughes        ):
2106*e1fe3e4aSElliott Hughes            raise FeatureLibError(
2107*e1fe3e4aSElliott Hughes                f"variation block used undefined conditionset {self.conditionset}",
2108*e1fe3e4aSElliott Hughes                self.location,
2109*e1fe3e4aSElliott Hughes            )
2110*e1fe3e4aSElliott Hughes
2111*e1fe3e4aSElliott Hughes        # language exclude_dflt statements modify builder.features_
2112*e1fe3e4aSElliott Hughes        # limit them to this block with temporary builder.features_
2113*e1fe3e4aSElliott Hughes        features = builder.features_
2114*e1fe3e4aSElliott Hughes        builder.features_ = {}
2115*e1fe3e4aSElliott Hughes        Block.build(self, builder)
2116*e1fe3e4aSElliott Hughes        for key, value in builder.features_.items():
2117*e1fe3e4aSElliott Hughes            items = builder.feature_variations_.setdefault(key, {}).setdefault(
2118*e1fe3e4aSElliott Hughes                self.conditionset, []
2119*e1fe3e4aSElliott Hughes            )
2120*e1fe3e4aSElliott Hughes            items.extend(value)
2121*e1fe3e4aSElliott Hughes            if key not in features:
2122*e1fe3e4aSElliott Hughes                features[key] = []  # Ensure we make a feature record
2123*e1fe3e4aSElliott Hughes        builder.features_ = features
2124*e1fe3e4aSElliott Hughes        builder.end_feature()
2125*e1fe3e4aSElliott Hughes
2126*e1fe3e4aSElliott Hughes    def asFea(self, indent=""):
2127*e1fe3e4aSElliott Hughes        res = indent + "variation %s " % self.name.strip()
2128*e1fe3e4aSElliott Hughes        res += self.conditionset + " "
2129*e1fe3e4aSElliott Hughes        if self.use_extension:
2130*e1fe3e4aSElliott Hughes            res += "useExtension "
2131*e1fe3e4aSElliott Hughes        res += "{\n"
2132*e1fe3e4aSElliott Hughes        res += Block.asFea(self, indent=indent)
2133*e1fe3e4aSElliott Hughes        res += indent + "} %s;\n" % self.name.strip()
2134*e1fe3e4aSElliott Hughes        return res
2135