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 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