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)
fea_keywords = set(
[
"anchor",
@ -260,7 +259,7 @@ class GlyphClass(Expression):
def add_range(self, start, end, glyphs):
"""Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
are either :class:`GlyphName` objects or strings representing the
are either :class:`GlyphName` objects or strings representing the
start and end glyphs in the class, and ``glyphs`` is the full list of
:class:`GlyphName` objects in the range."""
if self.curr < len(self.glyphs):
@ -555,7 +554,7 @@ class MarkClass(object):
class MarkClassDefinition(Statement):
"""A single ``markClass`` statement. The ``markClass`` should be a
"""A single ``markClass`` statement. The ``markClass`` should be a
:class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
and the ``glyphs`` parameter should be a `glyph-containing object`_ .
@ -857,7 +856,7 @@ class IgnorePosStatement(Statement):
"""An ``ignore pos`` statement, containing `one or more` contexts to ignore.
``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
with each of ``prefix``, ``glyphs`` and ``suffix`` being
with each of ``prefix``, ``glyphs`` and ``suffix`` being
`glyph-containing objects`_ ."""
def __init__(self, chainContexts, location=None):
@ -1173,7 +1172,7 @@ class MarkLigPosStatement(Statement):
# ... add definitions to mark classes...
glyph = GlyphName("lam_meem_jeem")
marks = [
marks = [
[ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
[ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
[ ] # No attachments on the jeem
@ -1910,6 +1909,7 @@ class STATDesignAxisStatement(Statement):
axisOrder (int): an int
names (list): a list of :class:`STATNameStatement` objects
"""
def __init__(self, tag, axisOrder, names, location=None):
Statement.__init__(self, location)
self.tag = tag
@ -1923,8 +1923,7 @@ class STATDesignAxisStatement(Statement):
def asFea(self, indent=""):
indent += SHIFT
res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in
self.names]) + "\n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
res += "};"
return res
@ -1935,6 +1934,7 @@ class ElidedFallbackName(Statement):
Args:
names: a list of :class:`STATNameStatement` objects
"""
def __init__(self, names, location=None):
Statement.__init__(self, location)
self.names = names
@ -1946,8 +1946,7 @@ class ElidedFallbackName(Statement):
def asFea(self, indent=""):
indent += SHIFT
res = "ElidedFallbackName { \n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in
self.names]) + "\n"
res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
res += "};"
return res
@ -1958,6 +1957,7 @@ class ElidedFallbackNameID(Statement):
Args:
value: an int pointing to an existing name table name ID
"""
def __init__(self, value, location=None):
Statement.__init__(self, location)
self.value = value
@ -1978,6 +1978,7 @@ class STATAxisValueStatement(Statement):
locations (list): a list of :class:`AxisValueLocationStatement` objects
flags (int): an int
"""
def __init__(self, names, locations, flags, location=None):
Statement.__init__(self, location)
self.names = names
@ -2017,6 +2018,7 @@ class AxisValueLocationStatement(Statement):
tag (str): a 4 letter axis tag
values (list): a list of ints and/or floats
"""
def __init__(self, tag, values, location=None):
Statement.__init__(self, location)
self.tag = tag

View File

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

View File

@ -1322,32 +1322,36 @@ class Parser(object):
if self.is_cur_keyword_("name"):
platformID, platEncID, langID, string = self.parse_stat_name_()
nameRecord = self.ast.STATNameStatement(
"stat", platformID, platEncID, langID, string,
location=self.cur_token_location_
"stat",
platformID,
platEncID,
langID,
string,
location=self.cur_token_location_,
)
names.append(nameRecord)
else:
if self.cur_token_ != ";":
raise FeatureLibError(f"Unexpected token {self.cur_token_} "
f"in ElidedFallbackName",
self.cur_token_location_)
raise FeatureLibError(
f"Unexpected token {self.cur_token_} " f"in ElidedFallbackName",
self.cur_token_location_,
)
self.expect_symbol_("}")
if not names:
raise FeatureLibError('Expected "name"',
self.cur_token_location_)
raise FeatureLibError('Expected "name"', self.cur_token_location_)
return names
def parse_STAT_design_axis(self):
assert self.is_cur_keyword_('DesignAxis')
assert self.is_cur_keyword_("DesignAxis")
names = []
axisTag = self.expect_tag_()
if (axisTag not in ('ital', 'opsz', 'slnt', 'wdth', 'wght')
and not axisTag.isupper()):
log.warning(
f'Unregistered axis tag {axisTag} should be uppercase.'
)
if (
axisTag not in ("ital", "opsz", "slnt", "wdth", "wght")
and not axisTag.isupper()
):
log.warning(f"Unregistered axis tag {axisTag} should be uppercase.")
axisOrder = self.expect_number_()
self.expect_symbol_('{')
self.expect_symbol_("{")
while self.next_token_ != "}" or self.cur_comments_:
self.advance_lexer_()
if self.cur_token_type_ is Lexer.COMMENT:
@ -1362,11 +1366,14 @@ class Parser(object):
elif self.cur_token_ == ";":
continue
else:
raise FeatureLibError(f'Expected "name", got {self.cur_token_}',
self.cur_token_location_)
raise FeatureLibError(
f'Expected "name", got {self.cur_token_}', self.cur_token_location_
)
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):
assert self.is_cur_keyword_("AxisValue")
@ -1393,39 +1400,45 @@ class Parser(object):
elif self.cur_token_ == ";":
continue
else:
raise FeatureLibError(f"Unexpected token {self.cur_token_} "
f"in AxisValue", self.cur_token_location_)
raise FeatureLibError(
f"Unexpected token {self.cur_token_} " f"in AxisValue",
self.cur_token_location_,
)
self.expect_symbol_("}")
if not names:
raise FeatureLibError('Expected "Axis Name"',
self.cur_token_location_)
raise FeatureLibError('Expected "Axis Name"', self.cur_token_location_)
if not locations:
raise FeatureLibError('Expected "Axis location"',
self.cur_token_location_)
raise FeatureLibError('Expected "Axis location"', self.cur_token_location_)
if len(locations) > 1:
for location in locations:
if len(location.values) > 1:
raise FeatureLibError("Only one value is allowed in a "
"Format 4 Axis Value Record, but "
f"{len(location.values)} were found.",
self.cur_token_location_)
raise FeatureLibError(
"Only one value is allowed in a "
"Format 4 Axis Value Record, but "
f"{len(location.values)} were found.",
self.cur_token_location_,
)
format4_tags = []
for location in locations:
tag = location.tag
if tag in format4_tags:
raise FeatureLibError(f"Axis tag {tag} already "
"defined.",
self.cur_token_location_)
raise FeatureLibError(
f"Axis tag {tag} already " "defined.", self.cur_token_location_
)
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):
values = []
tag = self.expect_tag_()
if len(tag.strip()) != 4:
raise FeatureLibError(f"Axis tag {self.cur_token_} must be 4 "
"characters", self.cur_token_location_)
raise FeatureLibError(
f"Axis tag {self.cur_token_} must be 4 " "characters",
self.cur_token_location_,
)
while self.next_token_ != ";":
if self.next_token_type_ is Lexer.FLOAT:
@ -1435,16 +1448,20 @@ class Parser(object):
value = self.expect_number_()
values.append(value)
else:
raise FeatureLibError(f'Unexpected value "{self.next_token_}". '
'Expected integer or float.',
self.next_token_location_)
raise FeatureLibError(
f'Unexpected value "{self.next_token_}". '
"Expected integer or float.",
self.next_token_location_,
)
if len(values) == 3:
nominal, min_val, max_val = values
if nominal < min_val or nominal > max_val:
raise FeatureLibError(f'Default value {nominal} is outside '
f'of specified range '
f'{min_val}-{max_val}.',
self.next_token_location_)
raise FeatureLibError(
f"Default value {nominal} is outside "
f"of specified range "
f"{min_val}-{max_val}.",
self.next_token_location_,
)
return self.ast.AxisValueLocationStatement(tag, values)
def parse_table_STAT_(self, table):
@ -1475,14 +1492,16 @@ class Parser(object):
if location.tag not in design_axes:
# Tag must be defined in a DesignAxis before it
# can be referenced
raise FeatureLibError("DesignAxis not defined for "
f"{location.tag}.",
self.cur_token_location_)
raise FeatureLibError(
"DesignAxis not defined for " f"{location.tag}.",
self.cur_token_location_,
)
statements.append(axisValueRecord)
self.expect_symbol_(";")
else:
raise FeatureLibError(f"Unexpected token {self.cur_token_}",
self.cur_token_location_)
raise FeatureLibError(
f"Unexpected token {self.cur_token_}", self.cur_token_location_
)
elif self.cur_token_ == ";":
continue
@ -2056,8 +2075,7 @@ class Parser(object):
return self.expect_number_() / 10
else:
raise FeatureLibError(
"Expected an integer or floating-point number",
self.cur_token_location_
"Expected an integer or floating-point number", self.cur_token_location_
)
def expect_stat_flags(self):
@ -2072,8 +2090,7 @@ class Parser(object):
value = value | flags[name]
else:
raise FeatureLibError(
f"Unexpected STAT flag {self.cur_token_}",
self.cur_token_location_
f"Unexpected STAT flag {self.cur_token_}", self.cur_token_location_
)
return value
@ -2084,8 +2101,7 @@ class Parser(object):
return self.expect_number_()
else:
raise FeatureLibError(
"Expected an integer or floating-point number",
self.cur_token_location_
"Expected an integer or floating-point number", self.cur_token_location_
)
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]
if not subtables:
return None
assert all(t.LookupType == subtables[0].LookupType for t in subtables), (
"all subtables must have the same LookupType; got %s"
% repr([t.LookupType for t in subtables])
assert all(
t.LookupType == subtables[0].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.LookupType = subtables[0].LookupType
@ -2576,7 +2577,9 @@ class ClassDefBuilder(object):
self.classes_.add(glyphs)
for glyph in 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
def classes(self):
@ -2688,7 +2691,7 @@ def buildStatTable(ttFont, axes, locations=None, elidedFallbackName=2):
]
The optional 'elidedFallbackName' argument can be a name ID (int),
a string, a dictionary containing multilingual names, or a list of
a string, a dictionary containing multilingual names, or a list of
STATNameStatements. It translates to the ElidedFallbackNameID field.
The 'ttFont' argument must be a TTFont instance that already has a
@ -2802,9 +2805,13 @@ def _addName(nameTable, value, minNameID=0):
nameID = nameTable._findUnusedNameID()
for nameRecord in value:
if isinstance(nameRecord, STATNameStatement):
nameTable.setName(nameRecord.string,
nameID,nameRecord.platformID,
nameRecord.platEncID,nameRecord.langID)
nameTable.setName(
nameRecord.string,
nameID,
nameRecord.platformID,
nameRecord.platEncID,
nameRecord.langID,
)
else:
raise TypeError("value must be a list of STATNameStatements")
return nameID