[feaLib] Implement GSUB chain substitution rules
This commit is contained in:
parent
196254ae1e
commit
152dff4361
@ -169,6 +169,12 @@ class SubstitutionRule(Statement):
|
|||||||
self.old_suffix = []
|
self.old_suffix = []
|
||||||
self.lookups = [None] * len(old)
|
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):
|
class ValueRecord(Statement):
|
||||||
def __init__(self, location, xPlacement, yPlacement, xAdvance, yAdvance):
|
def __init__(self, location, xPlacement, yPlacement, xAdvance, yAdvance):
|
||||||
|
@ -247,6 +247,19 @@ class Builder(object):
|
|||||||
self.set_language(location, "dflt",
|
self.set_language(location, "dflt",
|
||||||
include_default=True, required=False)
|
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):
|
def add_alternate_substitution(self, location, glyph, from_class):
|
||||||
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
||||||
if glyph in lookup.alternates:
|
if glyph in lookup.alternates:
|
||||||
@ -314,6 +327,59 @@ class AlternateSubstBuilder(LookupBuilder):
|
|||||||
return lookup
|
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):
|
class LigatureSubstBuilder(LookupBuilder):
|
||||||
def __init__(self, location, lookup_flag):
|
def __init__(self, location, lookup_flag):
|
||||||
LookupBuilder.__init__(self, location, 'GSUB', 4, lookup_flag)
|
LookupBuilder.__init__(self, location, 'GSUB', 4, lookup_flag)
|
||||||
|
@ -136,8 +136,7 @@ class BuilderTest(unittest.TestCase):
|
|||||||
# OpenType Feature File specification, section 5.f.i, example 1.
|
# OpenType Feature File specification, section 5.f.i, example 1.
|
||||||
font = TTFont()
|
font = TTFont()
|
||||||
addOpenTypeFeatures(self.getpath("spec5fi1.fea"), font)
|
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):
|
def test_languagesystem(self):
|
||||||
builder = Builder(None, TTFont())
|
builder = Builder(None, TTFont())
|
||||||
|
@ -342,7 +342,7 @@ class ParserTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_substitute_lookups(self):
|
def test_substitute_lookups(self):
|
||||||
doc = Parser(self.getpath("spec5fi1.fea")).parse()
|
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[0].lookups, [ligs, None, sub])
|
||||||
self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
|
self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
|
||||||
|
|
||||||
|
4
Lib/fontTools/feaLib/testdata/spec5fi1.fea
vendored
4
Lib/fontTools/feaLib/testdata/spec5fi1.fea
vendored
@ -2,11 +2,13 @@
|
|||||||
# "Specifying a Chain Sub rule and marking sub-runs"
|
# "Specifying a Chain Sub rule and marking sub-runs"
|
||||||
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
||||||
|
|
||||||
|
languagesystem latn dflt;
|
||||||
|
|
||||||
lookup CNTXT_LIGS {
|
lookup CNTXT_LIGS {
|
||||||
substitute f i by f_i;
|
substitute f i by f_i;
|
||||||
substitute c t by c_t;
|
substitute c t by c_t;
|
||||||
} CNTXT_LIGS;
|
} CNTXT_LIGS;
|
||||||
|
|
||||||
lookup CNTXT_SUB {
|
lookup CNTXT_SUB {
|
||||||
substitute n by n.end;
|
substitute n by n.end;
|
||||||
substitute s by s.end;
|
substitute s by s.end;
|
||||||
|
22
Lib/fontTools/feaLib/testdata/spec5fi1.ttx
vendored
22
Lib/fontTools/feaLib/testdata/spec5fi1.ttx
vendored
@ -4,10 +4,28 @@
|
|||||||
<GSUB>
|
<GSUB>
|
||||||
<Version value="1.0"/>
|
<Version value="1.0"/>
|
||||||
<ScriptList>
|
<ScriptList>
|
||||||
<!-- ScriptCount=0 -->
|
<!-- ScriptCount=1 -->
|
||||||
|
<ScriptRecord index="0">
|
||||||
|
<ScriptTag value="latn"/>
|
||||||
|
<Script>
|
||||||
|
<DefaultLangSys>
|
||||||
|
<ReqFeatureIndex value="65535"/>
|
||||||
|
<!-- FeatureCount=1 -->
|
||||||
|
<FeatureIndex index="0" value="0"/>
|
||||||
|
</DefaultLangSys>
|
||||||
|
<!-- LangSysCount=0 -->
|
||||||
|
</Script>
|
||||||
|
</ScriptRecord>
|
||||||
</ScriptList>
|
</ScriptList>
|
||||||
<FeatureList>
|
<FeatureList>
|
||||||
<!-- FeatureCount=0 -->
|
<!-- FeatureCount=1 -->
|
||||||
|
<FeatureRecord index="0">
|
||||||
|
<FeatureTag value="test"/>
|
||||||
|
<Feature>
|
||||||
|
<!-- LookupCount=1 -->
|
||||||
|
<LookupListIndex index="0" value="2"/>
|
||||||
|
</Feature>
|
||||||
|
</FeatureRecord>
|
||||||
</FeatureList>
|
</FeatureList>
|
||||||
<LookupList>
|
<LookupList>
|
||||||
<!-- LookupCount=3 -->
|
<!-- LookupCount=3 -->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user