Spaces:
Paused
Paused
| """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 | |
| """ | |
| # First, get the axis labels with explicit ordering | |
| # then append the others in the order they appear. | |
| 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 in the designspace is keyed by axis name | |
| # Location in buildStatTable by axis tag | |
| 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") | |