Esempio n. 1
0
 def unparseSingleSubstitution(self, lookup):
     b = fontFeatures.Routine(
         name=self.getname("SingleSubstitution" + self.gensym())
     )
     self._fix_flags(b, lookup)
     for sub in lookup.SubTable:
         if len(sub.mapping) > 5:
             k = list(sub.mapping.keys())
             v = list(sub.mapping.values())
             b.addRule(
                 fontFeatures.Substitution(
                     [k], [v], address=self.currentLookup, flags=lookup.LookupFlag
                 )
             )
         else:
             for k, v in sub.mapping.items():
                 b.addRule(
                     fontFeatures.Substitution(
                         [[k]],
                         [[v]],
                         address=self.currentLookup,
                         flags=lookup.LookupFlag,
                     )
                 )
     return b, []
Esempio n. 2
0
    def action(self, args):
        (pre, l, r, post) = args[0]
        left = l.resolve(self.parser.fontfeatures, self.parser.font)
        right = r.resolve(self.parser.fontfeatures, self.parser.font)
        precontext = None
        postcontext = None
        if pre:
            precontext = [
                g.resolve(self.parser.fontfeatures, self.parser.font)
                for g in pre
            ]
        if post:
            postcontext = [
                g.resolve(self.parser.fontfeatures, self.parser.font)
                for g in post
            ]

        lefttoright = fontFeatures.Routine(
            rules=[fontFeatures.Substitution([left], [right])])
        righttoleft = fontFeatures.Routine(
            rules=[fontFeatures.Substitution([right], [left])])
        return [
            fontFeatures.Chaining(
                [left, right],
                lookups=[[lefttoright], [righttoleft]],
                precontext=precontext,
                postcontext=postcontext,
            )
        ]
Esempio n. 3
0
    def action(self, args):
        (l, r, languages, pre, post) = args[0]

        inputs = [
            g.resolve(self.parser.fontfeatures, self.parser.font) for g in l
        ]
        pre = [
            g.resolve(self.parser.fontfeatures, self.parser.font) for g in pre
        ]
        post = [
            g.resolve(self.parser.fontfeatures, self.parser.font) for g in post
        ]
        for ix, output in enumerate(r):
            if isinstance(output, dict):
                r[ix] = l[output["reference"] - 1]
                if "suffixes" in output:
                    r[ix].suffixes = output["suffixes"]
        outputs = [
            g.resolve(self.parser.fontfeatures, self.parser.font) for g in r
        ]
        return [
            fontFeatures.Substitution(inputs,
                                      outputs,
                                      precontext=pre,
                                      postcontext=post,
                                      languages=languages)
        ]
Esempio n. 4
0
    def action(self, args):
        parser = self.parser
        (bases, matra, matras) = args
        bases = bases.resolve(parser.fontfeatures, parser.font)
        matra = matra.resolve(parser.fontfeatures, parser.font)
        matras = matras.resolve(parser.fontfeatures, parser.font)

        # Organise matras into overhang widths
        matras2bases = {}
        matrasAndOverhangs = [(m, -parser.font[m].rightMargin) for m in matras]
        for b in bases:
            w = self.find_stem(parser.font, b)
            warnings.warn("Stem location for %s: %s" % (b, w))
            (bestMatra, _) = min(matrasAndOverhangs,
                                 key=lambda s: abs(s[1] - w))
            if not bestMatra in matras2bases:
                matras2bases[bestMatra] = []
            matras2bases[bestMatra].append(b)
        rv = []
        for bestMatra, basesForThisMatra in matras2bases.items():
            rv.append(
                fontFeatures.Substitution([matra],
                                          postcontext=[basesForThisMatra],
                                          replacement=[[bestMatra]]))
        return [fontFeatures.Routine(rules=rv)]
Esempio n. 5
0
 def unparseReverseContextualSubstitution(self, lookup):
     b = fontFeatures.Routine(
         name=self.getname("ReverseContextualSubstitution" + self.gensym())
     )
     self._fix_flags(b, lookup)
     for sub in lookup.SubTable:
         prefix  = []
         outputs = []
         suffix  = []
         if hasattr(sub, "BacktrackCoverage"):
             for coverage in reversed(sub.BacktrackCoverage):
                 prefix.append(coverage.glyphs)
         if hasattr(sub, "LookAheadCoverage"):
             for i, coverage in enumerate(sub.LookAheadCoverage):
                 suffix.append(coverage.glyphs)
         outputs = [ sub.Substitute ]
         inputs =  [ sub.Coverage.glyphs ]
         b.addRule(
             fontFeatures.Substitution(
                 inputs,
                 outputs,
                 prefix,
                 suffix,
                 flags=lookup.LookupFlag,
                 reverse=True
             )
         )
     return b,[]
Esempio n. 6
0
 def apply(self):
     right = self.matches()
     left = self.transform(right)
     left2, right2 = [], []
     routine = fontFeatures.Routine(name=self.name.title().replace(" ", ""))
     for l, r in zip(left, right):
         if l in self.glyphnames and r in self.glyphnames:
             routine.addRule(fontFeatures.Substitution([[l]], [[r]]))
     self.project.fontfeatures.routines.extend([routine])
     self.project.fontfeatures.addFeature(self.feature, [routine])
Esempio n. 7
0
 def add_alternate_subst(self, location, prefix, glyph, suffix,
                         replacement):
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(
         input_=[[glyph]],
         replacement=[replacement],
         precontext=prefix,
         postcontext=suffix,
         address=location,
     )
     self.currentRoutine.addRule(s)
Esempio n. 8
0
 def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
     self._start_routine_if_necessary(location)
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(
         input_=[list(mapping.keys())],
         replacement=[list(mapping.values())],
         precontext=prefix,
         postcontext=suffix,
         address=location,
     )
     self.currentRoutine.addRule(s)
Esempio n. 9
0
 def add_ligature_subst(self, location, prefix, glyphs, suffix, replacement,
                        forceChain):
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(
         input_=[list(x) for x in glyphs],
         replacement=[[replacement]],
         precontext=prefix,
         postcontext=suffix,
         address=location,
     )
     self.currentRoutine.addRule(s)
Esempio n. 10
0
    def action(self, parser):
        glyphs = FontProxy(parser.font).glyphs
        rules = []
        for g in glyphs:
            m = re.match(self.ligature, g)
            if not m:
                continue
            first, second, position = m[1], m[2], m[3]
            if position == "u":
                first = first + "i1"
                second = second + "f1"
                rules.append(
                    fontFeatures.Substitution([[first], [second]], [[g]]))
            if position == "f":
                first = first + "m1"
                second = second + "f1"
                rules.append(
                    fontFeatures.Substitution([[first], [second]], [[g]]))

        return [fontFeatures.Routine(name="Ligatures", rules=rules, flags=0x8)]
Esempio n. 11
0
 def apply(self):
     right = self.matches()
     routine = fontFeatures.Routine(name=self.name.title().replace(" ", ""))
     for r in right:
         left = r.replace(".liga","").split("_")
         if r == "fl" or r == "fi":
             left = list(r)
         if all(l in self.glyphnames for l in left) and r in self.glyphnames:
             routine.addRule(fontFeatures.Substitution([ [l] for l in left], [[r]]))
     self.project.fontfeatures.routines.extend([routine])
     self.project.fontfeatures.addFeature(self.feature, [routine])
Esempio n. 12
0
 def add_multiple_subst(self, location, prefix, glyph, suffix, replacements,
                        forceChain):
     self._start_routine_if_necessary(location)
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(
         input_=[[glyph]],
         replacement=[[g] for g in replacements],
         precontext=prefix,
         postcontext=suffix,
         address=location,
     )
     self.currentRoutine.addRule(s)
Esempio n. 13
0
 def add_ligature_subst(self, location, prefix, glyphs, suffix, replacement,
                        forceChain):
     self._start_routine_if_necessary(location)
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(input_=[list(x) for x in glyphs],
                                   replacement=[[replacement]],
                                   precontext=[[str(g) for g in group]
                                               for group in prefix],
                                   postcontext=[[str(g) for g in group]
                                                for group in suffix],
                                   address=location,
                                   languages=self.currentLanguage)
     self.currentRoutine.addRule(s)
Esempio n. 14
0
 def add_alternate_subst(self, location, prefix, glyph, suffix,
                         replacement):
     self._start_routine_if_necessary(location)
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(input_=[[glyph]],
                                   replacement=[replacement],
                                   precontext=[[str(g) for g in group]
                                               for group in prefix],
                                   postcontext=[[str(g) for g in group]
                                                for group in suffix],
                                   address=location,
                                   languages=self.currentLanguage)
     self.currentRoutine.addRule(s)
Esempio n. 15
0
 def add_reverse_chain_single_subst(self, location, prefix, suffix,
                                    mapping):
     self._start_routine_if_necessary(location)
     location = "%s:%i:%i" % (location)
     s = fontFeatures.Substitution(input_=[list(mapping.keys())],
                                   replacement=[list(mapping.values())],
                                   precontext=[[str(g) for g in group]
                                               for group in prefix],
                                   postcontext=[[str(g) for g in group]
                                                for group in suffix],
                                   address=location,
                                   languages=self.currentLanguage,
                                   reverse=True)
     self.currentRoutine.addRule(s)
Esempio n. 16
0
 def unparseSingleSubstitution(self, lookup):
     """Turn a GPOS1 (single substitution) subtable into a fontFeatures Routine."""
     b = fontFeatures.Routine(name=self.getname("SingleSubstitution" +
                                                self.gensym()))
     self._fix_flags(b, lookup)
     for sub in lookup.SubTable:
         for k, v in sub.mapping.items():
             b.addRule(
                 fontFeatures.Substitution(
                     [[k]],
                     [[v]],
                     address=self.currentLookup,
                     flags=lookup.LookupFlag,
                 ))
     return b, []
Esempio n. 17
0
    def unparseMultipleSubstitution(self, lookup):
        """Turn a GPOS2 (multiple substitution) subtable into a fontFeatures Routine."""
        b = fontFeatures.Routine(name=self.getname("MultipleSubstitution" +
                                                   self.gensym()))
        self._fix_flags(b, lookup)

        for sub in lookup.SubTable:
            for in_glyph, out_glyphs in sub.mapping.items():
                b.addRule(
                    fontFeatures.Substitution(
                        singleglyph(in_glyph),
                        [glyph(x) for x in out_glyphs],
                        address=self.currentLookup,
                        flags=lookup.LookupFlag,
                    ))
        return b, []
Esempio n. 18
0
 def unparseAlternateSubstitution(self, lookup):
     b = fontFeatures.Routine(
         name=self.getname("AlternateSubstitution" + self.gensym())
     )
     self._fix_flags(b, lookup)
     for sub in lookup.SubTable:
         for in_glyph, out_glyphs in sub.alternates.items():
             b.addRule(
                 fontFeatures.Substitution(
                     singleglyph(in_glyph),
                     [out_glyphs],
                     address=self.currentLookup,
                     flags=lookup.LookupFlag,
                 )
             )
     return b, []
Esempio n. 19
0
 def action(cls, parser, ligatures):
     ligatures = sorted(ligatures.resolve(parser.fontfeatures, parser.font),
                        key=lambda a: len(a))
     rv = []
     glyphnames = "|".join(sorted(parser.font.keys(), key=lambda a: len(a)))
     for l in ligatures:
         for liglen in range(5, 0, -1):
             ligre = "^" + ("(" + glyphnames +
                            ")_") * liglen + "(" + glyphnames + ")$"
             m = re.match(ligre, l)
             if m:
                 rv.append(
                     fontFeatures.Substitution([[c] for c in m.groups()],
                                               replacement=[[l]]))
                 break
     return [fontFeatures.Routine(rules=rv)]
Esempio n. 20
0
    def action(self, parser, filename):
        rules = {}
        reachable = set([])
        basedir = os.path.dirname(parser.current_file)
        trypath = os.path.join(basedir, filename)

        if not os.path.exists(trypath):
            trypath = filename
            if not os.path.exists(trypath):
                raise ValueError("Couldn't find connections file %s" % trypath)

        with open(trypath) as csvfile:
            reader = csv.DictReader(csvfile)
            for line in reader:
                left_glyph = line["Left Glyph"]
                remainder = list(line.items())[1:]
                for (g, v) in remainder:
                    old = g + "1"
                    if v == "1" or v == 1 or not v:
                        continue
                    replacement = g + str(v)
                    if not old in rules:
                        rules[old] = {}
                    if not replacement in rules[old]:
                        rules[old][replacement] = []
                    rules[old][replacement].append(left_glyph)

        r = fontFeatures.Routine(name="connections", flags=0x8)
        for oldglyph in rules:
            for replacement in rules[oldglyph]:
                context = rules[oldglyph][replacement]
                reachable |= set(context)
                reachable |= set([oldglyph, replacement])
                r.addRule(
                    fontFeatures.Substitution(
                        [[oldglyph]],
                        [[replacement]],
                        postcontext=[context],
                        reverse=True,
                    ))
        parser.fontfeatures.namedClasses["reachable_glyphs"] = tuple(
            sorted(reachable))

        return [r]
Esempio n. 21
0
 def unparseLigatureSubstitution(self, lookup):
     """Turn a GPOS4 (ligature substitution) subtable into a fontFeatures Routine."""
     b = fontFeatures.Routine(name=self.getname("LigatureSubstitution" +
                                                self.gensym()))
     self._fix_flags(b, lookup)
     for sub in lookup.SubTable:
         for first, ligatures in sub.ligatures.items():
             for lig in ligatures:
                 substarray = [glyph(first)]
                 for x in lig.Component:
                     substarray.append(glyph(x))
                 b.addRule(
                     fontFeatures.Substitution(
                         substarray,
                         singleglyph(lig.LigGlyph),
                         address=self.currentLookup,
                         flags=lookup.LookupFlag,
                     ))
     return b, []
Esempio n. 22
0
    def action(self, args):
        parser = self.parser
        bases = args[0].resolve(parser.fontfeatures, parser.font)
        medialra = args[1].resolve(parser.fontfeatures, parser.font)
        if len(args) == 3:
            otherras = args[2].resolve(parser.fontfeatures, parser.font)
            overshoot = 0
        else:
            overshoot = args[2]
            otherras = args[3].resolve(parser.fontfeatures, parser.font)

        # Chuck the original ra in the basket
        if overshoot is None:
            overshoot = 0
        ras = set(otherras + medialra)

        # Organise ras into overhang widths
        ras2bases = {}
        rasAndOverhangs = [(m, -parser.font[m].rightMargin + overshoot)
                           for m in ras]
        rasAndOverhangs = list(reversed(sorted(rasAndOverhangs)))

        for b in bases:
            w = parser.font[b].width - ((parser.font[b].rightMargin or 0) +
                                        (parser.font[b].leftMargin or 0))
            bestRa = None
            for ra, overhang in rasAndOverhangs:
                if overhang <= w:
                    bestRa = ra
                    break
            if not bestRa:
                continue
            if bestRa not in ras2bases:
                ras2bases[bestRa] = []
            ras2bases[bestRa].append(b)
        rv = []
        for bestRa, basesForThisRa in ras2bases.items():
            rv.append(
                fontFeatures.Substitution([medialra],
                                          postcontext=[basesForThisRa],
                                          replacement=[[bestRa]]))
        return [fontFeatures.Routine(rules=rv)]
Esempio n. 23
0
def rulesFromComputedRoutine(routine):
    p = routine.parameters
    glyphnames = routine.project.font.keys()
    rules = []
    for g in glyphnames:
        if not re.search(p["filter"], g):
            continue
        try:
            new = re.sub(p["match"], p["replace"], g)
        except:
            continue
        if new not in glyphnames:
            continue
        sub = fontFeatures.Substitution([[g]], [[new]])
        # XXX classes
        if p["before"]:
            sub.input = [[glyph] for glyph in p["before"].split()] + sub.input
        if p["after"]:
            sub.input.extend([[glyph] for glyph in p["after"].split()])
        rules.append(sub)
    return rules
Esempio n. 24
0
def buildSub(self, font, lookuptype, ff):
    """Build a GSUB subtable."""
    builders = []
    if lookuptype == 1:
        builder = otl.SingleSubstBuilder(font, self.address)
        for rule in self.rules:
            for left, right in zip(rule.input[0], rule.replacement[0]):
                builder.mapping[left] = right
    elif lookuptype == 2:
        builder = otl.MultipleSubstBuilder(font, self.address)
        for rule in self.rules:
            builder.mapping[rule.input[0][0]] = [
                x[0] for x in rule.replacement
            ]
    elif lookuptype == 4:
        builder = otl.LigatureSubstBuilder(font, self.address)
        for rule in self.rules:
            for sequence in itertools.product(*rule.input):
                builder.ligatures[sequence] = rule.replacement[0][0]
    elif lookuptype == 6:
        builder = otl.ChainContextSubstBuilder(font, self.address)
        for r in self.rules:
            import fontFeatures
            if isinstance(r, fontFeatures.Substitution) and r.replacement:
                lookups = []
                for left, right in zip(r.input, r.replacement):
                    # Make a fake sub routine

                    subbuilder = buildSub(
                        fontFeatures.Routine(rules=[
                            fontFeatures.Substitution([left],
                                                      replacement=[right])
                        ]), font, 1, ff)
                    builders.extend(subbuilder)
                    lookups.append(subbuilder)
            else:
                lookups = []
                for list_of_lookups in r.lookups:
                    lookups.append([
                        lu.routine.__builder for lu in (list_of_lookups or [])
                    ])
            builder.rules.append(
                otl.ChainContextualRule(
                    r.precontext or [],
                    r.input or [],
                    r.postcontext or [],
                    lookups,
                ))
    elif lookuptype == 8:
        builder = otl.ReverseChainSingleSubstBuilder(font, self.address)
        for rule in self.rules:
            mapping = {
                l[0]: r[0]
                for l, r in zip(rule.input, rule.replacement)
            }
            builder.rules.append((rule.precontext, rule.postcontext, mapping))
    else:
        raise ValueError("Don't know how to build a SUB type %i lookup" %
                         lookuptype)

    builder.lookupflag = self.flags
    # XXX mark filtering set
    self.__builder = builder
    builders.append(builder)
    return builders
Esempio n. 25
0
    def accept(self):
        glyphnames = self.project.font.keys()
        isol_re = self.isol_re.text()
        init_re = self.init_re.text()
        medi_re = self.medi_re.text()
        fina_re = self.fina_re.text()

        init_class = []
        medi_class = []
        fina_class = []
        init_rules = fontFeatures.Routine(name="Init")
        medi_rules = fontFeatures.Routine(name="Medi")
        fina_rules = fontFeatures.Routine(name="Fina")
        # We know these are valid REs
        arabic_glyphs = [
            g for g in glyphnames if re.search(init_re, g)
            or re.search(medi_re, g) or re.search(fina_re, g)
        ]
        for g in glyphnames:
            m = re.search(isol_re, g)
            if not m:
                continue
            if m.groups():
                base_name = g.replace(m[1], "")
            else:
                base_name = g
            for g2 in arabic_glyphs:
                m = re.search(init_re, g2)
                if not m or not m.groups():
                    continue
                base_init = g2.replace(m[1], "")
                if base_init == base_name:
                    init_class.append(g2)
                    init_rules.addRule(fontFeatures.Substitution([[g]],
                                                                 [[g2]]))
                    break

            for g2 in arabic_glyphs:
                m = re.search(medi_re, g2)
                if not m or not m.groups():
                    continue
                base_medi = g2.replace(m[1], "")
                if base_medi == base_name:
                    medi_class.append(g2)
                    medi_rules.addRule(fontFeatures.Substitution([[g]],
                                                                 [[g2]]))
                    break

            for g2 in arabic_glyphs:
                m = re.search(fina_re, g2)
                if not m or not m.groups():
                    continue
                base_fina = g2.replace(m[1], "")
                if base_fina == base_name:
                    fina_class.append(g2)
                    fina_rules.addRule(fontFeatures.Substitution([[g]],
                                                                 [[g2]]))
                    break

        warnings = []
        if len(init_class) < 10 or len(init_class) > len(glyphnames) / 2:
            warnings.append(
                f"Init regexp '{init_re} matched a surprising number of glyphs ({len(init_class)})"
            )
        if len(medi_class) < 10 or len(medi_class) > len(glyphnames) / 2:
            warnings.append(
                f"Medi regexp '{medi_re} matched a surprising number of glyphs ({len(medi_class)})"
            )
        if len(fina_class) < 10 or len(fina_class) > len(glyphnames) / 2:
            warnings.append(
                f"Fina regexp '{fina_re} matched a surprising number of glyphs ({len(fina_class)})"
            )

        if len(warnings) and self.show_warnings(
                warnings) == QMessageBox.Cancel:
            return

        self.project.fontfeatures.routines.extend(
            [init_rules, medi_rules, fina_rules])
        self.project.fontfeatures.addFeature("init", [init_rules])
        self.project.fontfeatures.addFeature("medi", [medi_rules])
        self.project.fontfeatures.addFeature("fina", [fina_rules])
        if not "init" in self.project.glyphclasses:
            self.project.glyphclasses["init"] = {
                "type":
                "automatic",
                "predicates": [{
                    "type": "name",
                    "comparator": "matches",
                    "value": init_re
                }],
            }
        if not "medi" in self.project.glyphclasses:
            self.project.glyphclasses["medi"] = {
                "type":
                "automatic",
                "predicates": [{
                    "type": "name",
                    "comparator": "matches",
                    "value": medi_re
                }],
            }
        if not "fina" in self.project.glyphclasses:
            self.project.glyphclasses["fina"] = {
                "type":
                "automatic",
                "predicates": [{
                    "type": "name",
                    "comparator": "matches",
                    "value": fina_re
                }],
            }

        if self.doCursive.isChecked():
            exitdict = {}
            entrydict = {}
            for g in glyphnames:
                anchors = self.project.font[g].anchors
                if not anchors:
                    continue
                entry = [a for a in anchors if a.name == "entry"]
                exit = [a for a in anchors if a.name == "exit"]
                if len(entry):
                    entrydict[g] = (entry[0].x, entry[0].y)
                if len(exit):
                    exitdict[g] = (exit[0].x, exit[0].y)
            s = fontFeatures.Attachment(
                base_name="entry",
                mark_name="exit",
                bases=entrydict,
                marks=exitdict,
            )
            r = fontFeatures.Routine(name="CursiveAttachment", rules=[s])
            self.project.fontfeatures.routines.extend([r])
            self.project.fontfeatures.addFeature("curs", [r])

        return super().accept()
Esempio n. 26
0
 def action(self, args):
     glyph = args[0].resolve(self.parser.fontfeatures, self.parser.font)
     return [fontFeatures.Substitution([glyph], [])]
Esempio n. 27
0
    def action(self, args):
        strategy, below_dots = args
        parser = self.parser
        for c in ["inits", "medis", "bariye", "behs"]:
            if c not in parser.fontfeatures.namedClasses:
                raise ValueError("Please define @%s class before calling" % c)

        alwaysDrop = strategy == "AlwaysDrop"

        # OK, here's the plan of attack for bari ye.
        # First, we find the length of the longest possible sequence.
        # Smallest medi * n + smallest beh + smallest init -> length of bari yeh right tail
        medis = parser.fontfeatures.namedClasses["medis"]
        inits = parser.fontfeatures.namedClasses["inits"]
        behs = parser.fontfeatures.namedClasses["behs"]
        below_dots = below_dots.resolve(parser.fontfeatures, parser.font)
        smallest_medi_width = max(
            failsafe_min_run, min([get_run(parser.font, g) for g in medis]))
        smallest_init_beh_width = min(
            [get_run(parser.font, g) for g in (set(behs) & set(inits))])
        smallest_init_beh = min(
            (set(behs) & set(inits)),
            key=lambda g: get_run(parser.font, g),
        )
        warnings.warn("Smallest medi width is %i" % smallest_medi_width)
        smallest_medi = min(medis, key=lambda g: get_run(parser.font, g))

        routines = []
        for bariye in parser.fontfeatures.namedClasses["bariye"]:
            warnings.warn("BariYe computation for %s" % bariye)
            entry_anchor = parser.fontfeatures.anchors[bariye]["entry"]
            bariye_tail = max(
                -get_glyph_metrics(parser.font, bariye)["rsb"],
                get_glyph_metrics(parser.font, bariye)["xMax"] -
                entry_anchor[0],
            )
            # bariye_tail += max(get_glyph_metrics(parser.font, dot)["width"] for dot in below_dots) / 2
            # # Increase tail by half the width of the widest nukta
            # bariye_tail += (
            #     max(
            #         [
            #             get_glyph_metrics(parser.font, g)["xMax"]
            #             - get_glyph_metrics(parser.font, g)["xMin"]
            #             for g in below_dots
            #         ]
            #     )
            #     / 2
            # )

            # Consider two cases.
            # First, the case where the beh is init and full of short medis
            maximum_sequence_length_1 = 1 + math.ceil(
                (bariye_tail - smallest_init_beh_width) / smallest_medi_width)
            warnings.warn("Longest init beh sequence: %s" %
                          " ".join([smallest_medi] *
                                   (maximum_sequence_length_1 - 1) +
                                   [smallest_init_beh]))
            # Second, the case where the init is not beh, but there are a bunch of medis,
            # one (or more) of which is a medi beh
            smallest_init_nonbeh_width = min([
                max(get_rise(parser.font, g), failsafe_min_run)
                for g in (set(inits) - set(behs))
            ])
            smallest_medi_beh_width = min([
                max(get_rise(parser.font, g), failsafe_min_run)
                for g in (set(medis) & set(behs))
            ])

            maximum_sequence_length_2 = 2 + math.ceil(
                (bariye_tail - smallest_init_nonbeh_width -
                 smallest_medi_beh_width) / smallest_medi_width)

            maximum_sequence_length = min(
                failsafe_max_length,
                max(maximum_sequence_length_1, maximum_sequence_length_2))
            warnings.warn("Max sequence width is %i" % maximum_sequence_length)

            # Next, let's create a chain rule for all nukta sequences
            dropBYsRoutine = fontFeatures.Routine(flags=0x0010)
            dropBYsRoutine.markFilteringSet = below_dots

            dropADotRoutine = fontFeatures.Routine()
            # Substitute those not ending .yb with those ending .yb
            below_dots_non_yb = list(
                sorted(filter(lambda x: not x.endswith(".yb"), below_dots)))
            below_dots_yb = list(
                sorted(filter(lambda x: x.endswith(".yb"), below_dots)))
            if len(below_dots_non_yb) != len(below_dots_yb):
                raise ValueError(
                    "Mismatch in @below_dots: %s has .yb suffix, %s does not" %
                    (below_dots_yb, below_dots_non_yb))

            dropADotRoutine.addRule(
                fontFeatures.Substitution([below_dots_non_yb],
                                          [below_dots_yb]))

            maybeDropDotRoutine = fontFeatures.Routine(flags=0x0010)
            maybeDropDotRoutine.markFilteringSet = below_dots

            binned_medis = bin_glyphs_by_metric(parser.font,
                                                medis,
                                                "run",
                                                bincount=accuracy1)
            binned_inits = bin_glyphs_by_metric(parser.font,
                                                inits,
                                                "run",
                                                bincount=accuracy1)

            queue = [[[[bariye]]]]
            bin2mk = {"0": None, "1": below_dots}
            while len(queue) > 0:
                consideration = queue.pop(0)
                # import code; code.interact(local=locals())
                seq_length = sum([s[1] for s in consideration[:-1]])
                repsequence = [(s[0][0], s[1]) for s in consideration[:-1]]
                sequence = [s[0] for s in consideration]
                warnings.warn("Sequence %s total %i bariye_tail %i" %
                              (repsequence, seq_length, bariye_tail))

                if seq_length > bariye_tail or len(
                        consideration) > maximum_sequence_length:
                    continue

                lu = [None] * len(sequence)
                if alwaysDrop:
                    lu[0] = [dropADotRoutine]
                else:
                    lu[0] = [maybeDropDotRoutine]
                for j in range(0, 2**(len(sequence) - 1)):
                    binary = "{0:0%ib}" % (len(sequence) - 1)
                    marksequence = [bin2mk[x] for x in list(binary.format(j))]
                    # import code; code.interact(local=locals())
                    newsequence = dropnone(interleave(sequence, marksequence))
                    chainrule = fontFeatures.Chaining([below_dots],
                                                      postcontext=newsequence,
                                                      lookups=lu)
                    # We don't combine the bins here precisely because they're
                    # disjoint sets and that means they can be expressed as a
                    # format 2 class-based rule! Wonderful!
                    dropBYsRoutine.addRule(chainrule)

                for m in binned_medis:
                    queue.append([list(m)] + consideration)

            if not alwaysDrop:
                # Check to see if it can fit in the gap, and only move it if it can't
                medis_by_rise = bin_glyphs_by_metric(parser.font,
                                                     medis,
                                                     "rise",
                                                     bincount=accuracy2)
                queue = [[[[bariye],
                           get_glyph_metrics(parser.font, bariye)["rise"]]]]
                ybClearance = self.get_yb_clearance(parser, bariye)
                gapRequired = self.compute_threshold(parser,
                                                     below_dots) - ybClearance
                warnings.warn(
                    "%i units of rise are required to fit a nukta in the gap" %
                    gapRequired)
                while len(queue) > 0:
                    consideration = queue.pop(0)
                    total_rise = sum([s[1] for s in consideration])
                    repsequence = [(s[0][0], s[1]) for s in consideration]
                    # warnings.warn("Sequence %s total rise %i required %i" % (repsequence, total_rise, gapRequired))
                    if (total_rise > gapRequired
                            or len(consideration) > maximum_sequence_length):
                        # warnings.warn("Does not drop")
                        continue

                    sequence = [s[0] for s in consideration]
                    lu = [None] * len(sequence)
                    lu[0] = [dropADotRoutine]
                    chainrule = fontFeatures.Chaining([below_dots],
                                                      postcontext=sequence,
                                                      lookups=lu)
                    maybeDropDotRoutine.addRule(chainrule)
                    # print("Drops %s"  % chainrule.asFea())
                    for m in medis_by_rise:
                        if total_rise + m[1] < gapRequired:
                            queue.append([list(m)] + consideration)

            # Add all the routines to the parser
            parser.fontfeatures.routines.append(dropADotRoutine)
            if not alwaysDrop:
                parser.fontfeatures.routines.append(maybeDropDotRoutine)
            routines.append(dropBYsRoutine)
        return routines