[feaLib] Support compact syntax for chaining to alternate substitutions

https://github.com/behdad/fonttools/issues/445

Not sure whether it makes much sense to define a contextual chain
that points to a GSUB type 3, but the OpenType feature file syntax
does not explicitly forbid it.  Adobe's `makeotf` tool rejects this
kind of input with "Contextual alternate rule not yet supported";
this implies that the construct is valid (albeit definitely exotic).
This commit is contained in:
Sascha Brawer 2016-01-07 11:32:54 +01:00
parent e19d5a8cbe
commit db49f20d6b
6 changed files with 88 additions and 19 deletions

View File

@ -159,12 +159,20 @@ class MarkClassDefinition(Statement):
class AlternateSubstStatement(Statement):
def __init__(self, location, glyph, from_class):
def __init__(self, location, prefix, glyph, suffix, replacement):
Statement.__init__(self, location)
self.glyph, self.from_class = (glyph, from_class)
self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
self.replacement = replacement
def build(self, builder):
builder.add_alternate_subst(self.location, self.glyph, self.from_class)
glyph = self.glyph.glyphSet()
assert len(glyph) == 1, glyph
glyph = list(glyph)[0]
prefix = [p.glyphSet() for p in self.prefix]
suffix = [s.glyphSet() for s in self.suffix]
replacement = self.replacement.glyphSet()
builder.add_alternate_subst(self.location, prefix, glyph, suffix,
replacement)
class Anchor(Expression):

View File

@ -403,13 +403,19 @@ class Builder(object):
lookup.substitutions.append((prefix, glyphs, suffix,
self.find_lookup_builders_(lookups)))
def add_alternate_subst(self, location, glyph, from_class):
lookup = self.get_lookup_(location, AlternateSubstBuilder)
def add_alternate_subst(self, location,
prefix, glyph, suffix, replacement):
if prefix or suffix:
lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
chain = self.get_lookup_(location, ChainContextSubstBuilder)
chain.substitutions.append((prefix, [glyph], suffix, [lookup]))
else:
lookup = self.get_lookup_(location, AlternateSubstBuilder)
if glyph in lookup.alternates:
raise FeatureLibError(
'Already defined alternates for glyph "%s"' % glyph,
location)
lookup.alternates[glyph] = from_class
lookup.alternates[glyph] = replacement
def add_ligature_subst(self, location,
prefix, glyphs, suffix, replacement):

View File

@ -473,9 +473,8 @@ class Parser(object):
raise FeatureLibError(
'Expected a single glyphclass after "from"',
location)
return ast.AlternateSubstStatement(location,
list(old[0].glyphSet())[0],
new[0].glyphSet())
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])

View File

@ -819,9 +819,22 @@ class ParserTest(unittest.TestCase):
" substitute a from [a.1 a.2 a.3];"
"} test;")
sub = doc.statements[0].statements[0]
self.assertEqual(type(sub), ast.AlternateSubstStatement)
self.assertEqual(sub.glyph, "a")
self.assertEqual(sub.from_class, {"a.1", "a.2", "a.3"})
self.assertIsInstance(sub, ast.AlternateSubstStatement)
self.assertEqual(glyphstr(sub.prefix), "")
self.assertEqual(glyphstr([sub.glyph]), "a")
self.assertEqual(glyphstr(sub.suffix), "")
self.assertEqual(glyphstr([sub.replacement]), "[a.1 a.2 a.3]")
def test_substitute_from_chained(self): # chain to GSUB LookupType 3
doc = self.parse("feature test {"
" substitute A B a' [Y y] Z from [a.1 a.2 a.3];"
"} test;")
sub = doc.statements[0].statements[0]
self.assertIsInstance(sub, ast.AlternateSubstStatement)
self.assertEqual(glyphstr(sub.prefix), "A B")
self.assertEqual(glyphstr([sub.glyph]), "a")
self.assertEqual(glyphstr(sub.suffix), "[Y y] Z")
self.assertEqual(glyphstr([sub.replacement]), "[a.1 a.2 a.3]")
def test_substitute_from_glyphclass(self): # GSUB LookupType 3
doc = self.parse("feature test {"
@ -829,9 +842,12 @@ class ParserTest(unittest.TestCase):
" substitute ampersand from @Ampersands;"
"} test;")
[glyphclass, sub] = doc.statements[0].statements
self.assertEqual(type(sub), ast.AlternateSubstStatement)
self.assertEqual(sub.glyph, "ampersand")
self.assertEqual(sub.from_class, {"ampersand.1", "ampersand.2"})
self.assertIsInstance(sub, ast.AlternateSubstStatement)
self.assertEqual(glyphstr(sub.prefix), "")
self.assertEqual(glyphstr([sub.glyph]), "ampersand")
self.assertEqual(glyphstr(sub.suffix), "")
self.assertEqual(glyphstr([sub.replacement]),
"[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;")

View File

@ -7,6 +7,10 @@ lookup ChainedMultipleSubst {
substitute [A-C a-c] [D d] E c_t' V [W w] [X-Z x-z] by c t;
} ChainedMultipleSubst;
lookup ChainedAlternateSubst {
substitute [space comma semicolon] e' from [e e.begin];
} ChainedAlternateSubst;
lookup ChainedLigatureSubst {
substitute A [C c]' [T t]' Z by c_t;
} ChainedLigatureSubst;
@ -14,5 +18,6 @@ lookup ChainedLigatureSubst {
feature test {
lookup ChainedSingleSubst;
lookup ChainedMultipleSubst;
lookup ChainedAlternateSubst;
lookup ChainedLigatureSubst;
} test;

View File

@ -22,15 +22,16 @@
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=3 -->
<!-- LookupCount=4 -->
<LookupListIndex index="0" value="1"/>
<LookupListIndex index="1" value="4"/>
<LookupListIndex index="2" value="6"/>
<LookupListIndex index="3" value="8"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=7 -->
<!-- LookupCount=9 -->
<Lookup index="0">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
@ -153,6 +154,40 @@
</ChainContextSubst>
</Lookup>
<Lookup index="5">
<!-- LookupType=3 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<AlternateSubst index="0" Format="1">
<AlternateSet glyph="e">
<Alternate glyph="e"/>
<Alternate glyph="e.begin"/>
</AlternateSet>
</AlternateSubst>
</Lookup>
<Lookup index="6">
<!-- LookupType=6 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
<BacktrackCoverage index="0">
<Glyph value="space"/>
<Glyph value="semicolon"/>
<Glyph value="comma"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="e"/>
</InputCoverage>
<!-- LookAheadGlyphCount=0 -->
<!-- SubstCount=1 -->
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="5"/>
</SubstLookupRecord>
</ChainContextSubst>
</Lookup>
<Lookup index="7">
<!-- LookupType=4 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
@ -167,7 +202,7 @@
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="6">
<Lookup index="8">
<!-- LookupType=6 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
@ -192,7 +227,7 @@
<!-- SubstCount=1 -->
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="5"/>
<LookupListIndex value="7"/>
</SubstLookupRecord>
</ChainContextSubst>
</Lookup>