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