[feaLib] Implement required qualifier on language statements

This commit is contained in:
Sascha Brawer 2015-09-08 15:55:54 +02:00
parent a3783e1095
commit a35291e8c1
5 changed files with 215 additions and 11 deletions

View File

@ -82,9 +82,9 @@ class LanguageStatement(Statement):
self.required = required
def build(self, builder):
# TODO(sascha): Handle required.
builder.set_language(location=self.location, language=self.language,
include_default=self.include_default)
include_default=self.include_default,
required=self.required)
class LanguageSystemStatement(Statement):

View File

@ -24,7 +24,8 @@ class Builder(object):
self.cur_lookup_name_ = None
self.cur_feature_name_ = None
self.lookups_ = []
self.features_ = {} # ('latn', 'DEU', 'smcp') --> [LookupBuilder*]
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
def build(self):
parsetree = Parser(self.featurefile_path).parse()
@ -86,7 +87,8 @@ class Builder(object):
# Build a table for mapping (tag, lookup_indices) to feature_index.
# For example, ('liga', (2,3,7)) --> 23.
feature_indices = {}
scripts = {} # 'cyrl' --> {'DEU': [23, 24]} for feature #23,24
required_feature_indices = {} # ('latn', 'DEU') --> 23
scripts = {} # 'latn' --> {'DEU': [23, 24]} for feature #23,24
for key, lookups in sorted(self.features_.items()):
script, lang, feature_tag = key
# l.lookup_index will be None when a lookup is not needed
@ -111,6 +113,8 @@ class Builder(object):
feature_indices[feature_key] = feature_index
scripts.setdefault(script, {}).setdefault(lang, []).append(
feature_index)
if self.required_features_.get((script, lang)) == feature_tag:
required_feature_indices[(script, lang)] = feature_index
# Build ScriptList.
for script, lang_features in sorted(scripts.items()):
@ -123,9 +127,19 @@ class Builder(object):
langrec = otTables.LangSysRecord()
langrec.LangSys = otTables.LangSys()
langrec.LangSys.LookupOrder = None
langrec.LangSys.ReqFeatureIndex = 0xFFFF
langrec.LangSys.FeatureCount = len(feature_indices)
langrec.LangSys.FeatureIndex = feature_indices
req_feature_index = \
required_feature_indices.get((script, lang))
if req_feature_index is None:
langrec.LangSys.ReqFeatureIndex = 0xFFFF
else:
langrec.LangSys.ReqFeatureIndex = req_feature_index
langrec.LangSys.FeatureIndex = [i for i in feature_indices
if i != req_feature_index]
langrec.LangSys.FeatureCount = \
len(langrec.LangSys.FeatureIndex)
if lang == "dflt":
srec.Script.DefaultLangSys = langrec.LangSys
else:
@ -187,7 +201,7 @@ class Builder(object):
self.cur_lookup_name_ = None
self.cur_lookup_ = None
def set_language(self, location, language, include_default):
def set_language(self, location, language, include_default, required):
assert(len(language) == 4)
if self.cur_lookup_name_:
raise FeatureLibError(
@ -204,6 +218,16 @@ class Builder(object):
langsys = set()
langsys.add((self.script_, language))
self.language_systems = frozenset(langsys)
if required:
key = (self.script_, language)
if key in self.required_features_:
raise FeatureLibError(
"Language %s (script %s) has already "
"specified feature %s as its required feature" % (
language.strip(), self.script_.strip(),
self.required_features_[key].strip()),
location)
self.required_features_[key] = self.cur_feature_name_
def set_script(self, location, script):
if self.cur_lookup_name_:
@ -217,7 +241,8 @@ class Builder(object):
self.cur_lookup_ = None
self.script_ = script
self.lookup_flag_ = 0
self.set_language(location, 'dflt', include_default=True)
self.set_language(location, "dflt",
include_default=True, required=False)
def add_alternate_substitution(self, location, glyph, from_class):
lookup = self.get_lookup_(location, AlternateSubstBuilder)

View File

@ -174,10 +174,10 @@ class BuilderTest(unittest.TestCase):
builder.start_feature(location=None, name='test')
builder.set_script(location=None, script='cyrl')
builder.set_language(location=None, language='RUS ',
include_default=False)
include_default=False, required=False)
self.assertEqual(builder.language_systems, {('cyrl', 'RUS ')})
builder.set_language(location=None, language='BGR ',
include_default=True)
include_default=True, required=False)
self.assertEqual(builder.language_systems,
{('latn', 'FRA '), ('cyrl', 'BGR ')})
@ -200,6 +200,28 @@ class BuilderTest(unittest.TestCase):
"Language statements are not allowed within \"feature size\"",
self.build, "feature size { language FRA; } size;")
def test_language_required(self):
font = TTFont()
addOpenTypeFeatures(self.getpath("language_required.fea"), font)
self.expect_ttx(font, self.getpath("language_required.ttx"))
def test_language_required_duplicate(self):
self.assertRaisesRegex(
FeatureLibError,
r"Language FRA \(script latn\) has already specified "
"feature scmp as its required feature",
self.build,
"feature scmp {"
" script latn;"
" language FRA required;"
" language DEU required;"
" substitute [a-z] by [A.sc-Z.sc];"
"} scmp;"
"feature test {"
" language FRA required;"
" substitute [a-z] by [A.sc-Z.sc];"
"} test;")
def test_lookup_already_defined(self):
self.assertRaisesRegex(
FeatureLibError,

View File

@ -0,0 +1,21 @@
languagesystem latn DEU;
languagesystem latn FRA;
languagesystem latn ITA;
feature hlig {
script latn;
language DEU exclude_dflt required;
sub D E U by D_E_U;
language FRA exclude_dflt;
sub F R A by D_E_U;
} hlig;
feature liga {
language ITA exclude_dflt required;
sub I T A by I_T_A;
} liga;
feature scmp {
sub [a-z] by [A.sc-Z.sc];
} scmp;

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GSUB>
<GSUB>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<!-- LangSysCount=3 -->
<LangSysRecord index="0">
<LangSysTag value="DEU "/>
<LangSys>
<ReqFeatureIndex value="0"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="1"/>
</LangSys>
</LangSysRecord>
<LangSysRecord index="1">
<LangSysTag value="FRA "/>
<LangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=2 -->
<FeatureIndex index="0" value="2"/>
<FeatureIndex index="1" value="1"/>
</LangSys>
</LangSysRecord>
<LangSysRecord index="2">
<LangSysTag value="ITA "/>
<LangSys>
<ReqFeatureIndex value="3"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="1"/>
</LangSys>
</LangSysRecord>
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=4 -->
<FeatureRecord index="0">
<FeatureTag value="hlig"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="1">
<FeatureTag value="scmp"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="3"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="2">
<FeatureTag value="hlig"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="3">
<FeatureTag value="liga"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="2"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=4 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="D">
<Ligature components="D,E,U" glyph="D_E_U"/>
</LigatureSet>
</LigatureSubst>
<LigatureSubst index="1" Format="1">
<LigatureSet glyph="F">
<Ligature components="F,R,A" glyph="D_E_U"/>
</LigatureSet>
</LigatureSubst>
<LigatureSubst index="2" Format="1">
<LigatureSet glyph="I">
<Ligature components="I,T,A" glyph="I_T_A"/>
</LigatureSet>
</LigatureSubst>
<SingleSubst index="3">
<Substitution in="a" out="A.sc"/>
<Substitution in="b" out="B.sc"/>
<Substitution in="c" out="C.sc"/>
<Substitution in="d" out="D.sc"/>
<Substitution in="e" out="E.sc"/>
<Substitution in="f" out="F.sc"/>
<Substitution in="g" out="G.sc"/>
<Substitution in="h" out="H.sc"/>
<Substitution in="i" out="I.sc"/>
<Substitution in="j" out="J.sc"/>
<Substitution in="k" out="K.sc"/>
<Substitution in="l" out="L.sc"/>
<Substitution in="m" out="M.sc"/>
<Substitution in="n" out="N.sc"/>
<Substitution in="o" out="O.sc"/>
<Substitution in="p" out="P.sc"/>
<Substitution in="q" out="Q.sc"/>
<Substitution in="r" out="R.sc"/>
<Substitution in="s" out="S.sc"/>
<Substitution in="t" out="T.sc"/>
<Substitution in="u" out="U.sc"/>
<Substitution in="v" out="V.sc"/>
<Substitution in="w" out="W.sc"/>
<Substitution in="x" out="X.sc"/>
<Substitution in="y" out="Y.sc"/>
<Substitution in="z" out="Z.sc"/>
</SingleSubst>
</LookupList>
</GSUB>
</GSUB>
<GPOS>
<GPOS>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=0 -->
</ScriptList>
<FeatureList>
<!-- FeatureCount=0 -->
</FeatureList>
<LookupList>
<!-- LookupCount=0 -->
</LookupList>
</GPOS>
</GPOS>
</ttFont>