fonttools/otvar/
locations.rs

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/// Structs to store locations (user and normalized)
9
10/// A location in the internal -1 <= 0 => 1 representation
11#[derive(Debug)]
12pub struct NormalizedLocation(pub Tuple);
13
14type Support = HashMap<Tag, (f32, f32, f32)>;
15/// A location as a mapping of tags to user-space values
16pub type Location = HashMap<Tag, f32>;
17type AxisPoints = HashMap<Tag, HashSet<F2DOT14>>;
18
19/// An OpenType variation model helps to determine and interpolate the correct
20/// supports and deltasets when there are intermediate masters.
21#[derive(Debug)]
22pub struct VariationModel {
23    /// The rearranged list of master locations
24    pub locations: Vec<Location>,
25    sort_order: Permutation,
26    /// The supports computed for each master
27    pub supports: Vec<Support>,
28    /// The axis order provided by the user
29    pub axis_order: Vec<Tag>,
30    /// The original, unordered list of locations
31    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    /// Create a new OpenType variation model for the given list of master
98    /// locations. Locations must be provided in normalized coordinates (-1..1)
99    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 &regions[..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    /// Retrieve the deltas, together with their support regions, for a given
289    /// set of master values. Values may be provided for a subset of the model's
290    /// locations, although a value must be provided for the default location.
291    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}