def unparseSinglePositioning(self, lookup): """Turn a GPOS1 (single positioning) subtable into a fontFeatures Routine.""" b = fontFeatures.Routine(name=self.getname("SinglePositioning" + self.gensym())) self._fix_flags(b, lookup) for subtable in lookup.SubTable: if subtable.Format == 1: spos = fontFeatures.Positioning( [subtable.Coverage.glyphs], [self.makeValueRecord(subtable.Value)], address=self.currentLookup, flags=lookup.LookupFlag, ) b.addRule(spos) else: # Optimize it later for g, v in zip(subtable.Coverage.glyphs, subtable.Value): spos = fontFeatures.Positioning( [[g]], [self.makeValueRecord(v)], address=self.currentLookup, flags=lookup.LookupFlag, ) b.addRule(spos) return b, []
def store(self, parser, tokens, doFilter=None): import fontFeatures from fontFeatures.jankyPOS import JankyPos import collidoscope import warnings import beziers import itertools combinations = [ parser.expandGlyphOrClassName(x.token) for x in tokens[0:-2] ] mitigation = tokens[-2].token units = int(tokens[-1].token) janky = fontFeatures.jankyPOS.JankyPos(parser.font) col = collidoscope.Collidoscope(None, { "cursive": False, "faraway": True, "area": 0 }, ttFont=parser.font) rv = [] for element in itertools.product(*combinations): buf = janky.positioning_buffer(element) buf = janky.process_fontfeatures(buf, parser.fea) glyphs = [] cursor = 0 for g, vr in buf: offset = beziers.point.Point(cursor + (vr.xPlacement or 0), vr.yPlacement or 0) glyphs.append(col.get_positioned_glyph(g, offset)) glyphs[-1]["advance"] = vr.xAdvance cursor = cursor + vr.xAdvance overlaps = col.has_collisions(glyphs) if overlaps: warnings.warn( "Overlap found in glyph sequence %s - mitigating" % (" ".join(element))) intersects = [p1.intersection(p2) for p1, p2 in overlaps] assert len(intersects) == 1 # If it's not, we have to find the leftmost intersection if mitigation == "kern": correction = intersects[0][0].bounds().width + units v = fontFeatures.ValueRecord(xAdvance=int(correction)) s = fontFeatures.Positioning( [[element[0]], [element[1]]], [v, fontFeatures.ValueRecord()]) else: correction = intersects[0][0].bounds().height + units v = fontFeatures.ValueRecord(yPlacement=int(correction)) s = fontFeatures.Positioning([[element[2]]], [v], postcontext=[[element[1]], [element[0]]]) rv.append(s) return rv
def unparsePairPositioning(self, lookup): b = fontFeatures.Routine(name=self.getname("PairPositioning" + self.gensym())) self._fix_flags(b, lookup) for subtable in lookup.SubTable: if subtable.Format == 1: for g, pair in zip(subtable.Coverage.glyphs, subtable.PairSet): for vr in pair.PairValueRecord: spos = fontFeatures.Positioning( [[g], [vr.SecondGlyph]], [ self.makeValueRecord(vr.Value1, subtable.ValueFormat1), self.makeValueRecord(vr.Value2, subtable.ValueFormat2), ], address=self.currentLookup, flags=lookup.LookupFlag, ) b.addRule(spos) else: class1 = self._invertClassDef(subtable.ClassDef1.classDefs, self.font) class2 = self._invertClassDef(subtable.ClassDef2.classDefs, self.font) for ix1, c1 in enumerate(subtable.Class1Record): if ix1 not in class1: continue # XXX for ix2, c2 in enumerate(c1.Class2Record): if ix2 not in class2: continue # XXX vr1 = self.makeValueRecord(c2.Value1, subtable.ValueFormat1) vr2 = self.makeValueRecord(c2.Value2, subtable.ValueFormat2) if not vr1 and not vr2: continue firstClass = list( set(class1[ix1]) & set(subtable.Coverage.glyphs)) spos = fontFeatures.Positioning( [firstClass, class2[ix2]], [vr1, vr2], address=self.currentLookup, flags=lookup.LookupFlag, ) b.addRule(spos) return b, []
def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2): self._start_routine_if_necessary(location) location = "%s:%i:%i" % (location) s = fontFeatures.Positioning(glyphs=[[glyph1], [glyph2]], valuerecords=[value1, value2], address=location, languages=self.currentLanguage) self.currentRoutine.addRule(s)
def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2): location = "%s:%i:%i" % (location) s = fontFeatures.Positioning( glyphs=[glyphclass1, glyphclass2], valuerecords=[value1, value2], address=location, ) self.currentRoutine.addRule(s)
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 add_single_pos(self, location, prefix, suffix, pos, forceChain): location = "%s:%i:%i" % (location) s = fontFeatures.Positioning( glyphs=[p[0] for p in pos], valuerecords=[p[1] for p in pos], precontext=prefix, postcontext=suffix, address=location, ) self.currentRoutine.addRule(s)
def add_single_pos(self, location, prefix, suffix, pos, forceChain): self._start_routine_if_necessary(location) location = "%s:%i:%i" % (location) s = fontFeatures.Positioning(glyphs=[p[0] for p in pos], valuerecords=[p[1] for p in pos], 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 action(self, parser, glyphs, maxlen, distance): glyphs = glyphs.resolve(parser.fontfeatures, parser.font) postcontext = [glyphs] rules = [] # r = fontFeatures.Routine(flags=0x12) # r.markFilteringSet = parser.fontfeatures.namedClasses["nuktas"] for i in reversed(range(1, maxlen)): positions = [] input_ = [glyphs] * i # Only include lo-rise medis for j in range(1, i + 1): positions.append( fontFeatures.ValueRecord((i - j + 1) * distance, 0, 0, 0) ) rules.append( fontFeatures.Positioning(input_, positions, postcontext=postcontext) ) return rules
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
def buildPos(self, font, lookuptype, ff): """Build a GPOS subtable.""" builders = [] if lookuptype == 1: builder = otl.SinglePosBuilder(font, self.address) for rule in self.rules: ot_valuerecs = [ x.toOTValueRecord(ff, pairPosContext=False) for x in rule.valuerecords ] for glyph in rule.glyphs[0]: builder.add_pos(rule.address, glyph, ot_valuerecs[0]) elif lookuptype == 2: builder = otl.PairPosBuilder(font, self.address) for rule in self.rules: ot_valuerecs = [ x.toOTValueRecord(ff, pairPosContext=True) for x in rule.valuerecords ] if len(rule.glyphs[0]) == 1 and len(rule.glyphs[1]) == 1: builder.addGlyphPair( rule.address, rule.glyphs[0][0], ot_valuerecs[0], rule.glyphs[1][0], ot_valuerecs[1], ) else: builder.addClassPair( rule.address, tuple(rule.glyphs[0]), ot_valuerecs[0], tuple(rule.glyphs[1]), ot_valuerecs[1], ) elif lookuptype == 3: builder = otl.CursivePosBuilder(font, self.address) for rule in self.rules: allglyphs = set(rule.bases.keys()) | set(rule.marks.keys()) for g in allglyphs: builder.attachments[g] = ( g in rule.bases and makeAnchor(rule.bases[g], ff) or None, g in rule.marks and makeAnchor(rule.marks[g], ff) or None, ) elif lookuptype == 4 or lookuptype == 6: if lookuptype == 4: builder = otl.MarkBasePosBuilder(font, self.address) baseholder = builder.bases else: builder = otl.MarkMarkPosBuilder(font, self.address) baseholder = builder.baseMarks for r in self.rules: for mark, anchor in r.marks.items(): if mark in builder.marks: raise ValueError( "A mark glyph %s tried to be in two categories (%s, %s)" % (mark, r.mark_name, builder.marks[mark][0])) builder.marks[mark] = (r.mark_name, makeAnchor(anchor, ff)) for base, anchor in r.bases.items(): if base not in baseholder: baseholder[base] = {} baseholder[base][r.mark_name] = makeAnchor(anchor, ff) # XXX. We may need to express this as multiple lookups elif lookuptype == 8: builder = otl.ChainContextPosBuilder(font, self.address) for r in self.rules: new_lookup_list = [] import fontFeatures if isinstance(r, fontFeatures.Positioning): lookups = [] for glyphs, vr in zip(r.glyphs, r.valuerecords): # Make a fake pos routine subbuilder = buildPos( fontFeatures.Routine(rules=[ fontFeatures.Positioning([glyphs], valuerecords=[vr]) ]), font, 1, ff) builders.extend(subbuilder) new_lookup_list.append(subbuilder) glyphs = r.glyphs else: for list_of_lookups in r.lookups: new_lookup_list.append([ lu.routine.__builder for lu in (list_of_lookups or []) ]) glyphs = r.input builder.rules.append( otl.ChainContextualRule( r.precontext or [], glyphs or [], r.postcontext or [], new_lookup_list, )) else: raise ValueError("Don't know how to build a POS type %i lookup" % lookuptype) builder.lookupflag = self.flags # XXX mark filtering set self.__builder = builder builders.append(builder) return builders
def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2): location = "%s:%i:%i" % (location) s = fontFeatures.Positioning(glyphs=[[glyph1], [glyph2]], valuerecords=[value1, value2], address=location) self.currentRoutine.addRule(s)