[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:
parent
e19d5a8cbe
commit
db49f20d6b
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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])
|
||||
|
||||
|
@ -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;")
|
||||
|
5
Lib/fontTools/feaLib/testdata/GSUB_6.fea
vendored
5
Lib/fontTools/feaLib/testdata/GSUB_6.fea
vendored
@ -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;
|
||||
|
43
Lib/fontTools/feaLib/testdata/GSUB_6.ttx
vendored
43
Lib/fontTools/feaLib/testdata/GSUB_6.ttx
vendored
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user