[feaLib] Imlement special support for feature aalt

This commit is contained in:
Sascha Brawer 2016-01-11 16:00:52 +01:00
parent 03796cf3a4
commit 2de6fc7744
7 changed files with 333 additions and 4 deletions

View File

@ -263,6 +263,16 @@ class CursivePosStatement(Statement):
self.location, self.glyphclass, self.entryAnchor, self.exitAnchor)
class FeatureReferenceStatement(Statement):
"""Example: feature salt;"""
def __init__(self, location, featureName):
Statement.__init__(self, location)
self.location, self.featureName = (location, featureName)
def build(self, builder):
builder.add_feature_reference(self.location, self.featureName)
class LanguageStatement(Statement):
def __init__(self, location, language, include_default, required):
Statement.__init__(self, location)

View File

@ -28,11 +28,15 @@ class Builder(object):
self.cur_feature_name_ = None
self.lookups_ = []
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.parseTree = None
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
# for feature 'aalt'
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
self.aalt_location_ = None
# for table 'GDEF'
self.attachPoints_ = {} # "a" --> {3, 7}
self.ligatureCaretByIndex_ = {} # "f_f_i" --> {3, 7}
self.ligatureCaretByPos_ = {} # "f_f_i" --> {300, 600}
self.parseTree = None
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
self.glyphClassDefs_ = {} # "fi" --> (2, (file, line, column))
self.markAttach_ = {} # "acute" --> (4, (file, line, column))
self.markAttachClassID_ = {} # frozenset({"acute", "grave"}) --> 4
@ -41,6 +45,7 @@ class Builder(object):
def build(self):
self.parseTree = Parser(self.featurefile_path).parse()
self.parseTree.build(self)
self.build_feature_aalt_()
for tag in ('GPOS', 'GSUB'):
table = self.makeTable(tag)
if (table.ScriptList.ScriptCount > 0 or
@ -93,6 +98,50 @@ class Builder(object):
self.cur_feature_name_)
return self.cur_lookup_
def build_feature_aalt_(self):
if not self.aalt_features_:
return
alternates = {} # glyph --> {glyph.alt1, glyph.alt2, ...}
for location, name in self.aalt_features_ + [(None, "aalt")]:
feature = [(script, lang, feature, lookups)
for (script, lang, feature), lookups
in self.features_.items()
if feature == name]
if not feature:
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)
single = {glyph: list(repl)[0] for glyph, repl in alternates.items()
if len(repl) == 1}
multi = {glyph: sorted(repl, key=self.font.getGlyphID)
for glyph, repl in alternates.items()
if len(repl) > 1}
if not single and not multi:
return
aalt_lookups = []
for (script, lang, feature), lookups in self.features_.items():
if feature == "aalt":
aalt_lookups.extend(lookups)
self.features_ = {(script, lang, feature): lookups
for (script, lang, feature), lookups
in self.features_.items()
if feature != "aalt"}
old_lookups = self.lookups_
self.lookups_ = []
self.start_feature(self.aalt_location_, "aalt")
if single:
self.add_single_subst(
self.aalt_location_, prefix=None, suffix=None, mapping=single)
for glyph, repl in multi.items():
self.add_multiple_subst(
self.aalt_location_, prefix=None, glyph=glyph, suffix=None,
replacements=repl)
self.end_feature()
self.lookups_.extend(old_lookups)
def buildGDEF(self):
gdef = otTables.GDEF()
gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
@ -331,6 +380,8 @@ class Builder(object):
self.language_systems = self.get_default_language_systems_()
self.cur_lookup_ = None
self.cur_feature_name_ = name
if name == "aalt":
self.aalt_location_ = location
def end_feature(self):
assert self.cur_feature_name_ is not None
@ -482,6 +533,13 @@ class Builder(object):
location)
lookup.alternates[glyph] = replacement
def add_feature_reference(self, location, featureName):
if self.cur_feature_name_ != "aalt":
raise FeatureLibError(
'Feature references are only allowed inside "feature aalt"',
location)
self.aalt_features_.append((location, featureName))
def add_ligature_subst(self, location,
prefix, glyphs, suffix, replacement):
if prefix or suffix:
@ -732,6 +790,10 @@ class LookupBuilder(object):
"""Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
return {}
def getAlternateGlyphs(self):
"""Helper for building 'aalt' features."""
return {}
def buildCoverage_(self, glyphs, tableClass=otTables.Coverage):
coverage = tableClass()
coverage.glyphs = sorted(glyphs, key=self.font.getGlyphID)
@ -822,6 +884,9 @@ class AlternateSubstBuilder(LookupBuilder):
subtable.alternates = self.alternates
return self.buildLookup_([subtable])
def getAlternateGlyphs(self):
return self.alternates
class ChainContextPosBuilder(LookupBuilder):
def __init__(self, font, location):
@ -882,6 +947,15 @@ class ChainContextSubstBuilder(LookupBuilder):
st.SubstLookupRecord.append(rec)
return self.buildLookup_(subtables)
def getAlternateGlyphs(self):
result = {}
for (_prefix, _input, _suffix, lookups) in self.substitutions:
for lookup in lookups:
alts = lookup.getAlternateGlyphs()
for glyph, replacements in alts.items():
result.setdefault(glyph, set()).update(replacements)
return result
class LigatureSubstBuilder(LookupBuilder):
def __init__(self, font, location):
@ -1189,6 +1263,9 @@ class SingleSubstBuilder(LookupBuilder):
subtable.mapping = self.mapping
return self.buildLookup_([subtable])
def getAlternateGlyphs(self):
return {glyph: set([repl]) for glyph, repl in self.mapping.items()}
class ClassPairPosSubtableBuilder(object):
def __init__(self, builder, valueFormat1, valueFormat2):

View File

@ -26,7 +26,8 @@ def makeTTFont():
"A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc "
"N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc "
"A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 "
"d.alt e.begin n.end s.end "
"a.alt1 a.alt2 a.alt3 b.alt c.mid d.alt d.mid e.begin e.mid "
"n.end s.end "
"f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t "
"ydieresis yacute "
"grave acute dieresis macron circumflex cedilla umlaut ogonek caron "
@ -167,11 +168,22 @@ class BuilderTest(unittest.TestCase):
def test_spec(self):
for name in ("4h1 5d1 5d2 5fi1 5fi2 5fi3 5fi4 5h1 "
"6d2 6e 6f 6h_ii 9b").split():
"6d2 6e 6f 6h_ii 8a 9b").split():
font = makeTTFont()
addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font)
self.expect_ttx(font, self.getpath("spec%s.ttx" % name))
def test_feature_outside_aalt(self):
self.assertRaisesRegex(
FeatureLibError,
'Feature references are only allowed inside "feature aalt"',
self.build, "feature test { feature test; } test;")
def test_feature_undefinedReference(self):
self.assertRaisesRegex(
FeatureLibError, 'Feature none has not been defined',
self.build, "feature aalt { feature none; } aalt;")
def test_GlyphClassDef_conflictingClasses(self):
self.assertRaisesRegex(
FeatureLibError, "Glyph X was assigned to a different class",

View File

@ -765,6 +765,13 @@ class Parser(object):
self.parse_block_(block, vertical)
return block
def parse_feature_reference_(self):
assert self.cur_token_ == "feature", self.cur_token_
location = self.cur_token_location_
featureName = self.expect_tag_()
self.expect_symbol_(";")
return ast.FeatureReferenceStatement(location, featureName)
def parse_block_(self, block, vertical):
self.expect_symbol_("{")
for symtab in self.symbol_tables_:
@ -779,6 +786,8 @@ class Parser(object):
statements.append(self.parse_anchordef_())
elif self.is_cur_keyword_({"enum", "enumerate"}):
statements.append(self.parse_enumerate_(vertical=vertical))
elif self.is_cur_keyword_("feature"):
statements.append(self.parse_feature_reference_())
elif self.is_cur_keyword_("ignore"):
statements.append(self.parse_ignore_())
elif self.is_cur_keyword_("language"):

View File

@ -132,6 +132,12 @@ class ParserTest(unittest.TestCase):
self.assertEqual(liga.name, "liga")
self.assertTrue(liga.use_extension)
def test_feature_reference(self):
doc = self.parse("feature aalt { feature salt; } aalt;")
ref = doc.statements[0].statements[0]
self.assertIsInstance(ref, ast.FeatureReferenceStatement)
self.assertEqual(ref.featureName, "salt")
def test_glyphclass(self):
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
self.assertEqual(gc.name, "dash")

View File

@ -0,0 +1,21 @@
languagesystem DFLT dflt;
languagesystem latn dflt;
languagesystem latn TRK;
languagesystem cyrl dflt;
feature aalt {
feature salt;
feature smcp;
substitute d by d.alt;
} aalt;
feature smcp {
sub [a-c] by [A.sc-C.sc];
sub f i by f_i; # not considered for aalt
} smcp;
feature salt {
sub a from [a.alt1 a.alt2 a.alt3];
sub e [c d e]' f by [c.mid d.mid e.mid];
sub b by b.alt;
} salt;

194
Lib/fontTools/feaLib/testdata/spec8a.ttx vendored Normal file
View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GSUB>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=3 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=3 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
<FeatureIndex index="2" value="2"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
<ScriptRecord index="1">
<ScriptTag value="cyrl"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=3 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
<FeatureIndex index="2" value="2"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
<ScriptRecord index="2">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=3 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
<FeatureIndex index="2" value="2"/>
</DefaultLangSys>
<!-- LangSysCount=1 -->
<LangSysRecord index="0">
<LangSysTag value="TRK "/>
<LangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=3 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
<FeatureIndex index="2" value="2"/>
</LangSys>
</LangSysRecord>
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=3 -->
<FeatureRecord index="0">
<FeatureTag value="aalt"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="1"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="1">
<FeatureTag value="salt"/>
<Feature>
<!-- LookupCount=3 -->
<LookupListIndex index="0" value="5"/>
<LookupListIndex index="1" value="7"/>
<LookupListIndex index="2" value="8"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="2">
<FeatureTag value="smcp"/>
<Feature>
<!-- LookupCount=2 -->
<LookupListIndex index="0" value="3"/>
<LookupListIndex index="1" value="4"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=9 -->
<Lookup index="0">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="e" out="e.mid"/>
</SingleSubst>
</Lookup>
<Lookup index="1">
<!-- LookupType=2 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MultipleSubst index="0">
<Substitution in="a" out="A.sc,a.alt1,a.alt2,a.alt3"/>
<Substitution in="b" out="B.sc,b.alt"/>
<Substitution in="c" out="C.sc,c.mid"/>
<Substitution in="d" out="d.alt,d.mid"/>
</MultipleSubst>
</Lookup>
<Lookup index="2">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="d" out="d.alt"/>
</SingleSubst>
</Lookup>
<Lookup index="3">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="a" out="A.sc"/>
<Substitution in="b" out="B.sc"/>
<Substitution in="c" out="C.sc"/>
</SingleSubst>
</Lookup>
<Lookup index="4">
<!-- LookupType=4 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="f">
<Ligature components="i" glyph="f_i"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="5">
<!-- LookupType=3 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<AlternateSubst index="0" Format="1">
<AlternateSet glyph="a">
<Alternate glyph="a.alt1"/>
<Alternate glyph="a.alt2"/>
<Alternate glyph="a.alt3"/>
</AlternateSet>
</AlternateSubst>
</Lookup>
<Lookup index="6">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="c" out="c.mid"/>
<Substitution in="d" out="d.mid"/>
<Substitution in="e" out="e.mid"/>
</SingleSubst>
</Lookup>
<Lookup index="7">
<!-- LookupType=6 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ChainContextSubst index="0" Format="3">
<!-- BacktrackGlyphCount=1 -->
<BacktrackCoverage index="0">
<Glyph value="e"/>
</BacktrackCoverage>
<!-- InputGlyphCount=1 -->
<InputCoverage index="0">
<Glyph value="c"/>
<Glyph value="d"/>
<Glyph value="e"/>
</InputCoverage>
<!-- LookAheadGlyphCount=1 -->
<LookAheadCoverage index="0">
<Glyph value="f"/>
</LookAheadCoverage>
<!-- SubstCount=1 -->
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="6"/>
</SubstLookupRecord>
</ChainContextSubst>
</Lookup>
<Lookup index="8">
<!-- LookupType=1 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="b" out="b.alt"/>
</SingleSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>