xref: /aosp_15_r20/external/fonttools/Lib/fontTools/feaLib/variableScalar.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import VariationModel, normalizeValue, piecewiseLinearMap
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughesdef Location(loc):
5*e1fe3e4aSElliott Hughes    return tuple(sorted(loc.items()))
6*e1fe3e4aSElliott Hughes
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughesclass VariableScalar:
9*e1fe3e4aSElliott Hughes    """A scalar with different values at different points in the designspace."""
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughes    def __init__(self, location_value={}):
12*e1fe3e4aSElliott Hughes        self.values = {}
13*e1fe3e4aSElliott Hughes        self.axes = {}
14*e1fe3e4aSElliott Hughes        for location, value in location_value.items():
15*e1fe3e4aSElliott Hughes            self.add_value(location, value)
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughes    def __repr__(self):
18*e1fe3e4aSElliott Hughes        items = []
19*e1fe3e4aSElliott Hughes        for location, value in self.values.items():
20*e1fe3e4aSElliott Hughes            loc = ",".join(["%s=%i" % (ax, loc) for ax, loc in location])
21*e1fe3e4aSElliott Hughes            items.append("%s:%i" % (loc, value))
22*e1fe3e4aSElliott Hughes        return "(" + (" ".join(items)) + ")"
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughes    @property
25*e1fe3e4aSElliott Hughes    def does_vary(self):
26*e1fe3e4aSElliott Hughes        values = list(self.values.values())
27*e1fe3e4aSElliott Hughes        return any(v != values[0] for v in values[1:])
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hughes    @property
30*e1fe3e4aSElliott Hughes    def axes_dict(self):
31*e1fe3e4aSElliott Hughes        if not self.axes:
32*e1fe3e4aSElliott Hughes            raise ValueError(
33*e1fe3e4aSElliott Hughes                ".axes must be defined on variable scalar before interpolating"
34*e1fe3e4aSElliott Hughes            )
35*e1fe3e4aSElliott Hughes        return {ax.axisTag: ax for ax in self.axes}
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hughes    def _normalized_location(self, location):
38*e1fe3e4aSElliott Hughes        location = self.fix_location(location)
39*e1fe3e4aSElliott Hughes        normalized_location = {}
40*e1fe3e4aSElliott Hughes        for axtag in location.keys():
41*e1fe3e4aSElliott Hughes            if axtag not in self.axes_dict:
42*e1fe3e4aSElliott Hughes                raise ValueError("Unknown axis %s in %s" % (axtag, location))
43*e1fe3e4aSElliott Hughes            axis = self.axes_dict[axtag]
44*e1fe3e4aSElliott Hughes            normalized_location[axtag] = normalizeValue(
45*e1fe3e4aSElliott Hughes                location[axtag], (axis.minValue, axis.defaultValue, axis.maxValue)
46*e1fe3e4aSElliott Hughes            )
47*e1fe3e4aSElliott Hughes
48*e1fe3e4aSElliott Hughes        return Location(normalized_location)
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes    def fix_location(self, location):
51*e1fe3e4aSElliott Hughes        location = dict(location)
52*e1fe3e4aSElliott Hughes        for tag, axis in self.axes_dict.items():
53*e1fe3e4aSElliott Hughes            if tag not in location:
54*e1fe3e4aSElliott Hughes                location[tag] = axis.defaultValue
55*e1fe3e4aSElliott Hughes        return location
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes    def add_value(self, location, value):
58*e1fe3e4aSElliott Hughes        if self.axes:
59*e1fe3e4aSElliott Hughes            location = self.fix_location(location)
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughes        self.values[Location(location)] = value
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughes    def fix_all_locations(self):
64*e1fe3e4aSElliott Hughes        self.values = {
65*e1fe3e4aSElliott Hughes            Location(self.fix_location(l)): v for l, v in self.values.items()
66*e1fe3e4aSElliott Hughes        }
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    @property
69*e1fe3e4aSElliott Hughes    def default(self):
70*e1fe3e4aSElliott Hughes        self.fix_all_locations()
71*e1fe3e4aSElliott Hughes        key = Location({ax.axisTag: ax.defaultValue for ax in self.axes})
72*e1fe3e4aSElliott Hughes        if key not in self.values:
73*e1fe3e4aSElliott Hughes            raise ValueError("Default value could not be found")
74*e1fe3e4aSElliott Hughes            # I *guess* we could interpolate one, but I don't know how.
75*e1fe3e4aSElliott Hughes        return self.values[key]
76*e1fe3e4aSElliott Hughes
77*e1fe3e4aSElliott Hughes    def value_at_location(self, location, model_cache=None, avar=None):
78*e1fe3e4aSElliott Hughes        loc = location
79*e1fe3e4aSElliott Hughes        if loc in self.values.keys():
80*e1fe3e4aSElliott Hughes            return self.values[loc]
81*e1fe3e4aSElliott Hughes        values = list(self.values.values())
82*e1fe3e4aSElliott Hughes        return self.model(model_cache, avar).interpolateFromMasters(loc, values)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes    def model(self, model_cache=None, avar=None):
85*e1fe3e4aSElliott Hughes        if model_cache is not None:
86*e1fe3e4aSElliott Hughes            key = tuple(self.values.keys())
87*e1fe3e4aSElliott Hughes            if key in model_cache:
88*e1fe3e4aSElliott Hughes                return model_cache[key]
89*e1fe3e4aSElliott Hughes        locations = [dict(self._normalized_location(k)) for k in self.values.keys()]
90*e1fe3e4aSElliott Hughes        if avar is not None:
91*e1fe3e4aSElliott Hughes            mapping = avar.segments
92*e1fe3e4aSElliott Hughes            locations = [
93*e1fe3e4aSElliott Hughes                {
94*e1fe3e4aSElliott Hughes                    k: piecewiseLinearMap(v, mapping[k]) if k in mapping else v
95*e1fe3e4aSElliott Hughes                    for k, v in location.items()
96*e1fe3e4aSElliott Hughes                }
97*e1fe3e4aSElliott Hughes                for location in locations
98*e1fe3e4aSElliott Hughes            ]
99*e1fe3e4aSElliott Hughes        m = VariationModel(locations)
100*e1fe3e4aSElliott Hughes        if model_cache is not None:
101*e1fe3e4aSElliott Hughes            model_cache[key] = m
102*e1fe3e4aSElliott Hughes        return m
103*e1fe3e4aSElliott Hughes
104*e1fe3e4aSElliott Hughes    def get_deltas_and_supports(self, model_cache=None, avar=None):
105*e1fe3e4aSElliott Hughes        values = list(self.values.values())
106*e1fe3e4aSElliott Hughes        return self.model(model_cache, avar).getDeltasAndSupports(values)
107*e1fe3e4aSElliott Hughes
108*e1fe3e4aSElliott Hughes    def add_to_variation_store(self, store_builder, model_cache=None, avar=None):
109*e1fe3e4aSElliott Hughes        deltas, supports = self.get_deltas_and_supports(model_cache, avar)
110*e1fe3e4aSElliott Hughes        store_builder.setSupports(supports)
111*e1fe3e4aSElliott Hughes        index = store_builder.storeDeltas(deltas)
112*e1fe3e4aSElliott Hughes        return int(self.default), index
113