diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index e416c0c62..c630a514d 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -546,8 +546,9 @@ class ChainContextPosStatement(Statement):
res += " ".join(g.asFea() for g in self.prefix) + " "
for i, g in enumerate(self.glyphs):
res += g.asFea() + "'"
- if self.lookups[i] is not None:
- res += " lookup " + self.lookups[i].name
+ if self.lookups[i]:
+ for lu in self.lookups[i]:
+ res += " lookup " + lu.name
if i < len(self.glyphs) - 1:
res += " "
if len(self.suffix):
@@ -578,8 +579,9 @@ class ChainContextSubstStatement(Statement):
res += " ".join(g.asFea() for g in self.prefix) + " "
for i, g in enumerate(self.glyphs):
res += g.asFea() + "'"
- if self.lookups[i] is not None:
- res += " lookup " + self.lookups[i].name
+ if self.lookups[i]:
+ for lu in self.lookups[i]:
+ res += " lookup " + lu.name
if i < len(self.glyphs) - 1:
res += " "
if len(self.suffix):
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 52b23f1b2..d8c4476ce 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -203,9 +203,12 @@ class Builder(object):
raise FeatureLibError("Feature %s has not been defined" % name,
location)
for script, lang, feature, lookups in feature:
- for lookup in lookups:
- for glyph, alts in lookup.getAlternateGlyphs().items():
- alternates.setdefault(glyph, set()).update(alts)
+ for lookuplist in lookups:
+ if not isinstance(lookuplist, list):
+ lookuplist = [lookuplist]
+ for lookup in lookuplist:
+ for glyph, alts in lookup.getAlternateGlyphs().items():
+ alternates.setdefault(glyph, set()).update(alts)
single = {glyph: list(repl)[0] for glyph, repl in alternates.items()
if len(repl) == 1}
# TODO: Figure out the glyph alternate ordering used by makeotf.
@@ -797,9 +800,10 @@ class Builder(object):
If an input name is None, it gets mapped to a None LookupBuilder.
"""
lookup_builders = []
- for lookup in lookups:
- if lookup is not None:
- lookup_builders.append(self.named_lookups_.get(lookup.name))
+ for lookuplist in lookups:
+ if lookuplist is not None:
+ lookup_builders.append([self.named_lookups_.get(l.name)
+ for l in lookuplist])
else:
lookup_builders.append(None)
return lookup_builders
@@ -1259,18 +1263,23 @@ class ChainContextPosBuilder(LookupBuilder):
self.setLookAheadCoverage_(suffix, st)
self.setInputCoverage_(glyphs, st)
- st.PosCount = len([l for l in lookups if l is not None])
+ st.PosCount = 0
st.PosLookupRecord = []
- for sequenceIndex, l in enumerate(lookups):
- if l is not None:
- if l.lookup_index is None:
- raise FeatureLibError('Missing index of the specified '
- 'lookup, might be a substitution lookup',
- self.location)
- rec = otTables.PosLookupRecord()
- rec.SequenceIndex = sequenceIndex
- rec.LookupListIndex = l.lookup_index
- st.PosLookupRecord.append(rec)
+ for sequenceIndex, lookupList in enumerate(lookups):
+ if lookupList is not None:
+ if not isinstance(lookupList, list):
+ # Can happen with synthesised lookups
+ lookupList = [ lookupList ]
+ for l in lookupList:
+ st.PosCount += 1
+ if l.lookup_index is None:
+ raise FeatureLibError('Missing index of the specified '
+ 'lookup, might be a substitution lookup',
+ self.location)
+ rec = otTables.PosLookupRecord()
+ rec.SequenceIndex = sequenceIndex
+ rec.LookupListIndex = l.lookup_index
+ st.PosLookupRecord.append(rec)
return self.buildLookup_(subtables)
def find_chainable_single_pos(self, lookups, glyphs, value):
@@ -1310,30 +1319,38 @@ class ChainContextSubstBuilder(LookupBuilder):
self.setLookAheadCoverage_(suffix, st)
self.setInputCoverage_(input, st)
- st.SubstCount = len([l for l in lookups if l is not None])
+ st.SubstCount = 0
st.SubstLookupRecord = []
- for sequenceIndex, l in enumerate(lookups):
- if l is not None:
- if l.lookup_index is None:
- raise FeatureLibError('Missing index of the specified '
- 'lookup, might be a positioning lookup',
- self.location)
- rec = otTables.SubstLookupRecord()
- rec.SequenceIndex = sequenceIndex
- rec.LookupListIndex = l.lookup_index
- st.SubstLookupRecord.append(rec)
+ for sequenceIndex, lookupList in enumerate(lookups):
+ if lookupList is not None:
+ if not isinstance(lookupList, list):
+ # Can happen with synthesised lookups
+ lookupList = [ lookupList ]
+ for l in lookupList:
+ st.SubstCount += 1
+ if l.lookup_index is None:
+ raise FeatureLibError('Missing index of the specified '
+ 'lookup, might be a positioning lookup',
+ self.location)
+ rec = otTables.SubstLookupRecord()
+ rec.SequenceIndex = sequenceIndex
+ rec.LookupListIndex = l.lookup_index
+ st.SubstLookupRecord.append(rec)
return self.buildLookup_(subtables)
def getAlternateGlyphs(self):
result = {}
- for (_, _, _, lookups) in self.substitutions:
- if lookups == self.SUBTABLE_BREAK_:
+ for (_, _, _, lookuplist) in self.substitutions:
+ if lookuplist == self.SUBTABLE_BREAK_:
continue
- for lookup in lookups:
- if lookup is not None:
- alts = lookup.getAlternateGlyphs()
- for glyph, replacements in alts.items():
- result.setdefault(glyph, set()).update(replacements)
+ for lookups in lookuplist:
+ if not isinstance(lookups, list):
+ lookups = [lookups]
+ for lookup in lookups:
+ if lookup is not None:
+ alts = lookup.getAlternateGlyphs()
+ for glyph, replacements in alts.items():
+ result.setdefault(glyph, set()).update(replacements)
return result
def find_chainable_single_subst(self, glyphs):
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index a3eaf6261..6f42fbe4d 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -404,8 +404,10 @@ class Parser(object):
else:
values.append(None)
- lookup = None
- if self.next_token_ == "lookup":
+ lookuplist = None
+ while self.next_token_ == "lookup":
+ if lookuplist is None:
+ lookuplist = []
self.expect_keyword_("lookup")
if not marked:
raise FeatureLibError(
@@ -417,8 +419,9 @@ class Parser(object):
raise FeatureLibError(
'Unknown lookup "%s"' % lookup_name,
self.cur_token_location_)
+ lookuplist.append(lookup)
if marked:
- lookups.append(lookup)
+ lookups.append(lookuplist)
if not glyphs and not suffix: # eg., "sub f f i by"
assert lookups == []
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 5ce4cc266..f2f1c05d6 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -71,7 +71,8 @@ class BuilderTest(unittest.TestCase):
ZeroValue_ChainSinglePos_horizontal ZeroValue_ChainSinglePos_vertical
PairPosSubtable ChainSubstSubtable ChainPosSubtable LigatureSubtable
AlternateSubtable MultipleSubstSubtable SingleSubstSubtable
- aalt_chain_contextual_subst AlternateChained
+ aalt_chain_contextual_subst AlternateChained MultipleLookupsPerGlyph
+ MultipleLookupsPerGlyph2
""".split()
def __init__(self, methodName):
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph.fea b/Tests/feaLib/data/MultipleLookupsPerGlyph.fea
new file mode 100644
index 000000000..e0c22226b
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph.fea
@@ -0,0 +1,11 @@
+lookup a_to_bc {
+ sub a by b c;
+} a_to_bc;
+
+lookup b_to_d {
+ sub b by d;
+} b_to_d;
+
+feature test {
+ sub a' lookup a_to_bc lookup b_to_d b;
+} test;
\ No newline at end of file
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx b/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx
new file mode 100644
index 000000000..927694cbc
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph.ttx
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea b/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea
new file mode 100644
index 000000000..5a9d19b24
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph2.fea
@@ -0,0 +1,11 @@
+lookup a_reduce_sb {
+ pos a <-80 0 -160 0>;
+} a_reduce_sb;
+
+lookup a_raise {
+ pos a <0 100 0 0>;
+} a_raise;
+
+feature test {
+ pos a' lookup a_reduce_sb lookup a_raise b;
+} test;
\ No newline at end of file
diff --git a/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx b/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx
new file mode 100644
index 000000000..008d95b65
--- /dev/null
+++ b/Tests/feaLib/data/MultipleLookupsPerGlyph2.ttx
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py
index d05a82448..87b8c96a4 100644
--- a/Tests/feaLib/parser_test.py
+++ b/Tests/feaLib/parser_test.py
@@ -1065,7 +1065,7 @@ class ParserTest(unittest.TestCase):
self.assertEqual(glyphstr(pos.prefix), "[A a] [B b]")
self.assertEqual(glyphstr(pos.glyphs), "I [N n] P")
self.assertEqual(glyphstr(pos.suffix), "[Y y] [Z z]")
- self.assertEqual(pos.lookups, [lookup1, lookup2, None])
+ self.assertEqual(pos.lookups, [[lookup1], [lookup2], None])
def test_gpos_type_8_lookup_with_values(self):
self.assertRaisesRegex(
@@ -1508,8 +1508,8 @@ class ParserTest(unittest.TestCase):
def test_substitute_lookups(self): # GSUB LookupType 6
doc = Parser(self.getpath("spec5fi1.fea"), GLYPHNAMES).parse()
[_, _, _, langsys, ligs, sub, feature] = doc.statements
- self.assertEqual(feature.statements[0].lookups, [ligs, None, sub])
- self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
+ self.assertEqual(feature.statements[0].lookups, [[ligs], None, [sub]])
+ self.assertEqual(feature.statements[1].lookups, [[ligs], None, [sub]])
def test_substitute_missing_by(self):
self.assertRaisesRegex(