From dba1d6666b200520c36a32b083077cfaaceb634b Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Thu, 4 Feb 2016 15:31:29 +0100 Subject: [PATCH] [feaLib] Emit chains to LigatureSubst even without backtrack or lookahead https://github.com/behdad/fonttools/issues/506 --- Lib/fontTools/feaLib/ast.py | 8 +-- Lib/fontTools/feaLib/builder.py | 4 +- Lib/fontTools/feaLib/builder_test.py | 2 +- Lib/fontTools/feaLib/parser.py | 15 +++--- Lib/fontTools/feaLib/testdata/bug506.fea | 4 ++ Lib/fontTools/feaLib/testdata/bug506.ttx | 66 ++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 Lib/fontTools/feaLib/testdata/bug506.fea create mode 100644 Lib/fontTools/feaLib/testdata/bug506.ttx diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 77b9b8451..55c62dfdf 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -326,17 +326,19 @@ class LigatureCaretByPosStatement(Statement): class LigatureSubstStatement(Statement): - def __init__(self, location, prefix, glyphs, suffix, replacement): + def __init__(self, location, prefix, glyphs, suffix, replacement, + forceChain): Statement.__init__(self, location) self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) - self.replacement = replacement + self.replacement, self.forceChain = replacement, forceChain def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] glyphs = [g.glyphSet() for g in self.glyphs] suffix = [s.glyphSet() for s in self.suffix] builder.add_ligature_subst( - self.location, prefix, glyphs, suffix, self.replacement) + self.location, prefix, glyphs, suffix, self.replacement, + self.forceChain) class LookupFlagStatement(Statement): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 7b6203684..91c497e5a 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -512,8 +512,8 @@ class Builder(object): self.aalt_features_.append((location, featureName)) def add_ligature_subst(self, location, - prefix, glyphs, suffix, replacement): - if prefix or suffix: + prefix, glyphs, suffix, replacement, forceChain): + if prefix or suffix or forceChain: chain = self.get_lookup_(location, ChainContextSubstBuilder) lookup = self.get_chained_lookup_(location, LigatureSubstBuilder) chain.substitutions.append((prefix, glyphs, suffix, [lookup])) diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index 1dffe1b49..c1585236f 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -52,7 +52,7 @@ class BuilderTest(unittest.TestCase): spec5f_ii_1 spec5h1 spec6b_ii spec6d2 spec6e spec6f spec6h_ii spec6h_iii_1 spec8a spec9b spec9c1 spec9c2 spec9c3 - bug463 bug501 bug502 bug505 + bug463 bug501 bug502 bug505 bug506 """.split() def __init__(self, methodName): diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 8468e477b..bb7fe51cd 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -241,12 +241,13 @@ class Parser(object): def parse_glyph_pattern_(self, vertical): prefix, glyphs, lookups, values, suffix = ([], [], [], [], []) + hasMarks = False while self.next_token_ not in {"by", "from", ";"}: gc = self.parse_glyphclass_(accept_glyphname=True) marked = False if self.next_token_ == "'": self.expect_symbol_("'") - marked = True + hasMarks = marked = True if marked: glyphs.append(gc) elif glyphs: @@ -277,18 +278,18 @@ class Parser(object): if not glyphs and not suffix: # eg., "sub f f i by" assert lookups == [] - return ([], prefix, [None] * len(prefix), values, []) + return ([], prefix, [None] * len(prefix), values, [], hasMarks) else: assert not any(values[:len(prefix)]), values values = values[len(prefix):][:len(glyphs)] - return (prefix, glyphs, lookups, values, suffix) + return (prefix, glyphs, lookups, values, suffix, hasMarks) def parse_ignore_(self): assert self.is_cur_keyword_("ignore") location = self.cur_token_location_ self.advance_lexer_() if self.cur_token_ in ["substitute", "sub"]: - prefix, glyphs, lookups, values, suffix = \ + prefix, glyphs, lookups, values, suffix, hasMarks = \ self.parse_glyph_pattern_(vertical=False) self.expect_symbol_(";") if any(lookups): @@ -423,7 +424,7 @@ class Parser(object): return self.parse_position_mark_(enumerated, vertical) location = self.cur_token_location_ - prefix, glyphs, lookups, values, suffix = \ + prefix, glyphs, lookups, values, suffix, hasMarks = \ self.parse_glyph_pattern_(vertical) self.expect_symbol_(";") @@ -518,7 +519,7 @@ class Parser(object): 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 = \ + old_prefix, old, lookups, values, old_suffix, hasMarks = \ self.parse_glyph_pattern_(vertical=False) if any(values): raise FeatureLibError( @@ -597,7 +598,7 @@ class Parser(object): num_lookups == 0): return ast.LigatureSubstStatement( location, old_prefix, old, old_suffix, - list(new[0].glyphSet())[0]) + list(new[0].glyphSet())[0], forceChain=hasMarks) # GSUB lookup type 8: Reverse chaining substitution. if reverse: diff --git a/Lib/fontTools/feaLib/testdata/bug506.fea b/Lib/fontTools/feaLib/testdata/bug506.fea new file mode 100644 index 000000000..62e4bbb91 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/bug506.fea @@ -0,0 +1,4 @@ +# https://github.com/behdad/fonttools/issues/506 +feature test { + sub f' i' by f_i; +} test; diff --git a/Lib/fontTools/feaLib/testdata/bug506.ttx b/Lib/fontTools/feaLib/testdata/bug506.ttx new file mode 100644 index 000000000..9a2385b7f --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/bug506.ttx @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +