• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from __future__ import unicode_literals
3from fontTools.misc.py23 import *
4from fontTools.feaLib.error import FeatureLibError
5from fontTools.misc.encodingTools import getEncoding
6from collections import OrderedDict
7import itertools
8
9SHIFT = " " * 4
10
11__all__ = [
12    'AlternateSubstStatement',
13    'Anchor',
14    'AnchorDefinition',
15    'AnonymousBlock',
16    'AttachStatement',
17    'BaseAxis',
18    'Block',
19    'BytesIO',
20    'CVParametersNameStatement',
21    'ChainContextPosStatement',
22    'ChainContextSubstStatement',
23    'CharacterStatement',
24    'Comment',
25    'CursivePosStatement',
26    'Element',
27    'Expression',
28    'FeatureBlock',
29    'FeatureFile',
30    'FeatureLibError',
31    'FeatureNameStatement',
32    'FeatureReferenceStatement',
33    'FontRevisionStatement',
34    'GlyphClass',
35    'GlyphClassDefStatement',
36    'GlyphClassDefinition',
37    'GlyphClassName',
38    'GlyphName',
39    'HheaField',
40    'IgnorePosStatement',
41    'IgnoreSubstStatement',
42    'IncludeStatement',
43    'LanguageStatement',
44    'LanguageSystemStatement',
45    'LigatureCaretByIndexStatement',
46    'LigatureCaretByPosStatement',
47    'LigatureSubstStatement',
48    'LookupBlock',
49    'LookupFlagStatement',
50    'LookupReferenceStatement',
51    'MarkBasePosStatement',
52    'MarkClass',
53    'MarkClassDefinition',
54    'MarkClassName',
55    'MarkLigPosStatement',
56    'MarkMarkPosStatement',
57    'MultipleSubstStatement',
58    'NameRecord',
59    'NestedBlock',
60    'OS2Field',
61    'OrderedDict',
62    'PairPosStatement',
63    'Py23Error',
64    'ReverseChainSingleSubstStatement',
65    'ScriptStatement',
66    'SimpleNamespace',
67    'SinglePosStatement',
68    'SingleSubstStatement',
69    'SizeParameters',
70    'Statement',
71    'StringIO',
72    'SubtableStatement',
73    'TableBlock',
74    'Tag',
75    'UnicodeIO',
76    'ValueRecord',
77    'ValueRecordDefinition',
78    'VheaField',
79]
80
81
82def deviceToString(device):
83    if device is None:
84        return "<device NULL>"
85    else:
86        return "<device %s>" % ", ".join("%d %d" % t for t in device)
87
88
89fea_keywords = set([
90    "anchor", "anchordef", "anon", "anonymous",
91    "by",
92    "contour", "cursive",
93    "device",
94    "enum", "enumerate", "excludedflt", "exclude_dflt",
95    "feature", "from",
96    "ignore", "ignorebaseglyphs", "ignoreligatures", "ignoremarks",
97    "include", "includedflt", "include_dflt",
98    "language", "languagesystem", "lookup", "lookupflag",
99    "mark", "markattachmenttype", "markclass",
100    "nameid", "null",
101    "parameters", "pos", "position",
102    "required", "righttoleft", "reversesub", "rsub",
103    "script", "sub", "substitute", "subtable",
104    "table",
105    "usemarkfilteringset", "useextension", "valuerecorddef",
106    "base", "gdef", "head", "hhea", "name", "vhea", "vmtx"]
107)
108
109
110def asFea(g):
111    if hasattr(g, 'asFea'):
112        return g.asFea()
113    elif isinstance(g, tuple) and len(g) == 2:
114        return asFea(g[0]) + "-" + asFea(g[1])   # a range
115    elif g.lower() in fea_keywords:
116        return "\\" + g
117    else:
118        return g
119
120
121class Element(object):
122
123    def __init__(self, location=None):
124        self.location = location
125
126    def build(self, builder):
127        pass
128
129    def asFea(self, indent=""):
130        raise NotImplementedError
131
132    def __str__(self):
133        return self.asFea()
134
135
136class Statement(Element):
137    pass
138
139
140class Expression(Element):
141    pass
142
143
144class Comment(Element):
145    def __init__(self, text, location=None):
146        super(Comment, self).__init__(location)
147        self.text = text
148
149    def asFea(self, indent=""):
150        return self.text
151
152
153class GlyphName(Expression):
154    """A single glyph name, such as cedilla."""
155    def __init__(self, glyph, location=None):
156        Expression.__init__(self, location)
157        self.glyph = glyph
158
159    def glyphSet(self):
160        return (self.glyph,)
161
162    def asFea(self, indent=""):
163        return asFea(self.glyph)
164
165
166class GlyphClass(Expression):
167    """A glyph class, such as [acute cedilla grave]."""
168    def __init__(self, glyphs=None, location=None):
169        Expression.__init__(self, location)
170        self.glyphs = glyphs if glyphs is not None else []
171        self.original = []
172        self.curr = 0
173
174    def glyphSet(self):
175        return tuple(self.glyphs)
176
177    def asFea(self, indent=""):
178        if len(self.original):
179            if self.curr < len(self.glyphs):
180                self.original.extend(self.glyphs[self.curr:])
181                self.curr = len(self.glyphs)
182            return "[" + " ".join(map(asFea, self.original)) + "]"
183        else:
184            return "[" + " ".join(map(asFea, self.glyphs)) + "]"
185
186    def extend(self, glyphs):
187        self.glyphs.extend(glyphs)
188
189    def append(self, glyph):
190        self.glyphs.append(glyph)
191
192    def add_range(self, start, end, glyphs):
193        if self.curr < len(self.glyphs):
194            self.original.extend(self.glyphs[self.curr:])
195        self.original.append((start, end))
196        self.glyphs.extend(glyphs)
197        self.curr = len(self.glyphs)
198
199    def add_cid_range(self, start, end, glyphs):
200        if self.curr < len(self.glyphs):
201            self.original.extend(self.glyphs[self.curr:])
202        self.original.append(("cid{:05d}".format(start), "cid{:05d}".format(end)))
203        self.glyphs.extend(glyphs)
204        self.curr = len(self.glyphs)
205
206    def add_class(self, gc):
207        if self.curr < len(self.glyphs):
208            self.original.extend(self.glyphs[self.curr:])
209        self.original.append(gc)
210        self.glyphs.extend(gc.glyphSet())
211        self.curr = len(self.glyphs)
212
213
214class GlyphClassName(Expression):
215    """A glyph class name, such as @FRENCH_MARKS."""
216    def __init__(self, glyphclass, location=None):
217        Expression.__init__(self, location)
218        assert isinstance(glyphclass, GlyphClassDefinition)
219        self.glyphclass = glyphclass
220
221    def glyphSet(self):
222        return tuple(self.glyphclass.glyphSet())
223
224    def asFea(self, indent=""):
225        return "@" + self.glyphclass.name
226
227
228class MarkClassName(Expression):
229    """A mark class name, such as @FRENCH_MARKS defined with markClass."""
230    def __init__(self, markClass, location=None):
231        Expression.__init__(self, location)
232        assert isinstance(markClass, MarkClass)
233        self.markClass = markClass
234
235    def glyphSet(self):
236        return self.markClass.glyphSet()
237
238    def asFea(self, indent=""):
239        return "@" + self.markClass.name
240
241
242class AnonymousBlock(Statement):
243    def __init__(self, tag, content, location=None):
244        Statement.__init__(self, location)
245        self.tag, self.content = tag, content
246
247    def asFea(self, indent=""):
248        res = "anon {} {{\n".format(self.tag)
249        res += self.content
250        res += "}} {};\n\n".format(self.tag)
251        return res
252
253
254class Block(Statement):
255    def __init__(self, location=None):
256        Statement.__init__(self, location)
257        self.statements = []
258
259    def build(self, builder):
260        for s in self.statements:
261            s.build(builder)
262
263    def asFea(self, indent=""):
264        indent += SHIFT
265        return indent + ("\n" + indent).join(
266            [s.asFea(indent=indent) for s in self.statements]) + "\n"
267
268
269class FeatureFile(Block):
270    def __init__(self):
271        Block.__init__(self, location=None)
272        self.markClasses = {}  # name --> ast.MarkClass
273
274    def asFea(self, indent=""):
275        return "\n".join(s.asFea(indent=indent) for s in self.statements)
276
277
278class FeatureBlock(Block):
279    def __init__(self, name, use_extension=False, location=None):
280        Block.__init__(self, location)
281        self.name, self.use_extension = name, use_extension
282
283    def build(self, builder):
284        # TODO(sascha): Handle use_extension.
285        builder.start_feature(self.location, self.name)
286        # language exclude_dflt statements modify builder.features_
287        # limit them to this block with temporary builder.features_
288        features = builder.features_
289        builder.features_ = {}
290        Block.build(self, builder)
291        for key, value in builder.features_.items():
292            features.setdefault(key, []).extend(value)
293        builder.features_ = features
294        builder.end_feature()
295
296    def asFea(self, indent=""):
297        res = indent + "feature %s " % self.name.strip()
298        if self.use_extension:
299            res += "useExtension "
300        res += "{\n"
301        res += Block.asFea(self, indent=indent)
302        res += indent + "} %s;\n" % self.name.strip()
303        return res
304
305
306class NestedBlock(Block):
307    def __init__(self, tag, block_name, location=None):
308        Block.__init__(self, location)
309        self.tag = tag
310        self.block_name = block_name
311
312    def build(self, builder):
313        Block.build(self, builder)
314        if self.block_name == "ParamUILabelNameID":
315            builder.add_to_cv_num_named_params(self.tag)
316
317    def asFea(self, indent=""):
318        res = "{}{} {{\n".format(indent, self.block_name)
319        res += Block.asFea(self, indent=indent)
320        res += "{}}};\n".format(indent)
321        return res
322
323
324class LookupBlock(Block):
325    def __init__(self, name, use_extension=False, location=None):
326        Block.__init__(self, location)
327        self.name, self.use_extension = name, use_extension
328
329    def build(self, builder):
330        # TODO(sascha): Handle use_extension.
331        builder.start_lookup_block(self.location, self.name)
332        Block.build(self, builder)
333        builder.end_lookup_block()
334
335    def asFea(self, indent=""):
336        res = "lookup {} ".format(self.name)
337        if self.use_extension:
338            res += "useExtension "
339        res += "{\n"
340        res += Block.asFea(self, indent=indent)
341        res += "{}}} {};\n".format(indent, self.name)
342        return res
343
344
345class TableBlock(Block):
346    def __init__(self, name, location=None):
347        Block.__init__(self, location)
348        self.name = name
349
350    def asFea(self, indent=""):
351        res = "table {} {{\n".format(self.name.strip())
352        res += super(TableBlock, self).asFea(indent=indent)
353        res += "}} {};\n".format(self.name.strip())
354        return res
355
356
357class GlyphClassDefinition(Statement):
358    """Example: @UPPERCASE = [A-Z];"""
359    def __init__(self, name, glyphs, location=None):
360        Statement.__init__(self, location)
361        self.name = name
362        self.glyphs = glyphs
363
364    def glyphSet(self):
365        return tuple(self.glyphs.glyphSet())
366
367    def asFea(self, indent=""):
368        return "@" + self.name + " = " + self.glyphs.asFea() + ";"
369
370
371class GlyphClassDefStatement(Statement):
372    """Example: GlyphClassDef @UPPERCASE, [B], [C], [D];"""
373    def __init__(self, baseGlyphs, markGlyphs, ligatureGlyphs,
374                 componentGlyphs, location=None):
375        Statement.__init__(self, location)
376        self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
377        self.ligatureGlyphs = ligatureGlyphs
378        self.componentGlyphs = componentGlyphs
379
380    def build(self, builder):
381        base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
382        liga = self.ligatureGlyphs.glyphSet() \
383            if self.ligatureGlyphs else tuple()
384        mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
385        comp = (self.componentGlyphs.glyphSet()
386                if self.componentGlyphs else tuple())
387        builder.add_glyphClassDef(self.location, base, liga, mark, comp)
388
389    def asFea(self, indent=""):
390        return "GlyphClassDef {}, {}, {}, {};".format(
391            self.baseGlyphs.asFea() if self.baseGlyphs else "",
392            self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
393            self.markGlyphs.asFea() if self.markGlyphs else "",
394            self.componentGlyphs.asFea() if self.componentGlyphs else "")
395
396
397# While glyph classes can be defined only once, the feature file format
398# allows expanding mark classes with multiple definitions, each using
399# different glyphs and anchors. The following are two MarkClassDefinitions
400# for the same MarkClass:
401#     markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
402#     markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
403class MarkClass(object):
404    def __init__(self, name):
405        self.name = name
406        self.definitions = []
407        self.glyphs = OrderedDict()  # glyph --> ast.MarkClassDefinitions
408
409    def addDefinition(self, definition):
410        assert isinstance(definition, MarkClassDefinition)
411        self.definitions.append(definition)
412        for glyph in definition.glyphSet():
413            if glyph in self.glyphs:
414                otherLoc = self.glyphs[glyph].location
415                if otherLoc is None:
416                    end = ""
417                else:
418                    end = " at %s:%d:%d" % (
419                        otherLoc[0], otherLoc[1], otherLoc[2])
420                raise FeatureLibError(
421                    "Glyph %s already defined%s" % (glyph, end),
422                    definition.location)
423            self.glyphs[glyph] = definition
424
425    def glyphSet(self):
426        return tuple(self.glyphs.keys())
427
428    def asFea(self, indent=""):
429        res = "\n".join(d.asFea() for d in self.definitions)
430        return res
431
432
433class MarkClassDefinition(Statement):
434    def __init__(self, markClass, anchor, glyphs, location=None):
435        Statement.__init__(self, location)
436        assert isinstance(markClass, MarkClass)
437        assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
438        self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
439
440    def glyphSet(self):
441        return self.glyphs.glyphSet()
442
443    def asFea(self, indent=""):
444        return "markClass {} {} @{};".format(
445            self.glyphs.asFea(), self.anchor.asFea(),
446            self.markClass.name)
447
448
449class AlternateSubstStatement(Statement):
450    def __init__(self, prefix, glyph, suffix, replacement, location=None):
451        Statement.__init__(self, location)
452        self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
453        self.replacement = replacement
454
455    def build(self, builder):
456        glyph = self.glyph.glyphSet()
457        assert len(glyph) == 1, glyph
458        glyph = list(glyph)[0]
459        prefix = [p.glyphSet() for p in self.prefix]
460        suffix = [s.glyphSet() for s in self.suffix]
461        replacement = self.replacement.glyphSet()
462        builder.add_alternate_subst(self.location, prefix, glyph, suffix,
463                                    replacement)
464
465    def asFea(self, indent=""):
466        res = "sub "
467        if len(self.prefix) or len(self.suffix):
468            if len(self.prefix):
469                res += " ".join(map(asFea, self.prefix)) + " "
470            res += asFea(self.glyph) + "'"    # even though we really only use 1
471            if len(self.suffix):
472                res += " " + " ".join(map(asFea, self.suffix))
473        else:
474            res += asFea(self.glyph)
475        res += " from "
476        res += asFea(self.replacement)
477        res += ";"
478        return res
479
480
481class Anchor(Expression):
482    def __init__(self, x, y, name=None, contourpoint=None,
483                 xDeviceTable=None, yDeviceTable=None, location=None):
484        Expression.__init__(self, location)
485        self.name = name
486        self.x, self.y, self.contourpoint = x, y, contourpoint
487        self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable
488
489    def asFea(self, indent=""):
490        if self.name is not None:
491            return "<anchor {}>".format(self.name)
492        res = "<anchor {} {}".format(self.x, self.y)
493        if self.contourpoint:
494            res += " contourpoint {}".format(self.contourpoint)
495        if self.xDeviceTable or self.yDeviceTable:
496            res += " "
497            res += deviceToString(self.xDeviceTable)
498            res += " "
499            res += deviceToString(self.yDeviceTable)
500        res += ">"
501        return res
502
503
504class AnchorDefinition(Statement):
505    def __init__(self, name, x, y, contourpoint=None, location=None):
506        Statement.__init__(self, location)
507        self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
508
509    def asFea(self, indent=""):
510        res = "anchorDef {} {}".format(self.x, self.y)
511        if self.contourpoint:
512            res += " contourpoint {}".format(self.contourpoint)
513        res += " {};".format(self.name)
514        return res
515
516
517class AttachStatement(Statement):
518    def __init__(self, glyphs, contourPoints, location=None):
519        Statement.__init__(self, location)
520        self.glyphs, self.contourPoints = (glyphs, contourPoints)
521
522    def build(self, builder):
523        glyphs = self.glyphs.glyphSet()
524        builder.add_attach_points(self.location, glyphs, self.contourPoints)
525
526    def asFea(self, indent=""):
527        return "Attach {} {};".format(
528            self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints))
529
530
531class ChainContextPosStatement(Statement):
532    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
533        Statement.__init__(self, location)
534        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
535        self.lookups = lookups
536
537    def build(self, builder):
538        prefix = [p.glyphSet() for p in self.prefix]
539        glyphs = [g.glyphSet() for g in self.glyphs]
540        suffix = [s.glyphSet() for s in self.suffix]
541        builder.add_chain_context_pos(
542            self.location, prefix, glyphs, suffix, self.lookups)
543
544    def asFea(self, indent=""):
545        res = "pos "
546        if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]):
547            if len(self.prefix):
548                res += " ".join(g.asFea() for g in self.prefix) + " "
549            for i, g in enumerate(self.glyphs):
550                res += g.asFea() + "'"
551                if self.lookups[i] is not None:
552                    res += " lookup " + self.lookups[i].name
553                if i < len(self.glyphs) - 1:
554                    res += " "
555            if len(self.suffix):
556                res += " " + " ".join(map(asFea, self.suffix))
557        else:
558            res += " ".join(map(asFea, self.glyph))
559        res += ";"
560        return res
561
562
563class ChainContextSubstStatement(Statement):
564    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
565        Statement.__init__(self, location)
566        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
567        self.lookups = lookups
568
569    def build(self, builder):
570        prefix = [p.glyphSet() for p in self.prefix]
571        glyphs = [g.glyphSet() for g in self.glyphs]
572        suffix = [s.glyphSet() for s in self.suffix]
573        builder.add_chain_context_subst(
574            self.location, prefix, glyphs, suffix, self.lookups)
575
576    def asFea(self, indent=""):
577        res = "sub "
578        if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]):
579            if len(self.prefix):
580                res += " ".join(g.asFea() for g in self.prefix) + " "
581            for i, g in enumerate(self.glyphs):
582                res += g.asFea() + "'"
583                if self.lookups[i] is not None:
584                    res += " lookup " + self.lookups[i].name
585                if i < len(self.glyphs) - 1:
586                    res += " "
587            if len(self.suffix):
588                res += " " + " ".join(map(asFea, self.suffix))
589        else:
590            res += " ".join(map(asFea, self.glyph))
591        res += ";"
592        return res
593
594
595class CursivePosStatement(Statement):
596    def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
597        Statement.__init__(self, location)
598        self.glyphclass = glyphclass
599        self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
600
601    def build(self, builder):
602        builder.add_cursive_pos(
603            self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor)
604
605    def asFea(self, indent=""):
606        entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
607        exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
608        return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)
609
610
611class FeatureReferenceStatement(Statement):
612    """Example: feature salt;"""
613    def __init__(self, featureName, location=None):
614        Statement.__init__(self, location)
615        self.location, self.featureName = (location, featureName)
616
617    def build(self, builder):
618        builder.add_feature_reference(self.location, self.featureName)
619
620    def asFea(self, indent=""):
621        return "feature {};".format(self.featureName)
622
623
624class IgnorePosStatement(Statement):
625    def __init__(self, chainContexts, location=None):
626        Statement.__init__(self, location)
627        self.chainContexts = chainContexts
628
629    def build(self, builder):
630        for prefix, glyphs, suffix in self.chainContexts:
631            prefix = [p.glyphSet() for p in prefix]
632            glyphs = [g.glyphSet() for g in glyphs]
633            suffix = [s.glyphSet() for s in suffix]
634            builder.add_chain_context_pos(
635                self.location, prefix, glyphs, suffix, [])
636
637    def asFea(self, indent=""):
638        contexts = []
639        for prefix, glyphs, suffix in self.chainContexts:
640            res = ""
641            if len(prefix) or len(suffix):
642                if len(prefix):
643                    res += " ".join(map(asFea, prefix)) + " "
644                res += " ".join(g.asFea() + "'" for g in glyphs)
645                if len(suffix):
646                    res += " " + " ".join(map(asFea, suffix))
647            else:
648                res += " ".join(map(asFea, glyphs))
649            contexts.append(res)
650        return "ignore pos " + ", ".join(contexts) + ";"
651
652
653class IgnoreSubstStatement(Statement):
654    def __init__(self, chainContexts, location=None):
655        Statement.__init__(self, location)
656        self.chainContexts = chainContexts
657
658    def build(self, builder):
659        for prefix, glyphs, suffix in self.chainContexts:
660            prefix = [p.glyphSet() for p in prefix]
661            glyphs = [g.glyphSet() for g in glyphs]
662            suffix = [s.glyphSet() for s in suffix]
663            builder.add_chain_context_subst(
664                self.location, prefix, glyphs, suffix, [])
665
666    def asFea(self, indent=""):
667        contexts = []
668        for prefix, glyphs, suffix in self.chainContexts:
669            res = ""
670            if len(prefix) or len(suffix):
671                if len(prefix):
672                    res += " ".join(map(asFea, prefix)) + " "
673                res += " ".join(g.asFea() + "'" for g in glyphs)
674                if len(suffix):
675                    res += " " + " ".join(map(asFea, suffix))
676            else:
677                res += " ".join(map(asFea, glyphs))
678            contexts.append(res)
679        return "ignore sub " + ", ".join(contexts) + ";"
680
681
682class IncludeStatement(Statement):
683    def __init__(self, filename, location=None):
684        super(IncludeStatement, self).__init__(location)
685        self.filename = filename
686
687    def build(self):
688        # TODO: consider lazy-loading the including parser/lexer?
689        raise FeatureLibError(
690            "Building an include statement is not implemented yet. "
691            "Instead, use Parser(..., followIncludes=True) for building.",
692            self.location)
693
694    def asFea(self, indent=""):
695        return indent + "include(%s);" % self.filename
696
697
698class LanguageStatement(Statement):
699    def __init__(self, language, include_default=True, required=False,
700                 location=None):
701        Statement.__init__(self, location)
702        assert(len(language) == 4)
703        self.language = language
704        self.include_default = include_default
705        self.required = required
706
707    def build(self, builder):
708        builder.set_language(location=self.location, language=self.language,
709                             include_default=self.include_default,
710                             required=self.required)
711
712    def asFea(self, indent=""):
713        res = "language {}".format(self.language.strip())
714        if not self.include_default:
715            res += " exclude_dflt"
716        if self.required:
717            res += " required"
718        res += ";"
719        return res
720
721
722class LanguageSystemStatement(Statement):
723    def __init__(self, script, language, location=None):
724        Statement.__init__(self, location)
725        self.script, self.language = (script, language)
726
727    def build(self, builder):
728        builder.add_language_system(self.location, self.script, self.language)
729
730    def asFea(self, indent=""):
731        return "languagesystem {} {};".format(self.script, self.language.strip())
732
733
734class FontRevisionStatement(Statement):
735    def __init__(self, revision, location=None):
736        Statement.__init__(self, location)
737        self.revision = revision
738
739    def build(self, builder):
740        builder.set_font_revision(self.location, self.revision)
741
742    def asFea(self, indent=""):
743        return "FontRevision {:.3f};".format(self.revision)
744
745
746class LigatureCaretByIndexStatement(Statement):
747    def __init__(self, glyphs, carets, location=None):
748        Statement.__init__(self, location)
749        self.glyphs, self.carets = (glyphs, carets)
750
751    def build(self, builder):
752        glyphs = self.glyphs.glyphSet()
753        builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
754
755    def asFea(self, indent=""):
756        return "LigatureCaretByIndex {} {};".format(
757            self.glyphs.asFea(), " ".join(str(x) for x in self.carets))
758
759
760class LigatureCaretByPosStatement(Statement):
761    def __init__(self, glyphs, carets, location=None):
762        Statement.__init__(self, location)
763        self.glyphs, self.carets = (glyphs, carets)
764
765    def build(self, builder):
766        glyphs = self.glyphs.glyphSet()
767        builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
768
769    def asFea(self, indent=""):
770        return "LigatureCaretByPos {} {};".format(
771            self.glyphs.asFea(), " ".join(str(x) for x in self.carets))
772
773
774class LigatureSubstStatement(Statement):
775    def __init__(self, prefix, glyphs, suffix, replacement,
776                 forceChain, location=None):
777        Statement.__init__(self, location)
778        self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
779        self.replacement, self.forceChain = replacement, forceChain
780
781    def build(self, builder):
782        prefix = [p.glyphSet() for p in self.prefix]
783        glyphs = [g.glyphSet() for g in self.glyphs]
784        suffix = [s.glyphSet() for s in self.suffix]
785        builder.add_ligature_subst(
786            self.location, prefix, glyphs, suffix, self.replacement,
787            self.forceChain)
788
789    def asFea(self, indent=""):
790        res = "sub "
791        if len(self.prefix) or len(self.suffix) or self.forceChain:
792            if len(self.prefix):
793                res += " ".join(g.asFea() for g in self.prefix) + " "
794            res += " ".join(g.asFea() + "'" for g in self.glyphs)
795            if len(self.suffix):
796                res += " " + " ".join(g.asFea() for g in self.suffix)
797        else:
798            res += " ".join(g.asFea() for g in self.glyphs)
799        res += " by "
800        res += asFea(self.replacement)
801        res += ";"
802        return res
803
804
805class LookupFlagStatement(Statement):
806    def __init__(self, value=0, markAttachment=None, markFilteringSet=None,
807                 location=None):
808        Statement.__init__(self, location)
809        self.value = value
810        self.markAttachment = markAttachment
811        self.markFilteringSet = markFilteringSet
812
813    def build(self, builder):
814        markAttach = None
815        if self.markAttachment is not None:
816            markAttach = self.markAttachment.glyphSet()
817        markFilter = None
818        if self.markFilteringSet is not None:
819            markFilter = self.markFilteringSet.glyphSet()
820        builder.set_lookup_flag(self.location, self.value,
821                                markAttach, markFilter)
822
823    def asFea(self, indent=""):
824        res = []
825        flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
826        curr = 1
827        for i in range(len(flags)):
828            if self.value & curr != 0:
829                res.append(flags[i])
830            curr = curr << 1
831        if self.markAttachment is not None:
832            res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
833        if self.markFilteringSet is not None:
834            res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
835        if not res:
836            res = ["0"]
837        return "lookupflag {};".format(" ".join(res))
838
839
840class LookupReferenceStatement(Statement):
841    def __init__(self, lookup, location=None):
842        Statement.__init__(self, location)
843        self.location, self.lookup = (location, lookup)
844
845    def build(self, builder):
846        builder.add_lookup_call(self.lookup.name)
847
848    def asFea(self, indent=""):
849        return "lookup {};".format(self.lookup.name)
850
851
852class MarkBasePosStatement(Statement):
853    def __init__(self, base, marks, location=None):
854        Statement.__init__(self, location)
855        self.base, self.marks = base, marks
856
857    def build(self, builder):
858        builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
859
860    def asFea(self, indent=""):
861        res = "pos base {}".format(self.base.asFea())
862        for a, m in self.marks:
863            res += " {} mark @{}".format(a.asFea(), m.name)
864        res += ";"
865        return res
866
867
868class MarkLigPosStatement(Statement):
869    def __init__(self, ligatures, marks, location=None):
870        Statement.__init__(self, location)
871        self.ligatures, self.marks = ligatures, marks
872
873    def build(self, builder):
874        builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
875
876    def asFea(self, indent=""):
877        res = "pos ligature {}".format(self.ligatures.asFea())
878        ligs = []
879        for l in self.marks:
880            temp = ""
881            if l is None or not len(l):
882                temp = " <anchor NULL>"
883            else:
884                for a, m in l:
885                    temp += " {} mark @{}".format(a.asFea(), m.name)
886            ligs.append(temp)
887        res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
888        res += ";"
889        return res
890
891
892class MarkMarkPosStatement(Statement):
893    def __init__(self, baseMarks, marks, location=None):
894        Statement.__init__(self, location)
895        self.baseMarks, self.marks = baseMarks, marks
896
897    def build(self, builder):
898        builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
899
900    def asFea(self, indent=""):
901        res = "pos mark {}".format(self.baseMarks.asFea())
902        for a, m in self.marks:
903            res += " {} mark @{}".format(a.asFea(), m.name)
904        res += ";"
905        return res
906
907
908class MultipleSubstStatement(Statement):
909    def __init__(
910        self, prefix, glyph, suffix, replacement, forceChain=False, location=None
911    ):
912        Statement.__init__(self, location)
913        self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
914        self.replacement = replacement
915        self.forceChain = forceChain
916
917    def build(self, builder):
918        prefix = [p.glyphSet() for p in self.prefix]
919        suffix = [s.glyphSet() for s in self.suffix]
920        builder.add_multiple_subst(
921            self.location, prefix, self.glyph, suffix, self.replacement,
922            self.forceChain)
923
924    def asFea(self, indent=""):
925        res = "sub "
926        if len(self.prefix) or len(self.suffix) or self.forceChain:
927            if len(self.prefix):
928                res += " ".join(map(asFea, self.prefix)) + " "
929            res += asFea(self.glyph) + "'"
930            if len(self.suffix):
931                res += " " + " ".join(map(asFea, self.suffix))
932        else:
933            res += asFea(self.glyph)
934        res += " by "
935        res += " ".join(map(asFea, self.replacement))
936        res += ";"
937        return res
938
939
940class PairPosStatement(Statement):
941    def __init__(self, glyphs1, valuerecord1, glyphs2, valuerecord2,
942                 enumerated=False, location=None):
943        Statement.__init__(self, location)
944        self.enumerated = enumerated
945        self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
946        self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
947
948    def build(self, builder):
949        if self.enumerated:
950            g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
951            for glyph1, glyph2 in itertools.product(*g):
952                builder.add_specific_pair_pos(
953                    self.location, glyph1, self.valuerecord1,
954                    glyph2, self.valuerecord2)
955            return
956
957        is_specific = (isinstance(self.glyphs1, GlyphName) and
958                       isinstance(self.glyphs2, GlyphName))
959        if is_specific:
960            builder.add_specific_pair_pos(
961                self.location, self.glyphs1.glyph, self.valuerecord1,
962                self.glyphs2.glyph, self.valuerecord2)
963        else:
964            builder.add_class_pair_pos(
965                self.location, self.glyphs1.glyphSet(), self.valuerecord1,
966                self.glyphs2.glyphSet(), self.valuerecord2)
967
968    def asFea(self, indent=""):
969        res = "enum " if self.enumerated else ""
970        if self.valuerecord2:
971            res += "pos {} {} {} {};".format(
972                self.glyphs1.asFea(), self.valuerecord1.asFea(),
973                self.glyphs2.asFea(), self.valuerecord2.asFea())
974        else:
975            res += "pos {} {} {};".format(
976                self.glyphs1.asFea(), self.glyphs2.asFea(),
977                self.valuerecord1.asFea())
978        return res
979
980
981class ReverseChainSingleSubstStatement(Statement):
982    def __init__(self, old_prefix, old_suffix, glyphs, replacements,
983                 location=None):
984        Statement.__init__(self, location)
985        self.old_prefix, self.old_suffix = old_prefix, old_suffix
986        self.glyphs = glyphs
987        self.replacements = replacements
988
989    def build(self, builder):
990        prefix = [p.glyphSet() for p in self.old_prefix]
991        suffix = [s.glyphSet() for s in self.old_suffix]
992        originals = self.glyphs[0].glyphSet()
993        replaces = self.replacements[0].glyphSet()
994        if len(replaces) == 1:
995            replaces = replaces * len(originals)
996        builder.add_reverse_chain_single_subst(
997            self.location, prefix, suffix, dict(zip(originals, replaces)))
998
999    def asFea(self, indent=""):
1000        res = "rsub "
1001        if len(self.old_prefix) or len(self.old_suffix):
1002            if len(self.old_prefix):
1003                res += " ".join(asFea(g) for g in self.old_prefix) + " "
1004            res += " ".join(asFea(g) + "'" for g in self.glyphs)
1005            if len(self.old_suffix):
1006                res += " " + " ".join(asFea(g) for g in self.old_suffix)
1007        else:
1008            res += " ".join(map(asFea, self.glyphs))
1009        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1010        return res
1011
1012
1013class SingleSubstStatement(Statement):
1014    def __init__(self, glyphs, replace, prefix, suffix, forceChain,
1015                 location=None):
1016        Statement.__init__(self, location)
1017        self.prefix, self.suffix = prefix, suffix
1018        self.forceChain = forceChain
1019        self.glyphs = glyphs
1020        self.replacements = replace
1021
1022    def build(self, builder):
1023        prefix = [p.glyphSet() for p in self.prefix]
1024        suffix = [s.glyphSet() for s in self.suffix]
1025        originals = self.glyphs[0].glyphSet()
1026        replaces = self.replacements[0].glyphSet()
1027        if len(replaces) == 1:
1028            replaces = replaces * len(originals)
1029        builder.add_single_subst(self.location, prefix, suffix,
1030                                 OrderedDict(zip(originals, replaces)),
1031                                 self.forceChain)
1032
1033    def asFea(self, indent=""):
1034        res = "sub "
1035        if len(self.prefix) or len(self.suffix) or self.forceChain:
1036            if len(self.prefix):
1037                res += " ".join(asFea(g) for g in self.prefix) + " "
1038            res += " ".join(asFea(g) + "'" for g in self.glyphs)
1039            if len(self.suffix):
1040                res += " " + " ".join(asFea(g) for g in self.suffix)
1041        else:
1042            res += " ".join(asFea(g) for g in self.glyphs)
1043        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1044        return res
1045
1046
1047class ScriptStatement(Statement):
1048    def __init__(self, script, location=None):
1049        Statement.__init__(self, location)
1050        self.script = script
1051
1052    def build(self, builder):
1053        builder.set_script(self.location, self.script)
1054
1055    def asFea(self, indent=""):
1056        return "script {};".format(self.script.strip())
1057
1058
1059class SinglePosStatement(Statement):
1060    def __init__(self, pos, prefix, suffix, forceChain, location=None):
1061        Statement.__init__(self, location)
1062        self.pos, self.prefix, self.suffix = pos, prefix, suffix
1063        self.forceChain = forceChain
1064
1065    def build(self, builder):
1066        prefix = [p.glyphSet() for p in self.prefix]
1067        suffix = [s.glyphSet() for s in self.suffix]
1068        pos = [(g.glyphSet(), value) for g, value in self.pos]
1069        builder.add_single_pos(self.location, prefix, suffix,
1070                               pos, self.forceChain)
1071
1072    def asFea(self, indent=""):
1073        res = "pos "
1074        if len(self.prefix) or len(self.suffix) or self.forceChain:
1075            if len(self.prefix):
1076                res += " ".join(map(asFea, self.prefix)) + " "
1077            res += " ".join([asFea(x[0]) + "'" + (
1078                (" " + x[1].asFea()) if x[1] else "") for x in self.pos])
1079            if len(self.suffix):
1080                res += " " + " ".join(map(asFea, self.suffix))
1081        else:
1082            res += " ".join([asFea(x[0]) + " " +
1083                             (x[1].asFea() if x[1] else "") for x in self.pos])
1084        res += ";"
1085        return res
1086
1087
1088class SubtableStatement(Statement):
1089    def __init__(self, location=None):
1090        Statement.__init__(self, location)
1091
1092    def build(self, builder):
1093        builder.add_subtable_break(self.location)
1094
1095    def asFea(self, indent=""):
1096        return "subtable;"
1097
1098
1099class ValueRecord(Expression):
1100    def __init__(self, xPlacement=None, yPlacement=None,
1101                 xAdvance=None, yAdvance=None,
1102                 xPlaDevice=None, yPlaDevice=None,
1103                 xAdvDevice=None, yAdvDevice=None,
1104                 vertical=False, location=None):
1105        Expression.__init__(self, location)
1106        self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
1107        self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
1108        self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice)
1109        self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice)
1110        self.vertical = vertical
1111
1112    def __eq__(self, other):
1113        return (self.xPlacement == other.xPlacement and
1114                self.yPlacement == other.yPlacement and
1115                self.xAdvance == other.xAdvance and
1116                self.yAdvance == other.yAdvance and
1117                self.xPlaDevice == other.xPlaDevice and
1118                self.xAdvDevice == other.xAdvDevice)
1119
1120    def __ne__(self, other):
1121        return not self.__eq__(other)
1122
1123    def __hash__(self):
1124        return (hash(self.xPlacement) ^ hash(self.yPlacement) ^
1125                hash(self.xAdvance) ^ hash(self.yAdvance) ^
1126                hash(self.xPlaDevice) ^ hash(self.yPlaDevice) ^
1127                hash(self.xAdvDevice) ^ hash(self.yAdvDevice))
1128
1129    def asFea(self, indent=""):
1130        if not self:
1131            return "<NULL>"
1132
1133        x, y = self.xPlacement, self.yPlacement
1134        xAdvance, yAdvance = self.xAdvance, self.yAdvance
1135        xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice
1136        xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice
1137        vertical = self.vertical
1138
1139        # Try format A, if possible.
1140        if x is None and y is None:
1141            if xAdvance is None and vertical:
1142                return str(yAdvance)
1143            elif yAdvance is None and not vertical:
1144                return str(xAdvance)
1145
1146        # Try format B, if possible.
1147        if (xPlaDevice is None and yPlaDevice is None and
1148                xAdvDevice is None and yAdvDevice is None):
1149            return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
1150
1151        # Last resort is format C.
1152        return "<%s %s %s %s %s %s %s %s>" % (
1153            x, y, xAdvance, yAdvance,
1154            deviceToString(xPlaDevice), deviceToString(yPlaDevice),
1155            deviceToString(xAdvDevice), deviceToString(yAdvDevice))
1156
1157    def __bool__(self):
1158        return any(
1159            getattr(self, v) is not None
1160            for v in [
1161                "xPlacement",
1162                "yPlacement",
1163                "xAdvance",
1164                "yAdvance",
1165                "xPlaDevice",
1166                "yPlaDevice",
1167                "xAdvDevice",
1168                "yAdvDevice",
1169            ]
1170        )
1171
1172    __nonzero__ = __bool__
1173
1174
1175class ValueRecordDefinition(Statement):
1176    def __init__(self, name, value, location=None):
1177        Statement.__init__(self, location)
1178        self.name = name
1179        self.value = value
1180
1181    def asFea(self, indent=""):
1182        return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
1183
1184
1185def simplify_name_attributes(pid, eid, lid):
1186    if pid == 3 and eid == 1 and lid == 1033:
1187        return ""
1188    elif pid == 1 and eid == 0 and lid == 0:
1189        return "1"
1190    else:
1191        return "{} {} {}".format(pid, eid, lid)
1192
1193
1194class NameRecord(Statement):
1195    def __init__(self, nameID, platformID, platEncID, langID, string,
1196                 location=None):
1197        Statement.__init__(self, location)
1198        self.nameID = nameID
1199        self.platformID = platformID
1200        self.platEncID = platEncID
1201        self.langID = langID
1202        self.string = string
1203
1204    def build(self, builder):
1205        builder.add_name_record(
1206            self.location, self.nameID, self.platformID,
1207            self.platEncID, self.langID, self.string)
1208
1209    def asFea(self, indent=""):
1210        def escape(c, escape_pattern):
1211            # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
1212            if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
1213                return unichr(c)
1214            else:
1215                return escape_pattern % c
1216        encoding = getEncoding(self.platformID, self.platEncID, self.langID)
1217        if encoding is None:
1218            raise FeatureLibError("Unsupported encoding", self.location)
1219        s = tobytes(self.string, encoding=encoding)
1220        if encoding == "utf_16_be":
1221            escaped_string = "".join([
1222                escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
1223                for i in range(0, len(s), 2)])
1224        else:
1225            escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
1226        plat = simplify_name_attributes(
1227            self.platformID, self.platEncID, self.langID)
1228        if plat != "":
1229            plat += " "
1230        return "nameid {} {}\"{}\";".format(self.nameID, plat, escaped_string)
1231
1232
1233class FeatureNameStatement(NameRecord):
1234    def build(self, builder):
1235        NameRecord.build(self, builder)
1236        builder.add_featureName(self.nameID)
1237
1238    def asFea(self, indent=""):
1239        if self.nameID == "size":
1240            tag = "sizemenuname"
1241        else:
1242            tag = "name"
1243        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1244        if plat != "":
1245            plat += " "
1246        return "{} {}\"{}\";".format(tag, plat, self.string)
1247
1248
1249class SizeParameters(Statement):
1250    def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd,
1251                 location=None):
1252        Statement.__init__(self, location)
1253        self.DesignSize = DesignSize
1254        self.SubfamilyID = SubfamilyID
1255        self.RangeStart = RangeStart
1256        self.RangeEnd = RangeEnd
1257
1258    def build(self, builder):
1259        builder.set_size_parameters(self.location, self.DesignSize,
1260                                    self.SubfamilyID, self.RangeStart, self.RangeEnd)
1261
1262    def asFea(self, indent=""):
1263        res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
1264        if self.RangeStart != 0 or self.RangeEnd != 0:
1265            res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10))
1266        return res + ";"
1267
1268
1269class CVParametersNameStatement(NameRecord):
1270    def __init__(self, nameID, platformID, platEncID, langID, string,
1271                 block_name, location=None):
1272        NameRecord.__init__(self, nameID, platformID, platEncID, langID,
1273                            string, location=location)
1274        self.block_name = block_name
1275
1276    def build(self, builder):
1277        item = ""
1278        if self.block_name == "ParamUILabelNameID":
1279            item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
1280        builder.add_cv_parameter(self.nameID)
1281        self.nameID = (self.nameID, self.block_name + item)
1282        NameRecord.build(self, builder)
1283
1284    def asFea(self, indent=""):
1285        plat = simplify_name_attributes(self.platformID, self.platEncID,
1286                                        self.langID)
1287        if plat != "":
1288            plat += " "
1289        return "name {}\"{}\";".format(plat, self.string)
1290
1291
1292class CharacterStatement(Statement):
1293    """
1294    Statement used in cvParameters blocks of Character Variant features (cvXX).
1295    The Unicode value may be written with either decimal or hexadecimal
1296    notation. The value must be preceded by '0x' if it is a hexadecimal value.
1297    The largest Unicode value allowed is 0xFFFFFF.
1298    """
1299    def __init__(self, character, tag, location=None):
1300        Statement.__init__(self, location)
1301        self.character = character
1302        self.tag = tag
1303
1304    def build(self, builder):
1305        builder.add_cv_character(self.character, self.tag)
1306
1307    def asFea(self, indent=""):
1308        return "Character {:#x};".format(self.character)
1309
1310
1311class BaseAxis(Statement):
1312    def __init__(self, bases, scripts, vertical, location=None):
1313        Statement.__init__(self, location)
1314        self.bases = bases
1315        self.scripts = scripts
1316        self.vertical = vertical
1317
1318    def build(self, builder):
1319        builder.set_base_axis(self.bases, self.scripts, self.vertical)
1320
1321    def asFea(self, indent=""):
1322        direction = "Vert" if self.vertical else "Horiz"
1323        scripts = ["{} {} {}".format(a[0], a[1], " ".join(map(str, a[2]))) for a in self.scripts]
1324        return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
1325            direction, " ".join(self.bases), indent, direction, ", ".join(scripts))
1326
1327
1328class OS2Field(Statement):
1329    def __init__(self, key, value, location=None):
1330        Statement.__init__(self, location)
1331        self.key = key
1332        self.value = value
1333
1334    def build(self, builder):
1335        builder.add_os2_field(self.key, self.value)
1336
1337    def asFea(self, indent=""):
1338        def intarr2str(x):
1339            return " ".join(map(str, x))
1340        numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap",
1341                   "winAscent", "winDescent", "XHeight", "CapHeight",
1342                   "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize")
1343        ranges = ("UnicodeRange", "CodePageRange")
1344        keywords = dict([(x.lower(), [x, str]) for x in numbers])
1345        keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
1346        keywords["panose"] = ["Panose", intarr2str]
1347        keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
1348        if self.key in keywords:
1349            return "{} {};".format(keywords[self.key][0], keywords[self.key][1](self.value))
1350        return ""   # should raise exception
1351
1352
1353class HheaField(Statement):
1354    def __init__(self, key, value, location=None):
1355        Statement.__init__(self, location)
1356        self.key = key
1357        self.value = value
1358
1359    def build(self, builder):
1360        builder.add_hhea_field(self.key, self.value)
1361
1362    def asFea(self, indent=""):
1363        fields = ("CaretOffset", "Ascender", "Descender", "LineGap")
1364        keywords = dict([(x.lower(), x) for x in fields])
1365        return "{} {};".format(keywords[self.key], self.value)
1366
1367
1368class VheaField(Statement):
1369    def __init__(self, key, value, location=None):
1370        Statement.__init__(self, location)
1371        self.key = key
1372        self.value = value
1373
1374    def build(self, builder):
1375        builder.add_vhea_field(self.key, self.value)
1376
1377    def asFea(self, indent=""):
1378        fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
1379        keywords = dict([(x.lower(), x) for x in fields])
1380        return "{} {};".format(keywords[self.key], self.value)
1381