[otlLib.builder] Add function to build STAT table from high-level description (#1926)
* added a function to build a STAT table: `fontTools.otlLib.builder.buildStatTable()` * make `varLib._add_stat()` a client of `buildStatTable()`
This commit is contained in:
parent
50c77c138e
commit
d6bb38c7e6
@ -803,6 +803,15 @@ class FontBuilder(object):
|
||||
nameTable=self.font.get("name")
|
||||
)
|
||||
|
||||
def setupStat(self, axes, locations=None, elidedFallbackName=2):
|
||||
"""Build a new 'STAT' table.
|
||||
|
||||
See `fontTools.otlLib.builder.buildStatTable` for details about
|
||||
the arguments.
|
||||
"""
|
||||
from .otlLib.builder import buildStatTable
|
||||
buildStatTable(self.font, axes, locations, elidedFallbackName)
|
||||
|
||||
|
||||
def buildCmapSubTable(cmapping, format, platformID, platEncID):
|
||||
subTable = cmap_classes[format](format)
|
||||
|
@ -1,4 +1,5 @@
|
||||
from collections import namedtuple
|
||||
from fontTools.misc.fixedTools import fixedToFloat
|
||||
from fontTools import ttLib
|
||||
from fontTools.ttLib.tables import otTables as ot
|
||||
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
|
||||
@ -657,3 +658,193 @@ class ClassDefBuilder(object):
|
||||
classDef = ot.ClassDef()
|
||||
classDef.classDefs = glyphClasses
|
||||
return classDef
|
||||
|
||||
|
||||
AXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16)
|
||||
AXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16)
|
||||
|
||||
|
||||
def buildStatTable(ttFont, axes, locations=None, elidedFallbackName=2):
|
||||
"""Add a 'STAT' table to 'ttFont'.
|
||||
|
||||
'axes' is a list of dictionaries describing axes and their
|
||||
values.
|
||||
|
||||
Example:
|
||||
|
||||
axes = [
|
||||
dict(
|
||||
tag="wght",
|
||||
name="Weight",
|
||||
ordering=0, # optional
|
||||
values=[
|
||||
dict(value=100, name='Thin'),
|
||||
dict(value=300, name='Light'),
|
||||
dict(value=400, name='Regular', flags=0x2),
|
||||
dict(value=900, name='Black'),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
Each axis dict must have 'tag' and 'name' items. 'tag' maps
|
||||
to the 'AxisTag' field. 'name' can be a name ID (int), a string,
|
||||
or a dictionary containing multilingual names (see the
|
||||
addMultilingualName() name table method), and will translate to
|
||||
the AxisNameID field.
|
||||
|
||||
An axis dict may contain an 'ordering' item that maps to the
|
||||
AxisOrdering field. If omitted, the order of the axes list is
|
||||
used to calculate AxisOrdering fields.
|
||||
|
||||
The axis dict may contain a 'values' item, which is a list of
|
||||
dictionaries describing AxisValue records belonging to this axis.
|
||||
|
||||
Each value dict must have a 'name' item, which can be a name ID
|
||||
(int), a string, or a dictionary containing multilingual names,
|
||||
like the axis name. It translates to the ValueNameID field.
|
||||
|
||||
Optionally the value dict can contain a 'flags' item. It maps to
|
||||
the AxisValue Flags field, and will be 0 when omitted.
|
||||
|
||||
The format of the AxisValue is determined by the remaining contents
|
||||
of the value dictionary:
|
||||
|
||||
If the value dict contains a 'value' item, an AxisValue record
|
||||
Format 1 is created. If in addition to the 'value' item it contains
|
||||
a 'linkedValue' item, an AxisValue record Format 3 is built.
|
||||
|
||||
If the value dict contains a 'nominalValue' item, an AxisValue
|
||||
record Format 2 is built. Optionally it may contain 'rangeMinValue'
|
||||
and 'rangeMaxValue' items. These map to -Infinity and +Infinity
|
||||
respectively if omitted.
|
||||
|
||||
You cannot specify Format 4 AxisValue tables this way, as they are
|
||||
not tied to a single axis, and specify a name for a location that
|
||||
is defined by multiple axes values. Instead, you need to supply the
|
||||
'locations' argument.
|
||||
|
||||
The optional 'locations' argument specifies AxisValue Format 4
|
||||
tables. It should be a list of dicts, where each dict has a 'name'
|
||||
item, which works just like the value dicts above, an optional
|
||||
'flags' item (defaulting to 0x0), and a 'location' dict. A
|
||||
location dict key is an axis tag, and the associated value is the
|
||||
location on the specified axis. They map to the AxisIndex and Value
|
||||
fields of the AxisValueRecord.
|
||||
|
||||
Example:
|
||||
|
||||
locations = [
|
||||
dict(name='Regular ABCD', location=dict(wght=300, ABCD=100)),
|
||||
dict(name='Bold ABCD XYZ', location=dict(wght=600, ABCD=200)),
|
||||
]
|
||||
|
||||
The optional 'elidedFallbackName' argument can be a name ID (int),
|
||||
a string, or a dictionary containing multilingual names. 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
|
||||
overwritten by the newly created one.
|
||||
"""
|
||||
ttFont["STAT"] = ttLib.newTable("STAT")
|
||||
statTable = ttFont["STAT"].table = ot.STAT()
|
||||
nameTable = ttFont["name"]
|
||||
statTable.ElidedFallbackNameID = _addName(nameTable, elidedFallbackName)
|
||||
|
||||
# 'locations' contains data for AxisValue Format 4
|
||||
axisRecords, axisValues = _buildAxisRecords(axes, nameTable)
|
||||
if not locations:
|
||||
statTable.Version = 0x00010001
|
||||
else:
|
||||
# We'll be adding Format 4 AxisValue records, which
|
||||
# requires a higher table version
|
||||
statTable.Version = 0x00010002
|
||||
multiAxisValues = _buildAxisValuesFormat4(locations, axes, nameTable)
|
||||
axisValues = multiAxisValues + axisValues
|
||||
|
||||
# Store AxisRecords
|
||||
axisRecordArray = ot.AxisRecordArray()
|
||||
axisRecordArray.Axis = axisRecords
|
||||
# XXX these should not be hard-coded but computed automatically
|
||||
statTable.DesignAxisRecordSize = 8
|
||||
statTable.DesignAxisRecord = axisRecordArray
|
||||
statTable.DesignAxisCount = len(axisRecords)
|
||||
|
||||
if axisValues:
|
||||
# Store AxisValueRecords
|
||||
axisValueArray = ot.AxisValueArray()
|
||||
axisValueArray.AxisValue = axisValues
|
||||
statTable.AxisValueArray = axisValueArray
|
||||
statTable.AxisValueCount = len(axisValues)
|
||||
|
||||
|
||||
def _buildAxisRecords(axes, nameTable):
|
||||
axisRecords = []
|
||||
axisValues = []
|
||||
for axisRecordIndex, axisDict in enumerate(axes):
|
||||
axis = ot.AxisRecord()
|
||||
axis.AxisTag = axisDict["tag"]
|
||||
axis.AxisNameID = _addName(nameTable, axisDict["name"])
|
||||
axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex)
|
||||
axisRecords.append(axis)
|
||||
|
||||
for axisVal in axisDict.get("values", ()):
|
||||
axisValRec = ot.AxisValue()
|
||||
axisValRec.AxisIndex = axisRecordIndex
|
||||
axisValRec.Flags = axisVal.get("flags", 0)
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisVal['name'])
|
||||
|
||||
if "value" in axisVal:
|
||||
axisValRec.Value = axisVal["value"]
|
||||
if "linkedValue" in axisVal:
|
||||
axisValRec.Format = 3
|
||||
axisValRec.LinkedValue = axisVal["linkedValue"]
|
||||
else:
|
||||
axisValRec.Format = 1
|
||||
elif "nominalValue" in axisVal:
|
||||
axisValRec.Format = 2
|
||||
axisValRec.NominalValue = axisVal["nominalValue"]
|
||||
axisValRec.RangeMinValue = axisVal.get("rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY)
|
||||
axisValRec.RangeMaxValue = axisVal.get("rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY)
|
||||
else:
|
||||
raise ValueError("Can't determine format for AxisValue")
|
||||
|
||||
axisValues.append(axisValRec)
|
||||
return axisRecords, axisValues
|
||||
|
||||
|
||||
def _buildAxisValuesFormat4(locations, axes, nameTable):
|
||||
axisTagToIndex = {}
|
||||
for axisRecordIndex, axisDict in enumerate(axes):
|
||||
axisTagToIndex[axisDict["tag"]] = axisRecordIndex
|
||||
|
||||
axisValues = []
|
||||
for axisLocationDict in locations:
|
||||
axisValRec = ot.AxisValue()
|
||||
axisValRec.Format = 4
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisLocationDict['name'])
|
||||
axisValRec.Flags = axisLocationDict.get("flags", 0)
|
||||
axisValueRecords = []
|
||||
for tag, value in axisLocationDict["location"].items():
|
||||
avr = ot.AxisValueRecord()
|
||||
avr.AxisIndex = axisTagToIndex[tag]
|
||||
avr.Value = value
|
||||
axisValueRecords.append(avr)
|
||||
axisValueRecords.sort(key=lambda avr: avr.AxisIndex)
|
||||
axisValRec.AxisCount = len(axisValueRecords)
|
||||
axisValRec.AxisValueRecord = axisValueRecords
|
||||
axisValues.append(axisValRec)
|
||||
return axisValues
|
||||
|
||||
|
||||
def _addName(nameTable, value):
|
||||
if isinstance(value, int):
|
||||
# Already a nameID
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
names = dict(en=value)
|
||||
elif isinstance(value, dict):
|
||||
names = value
|
||||
else:
|
||||
raise TypeError("value must be int, str or dict")
|
||||
return nameTable.addMultilingualName(names)
|
||||
|
@ -202,30 +202,10 @@ def _add_stat(font, axes):
|
||||
if "STAT" in font:
|
||||
return
|
||||
|
||||
from ..otlLib.builder import buildStatTable
|
||||
fvarTable = font['fvar']
|
||||
|
||||
STAT = font["STAT"] = newTable('STAT')
|
||||
stat = STAT.table = ot.STAT()
|
||||
stat.Version = 0x00010001
|
||||
|
||||
axisRecords = []
|
||||
for i, a in enumerate(fvarTable.axes):
|
||||
axis = ot.AxisRecord()
|
||||
axis.AxisTag = Tag(a.axisTag)
|
||||
axis.AxisNameID = a.axisNameID
|
||||
axis.AxisOrdering = i
|
||||
axisRecords.append(axis)
|
||||
|
||||
axisRecordArray = ot.AxisRecordArray()
|
||||
axisRecordArray.Axis = axisRecords
|
||||
# XXX these should not be hard-coded but computed automatically
|
||||
stat.DesignAxisRecordSize = 8
|
||||
stat.DesignAxisCount = len(axisRecords)
|
||||
stat.DesignAxisRecord = axisRecordArray
|
||||
|
||||
# for the elided fallback name, we default to the base style name.
|
||||
# TODO make this user-configurable via designspace document
|
||||
stat.ElidedFallbackNameID = 2
|
||||
axes = [dict(tag=a.axisTag, name=a.axisNameID) for a in fvarTable.axes]
|
||||
buildStatTable(font, axes)
|
||||
|
||||
|
||||
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
||||
|
@ -204,6 +204,9 @@
|
||||
<namerecord nameID="261" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
Right Up
|
||||
</namerecord>
|
||||
<namerecord nameID="262" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
Neutral
|
||||
</namerecord>
|
||||
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
|
||||
HalloTestFont
|
||||
</namerecord>
|
||||
@ -237,6 +240,9 @@
|
||||
<namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
|
||||
Right Up
|
||||
</namerecord>
|
||||
<namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
|
||||
Neutral
|
||||
</namerecord>
|
||||
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
|
||||
HalloTestFont
|
||||
</namerecord>
|
||||
@ -363,6 +369,86 @@
|
||||
</FeatureVariations>
|
||||
</GSUB>
|
||||
|
||||
<STAT>
|
||||
<Version value="0x00010001"/>
|
||||
<DesignAxisRecordSize value="8"/>
|
||||
<!-- DesignAxisCount=4 -->
|
||||
<DesignAxisRecord>
|
||||
<Axis index="0">
|
||||
<AxisTag value="LEFT"/>
|
||||
<AxisNameID value="256"/> <!-- Left -->
|
||||
<AxisOrdering value="0"/>
|
||||
</Axis>
|
||||
<Axis index="1">
|
||||
<AxisTag value="RGHT"/>
|
||||
<AxisNameID value="257"/> <!-- Right -->
|
||||
<AxisOrdering value="1"/>
|
||||
</Axis>
|
||||
<Axis index="2">
|
||||
<AxisTag value="UPPP"/>
|
||||
<AxisNameID value="258"/> <!-- Up -->
|
||||
<AxisOrdering value="2"/>
|
||||
</Axis>
|
||||
<Axis index="3">
|
||||
<AxisTag value="DOWN"/>
|
||||
<AxisNameID value="259"/> <!-- Down -->
|
||||
<AxisOrdering value="3"/>
|
||||
</Axis>
|
||||
</DesignAxisRecord>
|
||||
<!-- AxisValueCount=8 -->
|
||||
<AxisValueArray>
|
||||
<AxisValue index="0" Format="1">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="2"/>
|
||||
<ValueNameID value="262"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="1" Format="1">
|
||||
<AxisIndex value="0"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="256"/> <!-- Left -->
|
||||
<Value value="100.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="2" Format="1">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="2"/>
|
||||
<ValueNameID value="262"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="3" Format="1">
|
||||
<AxisIndex value="1"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="257"/> <!-- Right -->
|
||||
<Value value="100.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="4" Format="1">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="2"/>
|
||||
<ValueNameID value="262"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="5" Format="1">
|
||||
<AxisIndex value="2"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="258"/> <!-- Up -->
|
||||
<Value value="100.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="6" Format="1">
|
||||
<AxisIndex value="3"/>
|
||||
<Flags value="2"/>
|
||||
<ValueNameID value="262"/> <!-- Neutral -->
|
||||
<Value value="0.0"/>
|
||||
</AxisValue>
|
||||
<AxisValue index="7" Format="1">
|
||||
<AxisIndex value="3"/>
|
||||
<Flags value="0"/>
|
||||
<ValueNameID value="259"/> <!-- Down -->
|
||||
<Value value="100.0"/>
|
||||
</AxisValue>
|
||||
</AxisValueArray>
|
||||
<ElidedFallbackNameID value="2"/> <!-- TotallyNormal -->
|
||||
</STAT>
|
||||
|
||||
<fvar>
|
||||
|
||||
<!-- Left -->
|
||||
|
@ -226,6 +226,13 @@ def test_build_var(tmpdir):
|
||||
featureTag="rclt",
|
||||
)
|
||||
|
||||
statAxes = []
|
||||
for tag, minVal, defaultVal, maxVal, name in axes:
|
||||
values = [dict(name="Neutral", value=defaultVal, flags=0x2),
|
||||
dict(name=name, value=maxVal)]
|
||||
statAxes.append(dict(tag=tag, name=name, values=values))
|
||||
fb.setupStat(statAxes)
|
||||
|
||||
fb.setupOS2()
|
||||
fb.setupPost()
|
||||
fb.setupDummyDSIG()
|
||||
|
@ -1,5 +1,9 @@
|
||||
import io
|
||||
import struct
|
||||
from fontTools.misc.fixedTools import floatToFixed
|
||||
from fontTools.misc.testTools import getXML
|
||||
from fontTools.otlLib import builder
|
||||
from fontTools import ttLib
|
||||
from fontTools.ttLib.tables import otTables
|
||||
import pytest
|
||||
|
||||
@ -1106,6 +1110,291 @@ class ClassDefBuilderTest(object):
|
||||
assert not b.canAdd({"f"})
|
||||
|
||||
|
||||
buildStatTable_test_data = [
|
||||
([
|
||||
dict(
|
||||
tag="wght",
|
||||
name="Weight",
|
||||
values=[
|
||||
dict(value=100, name='Thin'),
|
||||
dict(value=400, name='Regular', flags=0x2),
|
||||
dict(value=900, name='Black')])], None, "Regular", [
|
||||
' <STAT>',
|
||||
' <Version value="0x00010001"/>',
|
||||
' <DesignAxisRecordSize value="8"/>',
|
||||
' <!-- DesignAxisCount=1 -->',
|
||||
' <DesignAxisRecord>',
|
||||
' <Axis index="0">',
|
||||
' <AxisTag value="wght"/>',
|
||||
' <AxisNameID value="257"/> <!-- Weight -->',
|
||||
' <AxisOrdering value="0"/>',
|
||||
' </Axis>',
|
||||
' </DesignAxisRecord>',
|
||||
' <!-- AxisValueCount=3 -->',
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="258"/> <!-- Thin -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="256"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="2" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="259"/> <!-- Black -->',
|
||||
' <Value value="900.0"/>',
|
||||
' </AxisValue>',
|
||||
' </AxisValueArray>',
|
||||
' <ElidedFallbackNameID value="256"/> <!-- Regular -->',
|
||||
' </STAT>']),
|
||||
([
|
||||
dict(
|
||||
tag="wght",
|
||||
name=dict(en="Weight", nl="Gewicht"),
|
||||
values=[
|
||||
dict(value=100, name=dict(en='Thin', nl='Dun')),
|
||||
dict(value=400, name='Regular', flags=0x2),
|
||||
dict(value=900, name='Black'),
|
||||
]),
|
||||
dict(
|
||||
tag="wdth",
|
||||
name="Width",
|
||||
values=[
|
||||
dict(value=50, name='Condensed'),
|
||||
dict(value=100, name='Regular', flags=0x2),
|
||||
dict(value=200, name='Extended')])], None, 2, [
|
||||
' <STAT>',
|
||||
' <Version value="0x00010001"/>',
|
||||
' <DesignAxisRecordSize value="8"/>',
|
||||
' <!-- DesignAxisCount=2 -->',
|
||||
' <DesignAxisRecord>',
|
||||
' <Axis index="0">',
|
||||
' <AxisTag value="wght"/>',
|
||||
' <AxisNameID value="256"/> <!-- Weight -->',
|
||||
' <AxisOrdering value="0"/>',
|
||||
' </Axis>',
|
||||
' <Axis index="1">',
|
||||
' <AxisTag value="wdth"/>',
|
||||
' <AxisNameID value="260"/> <!-- Width -->',
|
||||
' <AxisOrdering value="1"/>',
|
||||
' </Axis>',
|
||||
' </DesignAxisRecord>',
|
||||
' <!-- AxisValueCount=6 -->',
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="257"/> <!-- Thin -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="2" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="259"/> <!-- Black -->',
|
||||
' <Value value="900.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="3" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="261"/> <!-- Condensed -->',
|
||||
' <Value value="50.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="4" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="5" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="262"/> <!-- Extended -->',
|
||||
' <Value value="200.0"/>',
|
||||
' </AxisValue>',
|
||||
' </AxisValueArray>',
|
||||
' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->',
|
||||
' </STAT>']),
|
||||
([
|
||||
dict(
|
||||
tag="wght",
|
||||
name="Weight",
|
||||
values=[
|
||||
dict(value=400, name='Regular', flags=0x2),
|
||||
dict(value=600, linkedValue=650, name='Bold')])], None, 18, [
|
||||
' <STAT>',
|
||||
' <Version value="0x00010001"/>',
|
||||
' <DesignAxisRecordSize value="8"/>',
|
||||
' <!-- DesignAxisCount=1 -->',
|
||||
' <DesignAxisRecord>',
|
||||
' <Axis index="0">',
|
||||
' <AxisTag value="wght"/>',
|
||||
' <AxisNameID value="256"/> <!-- Weight -->',
|
||||
' <AxisOrdering value="0"/>',
|
||||
' </Axis>',
|
||||
' </DesignAxisRecord>',
|
||||
' <!-- AxisValueCount=2 -->',
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="1">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="257"/> <!-- Regular -->',
|
||||
' <Value value="400.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="3">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="258"/> <!-- Bold -->',
|
||||
' <Value value="600.0"/>',
|
||||
' <LinkedValue value="650.0"/>',
|
||||
' </AxisValue>',
|
||||
' </AxisValueArray>',
|
||||
' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->',
|
||||
' </STAT>']),
|
||||
([
|
||||
dict(
|
||||
tag="opsz",
|
||||
name="Optical Size",
|
||||
values=[
|
||||
dict(nominalValue=6, rangeMaxValue=10, name='Small'),
|
||||
dict(rangeMinValue=10, nominalValue=14, rangeMaxValue=24, name='Text', flags=0x2),
|
||||
dict(rangeMinValue=24, nominalValue=600, name='Display')])], None, 2, [
|
||||
' <STAT>',
|
||||
' <Version value="0x00010001"/>',
|
||||
' <DesignAxisRecordSize value="8"/>',
|
||||
' <!-- DesignAxisCount=1 -->',
|
||||
' <DesignAxisRecord>',
|
||||
' <Axis index="0">',
|
||||
' <AxisTag value="opsz"/>',
|
||||
' <AxisNameID value="256"/> <!-- Optical Size -->',
|
||||
' <AxisOrdering value="0"/>',
|
||||
' </Axis>',
|
||||
' </DesignAxisRecord>',
|
||||
' <!-- AxisValueCount=3 -->',
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="2">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="257"/> <!-- Small -->',
|
||||
' <NominalValue value="6.0"/>',
|
||||
' <RangeMinValue value="-32768.0"/>',
|
||||
' <RangeMaxValue value="10.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="2">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="258"/> <!-- Text -->',
|
||||
' <NominalValue value="14.0"/>',
|
||||
' <RangeMinValue value="10.0"/>',
|
||||
' <RangeMaxValue value="24.0"/>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="2" Format="2">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="259"/> <!-- Display -->',
|
||||
' <NominalValue value="600.0"/>',
|
||||
' <RangeMinValue value="24.0"/>',
|
||||
' <RangeMaxValue value="32767.99998"/>',
|
||||
' </AxisValue>',
|
||||
' </AxisValueArray>',
|
||||
' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->',
|
||||
' </STAT>']),
|
||||
([
|
||||
dict(
|
||||
tag="wght",
|
||||
name="Weight",
|
||||
ordering=1,
|
||||
values=[]),
|
||||
dict(
|
||||
tag="ABCD",
|
||||
name="ABCDTest",
|
||||
ordering=0,
|
||||
values=[
|
||||
dict(value=100, name="Regular", flags=0x2)])],
|
||||
[dict(location=dict(wght=300, ABCD=100), name='Regular ABCD')], 18, [
|
||||
' <STAT>',
|
||||
' <Version value="0x00010002"/>',
|
||||
' <DesignAxisRecordSize value="8"/>',
|
||||
' <!-- DesignAxisCount=2 -->',
|
||||
' <DesignAxisRecord>',
|
||||
' <Axis index="0">',
|
||||
' <AxisTag value="wght"/>',
|
||||
' <AxisNameID value="256"/> <!-- Weight -->',
|
||||
' <AxisOrdering value="1"/>',
|
||||
' </Axis>',
|
||||
' <Axis index="1">',
|
||||
' <AxisTag value="ABCD"/>',
|
||||
' <AxisNameID value="257"/> <!-- ABCDTest -->',
|
||||
' <AxisOrdering value="0"/>',
|
||||
' </Axis>',
|
||||
' </DesignAxisRecord>',
|
||||
' <!-- AxisValueCount=2 -->',
|
||||
' <AxisValueArray>',
|
||||
' <AxisValue index="0" Format="4">',
|
||||
' <!-- AxisCount=2 -->',
|
||||
' <Flags value="0"/>',
|
||||
' <ValueNameID value="259"/> <!-- Regular ABCD -->',
|
||||
' <AxisValueRecord index="0">',
|
||||
' <AxisIndex value="0"/>',
|
||||
' <Value value="300.0"/>',
|
||||
' </AxisValueRecord>',
|
||||
' <AxisValueRecord index="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValueRecord>',
|
||||
' </AxisValue>',
|
||||
' <AxisValue index="1" Format="1">',
|
||||
' <AxisIndex value="1"/>',
|
||||
' <Flags value="2"/>',
|
||||
' <ValueNameID value="258"/> <!-- Regular -->',
|
||||
' <Value value="100.0"/>',
|
||||
' </AxisValue>',
|
||||
' </AxisValueArray>',
|
||||
' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->',
|
||||
' </STAT>']),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("axes, axisValues, elidedFallbackName, expected_ttx", buildStatTable_test_data)
|
||||
def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx):
|
||||
font = ttLib.TTFont()
|
||||
font["name"] = ttLib.newTable("name")
|
||||
font["name"].names = []
|
||||
builder.buildStatTable(font, axes, axisValues, elidedFallbackName)
|
||||
f = io.StringIO()
|
||||
font.saveXML(f, tables=["STAT"])
|
||||
ttx = f.getvalue().splitlines()
|
||||
ttx = ttx[3:-2] # strip XML header and <ttFont> element
|
||||
assert expected_ttx == ttx
|
||||
# Compile and round-trip
|
||||
f = io.BytesIO()
|
||||
font.save(f)
|
||||
font = ttLib.TTFont(f)
|
||||
f = io.StringIO()
|
||||
font.saveXML(f, tables=["STAT"])
|
||||
ttx = f.getvalue().splitlines()
|
||||
ttx = ttx[3:-2] # strip XML header and <ttFont> element
|
||||
assert expected_ttx == ttx
|
||||
|
||||
|
||||
def test_stat_infinities():
|
||||
negInf = floatToFixed(builder.AXIS_VALUE_NEGATIVE_INFINITY, 16)
|
||||
assert struct.pack(">l", negInf) == b"\x80\x00\x00\x00"
|
||||
posInf = floatToFixed(builder.AXIS_VALUE_POSITIVE_INFINITY, 16)
|
||||
assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user