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