From 9feaab13aa71c708c3ad3ec0c48cb640c8f16988 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Fri, 18 Mar 2016 09:55:51 +0400 Subject: [PATCH] [feaLib] Support stylistic set featureNames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Does not handle featureNames for cvXX features, but it shouldn’t be hard for someone to extend the code to support them if inclined to do so. --- Lib/fontTools/feaLib/ast.py | 5 ++ Lib/fontTools/feaLib/builder.py | 33 ++++++++++++- Lib/fontTools/feaLib/builder_test.py | 2 +- Lib/fontTools/feaLib/parser.py | 61 +++++++++++++++++------ Lib/fontTools/feaLib/testdata/spec8c.fea | 11 +++++ Lib/fontTools/feaLib/testdata/spec8c.ttx | 62 ++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 16 deletions(-) create mode 100644 Lib/fontTools/feaLib/testdata/spec8c.fea create mode 100644 Lib/fontTools/feaLib/testdata/spec8c.ttx diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 9eac867ee..5fef07afa 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -591,3 +591,8 @@ class NameRecord(Statement): builder.add_name_record( self.location, self.nameID, self.platformID, self.platEncID, self.langID, self.string) + +class FeatureNameStatement(NameRecord): + def build(self, builder): + NameRecord.build(self, builder) + builder.add_featureName(self.location, self.nameID) diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index bc0235696..67cba60f5 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -35,6 +35,9 @@ class Builder(object): self.aalt_features_ = [] # [(location, featureName)*], for 'aalt' self.aalt_location_ = None self.aalt_alternates_ = {} + # for 'featureNames' + self.featureNames_ = [] + self.featureNames_ids_ = {} # for table 'head' self.fontRevision_ = None # 2.71 # for table 'name' @@ -157,14 +160,38 @@ class Builder(object): table.created = table.modified = 3406620153 # 2011-12-13 11:22:33 table.fontRevision = self.fontRevision_ + def get_user_name_id(self, table): + # Try to find first unused font-specific name id + nameIDs = [name.nameID for name in table.names] + for user_name_id in range(256, 32767): + if user_name_id not in nameIDs: + return user_name_id + + def buildFeatureParams(self, tag): + params = None + if tag in self.featureNames_: + assert tag in self.featureNames_ids_ + params = otTables.FeatureParamsStylisticSet() + params.Version = 0 + params.UINameID = self.featureNames_ids_[tag] + return params + def build_name(self): if not self.names_: return table = self.font.get("name") if not table: # this only happens for unit tests table = self.font["name"] = getTableClass("name")() + table.names = [] for name in self.names_: nameID, platformID, platEncID, langID, string = name + if not isinstance(nameID, int): + # A featureNames name and nameID is actually the tag + tag = nameID + if tag not in self.featureNames_ids_: + self.featureNames_ids_[tag] = self.get_user_name_id(table) + assert self.featureNames_ids_[tag] is not None + nameID = self.featureNames_ids_[tag] table.setName(string, nameID, platformID, platEncID, langID) def buildGDEF(self): @@ -276,7 +303,8 @@ class Builder(object): frec = otTables.FeatureRecord() frec.FeatureTag = feature_tag frec.Feature = otTables.Feature() - frec.Feature.FeatureParams = None + frec.Feature.FeatureParams = self.buildFeatureParams( + feature_tag) frec.Feature.LookupListIndex = lookup_indices frec.Feature.LookupCount = len(lookup_indices) table.FeatureList.FeatureRecord.append(frec) @@ -523,6 +551,9 @@ class Builder(object): location) self.aalt_features_.append((location, featureName)) + def add_featureName(self, location, tag): + self.featureNames_.append(tag) + def add_ligature_subst(self, location, prefix, glyphs, suffix, replacement, forceChain): if prefix or suffix or forceChain: diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index bb9d0b421..4c3c5ba46 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -56,7 +56,7 @@ class BuilderTest(unittest.TestCase): spec4h1 spec5d1 spec5d2 spec5fi1 spec5fi2 spec5fi3 spec5fi4 spec5f_ii_1 spec5f_ii_2 spec5f_ii_3 spec5f_ii_4 spec5h1 spec6b_ii spec6d2 spec6e spec6f - spec6h_ii spec6h_iii_1 spec6h_iii_3d spec8a + spec6h_ii spec6h_iii_1 spec6h_iii_3d spec8a spec8c spec9b spec9c1 spec9c2 spec9c3 spec9e bug453 bug463 bug501 bug502 bug505 bug506 bug509 bug512 name diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 1087f0917..a28d40e1e 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -726,16 +726,7 @@ class Parser(object): raise FeatureLibError("Expected nameid", self.cur_token_location_) - def parse_nameid_(self): - assert self.cur_token_ == "nameid", self.cur_token_ - location, nameID = self.cur_token_location_, self.expect_number_() - if nameID > 255: - raise FeatureLibError("Expected name id < 255", - self.cur_token_location_) - if 1 <= nameID <= 6: - # TODO: issue a warning - return None - + def parse_name_(self): platEncID = None langID = None if self.next_token_type_ == Lexer.NUMBER: @@ -764,8 +755,21 @@ class Parser(object): elif platformID == 3 and platEncID == 1: string = self.unescape_windows_name_string(string) - return ast.NameRecord(location, nameID, platformID, - platEncID, langID, string) + return platformID, platEncID, langID, string + + def parse_nameid_(self): + assert self.cur_token_ == "nameid", self.cur_token_ + location, nameID = self.cur_token_location_, self.expect_number_() + if nameID > 255: + raise FeatureLibError("Expected name id < 255", + self.cur_token_location_) + if 1 <= nameID <= 6: + # TODO: issue a warning + return None + + platformID, platEncID, langID, string = self.parse_name_() + return ast.NameRecord(location, nameID, platformID, platEncID, + langID, string) def unescape_mac_name_string(self, string): def unescape(match): @@ -881,6 +885,9 @@ class Parser(object): location = self.cur_token_location_ tag = self.expect_tag_() vertical = (tag in {"vkrn", "vpal", "vhal", "valt"}) + stylisticset = None + if tag in ["ss%02d" % i for i in range(1, 20+1)]: + stylisticset = tag use_extension = False if self.next_token_ == "useExtension": @@ -888,7 +895,7 @@ class Parser(object): use_extension = True block = ast.FeatureBlock(location, tag, use_extension) - self.parse_block_(block, vertical) + self.parse_block_(block, vertical, stylisticset) return block def parse_feature_reference_(self): @@ -898,6 +905,30 @@ class Parser(object): self.expect_symbol_(";") return ast.FeatureReferenceStatement(location, featureName) + def parse_featureNames_(self, tag): + assert self.cur_token_ == "featureNames", self.cur_token_ + self.expect_symbol_("{") + for symtab in self.symbol_tables_: + symtab.enter_scope() + + statements = [] + while self.next_token_ != "}": + self.expect_keyword_("name") + location = self.cur_token_location_ + platformID, platEncID, langID, string = self.parse_name_() + statements.append( + ast.FeatureNameStatement(location, tag, platformID, + platEncID, langID, string)) + + self.expect_symbol_("}") + + for symtab in self.symbol_tables_: + symtab.exit_scope() + + self.expect_symbol_(";") + + return statements + def parse_FontRevision_(self): assert self.cur_token_ == "FontRevision", self.cur_token_ location, version = self.cur_token_location_, self.expect_float_() @@ -907,7 +938,7 @@ class Parser(object): location) return ast.FontRevisionStatement(location, version) - def parse_block_(self, block, vertical): + def parse_block_(self, block, vertical, stylisticset=None): self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() @@ -945,6 +976,8 @@ class Parser(object): statements.append(self.parse_subtable_()) elif self.is_cur_keyword_("valueRecordDef"): statements.append(self.parse_valuerecord_definition_(vertical)) + elif stylisticset and self.is_cur_keyword_("featureNames"): + statements.extend(self.parse_featureNames_(stylisticset)) elif self.cur_token_ == ";": continue else: diff --git a/Lib/fontTools/feaLib/testdata/spec8c.fea b/Lib/fontTools/feaLib/testdata/spec8c.fea new file mode 100644 index 000000000..21ed817fb --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec8c.fea @@ -0,0 +1,11 @@ + feature ss01 { + featureNames { + name "Feature description for MS Platform, script Unicode, language English"; +# With no platform ID, script ID, or language ID specified, the implementation assumes (3,1,0x409). + name 3 1 0x411 "Feature description for MS Platform, script Unicode, language Japanese"; + name 1 "Feature description for Apple Platform, script Roman, language unspecified"; +# With only the platform ID specified, the implementation assumes script and language = Latin. For Apple this is (1,0,0). + name 1 1 12 "Feature description for Apple Platform, script Japanese, language Japanese"; } ; +# --- rules for this feature --- + sub A by B; + } ss01; diff --git a/Lib/fontTools/feaLib/testdata/spec8c.ttx b/Lib/fontTools/feaLib/testdata/spec8c.ttx new file mode 100644 index 000000000..2c2653654 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec8c.ttx @@ -0,0 +1,62 @@ + + + + + + Feature description for MS Platform, script Unicode, language English + + + Feature description for MS Platform, script Unicode, language Japanese + + + Feature description for Apple Platform, script Roman, language unspecified + + + Feature description for Apple Platform, script Japanese, language Japanese + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +