1use core::ops::{Mul, Sub};
2use otspec::types::{Tag, Tuple, F2DOT14};
3use permutation::Permutation;
4use std::array::IntoIter;
5use std::cmp::Ordering;
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug)]
12pub struct NormalizedLocation(pub Tuple);
13
14type Support = HashMap<Tag, (f32, f32, f32)>;
15pub type Location = HashMap<Tag, f32>;
17type AxisPoints = HashMap<Tag, HashSet<F2DOT14>>;
18
19#[derive(Debug)]
22pub struct VariationModel {
23 pub locations: Vec<Location>,
25 sort_order: Permutation,
26 pub supports: Vec<Support>,
28 pub axis_order: Vec<Tag>,
30 pub original_locations: Vec<Location>,
32 delta_weights: Vec<HashMap<usize, f32>>,
33}
34
35fn support_scalar(loc: &Location, support: &Support) -> f32 {
36 let mut scalar = 1.0;
37 for (&axis, &(lower, peak, upper)) in support.iter() {
38 if peak == 0.0 {
39 continue;
40 }
41 if lower > peak || peak > upper {
42 continue;
43 }
44 if lower < 0.0 && upper > 0.0 {
45 continue;
46 }
47 let v: f32 = *loc.get(&axis).unwrap_or(&0.0);
48 if (v - peak).abs() < f32::EPSILON {
49 continue;
50 }
51 if v <= lower || upper <= v {
52 scalar = 0.0;
53 break;
54 }
55 if v < peak {
56 scalar *= (v - lower) / (peak - lower)
57 } else {
58 scalar *= (v - upper) / (peak - upper)
59 }
60 }
61 scalar
62}
63
64fn locations_to_regions(locations: &[Location]) -> Vec<Support> {
65 let mut axis_minimum: HashMap<Tag, f32> = HashMap::new();
66 let mut axis_maximum: HashMap<Tag, f32> = HashMap::new();
67 for (tag, value) in locations.iter().flatten() {
68 axis_maximum
69 .entry(*tag)
70 .and_modify(|v| *v = v.max(*value))
71 .or_insert(*value);
72 axis_minimum
73 .entry(*tag)
74 .and_modify(|v| *v = v.min(*value))
75 .or_insert(*value);
76 }
77 locations
78 .iter()
79 .map(|loc| {
80 loc.iter()
81 .map(|(axis, loc_v)| {
82 (
83 *axis,
84 if *loc_v > 0.0 {
85 (0.0, *loc_v, *axis_maximum.get(axis).unwrap())
86 } else {
87 (*axis_minimum.get(axis).unwrap(), *loc_v, 0.0)
88 },
89 )
90 })
91 .collect()
92 })
93 .collect()
94}
95
96impl VariationModel {
97 pub fn new(locations: Vec<Location>, axis_order: Vec<Tag>) -> Self {
100 let original_locations = locations.clone();
101 let locations: Vec<Location> = locations
102 .iter()
103 .map(|l| {
104 let mut l2 = l.clone();
105 l2.retain(|_, v| *v != 0.0);
106 l2
107 })
108 .collect();
109 let indices: Vec<usize> = (0..locations.len()).collect();
110 let mut axis_points = AxisPoints::new();
111 for loc in locations.iter().filter(|l| l.len() == 1) {
112 if let Some((axis, value)) = loc.iter().next() {
113 let entry = axis_points
114 .entry(*axis)
115 .or_insert_with(|| IntoIter::new([F2DOT14::from(0.0)]).collect());
116 entry.insert(F2DOT14::from(*value));
117 }
118 }
119 let on_point_count = |loc: &Location| {
120 loc.iter()
121 .filter(|(&axis, &value)| {
122 axis_points.contains_key(&axis)
123 && axis_points
124 .get(&axis)
125 .unwrap()
126 .contains(&F2DOT14::from(value))
127 })
128 .count()
129 };
130 let sort_order = permutation::sort_by(&indices[..], |a_ix, b_ix| {
131 let a = &locations[*a_ix];
132 let b = &locations[*b_ix];
133 if a.keys().len() != b.keys().len() {
134 return a.keys().len().cmp(&b.keys().len());
135 }
136
137 let a_on_point = on_point_count(a);
138 let b_on_point = on_point_count(b);
139 if a_on_point != b_on_point {
140 return b_on_point.cmp(&a_on_point);
141 }
142
143 let mut a_ordered_axes: Vec<Tag> = a.keys().copied().collect();
144 let mut b_ordered_axes: Vec<Tag> = b.keys().copied().collect();
145 a_ordered_axes.sort_by(|ka, kb| {
146 if axis_order.contains(ka) && !axis_order.contains(kb) {
147 return Ordering::Less;
148 }
149 if axis_order.contains(kb) && !axis_order.contains(ka) {
150 return Ordering::Greater;
151 }
152 ka.cmp(kb)
153 });
154 b_ordered_axes.sort_by(|ka, kb| {
155 if axis_order.contains(ka) && !axis_order.contains(kb) {
156 return Ordering::Less;
157 }
158 if axis_order.contains(kb) && !axis_order.contains(ka) {
159 return Ordering::Greater;
160 }
161 ka.cmp(kb)
162 });
163 for (left, right) in a_ordered_axes.iter().zip(b_ordered_axes.iter()) {
164 let l_index = axis_order.iter().position(|ax| ax == left);
165 let r_index = axis_order.iter().position(|ax| ax == right);
166
167 if l_index.is_some() && r_index.is_none() {
168 return Ordering::Less;
169 }
170 if r_index.is_some() && l_index.is_none() {
171 return Ordering::Greater;
172 }
173 if l_index != r_index {
174 return l_index.cmp(&r_index);
175 }
176 }
177
178 if let Some(axes_order) = a_ordered_axes.iter().partial_cmp(b_ordered_axes.iter()) {
179 if axes_order != Ordering::Equal {
180 return axes_order;
181 }
182 }
183
184 for (left, _) in a_ordered_axes.iter().zip(b_ordered_axes.iter()) {
185 let a_sign = a.get(left).unwrap().signum();
186 let b_sign = b.get(left).unwrap().signum();
187 if (a_sign - b_sign).abs() > f32::EPSILON {
188 return a_sign.partial_cmp(&b_sign).unwrap();
189 }
190 }
191 for (left, _) in a_ordered_axes.iter().zip(b_ordered_axes.iter()) {
192 let a_abs = a.get(left).unwrap().abs();
193 let b_abs = b.get(left).unwrap().abs();
194 if (a_abs - b_abs).abs() > f32::EPSILON {
195 return a_abs.partial_cmp(&b_abs).unwrap();
196 }
197 }
198 Ordering::Equal
199 });
200
201 let mut vm = VariationModel {
202 locations: sort_order.apply_slice(&locations[..]),
203 sort_order,
204 axis_order,
205 original_locations,
206 supports: vec![],
207 delta_weights: vec![],
208 };
209 vm._compute_master_supports();
210 vm._compute_delta_weights();
211 vm
212 }
213
214 fn _compute_master_supports(&mut self) {
215 let regions = locations_to_regions(&self.locations);
216 self.supports.clear();
217 for (i, region) in regions.iter().enumerate() {
218 let loc_axes: HashSet<Tag> = region.keys().copied().collect();
219 let mut region_copy = region.clone();
220 for prev_region in ®ions[..i] {
221 let prev_loc_axes: HashSet<Tag> = prev_region.keys().copied().collect();
222 if !prev_loc_axes.is_subset(&loc_axes) {
223 continue;
224 }
225 let mut relevant = true;
226 for (axis, &(lower, peak, upper)) in region.iter() {
227 if !prev_region.contains_key(axis) {
228 relevant = false;
229 break;
230 }
231 let prev_peak = prev_region.get(axis).unwrap().1;
232 if !((prev_peak - peak).abs() < f32::EPSILON
233 || (lower < prev_peak && prev_peak < upper))
234 {
235 relevant = false;
236 break;
237 }
238 }
239 if !relevant {
240 continue;
241 }
242 let mut best_axes: Support = Support::new();
243 let mut best_ratio = -1_f32;
244 for (&axis, &(_, val, _)) in prev_region.iter() {
245 let &(lower, loc_v, upper) = region.get(&axis).unwrap();
246 let mut new_lower = lower;
247 let mut new_upper = upper;
248 let ratio: f32;
249 if val < loc_v {
250 new_lower = val;
251 ratio = (val - loc_v) / (lower - loc_v);
252 } else if loc_v < val {
253 new_upper = val;
254 ratio = (val - loc_v) / (upper - loc_v);
255 } else {
256 continue;
257 }
258 if ratio > best_ratio {
259 best_ratio = ratio;
260 best_axes.clear();
261 }
262 if (ratio - best_ratio).abs() < f32::EPSILON {
263 best_axes.insert(axis, (new_lower, loc_v, new_upper));
264 }
265 }
266 for (axis, triple) in best_axes.iter() {
267 region_copy.insert(*axis, *triple);
268 }
269 }
270 self.supports.push(region_copy);
271 }
272 }
273
274 fn _compute_delta_weights(&mut self) {
275 self.delta_weights.clear();
276 for (i, loc) in self.locations.iter().enumerate() {
277 let mut delta_weight: HashMap<usize, f32> = HashMap::new();
278 for (j, support) in self.supports[..i].iter().enumerate() {
279 let scalar = support_scalar(loc, support);
280 if scalar != 0.0 {
281 delta_weight.insert(j, scalar);
282 }
283 }
284 self.delta_weights.push(delta_weight);
285 }
286 }
287
288 pub fn get_deltas_and_supports<T>(&self, master_values: &[Option<T>]) -> Vec<(T, Support)>
292 where
293 T: Sub<Output = T> + Mul<f32, Output = T> + Clone,
294 {
295 let mut out: Vec<(T, Support)> = vec![];
296 let submodel = &VariationModel::new(
297 self.original_locations
298 .iter()
299 .zip(master_values.iter())
300 .filter_map(|(loc, value)| value.as_ref().map(|_| loc.clone()))
301 .collect(),
302 self.axis_order.clone(),
303 );
304 let master_values: Vec<&T> = master_values.iter().flatten().collect();
305 assert_eq!(master_values.len(), submodel.delta_weights.len());
306 for (ix, weights) in submodel.delta_weights.iter().enumerate() {
307 let support = &submodel.supports[ix];
308 let mut delta = master_values[submodel.sort_order.apply_inv_idx(ix)].clone();
309 for (&j, &weight) in weights.iter() {
310 delta = delta - out[j].0.clone() * weight;
311 }
312 out.push((delta, support.clone()));
313 }
314 out
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use assert_approx_eq::assert_approx_eq;
322 use std::iter::FromIterator;
323
324 macro_rules! hashmap {
325 ($($k:expr => $v:expr),* $(,)?) => {
326 std::collections::HashMap::<_, _>::from_iter(std::array::IntoIter::new([$(($k, $v),)*]))
327 };
328 }
329 #[test]
330 fn test_support_scalar() {
331 assert_approx_eq!(support_scalar(&Location::new(), &Support::new()), 1.0);
332 assert_approx_eq!(
333 support_scalar(&hashmap!( *b"wght" => 0.2), &Support::new()),
334 1.0
335 );
336 assert_approx_eq!(
337 support_scalar(
338 &hashmap!( *b"wght" => 0.2),
339 &hashmap!( *b"wght" => (0_f32,2_f32,3_f32))
340 ),
341 0.1
342 );
343 assert_approx_eq!(
344 support_scalar(
345 &hashmap!( *b"wght" => 2.5),
346 &hashmap!( *b"wght" => (0_f32,2_f32,4_f32))
347 ),
348 0.75
349 );
350 }
351
352 #[test]
353 fn test_variation_model() {
354 let locations = vec![
355 hashmap!(*b"wght" => 0.55, *b"wdth" => 0.0),
356 hashmap!(*b"wght" => -0.55, *b"wdth" => 0.0),
357 hashmap!(*b"wght" => -1.0, *b"wdth" => 0.0),
358 hashmap!(*b"wght" => 0.0, *b"wdth" => 1.0),
359 hashmap!(*b"wght" => 0.66, *b"wdth" => 1.0),
360 hashmap!(*b"wght" => 0.66, *b"wdth" => 0.66),
361 hashmap!(*b"wght" => 0.0, *b"wdth" => 0.0),
362 hashmap!(*b"wght" => 1.0, *b"wdth" => 1.0),
363 hashmap!(*b"wght" => 1.0, *b"wdth" => 0.0),
364 ];
365 let axis_order = vec![*b"wght"];
366 let vm = VariationModel::new(locations, axis_order);
367 let expected_locations = vec![
368 hashmap!(),
369 hashmap!(*b"wght" => -0.55),
370 hashmap!(*b"wght" => -1.0),
371 hashmap!(*b"wght" => 0.55),
372 hashmap!(*b"wght" => 1.0),
373 hashmap!(*b"wdth" => 1.0),
374 hashmap!(*b"wdth" => 1.0, *b"wght" => 1.0),
375 hashmap!(*b"wdth" => 1.0, *b"wght" => 0.66),
376 hashmap!(*b"wdth" => 0.66, *b"wght" => 0.66),
377 ];
378 assert_eq!(vm.locations, expected_locations);
379
380 let expected_supports = vec![
381 hashmap!(),
382 hashmap!(*b"wght" => (-1.0, -0.55, 0.0)),
383 hashmap!(*b"wght" => (-1.0, -1.0, -0.55)),
384 hashmap!(*b"wght" => (0.0, 0.55, 1.0)),
385 hashmap!(*b"wght" => (0.55, 1.0, 1.0)),
386 hashmap!(*b"wdth" => (0.0, 1.0, 1.0)),
387 hashmap!(*b"wdth" => (0.0, 1.0, 1.0), *b"wght" => (0.0, 1.0, 1.0)),
388 hashmap!(*b"wdth" => (0.0, 1.0, 1.0), *b"wght" => (0.0, 0.66, 1.0)),
389 hashmap!(*b"wdth" => (0.0, 0.66, 1.0), *b"wght" => (0.0, 0.66, 1.0)),
390 ];
391 assert_eq!(vm.supports, expected_supports);
392
393 assert_eq!(vm.delta_weights[0], hashmap!());
394 assert_eq!(vm.delta_weights[1], hashmap!(0 => 1.0));
395 assert_eq!(vm.delta_weights[2], hashmap!(0 => 1.0));
396 assert_eq!(vm.delta_weights[3], hashmap!(0 => 1.0));
397 assert_eq!(vm.delta_weights[4], hashmap!(0 => 1.0));
398 assert_eq!(vm.delta_weights[5], hashmap!(0 => 1.0));
399 assert_eq!(vm.delta_weights[6], hashmap!(0 => 1.0, 4 => 1.0, 5 => 1.0));
400 assert_approx_eq!(vm.delta_weights[7].get(&3).unwrap(), 0.755_555_57);
401 assert_approx_eq!(vm.delta_weights[7].get(&4).unwrap(), 0.244_444_49);
402 assert_approx_eq!(vm.delta_weights[7].get(&5).unwrap(), 1.0);
403 assert_approx_eq!(vm.delta_weights[7].get(&6).unwrap(), 0.66);
404 }
405}