You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			2144 lines
		
	
	
		
			72 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			2144 lines
		
	
	
		
			72 KiB
		
	
	
	
		
			Python
		
	
import weakref
 | 
						|
from fontTools.feaLib.error import FeatureLibError
 | 
						|
from fontTools.feaLib.location import FeatureLibLocation
 | 
						|
from fontTools.misc.encodingTools import getEncoding
 | 
						|
from fontTools.misc.textTools import byteord, tobytes
 | 
						|
from collections import OrderedDict
 | 
						|
import itertools
 | 
						|
 | 
						|
SHIFT = " " * 4
 | 
						|
 | 
						|
__all__ = [
 | 
						|
    "Element",
 | 
						|
    "FeatureFile",
 | 
						|
    "Comment",
 | 
						|
    "GlyphName",
 | 
						|
    "GlyphClass",
 | 
						|
    "GlyphClassName",
 | 
						|
    "MarkClassName",
 | 
						|
    "AnonymousBlock",
 | 
						|
    "Block",
 | 
						|
    "FeatureBlock",
 | 
						|
    "NestedBlock",
 | 
						|
    "LookupBlock",
 | 
						|
    "GlyphClassDefinition",
 | 
						|
    "GlyphClassDefStatement",
 | 
						|
    "MarkClass",
 | 
						|
    "MarkClassDefinition",
 | 
						|
    "AlternateSubstStatement",
 | 
						|
    "Anchor",
 | 
						|
    "AnchorDefinition",
 | 
						|
    "AttachStatement",
 | 
						|
    "AxisValueLocationStatement",
 | 
						|
    "BaseAxis",
 | 
						|
    "CVParametersNameStatement",
 | 
						|
    "ChainContextPosStatement",
 | 
						|
    "ChainContextSubstStatement",
 | 
						|
    "CharacterStatement",
 | 
						|
    "ConditionsetStatement",
 | 
						|
    "CursivePosStatement",
 | 
						|
    "ElidedFallbackName",
 | 
						|
    "ElidedFallbackNameID",
 | 
						|
    "Expression",
 | 
						|
    "FeatureNameStatement",
 | 
						|
    "FeatureReferenceStatement",
 | 
						|
    "FontRevisionStatement",
 | 
						|
    "HheaField",
 | 
						|
    "IgnorePosStatement",
 | 
						|
    "IgnoreSubstStatement",
 | 
						|
    "IncludeStatement",
 | 
						|
    "LanguageStatement",
 | 
						|
    "LanguageSystemStatement",
 | 
						|
    "LigatureCaretByIndexStatement",
 | 
						|
    "LigatureCaretByPosStatement",
 | 
						|
    "LigatureSubstStatement",
 | 
						|
    "LookupFlagStatement",
 | 
						|
    "LookupReferenceStatement",
 | 
						|
    "MarkBasePosStatement",
 | 
						|
    "MarkLigPosStatement",
 | 
						|
    "MarkMarkPosStatement",
 | 
						|
    "MultipleSubstStatement",
 | 
						|
    "NameRecord",
 | 
						|
    "OS2Field",
 | 
						|
    "PairPosStatement",
 | 
						|
    "ReverseChainSingleSubstStatement",
 | 
						|
    "ScriptStatement",
 | 
						|
    "SinglePosStatement",
 | 
						|
    "SingleSubstStatement",
 | 
						|
    "SizeParameters",
 | 
						|
    "Statement",
 | 
						|
    "STATAxisValueStatement",
 | 
						|
    "STATDesignAxisStatement",
 | 
						|
    "STATNameStatement",
 | 
						|
    "SubtableStatement",
 | 
						|
    "TableBlock",
 | 
						|
    "ValueRecord",
 | 
						|
    "ValueRecordDefinition",
 | 
						|
    "VheaField",
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
def deviceToString(device):
 | 
						|
    if device is None:
 | 
						|
        return "<device NULL>"
 | 
						|
    else:
 | 
						|
        return "<device %s>" % ", ".join("%d %d" % t for t in device)
 | 
						|
 | 
						|
 | 
						|
fea_keywords = set(
 | 
						|
    [
 | 
						|
        "anchor",
 | 
						|
        "anchordef",
 | 
						|
        "anon",
 | 
						|
        "anonymous",
 | 
						|
        "by",
 | 
						|
        "contour",
 | 
						|
        "cursive",
 | 
						|
        "device",
 | 
						|
        "enum",
 | 
						|
        "enumerate",
 | 
						|
        "excludedflt",
 | 
						|
        "exclude_dflt",
 | 
						|
        "feature",
 | 
						|
        "from",
 | 
						|
        "ignore",
 | 
						|
        "ignorebaseglyphs",
 | 
						|
        "ignoreligatures",
 | 
						|
        "ignoremarks",
 | 
						|
        "include",
 | 
						|
        "includedflt",
 | 
						|
        "include_dflt",
 | 
						|
        "language",
 | 
						|
        "languagesystem",
 | 
						|
        "lookup",
 | 
						|
        "lookupflag",
 | 
						|
        "mark",
 | 
						|
        "markattachmenttype",
 | 
						|
        "markclass",
 | 
						|
        "nameid",
 | 
						|
        "null",
 | 
						|
        "parameters",
 | 
						|
        "pos",
 | 
						|
        "position",
 | 
						|
        "required",
 | 
						|
        "righttoleft",
 | 
						|
        "reversesub",
 | 
						|
        "rsub",
 | 
						|
        "script",
 | 
						|
        "sub",
 | 
						|
        "substitute",
 | 
						|
        "subtable",
 | 
						|
        "table",
 | 
						|
        "usemarkfilteringset",
 | 
						|
        "useextension",
 | 
						|
        "valuerecorddef",
 | 
						|
        "base",
 | 
						|
        "gdef",
 | 
						|
        "head",
 | 
						|
        "hhea",
 | 
						|
        "name",
 | 
						|
        "vhea",
 | 
						|
        "vmtx",
 | 
						|
    ]
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
def asFea(g):
 | 
						|
    if hasattr(g, "asFea"):
 | 
						|
        return g.asFea()
 | 
						|
    elif isinstance(g, tuple) and len(g) == 2:
 | 
						|
        return asFea(g[0]) + " - " + asFea(g[1])  # a range
 | 
						|
    elif g.lower() in fea_keywords:
 | 
						|
        return "\\" + g
 | 
						|
    else:
 | 
						|
        return g
 | 
						|
 | 
						|
 | 
						|
class Element(object):
 | 
						|
    """A base class representing "something" in a feature file."""
 | 
						|
 | 
						|
    def __init__(self, location=None):
 | 
						|
        #: location of this element as a `FeatureLibLocation` object.
 | 
						|
        if location and not isinstance(location, FeatureLibLocation):
 | 
						|
            location = FeatureLibLocation(*location)
 | 
						|
        self.location = location
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        pass
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        """Returns this element as a string of feature code. For block-type
 | 
						|
        elements (such as :class:`FeatureBlock`), the `indent` string is
 | 
						|
        added to the start of each line in the output."""
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return self.asFea()
 | 
						|
 | 
						|
 | 
						|
class Statement(Element):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class Expression(Element):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class Comment(Element):
 | 
						|
    """A comment in a feature file."""
 | 
						|
 | 
						|
    def __init__(self, text, location=None):
 | 
						|
        super(Comment, self).__init__(location)
 | 
						|
        #: Text of the comment
 | 
						|
        self.text = text
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return self.text
 | 
						|
 | 
						|
 | 
						|
class NullGlyph(Expression):
 | 
						|
    """The NULL glyph, used in glyph deletion substitutions."""
 | 
						|
 | 
						|
    def __init__(self, location=None):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        #: The name itself as a string
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return ()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "NULL"
 | 
						|
 | 
						|
 | 
						|
class GlyphName(Expression):
 | 
						|
    """A single glyph name, such as ``cedilla``."""
 | 
						|
 | 
						|
    def __init__(self, glyph, location=None):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        #: The name itself as a string
 | 
						|
        self.glyph = glyph
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return (self.glyph,)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return asFea(self.glyph)
 | 
						|
 | 
						|
 | 
						|
class GlyphClass(Expression):
 | 
						|
    """A glyph class, such as ``[acute cedilla grave]``."""
 | 
						|
 | 
						|
    def __init__(self, glyphs=None, location=None):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        #: The list of glyphs in this class, as :class:`GlyphName` objects.
 | 
						|
        self.glyphs = glyphs if glyphs is not None else []
 | 
						|
        self.original = []
 | 
						|
        self.curr = 0
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return tuple(self.glyphs)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        if len(self.original):
 | 
						|
            if self.curr < len(self.glyphs):
 | 
						|
                self.original.extend(self.glyphs[self.curr :])
 | 
						|
                self.curr = len(self.glyphs)
 | 
						|
            return "[" + " ".join(map(asFea, self.original)) + "]"
 | 
						|
        else:
 | 
						|
            return "[" + " ".join(map(asFea, self.glyphs)) + "]"
 | 
						|
 | 
						|
    def extend(self, glyphs):
 | 
						|
        """Add a list of :class:`GlyphName` objects to the class."""
 | 
						|
        self.glyphs.extend(glyphs)
 | 
						|
 | 
						|
    def append(self, glyph):
 | 
						|
        """Add a single :class:`GlyphName` object to the class."""
 | 
						|
        self.glyphs.append(glyph)
 | 
						|
 | 
						|
    def add_range(self, start, end, glyphs):
 | 
						|
        """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
 | 
						|
        are either :class:`GlyphName` objects or strings representing the
 | 
						|
        start and end glyphs in the class, and ``glyphs`` is the full list of
 | 
						|
        :class:`GlyphName` objects in the range."""
 | 
						|
        if self.curr < len(self.glyphs):
 | 
						|
            self.original.extend(self.glyphs[self.curr :])
 | 
						|
        self.original.append((start, end))
 | 
						|
        self.glyphs.extend(glyphs)
 | 
						|
        self.curr = len(self.glyphs)
 | 
						|
 | 
						|
    def add_cid_range(self, start, end, glyphs):
 | 
						|
        """Add a range to the class by glyph ID. ``start`` and ``end`` are the
 | 
						|
        initial and final IDs, and ``glyphs`` is the full list of
 | 
						|
        :class:`GlyphName` objects in the range."""
 | 
						|
        if self.curr < len(self.glyphs):
 | 
						|
            self.original.extend(self.glyphs[self.curr :])
 | 
						|
        self.original.append(("\\{}".format(start), "\\{}".format(end)))
 | 
						|
        self.glyphs.extend(glyphs)
 | 
						|
        self.curr = len(self.glyphs)
 | 
						|
 | 
						|
    def add_class(self, gc):
 | 
						|
        """Add glyphs from the given :class:`GlyphClassName` object to the
 | 
						|
        class."""
 | 
						|
        if self.curr < len(self.glyphs):
 | 
						|
            self.original.extend(self.glyphs[self.curr :])
 | 
						|
        self.original.append(gc)
 | 
						|
        self.glyphs.extend(gc.glyphSet())
 | 
						|
        self.curr = len(self.glyphs)
 | 
						|
 | 
						|
 | 
						|
class GlyphClassName(Expression):
 | 
						|
    """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
 | 
						|
    with a :class:`GlyphClassDefinition` object."""
 | 
						|
 | 
						|
    def __init__(self, glyphclass, location=None):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        assert isinstance(glyphclass, GlyphClassDefinition)
 | 
						|
        self.glyphclass = glyphclass
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return tuple(self.glyphclass.glyphSet())
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "@" + self.glyphclass.name
 | 
						|
 | 
						|
 | 
						|
class MarkClassName(Expression):
 | 
						|
    """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
 | 
						|
    This must be instantiated with a :class:`MarkClass` object."""
 | 
						|
 | 
						|
    def __init__(self, markClass, location=None):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        assert isinstance(markClass, MarkClass)
 | 
						|
        self.markClass = markClass
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return self.markClass.glyphSet()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "@" + self.markClass.name
 | 
						|
 | 
						|
 | 
						|
class AnonymousBlock(Statement):
 | 
						|
    """An anonymous data block."""
 | 
						|
 | 
						|
    def __init__(self, tag, content, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.tag = tag  #: string containing the block's "tag"
 | 
						|
        self.content = content  #: block data as string
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "anon {} {{\n".format(self.tag)
 | 
						|
        res += self.content
 | 
						|
        res += "}} {};\n\n".format(self.tag)
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class Block(Statement):
 | 
						|
    """A block of statements: feature, lookup, etc."""
 | 
						|
 | 
						|
    def __init__(self, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.statements = []  #: Statements contained in the block
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """When handed a 'builder' object of comparable interface to
 | 
						|
        :class:`fontTools.feaLib.builder`, walks the statements in this
 | 
						|
        block, calling the builder callbacks."""
 | 
						|
        for s in self.statements:
 | 
						|
            s.build(builder)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        indent += SHIFT
 | 
						|
        return (
 | 
						|
            indent
 | 
						|
            + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
 | 
						|
            + "\n"
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class FeatureFile(Block):
 | 
						|
    """The top-level element of the syntax tree, containing the whole feature
 | 
						|
    file in its ``statements`` attribute."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        Block.__init__(self, location=None)
 | 
						|
        self.markClasses = {}  # name --> ast.MarkClass
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "\n".join(s.asFea(indent=indent) for s in self.statements)
 | 
						|
 | 
						|
 | 
						|
class FeatureBlock(Block):
 | 
						|
    """A named feature block."""
 | 
						|
 | 
						|
    def __init__(self, name, use_extension=False, location=None):
 | 
						|
        Block.__init__(self, location)
 | 
						|
        self.name, self.use_extension = name, use_extension
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Call the ``start_feature`` callback on the builder object, visit
 | 
						|
        all the statements in this feature, and then call ``end_feature``."""
 | 
						|
        builder.start_feature(self.location, self.name, self.use_extension)
 | 
						|
        # language exclude_dflt statements modify builder.features_
 | 
						|
        # limit them to this block with temporary builder.features_
 | 
						|
        features = builder.features_
 | 
						|
        builder.features_ = {}
 | 
						|
        Block.build(self, builder)
 | 
						|
        for key, value in builder.features_.items():
 | 
						|
            features.setdefault(key, []).extend(value)
 | 
						|
        builder.features_ = features
 | 
						|
        builder.end_feature()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = indent + "feature %s " % self.name.strip()
 | 
						|
        if self.use_extension:
 | 
						|
            res += "useExtension "
 | 
						|
        res += "{\n"
 | 
						|
        res += Block.asFea(self, indent=indent)
 | 
						|
        res += indent + "} %s;\n" % self.name.strip()
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class NestedBlock(Block):
 | 
						|
    """A block inside another block, for example when found inside a
 | 
						|
    ``cvParameters`` block."""
 | 
						|
 | 
						|
    def __init__(self, tag, block_name, location=None):
 | 
						|
        Block.__init__(self, location)
 | 
						|
        self.tag = tag
 | 
						|
        self.block_name = block_name
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        Block.build(self, builder)
 | 
						|
        if self.block_name == "ParamUILabelNameID":
 | 
						|
            builder.add_to_cv_num_named_params(self.tag)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "{}{} {{\n".format(indent, self.block_name)
 | 
						|
        res += Block.asFea(self, indent=indent)
 | 
						|
        res += "{}}};\n".format(indent)
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class LookupBlock(Block):
 | 
						|
    """A named lookup, containing ``statements``."""
 | 
						|
 | 
						|
    def __init__(self, name, use_extension=False, location=None):
 | 
						|
        Block.__init__(self, location)
 | 
						|
        self.name, self.use_extension = name, use_extension
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.start_lookup_block(self.location, self.name, self.use_extension)
 | 
						|
        Block.build(self, builder)
 | 
						|
        builder.end_lookup_block()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "lookup {} ".format(self.name)
 | 
						|
        if self.use_extension:
 | 
						|
            res += "useExtension "
 | 
						|
        res += "{\n"
 | 
						|
        res += Block.asFea(self, indent=indent)
 | 
						|
        res += "{}}} {};\n".format(indent, self.name)
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class TableBlock(Block):
 | 
						|
    """A ``table ... { }`` block."""
 | 
						|
 | 
						|
    def __init__(self, name, location=None):
 | 
						|
        Block.__init__(self, location)
 | 
						|
        self.name = name
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "table {} {{\n".format(self.name.strip())
 | 
						|
        res += super(TableBlock, self).asFea(indent=indent)
 | 
						|
        res += "}} {};\n".format(self.name.strip())
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class GlyphClassDefinition(Statement):
 | 
						|
    """Example: ``@UPPERCASE = [A-Z];``."""
 | 
						|
 | 
						|
    def __init__(self, name, glyphs, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.name = name  #: class name as a string, without initial ``@``
 | 
						|
        self.glyphs = glyphs  #: a :class:`GlyphClass` object
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return tuple(self.glyphs.glyphSet())
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "@" + self.name + " = " + self.glyphs.asFea() + ";"
 | 
						|
 | 
						|
 | 
						|
class GlyphClassDefStatement(Statement):
 | 
						|
    """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
 | 
						|
    must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
 | 
						|
    ``None``."""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
 | 
						|
    ):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
 | 
						|
        self.ligatureGlyphs = ligatureGlyphs
 | 
						|
        self.componentGlyphs = componentGlyphs
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``add_glyphClassDef`` callback."""
 | 
						|
        base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
 | 
						|
        liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
 | 
						|
        mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
 | 
						|
        comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
 | 
						|
        builder.add_glyphClassDef(self.location, base, liga, mark, comp)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "GlyphClassDef {}, {}, {}, {};".format(
 | 
						|
            self.baseGlyphs.asFea() if self.baseGlyphs else "",
 | 
						|
            self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
 | 
						|
            self.markGlyphs.asFea() if self.markGlyphs else "",
 | 
						|
            self.componentGlyphs.asFea() if self.componentGlyphs else "",
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class MarkClass(object):
 | 
						|
    """One `or more` ``markClass`` statements for the same mark class.
 | 
						|
 | 
						|
    While glyph classes can be defined only once, the feature file format
 | 
						|
    allows expanding mark classes with multiple definitions, each using
 | 
						|
    different glyphs and anchors. The following are two ``MarkClassDefinitions``
 | 
						|
    for the same ``MarkClass``::
 | 
						|
 | 
						|
        markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
 | 
						|
        markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
 | 
						|
 | 
						|
    The ``MarkClass`` object is therefore just a container for a list of
 | 
						|
    :class:`MarkClassDefinition` statements.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, name):
 | 
						|
        self.name = name
 | 
						|
        self.definitions = []
 | 
						|
        self.glyphs = OrderedDict()  # glyph --> ast.MarkClassDefinitions
 | 
						|
 | 
						|
    def addDefinition(self, definition):
 | 
						|
        """Add a :class:`MarkClassDefinition` statement to this mark class."""
 | 
						|
        assert isinstance(definition, MarkClassDefinition)
 | 
						|
        self.definitions.append(weakref.proxy(definition))
 | 
						|
        for glyph in definition.glyphSet():
 | 
						|
            if glyph in self.glyphs:
 | 
						|
                otherLoc = self.glyphs[glyph].location
 | 
						|
                if otherLoc is None:
 | 
						|
                    end = ""
 | 
						|
                else:
 | 
						|
                    end = f" at {otherLoc}"
 | 
						|
                raise FeatureLibError(
 | 
						|
                    "Glyph %s already defined%s" % (glyph, end), definition.location
 | 
						|
                )
 | 
						|
            self.glyphs[glyph] = definition
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return tuple(self.glyphs.keys())
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "\n".join(d.asFea() for d in self.definitions)
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class MarkClassDefinition(Statement):
 | 
						|
    """A single ``markClass`` statement. The ``markClass`` should be a
 | 
						|
    :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
 | 
						|
    and the ``glyphs`` parameter should be a `glyph-containing object`_ .
 | 
						|
 | 
						|
    Example:
 | 
						|
 | 
						|
        .. code:: python
 | 
						|
 | 
						|
            mc = MarkClass("FRENCH_ACCENTS")
 | 
						|
            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
 | 
						|
                GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
 | 
						|
            ) )
 | 
						|
            mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
 | 
						|
                GlyphClass([ GlyphName("cedilla") ])
 | 
						|
            ) )
 | 
						|
 | 
						|
            mc.asFea()
 | 
						|
            # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
 | 
						|
            # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, markClass, anchor, glyphs, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        assert isinstance(markClass, MarkClass)
 | 
						|
        assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
 | 
						|
        self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
 | 
						|
 | 
						|
    def glyphSet(self):
 | 
						|
        """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
 | 
						|
        return self.glyphs.glyphSet()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "markClass {} {} @{};".format(
 | 
						|
            self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class AlternateSubstStatement(Statement):
 | 
						|
    """A ``sub ... from ...`` statement.
 | 
						|
 | 
						|
    ``glyph`` and ``replacement`` should be `glyph-containing objects`_.
 | 
						|
    ``prefix`` and ``suffix`` should be lists of `glyph-containing objects`_."""
 | 
						|
 | 
						|
    def __init__(self, prefix, glyph, suffix, replacement, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
 | 
						|
        self.replacement = replacement
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``add_alternate_subst`` callback."""
 | 
						|
        glyph = self.glyph.glyphSet()
 | 
						|
        assert len(glyph) == 1, glyph
 | 
						|
        glyph = list(glyph)[0]
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        replacement = self.replacement.glyphSet()
 | 
						|
        builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "sub "
 | 
						|
        if len(self.prefix) or len(self.suffix):
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(map(asFea, self.prefix)) + " "
 | 
						|
            res += asFea(self.glyph) + "'"  # even though we really only use 1
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(map(asFea, self.suffix))
 | 
						|
        else:
 | 
						|
            res += asFea(self.glyph)
 | 
						|
        res += " from "
 | 
						|
        res += asFea(self.replacement)
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class Anchor(Expression):
 | 
						|
    """An ``Anchor`` element, used inside a ``pos`` rule.
 | 
						|
 | 
						|
    If a ``name`` is given, this will be used in preference to the coordinates.
 | 
						|
    Other values should be integer.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        x,
 | 
						|
        y,
 | 
						|
        name=None,
 | 
						|
        contourpoint=None,
 | 
						|
        xDeviceTable=None,
 | 
						|
        yDeviceTable=None,
 | 
						|
        location=None,
 | 
						|
    ):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        self.name = name
 | 
						|
        self.x, self.y, self.contourpoint = x, y, contourpoint
 | 
						|
        self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        if self.name is not None:
 | 
						|
            return "<anchor {}>".format(self.name)
 | 
						|
        res = "<anchor {} {}".format(self.x, self.y)
 | 
						|
        if self.contourpoint:
 | 
						|
            res += " contourpoint {}".format(self.contourpoint)
 | 
						|
        if self.xDeviceTable or self.yDeviceTable:
 | 
						|
            res += " "
 | 
						|
            res += deviceToString(self.xDeviceTable)
 | 
						|
            res += " "
 | 
						|
            res += deviceToString(self.yDeviceTable)
 | 
						|
        res += ">"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class AnchorDefinition(Statement):
 | 
						|
    """A named anchor definition. (2.e.viii). ``name`` should be a string."""
 | 
						|
 | 
						|
    def __init__(self, name, x, y, contourpoint=None, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "anchorDef {} {}".format(self.x, self.y)
 | 
						|
        if self.contourpoint:
 | 
						|
            res += " contourpoint {}".format(self.contourpoint)
 | 
						|
        res += " {};".format(self.name)
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class AttachStatement(Statement):
 | 
						|
    """A ``GDEF`` table ``Attach`` statement."""
 | 
						|
 | 
						|
    def __init__(self, glyphs, contourPoints, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.glyphs = glyphs  #: A `glyph-containing object`_
 | 
						|
        self.contourPoints = contourPoints  #: A list of integer contour points
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``add_attach_points`` callback."""
 | 
						|
        glyphs = self.glyphs.glyphSet()
 | 
						|
        builder.add_attach_points(self.location, glyphs, self.contourPoints)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "Attach {} {};".format(
 | 
						|
            self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class ChainContextPosStatement(Statement):
 | 
						|
    r"""A chained contextual positioning statement.
 | 
						|
 | 
						|
    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
 | 
						|
    `glyph-containing objects`_ .
 | 
						|
 | 
						|
    ``lookups`` should be a list of elements representing what lookups
 | 
						|
    to apply at each glyph position. Each element should be a
 | 
						|
    :class:`LookupBlock` to apply a single chaining lookup at the given
 | 
						|
    position, a list of :class:`LookupBlock`\ s to apply multiple
 | 
						|
    lookups, or ``None`` to apply no lookup. The length of the outer
 | 
						|
    list should equal the length of ``glyphs``; the inner lists can be
 | 
						|
    of variable length."""
 | 
						|
 | 
						|
    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
 | 
						|
        self.lookups = list(lookups)
 | 
						|
        for i, lookup in enumerate(lookups):
 | 
						|
            if lookup:
 | 
						|
                try:
 | 
						|
                    iter(lookup)
 | 
						|
                except TypeError:
 | 
						|
                    self.lookups[i] = [lookup]
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``add_chain_context_pos`` callback."""
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        glyphs = [g.glyphSet() for g in self.glyphs]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        builder.add_chain_context_pos(
 | 
						|
            self.location, prefix, glyphs, suffix, self.lookups
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "pos "
 | 
						|
        if (
 | 
						|
            len(self.prefix)
 | 
						|
            or len(self.suffix)
 | 
						|
            or any([x is not None for x in self.lookups])
 | 
						|
        ):
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(g.asFea() for g in self.prefix) + " "
 | 
						|
            for i, g in enumerate(self.glyphs):
 | 
						|
                res += g.asFea() + "'"
 | 
						|
                if self.lookups[i]:
 | 
						|
                    for lu in self.lookups[i]:
 | 
						|
                        res += " lookup " + lu.name
 | 
						|
                if i < len(self.glyphs) - 1:
 | 
						|
                    res += " "
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(map(asFea, self.suffix))
 | 
						|
        else:
 | 
						|
            res += " ".join(map(asFea, self.glyphs))
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ChainContextSubstStatement(Statement):
 | 
						|
    r"""A chained contextual substitution statement.
 | 
						|
 | 
						|
    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
 | 
						|
    `glyph-containing objects`_ .
 | 
						|
 | 
						|
    ``lookups`` should be a list of elements representing what lookups
 | 
						|
    to apply at each glyph position. Each element should be a
 | 
						|
    :class:`LookupBlock` to apply a single chaining lookup at the given
 | 
						|
    position, a list of :class:`LookupBlock`\ s to apply multiple
 | 
						|
    lookups, or ``None`` to apply no lookup. The length of the outer
 | 
						|
    list should equal the length of ``glyphs``; the inner lists can be
 | 
						|
    of variable length."""
 | 
						|
 | 
						|
    def __init__(self, prefix, glyphs, suffix, lookups, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
 | 
						|
        self.lookups = list(lookups)
 | 
						|
        for i, lookup in enumerate(lookups):
 | 
						|
            if lookup:
 | 
						|
                try:
 | 
						|
                    iter(lookup)
 | 
						|
                except TypeError:
 | 
						|
                    self.lookups[i] = [lookup]
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``add_chain_context_subst`` callback."""
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        glyphs = [g.glyphSet() for g in self.glyphs]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        builder.add_chain_context_subst(
 | 
						|
            self.location, prefix, glyphs, suffix, self.lookups
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "sub "
 | 
						|
        if (
 | 
						|
            len(self.prefix)
 | 
						|
            or len(self.suffix)
 | 
						|
            or any([x is not None for x in self.lookups])
 | 
						|
        ):
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(g.asFea() for g in self.prefix) + " "
 | 
						|
            for i, g in enumerate(self.glyphs):
 | 
						|
                res += g.asFea() + "'"
 | 
						|
                if self.lookups[i]:
 | 
						|
                    for lu in self.lookups[i]:
 | 
						|
                        res += " lookup " + lu.name
 | 
						|
                if i < len(self.glyphs) - 1:
 | 
						|
                    res += " "
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(map(asFea, self.suffix))
 | 
						|
        else:
 | 
						|
            res += " ".join(map(asFea, self.glyphs))
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class CursivePosStatement(Statement):
 | 
						|
    """A cursive positioning statement. Entry and exit anchors can either
 | 
						|
    be :class:`Anchor` objects or ``None``."""
 | 
						|
 | 
						|
    def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.glyphclass = glyphclass
 | 
						|
        self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_cursive_pos`` callback."""
 | 
						|
        builder.add_cursive_pos(
 | 
						|
            self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
 | 
						|
        exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
 | 
						|
        return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)
 | 
						|
 | 
						|
 | 
						|
class FeatureReferenceStatement(Statement):
 | 
						|
    """Example: ``feature salt;``"""
 | 
						|
 | 
						|
    def __init__(self, featureName, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.location, self.featureName = (location, featureName)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_feature_reference`` callback."""
 | 
						|
        builder.add_feature_reference(self.location, self.featureName)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "feature {};".format(self.featureName)
 | 
						|
 | 
						|
 | 
						|
class IgnorePosStatement(Statement):
 | 
						|
    """An ``ignore pos`` statement, containing `one or more` contexts to ignore.
 | 
						|
 | 
						|
    ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
 | 
						|
    with each of ``prefix``, ``glyphs`` and ``suffix`` being
 | 
						|
    `glyph-containing objects`_ ."""
 | 
						|
 | 
						|
    def __init__(self, chainContexts, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.chainContexts = chainContexts
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_chain_context_pos`` callback on each
 | 
						|
        rule context."""
 | 
						|
        for prefix, glyphs, suffix in self.chainContexts:
 | 
						|
            prefix = [p.glyphSet() for p in prefix]
 | 
						|
            glyphs = [g.glyphSet() for g in glyphs]
 | 
						|
            suffix = [s.glyphSet() for s in suffix]
 | 
						|
            builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        contexts = []
 | 
						|
        for prefix, glyphs, suffix in self.chainContexts:
 | 
						|
            res = ""
 | 
						|
            if len(prefix) or len(suffix):
 | 
						|
                if len(prefix):
 | 
						|
                    res += " ".join(map(asFea, prefix)) + " "
 | 
						|
                res += " ".join(g.asFea() + "'" for g in glyphs)
 | 
						|
                if len(suffix):
 | 
						|
                    res += " " + " ".join(map(asFea, suffix))
 | 
						|
            else:
 | 
						|
                res += " ".join(map(asFea, glyphs))
 | 
						|
            contexts.append(res)
 | 
						|
        return "ignore pos " + ", ".join(contexts) + ";"
 | 
						|
 | 
						|
 | 
						|
class IgnoreSubstStatement(Statement):
 | 
						|
    """An ``ignore sub`` statement, containing `one or more` contexts to ignore.
 | 
						|
 | 
						|
    ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
 | 
						|
    with each of ``prefix``, ``glyphs`` and ``suffix`` being
 | 
						|
    `glyph-containing objects`_ ."""
 | 
						|
 | 
						|
    def __init__(self, chainContexts, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.chainContexts = chainContexts
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_chain_context_subst`` callback on
 | 
						|
        each rule context."""
 | 
						|
        for prefix, glyphs, suffix in self.chainContexts:
 | 
						|
            prefix = [p.glyphSet() for p in prefix]
 | 
						|
            glyphs = [g.glyphSet() for g in glyphs]
 | 
						|
            suffix = [s.glyphSet() for s in suffix]
 | 
						|
            builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, [])
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        contexts = []
 | 
						|
        for prefix, glyphs, suffix in self.chainContexts:
 | 
						|
            res = ""
 | 
						|
            if len(prefix):
 | 
						|
                res += " ".join(map(asFea, prefix)) + " "
 | 
						|
            res += " ".join(g.asFea() + "'" for g in glyphs)
 | 
						|
            if len(suffix):
 | 
						|
                res += " " + " ".join(map(asFea, suffix))
 | 
						|
            contexts.append(res)
 | 
						|
        return "ignore sub " + ", ".join(contexts) + ";"
 | 
						|
 | 
						|
 | 
						|
class IncludeStatement(Statement):
 | 
						|
    """An ``include()`` statement."""
 | 
						|
 | 
						|
    def __init__(self, filename, location=None):
 | 
						|
        super(IncludeStatement, self).__init__(location)
 | 
						|
        self.filename = filename  #: String containing name of file to include
 | 
						|
 | 
						|
    def build(self):
 | 
						|
        # TODO: consider lazy-loading the including parser/lexer?
 | 
						|
        raise FeatureLibError(
 | 
						|
            "Building an include statement is not implemented yet. "
 | 
						|
            "Instead, use Parser(..., followIncludes=True) for building.",
 | 
						|
            self.location,
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return indent + "include(%s);" % self.filename
 | 
						|
 | 
						|
 | 
						|
class LanguageStatement(Statement):
 | 
						|
    """A ``language`` statement within a feature."""
 | 
						|
 | 
						|
    def __init__(self, language, include_default=True, required=False, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        assert len(language) == 4
 | 
						|
        self.language = language  #: A four-character language tag
 | 
						|
        self.include_default = include_default  #: If false, "exclude_dflt"
 | 
						|
        self.required = required
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Call the builder object's ``set_language`` callback."""
 | 
						|
        builder.set_language(
 | 
						|
            location=self.location,
 | 
						|
            language=self.language,
 | 
						|
            include_default=self.include_default,
 | 
						|
            required=self.required,
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "language {}".format(self.language.strip())
 | 
						|
        if not self.include_default:
 | 
						|
            res += " exclude_dflt"
 | 
						|
        if self.required:
 | 
						|
            res += " required"
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class LanguageSystemStatement(Statement):
 | 
						|
    """A top-level ``languagesystem`` statement."""
 | 
						|
 | 
						|
    def __init__(self, script, language, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.script, self.language = (script, language)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_language_system`` callback."""
 | 
						|
        builder.add_language_system(self.location, self.script, self.language)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "languagesystem {} {};".format(self.script, self.language.strip())
 | 
						|
 | 
						|
 | 
						|
class FontRevisionStatement(Statement):
 | 
						|
    """A ``head`` table ``FontRevision`` statement. ``revision`` should be a
 | 
						|
    number, and will be formatted to three significant decimal places."""
 | 
						|
 | 
						|
    def __init__(self, revision, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.revision = revision
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.set_font_revision(self.location, self.revision)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "FontRevision {:.3f};".format(self.revision)
 | 
						|
 | 
						|
 | 
						|
class LigatureCaretByIndexStatement(Statement):
 | 
						|
    """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be
 | 
						|
    a `glyph-containing object`_, and ``carets`` should be a list of integers."""
 | 
						|
 | 
						|
    def __init__(self, glyphs, carets, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.glyphs, self.carets = (glyphs, carets)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_ligatureCaretByIndex_`` callback."""
 | 
						|
        glyphs = self.glyphs.glyphSet()
 | 
						|
        builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "LigatureCaretByIndex {} {};".format(
 | 
						|
            self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class LigatureCaretByPosStatement(Statement):
 | 
						|
    """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be
 | 
						|
    a `glyph-containing object`_, and ``carets`` should be a list of integers."""
 | 
						|
 | 
						|
    def __init__(self, glyphs, carets, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.glyphs, self.carets = (glyphs, carets)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_ligatureCaretByPos_`` callback."""
 | 
						|
        glyphs = self.glyphs.glyphSet()
 | 
						|
        builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "LigatureCaretByPos {} {};".format(
 | 
						|
            self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class LigatureSubstStatement(Statement):
 | 
						|
    """A chained contextual substitution statement.
 | 
						|
 | 
						|
    ``prefix``, ``glyphs``, and ``suffix`` should be lists of
 | 
						|
    `glyph-containing objects`_; ``replacement`` should be a single
 | 
						|
    `glyph-containing object`_.
 | 
						|
 | 
						|
    If ``forceChain`` is True, this is expressed as a chaining rule
 | 
						|
    (e.g. ``sub f' i' by f_i``) even when no context is given."""
 | 
						|
 | 
						|
    def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
 | 
						|
        self.replacement, self.forceChain = replacement, forceChain
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        glyphs = [g.glyphSet() for g in self.glyphs]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        builder.add_ligature_subst(
 | 
						|
            self.location, prefix, glyphs, suffix, self.replacement, self.forceChain
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "sub "
 | 
						|
        if len(self.prefix) or len(self.suffix) or self.forceChain:
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(g.asFea() for g in self.prefix) + " "
 | 
						|
            res += " ".join(g.asFea() + "'" for g in self.glyphs)
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(g.asFea() for g in self.suffix)
 | 
						|
        else:
 | 
						|
            res += " ".join(g.asFea() for g in self.glyphs)
 | 
						|
        res += " by "
 | 
						|
        res += asFea(self.replacement)
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class LookupFlagStatement(Statement):
 | 
						|
    """A ``lookupflag`` statement. The ``value`` should be an integer value
 | 
						|
    representing the flags in use, but not including the ``markAttachment``
 | 
						|
    class and ``markFilteringSet`` values, which must be specified as
 | 
						|
    glyph-containing objects."""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, value=0, markAttachment=None, markFilteringSet=None, location=None
 | 
						|
    ):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.value = value
 | 
						|
        self.markAttachment = markAttachment
 | 
						|
        self.markFilteringSet = markFilteringSet
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``set_lookup_flag`` callback."""
 | 
						|
        markAttach = None
 | 
						|
        if self.markAttachment is not None:
 | 
						|
            markAttach = self.markAttachment.glyphSet()
 | 
						|
        markFilter = None
 | 
						|
        if self.markFilteringSet is not None:
 | 
						|
            markFilter = self.markFilteringSet.glyphSet()
 | 
						|
        builder.set_lookup_flag(self.location, self.value, markAttach, markFilter)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = []
 | 
						|
        flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
 | 
						|
        curr = 1
 | 
						|
        for i in range(len(flags)):
 | 
						|
            if self.value & curr != 0:
 | 
						|
                res.append(flags[i])
 | 
						|
            curr = curr << 1
 | 
						|
        if self.markAttachment is not None:
 | 
						|
            res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
 | 
						|
        if self.markFilteringSet is not None:
 | 
						|
            res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
 | 
						|
        if not res:
 | 
						|
            res = ["0"]
 | 
						|
        return "lookupflag {};".format(" ".join(res))
 | 
						|
 | 
						|
 | 
						|
class LookupReferenceStatement(Statement):
 | 
						|
    """Represents a ``lookup ...;`` statement to include a lookup in a feature.
 | 
						|
 | 
						|
    The ``lookup`` should be a :class:`LookupBlock` object."""
 | 
						|
 | 
						|
    def __init__(self, lookup, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.location, self.lookup = (location, lookup)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_lookup_call`` callback."""
 | 
						|
        builder.add_lookup_call(self.lookup.name)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "lookup {};".format(self.lookup.name)
 | 
						|
 | 
						|
 | 
						|
class MarkBasePosStatement(Statement):
 | 
						|
    """A mark-to-base positioning rule. The ``base`` should be a
 | 
						|
    `glyph-containing object`_. The ``marks`` should be a list of
 | 
						|
    (:class:`Anchor`, :class:`MarkClass`) tuples."""
 | 
						|
 | 
						|
    def __init__(self, base, marks, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.base, self.marks = base, marks
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_mark_base_pos`` callback."""
 | 
						|
        builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "pos base {}".format(self.base.asFea())
 | 
						|
        for a, m in self.marks:
 | 
						|
            res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class MarkLigPosStatement(Statement):
 | 
						|
    """A mark-to-ligature positioning rule. The ``ligatures`` must be a
 | 
						|
    `glyph-containing object`_. The ``marks`` should be a list of lists: each
 | 
						|
    element in the top-level list represents a component glyph, and is made
 | 
						|
    up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing
 | 
						|
    mark attachment points for that position.
 | 
						|
 | 
						|
    Example::
 | 
						|
 | 
						|
        m1 = MarkClass("TOP_MARKS")
 | 
						|
        m2 = MarkClass("BOTTOM_MARKS")
 | 
						|
        # ... add definitions to mark classes...
 | 
						|
 | 
						|
        glyph = GlyphName("lam_meem_jeem")
 | 
						|
        marks = [
 | 
						|
            [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
 | 
						|
            [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
 | 
						|
            [ ]                         # No attachments on the jeem
 | 
						|
        ]
 | 
						|
        mlp = MarkLigPosStatement(glyph, marks)
 | 
						|
 | 
						|
        mlp.asFea()
 | 
						|
        # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS
 | 
						|
        # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS;
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, ligatures, marks, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.ligatures, self.marks = ligatures, marks
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_mark_lig_pos`` callback."""
 | 
						|
        builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "pos ligature {}".format(self.ligatures.asFea())
 | 
						|
        ligs = []
 | 
						|
        for l in self.marks:
 | 
						|
            temp = ""
 | 
						|
            if l is None or not len(l):
 | 
						|
                temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
 | 
						|
            else:
 | 
						|
                for a, m in l:
 | 
						|
                    temp += (
 | 
						|
                        "\n"
 | 
						|
                        + indent
 | 
						|
                        + SHIFT * 2
 | 
						|
                        + "{} mark @{}".format(a.asFea(), m.name)
 | 
						|
                    )
 | 
						|
            ligs.append(temp)
 | 
						|
        res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class MarkMarkPosStatement(Statement):
 | 
						|
    """A mark-to-mark positioning rule. The ``baseMarks`` must be a
 | 
						|
    `glyph-containing object`_. The ``marks`` should be a list of
 | 
						|
    (:class:`Anchor`, :class:`MarkClass`) tuples."""
 | 
						|
 | 
						|
    def __init__(self, baseMarks, marks, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.baseMarks, self.marks = baseMarks, marks
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_mark_mark_pos`` callback."""
 | 
						|
        builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "pos mark {}".format(self.baseMarks.asFea())
 | 
						|
        for a, m in self.marks:
 | 
						|
            res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class MultipleSubstStatement(Statement):
 | 
						|
    """A multiple substitution statement.
 | 
						|
 | 
						|
    Args:
 | 
						|
        prefix: a list of `glyph-containing objects`_.
 | 
						|
        glyph: a single glyph-containing object.
 | 
						|
        suffix: a list of glyph-containing objects.
 | 
						|
        replacement: a list of glyph-containing objects.
 | 
						|
        forceChain: If true, the statement is expressed as a chaining rule
 | 
						|
            (e.g. ``sub f' i' by f_i``) even when no context is given.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, prefix, glyph, suffix, replacement, forceChain=False, location=None
 | 
						|
    ):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
 | 
						|
        self.replacement = replacement
 | 
						|
        self.forceChain = forceChain
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_multiple_subst`` callback."""
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        if hasattr(self.glyph, "glyphSet"):
 | 
						|
            originals = self.glyph.glyphSet()
 | 
						|
        else:
 | 
						|
            originals = [self.glyph]
 | 
						|
        count = len(originals)
 | 
						|
        replaces = []
 | 
						|
        for r in self.replacement:
 | 
						|
            if hasattr(r, "glyphSet"):
 | 
						|
                replace = r.glyphSet()
 | 
						|
            else:
 | 
						|
                replace = [r]
 | 
						|
            if len(replace) == 1 and len(replace) != count:
 | 
						|
                replace = replace * count
 | 
						|
            replaces.append(replace)
 | 
						|
        replaces = list(zip(*replaces))
 | 
						|
 | 
						|
        seen_originals = set()
 | 
						|
        for i, original in enumerate(originals):
 | 
						|
            if original not in seen_originals:
 | 
						|
                seen_originals.add(original)
 | 
						|
                builder.add_multiple_subst(
 | 
						|
                    self.location,
 | 
						|
                    prefix,
 | 
						|
                    original,
 | 
						|
                    suffix,
 | 
						|
                    replaces and replaces[i] or (),
 | 
						|
                    self.forceChain,
 | 
						|
                )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "sub "
 | 
						|
        if len(self.prefix) or len(self.suffix) or self.forceChain:
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(map(asFea, self.prefix)) + " "
 | 
						|
            res += asFea(self.glyph) + "'"
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(map(asFea, self.suffix))
 | 
						|
        else:
 | 
						|
            res += asFea(self.glyph)
 | 
						|
        replacement = self.replacement or [NullGlyph()]
 | 
						|
        res += " by "
 | 
						|
        res += " ".join(map(asFea, replacement))
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class PairPosStatement(Statement):
 | 
						|
    """A pair positioning statement.
 | 
						|
 | 
						|
    ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_.
 | 
						|
    ``valuerecord1`` should be a :class:`ValueRecord` object;
 | 
						|
    ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``.
 | 
						|
    If ``enumerated`` is true, then this is expressed as an
 | 
						|
    `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        glyphs1,
 | 
						|
        valuerecord1,
 | 
						|
        glyphs2,
 | 
						|
        valuerecord2,
 | 
						|
        enumerated=False,
 | 
						|
        location=None,
 | 
						|
    ):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.enumerated = enumerated
 | 
						|
        self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
 | 
						|
        self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls a callback on the builder object:
 | 
						|
 | 
						|
        * If the rule is enumerated, calls ``add_specific_pair_pos`` on each
 | 
						|
          combination of first and second glyphs.
 | 
						|
        * If the glyphs are both single :class:`GlyphName` objects, calls
 | 
						|
          ``add_specific_pair_pos``.
 | 
						|
        * Else, calls ``add_class_pair_pos``.
 | 
						|
        """
 | 
						|
        if self.enumerated:
 | 
						|
            g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
 | 
						|
            seen_pair = False
 | 
						|
            for glyph1, glyph2 in itertools.product(*g):
 | 
						|
                seen_pair = True
 | 
						|
                builder.add_specific_pair_pos(
 | 
						|
                    self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2
 | 
						|
                )
 | 
						|
            if not seen_pair:
 | 
						|
                raise FeatureLibError(
 | 
						|
                    "Empty glyph class in positioning rule", self.location
 | 
						|
                )
 | 
						|
            return
 | 
						|
 | 
						|
        is_specific = isinstance(self.glyphs1, GlyphName) and isinstance(
 | 
						|
            self.glyphs2, GlyphName
 | 
						|
        )
 | 
						|
        if is_specific:
 | 
						|
            builder.add_specific_pair_pos(
 | 
						|
                self.location,
 | 
						|
                self.glyphs1.glyph,
 | 
						|
                self.valuerecord1,
 | 
						|
                self.glyphs2.glyph,
 | 
						|
                self.valuerecord2,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            builder.add_class_pair_pos(
 | 
						|
                self.location,
 | 
						|
                self.glyphs1.glyphSet(),
 | 
						|
                self.valuerecord1,
 | 
						|
                self.glyphs2.glyphSet(),
 | 
						|
                self.valuerecord2,
 | 
						|
            )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "enum " if self.enumerated else ""
 | 
						|
        if self.valuerecord2:
 | 
						|
            res += "pos {} {} {} {};".format(
 | 
						|
                self.glyphs1.asFea(),
 | 
						|
                self.valuerecord1.asFea(),
 | 
						|
                self.glyphs2.asFea(),
 | 
						|
                self.valuerecord2.asFea(),
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            res += "pos {} {} {};".format(
 | 
						|
                self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea()
 | 
						|
            )
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ReverseChainSingleSubstStatement(Statement):
 | 
						|
    """A reverse chaining substitution statement. You don't see those every day.
 | 
						|
 | 
						|
    Note the unusual argument order: ``suffix`` comes `before` ``glyphs``.
 | 
						|
    ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be
 | 
						|
    lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should
 | 
						|
    be one-item lists.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.old_prefix, self.old_suffix = old_prefix, old_suffix
 | 
						|
        self.glyphs = glyphs
 | 
						|
        self.replacements = replacements
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        prefix = [p.glyphSet() for p in self.old_prefix]
 | 
						|
        suffix = [s.glyphSet() for s in self.old_suffix]
 | 
						|
        originals = self.glyphs[0].glyphSet()
 | 
						|
        replaces = self.replacements[0].glyphSet()
 | 
						|
        if len(replaces) == 1:
 | 
						|
            replaces = replaces * len(originals)
 | 
						|
        builder.add_reverse_chain_single_subst(
 | 
						|
            self.location, prefix, suffix, dict(zip(originals, replaces))
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "rsub "
 | 
						|
        if len(self.old_prefix) or len(self.old_suffix):
 | 
						|
            if len(self.old_prefix):
 | 
						|
                res += " ".join(asFea(g) for g in self.old_prefix) + " "
 | 
						|
            res += " ".join(asFea(g) + "'" for g in self.glyphs)
 | 
						|
            if len(self.old_suffix):
 | 
						|
                res += " " + " ".join(asFea(g) for g in self.old_suffix)
 | 
						|
        else:
 | 
						|
            res += " ".join(map(asFea, self.glyphs))
 | 
						|
        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class SingleSubstStatement(Statement):
 | 
						|
    """A single substitution statement.
 | 
						|
 | 
						|
    Note the unusual argument order: ``prefix`` and suffix come `after`
 | 
						|
    the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and
 | 
						|
    ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and
 | 
						|
    ``replace`` should be one-item lists.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.prefix, self.suffix = prefix, suffix
 | 
						|
        self.forceChain = forceChain
 | 
						|
        self.glyphs = glyphs
 | 
						|
        self.replacements = replace
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_single_subst`` callback."""
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        originals = self.glyphs[0].glyphSet()
 | 
						|
        replaces = self.replacements[0].glyphSet()
 | 
						|
        if len(replaces) == 1:
 | 
						|
            replaces = replaces * len(originals)
 | 
						|
        builder.add_single_subst(
 | 
						|
            self.location,
 | 
						|
            prefix,
 | 
						|
            suffix,
 | 
						|
            OrderedDict(zip(originals, replaces)),
 | 
						|
            self.forceChain,
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "sub "
 | 
						|
        if len(self.prefix) or len(self.suffix) or self.forceChain:
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(asFea(g) for g in self.prefix) + " "
 | 
						|
            res += " ".join(asFea(g) + "'" for g in self.glyphs)
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(asFea(g) for g in self.suffix)
 | 
						|
        else:
 | 
						|
            res += " ".join(asFea(g) for g in self.glyphs)
 | 
						|
        res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ScriptStatement(Statement):
 | 
						|
    """A ``script`` statement."""
 | 
						|
 | 
						|
    def __init__(self, script, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.script = script  #: the script code
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder's ``set_script`` callback."""
 | 
						|
        builder.set_script(self.location, self.script)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "script {};".format(self.script.strip())
 | 
						|
 | 
						|
 | 
						|
class SinglePosStatement(Statement):
 | 
						|
    """A single position statement. ``prefix`` and ``suffix`` should be
 | 
						|
    lists of `glyph-containing objects`_.
 | 
						|
 | 
						|
    ``pos`` should be a one-element list containing a (`glyph-containing object`_,
 | 
						|
    :class:`ValueRecord`) tuple."""
 | 
						|
 | 
						|
    def __init__(self, pos, prefix, suffix, forceChain, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.pos, self.prefix, self.suffix = pos, prefix, suffix
 | 
						|
        self.forceChain = forceChain
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_single_pos`` callback."""
 | 
						|
        prefix = [p.glyphSet() for p in self.prefix]
 | 
						|
        suffix = [s.glyphSet() for s in self.suffix]
 | 
						|
        pos = [(g.glyphSet(), value) for g, value in self.pos]
 | 
						|
        builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "pos "
 | 
						|
        if len(self.prefix) or len(self.suffix) or self.forceChain:
 | 
						|
            if len(self.prefix):
 | 
						|
                res += " ".join(map(asFea, self.prefix)) + " "
 | 
						|
            res += " ".join(
 | 
						|
                [
 | 
						|
                    asFea(x[0])
 | 
						|
                    + "'"
 | 
						|
                    + ((" " + x[1].asFea()) if x[1] is not None else "")
 | 
						|
                    for x in self.pos
 | 
						|
                ]
 | 
						|
            )
 | 
						|
            if len(self.suffix):
 | 
						|
                res += " " + " ".join(map(asFea, self.suffix))
 | 
						|
        else:
 | 
						|
            res += " ".join(
 | 
						|
                [
 | 
						|
                    asFea(x[0]) + " " + (x[1].asFea() if x[1] is not None else "")
 | 
						|
                    for x in self.pos
 | 
						|
                ]
 | 
						|
            )
 | 
						|
        res += ";"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class SubtableStatement(Statement):
 | 
						|
    """Represents a subtable break."""
 | 
						|
 | 
						|
    def __init__(self, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder objects's ``add_subtable_break`` callback."""
 | 
						|
        builder.add_subtable_break(self.location)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "subtable;"
 | 
						|
 | 
						|
 | 
						|
class ValueRecord(Expression):
 | 
						|
    """Represents a value record."""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        xPlacement=None,
 | 
						|
        yPlacement=None,
 | 
						|
        xAdvance=None,
 | 
						|
        yAdvance=None,
 | 
						|
        xPlaDevice=None,
 | 
						|
        yPlaDevice=None,
 | 
						|
        xAdvDevice=None,
 | 
						|
        yAdvDevice=None,
 | 
						|
        vertical=False,
 | 
						|
        location=None,
 | 
						|
    ):
 | 
						|
        Expression.__init__(self, location)
 | 
						|
        self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
 | 
						|
        self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
 | 
						|
        self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice)
 | 
						|
        self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice)
 | 
						|
        self.vertical = vertical
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        return (
 | 
						|
            self.xPlacement == other.xPlacement
 | 
						|
            and self.yPlacement == other.yPlacement
 | 
						|
            and self.xAdvance == other.xAdvance
 | 
						|
            and self.yAdvance == other.yAdvance
 | 
						|
            and self.xPlaDevice == other.xPlaDevice
 | 
						|
            and self.xAdvDevice == other.xAdvDevice
 | 
						|
        )
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        return not self.__eq__(other)
 | 
						|
 | 
						|
    def __hash__(self):
 | 
						|
        return (
 | 
						|
            hash(self.xPlacement)
 | 
						|
            ^ hash(self.yPlacement)
 | 
						|
            ^ hash(self.xAdvance)
 | 
						|
            ^ hash(self.yAdvance)
 | 
						|
            ^ hash(self.xPlaDevice)
 | 
						|
            ^ hash(self.yPlaDevice)
 | 
						|
            ^ hash(self.xAdvDevice)
 | 
						|
            ^ hash(self.yAdvDevice)
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        if not self:
 | 
						|
            return "<NULL>"
 | 
						|
 | 
						|
        x, y = self.xPlacement, self.yPlacement
 | 
						|
        xAdvance, yAdvance = self.xAdvance, self.yAdvance
 | 
						|
        xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice
 | 
						|
        xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice
 | 
						|
        vertical = self.vertical
 | 
						|
 | 
						|
        # Try format A, if possible.
 | 
						|
        if x is None and y is None:
 | 
						|
            if xAdvance is None and vertical:
 | 
						|
                return str(yAdvance)
 | 
						|
            elif yAdvance is None and not vertical:
 | 
						|
                return str(xAdvance)
 | 
						|
 | 
						|
        # Make any remaining None value 0 to avoid generating invalid records.
 | 
						|
        x = x or 0
 | 
						|
        y = y or 0
 | 
						|
        xAdvance = xAdvance or 0
 | 
						|
        yAdvance = yAdvance or 0
 | 
						|
 | 
						|
        # Try format B, if possible.
 | 
						|
        if (
 | 
						|
            xPlaDevice is None
 | 
						|
            and yPlaDevice is None
 | 
						|
            and xAdvDevice is None
 | 
						|
            and yAdvDevice is None
 | 
						|
        ):
 | 
						|
            return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
 | 
						|
 | 
						|
        # Last resort is format C.
 | 
						|
        return "<%s %s %s %s %s %s %s %s>" % (
 | 
						|
            x,
 | 
						|
            y,
 | 
						|
            xAdvance,
 | 
						|
            yAdvance,
 | 
						|
            deviceToString(xPlaDevice),
 | 
						|
            deviceToString(yPlaDevice),
 | 
						|
            deviceToString(xAdvDevice),
 | 
						|
            deviceToString(yAdvDevice),
 | 
						|
        )
 | 
						|
 | 
						|
    def __bool__(self):
 | 
						|
        return any(
 | 
						|
            getattr(self, v) is not None
 | 
						|
            for v in [
 | 
						|
                "xPlacement",
 | 
						|
                "yPlacement",
 | 
						|
                "xAdvance",
 | 
						|
                "yAdvance",
 | 
						|
                "xPlaDevice",
 | 
						|
                "yPlaDevice",
 | 
						|
                "xAdvDevice",
 | 
						|
                "yAdvDevice",
 | 
						|
            ]
 | 
						|
        )
 | 
						|
 | 
						|
    __nonzero__ = __bool__
 | 
						|
 | 
						|
 | 
						|
class ValueRecordDefinition(Statement):
 | 
						|
    """Represents a named value record definition."""
 | 
						|
 | 
						|
    def __init__(self, name, value, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.name = name  #: Value record name as string
 | 
						|
        self.value = value  #: :class:`ValueRecord` object
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
 | 
						|
 | 
						|
 | 
						|
def simplify_name_attributes(pid, eid, lid):
 | 
						|
    if pid == 3 and eid == 1 and lid == 1033:
 | 
						|
        return ""
 | 
						|
    elif pid == 1 and eid == 0 and lid == 0:
 | 
						|
        return "1"
 | 
						|
    else:
 | 
						|
        return "{} {} {}".format(pid, eid, lid)
 | 
						|
 | 
						|
 | 
						|
class NameRecord(Statement):
 | 
						|
    """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)"""
 | 
						|
 | 
						|
    def __init__(self, nameID, platformID, platEncID, langID, string, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.nameID = nameID  #: Name ID as integer (e.g. 9 for designer's name)
 | 
						|
        self.platformID = platformID  #: Platform ID as integer
 | 
						|
        self.platEncID = platEncID  #: Platform encoding ID as integer
 | 
						|
        self.langID = langID  #: Language ID as integer
 | 
						|
        self.string = string  #: Name record value
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_name_record`` callback."""
 | 
						|
        builder.add_name_record(
 | 
						|
            self.location,
 | 
						|
            self.nameID,
 | 
						|
            self.platformID,
 | 
						|
            self.platEncID,
 | 
						|
            self.langID,
 | 
						|
            self.string,
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        def escape(c, escape_pattern):
 | 
						|
            # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
 | 
						|
            if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
 | 
						|
                return chr(c)
 | 
						|
            else:
 | 
						|
                return escape_pattern % c
 | 
						|
 | 
						|
        encoding = getEncoding(self.platformID, self.platEncID, self.langID)
 | 
						|
        if encoding is None:
 | 
						|
            raise FeatureLibError("Unsupported encoding", self.location)
 | 
						|
        s = tobytes(self.string, encoding=encoding)
 | 
						|
        if encoding == "utf_16_be":
 | 
						|
            escaped_string = "".join(
 | 
						|
                [
 | 
						|
                    escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
 | 
						|
                    for i in range(0, len(s), 2)
 | 
						|
                ]
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
 | 
						|
        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
 | 
						|
        if plat != "":
 | 
						|
            plat += " "
 | 
						|
        return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string)
 | 
						|
 | 
						|
 | 
						|
class FeatureNameStatement(NameRecord):
 | 
						|
    """Represents a ``sizemenuname`` or ``name`` statement."""
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_featureName`` callback."""
 | 
						|
        NameRecord.build(self, builder)
 | 
						|
        builder.add_featureName(self.nameID)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        if self.nameID == "size":
 | 
						|
            tag = "sizemenuname"
 | 
						|
        else:
 | 
						|
            tag = "name"
 | 
						|
        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
 | 
						|
        if plat != "":
 | 
						|
            plat += " "
 | 
						|
        return '{} {}"{}";'.format(tag, plat, self.string)
 | 
						|
 | 
						|
 | 
						|
class STATNameStatement(NameRecord):
 | 
						|
    """Represents a STAT table ``name`` statement."""
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
 | 
						|
        if plat != "":
 | 
						|
            plat += " "
 | 
						|
        return 'name {}"{}";'.format(plat, self.string)
 | 
						|
 | 
						|
 | 
						|
class SizeParameters(Statement):
 | 
						|
    """A ``parameters`` statement."""
 | 
						|
 | 
						|
    def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.DesignSize = DesignSize
 | 
						|
        self.SubfamilyID = SubfamilyID
 | 
						|
        self.RangeStart = RangeStart
 | 
						|
        self.RangeEnd = RangeEnd
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``set_size_parameters`` callback."""
 | 
						|
        builder.set_size_parameters(
 | 
						|
            self.location,
 | 
						|
            self.DesignSize,
 | 
						|
            self.SubfamilyID,
 | 
						|
            self.RangeStart,
 | 
						|
            self.RangeEnd,
 | 
						|
        )
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
 | 
						|
        if self.RangeStart != 0 or self.RangeEnd != 0:
 | 
						|
            res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10))
 | 
						|
        return res + ";"
 | 
						|
 | 
						|
 | 
						|
class CVParametersNameStatement(NameRecord):
 | 
						|
    """Represent a name statement inside a ``cvParameters`` block."""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, nameID, platformID, platEncID, langID, string, block_name, location=None
 | 
						|
    ):
 | 
						|
        NameRecord.__init__(
 | 
						|
            self, nameID, platformID, platEncID, langID, string, location=location
 | 
						|
        )
 | 
						|
        self.block_name = block_name
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_cv_parameter`` callback."""
 | 
						|
        item = ""
 | 
						|
        if self.block_name == "ParamUILabelNameID":
 | 
						|
            item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
 | 
						|
        builder.add_cv_parameter(self.nameID)
 | 
						|
        self.nameID = (self.nameID, self.block_name + item)
 | 
						|
        NameRecord.build(self, builder)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
 | 
						|
        if plat != "":
 | 
						|
            plat += " "
 | 
						|
        return 'name {}"{}";'.format(plat, self.string)
 | 
						|
 | 
						|
 | 
						|
class CharacterStatement(Statement):
 | 
						|
    """
 | 
						|
    Statement used in cvParameters blocks of Character Variant features (cvXX).
 | 
						|
    The Unicode value may be written with either decimal or hexadecimal
 | 
						|
    notation. The value must be preceded by '0x' if it is a hexadecimal value.
 | 
						|
    The largest Unicode value allowed is 0xFFFFFF.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, character, tag, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.character = character
 | 
						|
        self.tag = tag
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_cv_character`` callback."""
 | 
						|
        builder.add_cv_character(self.character, self.tag)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return "Character {:#x};".format(self.character)
 | 
						|
 | 
						|
 | 
						|
class BaseAxis(Statement):
 | 
						|
    """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList``
 | 
						|
    pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair."""
 | 
						|
 | 
						|
    def __init__(self, bases, scripts, vertical, minmax=None, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.bases = bases  #: A list of baseline tag names as strings
 | 
						|
        self.scripts = scripts  #: A list of script record tuplets (script tag, default baseline tag, base coordinate)
 | 
						|
        self.vertical = vertical  #: Boolean; VertAxis if True, HorizAxis if False
 | 
						|
        self.minmax = []  #: A set of minmax record
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``set_base_axis`` callback."""
 | 
						|
        builder.set_base_axis(self.bases, self.scripts, self.vertical, self.minmax)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        direction = "Vert" if self.vertical else "Horiz"
 | 
						|
        scripts = [
 | 
						|
            "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2])))
 | 
						|
            for a in self.scripts
 | 
						|
        ]
 | 
						|
        minmaxes = [
 | 
						|
            "\n{}Axis.MinMax {} {} {}, {};".format(direction, a[0], a[1], a[2], a[3])
 | 
						|
            for a in self.minmax
 | 
						|
        ]
 | 
						|
        return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
 | 
						|
            direction, " ".join(self.bases), indent, direction, ", ".join(scripts)
 | 
						|
        ) + "\n".join(minmaxes)
 | 
						|
 | 
						|
 | 
						|
class OS2Field(Statement):
 | 
						|
    """An entry in the ``OS/2`` table. Most ``values`` should be numbers or
 | 
						|
    strings, apart from when the key is ``UnicodeRange``, ``CodePageRange``
 | 
						|
    or ``Panose``, in which case it should be an array of integers."""
 | 
						|
 | 
						|
    def __init__(self, key, value, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.key = key
 | 
						|
        self.value = value
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_os2_field`` callback."""
 | 
						|
        builder.add_os2_field(self.key, self.value)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        def intarr2str(x):
 | 
						|
            return " ".join(map(str, x))
 | 
						|
 | 
						|
        numbers = (
 | 
						|
            "FSType",
 | 
						|
            "TypoAscender",
 | 
						|
            "TypoDescender",
 | 
						|
            "TypoLineGap",
 | 
						|
            "winAscent",
 | 
						|
            "winDescent",
 | 
						|
            "XHeight",
 | 
						|
            "CapHeight",
 | 
						|
            "WeightClass",
 | 
						|
            "WidthClass",
 | 
						|
            "LowerOpSize",
 | 
						|
            "UpperOpSize",
 | 
						|
        )
 | 
						|
        ranges = ("UnicodeRange", "CodePageRange")
 | 
						|
        keywords = dict([(x.lower(), [x, str]) for x in numbers])
 | 
						|
        keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
 | 
						|
        keywords["panose"] = ["Panose", intarr2str]
 | 
						|
        keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
 | 
						|
        if self.key in keywords:
 | 
						|
            return "{} {};".format(
 | 
						|
                keywords[self.key][0], keywords[self.key][1](self.value)
 | 
						|
            )
 | 
						|
        return ""  # should raise exception
 | 
						|
 | 
						|
 | 
						|
class HheaField(Statement):
 | 
						|
    """An entry in the ``hhea`` table."""
 | 
						|
 | 
						|
    def __init__(self, key, value, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.key = key
 | 
						|
        self.value = value
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_hhea_field`` callback."""
 | 
						|
        builder.add_hhea_field(self.key, self.value)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        fields = ("CaretOffset", "Ascender", "Descender", "LineGap")
 | 
						|
        keywords = dict([(x.lower(), x) for x in fields])
 | 
						|
        return "{} {};".format(keywords[self.key], self.value)
 | 
						|
 | 
						|
 | 
						|
class VheaField(Statement):
 | 
						|
    """An entry in the ``vhea`` table."""
 | 
						|
 | 
						|
    def __init__(self, key, value, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.key = key
 | 
						|
        self.value = value
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Calls the builder object's ``add_vhea_field`` callback."""
 | 
						|
        builder.add_vhea_field(self.key, self.value)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
 | 
						|
        keywords = dict([(x.lower(), x) for x in fields])
 | 
						|
        return "{} {};".format(keywords[self.key], self.value)
 | 
						|
 | 
						|
 | 
						|
class STATDesignAxisStatement(Statement):
 | 
						|
    """A STAT table Design Axis
 | 
						|
 | 
						|
    Args:
 | 
						|
        tag (str): a 4 letter axis tag
 | 
						|
        axisOrder (int): an int
 | 
						|
        names (list): a list of :class:`STATNameStatement` objects
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, tag, axisOrder, names, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.tag = tag
 | 
						|
        self.axisOrder = axisOrder
 | 
						|
        self.names = names
 | 
						|
        self.location = location
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.addDesignAxis(self, self.location)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        indent += SHIFT
 | 
						|
        res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
 | 
						|
        res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
 | 
						|
        res += "};"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ElidedFallbackName(Statement):
 | 
						|
    """STAT table ElidedFallbackName
 | 
						|
 | 
						|
    Args:
 | 
						|
        names: a list of :class:`STATNameStatement` objects
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, names, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.names = names
 | 
						|
        self.location = location
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.setElidedFallbackName(self.names, self.location)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        indent += SHIFT
 | 
						|
        res = "ElidedFallbackName { \n"
 | 
						|
        res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
 | 
						|
        res += "};"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ElidedFallbackNameID(Statement):
 | 
						|
    """STAT table ElidedFallbackNameID
 | 
						|
 | 
						|
    Args:
 | 
						|
        value: an int pointing to an existing name table name ID
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, value, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.value = value
 | 
						|
        self.location = location
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.setElidedFallbackName(self.value, self.location)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        return f"ElidedFallbackNameID {self.value};"
 | 
						|
 | 
						|
 | 
						|
class STATAxisValueStatement(Statement):
 | 
						|
    """A STAT table Axis Value Record
 | 
						|
 | 
						|
    Args:
 | 
						|
        names (list): a list of :class:`STATNameStatement` objects
 | 
						|
        locations (list): a list of :class:`AxisValueLocationStatement` objects
 | 
						|
        flags (int): an int
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, names, locations, flags, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.names = names
 | 
						|
        self.locations = locations
 | 
						|
        self.flags = flags
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.addAxisValueRecord(self, self.location)
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = "AxisValue {\n"
 | 
						|
        for location in self.locations:
 | 
						|
            res += location.asFea()
 | 
						|
 | 
						|
        for nameRecord in self.names:
 | 
						|
            res += nameRecord.asFea()
 | 
						|
            res += "\n"
 | 
						|
 | 
						|
        if self.flags:
 | 
						|
            flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
 | 
						|
            flagStrings = []
 | 
						|
            curr = 1
 | 
						|
            for i in range(len(flags)):
 | 
						|
                if self.flags & curr != 0:
 | 
						|
                    flagStrings.append(flags[i])
 | 
						|
                curr = curr << 1
 | 
						|
            res += f"flag {' '.join(flagStrings)};\n"
 | 
						|
        res += "};"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class AxisValueLocationStatement(Statement):
 | 
						|
    """
 | 
						|
    A STAT table Axis Value Location
 | 
						|
 | 
						|
    Args:
 | 
						|
        tag (str): a 4 letter axis tag
 | 
						|
        values (list): a list of ints and/or floats
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, tag, values, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.tag = tag
 | 
						|
        self.values = values
 | 
						|
 | 
						|
    def asFea(self, res=""):
 | 
						|
        res += f"location {self.tag} "
 | 
						|
        res += f"{' '.join(str(i) for i in self.values)};\n"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class ConditionsetStatement(Statement):
 | 
						|
    """
 | 
						|
    A variable layout conditionset
 | 
						|
 | 
						|
    Args:
 | 
						|
        name (str): the name of this conditionset
 | 
						|
        conditions (dict): a dictionary mapping axis tags to a
 | 
						|
            tuple of (min,max) userspace coordinates.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, name, conditions, location=None):
 | 
						|
        Statement.__init__(self, location)
 | 
						|
        self.name = name
 | 
						|
        self.conditions = conditions
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        builder.add_conditionset(self.location, self.name, self.conditions)
 | 
						|
 | 
						|
    def asFea(self, res="", indent=""):
 | 
						|
        res += indent + f"conditionset {self.name} " + "{\n"
 | 
						|
        for tag, (minvalue, maxvalue) in self.conditions.items():
 | 
						|
            res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n"
 | 
						|
        res += indent + "}" + f" {self.name};\n"
 | 
						|
        return res
 | 
						|
 | 
						|
 | 
						|
class VariationBlock(Block):
 | 
						|
    """A variation feature block, applicable in a given set of conditions."""
 | 
						|
 | 
						|
    def __init__(self, name, conditionset, use_extension=False, location=None):
 | 
						|
        Block.__init__(self, location)
 | 
						|
        self.name, self.conditionset, self.use_extension = (
 | 
						|
            name,
 | 
						|
            conditionset,
 | 
						|
            use_extension,
 | 
						|
        )
 | 
						|
 | 
						|
    def build(self, builder):
 | 
						|
        """Call the ``start_feature`` callback on the builder object, visit
 | 
						|
        all the statements in this feature, and then call ``end_feature``."""
 | 
						|
        builder.start_feature(self.location, self.name, self.use_extension)
 | 
						|
        if (
 | 
						|
            self.conditionset != "NULL"
 | 
						|
            and self.conditionset not in builder.conditionsets_
 | 
						|
        ):
 | 
						|
            raise FeatureLibError(
 | 
						|
                f"variation block used undefined conditionset {self.conditionset}",
 | 
						|
                self.location,
 | 
						|
            )
 | 
						|
 | 
						|
        # language exclude_dflt statements modify builder.features_
 | 
						|
        # limit them to this block with temporary builder.features_
 | 
						|
        features = builder.features_
 | 
						|
        builder.features_ = {}
 | 
						|
        Block.build(self, builder)
 | 
						|
        for key, value in builder.features_.items():
 | 
						|
            items = builder.feature_variations_.setdefault(key, {}).setdefault(
 | 
						|
                self.conditionset, []
 | 
						|
            )
 | 
						|
            items.extend(value)
 | 
						|
            if key not in features:
 | 
						|
                features[key] = []  # Ensure we make a feature record
 | 
						|
        builder.features_ = features
 | 
						|
        builder.end_feature()
 | 
						|
 | 
						|
    def asFea(self, indent=""):
 | 
						|
        res = indent + "variation %s " % self.name.strip()
 | 
						|
        res += self.conditionset + " "
 | 
						|
        if self.use_extension:
 | 
						|
            res += "useExtension "
 | 
						|
        res += "{\n"
 | 
						|
        res += Block.asFea(self, indent=indent)
 | 
						|
        res += indent + "} %s;\n" % self.name.strip()
 | 
						|
        return res
 |