fonttools/otvar/
iup.rs

1use crate::glyf::Glyph;
2use crate::otvar::Delta;
3use otspec::types::*;
4use std::collections::{HashMap, HashSet};
5
6fn iup_segment(
7    newdeltas: &mut Vec<(i16, i16)>,
8    coords: &[(i16, i16)],
9    rc1: (i16, i16),
10    rd1: &Option<Delta>,
11    rc2: (i16, i16),
12    rd2: &Option<Delta>,
13) {
14    let rd1 = rd1.as_ref().unwrap().get_2d();
15    let rd2 = rd2.as_ref().unwrap().get_2d();
16    let mut out_arrays: Vec<Vec<i16>> = vec![vec![], vec![]];
17    for (j, out_array) in out_arrays.iter_mut().enumerate() {
18        let (mut x1, mut x2, mut d1, mut d2) = if j == 0 {
19            (rc1.0, rc2.0, rd1.0, rd2.0)
20        } else {
21            (rc1.1, rc2.1, rd1.1, rd2.1)
22        };
23        if x1 == x2 {
24            let n = coords.len();
25            out_array.extend(std::iter::repeat(if d1 == d2 { d1 } else { 0 }).take(n));
26            continue;
27        }
28        if x1 > x2 {
29            std::mem::swap(&mut x2, &mut x1);
30            std::mem::swap(&mut d2, &mut d1);
31        }
32
33        let scale = (d2 - d1) as f32 / (x2 - x1) as f32;
34
35        for pair in coords {
36            let x = if j == 0 { pair.0 } else { pair.1 };
37            let d = if x <= x1 {
38                d1
39            } else if x >= x2 {
40                d2
41            } else {
42                d1 + ((x - x1) as f32 * scale) as i16
43            };
44            out_array.push(d);
45        }
46    }
47    newdeltas.extend(
48        out_arrays[0]
49            .iter()
50            .zip(out_arrays[1].iter())
51            .map(|(x, y)| (*x, *y)),
52    );
53}
54
55/// Perform Interpolation of Unreferenced Points on a set of deltas and coordinates
56pub fn iup_contour(
57    newdeltas: &mut Vec<(i16, i16)>,
58    deltas: &[Option<Delta>],
59    coords: &[(i16, i16)],
60) {
61    if deltas.iter().all(|x| x.is_some()) {
62        newdeltas.extend::<Vec<(i16, i16)>>(
63            deltas
64                .iter()
65                .map(|x| x.as_ref().unwrap().get_2d())
66                .collect(),
67        );
68        return;
69    }
70    let n = deltas.len();
71    let indices: Vec<usize> = deltas
72        .iter()
73        .enumerate()
74        .filter(|(_, d)| d.is_some())
75        .map(|(i, _)| i)
76        .collect();
77    if indices.is_empty() {
78        newdeltas.extend(std::iter::repeat((0, 0)).take(n));
79        return;
80    }
81    let mut start = indices[0];
82    let verystart = start;
83    if start != 0 {
84        let (i1, i2, ri1, ri2) = (0, start, start, *indices.last().unwrap());
85        iup_segment(
86            newdeltas,
87            &coords[i1..i2],
88            coords[ri1],
89            &deltas[ri1],
90            coords[ri2],
91            &deltas[ri2],
92        );
93    }
94    newdeltas.push(deltas[start].as_ref().unwrap().get_2d());
95    for end in indices.iter().skip(1) {
96        if *end - start > 1 {
97            let (i1, i2, ri1, ri2) = (start + 1, *end, start, *end);
98            iup_segment(
99                newdeltas,
100                &coords[i1..i2],
101                coords[ri1],
102                &deltas[ri1],
103                coords[ri2],
104                &deltas[ri2],
105            );
106        }
107        newdeltas.push(deltas[*end].as_ref().unwrap().get_2d());
108        start = *end;
109    }
110    if start != n - 1 {
111        let (i1, i2, ri1, ri2) = (start + 1, n, start, verystart);
112        iup_segment(
113            newdeltas,
114            &coords[i1..i2],
115            coords[ri1],
116            &deltas[ri1],
117            coords[ri2],
118            &deltas[ri2],
119        );
120    }
121}
122
123/// Optimize the deltas by removing deltas that be inferred using IUP
124pub fn optimize_deltas(deltas: Vec<Option<Delta>>, glyph: &Glyph) -> Vec<Option<Delta>> {
125    let (coords, ends): (Vec<(int16, int16)>, Vec<usize>) = glyph.gvar_coords_and_ends();
126
127    let deltas_xy: Vec<(int16, int16)>;
128    if !deltas.iter().all(|x| x.is_some()) {
129        // Perhaps we're re-optimizing an optimized thing already.
130        // Oh well, IUP it all first.
131        let mut start = 0;
132        let mut newdeltas = vec![];
133        for end in &ends {
134            let contour_delta = &deltas[start..end + 1];
135            let contour_orig = &coords[start..end + 1];
136            start = end + 1;
137            iup_contour(&mut newdeltas, contour_delta, contour_orig);
138        }
139        deltas_xy = newdeltas;
140    } else {
141        deltas_xy = deltas
142            .iter()
143            .map(|o_d| {
144                if let Delta::Delta2D((x, y)) = o_d.as_ref().unwrap() {
145                    (*x, *y)
146                } else {
147                    panic!("Tried to IUP something that wasn't a 2d delta: {:?}", o_d);
148                }
149            })
150            .collect();
151    }
152    // Again, ends has the phantom points in already
153    let mut start = 0;
154    let mut newdeltas: Vec<Option<Delta>> = vec![];
155    // println!("Coords: {:?}", coords);
156    // println!("Ends: {:?}", ends);
157    for end in ends {
158        // println!("Start={:} End={:}", start, end);
159        // println!("Deltas: {:?}", &deltas_xy[start..end + 1]);
160        let contour =
161            iup_contour_optimize(&deltas_xy[start..end + 1], &coords[start..end + 1], 0.5);
162        assert_eq!(contour.len(), end - start + 1);
163        newdeltas.extend(contour);
164        start = end + 1;
165    }
166    newdeltas
167}
168
169fn iup_contour_optimize(
170    deltas_slice: &[(i16, i16)],
171    coords_slice: &[(i16, i16)],
172    tolerance: f32,
173) -> Vec<Option<Delta>> {
174    let mut deltas = deltas_slice.to_vec();
175    let mut coords = coords_slice.to_vec();
176    let n = deltas.len();
177    let mut rv = vec![];
178    if deltas
179        .iter()
180        .all(|(x, y)| (x.abs() as f32) <= tolerance && (y.abs() as f32) <= tolerance)
181    {
182        for _ in 0..n {
183            rv.push(None);
184        }
185        return rv;
186    }
187
188    if n == 1 {
189        return vec![Some(Delta::Delta2D(deltas[0]))];
190    }
191
192    let (first_x, first_y) = deltas[0];
193    if deltas.iter().all(|(x, y)| *x == first_x && *y == first_y) {
194        rv.push(Some(Delta::Delta2D(deltas[0])));
195        for _ in 1..n {
196            rv.push(None);
197        }
198        return rv;
199    }
200
201    let mut forced = _iup_contour_bound_forced_set(&deltas, &coords, tolerance);
202    let mut output_deltas: Vec<Option<Delta>>;
203    if !forced.is_empty() {
204        let k: i16 = ((n - 1) - (*forced.iter().max().unwrap() as usize)) as i16;
205        assert!(k >= 0);
206        deltas = rotate_list(deltas, k);
207        coords = rotate_list(coords, k);
208        forced = rotate_set(forced, k, n);
209        let (chain, _) = _iup_contour_optimize_dp(&deltas, &coords, &forced, tolerance, None);
210        let mut solution: HashSet<i16> = HashSet::new();
211        let mut i = n as i16 - 1;
212        loop {
213            solution.insert(i);
214            let next = chain.get(&i);
215            if let Some(n) = next {
216                i = *n;
217            } else {
218                break;
219            }
220        }
221        output_deltas = (0..n)
222            .map(|ix| {
223                if solution.contains(&(ix as i16)) {
224                    Some(Delta::Delta2D(deltas[ix]))
225                } else {
226                    None
227                }
228            })
229            .collect();
230        output_deltas = rotate_list(output_deltas, -(k as i16));
231    } else {
232        deltas.extend(deltas.clone());
233        coords.extend(coords.clone());
234        let (chain, costs) =
235            _iup_contour_optimize_dp(&deltas, &coords, &forced, tolerance, Some(n as i16));
236        let mut best_cost = n as i16 + 1;
237        let mut best_sol: HashSet<usize> = HashSet::new();
238        for start in n - 1..2 * n + 1 {
239            let mut solution: HashSet<usize> = HashSet::new();
240            let mut i = start as i16;
241            while i > (start as i16) - (n as i16) {
242                solution.insert(i.rem_euclid(n as i16) as usize);
243                let next = chain.get(&(i as i16));
244                if let Some(n) = next {
245                    i = *n;
246                } else {
247                    break;
248                }
249            }
250            if i == (start as i16) - (n as i16) {
251                let cost = *costs.get(&(start as i16)).unwrap_or(&(n as i16 * 4))
252                    - *costs
253                        .get(&(start as i16 - n as i16))
254                        .unwrap_or(&(n as i16 * 3));
255                if cost <= best_cost {
256                    best_sol = solution.clone();
257                    best_cost = cost;
258                }
259            }
260        }
261        output_deltas = (0..n)
262            .map(|ix| {
263                if best_sol.contains(&ix) {
264                    Some(Delta::Delta2D(deltas[ix]))
265                } else {
266                    None
267                }
268            })
269            .collect();
270    }
271    // println!("Done {:?}", output_deltas);
272    output_deltas
273}
274
275fn rotate_set(s: HashSet<i16>, mut k: i16, n: usize) -> HashSet<i16> {
276    k %= n as i16;
277    if k == 0 {
278        return s;
279    }
280    s.iter().map(|v| ((*v + k) % (n as i16))).collect()
281}
282
283fn rotate_list<T: Clone + Sized + std::fmt::Debug>(l: Vec<T>, mut k: i16) -> Vec<T> {
284    // println!("Rotating list {:?} by {:?}", l, k);
285    let n = l.len();
286    k = k.rem_euclid(n as i16);
287    if k == 0 {
288        return l;
289    }
290    let partition = (n as i16 - k) as usize;
291    // println!("Partition at {:?}", partition);
292    let mut first = l[partition..].to_vec();
293    let second = &l[0..partition];
294    first.extend(second.to_vec());
295    first
296}
297
298fn _iup_contour_bound_forced_set(
299    deltas: &[(i16, i16)],
300    coords: &[(i16, i16)],
301    tolerance: f32,
302) -> HashSet<i16> {
303    assert_eq!(deltas.len(), coords.len());
304    let mut forced = HashSet::new();
305    let mut nd = deltas[0];
306    let mut nc = coords[0];
307    let mut i = (deltas.len() - 1) as i16;
308    let mut ld = deltas[i as usize];
309    let mut lc = coords[i as usize];
310    while i > -1 {
311        let d = ld;
312        let c = lc;
313        // Use Euclidean remainders here to get i=0 case
314        ld = deltas[((i - 1).rem_euclid(deltas.len() as i16)) as usize];
315        lc = coords[((i - 1).rem_euclid(coords.len() as i16)) as usize];
316        for j in 0..2 {
317            let cj = if j == 0 { c.0 } else { c.1 } as f32;
318            let dj = if j == 0 { d.0 } else { d.1 } as f32;
319            let lcj = if j == 0 { lc.0 } else { lc.1 } as f32;
320            let ldj = if j == 0 { ld.0 } else { ld.1 } as f32;
321            let ncj = if j == 0 { nc.0 } else { nc.1 } as f32;
322            let ndj = if j == 0 { nd.0 } else { nd.1 } as f32;
323            let (c1, c2, d1, d2);
324
325            if lcj <= ncj {
326                c1 = lcj;
327                c2 = ncj;
328                d1 = ldj;
329                d2 = ndj;
330            } else {
331                c1 = ncj;
332                c2 = lcj;
333                d1 = ndj;
334                d2 = ldj;
335            }
336            let mut force = false;
337            // This nested if statement is a mess, but it's a mess ported from Python.
338            // Keeping it a mess for ease of comparison.
339            #[allow(clippy::collapsible_else_if)]
340            if c1 <= cj && cj <= c2 {
341                if !(d1.min(d2) - tolerance <= dj && dj <= d1.max(d2) + tolerance) {
342                    force = true;
343                }
344            } else {
345                if (c1 - c2).abs() < f32::EPSILON {
346                    if (d1 - d2).abs() < f32::EPSILON {
347                        if (dj - d1).abs() > tolerance {
348                            force = true;
349                        }
350                    } else if dj.abs() > tolerance {
351                        // Not forced, surprisingly.
352                    }
353                } else if (d1 - d2).abs() > f32::EPSILON {
354                    if cj < c1 {
355                        if (dj - d1).abs() > f32::EPSILON && ((dj - tolerance < d1) != (d1 < d2)) {
356                            force = true;
357                        }
358                    } else if (d2 - dj).abs() > f32::EPSILON && ((d2 < dj + tolerance) != (d1 < d2))
359                    {
360                        force = true;
361                    }
362                }
363            }
364            if force {
365                forced.insert(i);
366                break;
367            }
368        }
369        nd = d;
370        nc = c;
371        i -= 1;
372    }
373    forced
374}
375
376fn _iup_contour_optimize_dp(
377    deltas: &[(i16, i16)],
378    coords: &[(i16, i16)],
379    forced: &HashSet<i16>,
380    tolerance: f32,
381    lookback_o: Option<i16>,
382) -> (HashMap<i16, i16>, HashMap<i16, i16>) {
383    let n = deltas.len();
384    let lookback = lookback_o.unwrap_or(n as i16);
385    let mut costs: HashMap<i16, i16> = HashMap::new();
386    let mut chain: HashMap<i16, i16> = HashMap::new();
387    // println!("Doing DP. Forced={:?}", forced);
388    costs.insert(-1, 0);
389    for i in 0..n {
390        // println!(" i={:?}", i);
391        let i_i16 = i as i16;
392        let mut best_cost = costs.get(&(i_i16 - 1)).unwrap() + 1;
393        costs.insert(i_i16, best_cost);
394        chain.insert(i_i16, i_i16 - 1);
395        // println!(" best_cost={:?}", best_cost);
396        if forced.contains(&(i_i16 - 1)) {
397            // println!(" Prev was forced");
398            continue;
399        }
400        let mut j: i16 = i_i16 - 2;
401        // println!(" j={:?}", j);
402        while j > (i_i16 - lookback).max(-2) {
403            let cost = costs.get(&j).unwrap() + 1;
404            if cost < best_cost && can_iup_between(deltas, coords, j, i_i16, tolerance) {
405                best_cost = cost;
406                costs.insert(i_i16, best_cost);
407                chain.insert(i_i16, j as i16);
408            }
409            if forced.contains(&j) {
410                break;
411            }
412            j -= 1;
413        }
414    }
415    // println!("Done, {:?}, {:?}", chain, costs);
416    (chain, costs)
417}
418
419fn can_iup_between(
420    deltas: &[(i16, i16)],
421    coords: &[(i16, i16)],
422    i_i16: i16,
423    j_i16: i16,
424    tolerance: f32,
425) -> bool {
426    assert!(j_i16 - i_i16 >= 2);
427    let i = i_i16.rem_euclid((deltas.len()) as i16) as usize;
428    let j = j_i16.rem_euclid((deltas.len()) as i16) as usize;
429    let mut coord_portion: Vec<(i16, i16)>;
430    let mut delta_portion: Vec<(i16, i16)>;
431    if i + 1 > j {
432        coord_portion = coords[i + 1..].to_vec();
433        coord_portion.extend(coords[0..j].to_vec());
434        delta_portion = deltas[i + 1..].to_vec();
435        delta_portion.extend(deltas[0..j].to_vec());
436    } else {
437        coord_portion = coords[i + 1..j].to_vec();
438        delta_portion = deltas[i + 1..j].to_vec();
439    };
440    // assert!(j - i >= 2);
441    let mut interp = vec![];
442    iup_segment(
443        &mut interp,
444        &coord_portion,
445        coords[i],
446        &Some(Delta::Delta2D(deltas[i])),
447        coords[j],
448        &Some(Delta::Delta2D(deltas[j])),
449    );
450    assert_eq!(interp.len(), delta_portion.len());
451    let can_iup = delta_portion
452        .iter()
453        .zip(interp.iter())
454        .all(|((x, y), (p, q))| {
455            ((x - p) as f32 * (x - p) as f32 + (y - q) as f32 * (y - q) as f32) <= tolerance
456        });
457    can_iup
458}
459
460#[cfg(test)]
461mod tests {
462    use super::*;
463
464    #[test]
465    fn test_can_iup() {
466        let coords = vec![(261, 611), (261, 113), (108, 113), (108, 611)];
467        let deltas = vec![
468            (38, 125),   // IUP
469            (38, -125),  // given
470            (-38, -125), // IUP
471            (-38, 125),  // given
472        ];
473        assert!(can_iup_between(&deltas, &coords, 1, 3, 0.5));
474        assert!(can_iup_between(&deltas, &coords, -1, 1, 0.5));
475    }
476
477    #[test]
478    fn test_do_iup1_optimize() {
479        let optimized = vec![
480            Some(Delta::Delta2D((155, 0))),
481            Some(Delta::Delta2D((123, 0))),
482            Some(Delta::Delta2D((32, 0))),
483            Some(Delta::Delta2D((64, 0))),
484            None,
485        ];
486        let coords = vec![(751, 0), (433, 700), (323, 700), (641, 0), (751, 0)];
487        let unoptimized = vec![(155, 0), (123, 0), (32, 0), (64, 0), (155, 0)];
488        let mut newdeltas = vec![];
489        iup_contour(&mut newdeltas, &optimized, &coords);
490        assert_eq!(newdeltas, unoptimized);
491
492        let check_optimized = iup_contour_optimize(&unoptimized, &coords, 0.5);
493        assert_eq!(check_optimized, optimized);
494    }
495
496    #[test]
497    fn test_do_iup2_optimize() {
498        let optimized = vec![
499            Some(Delta::Delta2D((38, 27))),
500            None,
501            Some(Delta::Delta2D((73, -13))),
502            None,
503            None,
504        ];
505        let coords = vec![(152, 284), (152, 204), (567, 204), (567, 284), (152, 284)];
506        let unoptimized = vec![(38, 27), (38, -13), (73, -13), (73, 27), (38, 27)];
507        let mut newdeltas = vec![];
508        iup_contour(&mut newdeltas, &optimized, &coords);
509        assert_eq!(newdeltas, unoptimized);
510
511        let check_optimized = iup_contour_optimize(&unoptimized, &coords, 0.5);
512        assert_eq!(check_optimized, optimized);
513    }
514}