Merge pull request #2039 from fonttools/feaLib-STAT
[feaLib] Add support for STAT table
This commit is contained in:
commit
d4ec4fffd2
@ -4,6 +4,7 @@ from fontTools.feaLib.location import FeatureLibLocation
|
||||
from fontTools.misc.encodingTools import getEncoding
|
||||
from collections import OrderedDict
|
||||
import itertools
|
||||
from typing import NamedTuple
|
||||
|
||||
SHIFT = " " * 4
|
||||
|
||||
@ -28,12 +29,15 @@ __all__ = [
|
||||
"Anchor",
|
||||
"AnchorDefinition",
|
||||
"AttachStatement",
|
||||
"AxisValueLocationStatement",
|
||||
"BaseAxis",
|
||||
"CVParametersNameStatement",
|
||||
"ChainContextPosStatement",
|
||||
"ChainContextSubstStatement",
|
||||
"CharacterStatement",
|
||||
"CursivePosStatement",
|
||||
"ElidedFallbackName",
|
||||
"ElidedFallbackNameID",
|
||||
"Expression",
|
||||
"FeatureNameStatement",
|
||||
"FeatureReferenceStatement",
|
||||
@ -62,6 +66,9 @@ __all__ = [
|
||||
"SingleSubstStatement",
|
||||
"SizeParameters",
|
||||
"Statement",
|
||||
"STATAxisValueStatement",
|
||||
"STATDesignAxisStatement",
|
||||
"STATNameStatement",
|
||||
"SubtableStatement",
|
||||
"TableBlock",
|
||||
"ValueRecord",
|
||||
@ -252,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):
|
||||
@ -547,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`_ .
|
||||
|
||||
@ -849,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):
|
||||
@ -1165,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
|
||||
@ -1704,6 +1711,16 @@ class FeatureNameStatement(NameRecord):
|
||||
return '{} {}"{}";'.format(tag, plat, self.string)
|
||||
|
||||
|
||||
class STATNameStatement(NameRecord):
|
||||
"""Represents a STAT table ``name`` statement."""
|
||||
|
||||
def asFea(self, indent=""):
|
||||
plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
|
||||
if plat != "":
|
||||
plat += " "
|
||||
return 'name {}"{}";'.format(plat, self.string)
|
||||
|
||||
|
||||
class SizeParameters(Statement):
|
||||
"""A ``parameters`` statement."""
|
||||
|
||||
@ -1882,3 +1899,132 @@ class VheaField(Statement):
|
||||
fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
|
||||
keywords = dict([(x.lower(), x) for x in fields])
|
||||
return "{} {};".format(keywords[self.key], self.value)
|
||||
|
||||
|
||||
class STATDesignAxisStatement(Statement):
|
||||
"""A STAT table Design Axis
|
||||
|
||||
Args:
|
||||
tag (str): a 4 letter axis tag
|
||||
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
|
||||
self.axisOrder = axisOrder
|
||||
self.names = names
|
||||
self.location = location
|
||||
|
||||
def build(self, builder):
|
||||
builder.addDesignAxis(self, self.location)
|
||||
|
||||
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 += "};"
|
||||
return res
|
||||
|
||||
|
||||
class ElidedFallbackName(Statement):
|
||||
"""STAT table ElidedFallbackName
|
||||
|
||||
Args:
|
||||
names: a list of :class:`STATNameStatement` objects
|
||||
"""
|
||||
|
||||
def __init__(self, names, location=None):
|
||||
Statement.__init__(self, location)
|
||||
self.names = names
|
||||
self.location = location
|
||||
|
||||
def build(self, builder):
|
||||
builder.setElidedFallbackName(self.names, self.location)
|
||||
|
||||
def asFea(self, indent=""):
|
||||
indent += SHIFT
|
||||
res = "ElidedFallbackName { \n"
|
||||
res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
|
||||
res += "};"
|
||||
return res
|
||||
|
||||
|
||||
class ElidedFallbackNameID(Statement):
|
||||
"""STAT table ElidedFallbackNameID
|
||||
|
||||
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
|
||||
self.location = location
|
||||
|
||||
def build(self, builder):
|
||||
builder.setElidedFallbackName(self.value, self.location)
|
||||
|
||||
def asFea(self, indent=""):
|
||||
return f"ElidedFallbackNameID {self.value};"
|
||||
|
||||
|
||||
class STATAxisValueStatement(Statement):
|
||||
"""A STAT table Axis Value Record
|
||||
|
||||
Args:
|
||||
names (list): a list of :class:`STATNameStatement` objects
|
||||
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
|
||||
self.locations = locations
|
||||
self.flags = flags
|
||||
|
||||
def build(self, builder):
|
||||
builder.addAxisValueRecord(self, self.location)
|
||||
|
||||
def asFea(self, indent=""):
|
||||
res = "AxisValue {\n"
|
||||
for location in self.locations:
|
||||
res += location.asFea()
|
||||
|
||||
for nameRecord in self.names:
|
||||
res += nameRecord.asFea()
|
||||
res += "\n"
|
||||
|
||||
if self.flags:
|
||||
flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
|
||||
flagStrings = []
|
||||
curr = 1
|
||||
for i in range(len(flags)):
|
||||
if self.flags & curr != 0:
|
||||
flagStrings.append(flags[i])
|
||||
curr = curr << 1
|
||||
res += f"flag {' '.join(flagStrings)};\n"
|
||||
res += "};"
|
||||
return res
|
||||
|
||||
|
||||
class AxisValueLocationStatement(Statement):
|
||||
"""
|
||||
A STAT table Axis Value Location
|
||||
|
||||
Args:
|
||||
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
|
||||
self.values = values
|
||||
|
||||
def asFea(self, res=""):
|
||||
res += f"location {self.tag} "
|
||||
res += f"{' '.join(str(i) for i in self.values)};\n"
|
||||
return res
|
||||
|
@ -98,6 +98,7 @@ class Builder(object):
|
||||
"hhea",
|
||||
"name",
|
||||
"vhea",
|
||||
"STAT",
|
||||
]
|
||||
)
|
||||
|
||||
@ -159,6 +160,8 @@ class Builder(object):
|
||||
self.hhea_ = {}
|
||||
# for table 'vhea'
|
||||
self.vhea_ = {}
|
||||
# for table 'STAT'
|
||||
self.stat_ = {}
|
||||
|
||||
def build(self, tables=None, debug=False):
|
||||
if self.parseTree is None:
|
||||
@ -188,6 +191,8 @@ class Builder(object):
|
||||
self.build_name()
|
||||
if "OS/2" in tables:
|
||||
self.build_OS_2()
|
||||
if "STAT" in tables:
|
||||
self.build_STAT()
|
||||
for tag in ("GPOS", "GSUB"):
|
||||
if tag not in tables:
|
||||
continue
|
||||
@ -510,6 +515,140 @@ class Builder(object):
|
||||
if version >= 5:
|
||||
checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))
|
||||
|
||||
def setElidedFallbackName(self, value, location):
|
||||
# ElidedFallbackName is a convenience method for setting
|
||||
# ElidedFallbackNameID so only one can be allowed
|
||||
for token in ("ElidedFallbackName", "ElidedFallbackNameID"):
|
||||
if token in self.stat_:
|
||||
raise FeatureLibError(
|
||||
f"{token} is already set.",
|
||||
location,
|
||||
)
|
||||
if isinstance(value, int):
|
||||
self.stat_["ElidedFallbackNameID"] = value
|
||||
elif isinstance(value, list):
|
||||
self.stat_["ElidedFallbackName"] = value
|
||||
else:
|
||||
raise AssertionError(value)
|
||||
|
||||
def addDesignAxis(self, designAxis, location):
|
||||
if "DesignAxes" not in self.stat_:
|
||||
self.stat_["DesignAxes"] = []
|
||||
if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
|
||||
raise FeatureLibError(
|
||||
f'DesignAxis already defined for tag "{designAxis.tag}".',
|
||||
location,
|
||||
)
|
||||
if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
|
||||
raise FeatureLibError(
|
||||
f"DesignAxis already defined for axis number {designAxis.axisOrder}.",
|
||||
location,
|
||||
)
|
||||
self.stat_["DesignAxes"].append(designAxis)
|
||||
|
||||
def addAxisValueRecord(self, axisValueRecord, location):
|
||||
if "AxisValueRecords" not in self.stat_:
|
||||
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
|
||||
):
|
||||
raise FeatureLibError(
|
||||
"An AxisValueRecord with these values is already defined.",
|
||||
location,
|
||||
)
|
||||
self.stat_["AxisValueRecords"].append(axisValueRecord)
|
||||
|
||||
def build_STAT(self):
|
||||
if not self.stat_:
|
||||
return
|
||||
|
||||
axes = self.stat_.get("DesignAxes")
|
||||
if not axes:
|
||||
raise FeatureLibError("DesignAxes not defined", None)
|
||||
axisValueRecords = self.stat_.get("AxisValueRecords")
|
||||
axisValues = {}
|
||||
format4_locations = []
|
||||
for tag in axes:
|
||||
axisValues[tag.tag] = []
|
||||
if axisValueRecords is not None:
|
||||
for avr in axisValueRecords:
|
||||
valuesDict = {}
|
||||
if avr.flags > 0:
|
||||
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
|
||||
nominal, minVal, maxVal = values
|
||||
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,
|
||||
}
|
||||
)
|
||||
format4_locations.append(valuesDict)
|
||||
|
||||
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)
|
||||
if not name:
|
||||
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,
|
||||
)
|
||||
|
||||
def build_codepages_(self, pages):
|
||||
pages2bits = {
|
||||
1252: 0,
|
||||
@ -718,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)
|
||||
|
@ -1003,6 +1003,7 @@ class Parser(object):
|
||||
"name": self.parse_table_name_,
|
||||
"BASE": self.parse_table_BASE_,
|
||||
"OS/2": self.parse_table_OS_2_,
|
||||
"STAT": self.parse_table_STAT_,
|
||||
}.get(name)
|
||||
if handler:
|
||||
handler(table)
|
||||
@ -1162,6 +1163,35 @@ class Parser(object):
|
||||
unescaped = self.unescape_string_(string, encoding)
|
||||
return platformID, platEncID, langID, unescaped
|
||||
|
||||
def parse_stat_name_(self):
|
||||
platEncID = None
|
||||
langID = None
|
||||
if self.next_token_type_ in Lexer.NUMBERS:
|
||||
platformID = self.expect_any_number_()
|
||||
location = self.cur_token_location_
|
||||
if platformID not in (1, 3):
|
||||
raise FeatureLibError("Expected platform id 1 or 3", location)
|
||||
if self.next_token_type_ in Lexer.NUMBERS:
|
||||
platEncID = self.expect_any_number_()
|
||||
langID = self.expect_any_number_()
|
||||
else:
|
||||
platformID = 3
|
||||
location = self.cur_token_location_
|
||||
|
||||
if platformID == 1: # Macintosh
|
||||
platEncID = platEncID or 0 # Roman
|
||||
langID = langID or 0 # English
|
||||
else: # 3, Windows
|
||||
platEncID = platEncID or 1 # Unicode
|
||||
langID = langID or 0x0409 # English
|
||||
|
||||
string = self.expect_string_()
|
||||
encoding = getEncoding(platformID, platEncID, langID)
|
||||
if encoding is None:
|
||||
raise FeatureLibError("Unsupported encoding", location)
|
||||
unescaped = self.unescape_string_(string, encoding)
|
||||
return platformID, platEncID, langID, unescaped
|
||||
|
||||
def parse_nameid_(self):
|
||||
assert self.cur_token_ == "nameid", self.cur_token_
|
||||
location, nameID = self.cur_token_location_, self.expect_any_number_()
|
||||
@ -1283,6 +1313,198 @@ class Parser(object):
|
||||
elif self.cur_token_ == ";":
|
||||
continue
|
||||
|
||||
def parse_STAT_ElidedFallbackName(self):
|
||||
assert self.is_cur_keyword_("ElidedFallbackName")
|
||||
self.expect_symbol_("{")
|
||||
names = []
|
||||
while self.next_token_ != "}" or self.cur_comments_:
|
||||
self.advance_lexer_()
|
||||
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_,
|
||||
)
|
||||
names.append(nameRecord)
|
||||
else:
|
||||
if self.cur_token_ != ";":
|
||||
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_)
|
||||
return names
|
||||
|
||||
def parse_STAT_design_axis(self):
|
||||
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.")
|
||||
axisOrder = self.expect_number_()
|
||||
self.expect_symbol_("{")
|
||||
while self.next_token_ != "}" or self.cur_comments_:
|
||||
self.advance_lexer_()
|
||||
if self.cur_token_type_ is Lexer.COMMENT:
|
||||
continue
|
||||
elif self.is_cur_keyword_("name"):
|
||||
location = self.cur_token_location_
|
||||
platformID, platEncID, langID, string = self.parse_stat_name_()
|
||||
name = self.ast.STATNameStatement(
|
||||
"stat", platformID, platEncID, langID, string, location=location
|
||||
)
|
||||
names.append(name)
|
||||
elif self.cur_token_ == ";":
|
||||
continue
|
||||
else:
|
||||
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_
|
||||
)
|
||||
|
||||
def parse_STAT_axis_value_(self):
|
||||
assert self.is_cur_keyword_("AxisValue")
|
||||
self.expect_symbol_("{")
|
||||
locations = []
|
||||
names = []
|
||||
flags = 0
|
||||
while self.next_token_ != "}" or self.cur_comments_:
|
||||
self.advance_lexer_(comments=True)
|
||||
if self.cur_token_type_ is Lexer.COMMENT:
|
||||
continue
|
||||
elif self.is_cur_keyword_("name"):
|
||||
location = self.cur_token_location_
|
||||
platformID, platEncID, langID, string = self.parse_stat_name_()
|
||||
name = self.ast.STATNameStatement(
|
||||
"stat", platformID, platEncID, langID, string, location=location
|
||||
)
|
||||
names.append(name)
|
||||
elif self.is_cur_keyword_("location"):
|
||||
location = self.parse_STAT_location()
|
||||
locations.append(location)
|
||||
elif self.is_cur_keyword_("flag"):
|
||||
flags = self.expect_stat_flags()
|
||||
elif self.cur_token_ == ";":
|
||||
continue
|
||||
else:
|
||||
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_)
|
||||
if not locations:
|
||||
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_,
|
||||
)
|
||||
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_
|
||||
)
|
||||
format4_tags.append(tag)
|
||||
|
||||
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_,
|
||||
)
|
||||
|
||||
while self.next_token_ != ";":
|
||||
if self.next_token_type_ is Lexer.FLOAT:
|
||||
value = self.expect_float_()
|
||||
values.append(value)
|
||||
elif self.next_token_type_ is Lexer.NUMBER:
|
||||
value = self.expect_number_()
|
||||
values.append(value)
|
||||
else:
|
||||
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_,
|
||||
)
|
||||
return self.ast.AxisValueLocationStatement(tag, values)
|
||||
|
||||
def parse_table_STAT_(self, table):
|
||||
statements = table.statements
|
||||
design_axes = []
|
||||
while self.next_token_ != "}" or self.cur_comments_:
|
||||
self.advance_lexer_(comments=True)
|
||||
if self.cur_token_type_ is Lexer.COMMENT:
|
||||
statements.append(
|
||||
self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
|
||||
)
|
||||
elif self.cur_token_type_ is Lexer.NAME:
|
||||
if self.is_cur_keyword_("ElidedFallbackName"):
|
||||
names = self.parse_STAT_ElidedFallbackName()
|
||||
statements.append(self.ast.ElidedFallbackName(names))
|
||||
elif self.is_cur_keyword_("ElidedFallbackNameID"):
|
||||
value = self.expect_number_()
|
||||
statements.append(self.ast.ElidedFallbackNameID(value))
|
||||
self.expect_symbol_(";")
|
||||
elif self.is_cur_keyword_("DesignAxis"):
|
||||
designAxis = self.parse_STAT_design_axis()
|
||||
design_axes.append(designAxis.tag)
|
||||
statements.append(designAxis)
|
||||
self.expect_symbol_(";")
|
||||
elif self.is_cur_keyword_("AxisValue"):
|
||||
axisValueRecord = self.parse_STAT_axis_value_()
|
||||
for location in axisValueRecord.locations:
|
||||
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_,
|
||||
)
|
||||
statements.append(axisValueRecord)
|
||||
self.expect_symbol_(";")
|
||||
else:
|
||||
raise FeatureLibError(
|
||||
f"Unexpected token {self.cur_token_}", self.cur_token_location_
|
||||
)
|
||||
elif self.cur_token_ == ";":
|
||||
continue
|
||||
|
||||
def parse_base_tag_list_(self):
|
||||
# Parses BASE table entries. (See `section 9.a <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.a>`_)
|
||||
assert self.cur_token_ in (
|
||||
@ -1784,7 +2006,7 @@ class Parser(object):
|
||||
raise FeatureLibError("Expected a tag", self.cur_token_location_)
|
||||
if len(self.cur_token_) > 4:
|
||||
raise FeatureLibError(
|
||||
"Tags can not be longer than 4 characters", self.cur_token_location_
|
||||
"Tags cannot be longer than 4 characters", self.cur_token_location_
|
||||
)
|
||||
return (self.cur_token_ + " ")[:4]
|
||||
|
||||
@ -1856,6 +2078,32 @@ class Parser(object):
|
||||
"Expected an integer or floating-point number", self.cur_token_location_
|
||||
)
|
||||
|
||||
def expect_stat_flags(self):
|
||||
value = 0
|
||||
flags = {
|
||||
"OlderSiblingFontAttribute": 1,
|
||||
"ElidableAxisValueName": 2,
|
||||
}
|
||||
while self.next_token_ != ";":
|
||||
if self.next_token_ in flags:
|
||||
name = self.expect_name_()
|
||||
value = value | flags[name]
|
||||
else:
|
||||
raise FeatureLibError(
|
||||
f"Unexpected STAT flag {self.cur_token_}", self.cur_token_location_
|
||||
)
|
||||
return value
|
||||
|
||||
def expect_stat_values_(self):
|
||||
if self.next_token_type_ == Lexer.FLOAT:
|
||||
return self.expect_float_()
|
||||
elif self.next_token_type_ is Lexer.NUMBER:
|
||||
return self.expect_number_()
|
||||
else:
|
||||
raise FeatureLibError(
|
||||
"Expected an integer or floating-point number", self.cur_token_location_
|
||||
)
|
||||
|
||||
def expect_string_(self):
|
||||
self.advance_lexer_()
|
||||
if self.cur_token_type_ is Lexer.STRING:
|
||||
|
@ -9,6 +9,7 @@ from fontTools.ttLib.tables.otBase import (
|
||||
CountReference,
|
||||
)
|
||||
from fontTools.ttLib.tables import otBase
|
||||
from fontTools.feaLib.ast import STATNameStatement
|
||||
from fontTools.otlLib.error import OpenTypeLibError
|
||||
import logging
|
||||
import copy
|
||||
@ -94,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
|
||||
@ -2575,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):
|
||||
@ -2687,8 +2691,8 @@ def buildStatTable(ttFont, axes, locations=None, elidedFallbackName=2):
|
||||
]
|
||||
|
||||
The optional 'elidedFallbackName' argument can be a name ID (int),
|
||||
a string, or a dictionary containing multilingual names. It
|
||||
translates to the ElidedFallbackNameID field.
|
||||
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
|
||||
'name' table. If a 'STAT' table already exists, it will be
|
||||
@ -2797,6 +2801,20 @@ def _addName(nameTable, value, minNameID=0):
|
||||
names = dict(en=value)
|
||||
elif isinstance(value, dict):
|
||||
names = value
|
||||
elif isinstance(value, list):
|
||||
nameID = nameTable._findUnusedNameID()
|
||||
for nameRecord in value:
|
||||
if isinstance(nameRecord, STATNameStatement):
|
||||
nameTable.setName(
|
||||
nameRecord.string,
|
||||
nameID,
|
||||
nameRecord.platformID,
|
||||
nameRecord.platEncID,
|
||||
nameRecord.langID,
|
||||
)
|
||||
else:
|
||||
raise TypeError("value must be a list of STATNameStatements")
|
||||
return nameID
|
||||
else:
|
||||
raise TypeError("value must be int, str or dict")
|
||||
raise TypeError("value must be int, str, dict or list")
|
||||
return nameTable.addMultilingualName(names, minNameID=minNameID)
|
||||
|
@ -338,6 +338,18 @@ class NameID(UShort):
|
||||
log.warning("name id %d missing from name table" % value)
|
||||
xmlWriter.newline()
|
||||
|
||||
class STATFlags(UShort):
|
||||
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
||||
xmlWriter.simpletag(name, attrs + [("value", value)])
|
||||
flags = []
|
||||
if value & 0x01:
|
||||
flags.append("OlderSiblingFontAttribute")
|
||||
if value & 0x02:
|
||||
flags.append("ElidableAxisValueName")
|
||||
if flags:
|
||||
xmlWriter.write(" ")
|
||||
xmlWriter.comment(" ".join(flags))
|
||||
xmlWriter.newline()
|
||||
|
||||
class FloatValue(SimpleValue):
|
||||
@staticmethod
|
||||
@ -1745,7 +1757,6 @@ converterMapping = {
|
||||
"int8": Int8,
|
||||
"int16": Short,
|
||||
"uint8": UInt8,
|
||||
"uint8": UInt8,
|
||||
"uint16": UShort,
|
||||
"uint24": UInt24,
|
||||
"uint32": ULong,
|
||||
@ -1770,6 +1781,7 @@ converterMapping = {
|
||||
"LookupFlag": LookupFlag,
|
||||
"ExtendMode": ExtendMode,
|
||||
"CompositeMode": CompositeMode,
|
||||
"STATFlags": STATFlags,
|
||||
|
||||
# AAT
|
||||
"CIDGlyphMap": CIDGlyphMap,
|
||||
|
@ -872,7 +872,7 @@ otData = [
|
||||
('AxisValueFormat1', [
|
||||
('uint16', 'Format', None, None, 'Format, = 1'),
|
||||
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
|
||||
('uint16', 'Flags', None, None, 'Flags.'),
|
||||
('STATFlags', 'Flags', None, None, 'Flags.'),
|
||||
('NameID', 'ValueNameID', None, None, ''),
|
||||
('Fixed', 'Value', None, None, ''),
|
||||
]),
|
||||
@ -880,7 +880,7 @@ otData = [
|
||||
('AxisValueFormat2', [
|
||||
('uint16', 'Format', None, None, 'Format, = 2'),
|
||||
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
|
||||
('uint16', 'Flags', None, None, 'Flags.'),
|
||||
('STATFlags', 'Flags', None, None, 'Flags.'),
|
||||
('NameID', 'ValueNameID', None, None, ''),
|
||||
('Fixed', 'NominalValue', None, None, ''),
|
||||
('Fixed', 'RangeMinValue', None, None, ''),
|
||||
@ -890,7 +890,7 @@ otData = [
|
||||
('AxisValueFormat3', [
|
||||
('uint16', 'Format', None, None, 'Format, = 3'),
|
||||
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
|
||||
('uint16', 'Flags', None, None, 'Flags.'),
|
||||
('STATFlags', 'Flags', None, None, 'Flags.'),
|
||||
('NameID', 'ValueNameID', None, None, ''),
|
||||
('Fixed', 'Value', None, None, ''),
|
||||
('Fixed', 'LinkedValue', None, None, ''),
|
||||
@ -899,7 +899,7 @@ otData = [
|
||||
('AxisValueFormat4', [
|
||||
('uint16', 'Format', None, None, 'Format, = 4'),
|
||||
('uint16', 'AxisCount', None, None, 'The total number of axes contributing to this axis-values combination.'),
|
||||
('uint16', 'Flags', None, None, 'Flags.'),
|
||||
('STATFlags', 'Flags', None, None, 'Flags.'),
|
||||
('NameID', 'ValueNameID', None, None, ''),
|
||||
('struct', 'AxisValueRecord', 'AxisCount', 0, 'Array of AxisValue records that provide the combination of axis values, one for each contributing axis. '),
|
||||
]),
|
||||
|
4
Tests/feaLib/STAT2.fea
Normal file
4
Tests/feaLib/STAT2.fea
Normal file
@ -0,0 +1,4 @@
|
||||
table STAT {
|
||||
ElidedFallbackName { name "Roman"; };
|
||||
DesignAxis zonk 0 { name "Zonkey"; };'
|
||||
} STAT;
|
@ -9,6 +9,7 @@ from fontTools.feaLib import ast
|
||||
from fontTools.feaLib.lexer import Lexer
|
||||
import difflib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
@ -73,7 +74,7 @@ class BuilderTest(unittest.TestCase):
|
||||
LigatureSubtable AlternateSubtable MultipleSubstSubtable
|
||||
SingleSubstSubtable aalt_chain_contextual_subst AlternateChained
|
||||
MultipleLookupsPerGlyph MultipleLookupsPerGlyph2 GSUB_6_formats
|
||||
GSUB_5_formats delete_glyph
|
||||
GSUB_5_formats delete_glyph STAT_test STAT_test_elidedFallbackNameID
|
||||
""".split()
|
||||
|
||||
def __init__(self, methodName):
|
||||
@ -118,7 +119,7 @@ class BuilderTest(unittest.TestCase):
|
||||
def expect_ttx(self, font, expected_ttx, replace=None):
|
||||
path = self.temp_path(suffix=".ttx")
|
||||
font.saveXML(path, tables=['head', 'name', 'BASE', 'GDEF', 'GSUB',
|
||||
'GPOS', 'OS/2', 'hhea', 'vhea'])
|
||||
'GPOS', 'OS/2', 'STAT', 'hhea', 'vhea'])
|
||||
actual = self.read_ttx(path)
|
||||
expected = self.read_ttx(expected_ttx)
|
||||
if replace:
|
||||
@ -463,6 +464,201 @@ class BuilderTest(unittest.TestCase):
|
||||
"} test;"
|
||||
)
|
||||
|
||||
def test_STAT_elidedfallbackname_already_defined(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'ElidedFallbackName is already set.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' ElidedFallbackNameID 256;'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_elidedfallbackname_set_twice(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'ElidedFallbackName is already set.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' ElidedFallbackName { name "Italic"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_elidedfallbacknameID_already_defined(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'ElidedFallbackNameID is already set.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackNameID 256;'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_elidedfallbacknameID_not_in_name_table(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'ElidedFallbackNameID 256 points to a nameID that does not '
|
||||
'exist in the "name" table',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 257 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackNameID 256;'
|
||||
' DesignAxis opsz 1 { name "Optical Size"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_design_axis_name(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Expected "name"',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { badtag "Optical Size"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_duplicate_design_axis_name(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'DesignAxis already defined for tag "opsz".',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' DesignAxis opsz 1 { name "Optical Size"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_design_axis_duplicate_order(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
"DesignAxis already defined for axis number 0.",
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' DesignAxis wdth 0 { name "Width"; };'
|
||||
' AxisValue {'
|
||||
' location opsz 8;'
|
||||
' location wdth 400;'
|
||||
' name "Caption";'
|
||||
' };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_undefined_tag(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'DesignAxis not defined for wdth.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' AxisValue { '
|
||||
' location wdth 125; '
|
||||
' name "Wide"; '
|
||||
' };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_axis_value_format4(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Axis tag wdth already defined.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' DesignAxis wdth 1 { name "Width"; };'
|
||||
' DesignAxis wght 2 { name "Weight"; };'
|
||||
' AxisValue { '
|
||||
' location opsz 8; '
|
||||
' location wdth 125; '
|
||||
' location wdth 125; '
|
||||
' location wght 500; '
|
||||
' name "Caption Medium Wide"; '
|
||||
' };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_duplicate_axis_value_record(self):
|
||||
# Test for Duplicate AxisValueRecords even when the definition order
|
||||
# is different.
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'An AxisValueRecord with these values is already defined.',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; };'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' DesignAxis wdth 1 { name "Width"; };'
|
||||
' AxisValue {'
|
||||
' location opsz 8;'
|
||||
' location wdth 400;'
|
||||
' name "Caption";'
|
||||
' };'
|
||||
' AxisValue {'
|
||||
' location wdth 400;'
|
||||
' location opsz 8;'
|
||||
' name "Caption";'
|
||||
' };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_axis_value_missing_location(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Expected "Axis location"',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; '
|
||||
'};'
|
||||
' DesignAxis opsz 0 { name "Optical Size"; };'
|
||||
' AxisValue { '
|
||||
' name "Wide"; '
|
||||
' };'
|
||||
'} STAT;')
|
||||
|
||||
def test_STAT_invalid_location_tag(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Tags cannot be longer than 4 characters',
|
||||
self.build,
|
||||
'table name {'
|
||||
' nameid 256 "Roman"; '
|
||||
'} name;'
|
||||
'table STAT {'
|
||||
' ElidedFallbackName { name "Roman"; '
|
||||
' name 3 1 0x0411 "ローマン"; }; '
|
||||
' DesignAxis width 0 { name "Width"; };'
|
||||
'} STAT;')
|
||||
|
||||
def test_extensions(self):
|
||||
class ast_BaseClass(ast.MarkClass):
|
||||
def asFea(self, indent=""):
|
||||
|
96
Tests/feaLib/data/STAT_bad.fea
Normal file
96
Tests/feaLib/data/STAT_bad.fea
Normal file
@ -0,0 +1,96 @@
|
||||
# bad fea file: Testing DesignAxis tag with incorrect label
|
||||
table name {
|
||||
nameid 25 "TestFont";
|
||||
} name;
|
||||
|
||||
|
||||
table STAT {
|
||||
|
||||
ElidedFallbackName { name "Roman"; };
|
||||
|
||||
DesignAxis opsz 0 { badtag "Optical Size"; }; #'badtag' instead of 'name' is incorrect
|
||||
DesignAxis wdth 1 { name "Width"; };
|
||||
DesignAxis wght 2 { name "Weight"; };
|
||||
DesignAxis ital 3 { name "Italic"; };
|
||||
|
||||
AxisValue {
|
||||
location opsz 8 5 9;
|
||||
location wdth 300 350 450;
|
||||
name "Caption";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 11 9 12;
|
||||
name "Text";
|
||||
flag OlderSiblingFontAttribute ElidableAxisValueName ;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 16.7 12 24;
|
||||
name "Subhead";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 72 24 72;
|
||||
name "Display";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 80 80 89;
|
||||
name "Condensed";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 90 90 96;
|
||||
name "Semicondensed";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 100 97 101;
|
||||
name "Normal";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 125 102 125;
|
||||
name "Extended";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 300 300 349;
|
||||
name "Light";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 400 350 449;
|
||||
name "Regular";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 500 450 549;
|
||||
name "Medium";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 600 550 649;
|
||||
name "Semibold";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 700 650 749;
|
||||
name "Bold";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 900 750 900;
|
||||
name "Black";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location ital 0;
|
||||
name "Roman";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
|
||||
} STAT;
|
109
Tests/feaLib/data/STAT_test.fea
Normal file
109
Tests/feaLib/data/STAT_test.fea
Normal file
@ -0,0 +1,109 @@
|
||||
table name {
|
||||
nameid 25 "TestFont";
|
||||
} name;
|
||||
|
||||
|
||||
table STAT {
|
||||
|
||||
ElidedFallbackName {
|
||||
name "Roman";
|
||||
name 3 1 1041 "ローマン";
|
||||
};
|
||||
|
||||
DesignAxis opsz 0 {
|
||||
name "Optical Size";
|
||||
};
|
||||
|
||||
DesignAxis wdth 1 {
|
||||
name "Width";
|
||||
};
|
||||
|
||||
DesignAxis wght 2 {
|
||||
name "Weight";
|
||||
};
|
||||
|
||||
DesignAxis ital 3 {
|
||||
name "Italic";
|
||||
}; # here comment
|
||||
|
||||
AxisValue {
|
||||
location opsz 8; # comment here
|
||||
location wdth 400; # another comment
|
||||
name "Caption"; # more comments
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 11 9 12;
|
||||
name "Text";
|
||||
flag OlderSiblingFontAttribute ElidableAxisValueName;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 16.7 12 24;
|
||||
name "Subhead";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location opsz 72 24 72;
|
||||
name "Display";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 80 80 89;
|
||||
name "Condensed";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 90 90 96;
|
||||
name "Semicondensed";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 100 97 101;
|
||||
name "Normal";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wdth 125 102 125;
|
||||
name "Extended";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 300 300 349;
|
||||
name "Light";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 400 350 449;
|
||||
name "Regular";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 500 450 549;
|
||||
name "Medium";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 600 550 649;
|
||||
name "Semibold";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 700 650 749;
|
||||
name "Bold";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location wght 900 750 900;
|
||||
name "Black";
|
||||
};
|
||||
|
||||
AxisValue {
|
||||
location ital 0;
|
||||
name "Roman";
|
||||
flag ElidableAxisValueName; # flag comment
|
||||
};
|
||||
|
||||
} STAT;
|
228
Tests/feaLib/data/STAT_test.ttx
Normal file
228
Tests/feaLib/data/STAT_test.ttx
Normal file
@ -0,0 +1,228 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
|
||||
|
||||
<name>
|
||||
<namerecord nameID="25" platformID="3" platEncID="1" langID="0x409">
|
||||
TestFont
|
||||
</namerecord>
|
||||
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
|
||||
Roman
|
||||
</namerecord>
|
||||
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
|
||||
ローマン
|
||||
</namerecord>
|
||||
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
|
||||
Optical Size
|
||||
</namerecord>
|
||||
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
|
||||
Text
|
||||
</namerecord>
|
||||
<namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
|
||||
Subhead
|
||||
</namerecord>
|
||||
<namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
|
||||
Display
|
||||
</namerecord>
|
||||
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
|
||||
Width
|
||||
</namerecord>
|
||||
<namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
|
||||
Condensed
|
||||
</namerecord>
|
||||
<namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
|
||||
Semicondensed
|
||||
</namerecord>
|
||||
<namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
|
||||
Normal
|
||||
</namerecord>
|
||||
<namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
|
||||
Extended
|
||||
</namerecord>
|
||||
<namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
|
||||
Weight
|
||||
</namerecord>
|
||||
<namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
|
||||
Light
|
||||
</namerecord>
|
||||
<namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
|
||||
Medium
|
||||
</namerecord>
|
||||
<namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
|
||||
Semibold
|
||||
</namerecord>
|
||||
<namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
|
||||
Bold
|
||||
</namerecord>
|
||||
<namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
|
||||
Black
|
||||
</namerecord>
|
||||
<namerecord nameID="273" platformID="3" platEncID="1" langID="0x409">
|
||||
Italic
|
||||
</namerecord>
|
||||
<namerecord nameID="274" platformID="3" platEncID="1" langID="0x409">
|
||||
Roman
|
||||
</namerecord>
|
||||
<namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
|
||||
Caption
|
||||
</namerecord>
|
||||
</name>
|
||||
|
||||
<STAT>
|
||||
<Version value="0x00010002"/>
|
||||
<DesignAxisRecordSize value="8"/>
|
||||
<!-- DesignAxisCount=4 -->
|
||||
<DesignAxisRecord>
|
||||
<Axis index="0">
|
||||
<AxisTag value="opsz"/>
|
||||
<AxisNameID value="257"/> <!-- Optical Size -->
|
||||
<AxisOrdering value="0"/>
|
||||
</Axis>
|
||||
<Axis index="1">
|
||||
<AxisTag value="wdth"/>
|
||||
<AxisNameID value="261"/> <!-- Width -->
|
||||
<AxisOrdering value="1"/>
|
||||
</Axis>
|
||||
<Axis index="2">
|
||||
<AxisTag value="wght"/>
|
||||
<AxisNameID value="266"/> <!-- Weight -->
|
||||
<AxisOrdering value="2"/>
|
||||
</Axis>
|
||||
<Axis index="3">
|
||||
<AxisTag value="ital"/>
|
||||
<AxisNameID value="273"/> <!-- Italic -->
|
||||
<AxisOrdering value="3"/>
|
||||
</Axis>
|
||||
</DesignAxisRecord>
|
||||
<!-- AxisValueCount=15 -->
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="4">
|
||||
<!-- AxisCount=2 -->
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="275"/> <!-- Caption -->
|
||||
<AxisValueRecord index="0">
|
||||
<AxisIndex value="0"/>
|
||||
<Value value="8.0"/>
|
||||
</AxisValueRecord>
|
||||
<AxisValueRecord index="1">
|
||||
<AxisIndex value="1"/>
|
||||
<Value value="400.0"/>
|
||||
</AxisValueRecord>
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="3"/> <!-- OlderSiblingFontAttribute ElidableAxisValueName -->
|
||||
<ValueNameID value="258"/> <!-- Text -->
|
||||
<NominalValue value="11.0"/>
|
||||
<RangeMinValue value="9.0"/>
|
||||
<RangeMaxValue value="12.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="2" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="259"/> <!-- Subhead -->
|
||||
<NominalValue value="16.7"/>
|
||||
<RangeMinValue value="12.0"/>
|
||||
<RangeMaxValue value="24.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="3" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="260"/> <!-- Display -->
|
||||
<NominalValue value="72.0"/>
|
||||
<RangeMinValue value="24.0"/>
|
||||
<RangeMaxValue value="72.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="4" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="262"/> <!-- Condensed -->
|
||||
<NominalValue value="80.0"/>
|
||||
<RangeMinValue value="80.0"/>
|
||||
<RangeMaxValue value="89.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="5" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="263"/> <!-- Semicondensed -->
|
||||
<NominalValue value="90.0"/>
|
||||
<RangeMinValue value="90.0"/>
|
||||
<RangeMaxValue value="96.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="6" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="264"/> <!-- Normal -->
|
||||
<NominalValue value="100.0"/>
|
||||
<RangeMinValue value="97.0"/>
|
||||
<RangeMaxValue value="101.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="7" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="265"/> <!-- Extended -->
|
||||
<NominalValue value="125.0"/>
|
||||
<RangeMinValue value="102.0"/>
|
||||
<RangeMaxValue value="125.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="8" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="267"/> <!-- Light -->
|
||||
<NominalValue value="300.0"/>
|
||||
<RangeMinValue value="300.0"/>
|
||||
<RangeMaxValue value="349.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="9" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="268"/> <!-- Regular -->
|
||||
<NominalValue value="400.0"/>
|
||||
<RangeMinValue value="350.0"/>
|
||||
<RangeMaxValue value="449.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="10" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="269"/> <!-- Medium -->
|
||||
<NominalValue value="500.0"/>
|
||||
<RangeMinValue value="450.0"/>
|
||||
<RangeMaxValue value="549.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="11" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="270"/> <!-- Semibold -->
|
||||
<NominalValue value="600.0"/>
|
||||
<RangeMinValue value="550.0"/>
|
||||
<RangeMaxValue value="649.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="12" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="271"/> <!-- Bold -->
|
||||
<NominalValue value="700.0"/>
|
||||
<RangeMinValue value="650.0"/>
|
||||
<RangeMaxValue value="749.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="13" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="272"/> <!-- Black -->
|
||||
<NominalValue value="900.0"/>
|
||||
<RangeMinValue value="750.0"/>
|
||||
<RangeMaxValue value="900.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="14" Format="1">
|
||||
<AxisIndex value="3"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="274"/> <!-- Roman -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
</AxisValueArray>
|
||||
<ElidedFallbackNameID value="256"/> <!-- Roman -->
|
||||
</STAT>
|
||||
|
||||
</ttFont>
|
84
Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea
Normal file
84
Tests/feaLib/data/STAT_test_elidedFallbackNameID.fea
Normal file
@ -0,0 +1,84 @@
|
||||
table name {
|
||||
nameid 25 "TestFont";
|
||||
nameid 256 "Roman";
|
||||
} name;
|
||||
table STAT {
|
||||
ElidedFallbackNameID 256;
|
||||
DesignAxis opsz 0 {
|
||||
name "Optical Size";
|
||||
};
|
||||
DesignAxis wdth 1 {
|
||||
name "Width";
|
||||
};
|
||||
DesignAxis wght 2 {
|
||||
name "Weight";
|
||||
};
|
||||
DesignAxis ital 3 {
|
||||
name "Italic";
|
||||
}; # here comment
|
||||
AxisValue {
|
||||
location opsz 8; # comment here
|
||||
location wdth 400; # another comment
|
||||
name "Caption"; # more comments
|
||||
};
|
||||
AxisValue {
|
||||
location opsz 11 9 12;
|
||||
name "Text";
|
||||
flag OlderSiblingFontAttribute ElidableAxisValueName;
|
||||
};
|
||||
AxisValue {
|
||||
location opsz 16.7 12 24;
|
||||
name "Subhead";
|
||||
};
|
||||
AxisValue {
|
||||
location opsz 72 24 72;
|
||||
name "Display";
|
||||
};
|
||||
AxisValue {
|
||||
location wdth 80 80 89;
|
||||
name "Condensed";
|
||||
};
|
||||
AxisValue {
|
||||
location wdth 90 90 96;
|
||||
name "Semicondensed";
|
||||
};
|
||||
AxisValue {
|
||||
location wdth 100 97 101;
|
||||
name "Normal";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
AxisValue {
|
||||
location wdth 125 102 125;
|
||||
name "Extended";
|
||||
};
|
||||
AxisValue {
|
||||
location wght 300 300 349;
|
||||
name "Light";
|
||||
};
|
||||
AxisValue {
|
||||
location wght 400 350 449;
|
||||
name "Regular";
|
||||
flag ElidableAxisValueName;
|
||||
};
|
||||
AxisValue {
|
||||
location wght 500 450 549;
|
||||
name "Medium";
|
||||
};
|
||||
AxisValue {
|
||||
location wght 600 550 649;
|
||||
name "Semibold";
|
||||
};
|
||||
AxisValue {
|
||||
location wght 700 650 749;
|
||||
name "Bold";
|
||||
};
|
||||
AxisValue {
|
||||
location wght 900 750 900;
|
||||
name "Black";
|
||||
};
|
||||
AxisValue {
|
||||
location ital 0;
|
||||
name "Roman";
|
||||
flag ElidableAxisValueName; # flag comment
|
||||
};
|
||||
} STAT;
|
225
Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx
Normal file
225
Tests/feaLib/data/STAT_test_elidedFallbackNameID.ttx
Normal file
@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
|
||||
|
||||
<name>
|
||||
<namerecord nameID="25" platformID="3" platEncID="1" langID="0x409">
|
||||
TestFont
|
||||
</namerecord>
|
||||
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
|
||||
Roman
|
||||
</namerecord>
|
||||
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
|
||||
Optical Size
|
||||
</namerecord>
|
||||
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
|
||||
Text
|
||||
</namerecord>
|
||||
<namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
|
||||
Subhead
|
||||
</namerecord>
|
||||
<namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
|
||||
Display
|
||||
</namerecord>
|
||||
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
|
||||
Width
|
||||
</namerecord>
|
||||
<namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
|
||||
Condensed
|
||||
</namerecord>
|
||||
<namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
|
||||
Semicondensed
|
||||
</namerecord>
|
||||
<namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
|
||||
Normal
|
||||
</namerecord>
|
||||
<namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
|
||||
Extended
|
||||
</namerecord>
|
||||
<namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
|
||||
Weight
|
||||
</namerecord>
|
||||
<namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
|
||||
Light
|
||||
</namerecord>
|
||||
<namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
|
||||
Medium
|
||||
</namerecord>
|
||||
<namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
|
||||
Semibold
|
||||
</namerecord>
|
||||
<namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
|
||||
Bold
|
||||
</namerecord>
|
||||
<namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
|
||||
Black
|
||||
</namerecord>
|
||||
<namerecord nameID="273" platformID="3" platEncID="1" langID="0x409">
|
||||
Italic
|
||||
</namerecord>
|
||||
<namerecord nameID="274" platformID="3" platEncID="1" langID="0x409">
|
||||
Roman
|
||||
</namerecord>
|
||||
<namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
|
||||
Caption
|
||||
</namerecord>
|
||||
</name>
|
||||
|
||||
<STAT>
|
||||
<Version value="0x00010002"/>
|
||||
<DesignAxisRecordSize value="8"/>
|
||||
<!-- DesignAxisCount=4 -->
|
||||
<DesignAxisRecord>
|
||||
<Axis index="0">
|
||||
<AxisTag value="opsz"/>
|
||||
<AxisNameID value="257"/> <!-- Optical Size -->
|
||||
<AxisOrdering value="0"/>
|
||||
</Axis>
|
||||
<Axis index="1">
|
||||
<AxisTag value="wdth"/>
|
||||
<AxisNameID value="261"/> <!-- Width -->
|
||||
<AxisOrdering value="1"/>
|
||||
</Axis>
|
||||
<Axis index="2">
|
||||
<AxisTag value="wght"/>
|
||||
<AxisNameID value="266"/> <!-- Weight -->
|
||||
<AxisOrdering value="2"/>
|
||||
</Axis>
|
||||
<Axis index="3">
|
||||
<AxisTag value="ital"/>
|
||||
<AxisNameID value="273"/> <!-- Italic -->
|
||||
<AxisOrdering value="3"/>
|
||||
</Axis>
|
||||
</DesignAxisRecord>
|
||||
<!-- AxisValueCount=15 -->
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="4">
|
||||
<!-- AxisCount=2 -->
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="275"/> <!-- Caption -->
|
||||
<AxisValueRecord index="0">
|
||||
<AxisIndex value="0"/>
|
||||
<Value value="8.0"/>
|
||||
</AxisValueRecord>
|
||||
<AxisValueRecord index="1">
|
||||
<AxisIndex value="1"/>
|
||||
<Value value="400.0"/>
|
||||
</AxisValueRecord>
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="3"/> <!-- OlderSiblingFontAttribute ElidableAxisValueName -->
|
||||
<ValueNameID value="258"/> <!-- Text -->
|
||||
<NominalValue value="11.0"/>
|
||||
<RangeMinValue value="9.0"/>
|
||||
<RangeMaxValue value="12.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="2" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="259"/> <!-- Subhead -->
|
||||
<NominalValue value="16.7"/>
|
||||
<RangeMinValue value="12.0"/>
|
||||
<RangeMaxValue value="24.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="3" Format="2">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="260"/> <!-- Display -->
|
||||
<NominalValue value="72.0"/>
|
||||
<RangeMinValue value="24.0"/>
|
||||
<RangeMaxValue value="72.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="4" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="262"/> <!-- Condensed -->
|
||||
<NominalValue value="80.0"/>
|
||||
<RangeMinValue value="80.0"/>
|
||||
<RangeMaxValue value="89.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="5" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="263"/> <!-- Semicondensed -->
|
||||
<NominalValue value="90.0"/>
|
||||
<RangeMinValue value="90.0"/>
|
||||
<RangeMaxValue value="96.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="6" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="264"/> <!-- Normal -->
|
||||
<NominalValue value="100.0"/>
|
||||
<RangeMinValue value="97.0"/>
|
||||
<RangeMaxValue value="101.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="7" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="265"/> <!-- Extended -->
|
||||
<NominalValue value="125.0"/>
|
||||
<RangeMinValue value="102.0"/>
|
||||
<RangeMaxValue value="125.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="8" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="267"/> <!-- Light -->
|
||||
<NominalValue value="300.0"/>
|
||||
<RangeMinValue value="300.0"/>
|
||||
<RangeMaxValue value="349.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="9" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="268"/> <!-- Regular -->
|
||||
<NominalValue value="400.0"/>
|
||||
<RangeMinValue value="350.0"/>
|
||||
<RangeMaxValue value="449.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="10" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="269"/> <!-- Medium -->
|
||||
<NominalValue value="500.0"/>
|
||||
<RangeMinValue value="450.0"/>
|
||||
<RangeMaxValue value="549.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="11" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="270"/> <!-- Semibold -->
|
||||
<NominalValue value="600.0"/>
|
||||
<RangeMinValue value="550.0"/>
|
||||
<RangeMaxValue value="649.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="12" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="271"/> <!-- Bold -->
|
||||
<NominalValue value="700.0"/>
|
||||
<RangeMinValue value="650.0"/>
|
||||
<RangeMaxValue value="749.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="13" Format="2">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="272"/> <!-- Black -->
|
||||
<NominalValue value="900.0"/>
|
||||
<RangeMinValue value="750.0"/>
|
||||
<RangeMaxValue value="900.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="14" Format="1">
|
||||
<AxisIndex value="3"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="274"/> <!-- Roman -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
</AxisValueArray>
|
||||
<ElidedFallbackNameID value="256"/> <!-- Roman -->
|
||||
</STAT>
|
||||
|
||||
</ttFont>
|
@ -1280,6 +1280,76 @@ class ParserTest(unittest.TestCase):
|
||||
'"dflt" is not a valid script tag; use "DFLT" instead',
|
||||
self.parse, "feature test {script dflt;} test;")
|
||||
|
||||
def test_stat_design_axis(self): # STAT DesignAxis
|
||||
doc = self.parse('table STAT { DesignAxis opsz 0 '
|
||||
'{name "Optical Size";}; } STAT;')
|
||||
da = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(da, ast.STATDesignAxisStatement)
|
||||
self.assertEqual(da.tag, 'opsz')
|
||||
self.assertEqual(da.axisOrder, 0)
|
||||
self.assertEqual(da.names[0].string, 'Optical Size')
|
||||
|
||||
def test_stat_axis_value_format1(self): # STAT AxisValue
|
||||
doc = self.parse('table STAT { DesignAxis opsz 0 '
|
||||
'{name "Optical Size";}; '
|
||||
'AxisValue {location opsz 8; name "Caption";}; } '
|
||||
'STAT;')
|
||||
avr = doc.statements[0].statements[1]
|
||||
self.assertIsInstance(avr, ast.STATAxisValueStatement)
|
||||
self.assertEqual(avr.locations[0].tag, 'opsz')
|
||||
self.assertEqual(avr.locations[0].values[0], 8)
|
||||
self.assertEqual(avr.names[0].string, 'Caption')
|
||||
|
||||
def test_stat_axis_value_format2(self): # STAT AxisValue
|
||||
doc = self.parse('table STAT { DesignAxis opsz 0 '
|
||||
'{name "Optical Size";}; '
|
||||
'AxisValue {location opsz 8 6 10; name "Caption";}; } '
|
||||
'STAT;')
|
||||
avr = doc.statements[0].statements[1]
|
||||
self.assertIsInstance(avr, ast.STATAxisValueStatement)
|
||||
self.assertEqual(avr.locations[0].tag, 'opsz')
|
||||
self.assertEqual(avr.locations[0].values, [8, 6, 10])
|
||||
self.assertEqual(avr.names[0].string, 'Caption')
|
||||
|
||||
def test_stat_axis_value_format2_bad_range(self): # STAT AxisValue
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Default value 5 is outside of specified range 6-10.',
|
||||
self.parse, 'table STAT { DesignAxis opsz 0 '
|
||||
'{name "Optical Size";}; '
|
||||
'AxisValue {location opsz 5 6 10; name "Caption";}; } '
|
||||
'STAT;')
|
||||
|
||||
def test_stat_axis_value_format4(self): # STAT AxisValue
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Only one value is allowed in a Format 4 Axis Value Record, but 3 were found.',
|
||||
self.parse, 'table STAT { '
|
||||
'DesignAxis opsz 0 {name "Optical Size";}; '
|
||||
'DesignAxis wdth 0 {name "Width";}; '
|
||||
'AxisValue {'
|
||||
'location opsz 8 6 10; '
|
||||
'location wdth 400; '
|
||||
'name "Caption";}; } '
|
||||
'STAT;')
|
||||
|
||||
def test_stat_elidedfallbackname(self): # STAT ElidedFallbackName
|
||||
doc = self.parse('table STAT { ElidedFallbackName {name "Roman"; '
|
||||
'name 3 1 0x0411 "ローマン"; }; '
|
||||
'} STAT;')
|
||||
nameRecord = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(nameRecord, ast.ElidedFallbackName)
|
||||
self.assertEqual(nameRecord.names[0].string, 'Roman')
|
||||
self.assertEqual(nameRecord.names[1].string, 'ローマン')
|
||||
|
||||
def test_stat_elidedfallbacknameid(self): # STAT ElidedFallbackNameID
|
||||
doc = self.parse('table name { nameid 278 "Roman"; } name; '
|
||||
'table STAT { ElidedFallbackNameID 278; '
|
||||
'} STAT;')
|
||||
nameRecord = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(nameRecord, ast.NameRecord)
|
||||
self.assertEqual(nameRecord.string, 'Roman')
|
||||
|
||||
def test_sub_single_format_a(self): # GSUB LookupType 1
|
||||
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
|
@ -393,7 +393,7 @@
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="1">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
@ -405,7 +405,7 @@
|
||||
</AxisValue>
|
||||
<AxisValue index="2" Format="1">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
@ -417,7 +417,7 @@
|
||||
</AxisValue>
|
||||
<AxisValue index="4" Format="1">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
@ -429,7 +429,7 @@
|
||||
</AxisValue>
|
||||
<AxisValue index="6" Format="1">
|
||||
<AxisIndex value="3"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
|
@ -1138,7 +1138,7 @@ buildStatTable_test_data = [
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="256"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
@ -1193,7 +1193,7 @@ buildStatTable_test_data = [
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
@ -1211,7 +1211,7 @@ buildStatTable_test_data = [
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="4" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
@ -1246,7 +1246,7 @@ buildStatTable_test_data = [
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="257"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
@ -1291,7 +1291,7 @@ buildStatTable_test_data = [
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="2">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="258"/> <!-- Text -->',
|
||||
' <NominalValue value="14.0"/>',
|
||||
' <RangeMinValue value="10.0"/>',
|
||||
@ -1354,7 +1354,7 @@ buildStatTable_test_data = [
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
|
@ -147,7 +147,7 @@ STAT_XML_AXIS_VALUE_FORMAT3 = [
|
||||
'<AxisValueArray>',
|
||||
' <AxisValue index="0" Format="3">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="2"/>',
|
||||
' <Value value="400.0"/>',
|
||||
' <LinkedValue value="700.0"/>',
|
||||
@ -191,7 +191,7 @@ STAT_XML_VERSION_1_1 = [
|
||||
'<AxisValueArray>',
|
||||
' <AxisValue index="0" Format="3">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
|
||||
' <ValueNameID value="2"/>',
|
||||
' <Value value="400.0"/>',
|
||||
' <LinkedValue value="700.0"/>',
|
||||
|
@ -531,7 +531,7 @@
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Regular -->
|
||||
<NominalValue value="100.0"/>
|
||||
<RangeMinValue value="93.75"/>
|
||||
|
@ -519,14 +519,14 @@
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="3">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Regular -->
|
||||
<Value value="400.0"/>
|
||||
<LinkedValue value="700.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Regular -->
|
||||
<NominalValue value="100.0"/>
|
||||
<RangeMinValue value="93.75"/>
|
||||
|
@ -525,7 +525,7 @@
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="3">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Regular -->
|
||||
<Value value="400.0"/>
|
||||
<LinkedValue value="700.0"/>
|
||||
|
@ -531,7 +531,7 @@
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="2">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/>
|
||||
<Flags value="2"/> <!-- ElidableAxisValueName -->
|
||||
<ValueNameID value="261"/> <!-- Regular -->
|
||||
<NominalValue value="100.0"/>
|
||||
<RangeMinValue value="93.75"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user