[feaLib] Support stylistic set featureNames
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.
This commit is contained in:
parent
53dc98be55
commit
9feaab13aa
@ -591,3 +591,8 @@ class NameRecord(Statement):
|
|||||||
builder.add_name_record(
|
builder.add_name_record(
|
||||||
self.location, self.nameID, self.platformID,
|
self.location, self.nameID, self.platformID,
|
||||||
self.platEncID, self.langID, self.string)
|
self.platEncID, self.langID, self.string)
|
||||||
|
|
||||||
|
class FeatureNameStatement(NameRecord):
|
||||||
|
def build(self, builder):
|
||||||
|
NameRecord.build(self, builder)
|
||||||
|
builder.add_featureName(self.location, self.nameID)
|
||||||
|
@ -35,6 +35,9 @@ class Builder(object):
|
|||||||
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
|
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
|
||||||
self.aalt_location_ = None
|
self.aalt_location_ = None
|
||||||
self.aalt_alternates_ = {}
|
self.aalt_alternates_ = {}
|
||||||
|
# for 'featureNames'
|
||||||
|
self.featureNames_ = []
|
||||||
|
self.featureNames_ids_ = {}
|
||||||
# for table 'head'
|
# for table 'head'
|
||||||
self.fontRevision_ = None # 2.71
|
self.fontRevision_ = None # 2.71
|
||||||
# for table 'name'
|
# for table 'name'
|
||||||
@ -157,14 +160,38 @@ class Builder(object):
|
|||||||
table.created = table.modified = 3406620153 # 2011-12-13 11:22:33
|
table.created = table.modified = 3406620153 # 2011-12-13 11:22:33
|
||||||
table.fontRevision = self.fontRevision_
|
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):
|
def build_name(self):
|
||||||
if not self.names_:
|
if not self.names_:
|
||||||
return
|
return
|
||||||
table = self.font.get("name")
|
table = self.font.get("name")
|
||||||
if not table: # this only happens for unit tests
|
if not table: # this only happens for unit tests
|
||||||
table = self.font["name"] = getTableClass("name")()
|
table = self.font["name"] = getTableClass("name")()
|
||||||
|
table.names = []
|
||||||
for name in self.names_:
|
for name in self.names_:
|
||||||
nameID, platformID, platEncID, langID, string = name
|
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)
|
table.setName(string, nameID, platformID, platEncID, langID)
|
||||||
|
|
||||||
def buildGDEF(self):
|
def buildGDEF(self):
|
||||||
@ -276,7 +303,8 @@ class Builder(object):
|
|||||||
frec = otTables.FeatureRecord()
|
frec = otTables.FeatureRecord()
|
||||||
frec.FeatureTag = feature_tag
|
frec.FeatureTag = feature_tag
|
||||||
frec.Feature = otTables.Feature()
|
frec.Feature = otTables.Feature()
|
||||||
frec.Feature.FeatureParams = None
|
frec.Feature.FeatureParams = self.buildFeatureParams(
|
||||||
|
feature_tag)
|
||||||
frec.Feature.LookupListIndex = lookup_indices
|
frec.Feature.LookupListIndex = lookup_indices
|
||||||
frec.Feature.LookupCount = len(lookup_indices)
|
frec.Feature.LookupCount = len(lookup_indices)
|
||||||
table.FeatureList.FeatureRecord.append(frec)
|
table.FeatureList.FeatureRecord.append(frec)
|
||||||
@ -523,6 +551,9 @@ class Builder(object):
|
|||||||
location)
|
location)
|
||||||
self.aalt_features_.append((location, featureName))
|
self.aalt_features_.append((location, featureName))
|
||||||
|
|
||||||
|
def add_featureName(self, location, tag):
|
||||||
|
self.featureNames_.append(tag)
|
||||||
|
|
||||||
def add_ligature_subst(self, location,
|
def add_ligature_subst(self, location,
|
||||||
prefix, glyphs, suffix, replacement, forceChain):
|
prefix, glyphs, suffix, replacement, forceChain):
|
||||||
if prefix or suffix or forceChain:
|
if prefix or suffix or forceChain:
|
||||||
|
@ -56,7 +56,7 @@ class BuilderTest(unittest.TestCase):
|
|||||||
spec4h1 spec5d1 spec5d2 spec5fi1 spec5fi2 spec5fi3 spec5fi4
|
spec4h1 spec5d1 spec5d2 spec5fi1 spec5fi2 spec5fi3 spec5fi4
|
||||||
spec5f_ii_1 spec5f_ii_2 spec5f_ii_3 spec5f_ii_4
|
spec5f_ii_1 spec5f_ii_2 spec5f_ii_3 spec5f_ii_4
|
||||||
spec5h1 spec6b_ii spec6d2 spec6e spec6f
|
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
|
spec9b spec9c1 spec9c2 spec9c3 spec9e
|
||||||
bug453 bug463 bug501 bug502 bug505 bug506 bug509 bug512
|
bug453 bug463 bug501 bug502 bug505 bug506 bug509 bug512
|
||||||
name
|
name
|
||||||
|
@ -726,16 +726,7 @@ class Parser(object):
|
|||||||
raise FeatureLibError("Expected nameid",
|
raise FeatureLibError("Expected nameid",
|
||||||
self.cur_token_location_)
|
self.cur_token_location_)
|
||||||
|
|
||||||
def parse_nameid_(self):
|
def parse_name_(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
|
|
||||||
|
|
||||||
platEncID = None
|
platEncID = None
|
||||||
langID = None
|
langID = None
|
||||||
if self.next_token_type_ == Lexer.NUMBER:
|
if self.next_token_type_ == Lexer.NUMBER:
|
||||||
@ -764,8 +755,21 @@ class Parser(object):
|
|||||||
elif platformID == 3 and platEncID == 1:
|
elif platformID == 3 and platEncID == 1:
|
||||||
string = self.unescape_windows_name_string(string)
|
string = self.unescape_windows_name_string(string)
|
||||||
|
|
||||||
return ast.NameRecord(location, nameID, platformID,
|
return platformID, platEncID, langID, string
|
||||||
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_mac_name_string(self, string):
|
||||||
def unescape(match):
|
def unescape(match):
|
||||||
@ -881,6 +885,9 @@ class Parser(object):
|
|||||||
location = self.cur_token_location_
|
location = self.cur_token_location_
|
||||||
tag = self.expect_tag_()
|
tag = self.expect_tag_()
|
||||||
vertical = (tag in {"vkrn", "vpal", "vhal", "valt"})
|
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
|
use_extension = False
|
||||||
if self.next_token_ == "useExtension":
|
if self.next_token_ == "useExtension":
|
||||||
@ -888,7 +895,7 @@ class Parser(object):
|
|||||||
use_extension = True
|
use_extension = True
|
||||||
|
|
||||||
block = ast.FeatureBlock(location, tag, use_extension)
|
block = ast.FeatureBlock(location, tag, use_extension)
|
||||||
self.parse_block_(block, vertical)
|
self.parse_block_(block, vertical, stylisticset)
|
||||||
return block
|
return block
|
||||||
|
|
||||||
def parse_feature_reference_(self):
|
def parse_feature_reference_(self):
|
||||||
@ -898,6 +905,30 @@ class Parser(object):
|
|||||||
self.expect_symbol_(";")
|
self.expect_symbol_(";")
|
||||||
return ast.FeatureReferenceStatement(location, featureName)
|
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):
|
def parse_FontRevision_(self):
|
||||||
assert self.cur_token_ == "FontRevision", self.cur_token_
|
assert self.cur_token_ == "FontRevision", self.cur_token_
|
||||||
location, version = self.cur_token_location_, self.expect_float_()
|
location, version = self.cur_token_location_, self.expect_float_()
|
||||||
@ -907,7 +938,7 @@ class Parser(object):
|
|||||||
location)
|
location)
|
||||||
return ast.FontRevisionStatement(location, version)
|
return ast.FontRevisionStatement(location, version)
|
||||||
|
|
||||||
def parse_block_(self, block, vertical):
|
def parse_block_(self, block, vertical, stylisticset=None):
|
||||||
self.expect_symbol_("{")
|
self.expect_symbol_("{")
|
||||||
for symtab in self.symbol_tables_:
|
for symtab in self.symbol_tables_:
|
||||||
symtab.enter_scope()
|
symtab.enter_scope()
|
||||||
@ -945,6 +976,8 @@ class Parser(object):
|
|||||||
statements.append(self.parse_subtable_())
|
statements.append(self.parse_subtable_())
|
||||||
elif self.is_cur_keyword_("valueRecordDef"):
|
elif self.is_cur_keyword_("valueRecordDef"):
|
||||||
statements.append(self.parse_valuerecord_definition_(vertical))
|
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_ == ";":
|
elif self.cur_token_ == ";":
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
11
Lib/fontTools/feaLib/testdata/spec8c.fea
vendored
Normal file
11
Lib/fontTools/feaLib/testdata/spec8c.fea
vendored
Normal file
@ -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;
|
62
Lib/fontTools/feaLib/testdata/spec8c.ttx
vendored
Normal file
62
Lib/fontTools/feaLib/testdata/spec8c.ttx
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ttFont>
|
||||||
|
|
||||||
|
<name>
|
||||||
|
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
|
||||||
|
Feature description for MS Platform, script Unicode, language English
|
||||||
|
</namerecord>
|
||||||
|
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
|
||||||
|
Feature description for MS Platform, script Unicode, language Japanese
|
||||||
|
</namerecord>
|
||||||
|
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||||
|
Feature description for Apple Platform, script Roman, language unspecified
|
||||||
|
</namerecord>
|
||||||
|
<namerecord nameID="256" platformID="1" platEncID="1" langID="0xc" unicode="True">
|
||||||
|
Feature description for Apple Platform, script Japanese, language Japanese
|
||||||
|
</namerecord>
|
||||||
|
</name>
|
||||||
|
|
||||||
|
<GSUB>
|
||||||
|
<Version value="1.0"/>
|
||||||
|
<ScriptList>
|
||||||
|
<!-- ScriptCount=1 -->
|
||||||
|
<ScriptRecord index="0">
|
||||||
|
<ScriptTag value="DFLT"/>
|
||||||
|
<Script>
|
||||||
|
<DefaultLangSys>
|
||||||
|
<ReqFeatureIndex value="65535"/>
|
||||||
|
<!-- FeatureCount=1 -->
|
||||||
|
<FeatureIndex index="0" value="0"/>
|
||||||
|
</DefaultLangSys>
|
||||||
|
<!-- LangSysCount=0 -->
|
||||||
|
</Script>
|
||||||
|
</ScriptRecord>
|
||||||
|
</ScriptList>
|
||||||
|
<FeatureList>
|
||||||
|
<!-- FeatureCount=1 -->
|
||||||
|
<FeatureRecord index="0">
|
||||||
|
<FeatureTag value="ss01"/>
|
||||||
|
<Feature>
|
||||||
|
<FeatureParamsStylisticSet>
|
||||||
|
<Version value="0"/>
|
||||||
|
<UINameID value="256"/>
|
||||||
|
</FeatureParamsStylisticSet>
|
||||||
|
<!-- LookupCount=1 -->
|
||||||
|
<LookupListIndex index="0" value="0"/>
|
||||||
|
</Feature>
|
||||||
|
</FeatureRecord>
|
||||||
|
</FeatureList>
|
||||||
|
<LookupList>
|
||||||
|
<!-- LookupCount=1 -->
|
||||||
|
<Lookup index="0">
|
||||||
|
<!-- LookupType=1 -->
|
||||||
|
<LookupFlag value="0"/>
|
||||||
|
<!-- SubTableCount=1 -->
|
||||||
|
<SingleSubst index="0">
|
||||||
|
<Substitution in="A" out="B"/>
|
||||||
|
</SingleSubst>
|
||||||
|
</Lookup>
|
||||||
|
</LookupList>
|
||||||
|
</GSUB>
|
||||||
|
|
||||||
|
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user