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