| | """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") |
| |
|