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
55pub 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
123pub 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 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 let mut start = 0;
154 let mut newdeltas: Vec<Option<Delta>> = vec![];
155 for end in ends {
158 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 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 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 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 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 #[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 }
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 costs.insert(-1, 0);
389 for i in 0..n {
390 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 if forced.contains(&(i_i16 - 1)) {
397 continue;
399 }
400 let mut j: i16 = i_i16 - 2;
401 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 (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 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), (38, -125), (-38, -125), (-38, 125), ];
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}