def action(self, args): overhang_padding, glyphs = args parser = self.parser for c in ["inits", "medis"]: if c not in parser.fontfeatures.namedClasses: raise ValueError("Please define @%s class before calling") medis = parser.fontfeatures.namedClasses["medis"] inits = parser.fontfeatures.namedClasses["inits"] overhangers = glyphs.resolve(parser.fontfeatures, parser.font) binned_medis = bin_glyphs_by_metric(parser.font, medis, "run", bincount=8) binned_inits = bin_glyphs_by_metric(parser.font, inits, "run", bincount=8) rules = [] maxchainlength = 0 longeststring = [] for yb in overhangers: entry_anchor = parser.fontfeatures.anchors[yb]["entry"] overhang = max( -get_glyph_metrics(parser.font, yb)["rsb"], get_glyph_metrics(parser.font, yb)["xMax"] - entry_anchor[0], ) workqueue = [[x] for x in binned_inits] while workqueue: string = workqueue.pop(0) totalwidth = sum([max(x[1], failsafe_min_run) for x in string]) if totalwidth > overhang or len(string) > failsafe_max_length: continue adjustment = overhang - totalwidth + int(overhang_padding) postcontext = [x[0] for x in string[:-1]] + [[yb]] input_ = string[-1] example = [input_[0][0]] + [x[0] for x in postcontext] warnings.warn( "For glyphs in %s, overhang=%i totalwidth=%i adjustment=%i" % (example, overhang, totalwidth, adjustment)) maxchainlength = max(maxchainlength, len(string)) rules.append( fontFeatures.Positioning( [input_[0]], [fontFeatures.ValueRecord(xAdvance=int(adjustment))], postcontext=postcontext, )) for medi in binned_medis: workqueue.append([medi] + string) warnings.warn("Bari Ye collision maximum chain length was %i glyphs" % maxchainlength) return [fontFeatures.Routine(rules=rules, flags=8)]
def action(self, args): # glyphs is already resolved, because this class has functions of DefineClass, which resolves `primary` classname, (metric, bincount), glyphs = args[0], (args[1].value, args[2].value), args[3] binned = bin_glyphs_by_metric(self.parser.font, glyphs, metric, bincount=int(bincount)) for i in range(1, int(bincount) + 1): self.parser.fontfeatures.namedClasses["%s_%s%i" % (classname, metric, i)] = tuple(binned[i - 1][0]) return classname, (metric, bincount), glyphs
def store(self, parser, tokens): from glyphtools import determine_kern, bin_glyphs_by_metric import fontFeatures import itertools units = int(tokens[-1].token) kerns = [] bincount = 5 lefts = parser.expandGlyphOrClassName(tokens[0].token) rights = parser.expandGlyphOrClassName(tokens[1].token) def make_kerns(rise=0, context=[], direction="LTR"): kerns = [] for l in lefts: for r in rights: if direction == "LTR": kern = determine_kern(parser.font, l, r, units, offset1=(0, rise)) else: kern = determine_kern(parser.font, r, l, units, offset1=(0, rise)) if abs(kern) < 5: continue v = fontFeatures.ValueRecord(xAdvance=kern) kerns.append( fontFeatures.Positioning([[r]], [v], precontext=[[l]], postcontext=context)) return kerns context = [ parser.expandGlyphOrClassName(x.token) for x in tokens[2:-1] ] if len(context) == 0: return [fontFeatures.Routine(rules=make_kerns())] kerns = [] binned_contexts = [ bin_glyphs_by_metric(parser.font, glyphs, "rise", bincount=bincount) for glyphs in context ] for c in itertools.product(*binned_contexts): totalrise = sum([x[1] for x in c]) precontext = [x[0] for x in c] kerns.extend( make_kerns(totalrise, context=precontext, direction="RTL")) return [fontFeatures.Routine(rules=kerns)]
def action(self, args): parser = self.parser bincount = 5 lefts, rights, units, pre = args[0] lefts = lefts.resolve(parser.fontfeatures, parser.font) rights = rights.resolve(parser.fontfeatures, parser.font) pre = [g.resolve(parser.fontfeatures, parser.font) for g in pre] def make_kerns(rise=0, context=[], direction="LTR"): kerns = [] for l in lefts: for r in rights: if direction == "LTR": kern = determine_kern(parser.font, l, r, units, offset1=(0, rise)) else: kern = determine_kern(parser.font, r, l, units, offset1=(0, rise)) if abs(kern) < 5: continue v = fontFeatures.ValueRecord(xAdvance=kern) kerns.append( fontFeatures.Positioning([[r]], [v], precontext=[[l]], postcontext=context)) return kerns if not pre: return [fontFeatures.Routine(rules=make_kerns())] kerns = [] binned_contexts = [ bin_glyphs_by_metric(parser.font, glyphs, "rise", bincount=bincount) for glyphs in pre ] for c in itertools.product(*binned_contexts): totalrise = sum([x[1] for x in c]) precontext = [x[0] for x in c] kerns.extend( make_kerns(totalrise, context=precontext, direction="RTL")) return [fontFeatures.Routine(rules=kerns)]
def action(self, parser, metric, bincount, classname, definition): glyphs = self.resolve_definition(parser, definition[0]) predicates = definition[1] for p in predicates: glyphs = list( filter(lambda x: self.meets_predicate(x, p, parser), glyphs)) binned = bin_glyphs_by_metric(parser.font, glyphs, metric, bincount=int(bincount)) for i in range(1, int(bincount) + 1): parser.fontfeatures.namedClasses["%s_%s%i" % (classname["classname"], metric, i)] = tuple(binned[i - 1][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