def _gposContextLookup(self, lookup, prefix, suffix, ignore, fealookup, targetlookup): statements = fealookup.statements assert not lookup.reversal pos = lookup.pos if isinstance(pos, VAst.PositionAdjustPairDefinition): for (idx1, idx2), (pos1, pos2) in pos.adjust_pair.items(): glyphs1 = self._coverage(pos.coverages_1[idx1 - 1]) glyphs2 = self._coverage(pos.coverages_2[idx2 - 1]) assert len(glyphs1) == 1 assert len(glyphs2) == 1 glyphs = (glyphs1[0], glyphs2[0]) if ignore: statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) else: lookups = (targetlookup, targetlookup) statement = ast.ChainContextPosStatement( prefix, glyphs, suffix, lookups) statements.append(statement) elif isinstance(pos, VAst.PositionAdjustSingleDefinition): glyphs = [ast.GlyphClass()] for a, b in pos.adjust_single: glyph = self._coverage(a) glyphs[0].extend(glyph) if ignore: statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) else: statement = ast.ChainContextPosStatement( prefix, glyphs, suffix, [targetlookup]) statements.append(statement) elif isinstance(pos, VAst.PositionAttachDefinition): glyphs = [ast.GlyphClass()] for coverage, _ in pos.coverage_to: glyphs[0].extend(self._coverage(coverage)) if ignore: statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) else: statement = ast.ChainContextPosStatement( prefix, glyphs, suffix, [targetlookup]) statements.append(statement) else: raise NotImplementedError(pos)
def parse(self): """Parse the feature code. Returns: A ``FontFeatures`` object containing the rules of this file. """ # Borrow glyph classes for name, members in self.ff.namedClasses.items(): glyphclass = ast.GlyphClassDefinition( name, ast.GlyphClass([m for m in members])) self.parser.glyphclasses_.define(name, glyphclass) parsetree = self.parser.parse() # Return glyph classes if len(self.parser.glyphclasses_.scopes_): for name, definition in self.parser.glyphclasses_.scopes_[ -1].items(): if isinstance(definition, ast.MarkClass): pass # self.ff.namedClasses[name] = list(definition.glyphs.keys()) else: self.ff.namedClasses[name] = definition.glyphs.glyphs self.features_ = {} parsetree.build(self) return self.ff
def asFeaAST(self, inFeature=False): if self.name and not inFeature: f = feaast.LookupBlock(name=self.name) elif self.name: f = feaast.LookupBlock(name=self.name) else: f = feaast.Block() arranged = arrange(self) if arranged and inFeature: f = feaast.Block() for a in arranged: f.statements.append(asFeaAST(a, inFeature)) return f if hasattr(self, "flags"): flags = feaast.LookupFlagStatement(self.flags) if self.flags & 0x10 and hasattr(self, "markFilteringSetAsClass"): # XXX # We only need the name, not the contents mfs = feaast.GlyphClassDefinition(self.markFilteringSetAsClass, feaast.GlyphClass([])) flags.markFilteringSet = feaast.GlyphClassName(mfs) if self.flags & 0xFF00 and hasattr(self, "markAttachmentSetAsClass"): # XXX mfs = feaast.GlyphClassDefinition(self.markAttachmentSetAsClass, feaast.GlyphClass([])) flags.markAttachment = feaast.GlyphClassName(mfs) f.statements.append(flags) for x in self.comments: f.statements.append(feaast.Comment(x)) f.statements.append(feaast.Comment(";")) lastaddress = self.address if lastaddress: f.statements.append( feaast.Comment("# Original source: %s " % (" ".join([str(x) for x in lastaddress])))) for x in self.rules: if x.address and x.address != lastaddress: f.statements.append( feaast.Comment("# Original source: %s " % x.address)) lastaddress = x.address f.statements.append(x.asFeaAST()) return f
def parse_glyphclass_(self, accept_glyphname): if (accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID)): glyph = self.expect_glyph_() return ast.GlyphName(self.cur_token_location_, glyph) if self.next_token_type_ is Lexer.GLYPHCLASS: self.advance_lexer_() gc = self.glyphclasses_.resolve(self.cur_token_) if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, self.cur_token_location_) if isinstance(gc, ast.MarkClass): return ast.MarkClassName(self.cur_token_location_, gc) else: return ast.GlyphClassName(self.cur_token_location_, gc) self.expect_symbol_("[") glyphs = set() location = self.cur_token_location_ while self.next_token_ != "]": if self.next_token_type_ is Lexer.NAME: glyph = self.expect_glyph_() if self.next_token_ == "-": range_location = self.cur_token_location_ range_start = glyph self.expect_symbol_("-") range_end = self.expect_glyph_() glyphs.update(self.make_glyph_range_(range_location, range_start, range_end)) else: glyphs.add(glyph) elif self.next_token_type_ is Lexer.CID: glyph = self.expect_glyph_() if self.next_token_ == "-": range_location = self.cur_token_location_ range_start = self.cur_token_ self.expect_symbol_("-") range_end = self.expect_cid_() glyphs.update(self.make_cid_range_(range_location, range_start, range_end)) else: glyphs.add("cid%05d" % self.cur_token_) elif self.next_token_type_ is Lexer.GLYPHCLASS: self.advance_lexer_() gc = self.glyphclasses_.resolve(self.cur_token_) if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, self.cur_token_location_) glyphs.update(gc.glyphSet()) else: raise FeatureLibError( "Expected glyph name, glyph range, " "or glyph class reference", self.next_token_location_) self.expect_symbol_("]") return ast.GlyphClass(location, glyphs)
def asFeaAST(self): lut = lookup_type(self) if not lut: return feaast.Comment("") if lut == 1: # GSUB 1 Single Substitution return feaast.SingleSubstStatement( [glyphref(x) for x in self.input], [glyphref(x) for x in self.replacement], [glyphref(x) for x in self.precontext], [glyphref(x) for x in self.postcontext], False, ) elif lut == 2: # GSUB 2 Multiple Substitution # Paired rules need to become a set of statements if is_paired(self): return paired_mult(self) return feaast.MultipleSubstStatement( [glyphref(x) for x in self.precontext], glyphref(self.input[0]), [glyphref(x) for x in self.postcontext], [glyphref(x) for x in self.replacement], ) elif lut == 3: # GSUB 3 Alternate Substitution return feaast.AlternateSubstStatement( [glyphref(x) for x in self.precontext], glyphref(self.input[0]), [glyphref(x) for x in self.postcontext], feaast.GlyphClass( [feaast.GlyphName(x) for x in self.replacement[0]]), ) elif lut == 4: # GSUB 4 Ligature Substitution # Paired rules need to become a set of statements if is_paired(self): return paired_ligature(self) return feaast.LigatureSubstStatement( [glyphref(x) for x in self.precontext], [glyphref(x) for x in self.input], [glyphref(x) for x in self.postcontext], glyphref(self.replacement[0]), False, ) elif lut in [5, 6, 7 ]: # GSUB 5, 6, 7 Different types of contextual substitutions raise NotImplementedError("Use the Chain verb for this") elif lut == 8: # GSUB 8 Reverse Chaining Single Substitution return feaast.ReverseChainSingleSubstStatement( [glyphref(x) for x in self.precontext], [glyphref(x) for x in self.postcontext], [glyphref(x) for x in self.input], [glyphref(self.replacement[0])], ) elif lut >= 9: raise NotImplementedError( "Invalid GSUB lookup type requested: {}".format(lut)) raise ValueError("LookupType must be a single positive integer")
def asFeaAST(self): if self.name: f = feaast.LookupBlock(name=self.name) else: f = feaast.Block() if hasattr(self, "flags"): flags = feaast.LookupFlagStatement(self.flags) if self.flags & 0x10 and hasattr(self, "markFilteringSetAsClass"): # XXX # We only need the name, not the contents mfs = feaast.GlyphClassDefinition(self.markFilteringSetAsClass, feaast.GlyphClass([])) flags.markFilteringSet = feaast.GlyphClassName(mfs) if self.flags & 0xFF00 and hasattr(self, "markAttachmentSetAsClass"): # XXX mfs = feaast.GlyphClassDefinition(self.markAttachmentSetAsClass, feaast.GlyphClass([])) flags.markAttachment = feaast.GlyphClassName(mfs) f.statements.append(flags) for x in self.comments: f.statements.append(feaast.Comment(x)) f.statements.append(feaast.Comment(";")) lastaddress = self.address if lastaddress: f.statements.append( feaast.Comment("# Original source: %s " % (" ".join([str(x) for x in lastaddress])))) for x in self.rules: if x.address and x.address != lastaddress: f.statements.append( feaast.Comment("# Original source: %s " % x.address)) lastaddress = x.address if hasattr(x, "note"): f.statements.append( feaast.Comment("\n".join( [f"# {n}" for n in x.note.split("\n")]))) f.statements.append(x.asFeaAST()) return f
def makeQuranSajdaLine(font): pos = font["uni06D7"].getBounds(font).yMax thickness = font.info.postscriptUnderlineThickness minwidth = 100 _, gdefclasses = findGDEF(font) # collect glyphs grouped by their widths rounded by 100 units, we will use # them to decide the widths of over/underline glyphs we will draw widths = {} for glyph in font: u = glyph.unicode if ((u is None ) or (0x0600 <= u <= 0x06FF) or u == ord(" ")) \ and glyph.width > 0: width = round(glyph.width / minwidth) * minwidth width = width > minwidth and width or minwidth if not width in widths: widths[width] = [] widths[width].append(glyph.name) base = 'uni0305' drawOverline(font, base, 0x0305, pos, thickness, 500) mark = ast.FeatureBlock("mark") overset = ast.GlyphClassDefinition("OverSet", ast.GlyphClass([base])) lookupflag = ast.LookupFlagStatement( markFilteringSet=ast.GlyphClassName(overset)) mark.statements.extend([overset, lookupflag]) for width in sorted(widths.keys()): # for each width group we create an over/underline glyph with the same # width, and add a contextual substitution lookup to use it when an # over/underline follows any glyph in this group replace = f"uni0305.{width}" drawOverline(font, replace, None, pos, thickness, width) sub = ast.SingleSubstStatement([ast.GlyphName(base)], [ast.GlyphName(replace)], [ast.GlyphClass(widths[width])], [], False) gdefclasses.markGlyphs.append(replace) mark.statements.append(sub) font.features.text.statements.append(mark)
def _glyphDefinition(self, glyph): try: self._glyph_map[glyph.name] = self._glyph_order[glyph.id] except TypeError: pass if glyph.type in ("BASE", "MARK", "LIGATURE", "COMPONENT"): if glyph.type not in self._gdef: self._gdef[glyph.type] = ast.GlyphClass() self._gdef[glyph.type].glyphs.append(self._glyphName(glyph.name)) if glyph.type == "MARK": self._marks.add(glyph.name) elif glyph.type == "LIGATURE": self._ligatures[glyph.name] = glyph.components
def asFea(self, indent=""): res = "" l = len(self.glyphs.glyphSet()) for i, glyph in enumerate(self.glyphs.glyphSet()): if i > 0: res += "\n" + indent res += "sub " if len(self.prefix) or len(self.suffix): if len(self.prefix): res += " ".join(map(asFea, self.prefix)) + " " res += asFea(glyph) + "'" # even though we really only use 1 if len(self.suffix): res += " " + " ".join(map(asFea, self.suffix)) else: res += asFea(glyph) res += " from " replacements = ast.GlyphClass(glyphs=self.replacements.glyphSet()[i::l], location=self.location) res += asFea(replacements) res += ";" return res
def makeGlyphClassDefinition(className, members): glyphNames = [ast.GlyphName(g) for g in members] glyphClass = ast.GlyphClass(glyphNames) classDef = ast.GlyphClassDefinition(className, glyphClass) return classDef
def _to_inline_class(glyphs): return feaast.GlyphClass([feaast.GlyphName(x) for x in glyphs])
def glyphref(g): if len(g) == 1: return feaast.GlyphName(g[0]) return feaast.GlyphClass([feaast.GlyphName(x) for x in g])
def _enum(self, enum): return ast.GlyphClass(self._coverage(enum.enum))
def test_glyphname_escape(self): statement = ast.GlyphClass() for name in ("BASE", "NULL", "foo", "a"): statement.append(ast.GlyphName(name)) self.assertEqual(statement.asFea(), r"[\BASE \NULL foo a]")