xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/stat.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""Extra methods for DesignSpaceDocument to generate its STAT table data."""
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesfrom __future__ import annotations
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughesfrom typing import Dict, List, Union
6*e1fe3e4aSElliott Hughes
7*e1fe3e4aSElliott Hughesimport fontTools.otlLib.builder
8*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib import (
9*e1fe3e4aSElliott Hughes    AxisLabelDescriptor,
10*e1fe3e4aSElliott Hughes    DesignSpaceDocument,
11*e1fe3e4aSElliott Hughes    DesignSpaceDocumentError,
12*e1fe3e4aSElliott Hughes    LocationLabelDescriptor,
13*e1fe3e4aSElliott Hughes)
14*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib.types import Region, getVFUserRegion, locationInRegion
15*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hughesdef buildVFStatTable(ttFont: TTFont, doc: DesignSpaceDocument, vfName: str) -> None:
19*e1fe3e4aSElliott Hughes    """Build the STAT table for the variable font identified by its name in
20*e1fe3e4aSElliott Hughes    the given document.
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughes    Knowing which variable we're building STAT data for is needed to subset
23*e1fe3e4aSElliott Hughes    the STAT locations to only include what the variable font actually ships.
24*e1fe3e4aSElliott Hughes
25*e1fe3e4aSElliott Hughes    .. versionadded:: 5.0
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes    .. seealso::
28*e1fe3e4aSElliott Hughes        - :func:`getStatAxes()`
29*e1fe3e4aSElliott Hughes        - :func:`getStatLocations()`
30*e1fe3e4aSElliott Hughes        - :func:`fontTools.otlLib.builder.buildStatTable()`
31*e1fe3e4aSElliott Hughes    """
32*e1fe3e4aSElliott Hughes    for vf in doc.getVariableFonts():
33*e1fe3e4aSElliott Hughes        if vf.name == vfName:
34*e1fe3e4aSElliott Hughes            break
35*e1fe3e4aSElliott Hughes    else:
36*e1fe3e4aSElliott Hughes        raise DesignSpaceDocumentError(
37*e1fe3e4aSElliott Hughes            f"Cannot find the variable font by name {vfName}"
38*e1fe3e4aSElliott Hughes        )
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughes    region = getVFUserRegion(doc, vf)
41*e1fe3e4aSElliott Hughes
42*e1fe3e4aSElliott Hughes    return fontTools.otlLib.builder.buildStatTable(
43*e1fe3e4aSElliott Hughes        ttFont,
44*e1fe3e4aSElliott Hughes        getStatAxes(doc, region),
45*e1fe3e4aSElliott Hughes        getStatLocations(doc, region),
46*e1fe3e4aSElliott Hughes        doc.elidedFallbackName if doc.elidedFallbackName is not None else 2,
47*e1fe3e4aSElliott Hughes    )
48*e1fe3e4aSElliott Hughes
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughesdef getStatAxes(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]:
51*e1fe3e4aSElliott Hughes    """Return a list of axis dicts suitable for use as the ``axes``
52*e1fe3e4aSElliott Hughes    argument to :func:`fontTools.otlLib.builder.buildStatTable()`.
53*e1fe3e4aSElliott Hughes
54*e1fe3e4aSElliott Hughes    .. versionadded:: 5.0
55*e1fe3e4aSElliott Hughes    """
56*e1fe3e4aSElliott Hughes    # First, get the axis labels with explicit ordering
57*e1fe3e4aSElliott Hughes    # then append the others in the order they appear.
58*e1fe3e4aSElliott Hughes    maxOrdering = max(
59*e1fe3e4aSElliott Hughes        (axis.axisOrdering for axis in doc.axes if axis.axisOrdering is not None),
60*e1fe3e4aSElliott Hughes        default=-1,
61*e1fe3e4aSElliott Hughes    )
62*e1fe3e4aSElliott Hughes    axisOrderings = []
63*e1fe3e4aSElliott Hughes    for axis in doc.axes:
64*e1fe3e4aSElliott Hughes        if axis.axisOrdering is not None:
65*e1fe3e4aSElliott Hughes            axisOrderings.append(axis.axisOrdering)
66*e1fe3e4aSElliott Hughes        else:
67*e1fe3e4aSElliott Hughes            maxOrdering += 1
68*e1fe3e4aSElliott Hughes            axisOrderings.append(maxOrdering)
69*e1fe3e4aSElliott Hughes    return [
70*e1fe3e4aSElliott Hughes        dict(
71*e1fe3e4aSElliott Hughes            tag=axis.tag,
72*e1fe3e4aSElliott Hughes            name={"en": axis.name, **axis.labelNames},
73*e1fe3e4aSElliott Hughes            ordering=ordering,
74*e1fe3e4aSElliott Hughes            values=[
75*e1fe3e4aSElliott Hughes                _axisLabelToStatLocation(label)
76*e1fe3e4aSElliott Hughes                for label in axis.axisLabels
77*e1fe3e4aSElliott Hughes                if locationInRegion({axis.name: label.userValue}, userRegion)
78*e1fe3e4aSElliott Hughes            ],
79*e1fe3e4aSElliott Hughes        )
80*e1fe3e4aSElliott Hughes        for axis, ordering in zip(doc.axes, axisOrderings)
81*e1fe3e4aSElliott Hughes    ]
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughesdef getStatLocations(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]:
85*e1fe3e4aSElliott Hughes    """Return a list of location dicts suitable for use as the ``locations``
86*e1fe3e4aSElliott Hughes    argument to :func:`fontTools.otlLib.builder.buildStatTable()`.
87*e1fe3e4aSElliott Hughes
88*e1fe3e4aSElliott Hughes    .. versionadded:: 5.0
89*e1fe3e4aSElliott Hughes    """
90*e1fe3e4aSElliott Hughes    axesByName = {axis.name: axis for axis in doc.axes}
91*e1fe3e4aSElliott Hughes    return [
92*e1fe3e4aSElliott Hughes        dict(
93*e1fe3e4aSElliott Hughes            name={"en": label.name, **label.labelNames},
94*e1fe3e4aSElliott Hughes            # Location in the designspace is keyed by axis name
95*e1fe3e4aSElliott Hughes            # Location in buildStatTable by axis tag
96*e1fe3e4aSElliott Hughes            location={
97*e1fe3e4aSElliott Hughes                axesByName[name].tag: value
98*e1fe3e4aSElliott Hughes                for name, value in label.getFullUserLocation(doc).items()
99*e1fe3e4aSElliott Hughes            },
100*e1fe3e4aSElliott Hughes            flags=_labelToFlags(label),
101*e1fe3e4aSElliott Hughes        )
102*e1fe3e4aSElliott Hughes        for label in doc.locationLabels
103*e1fe3e4aSElliott Hughes        if locationInRegion(label.getFullUserLocation(doc), userRegion)
104*e1fe3e4aSElliott Hughes    ]
105*e1fe3e4aSElliott Hughes
106*e1fe3e4aSElliott Hughes
107*e1fe3e4aSElliott Hughesdef _labelToFlags(label: Union[AxisLabelDescriptor, LocationLabelDescriptor]) -> int:
108*e1fe3e4aSElliott Hughes    flags = 0
109*e1fe3e4aSElliott Hughes    if label.olderSibling:
110*e1fe3e4aSElliott Hughes        flags |= 1
111*e1fe3e4aSElliott Hughes    if label.elidable:
112*e1fe3e4aSElliott Hughes        flags |= 2
113*e1fe3e4aSElliott Hughes    return flags
114*e1fe3e4aSElliott Hughes
115*e1fe3e4aSElliott Hughes
116*e1fe3e4aSElliott Hughesdef _axisLabelToStatLocation(
117*e1fe3e4aSElliott Hughes    label: AxisLabelDescriptor,
118*e1fe3e4aSElliott Hughes) -> Dict:
119*e1fe3e4aSElliott Hughes    label_format = label.getFormat()
120*e1fe3e4aSElliott Hughes    name = {"en": label.name, **label.labelNames}
121*e1fe3e4aSElliott Hughes    flags = _labelToFlags(label)
122*e1fe3e4aSElliott Hughes    if label_format == 1:
123*e1fe3e4aSElliott Hughes        return dict(name=name, value=label.userValue, flags=flags)
124*e1fe3e4aSElliott Hughes    if label_format == 3:
125*e1fe3e4aSElliott Hughes        return dict(
126*e1fe3e4aSElliott Hughes            name=name,
127*e1fe3e4aSElliott Hughes            value=label.userValue,
128*e1fe3e4aSElliott Hughes            linkedValue=label.linkedUserValue,
129*e1fe3e4aSElliott Hughes            flags=flags,
130*e1fe3e4aSElliott Hughes        )
131*e1fe3e4aSElliott Hughes    if label_format == 2:
132*e1fe3e4aSElliott Hughes        res = dict(
133*e1fe3e4aSElliott Hughes            name=name,
134*e1fe3e4aSElliott Hughes            nominalValue=label.userValue,
135*e1fe3e4aSElliott Hughes            flags=flags,
136*e1fe3e4aSElliott Hughes        )
137*e1fe3e4aSElliott Hughes        if label.userMinimum is not None:
138*e1fe3e4aSElliott Hughes            res["rangeMinValue"] = label.userMinimum
139*e1fe3e4aSElliott Hughes        if label.userMaximum is not None:
140*e1fe3e4aSElliott Hughes            res["rangeMaxValue"] = label.userMaximum
141*e1fe3e4aSElliott Hughes        return res
142*e1fe3e4aSElliott Hughes    raise NotImplementedError("Unknown STAT label format")
143