# Source code for fontTools.mtiLib

    #!/usr/bin/python

    # FontDame-to-FontTools for OpenType Layout tables
    #
    # Source language spec is available at:
    # http://monotype.github.io/OpenType_Table_Source/otl_source.html
    # https://github.com/Monotype/OpenType_Table_Source/

    from fontTools import ttLib
    from fontTools.ttLib.tables._c_m_a_p import cmap_classes
    from fontTools.ttLib.tables import otTables as ot
    from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
    from fontTools.otlLib import builder as otl
    from contextlib import contextmanager
    from fontTools.ttLib import newTable
    from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
    from operator import setitem
    import os
    import logging

    class MtiLibError(Exception):
        pass

    class ReferenceNotFoundError(MtiLibError):
        pass

    class FeatureNotFoundError(ReferenceNotFoundError):
        pass

    class LookupNotFoundError(ReferenceNotFoundError):
        pass

    log = logging.getLogger("fontTools.mtiLib")

    def makeGlyph(s):
        if s[:2] in ["U ", "u "]:
            return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
        elif s[:2] == "# ":
            return "glyph%.5d" % int(s[2:])
        assert s.find(" ") < 0, "Space found in glyph name: %s" % s
        assert s, "Glyph name is empty"
        return s

    def makeGlyphs(l):
        return [makeGlyph(g) for g in l]

    def mapLookup(sym, mapping):
        # Lookups are addressed by name.  So resolved them using a map if available.
        # Fallback to parsing as lookup index if a map isn't provided.
        if mapping is not None:
            try:
                idx = mapping[sym]
            except KeyError:
                raise LookupNotFoundError(sym)
        else:
            idx = int(sym)
        return idx

    def mapFeature(sym, mapping):
        # Features are referenced by index according the spec.  So, if symbol is an
        # integer, use it directly.  Otherwise look up in the map if provided.
        try:
            idx = int(sym)
        except ValueError:
            try:
                idx = mapping[sym]
            except KeyError:
                raise FeatureNotFoundError(sym)
        return idx

    def setReference(mapper, mapping, sym, setter, collection, key):
        try:
            mapped = mapper(sym, mapping)
        except ReferenceNotFoundError as e:
            try:
                if mapping is not None:
                    mapping.addDeferredMapping(
                        lambda ref: setter(collection, key, ref), sym, e
                    )
                    return
            except AttributeError:
                pass
            raise
        setter(collection, key, mapped)

    class DeferredMapping(dict):
        def __init__(self):
            self._deferredMappings = []

        def addDeferredMapping(self, setter, sym, e):
            log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
            self._deferredMappings.append((setter, sym, e))

        def applyDeferredMappings(self):
            for setter, sym, e in self._deferredMappings:
                log.debug(
                    "Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
                )
                try:
                    mapped = self[sym]
                except KeyError:
                    raise e
                setter(mapped)
                log.debug("Set to %s", mapped)
            self._deferredMappings = []

    def parseScriptList(lines, featureMap=None):
        self = ot.ScriptList()
        records = []
        with lines.between("script table"):
            for line in lines:
                while len(line) < 4:
                    line.append("")
                scriptTag, langSysTag, defaultFeature, features = line
                log.debug("Adding script %s language-system %s", scriptTag, langSysTag)

                langSys = ot.LangSys()
                langSys.LookupOrder = None
                if defaultFeature:
                    setReference(
                        mapFeature,
                        featureMap,
                        defaultFeature,
                        setattr,
                        langSys,
                        "ReqFeatureIndex",
                    )
                else:
                    langSys.ReqFeatureIndex = 0xFFFF
                syms = stripSplitComma(features)
                langSys.FeatureIndex = theList = [3] * len(syms)
                for i, sym in enumerate(syms):
                    setReference(mapFeature, featureMap, sym, setitem, theList, i)
                langSys.FeatureCount = len(langSys.FeatureIndex)

                script = [s for s in records if s.ScriptTag == scriptTag]
                if script:
                    script = script[0].Script
                else:
                    scriptRec = ot.ScriptRecord()
                    scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
                    scriptRec.Script = ot.Script()
                    records.append(scriptRec)
                    script = scriptRec.Script
                    script.DefaultLangSys = None
                    script.LangSysRecord = []
                    script.LangSysCount = 0

                if langSysTag == "default":
                    script.DefaultLangSys = langSys
                else:
                    langSysRec = ot.LangSysRecord()
                    langSysRec.LangSysTag = langSysTag + " " * (4 - len(langSysTag))
                    langSysRec.LangSys = langSys
                    script.LangSysRecord.append(langSysRec)
                    script.LangSysCount = len(script.LangSysRecord)

        for script in records:
            script.Script.LangSysRecord = sorted(
                script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
            )
        self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
        self.ScriptCount = len(self.ScriptRecord)
        return self

    def parseFeatureList(lines, lookupMap=None, featureMap=None):
        self = ot.FeatureList()
        self.FeatureRecord = []
        with lines.between("feature table"):
            for line in lines:
                name, featureTag, lookups = line
                if featureMap is not None:
                    assert name not in featureMap, "Duplicate feature name: %s" % name
                    featureMap[name] = len(self.FeatureRecord)
                # If feature name is integer, make sure it matches its index.
                try:
                    assert int(name) == len(self.FeatureRecord), "%d %d" % (
                        name,
                        len(self.FeatureRecord),
                    )
                except ValueError:
                    pass
                featureRec = ot.FeatureRecord()
                featureRec.FeatureTag = featureTag
                featureRec.Feature = ot.Feature()
                self.FeatureRecord.append(featureRec)
                feature = featureRec.Feature
                feature.FeatureParams = None
                syms = stripSplitComma(lookups)
                feature.LookupListIndex = theList = [None] * len(syms)
                for i, sym in enumerate(syms):
                    setReference(mapLookup, lookupMap, sym, setitem, theList, i)
                feature.LookupCount = len(feature.LookupListIndex)

        self.FeatureCount = len(self.FeatureRecord)
        return self

    def parseLookupFlags(lines):
        flags = 0
        filterset = None
        allFlags = [
            "righttoleft",
            "ignorebaseglyphs",
            "ignoreligatures",
            "ignoremarks",
            "markattachmenttype",
            "markfiltertype",
        ]
        while lines.peeks()[0].lower() in allFlags:
            line = next(lines)
            flag = {
                "righttoleft": 0x0001,
                "ignorebaseglyphs": 0x0002,
                "ignoreligatures": 0x0004,
                "ignoremarks": 0x0008,
            }.get(line[0].lower())
            if flag:
                assert line[1].lower() in ["yes", "no"], line[1]
                if line[1].lower() == "yes":
                    flags |= flag
                continue
            if line[0].lower() == "markattachmenttype":
                flags |= int(line[1]) << 8
                continue
            if line[0].lower() == "markfiltertype":
                flags |= 0x10
                filterset = int(line[1])
        return flags, filterset

    def parseSingleSubst(lines, font, _lookupMap=None):
        mapping = {}
        for line in lines:
            assert len(line) == 2, line
            line = makeGlyphs(line)
            mapping[line[0]] = line[1]
        return otl.buildSingleSubstSubtable(mapping)

    def parseMultiple(lines, font, _lookupMap=None):
        mapping = {}
        for line in lines:
            line = makeGlyphs(line)
            mapping[line[0]] = line[1:]
        return otl.buildMultipleSubstSubtable(mapping)

    def parseAlternate(lines, font, _lookupMap=None):
        mapping = {}
        for line in lines:
            line = makeGlyphs(line)
            mapping[line[0]] = line[1:]
        return otl.buildAlternateSubstSubtable(mapping)

    def parseLigature(lines, font, _lookupMap=None):
        mapping = {}
        for line in lines:
            assert len(line) >= 2, line
            line = makeGlyphs(line)
            mapping[tuple(line[1:])] = line[0]
        return otl.buildLigatureSubstSubtable(mapping)

    def parseSinglePos(lines, font, _lookupMap=None):
        values = {}
        for line in lines:
            assert len(line) == 3, line
            w = line[0].title().replace(" ", "")
            assert w in valueRecordFormatDict
            g = makeGlyph(line[1])
            v = int(line[2])
            if g not in values:
                values[g] = ValueRecord()
            assert not hasattr(values[g], w), (g, w)
            setattr(values[g], w, v)
        return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())

    def parsePair(lines, font, _lookupMap=None):
        self = ot.PairPos()
        self.ValueFormat1 = self.ValueFormat2 = 0
        typ = lines.peeks()[0].split()[0].lower()
        if typ in ("left", "right"):
            self.Format = 1
            values = {}
            for line in lines:
                assert len(line) == 4, line
                side = line[0].split()[0].lower()
                assert side in ("left", "right"), side
                what = line[0][len(side) :].title().replace(" ", "")
                mask = valueRecordFormatDict[what][0]
                glyph1, glyph2 = makeGlyphs(line[1:3])
                value = int(line[3])
                if not glyph1 in values:
                    values[glyph1] = {}
                if not glyph2 in values[glyph1]:
                    values[glyph1][glyph2] = (ValueRecord(), ValueRecord())
                rec2 = values[glyph1][glyph2]
                if side == "left":
                    self.ValueFormat1 |= mask
                    vr = rec2[0]
                else:
                    self.ValueFormat2 |= mask
                    vr = rec2[1]
                assert not hasattr(vr, what), (vr, what)
                setattr(vr, what, value)
            self.Coverage = makeCoverage(set(values.keys()), font)
            self.PairSet = []
            for glyph1 in self.Coverage.glyphs:
                values1 = values[glyph1]
                pairset = ot.PairSet()
                records = pairset.PairValueRecord = []
                for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
                    values2 = values1[glyph2]
                    pair = ot.PairValueRecord()
                    pair.SecondGlyph = glyph2
                    pair.Value1 = values2[0]
                    pair.Value2 = values2[1] if self.ValueFormat2 else None
                    records.append(pair)
                pairset.PairValueCount = len(pairset.PairValueRecord)
                self.PairSet.append(pairset)
            self.PairSetCount = len(self.PairSet)
        elif typ.endswith("class"):
            self.Format = 2
            classDefs = [None, None]
            while lines.peeks()[0].endswith("class definition begin"):
                typ = lines.peek()[0][: -len("class definition begin")].lower()
                idx, klass = {
                    "first": (0, ot.ClassDef1),
                    "second": (1, ot.ClassDef2),
                }[typ]
                assert classDefs[idx] is None
                classDefs[idx] = parseClassDef(lines, font, klass=klass)
            self.ClassDef1, self.ClassDef2 = classDefs
            self.Class1Count, self.Class2Count = (
                1 + max(c.classDefs.values()) for c in classDefs
            )
            self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
            for rec1 in self.Class1Record:
                rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
                for rec2 in rec1.Class2Record:
                    rec2.Value1 = ValueRecord()
                    rec2.Value2 = ValueRecord()
            for line in lines:
                assert len(line) == 4, line
                side = line[0].split()[0].lower()
                assert side in ("left", "right"), side
                what = line[0][len(side) :].title().replace(" ", "")
                mask = valueRecordFormatDict[what][0]
                class1, class2, value = (int(x) for x in line[1:4])
                rec2 = self.Class1Record[class1].Class2Record[class2]
                if side == "left":
                    self.ValueFormat1 |= mask
                    vr = rec2.Value1
                else:
                    self.ValueFormat2 |= mask
                    vr = rec2.Value2
                assert not hasattr(vr, what), (vr, what)
                setattr(vr, what, value)
            for rec1 in self.Class1Record:
                for rec2 in rec1.Class2Record:
                    rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
                    rec2.Value2 = (
                        ValueRecord(self.ValueFormat2, rec2.Value2)
                        if self.ValueFormat2
                        else None
                    )

            self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
        else:
            assert 0, typ
        return self

    def parseKernset(lines, font, _lookupMap=None):
        typ = lines.peeks()[0].split()[0].lower()
        if typ in ("left", "right"):
            with lines.until(
                ("firstclass definition begin", "secondclass definition begin")
            ):
                return parsePair(lines, font)
        return parsePair(lines, font)

    def makeAnchor(data, klass=ot.Anchor):
        assert len(data) <= 2
        anchor = klass()
        anchor.Format = 1
        anchor.XCoordinate, anchor.YCoordinate = intSplitComma(data[0])
        if len(data) > 1 and data[1] != "":
            anchor.Format = 2
            anchor.AnchorPoint = int(data[1])
        return anchor

    def parseCursive(lines, font, _lookupMap=None):
        records = {}
        for line in lines:
            assert len(line) in [3, 4], line
            idx, klass = {
                "entry": (0, ot.EntryAnchor),
                "exit": (1, ot.ExitAnchor),
            }[line[0]]
            glyph = makeGlyph(line[1])
            if glyph not in records:
                records[glyph] = [None, None]
            assert records[glyph][idx] is None, (glyph, idx)
            records[glyph][idx] = makeAnchor(line[2:], klass)
        return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())

    def makeMarkRecords(data, coverage, c):
        records = []
        for glyph in coverage.glyphs:
            klass, anchor = data[glyph]
            record = c.MarkRecordClass()
            record.Class = klass
            setattr(record, c.MarkAnchor, anchor)
            records.append(record)
        return records

    def makeBaseRecords(data, coverage, c, classCount):
        records = []
        idx = {}
        for glyph in coverage.glyphs:
            idx[glyph] = len(records)
            record = c.BaseRecordClass()
            anchors = [None] * classCount
            setattr(record, c.BaseAnchor, anchors)
            records.append(record)
        for (glyph, klass), anchor in data.items():
            record = records[idx[glyph]]
            anchors = getattr(record, c.BaseAnchor)
            assert anchors[klass] is None, (glyph, klass)
            anchors[klass] = anchor
        return records

    def makeLigatureRecords(data, coverage, c, classCount):
        records = [None] * len(coverage.glyphs)
        idx = {g: i for i, g in enumerate(coverage.glyphs)}

        for (glyph, klass, compIdx, compCount), anchor in data.items():
            record = records[idx[glyph]]
            if record is None:
                record = records[idx[glyph]] = ot.LigatureAttach()
                record.ComponentCount = compCount
                record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
                for compRec in record.ComponentRecord:
                    compRec.LigatureAnchor = [None] * classCount
            assert record.ComponentCount == compCount, (
                glyph,
                record.ComponentCount,
                compCount,
            )

            anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
            assert anchors[klass] is None, (glyph, compIdx, klass)
            anchors[klass] = anchor
        return records

    def parseMarkToSomething(lines, font, c):
        self = c.Type()
        self.Format = 1
        markData = {}
        baseData = {}
        Data = {
            "mark": (markData, c.MarkAnchorClass),
            "base": (baseData, c.BaseAnchorClass),
            "ligature": (baseData, c.BaseAnchorClass),
        }
        maxKlass = 0
        for line in lines:
            typ = line[0]
            assert typ in ("mark", "base", "ligature")
            glyph = makeGlyph(line[1])
            data, anchorClass = Data[typ]
            extraItems = 2 if typ == "ligature" else 0
            extras = tuple(int(i) for i in line[2 : 2 + extraItems])
            klass = int(line[2 + extraItems])
            anchor = makeAnchor(line[3 + extraItems :], anchorClass)
            if typ == "mark":
                key, value = glyph, (klass, anchor)
            else:
                key, value = ((glyph, klass) + extras), anchor
            assert key not in data, key
            data[key] = value
            maxKlass = max(maxKlass, klass)

        # Mark
        markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
        markArray = c.MarkArrayClass()
        markRecords = makeMarkRecords(markData, markCoverage, c)
        setattr(markArray, c.MarkRecord, markRecords)
        setattr(markArray, c.MarkCount, len(markRecords))
        setattr(self, c.MarkCoverage, markCoverage)
        setattr(self, c.MarkArray, markArray)
        self.ClassCount = maxKlass + 1

        # Base
        self.classCount = 0 if not baseData else 1 + max(k[1] for k, v in baseData.items())
        baseCoverage = makeCoverage(
            set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
        )
        baseArray = c.BaseArrayClass()
        if c.Base == "Ligature":
            baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
        else:
            baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
        setattr(baseArray, c.BaseRecord, baseRecords)
        setattr(baseArray, c.BaseCount, len(baseRecords))
        setattr(self, c.BaseCoverage, baseCoverage)
        setattr(self, c.BaseArray, baseArray)

        return self

    class MarkHelper(object):
        def __init__(self):
            for Which in ("Mark", "Base"):
                for What in ("Coverage", "Array", "Count", "Record", "Anchor"):
                    key = Which + What
                    if Which == "Mark" and What in ("Count", "Record", "Anchor"):
                        value = key
                    else:
                        value = getattr(self, Which) + What
                    if value == "LigatureRecord":
                        value = "LigatureAttach"
                    setattr(self, key, value)
                    if What != "Count":
                        klass = getattr(ot, value)
                        setattr(self, key + "Class", klass)

    class MarkToBaseHelper(MarkHelper):
        Mark = "Mark"
        Base = "Base"
        Type = ot.MarkBasePos

    class MarkToMarkHelper(MarkHelper):
        Mark = "Mark1"
        Base = "Mark2"
        Type = ot.MarkMarkPos

    class MarkToLigatureHelper(MarkHelper):
        Mark = "Mark"
        Base = "Ligature"
        Type = ot.MarkLigPos

    def parseMarkToBase(lines, font, _lookupMap=None):
        return parseMarkToSomething(lines, font, MarkToBaseHelper())

    def parseMarkToMark(lines, font, _lookupMap=None):
        return parseMarkToSomething(lines, font, MarkToMarkHelper())

    def parseMarkToLigature(lines, font, _lookupMap=None):
        return parseMarkToSomething(lines, font, MarkToLigatureHelper())

    def stripSplitComma(line):
        return [s.strip() for s in line.split(",")] if line else []

    def intSplitComma(line):
        return [int(i) for i in line.split(",")] if line else []

    # Copied from fontTools.subset

    class ContextHelper(object):
        def __init__(self, klassName, Format):
            if klassName.endswith("Subst"):
                Typ = "Sub"
                Type = "Subst"
            else:
                Typ = "Pos"
                Type = "Pos"
            if klassName.startswith("Chain"):
                Chain = "Chain"
                InputIdx = 1
                DataLen = 3
            else:
                Chain = ""
                InputIdx = 0
                DataLen = 1
            ChainTyp = Chain + Typ

            self.Typ = Typ
            self.Type = Type
            self.Chain = Chain
            self.ChainTyp = ChainTyp
            self.InputIdx = InputIdx
            self.DataLen = DataLen

            self.LookupRecord = Type + "LookupRecord"

            if Format == 1:
                Coverage = lambda r: r.Coverage
                ChainCoverage = lambda r: r.Coverage
                ContextData = lambda r: (None,)
                ChainContextData = lambda r: (None, None, None)
                SetContextData = None
                SetChainContextData = None
                RuleData = lambda r: (r.Input,)
                ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)

                def SetRuleData(r, d):
                    (r.Input,) = d
                    (r.GlyphCount,) = (len(x) + 1 for x in d)

                def ChainSetRuleData(r, d):
                    (r.Backtrack, r.Input, r.LookAhead) = d
                    (
                        r.BacktrackGlyphCount,
                        r.InputGlyphCount,
                        r.LookAheadGlyphCount,
                    ) = (len(d[0]), len(d[1]) + 1, len(d[2]))

            elif Format == 2:
                Coverage = lambda r: r.Coverage
                ChainCoverage = lambda r: r.Coverage
                ContextData = lambda r: (r.ClassDef,)
                ChainContextData = lambda r: (
                    r.BacktrackClassDef,
                    r.InputClassDef,
                    r.LookAheadClassDef,
                )

                def SetContextData(r, d):
                    (r.ClassDef,) = d

                def SetChainContextData(r, d):
                    (r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d

                RuleData = lambda r: (r.Class,)
                ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)

                def SetRuleData(r, d):
                    (r.Class,) = d
                    (r.GlyphCount,) = (len(x) + 1 for x in d)

                def ChainSetRuleData(r, d):
                    (r.Backtrack, r.Input, r.LookAhead) = d
                    (
                        r.BacktrackGlyphCount,
                        r.InputGlyphCount,
                        r.LookAheadGlyphCount,
                    ) = (len(d[0]), len(d[1]) + 1, len(d[2]))

            elif Format == 3:
                Coverage = lambda r: r.Coverage[0]
                ChainCoverage = lambda r: r.InputCoverage[0]
                ContextData = None
                ChainContextData = None
                SetContextData = None
                SetChainContextData = None
                RuleData = lambda r: r.Coverage
                ChainRuleData = lambda r: (
                    r.BacktrackCoverage + r.InputCoverage + r.LookAheadCoverage
                )

                def SetRuleData(r, d):
                    (r.Coverage,) = d
                    (r.GlyphCount,) = (len(x) for x in d)

                def ChainSetRuleData(r, d):
                    (r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
                    (
                        r.BacktrackGlyphCount,
                        r.InputGlyphCount,
                        r.LookAheadGlyphCount,
                    ) = (len(x) for x in d)

            else:
                assert 0, "unknown format: %s" % Format

            if Chain:
                self.Coverage = ChainCoverage
                self.ContextData = ChainContextData
                self.SetContextData = SetChainContextData
                self.RuleData = ChainRuleData
                self.SetRuleData = ChainSetRuleData
            else:
                self.Coverage = Coverage
                self.ContextData = ContextData
                self.SetContextData = SetContextData
                self.RuleData = RuleData
                self.SetRuleData = SetRuleData

            if Format == 1:
                self.Rule = ChainTyp + "Rule"
                self.RuleCount = ChainTyp + "RuleCount"
                self.RuleSet = ChainTyp + "RuleSet"
                self.RuleSetCount = ChainTyp + "RuleSetCount"
                self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
            elif Format == 2:
                self.Rule = ChainTyp + "ClassRule"
                self.RuleCount = ChainTyp + "ClassRuleCount"
                self.RuleSet = ChainTyp + "ClassSet"
                self.RuleSetCount = ChainTyp + "ClassSetCount"
                self.Intersect = lambda glyphs, c, r: (
                    c.intersect_class(glyphs, r)
                    if c
                    else (set(glyphs) if r == 0 else set())
                )

                self.ClassDef = "InputClassDef" if Chain else "ClassDef"
                self.ClassDefIndex = 1 if Chain else 0
                self.Input = "Input" if Chain else "Class"

    def parseLookupRecords(items, klassName, lookupMap=None):
        klass = getattr(ot, klassName)
        lst = []
        for item in items:
            rec = klass()
            item = stripSplitComma(item)
            assert len(item) == 2, item
            idx = int(item[0])
            assert idx > 0, idx
            rec.SequenceIndex = idx - 1
            setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
            lst.append(rec)
        return lst

    def makeClassDef(classDefs, font, klass=ot.Coverage):
        if not classDefs:
            return None
        self = klass()
        self.classDefs = dict(classDefs)
        return self

    def parseClassDef(lines, font, klass=ot.ClassDef):
        classDefs = {}
        with lines.between("class definition"):
            for line in lines:
                glyph = makeGlyph(line[0])
                assert glyph not in classDefs, glyph
                classDefs[glyph] = int(line[1])
        return makeClassDef(classDefs, font, klass)

    def makeCoverage(glyphs, font, klass=ot.Coverage):
        if not glyphs:
            return None
        if isinstance(glyphs, set):
            glyphs = sorted(glyphs)
        coverage = klass()
        coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
        return coverage

    def parseCoverage(lines, font, klass=ot.Coverage):
        glyphs = []
        with lines.between("coverage definition"):
            for line in lines:
                glyphs.append(makeGlyph(line[0]))
        return makeCoverage(glyphs, font, klass)

    def bucketizeRules(self, c, rules, bucketKeys):
        buckets = {}
        for seq, recs in rules:
            buckets.setdefault(seq[c.InputIdx][0], []).append(
                (tuple(s[1 if i == c.InputIdx else 0 :] for i, s in enumerate(seq)), recs)
            )

        rulesets = []
        for firstGlyph in bucketKeys:
            if firstGlyph not in buckets:
                rulesets.append(None)
                continue
            thisRules = []
            for seq, recs in buckets[firstGlyph]:
                rule = getattr(ot, c.Rule)()
                c.SetRuleData(rule, seq)
                setattr(rule, c.Type + "Count", len(recs))
                setattr(rule, c.LookupRecord, recs)
                thisRules.append(rule)

            ruleset = getattr(ot, c.RuleSet)()
            setattr(ruleset, c.Rule, thisRules)
            setattr(ruleset, c.RuleCount, len(thisRules))
            rulesets.append(ruleset)

        setattr(self, c.RuleSet, rulesets)
        setattr(self, c.RuleSetCount, len(rulesets))

    def parseContext(lines, font, Type, lookupMap=None):
        self = getattr(ot, Type)()
        typ = lines.peeks()[0].split()[0].lower()
        if typ == "glyph":
            self.Format = 1
            log.debug("Parsing %s format %s", Type, self.Format)
            c = ContextHelper(Type, self.Format)
            rules = []
            for line in lines:
                assert line[0].lower() == "glyph", line[0]
                while len(line) < 1 + c.DataLen:
                    line.append("")
                seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1 : 1 + c.DataLen])
                recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
                rules.append((seq, recs))

            firstGlyphs = set(seq[c.InputIdx][0] for seq, recs in rules)
            self.Coverage = makeCoverage(firstGlyphs, font)
            bucketizeRules(self, c, rules, self.Coverage.glyphs)
        elif typ.endswith("class"):
            self.Format = 2
            log.debug("Parsing %s format %s", Type, self.Format)
            c = ContextHelper(Type, self.Format)
            classDefs = [None] * c.DataLen
            while lines.peeks()[0].endswith("class definition begin"):
                typ = lines.peek()[0][: -len("class definition begin")].lower()
                idx, klass = {
                    1: {
                        "": (0, ot.ClassDef),
                    },
                    3: {
                        "backtrack": (0, ot.BacktrackClassDef),
                        "": (1, ot.InputClassDef),
                        "lookahead": (2, ot.LookAheadClassDef),
                    },
                }[c.DataLen][typ]
                assert classDefs[idx] is None, idx
                classDefs[idx] = parseClassDef(lines, font, klass=klass)
            c.SetContextData(self, classDefs)
            rules = []
            for line in lines:
                assert line[0].lower().startswith("class"), line[0]
                while len(line) < 1 + c.DataLen:
                    line.append("")
                seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
                recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
                rules.append((seq, recs))
            firstClasses = set(seq[c.InputIdx][0] for seq, recs in rules)
            firstGlyphs = set(
                g for g, c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses
            )
            self.Coverage = makeCoverage(firstGlyphs, font)
            bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
        elif typ.endswith("coverage"):
            self.Format = 3
            log.debug("Parsing %s format %s", Type, self.Format)
            c = ContextHelper(Type, self.Format)
            coverages = tuple([] for i in range(c.DataLen))
            while lines.peeks()[0].endswith("coverage definition begin"):
                typ = lines.peek()[0][: -len("coverage definition begin")].lower()
                idx, klass = {
                    1: {
                        "": (0, ot.Coverage),
                    },
                    3: {
                        "backtrack": (0, ot.BacktrackCoverage),
                        "input": (1, ot.InputCoverage),
                        "lookahead": (2, ot.LookAheadCoverage),
                    },
                }[c.DataLen][typ]
                coverages[idx].append(parseCoverage(lines, font, klass=klass))
            c.SetRuleData(self, coverages)
            lines = list(lines)
            assert len(lines) == 1
            line = lines[0]
            assert line[0].lower() == "coverage", line[0]
            recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
            setattr(self, c.Type + "Count", len(recs))
            setattr(self, c.LookupRecord, recs)
        else:
            assert 0, typ
        return self

    def parseContextSubst(lines, font, lookupMap=None):
        return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)

    def parseContextPos(lines, font, lookupMap=None):
        return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)

    def parseChainedSubst(lines, font, lookupMap=None):
        return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)

    def parseChainedPos(lines, font, lookupMap=None):
        return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)

    def parseReverseChainedSubst(lines, font, _lookupMap=None):
        self = ot.ReverseChainSingleSubst()
        self.Format = 1
        coverages = ([], [])
        while lines.peeks()[0].endswith("coverage definition begin"):
            typ = lines.peek()[0][: -len("coverage definition begin")].lower()
            idx, klass = {
                "backtrack": (0, ot.BacktrackCoverage),
                "lookahead": (1, ot.LookAheadCoverage),
            }[typ]
            coverages[idx].append(parseCoverage(lines, font, klass=klass))
        self.BacktrackCoverage = coverages[0]
        self.BacktrackGlyphCount = len(self.BacktrackCoverage)
        self.LookAheadCoverage = coverages[1]
        self.LookAheadGlyphCount = len(self.LookAheadCoverage)
        mapping = {}
        for line in lines:
            assert len(line) == 2, line
            line = makeGlyphs(line)
            mapping[line[0]] = line[1]
        self.Coverage = makeCoverage(set(mapping.keys()), font)
        self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
        self.GlyphCount = len(self.Substitute)
        return self

    def parseLookup(lines, tableTag, font, lookupMap=None):
        line = lines.expect("lookup")
        _, name, typ = line
        log.debug("Parsing lookup type %s %s", typ, name)
        lookup = ot.Lookup()
        lookup.LookupFlag, filterset = parseLookupFlags(lines)
        if filterset is not None:
            lookup.MarkFilteringSet = filterset
        lookup.LookupType, parseLookupSubTable = {
            "GSUB": {
                "single": (1, parseSingleSubst),
                "multiple": (2, parseMultiple),
                "alternate": (3, parseAlternate),
                "ligature": (4, parseLigature),
                "context": (5, parseContextSubst),
                "chained": (6, parseChainedSubst),
                "reversechained": (8, parseReverseChainedSubst),
            },
            "GPOS": {
                "single": (1, parseSinglePos),
                "pair": (2, parsePair),
                "kernset": (2, parseKernset),
                "cursive": (3, parseCursive),
                "mark to base": (4, parseMarkToBase),
                "mark to ligature": (5, parseMarkToLigature),
                "mark to mark": (6, parseMarkToMark),
                "context": (7, parseContextPos),
                "chained": (8, parseChainedPos),
            },
        }[tableTag][typ]

        with lines.until("lookup end"):
            subtables = []

            while lines.peek():
                with lines.until(("% subtable", "subtable end")):
                    while lines.peek():
                        subtable = parseLookupSubTable(lines, font, lookupMap)
                        assert lookup.LookupType == subtable.LookupType
                        subtables.append(subtable)
                if lines.peeks()[0] in ("% subtable", "subtable end"):
                    next(lines)
        lines.expect("lookup end")

        lookup.SubTable = subtables
        lookup.SubTableCount = len(lookup.SubTable)
        if lookup.SubTableCount == 0:
            # Remove this return when following is fixed:
            # https://github.com/fonttools/fonttools/issues/789
            return None
        return lookup

    def parseGSUBGPOS(lines, font, tableTag):
        container = ttLib.getTableClass(tableTag)()
        lookupMap = DeferredMapping()
        featureMap = DeferredMapping()
        assert tableTag in ("GSUB", "GPOS")
        log.debug("Parsing %s", tableTag)
        self = getattr(ot, tableTag)()
        self.Version = 0x00010000
        fields = {
            "script table begin": (
                "ScriptList",
                lambda lines: parseScriptList(lines, featureMap),
            ),
            "feature table begin": (
                "FeatureList",
                lambda lines: parseFeatureList(lines, lookupMap, featureMap),
            ),
            "lookup": ("LookupList", None),
        }
        for attr, parser in fields.values():
            setattr(self, attr, None)
        while lines.peek() is not None:
            typ = lines.peek()[0].lower()
            if typ not in fields:
                log.debug("Skipping %s", lines.peek())
                next(lines)
                continue
            attr, parser = fields[typ]
            if typ == "lookup":
                if self.LookupList is None:
                    self.LookupList = ot.LookupList()
                    self.LookupList.Lookup = []
                _, name, _ = lines.peek()
                lookup = parseLookup(lines, tableTag, font, lookupMap)
                if lookupMap is not None:
                    assert name not in lookupMap, "Duplicate lookup name: %s" % name
                    lookupMap[name] = len(self.LookupList.Lookup)
                else:
                    assert int(name) == len(self.LookupList.Lookup), "%d %d" % (
                        name,
                        len(self.Lookup),
                    )
                self.LookupList.Lookup.append(lookup)
            else:
                assert getattr(self, attr) is None, attr
                setattr(self, attr, parser(lines))
        if self.LookupList:
            self.LookupList.LookupCount = len(self.LookupList.Lookup)
        if lookupMap is not None:
            lookupMap.applyDeferredMappings()
            if os.environ.get(LOOKUP_DEBUG_ENV_VAR):
                if "Debg" not in font:
                    font["Debg"] = newTable("Debg")
                    font["Debg"].data = {}
                debug = (
                    font["Debg"]
                    .data.setdefault(LOOKUP_DEBUG_INFO_KEY, {})
                    .setdefault(tableTag, {})
                )
                for name, lookup in lookupMap.items():
                    debug[str(lookup)] = ["", name, ""]

            featureMap.applyDeferredMappings()
        container.table = self
        return container

    def parseGSUB(lines, font):
        return parseGSUBGPOS(lines, font, "GSUB")

    def parseGPOS(lines, font):
        return parseGSUBGPOS(lines, font, "GPOS")

    def parseAttachList(lines, font):
        points = {}
        with lines.between("attachment list"):
            for line in lines:
                glyph = makeGlyph(line[0])
                assert glyph not in points, glyph
                points[glyph] = [int(i) for i in line[1:]]
        return otl.buildAttachList(points, font.getReverseGlyphMap())

    def parseCaretList(lines, font):
        carets = {}
        with lines.between("carets"):
            for line in lines:
                glyph = makeGlyph(line[0])
                assert glyph not in carets, glyph
                num = int(line[1])
                thisCarets = [int(i) for i in line[2:]]
                assert num == len(thisCarets), line
                carets[glyph] = thisCarets
        return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())

    def makeMarkFilteringSets(sets, font):
        self = ot.MarkGlyphSetsDef()
        self.MarkSetTableFormat = 1
        self.MarkSetCount = 1 + max(sets.keys())
        self.Coverage = [None] * self.MarkSetCount
        for k, v in sorted(sets.items()):
            self.Coverage[k] = makeCoverage(set(v), font)
        return self

    def parseMarkFilteringSets(lines, font):
        sets = {}
        with lines.between("set definition"):
            for line in lines:
                assert len(line) == 2, line
                glyph = makeGlyph(line[0])
                # TODO accept set names
                st = int(line[1])
                if st not in sets:
                    sets[st] = []
                sets[st].append(glyph)
        return makeMarkFilteringSets(sets, font)

    def parseGDEF(lines, font):
        container = ttLib.getTableClass("GDEF")()
        log.debug("Parsing GDEF")
        self = ot.GDEF()
        fields = {
            "class definition begin": (
                "GlyphClassDef",
                lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef),
            ),
            "attachment list begin": ("AttachList", parseAttachList),
            "carets begin": ("LigCaretList", parseCaretList),
            "mark attachment class definition begin": (
                "MarkAttachClassDef",
                lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef),
            ),
            "markfilter set definition begin": ("MarkGlyphSetsDef", parseMarkFilteringSets),
        }
        for attr, parser in fields.values():
            setattr(self, attr, None)
        while lines.peek() is not None:
            typ = lines.peek()[0].lower()
            if typ not in fields:
                log.debug("Skipping %s", typ)
                next(lines)
                continue
            attr, parser = fields[typ]
            assert getattr(self, attr) is None, attr
            setattr(self, attr, parser(lines, font))
        self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
        container.table = self
        return container

    def parseCmap(lines, font):
        container = ttLib.getTableClass("cmap")()
        log.debug("Parsing cmap")
        tables = []
        while lines.peek() is not None:
            lines.expect("cmap subtable %d" % len(tables))
            platId, encId, fmt, lang = [
                parseCmapId(lines, field)
                for field in ("platformID", "encodingID", "format", "language")
            ]
            table = cmap_classes[fmt](fmt)
            table.platformID = platId
            table.platEncID = encId
            table.language = lang
            table.cmap = {}
            line = next(lines)
            while line[0] != "end subtable":
                table.cmap[int(line[0], 16)] = line[1]
                line = next(lines)
            tables.append(table)
        container.tableVersion = 0
        container.tables = tables
        return container

    def parseCmapId(lines, field):
        line = next(lines)
        assert field == line[0]
        return int(line[1])

    def parseTable(lines, font, tableTag=None):
        log.debug("Parsing table")
        line = lines.peeks()
        tag = None
        if line[0].split()[0] == "FontDame":
            tag = line[0].split()[1]
        elif " ".join(line[0].split()[:3]) == "Font Chef Table":
            tag = line[0].split()[3]
        if tag is not None:
            next(lines)
            tag = tag.ljust(4)
            if tableTag is None:
                tableTag = tag
            else:
                assert tableTag == tag, (tableTag, tag)

        assert (
            tableTag is not None
        ), "Don't know what table to parse and data doesn't specify"

        return {
            "GSUB": parseGSUB,
            "GPOS": parseGPOS,
            "GDEF": parseGDEF,
            "cmap": parseCmap,
        }[tableTag](lines, font)

    class Tokenizer(object):
        def __init__(self, f):
            # TODO BytesIO / StringIO as needed?  also, figure out whether we work on bytes or unicode
            lines = iter(f)
            try:
                self.filename = f.name
            except:
                self.filename = None
            self.lines = iter(lines)
            self.line = ""
            self.lineno = 0
            self.stoppers = []
            self.buffer = None

        def __iter__(self):
            return self

        def _next_line(self):
            self.lineno += 1
            line = self.line = next(self.lines)
            line = [s.strip() for s in line.split("\t")]
            if len(line) == 1 and not line[0]:
                del line[0]
            if line and not line[-1]:
                log.warning("trailing tab found on line %d: %s" % (self.lineno, self.line))
                while line and not line[-1]:
                    del line[-1]
            return line

        def _next_nonempty(self):
            while True:
                line = self._next_line()
                # Skip comments and empty lines
                if line and line[0] and (line[0][0] != "%" or line[0] == "% subtable"):
                    return line

        def _next_buffered(self):
            if self.buffer:
                ret = self.buffer
                self.buffer = None
                return ret
            else:
                return self._next_nonempty()

        def __next__(self):
            line = self._next_buffered()
            if line[0].lower() in self.stoppers:
                self.buffer = line
                raise StopIteration
            return line

        def next(self):
            return self.__next__()

        def peek(self):
            if not self.buffer:
                try:
                    self.buffer = self._next_nonempty()
                except StopIteration:
                    return None
            if self.buffer[0].lower() in self.stoppers:
                return None
            return self.buffer

        def peeks(self):
            ret = self.peek()
            return ret if ret is not None else ("",)

        @contextmanager
        def between(self, tag):
            start = tag + " begin"
            end = tag + " end"
            self.expectendswith(start)
            self.stoppers.append(end)
            yield
            del self.stoppers[-1]
            self.expect(tag + " end")

        @contextmanager
        def until(self, tags):
            if type(tags) is not tuple:
                tags = (tags,)
            self.stoppers.extend(tags)
            yield
            del self.stoppers[-len(tags) :]

        def expect(self, s):
            line = next(self)
            tag = line[0].lower()
            assert tag == s, "Expected '%s', got '%s'" % (s, tag)
            return line

        def expectendswith(self, s):
            line = next(self)
            tag = line[0].lower()
            assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
            return line

    def build(f, font, tableTag=None):
        """Convert a Monotype font layout file to an OpenType layout object

        A font object must be passed, but this may be a "dummy" font; it is only
        used for sorting glyph sets when making coverage tables and to hold the
        OpenType layout table while it is being built.

        Args:
                f: A file object.
                font (TTFont): A font object.
                tableTag (string): If provided, asserts that the file contains data for the
                        given OpenType table.

        Returns:
                An object representing the table. (e.g. ``table_G_S_U_B_``)
        """
        lines = Tokenizer(f)
        return parseTable(lines, font, tableTag=tableTag)

    def main(args=None, font=None):
        """Convert a FontDame OTL file to TTX XML

        Writes XML output to stdout.

        Args:
                args: Command line arguments (``--font``, ``--table``, input files).
        """
        import sys
        from fontTools import configLogger
        from fontTools.misc.testTools import MockFont

        if args is None:
            args = sys.argv[1:]

        # configure the library logger (for >= WARNING)
        configLogger()
        # comment this out to enable debug messages from mtiLib's logger
        # log.setLevel(logging.DEBUG)

        import argparse

        parser = argparse.ArgumentParser(
            "fonttools mtiLib",
            description=main.__doc__,
        )

        parser.add_argument(
            "--font",
            "-f",
            metavar="FILE",
            dest="font",
            help="Input TTF files (used for glyph classes and sorting coverage tables)",
        )
        parser.add_argument(
            "--table",
            "-t",
            metavar="TABLE",
            dest="tableTag",
            help="Table to fill (sniffed from input file if not provided)",
        )
        parser.add_argument(
            "inputs", metavar="FILE", type=str, nargs="+", help="Input FontDame .txt files"
        )

        args = parser.parse_args(args)

        if font is None:
            if args.font:
                font = ttLib.TTFont(args.font)
            else:
                font = MockFont()

        for f in args.inputs:
            log.debug("Processing %s", f)
            with open(f, "rt", encoding="utf-8") as f:
                table = build(f, font, tableTag=args.tableTag)
            blob = table.compile(font)  # Make sure it compiles
            decompiled = table.__class__()
            decompiled.decompile(blob, font)  # Make sure it decompiles!

            # continue
            from fontTools.misc import xmlWriter

            tag = table.tableTag
            writer = xmlWriter.XMLWriter(sys.stdout)
            writer.begintag(tag)
            writer.newline()
            # table.toXML(writer, font)
            decompiled.toXML(writer, font)
            writer.endtag(tag)
            writer.newline()

    if __name__ == "__main__":
        import sys

        sys.exit(main())