From 152dff436171b931d50b3eac148112d4aae1de06 Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Mon, 30 Nov 2015 15:02:09 +0100 Subject: [PATCH] [feaLib] Implement GSUB chain substitution rules --- Lib/fontTools/feaLib/ast.py | 6 ++ Lib/fontTools/feaLib/builder.py | 66 ++++++++++++++++++++++ Lib/fontTools/feaLib/builder_test.py | 3 +- Lib/fontTools/feaLib/parser_test.py | 2 +- Lib/fontTools/feaLib/testdata/spec5fi1.fea | 4 +- Lib/fontTools/feaLib/testdata/spec5fi1.ttx | 22 +++++++- 6 files changed, 97 insertions(+), 6 deletions(-) diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index dd6b8f1b2..40f20256e 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -169,6 +169,12 @@ class SubstitutionRule(Statement): self.old_suffix = [] self.lookups = [None] * len(old) + def build(self, builder): + builder.add_substitution( + self.location, + self.old_prefix, self.old, self.old_suffix, + self.new, self.lookups) + class ValueRecord(Statement): def __init__(self, location, xPlacement, yPlacement, xAdvance, yAdvance): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index b81c824f7..ce4e04e77 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -247,6 +247,19 @@ class Builder(object): self.set_language(location, "dflt", include_default=True, required=False) + def add_substitution(self, location, old_prefix, old, old_suffix, new, + lookups): + assert len(new) == 0, new + lookup_builders = [] + for lookup in lookups: + if lookup is not None: + lookup_builders.append(self.named_lookups_.get(lookup.name)) + else: + lookup_builders.append(None) + lookup = self.get_lookup_(location, ChainContextSubstBuilder) + lookup.substitutions.append((old_prefix, old, old_suffix, + lookup_builders)) + def add_alternate_substitution(self, location, glyph, from_class): lookup = self.get_lookup_(location, AlternateSubstBuilder) if glyph in lookup.alternates: @@ -314,6 +327,59 @@ class AlternateSubstBuilder(LookupBuilder): return lookup +class ChainContextSubstBuilder(LookupBuilder): + def __init__(self, location, lookup_flag): + LookupBuilder.__init__(self, location, 'GSUB', 6, lookup_flag) + self.substitutions = [] # (prefix, input, suffix, lookups) + + def equals(self, other): + return (LookupBuilder.equals(self, other) and + self.substitutions == other.substitutions) + + def build(self): + lookup = otTables.Lookup() + lookup.SubTable = [] + for (prefix, input, suffix, lookups) in self.substitutions: + st = otTables.ChainContextSubst() + lookup.SubTable.append(st) + st.Format = 3 + + st.BacktrackGlyphCount = len(prefix) + st.BacktrackCoverage = [] + for p in reversed(prefix): + coverage = otTables.BacktrackCoverage() + coverage.glyphs = sorted(list(p)) + st.BacktrackCoverage.append(coverage) + + st.InputGlyphCount = len(input) + st.InputCoverage = [] + for i in input: + coverage = otTables.InputCoverage() + coverage.glyphs = sorted(list(i)) + st.InputCoverage.append(coverage) + + st.LookAheadGlyphCount = len(suffix) + st.LookAheadCoverage = [] + for s in suffix: + coverage = otTables.LookAheadCoverage() + coverage.glyphs = sorted(list(s)) + st.LookAheadCoverage.append(coverage) + + st.SubstCount = len([l for l in lookups if l is not None]) + st.SubstLookupRecord = [] + for sequenceIndex, l in enumerate(lookups): + if l is not None: + rec = otTables.SubstLookupRecord() + rec.SequenceIndex = sequenceIndex + rec.LookupListIndex = l.lookup_index + st.SubstLookupRecord.append(rec) + + lookup.LookupFlag = self.lookup_flag + lookup.LookupType = self.lookup_type + lookup.SubTableCount = len(lookup.SubTable) + return lookup + + class LigatureSubstBuilder(LookupBuilder): def __init__(self, location, lookup_flag): LookupBuilder.__init__(self, location, 'GSUB', 4, lookup_flag) diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index bc13f54c9..f4ff12c07 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -136,8 +136,7 @@ class BuilderTest(unittest.TestCase): # OpenType Feature File specification, section 5.f.i, example 1. font = TTFont() addOpenTypeFeatures(self.getpath("spec5fi1.fea"), font) - # TODO: Fix the implementation until the test case passes. - # self.expect_ttx(font, self.getpath("spec5fi1.ttx")) + self.expect_ttx(font, self.getpath("spec5fi1.ttx")) def test_languagesystem(self): builder = Builder(None, TTFont()) diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index fb9433901..b9dfbc0d8 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -342,7 +342,7 @@ class ParserTest(unittest.TestCase): def test_substitute_lookups(self): doc = Parser(self.getpath("spec5fi1.fea")).parse() - [ligs, sub, feature] = doc.statements + [langsys, ligs, sub, feature] = doc.statements self.assertEqual(feature.statements[0].lookups, [ligs, None, sub]) self.assertEqual(feature.statements[1].lookups, [ligs, None, sub]) diff --git a/Lib/fontTools/feaLib/testdata/spec5fi1.fea b/Lib/fontTools/feaLib/testdata/spec5fi1.fea index eb29605b7..7d34421d5 100644 --- a/Lib/fontTools/feaLib/testdata/spec5fi1.fea +++ b/Lib/fontTools/feaLib/testdata/spec5fi1.fea @@ -2,11 +2,13 @@ # "Specifying a Chain Sub rule and marking sub-runs" # http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html +languagesystem latn dflt; + lookup CNTXT_LIGS { substitute f i by f_i; substitute c t by c_t; } CNTXT_LIGS; - + lookup CNTXT_SUB { substitute n by n.end; substitute s by s.end; diff --git a/Lib/fontTools/feaLib/testdata/spec5fi1.ttx b/Lib/fontTools/feaLib/testdata/spec5fi1.ttx index 1d74aef00..184b6644f 100644 --- a/Lib/fontTools/feaLib/testdata/spec5fi1.ttx +++ b/Lib/fontTools/feaLib/testdata/spec5fi1.ttx @@ -4,10 +4,28 @@ - + + + + + - + + + + + + + +