diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 419760aad..6ef2501e0 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -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): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 3fea2a6e9..4e7c27e68 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -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) diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index 2d7db9c08..6a1ec4aae 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -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, diff --git a/Lib/fontTools/feaLib/testdata/language_required.fea b/Lib/fontTools/feaLib/testdata/language_required.fea new file mode 100644 index 000000000..3346613e0 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/language_required.fea @@ -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; diff --git a/Lib/fontTools/feaLib/testdata/language_required.ttx b/Lib/fontTools/feaLib/testdata/language_required.ttx new file mode 100644 index 000000000..d3447e222 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/language_required.ttx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +