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 _gsubLookup(self, lookup, prefix, suffix, ignore, chain, fealookup): statements = fealookup.statements sub = lookup.sub for key, val in sub.mapping.items(): if not key or not val: path, line, column = sub.location log.warning( f"{path}:{line}:{column}: Ignoring empty substitution") continue statement = None glyphs = self._coverage(key) replacements = self._coverage(val) if ignore: chain_context = (prefix, glyphs, suffix) statement = ast.IgnoreSubstStatement([chain_context]) elif isinstance(sub, VAst.SubstitutionSingleDefinition): assert len(glyphs) == 1 assert len(replacements) == 1 statement = ast.SingleSubstStatement(glyphs, replacements, prefix, suffix, chain) elif isinstance(sub, VAst.SubstitutionReverseChainingSingleDefinition): assert len(glyphs) == 1 assert len(replacements) == 1 statement = ast.ReverseChainSingleSubstStatement( prefix, suffix, glyphs, replacements) elif isinstance(sub, VAst.SubstitutionMultipleDefinition): assert len(glyphs) == 1 statement = ast.MultipleSubstStatement(prefix, glyphs[0], suffix, replacements, chain) elif isinstance(sub, VAst.SubstitutionLigatureDefinition): assert len(replacements) == 1 statement = ast.LigatureSubstStatement(prefix, glyphs, suffix, replacements[0], chain) else: raise NotImplementedError(sub) statements.append(statement)
def parse_substitute_(self): assert self.cur_token_ in {"substitute", "sub", "reversesub", "rsub"} location = self.cur_token_location_ reverse = self.cur_token_ in {"reversesub", "rsub"} old_prefix, old, lookups, values, old_suffix, hasMarks = \ self.parse_glyph_pattern_(vertical=False) if any(values): raise FeatureLibError( "Substitution statements cannot contain values", location) new = [] if self.next_token_ == "by": keyword = self.expect_keyword_("by") while self.next_token_ != ";": gc = self.parse_glyphclass_(accept_glyphname=True) new.append(gc) elif self.next_token_ == "from": keyword = self.expect_keyword_("from") new = [self.parse_glyphclass_(accept_glyphname=False)] else: keyword = None self.expect_symbol_(";") if len(new) is 0 and not any(lookups): raise FeatureLibError( 'Expected "by", "from" or explicit lookup references', self.cur_token_location_) # GSUB lookup type 3: Alternate substitution. # Format: "substitute a from [a.1 a.2 a.3];" if keyword == "from": if reverse: raise FeatureLibError( 'Reverse chaining substitutions do not support "from"', location) if len(old) != 1 or len(old[0].glyphSet()) != 1: raise FeatureLibError( 'Expected a single glyph before "from"', location) if len(new) != 1: raise FeatureLibError( 'Expected a single glyphclass after "from"', location) return ast.AlternateSubstStatement( location, old_prefix, old[0], old_suffix, new[0]) num_lookups = len([l for l in lookups if l is not None]) # GSUB lookup type 1: Single substitution. # Format A: "substitute a by a.sc;" # Format B: "substitute [one.fitted one.oldstyle] by one;" # Format C: "substitute [a-d] by [A.sc-D.sc];" if (not reverse and len(old) == 1 and len(new) == 1 and num_lookups == 0): glyphs = sorted(list(old[0].glyphSet())) replacements = sorted(list(new[0].glyphSet())) if len(replacements) == 1: replacements = replacements * len(glyphs) if len(glyphs) != len(replacements): raise FeatureLibError( 'Expected a glyph class with %d elements after "by", ' 'but found a glyph class with %d elements' % (len(glyphs), len(replacements)), location) return ast.SingleSubstStatement(location, dict(zip(glyphs, replacements)), old_prefix, old_suffix, forceChain=hasMarks) # GSUB lookup type 2: Multiple substitution. # Format: "substitute f_f_i by f f i;" if (not reverse and len(old) == 1 and len(old[0].glyphSet()) == 1 and len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1 and num_lookups == 0): return ast.MultipleSubstStatement( location, old_prefix, tuple(old[0].glyphSet())[0], old_suffix, tuple([list(n.glyphSet())[0] for n in new])) # GSUB lookup type 4: Ligature substitution. # Format: "substitute f f i by f_f_i;" if (not reverse and len(old) > 1 and len(new) == 1 and len(new[0].glyphSet()) == 1 and num_lookups == 0): return ast.LigatureSubstStatement( location, old_prefix, old, old_suffix, list(new[0].glyphSet())[0], forceChain=hasMarks) # GSUB lookup type 8: Reverse chaining substitution. if reverse: if len(old) != 1: raise FeatureLibError( "In reverse chaining single substitutions, " "only a single glyph or glyph class can be replaced", location) if len(new) != 1: raise FeatureLibError( 'In reverse chaining single substitutions, ' 'the replacement (after "by") must be a single glyph ' 'or glyph class', location) if num_lookups != 0: raise FeatureLibError( "Reverse chaining substitutions cannot call named lookups", location) glyphs = sorted(list(old[0].glyphSet())) replacements = sorted(list(new[0].glyphSet())) if len(replacements) == 1: replacements = replacements * len(glyphs) if len(glyphs) != len(replacements): raise FeatureLibError( 'Expected a glyph class with %d elements after "by", ' 'but found a glyph class with %d elements' % (len(glyphs), len(replacements)), location) return ast.ReverseChainSingleSubstStatement( location, old_prefix, old_suffix, dict(zip(glyphs, replacements))) # GSUB lookup type 6: Chaining contextual substitution. assert len(new) == 0, new rule = ast.ChainContextSubstStatement( location, old_prefix, old, old_suffix, lookups) return rule