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 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 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 _glyphName(self, glyph): try: name = glyph.glyph except AttributeError: name = glyph return ast.GlyphName(self._glyph_map.get(name, name))
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]")