Reusing otlLib buildStatTable() in feaLib

This commit is contained in:
Kamile Demir 2021-02-19 17:17:28 -05:00 committed by Cosimo Lupo
parent 0434b1a917
commit 29ff42d15f
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F
10 changed files with 453 additions and 187 deletions

View File

@ -29,7 +29,7 @@ __all__ = [
"Anchor",
"AnchorDefinition",
"AttachStatement",
"AxisValueLocation",
"AxisValueLocationStatement",
"BaseAxis",
"CVParametersNameStatement",
"ChainContextPosStatement",
@ -66,8 +66,8 @@ __all__ = [
"SingleSubstStatement",
"SizeParameters",
"Statement",
"STATAxisValueRecord",
"STATDesignAxis",
"STATAxisValueStatement",
"STATDesignAxisStatement",
"STATNameStatement",
"SubtableStatement",
"TableBlock",
@ -1902,7 +1902,7 @@ class VheaField(Statement):
return "{} {};".format(keywords[self.key], self.value)
class STATDesignAxis(Statement):
class STATDesignAxisStatement(Statement):
"""A STAT table Design Axis
Args:
@ -1970,12 +1970,12 @@ class ElidedFallbackNameID(Statement):
return f"ElidedFallbackNameID {self.value};"
class STATAxisValueRecord(Statement):
class STATAxisValueStatement(Statement):
"""A STAT table Axis Value Record
Args:
names (list): a list of :class:`STATNameStatement` objects
locations (list): a list of :class:`AxisValueLocation` objects
locations (list): a list of :class:`AxisValueLocationStatement` objects
flags (int): an int
"""
def __init__(self, names, locations, flags, location=None):
@ -1990,8 +1990,7 @@ class STATAxisValueRecord(Statement):
def asFea(self, indent=""):
res = "AxisValue {\n"
for location in self.locations:
res += f"location {location.tag} "
res += f"{' '.join(str(i) for i in location.values)};\n"
res += location.asFea()
for nameRecord in self.names:
res += nameRecord.asFea()
@ -2010,7 +2009,7 @@ class STATAxisValueRecord(Statement):
return res
class AxisValueLocation(NamedTuple):
class AxisValueLocationStatement(Statement):
"""
A STAT table Axis Value Location
@ -2018,5 +2017,12 @@ class AxisValueLocation(NamedTuple):
tag (str): a 4 letter axis tag
values (list): a list of ints and/or floats
"""
tag: str
values: list
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

View File

@ -536,7 +536,7 @@ class Builder(object):
self.stat_["DesignAxes"] = []
if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
raise FeatureLibError(
'DesignAxis already defined for tag "%s".' % designAxis.tag,
f'DesignAxis already defined for tag "{designAxis.tag}".',
location,
)
if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
@ -551,9 +551,10 @@ class Builder(object):
self.stat_["AxisValueRecords"] = []
# Check for duplicate AxisValueRecords
for record_ in self.stat_["AxisValueRecords"]:
if (sorted([n.asFea() for n in record_.names]) ==
sorted([n.asFea() for n in axisValueRecord.names]) and
sorted(record_.locations) == sorted(axisValueRecord.locations)
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.",
@ -564,126 +565,65 @@ class Builder(object):
def build_STAT(self):
if not self.stat_:
return
self.font["STAT"] = newTable("STAT")
table = self.font["STAT"].table = otTables.STAT()
table.Version = 0x00010001
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('ElidedFallbackNameID %d points '
raise FeatureLibError(f'ElidedFallbackNameID {nameID} points '
'to a nameID that does not exist in the '
'"name" table' % nameID, None)
table.ElidedFallbackNameID = nameID
if "ElidedFallbackName" in self.stat_:
nameRecords = self.stat_["ElidedFallbackName"]
nameID = self.get_user_name_id(nameTable)
for nameRecord in nameRecords:
nameTable.setName(nameRecord.string, nameID,
nameRecord.platformID, nameRecord.platEncID,
nameRecord.langID)
table.ElidedFallbackNameID = nameID
'"name" table', None)
elif "ElidedFallbackName" in self.stat_:
nameID = self.stat_["ElidedFallbackName"]
axisRecords = []
axisValueRecords = []
designAxisOrder = {}
for record in self.stat_["DesignAxes"]:
axis = otTables.AxisRecord()
axis.AxisTag = record.tag
nameID = self.get_user_name_id(nameTable)
for nameRecord in record.names:
nameTable.setName(nameRecord.string, nameID,
nameRecord.platformID, nameRecord.platEncID,
nameRecord.langID)
otl.buildStatTable(self.font, designAxes, locations=format4_locations,
elidedFallbackName=nameID)
axis.AxisNameID = nameID
axis.AxisOrdering = record.axisOrder
axisRecords.append(axis)
designAxisOrder[record.tag] = record.axisOrder
if "AxisValueRecords" in self.stat_:
for record in self.stat_["AxisValueRecords"]:
if len(record.locations) == 1:
location = record.locations[0]
tag = location.tag
values = location.values
axisOrder = designAxisOrder[tag]
axisValueRecord = otTables.AxisValue()
axisValueRecord.AxisIndex = axisOrder
axisValueRecord.Flags = record.flags
nameID = self.get_user_name_id(nameTable)
for nameRecord in record.names:
nameTable.setName(nameRecord.string, nameID,
nameRecord.platformID,
nameRecord.platEncID,
nameRecord.langID)
axisValueRecord.ValueNameID = nameID
if len(values) == 1:
axisValueRecord.Format = 1
axisValueRecord.Value = values[0]
if len(values) == 2:
axisValueRecord.Format = 3
axisValueRecord.Value = values[0]
axisValueRecord.LinkedValue = values[1]
if len(values) == 3:
axisValueRecord.Format = 2
nominal, minVal, maxVal = values
axisValueRecord.NominalValue = nominal
axisValueRecord.RangeMinValue = minVal
axisValueRecord.RangeMaxValue = maxVal
axisValueRecords.append(axisValueRecord)
if len(record.locations) > 1:
# Multiple locations = Format 4
table.Version = 0x00010002
axisValue = otTables.AxisValue()
axisValue.Format = 4
nameID = self.get_user_name_id(nameTable)
for nameRecord in record.names:
nameTable.setName(nameRecord.string, nameID,
nameRecord.platformID,
nameRecord.platEncID,
nameRecord.langID)
axisValue.ValueNameID = nameID
axisValue.Flags = record.flags
axisValueRecords_fmt4 = []
for location in record.locations:
tag = location.tag
values = location.values
axisOrder = designAxisOrder[tag]
axisValueRecord = otTables.AxisValueRecord()
axisValueRecord.AxisIndex = axisOrder
axisValueRecord.Value = values[0]
axisValueRecords_fmt4.append(axisValueRecord)
axisValue.AxisCount = len(axisValueRecords_fmt4)
axisValue.AxisValueRecord = axisValueRecords_fmt4
axisValueRecords.append(axisValue)
if axisRecords:
# Store AxisRecords
axisRecordArray = otTables.AxisRecordArray()
axisRecordArray.Axis = axisRecords
# XXX these should not be hard-coded but computed automatically
table.DesignAxisRecordSize = 8
table.DesignAxisRecord = axisRecordArray
table.DesignAxisCount = len(axisRecords)
if axisValueRecords:
# Store AxisValueRecords
axisValueArray = otTables.AxisValueArray()
axisValueArray.AxisValue = axisValueRecords
table.AxisValueArray = axisValueArray
table.AxisValueCount = len(axisValueRecords)
def build_codepages_(self, pages):
pages2bits = {

View File

@ -1186,8 +1186,6 @@ class Parser(object):
langID = langID or 0x0409 # English
string = self.expect_string_()
# self.expect_symbol_(";")
encoding = getEncoding(platformID, platEncID, langID)
if encoding is None:
raise FeatureLibError("Unsupported encoding", location)
@ -1368,7 +1366,7 @@ class Parser(object):
self.cur_token_location_)
self.expect_symbol_("}")
return self.ast.STATDesignAxis(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")
@ -1420,7 +1418,7 @@ class Parser(object):
self.cur_token_location_)
format4_tags.append(tag)
return self.ast.STATAxisValueRecord(names, locations, flags, self.cur_token_location_)
return self.ast.STATAxisValueStatement(names, locations, flags, self.cur_token_location_)
def parse_STAT_location(self):
values = []
@ -1447,7 +1445,7 @@ class Parser(object):
f'of specified range '
f'{min_val}-{max_val}.',
self.next_token_location_)
return self.ast.AxisValueLocation(tag, values)
return self.ast.AxisValueLocationStatement(tag, values)
def parse_table_STAT_(self, table):
statements = table.statements
@ -1989,7 +1987,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]

View File

@ -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
@ -2687,8 +2688,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 +2798,16 @@ 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 int, str or dict")
raise TypeError("value must be a list of STATNameStatements")
return nameID
else:
raise TypeError("value must be int, str, dict or list")
return nameTable.addMultilingualName(names, minNameID=minNameID)

View File

@ -74,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 STAT_test
GSUB_5_formats delete_glyph STAT_test STAT_test_elidedFallbackNameID
""".split()
def __init__(self, methodName):
@ -514,6 +514,7 @@ class BuilderTest(unittest.TestCase):
'} name;'
'table STAT {'
' ElidedFallbackNameID 256;'
' DesignAxis opsz 1 { name "Optical Size"; };'
'} STAT;')
def test_STAT_design_axis_name(self):
@ -647,7 +648,7 @@ class BuilderTest(unittest.TestCase):
def test_STAT_invalid_location_tag(self):
self.assertRaisesRegex(
FeatureLibError,
'Tags can not be longer than 4 characters',
'Tags cannot be longer than 4 characters',
self.build,
'table name {'
' nameid 256 "Roman"; '

View File

@ -1,3 +1,4 @@
# bad fea file: Testing DesignAxis tag with incorrect label
table name {
nameid 25 "TestFont";
} name;
@ -7,7 +8,7 @@ table STAT {
ElidedFallbackName { name "Roman"; };
DesignAxis opsz 0 { badtag "Optical Size"; };
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"; };

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.13">
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.20">
<name>
<namerecord nameID="25" platformID="3" platEncID="1" langID="0x409">
@ -15,59 +15,59 @@
Optical Size
</namerecord>
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
Width
</namerecord>
<namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
Weight
</namerecord>
<namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
Italic
</namerecord>
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
Caption
</namerecord>
<namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
Text
</namerecord>
<namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
Subhead
</namerecord>
<namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
Display
</namerecord>
<namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
Width
</namerecord>
<namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
Condensed
</namerecord>
<namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
Semicondensed
</namerecord>
<namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
Normal
</namerecord>
<namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
Extended
</namerecord>
<namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
Weight
</namerecord>
<namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
Light
</namerecord>
<namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
Medium
</namerecord>
<namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="270" platformID="3" platEncID="1" langID="0x409">
Semibold
</namerecord>
<namerecord nameID="273" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="271" platformID="3" platEncID="1" langID="0x409">
Bold
</namerecord>
<namerecord nameID="274" platformID="3" platEncID="1" langID="0x409">
<namerecord nameID="272" platformID="3" platEncID="1" langID="0x409">
Black
</namerecord>
<namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
<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>
@ -82,17 +82,17 @@
</Axis>
<Axis index="1">
<AxisTag value="wdth"/>
<AxisNameID value="258"/> <!-- Width -->
<AxisNameID value="261"/> <!-- Width -->
<AxisOrdering value="1"/>
</Axis>
<Axis index="2">
<AxisTag value="wght"/>
<AxisNameID value="259"/> <!-- Weight -->
<AxisNameID value="266"/> <!-- Weight -->
<AxisOrdering value="2"/>
</Axis>
<Axis index="3">
<AxisTag value="ital"/>
<AxisNameID value="260"/> <!-- Italic -->
<AxisNameID value="273"/> <!-- Italic -->
<AxisOrdering value="3"/>
</Axis>
</DesignAxisRecord>
@ -101,7 +101,7 @@
<AxisValue index="0" Format="4">
<!-- AxisCount=2 -->
<Flags value="0"/>
<ValueNameID value="261"/> <!-- Caption -->
<ValueNameID value="275"/> <!-- Caption -->
<AxisValueRecord index="0">
<AxisIndex value="0"/>
<Value value="8.0"/>
@ -114,7 +114,7 @@
<AxisValue index="1" Format="2">
<AxisIndex value="0"/>
<Flags value="3"/> <!-- OlderSiblingFontAttribute ElidableAxisValueName -->
<ValueNameID value="262"/> <!-- Text -->
<ValueNameID value="258"/> <!-- Text -->
<NominalValue value="11.0"/>
<RangeMinValue value="9.0"/>
<RangeMaxValue value="12.0"/>
@ -122,7 +122,7 @@
<AxisValue index="2" Format="2">
<AxisIndex value="0"/>
<Flags value="0"/>
<ValueNameID value="263"/> <!-- Subhead -->
<ValueNameID value="259"/> <!-- Subhead -->
<NominalValue value="16.7"/>
<RangeMinValue value="12.0"/>
<RangeMaxValue value="24.0"/>
@ -130,7 +130,7 @@
<AxisValue index="3" Format="2">
<AxisIndex value="0"/>
<Flags value="0"/>
<ValueNameID value="264"/> <!-- Display -->
<ValueNameID value="260"/> <!-- Display -->
<NominalValue value="72.0"/>
<RangeMinValue value="24.0"/>
<RangeMaxValue value="72.0"/>
@ -138,7 +138,7 @@
<AxisValue index="4" Format="2">
<AxisIndex value="1"/>
<Flags value="0"/>
<ValueNameID value="265"/> <!-- Condensed -->
<ValueNameID value="262"/> <!-- Condensed -->
<NominalValue value="80.0"/>
<RangeMinValue value="80.0"/>
<RangeMaxValue value="89.0"/>
@ -146,7 +146,7 @@
<AxisValue index="5" Format="2">
<AxisIndex value="1"/>
<Flags value="0"/>
<ValueNameID value="266"/> <!-- Semicondensed -->
<ValueNameID value="263"/> <!-- Semicondensed -->
<NominalValue value="90.0"/>
<RangeMinValue value="90.0"/>
<RangeMaxValue value="96.0"/>
@ -154,7 +154,7 @@
<AxisValue index="6" Format="2">
<AxisIndex value="1"/>
<Flags value="2"/> <!-- ElidableAxisValueName -->
<ValueNameID value="267"/> <!-- Normal -->
<ValueNameID value="264"/> <!-- Normal -->
<NominalValue value="100.0"/>
<RangeMinValue value="97.0"/>
<RangeMaxValue value="101.0"/>
@ -162,7 +162,7 @@
<AxisValue index="7" Format="2">
<AxisIndex value="1"/>
<Flags value="0"/>
<ValueNameID value="268"/> <!-- Extended -->
<ValueNameID value="265"/> <!-- Extended -->
<NominalValue value="125.0"/>
<RangeMinValue value="102.0"/>
<RangeMaxValue value="125.0"/>
@ -170,7 +170,7 @@
<AxisValue index="8" Format="2">
<AxisIndex value="2"/>
<Flags value="0"/>
<ValueNameID value="269"/> <!-- Light -->
<ValueNameID value="267"/> <!-- Light -->
<NominalValue value="300.0"/>
<RangeMinValue value="300.0"/>
<RangeMaxValue value="349.0"/>
@ -178,7 +178,7 @@
<AxisValue index="9" Format="2">
<AxisIndex value="2"/>
<Flags value="2"/> <!-- ElidableAxisValueName -->
<ValueNameID value="270"/> <!-- Regular -->
<ValueNameID value="268"/> <!-- Regular -->
<NominalValue value="400.0"/>
<RangeMinValue value="350.0"/>
<RangeMaxValue value="449.0"/>
@ -186,7 +186,7 @@
<AxisValue index="10" Format="2">
<AxisIndex value="2"/>
<Flags value="0"/>
<ValueNameID value="271"/> <!-- Medium -->
<ValueNameID value="269"/> <!-- Medium -->
<NominalValue value="500.0"/>
<RangeMinValue value="450.0"/>
<RangeMaxValue value="549.0"/>
@ -194,7 +194,7 @@
<AxisValue index="11" Format="2">
<AxisIndex value="2"/>
<Flags value="0"/>
<ValueNameID value="272"/> <!-- Semibold -->
<ValueNameID value="270"/> <!-- Semibold -->
<NominalValue value="600.0"/>
<RangeMinValue value="550.0"/>
<RangeMaxValue value="649.0"/>
@ -202,7 +202,7 @@
<AxisValue index="12" Format="2">
<AxisIndex value="2"/>
<Flags value="0"/>
<ValueNameID value="273"/> <!-- Bold -->
<ValueNameID value="271"/> <!-- Bold -->
<NominalValue value="700.0"/>
<RangeMinValue value="650.0"/>
<RangeMaxValue value="749.0"/>
@ -210,7 +210,7 @@
<AxisValue index="13" Format="2">
<AxisIndex value="2"/>
<Flags value="0"/>
<ValueNameID value="274"/> <!-- Black -->
<ValueNameID value="272"/> <!-- Black -->
<NominalValue value="900.0"/>
<RangeMinValue value="750.0"/>
<RangeMaxValue value="900.0"/>
@ -218,7 +218,7 @@
<AxisValue index="14" Format="1">
<AxisIndex value="3"/>
<Flags value="2"/> <!-- ElidableAxisValueName -->
<ValueNameID value="275"/> <!-- Roman -->
<ValueNameID value="274"/> <!-- Roman -->
<Value value="0.0"/>
</AxisValue>
</AxisValueArray>

View 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;

View 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>

View File

@ -1284,7 +1284,7 @@ class ParserTest(unittest.TestCase):
doc = self.parse('table STAT { DesignAxis opsz 0 '
'{name "Optical Size";}; } STAT;')
da = doc.statements[0].statements[0]
self.assertIsInstance(da, ast.STATDesignAxis)
self.assertIsInstance(da, ast.STATDesignAxisStatement)
self.assertEqual(da.tag, 'opsz')
self.assertEqual(da.axisOrder, 0)
self.assertEqual(da.names[0].string, 'Optical Size')
@ -1295,7 +1295,7 @@ class ParserTest(unittest.TestCase):
'AxisValue {location opsz 8; name "Caption";}; } '
'STAT;')
avr = doc.statements[0].statements[1]
self.assertIsInstance(avr, ast.STATAxisValueRecord)
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')
@ -1306,7 +1306,7 @@ class ParserTest(unittest.TestCase):
'AxisValue {location opsz 8 6 10; name "Caption";}; } '
'STAT;')
avr = doc.statements[0].statements[1]
self.assertIsInstance(avr, ast.STATAxisValueRecord)
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')