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, []
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, ) ]
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) ]
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)]
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,[]
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])
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)
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)
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)
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)]
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])
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)
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)
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)
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)
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, []
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, []
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, []
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)]
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]
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, []
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)]
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
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
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()
def action(self, args): glyph = args[0].resolve(self.parser.fontfeatures, self.parser.font) return [fontFeatures.Substitution([glyph], [])]
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