diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index f3125e82d..41112baec 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -28,6 +28,12 @@ class GlyphClassDefinition(object): self.glyphs = glyphs +class AlternateSubstitution(object): + def __init__(self, location, glyph, from_class): + self.location = location + self.glyph, self.from_class = (glyph, from_class) + + class AnchorDefinition(object): def __init__(self, location, name, x, y, contourpoint): self.location = location diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index e01f1a8f1..45d09210f 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -130,7 +130,7 @@ class Parser(object): def parse_glyph_pattern_(self): prefix, glyphs, lookups, suffix = ([], [], [], []) - while self.next_token_ not in {"by", ";"}: + while self.next_token_ not in {"by", "from", ";"}: gc = self.parse_glyphclass_(accept_glyphname=True) marked = False if self.next_token_ == "'": @@ -218,15 +218,31 @@ class Parser(object): assert self.cur_token_ in {"substitute", "sub"} location = self.cur_token_location_ old_prefix, old, lookups, old_suffix = self.parse_glyph_pattern_() + if self.next_token_ == "by": - self.expect_keyword_("by") + keyword = self.expect_keyword_("by") new = [self.parse_glyphclass_(accept_glyphname=True)] + elif self.next_token_ == "from": + keyword = self.expect_keyword_("from") + new = [self.parse_glyphclass_(accept_glyphname=False)] else: + keyword = None new = [] self.expect_symbol_(";") if len(new) is 0 and not any(lookups): - raise ParserError('Expected "by" or explicit lookup references', - self.cur_token_location_) + raise ParserError( + 'Expected "by", "from" or explicit lookup references', + self.cur_token_location_) + + if keyword == "from": + if len(old) != 1 or len(old[0]) != 1: + raise ParserError('Expected a single glyph before "from"', + location) + if len(new) != 1: + raise ParserError('Expected a single glyphclass after "from"', + location) + return ast.AlternateSubstitution(location, list(old[0])[0], new[0]) + rule = ast.SubstitutionRule(location, old, new) rule.old_prefix, rule.old_suffix = old_prefix, old_suffix rule.lookups = lookups diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index 52ab7ca48..6bb8bc0de 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -289,6 +289,25 @@ class ParserTest(unittest.TestCase): self.assertEqual(sub.new, [{"A.sc", "B.sc", "C.sc", "D.sc"}]) self.assertEqual(sub.lookups, [None]) + def test_substitute_from(self): # GSUB LookupType 3 + doc = self.parse("feature test {" + " substitute a from [a.1 a.2 a.3];" + "} test;") + sub = doc.statements[0].statements[0] + self.assertEqual(type(sub), ast.AlternateSubstitution) + self.assertEqual(sub.glyph, "a") + self.assertEqual(sub.from_class, {"a.1", "a.2", "a.3"}) + + def test_substitute_from_glyphclass(self): # GSUB LookupType 3 + doc = self.parse("feature test {" + " @Ampersands = [ampersand.1 ampersand.2];" + " substitute ampersand from @Ampersands;" + "} test;") + [glyphclass, sub] = doc.statements[0].statements + self.assertEqual(type(sub), ast.AlternateSubstitution) + self.assertEqual(sub.glyph, "ampersand") + self.assertEqual(sub.from_class, {"ampersand.1", "ampersand.2"}) + def test_substitute_ligature(self): # GSUB LookupType 4 doc = self.parse("feature liga {substitute f f i by f_f_i;} liga;") sub = doc.statements[0].statements[0] @@ -306,7 +325,7 @@ class ParserTest(unittest.TestCase): def test_substitute_missing_by(self): self.assertRaisesRegex( - ParserError, 'Expected "by" or explicit lookup references', + ParserError, 'Expected "by", "from" or explicit lookup references', self.parse, "feature liga {substitute f f i;} liga;") def test_subtable(self):