This commit is contained in:
Cosimo Lupo 2021-02-25 16:59:47 +00:00
parent 29ff42d15f
commit 9aeb48286d
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F
4 changed files with 156 additions and 105 deletions

View File

@ -84,7 +84,6 @@ def deviceToString(device):
return "<device %s>" % ", ".join("%d %d" % t for t in device) return "<device %s>" % ", ".join("%d %d" % t for t in device)
fea_keywords = set( fea_keywords = set(
[ [
"anchor", "anchor",
@ -1910,6 +1909,7 @@ class STATDesignAxisStatement(Statement):
axisOrder (int): an int axisOrder (int): an int
names (list): a list of :class:`STATNameStatement` objects names (list): a list of :class:`STATNameStatement` objects
""" """
def __init__(self, tag, axisOrder, names, location=None): def __init__(self, tag, axisOrder, names, location=None):
Statement.__init__(self, location) Statement.__init__(self, location)
self.tag = tag self.tag = tag
@ -1923,8 +1923,7 @@ class STATDesignAxisStatement(Statement):
def asFea(self, indent=""): def asFea(self, indent=""):
indent += SHIFT indent += SHIFT
res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n" res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
self.names]) + "\n"
res += "};" res += "};"
return res return res
@ -1935,6 +1934,7 @@ class ElidedFallbackName(Statement):
Args: Args:
names: a list of :class:`STATNameStatement` objects names: a list of :class:`STATNameStatement` objects
""" """
def __init__(self, names, location=None): def __init__(self, names, location=None):
Statement.__init__(self, location) Statement.__init__(self, location)
self.names = names self.names = names
@ -1946,8 +1946,7 @@ class ElidedFallbackName(Statement):
def asFea(self, indent=""): def asFea(self, indent=""):
indent += SHIFT indent += SHIFT
res = "ElidedFallbackName { \n" res = "ElidedFallbackName { \n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
self.names]) + "\n"
res += "};" res += "};"
return res return res
@ -1958,6 +1957,7 @@ class ElidedFallbackNameID(Statement):
Args: Args:
value: an int pointing to an existing name table name ID value: an int pointing to an existing name table name ID
""" """
def __init__(self, value, location=None): def __init__(self, value, location=None):
Statement.__init__(self, location) Statement.__init__(self, location)
self.value = value self.value = value
@ -1978,6 +1978,7 @@ class STATAxisValueStatement(Statement):
locations (list): a list of :class:`AxisValueLocationStatement` objects locations (list): a list of :class:`AxisValueLocationStatement` objects
flags (int): an int flags (int): an int
""" """
def __init__(self, names, locations, flags, location=None): def __init__(self, names, locations, flags, location=None):
Statement.__init__(self, location) Statement.__init__(self, location)
self.names = names self.names = names
@ -2017,6 +2018,7 @@ class AxisValueLocationStatement(Statement):
tag (str): a 4 letter axis tag tag (str): a 4 letter axis tag
values (list): a list of ints and/or floats values (list): a list of ints and/or floats
""" """
def __init__(self, tag, values, location=None): def __init__(self, tag, values, location=None):
Statement.__init__(self, location) Statement.__init__(self, location)
self.tag = tag self.tag = tag

View File

@ -551,11 +551,13 @@ class Builder(object):
self.stat_["AxisValueRecords"] = [] self.stat_["AxisValueRecords"] = []
# Check for duplicate AxisValueRecords # Check for duplicate AxisValueRecords
for record_ in self.stat_["AxisValueRecords"]: for record_ in self.stat_["AxisValueRecords"]:
if ({n.asFea() for n in record_.names} == if (
{n.asFea() for n in axisValueRecord.names} and {n.asFea() for n in record_.names}
{n.asFea() for n in record_.locations} == == {n.asFea() for n in axisValueRecord.names}
{n.asFea() for n in axisValueRecord.locations} and {n.asFea() for n in record_.locations}
and record_.flags == axisValueRecord.flags): == {n.asFea() for n in axisValueRecord.locations}
and record_.flags == axisValueRecord.flags
):
raise FeatureLibError( raise FeatureLibError(
"An AxisValueRecord with these values is already defined.", "An AxisValueRecord with these values is already defined.",
location, location,
@ -568,7 +570,7 @@ class Builder(object):
axes = self.stat_.get("DesignAxes") axes = self.stat_.get("DesignAxes")
if not axes: if not axes:
raise FeatureLibError('DesignAxes not defined', None) raise FeatureLibError("DesignAxes not defined", None)
axisValueRecords = self.stat_.get("AxisValueRecords") axisValueRecords = self.stat_.get("AxisValueRecords")
axisValues = {} axisValues = {}
format4_locations = [] format4_locations = []
@ -578,33 +580,49 @@ class Builder(object):
for avr in axisValueRecords: for avr in axisValueRecords:
valuesDict = {} valuesDict = {}
if avr.flags > 0: if avr.flags > 0:
valuesDict['flags'] = avr.flags valuesDict["flags"] = avr.flags
if len(avr.locations) == 1: if len(avr.locations) == 1:
location = avr.locations[0] location = avr.locations[0]
values = location.values values = location.values
if len(values) == 1: #format1 if len(values) == 1: # format1
valuesDict.update({'value': values[0],'name': avr.names}) valuesDict.update({"value": values[0], "name": avr.names})
if len(values) == 2: #format3 if len(values) == 2: # format3
valuesDict.update({ 'value': values[0], valuesDict.update(
'linkedValue': values[1], {
'name': avr.names}) "value": values[0],
if len(values) == 3: #format2 "linkedValue": values[1],
"name": avr.names,
}
)
if len(values) == 3: # format2
nominal, minVal, maxVal = values nominal, minVal, maxVal = values
valuesDict.update({ 'nominalValue': nominal, valuesDict.update(
'rangeMinValue': minVal, {
'rangeMaxValue': maxVal, "nominalValue": nominal,
'name': avr.names}) "rangeMinValue": minVal,
"rangeMaxValue": maxVal,
"name": avr.names,
}
)
axisValues[location.tag].append(valuesDict) axisValues[location.tag].append(valuesDict)
else: else:
valuesDict.update({"location": {i.tag: i.values[0] valuesDict.update(
for i in avr.locations}, {
"name": avr.names}) "location": {i.tag: i.values[0] for i in avr.locations},
"name": avr.names,
}
)
format4_locations.append(valuesDict) format4_locations.append(valuesDict)
designAxes = [{"ordering": a.axisOrder, designAxes = [
{
"ordering": a.axisOrder,
"tag": a.tag, "tag": a.tag,
"name": a.names, "name": a.names,
'values': axisValues[a.tag]} for a in axes] "values": axisValues[a.tag],
}
for a in axes
]
nameTable = self.font.get("name") nameTable = self.font.get("name")
if not nameTable: # this only happens for unit tests if not nameTable: # this only happens for unit tests
@ -615,15 +633,21 @@ class Builder(object):
nameID = self.stat_["ElidedFallbackNameID"] nameID = self.stat_["ElidedFallbackNameID"]
name = nameTable.getDebugName(nameID) name = nameTable.getDebugName(nameID)
if not name: if not name:
raise FeatureLibError(f'ElidedFallbackNameID {nameID} points ' raise FeatureLibError(
'to a nameID that does not exist in the ' f"ElidedFallbackNameID {nameID} points "
'"name" table', None) "to a nameID that does not exist in the "
'"name" table',
None,
)
elif "ElidedFallbackName" in self.stat_: elif "ElidedFallbackName" in self.stat_:
nameID = self.stat_["ElidedFallbackName"] nameID = self.stat_["ElidedFallbackName"]
otl.buildStatTable(self.font, designAxes, locations=format4_locations, otl.buildStatTable(
elidedFallbackName=nameID) self.font,
designAxes,
locations=format4_locations,
elidedFallbackName=nameID,
)
def build_codepages_(self, pages): def build_codepages_(self, pages):
pages2bits = { pages2bits = {
@ -833,8 +857,10 @@ class Builder(object):
str(ix) str(ix)
]._replace(feature=key) ]._replace(feature=key)
except KeyError: except KeyError:
warnings.warn("feaLib.Builder subclass needs upgrading to " warnings.warn(
"stash debug information. See fonttools#2065.") "feaLib.Builder subclass needs upgrading to "
"stash debug information. See fonttools#2065."
)
feature_key = (feature_tag, lookup_indices) feature_key = (feature_tag, lookup_indices)
feature_index = feature_indices.get(feature_key) feature_index = feature_indices.get(feature_key)

View File

@ -1322,32 +1322,36 @@ class Parser(object):
if self.is_cur_keyword_("name"): if self.is_cur_keyword_("name"):
platformID, platEncID, langID, string = self.parse_stat_name_() platformID, platEncID, langID, string = self.parse_stat_name_()
nameRecord = self.ast.STATNameStatement( nameRecord = self.ast.STATNameStatement(
"stat", platformID, platEncID, langID, string, "stat",
location=self.cur_token_location_ platformID,
platEncID,
langID,
string,
location=self.cur_token_location_,
) )
names.append(nameRecord) names.append(nameRecord)
else: else:
if self.cur_token_ != ";": if self.cur_token_ != ";":
raise FeatureLibError(f"Unexpected token {self.cur_token_} " raise FeatureLibError(
f"in ElidedFallbackName", f"Unexpected token {self.cur_token_} " f"in ElidedFallbackName",
self.cur_token_location_) self.cur_token_location_,
)
self.expect_symbol_("}") self.expect_symbol_("}")
if not names: if not names:
raise FeatureLibError('Expected "name"', raise FeatureLibError('Expected "name"', self.cur_token_location_)
self.cur_token_location_)
return names return names
def parse_STAT_design_axis(self): def parse_STAT_design_axis(self):
assert self.is_cur_keyword_('DesignAxis') assert self.is_cur_keyword_("DesignAxis")
names = [] names = []
axisTag = self.expect_tag_() axisTag = self.expect_tag_()
if (axisTag not in ('ital', 'opsz', 'slnt', 'wdth', 'wght') if (
and not axisTag.isupper()): axisTag not in ("ital", "opsz", "slnt", "wdth", "wght")
log.warning( and not axisTag.isupper()
f'Unregistered axis tag {axisTag} should be uppercase.' ):
) log.warning(f"Unregistered axis tag {axisTag} should be uppercase.")
axisOrder = self.expect_number_() axisOrder = self.expect_number_()
self.expect_symbol_('{') self.expect_symbol_("{")
while self.next_token_ != "}" or self.cur_comments_: while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_() self.advance_lexer_()
if self.cur_token_type_ is Lexer.COMMENT: if self.cur_token_type_ is Lexer.COMMENT:
@ -1362,11 +1366,14 @@ class Parser(object):
elif self.cur_token_ == ";": elif self.cur_token_ == ";":
continue continue
else: else:
raise FeatureLibError(f'Expected "name", got {self.cur_token_}', raise FeatureLibError(
self.cur_token_location_) f'Expected "name", got {self.cur_token_}', self.cur_token_location_
)
self.expect_symbol_("}") self.expect_symbol_("}")
return self.ast.STATDesignAxisStatement(axisTag, axisOrder, names, self.cur_token_location_) return self.ast.STATDesignAxisStatement(
axisTag, axisOrder, names, self.cur_token_location_
)
def parse_STAT_axis_value_(self): def parse_STAT_axis_value_(self):
assert self.is_cur_keyword_("AxisValue") assert self.is_cur_keyword_("AxisValue")
@ -1393,39 +1400,45 @@ class Parser(object):
elif self.cur_token_ == ";": elif self.cur_token_ == ";":
continue continue
else: else:
raise FeatureLibError(f"Unexpected token {self.cur_token_} " raise FeatureLibError(
f"in AxisValue", self.cur_token_location_) f"Unexpected token {self.cur_token_} " f"in AxisValue",
self.cur_token_location_,
)
self.expect_symbol_("}") self.expect_symbol_("}")
if not names: if not names:
raise FeatureLibError('Expected "Axis Name"', raise FeatureLibError('Expected "Axis Name"', self.cur_token_location_)
self.cur_token_location_)
if not locations: if not locations:
raise FeatureLibError('Expected "Axis location"', raise FeatureLibError('Expected "Axis location"', self.cur_token_location_)
self.cur_token_location_)
if len(locations) > 1: if len(locations) > 1:
for location in locations: for location in locations:
if len(location.values) > 1: if len(location.values) > 1:
raise FeatureLibError("Only one value is allowed in a " raise FeatureLibError(
"Only one value is allowed in a "
"Format 4 Axis Value Record, but " "Format 4 Axis Value Record, but "
f"{len(location.values)} were found.", f"{len(location.values)} were found.",
self.cur_token_location_) self.cur_token_location_,
)
format4_tags = [] format4_tags = []
for location in locations: for location in locations:
tag = location.tag tag = location.tag
if tag in format4_tags: if tag in format4_tags:
raise FeatureLibError(f"Axis tag {tag} already " raise FeatureLibError(
"defined.", f"Axis tag {tag} already " "defined.", self.cur_token_location_
self.cur_token_location_) )
format4_tags.append(tag) format4_tags.append(tag)
return self.ast.STATAxisValueStatement(names, locations, flags, self.cur_token_location_) return self.ast.STATAxisValueStatement(
names, locations, flags, self.cur_token_location_
)
def parse_STAT_location(self): def parse_STAT_location(self):
values = [] values = []
tag = self.expect_tag_() tag = self.expect_tag_()
if len(tag.strip()) != 4: if len(tag.strip()) != 4:
raise FeatureLibError(f"Axis tag {self.cur_token_} must be 4 " raise FeatureLibError(
"characters", self.cur_token_location_) f"Axis tag {self.cur_token_} must be 4 " "characters",
self.cur_token_location_,
)
while self.next_token_ != ";": while self.next_token_ != ";":
if self.next_token_type_ is Lexer.FLOAT: if self.next_token_type_ is Lexer.FLOAT:
@ -1435,16 +1448,20 @@ class Parser(object):
value = self.expect_number_() value = self.expect_number_()
values.append(value) values.append(value)
else: else:
raise FeatureLibError(f'Unexpected value "{self.next_token_}". ' raise FeatureLibError(
'Expected integer or float.', f'Unexpected value "{self.next_token_}". '
self.next_token_location_) "Expected integer or float.",
self.next_token_location_,
)
if len(values) == 3: if len(values) == 3:
nominal, min_val, max_val = values nominal, min_val, max_val = values
if nominal < min_val or nominal > max_val: if nominal < min_val or nominal > max_val:
raise FeatureLibError(f'Default value {nominal} is outside ' raise FeatureLibError(
f'of specified range ' f"Default value {nominal} is outside "
f'{min_val}-{max_val}.', f"of specified range "
self.next_token_location_) f"{min_val}-{max_val}.",
self.next_token_location_,
)
return self.ast.AxisValueLocationStatement(tag, values) return self.ast.AxisValueLocationStatement(tag, values)
def parse_table_STAT_(self, table): def parse_table_STAT_(self, table):
@ -1475,14 +1492,16 @@ class Parser(object):
if location.tag not in design_axes: if location.tag not in design_axes:
# Tag must be defined in a DesignAxis before it # Tag must be defined in a DesignAxis before it
# can be referenced # can be referenced
raise FeatureLibError("DesignAxis not defined for " raise FeatureLibError(
f"{location.tag}.", "DesignAxis not defined for " f"{location.tag}.",
self.cur_token_location_) self.cur_token_location_,
)
statements.append(axisValueRecord) statements.append(axisValueRecord)
self.expect_symbol_(";") self.expect_symbol_(";")
else: else:
raise FeatureLibError(f"Unexpected token {self.cur_token_}", raise FeatureLibError(
self.cur_token_location_) f"Unexpected token {self.cur_token_}", self.cur_token_location_
)
elif self.cur_token_ == ";": elif self.cur_token_ == ";":
continue continue
@ -2056,8 +2075,7 @@ class Parser(object):
return self.expect_number_() / 10 return self.expect_number_() / 10
else: else:
raise FeatureLibError( raise FeatureLibError(
"Expected an integer or floating-point number", "Expected an integer or floating-point number", self.cur_token_location_
self.cur_token_location_
) )
def expect_stat_flags(self): def expect_stat_flags(self):
@ -2072,8 +2090,7 @@ class Parser(object):
value = value | flags[name] value = value | flags[name]
else: else:
raise FeatureLibError( raise FeatureLibError(
f"Unexpected STAT flag {self.cur_token_}", f"Unexpected STAT flag {self.cur_token_}", self.cur_token_location_
self.cur_token_location_
) )
return value return value
@ -2084,8 +2101,7 @@ class Parser(object):
return self.expect_number_() return self.expect_number_()
else: else:
raise FeatureLibError( raise FeatureLibError(
"Expected an integer or floating-point number", "Expected an integer or floating-point number", self.cur_token_location_
self.cur_token_location_
) )
def expect_string_(self): def expect_string_(self):

View File

@ -95,9 +95,10 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
subtables = [st for st in subtables if st is not None] subtables = [st for st in subtables if st is not None]
if not subtables: if not subtables:
return None return None
assert all(t.LookupType == subtables[0].LookupType for t in subtables), ( assert all(
"all subtables must have the same LookupType; got %s" t.LookupType == subtables[0].LookupType for t in subtables
% repr([t.LookupType for t in subtables]) ), "all subtables must have the same LookupType; got %s" % repr(
[t.LookupType for t in subtables]
) )
self = ot.Lookup() self = ot.Lookup()
self.LookupType = subtables[0].LookupType self.LookupType = subtables[0].LookupType
@ -2576,7 +2577,9 @@ class ClassDefBuilder(object):
self.classes_.add(glyphs) self.classes_.add(glyphs)
for glyph in glyphs: for glyph in glyphs:
if glyph in self.glyphs_: if glyph in self.glyphs_:
raise OpenTypeLibError(f"Glyph {glyph} is already present in class.", None) raise OpenTypeLibError(
f"Glyph {glyph} is already present in class.", None
)
self.glyphs_[glyph] = glyphs self.glyphs_[glyph] = glyphs
def classes(self): def classes(self):
@ -2802,9 +2805,13 @@ def _addName(nameTable, value, minNameID=0):
nameID = nameTable._findUnusedNameID() nameID = nameTable._findUnusedNameID()
for nameRecord in value: for nameRecord in value:
if isinstance(nameRecord, STATNameStatement): if isinstance(nameRecord, STATNameStatement):
nameTable.setName(nameRecord.string, nameTable.setName(
nameID,nameRecord.platformID, nameRecord.string,
nameRecord.platEncID,nameRecord.langID) nameID,
nameRecord.platformID,
nameRecord.platEncID,
nameRecord.langID,
)
else: else:
raise TypeError("value must be a list of STATNameStatements") raise TypeError("value must be a list of STATNameStatements")
return nameID return nameID