[feaLib] Implement GSUB chain substitution rules

This commit is contained in:
Sascha Brawer 2015-11-30 15:02:09 +01:00
parent 196254ae1e
commit 152dff4361
6 changed files with 97 additions and 6 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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())

View File

@ -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])

View File

@ -2,6 +2,8 @@
# "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;

View File

@ -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 -->