|
"""Extra methods for DesignSpaceDocument to generate its STAT table data.""" |
|
|
|
from __future__ import annotations |
|
|
|
from typing import Dict, List, Union |
|
|
|
import fontTools.otlLib.builder |
|
from fontTools.designspaceLib import ( |
|
AxisLabelDescriptor, |
|
DesignSpaceDocument, |
|
DesignSpaceDocumentError, |
|
LocationLabelDescriptor, |
|
) |
|
from fontTools.designspaceLib.types import Region, getVFUserRegion, locationInRegion |
|
from fontTools.ttLib import TTFont |
|
|
|
|
|
def buildVFStatTable(ttFont: TTFont, doc: DesignSpaceDocument, vfName: str) -> None: |
|
"""Build the STAT table for the variable font identified by its name in |
|
the given document. |
|
|
|
Knowing which variable we're building STAT data for is needed to subset |
|
the STAT locations to only include what the variable font actually ships. |
|
|
|
.. versionadded:: 5.0 |
|
|
|
.. seealso:: |
|
- :func:`getStatAxes()` |
|
- :func:`getStatLocations()` |
|
- :func:`fontTools.otlLib.builder.buildStatTable()` |
|
""" |
|
for vf in doc.getVariableFonts(): |
|
if vf.name == vfName: |
|
break |
|
else: |
|
raise DesignSpaceDocumentError( |
|
f"Cannot find the variable font by name {vfName}" |
|
) |
|
|
|
region = getVFUserRegion(doc, vf) |
|
|
|
return fontTools.otlLib.builder.buildStatTable( |
|
ttFont, |
|
getStatAxes(doc, region), |
|
getStatLocations(doc, region), |
|
doc.elidedFallbackName if doc.elidedFallbackName is not None else 2, |
|
) |
|
|
|
|
|
def getStatAxes(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]: |
|
"""Return a list of axis dicts suitable for use as the ``axes`` |
|
argument to :func:`fontTools.otlLib.builder.buildStatTable()`. |
|
|
|
.. versionadded:: 5.0 |
|
""" |
|
|
|
|
|
maxOrdering = max( |
|
(axis.axisOrdering for axis in doc.axes if axis.axisOrdering is not None), |
|
default=-1, |
|
) |
|
axisOrderings = [] |
|
for axis in doc.axes: |
|
if axis.axisOrdering is not None: |
|
axisOrderings.append(axis.axisOrdering) |
|
else: |
|
maxOrdering += 1 |
|
axisOrderings.append(maxOrdering) |
|
return [ |
|
dict( |
|
tag=axis.tag, |
|
name={"en": axis.name, **axis.labelNames}, |
|
ordering=ordering, |
|
values=[ |
|
_axisLabelToStatLocation(label) |
|
for label in axis.axisLabels |
|
if locationInRegion({axis.name: label.userValue}, userRegion) |
|
], |
|
) |
|
for axis, ordering in zip(doc.axes, axisOrderings) |
|
] |
|
|
|
|
|
def getStatLocations(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]: |
|
"""Return a list of location dicts suitable for use as the ``locations`` |
|
argument to :func:`fontTools.otlLib.builder.buildStatTable()`. |
|
|
|
.. versionadded:: 5.0 |
|
""" |
|
axesByName = {axis.name: axis for axis in doc.axes} |
|
return [ |
|
dict( |
|
name={"en": label.name, **label.labelNames}, |
|
|
|
|
|
location={ |
|
axesByName[name].tag: value |
|
for name, value in label.getFullUserLocation(doc).items() |
|
}, |
|
flags=_labelToFlags(label), |
|
) |
|
for label in doc.locationLabels |
|
if locationInRegion(label.getFullUserLocation(doc), userRegion) |
|
] |
|
|
|
|
|
def _labelToFlags(label: Union[AxisLabelDescriptor, LocationLabelDescriptor]) -> int: |
|
flags = 0 |
|
if label.olderSibling: |
|
flags |= 1 |
|
if label.elidable: |
|
flags |= 2 |
|
return flags |
|
|
|
|
|
def _axisLabelToStatLocation( |
|
label: AxisLabelDescriptor, |
|
) -> Dict: |
|
label_format = label.getFormat() |
|
name = {"en": label.name, **label.labelNames} |
|
flags = _labelToFlags(label) |
|
if label_format == 1: |
|
return dict(name=name, value=label.userValue, flags=flags) |
|
if label_format == 3: |
|
return dict( |
|
name=name, |
|
value=label.userValue, |
|
linkedValue=label.linkedUserValue, |
|
flags=flags, |
|
) |
|
if label_format == 2: |
|
res = dict( |
|
name=name, |
|
nominalValue=label.userValue, |
|
flags=flags, |
|
) |
|
if label.userMinimum is not None: |
|
res["rangeMinValue"] = label.userMinimum |
|
if label.userMaximum is not None: |
|
res["rangeMaxValue"] = label.userMaximum |
|
return res |
|
raise NotImplementedError("Unknown STAT label format") |
|
|