[feaLib] Imlement special support for feature aalt
This commit is contained in:
parent
03796cf3a4
commit
2de6fc7744
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -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"):
|
||||
|
@ -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")
|
||||
|
21
Lib/fontTools/feaLib/testdata/spec8a.fea
vendored
Normal file
21
Lib/fontTools/feaLib/testdata/spec8a.fea
vendored
Normal 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
194
Lib/fontTools/feaLib/testdata/spec8a.ttx
vendored
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user