use std::collections::HashSet;
use super::GlyphDelta;
use crate::{round::OtRound, util::WrappingGet};
use kurbo::{Point, Vec2};
const NUM_PHANTOM_POINTS: usize = 4;
pub fn iup_delta_optimize(
deltas: Vec<Vec2>,
coords: Vec<Point>,
tolerance: f64,
contour_ends: &[usize],
) -> Result<Vec<GlyphDelta>, IupError> {
let num_coords = coords.len();
if num_coords < NUM_PHANTOM_POINTS {
return Err(IupError::NotEnoughCoords(num_coords));
}
if deltas.len() != coords.len() {
return Err(IupError::DeltaCoordLengthMismatch {
num_deltas: deltas.len(),
num_coords: coords.len(),
});
}
let mut contour_ends = contour_ends.to_vec();
contour_ends.sort();
let expected_num_coords = contour_ends
.last()
.copied()
.map(|v| v + 1)
.unwrap_or_default()
+ NUM_PHANTOM_POINTS;
if num_coords != expected_num_coords {
return Err(IupError::CoordEndsMismatch {
num_coords,
expected_num_coords,
});
}
for offset in (1..=4).rev() {
contour_ends.push(num_coords.saturating_sub(offset));
}
let mut result = Vec::with_capacity(num_coords);
let mut start = 0;
let mut deltas = deltas;
let mut coords = coords;
for end in contour_ends {
let contour = iup_contour_optimize(
&mut deltas[start..=end],
&mut coords[start..=end],
tolerance,
)?;
result.extend_from_slice(&contour);
assert_eq!(contour.len() + start, end + 1);
start = end + 1;
}
Ok(result)
}
#[derive(Clone, Debug)]
pub enum IupError {
DeltaCoordLengthMismatch {
num_deltas: usize,
num_coords: usize,
},
NotEnoughCoords(usize),
CoordEndsMismatch {
num_coords: usize,
expected_num_coords: usize,
},
AchievedInvalidState(String),
}
fn must_encode_at(deltas: &[Vec2], coords: &[Point], tolerance: f64, at: usize) -> bool {
let ld = *deltas.wrapping_prev(at);
let d = deltas[at];
let nd = *deltas.wrapping_next(at);
let lc = *coords.wrapping_prev(at);
let c = coords[at];
let nc = *coords.wrapping_next(at);
for axis in [Axis2D::X, Axis2D::Y] {
let (ldj, lcj) = (ld.get(axis), lc.get(axis));
let (dj, cj) = (d.get(axis), c.get(axis));
let (ndj, ncj) = (nd.get(axis), nc.get(axis));
let (c1, c2, d1, d2) = if lcj <= ncj {
(lcj, ncj, ldj, ndj)
} else {
(ncj, lcj, ndj, ldj)
};
match (c1, c2) {
_ if c1 == c2 => {
if (d1 - d2).abs() > tolerance && dj.abs() > tolerance {
return true;
}
}
_ if c1 <= cj && cj <= c2 => {
if !(d1.min(d2) - tolerance <= dj && dj <= d1.max(d2) + tolerance) {
return true;
}
}
_ => {
if d1 != d2 && dj.abs() > tolerance {
if cj < c1 {
if ((dj - d1).abs() > tolerance) && (dj - tolerance < d1) != (d1 < d2) {
return true;
}
} else if ((dj - d2).abs() > tolerance) && (d2 < dj + tolerance) != (d1 < d2) {
return true;
}
}
}
}
}
false
}
fn iup_must_encode(
deltas: &[Vec2],
coords: &[Point],
tolerance: f64,
) -> Result<HashSet<usize>, IupError> {
Ok((0..deltas.len())
.rev()
.filter(|i| must_encode_at(deltas, coords, tolerance, *i))
.collect())
}
#[derive(Copy, Clone, Debug)]
enum Axis2D {
X,
Y,
}
trait Coord {
fn get(&self, axis: Axis2D) -> f64;
fn set(&mut self, coord: Axis2D, value: f64);
}
impl Coord for Point {
fn get(&self, axis: Axis2D) -> f64 {
match axis {
Axis2D::X => self.x,
Axis2D::Y => self.y,
}
}
fn set(&mut self, axis: Axis2D, value: f64) {
match axis {
Axis2D::X => self.x = value,
Axis2D::Y => self.y = value,
}
}
}
impl Coord for Vec2 {
fn get(&self, axis: Axis2D) -> f64 {
match axis {
Axis2D::X => self.x,
Axis2D::Y => self.y,
}
}
fn set(&mut self, axis: Axis2D, value: f64) {
match axis {
Axis2D::X => self.x = value,
Axis2D::Y => self.y = value,
}
}
}
fn iup_segment(coords: &[Point], rc1: Point, rd1: Vec2, rc2: Point, rd2: Vec2) -> Vec<Vec2> {
let n = coords.len();
let mut result = vec![Vec2::default(); n];
for axis in [Axis2D::X, Axis2D::Y] {
let c1 = rc1.get(axis);
let c2 = rc2.get(axis);
let d1 = rd1.get(axis);
let d2 = rd2.get(axis);
if c1 == c2 {
let value = if d1 == d2 { d1 } else { 0.0 };
for r in result.iter_mut() {
r.set(axis, value);
}
continue;
}
let (c1, c2, d1, d2) = if c1 > c2 {
(c2, c1, d2, d1) } else {
(c1, c2, d1, d2) };
let scale = (d2 - d1) / (c2 - c1);
for (idx, point) in coords.iter().enumerate() {
let c = point.get(axis);
let d = if c <= c1 {
d1
} else if c >= c2 {
d2
} else {
d1 + (c - c1) * scale
};
result[idx].set(axis, d);
}
}
result
}
fn can_iup_in_between(
deltas: &[Vec2],
coords: &[Point],
tolerance: f64,
from: isize,
to: isize,
) -> Result<bool, IupError> {
if from < -1 || to <= from || to - from < 2 {
return Err(IupError::AchievedInvalidState(format!(
"bad from/to: {from}..{to}"
)));
}
let to = to as usize;
let (rc1, rd1) = if from < 0 {
(*coords.last().unwrap(), *deltas.last().unwrap())
} else {
(coords[from as usize], deltas[from as usize])
};
let iup_values = iup_segment(
&coords[(from + 1) as usize..to],
rc1,
rd1,
coords[to],
deltas[to],
);
let real_values = &deltas[(from + 1) as usize..to];
let tolerance_sq = tolerance.powi(2);
Ok(real_values
.iter()
.zip(iup_values)
.all(|(d, i)| (*d - i).hypot2() <= tolerance_sq))
}
fn iup_initial_lookback(deltas: &[Vec2]) -> usize {
std::cmp::min(deltas.len(), 8usize) }
#[derive(Debug, PartialEq)]
struct OptimizeDpResult {
costs: Vec<i32>,
chain: Vec<Option<usize>>,
}
fn iup_contour_optimize_dp(
deltas: &[Vec2],
coords: &[Point],
tolerance: f64,
must_encode: &HashSet<usize>,
lookback: usize,
) -> Result<OptimizeDpResult, IupError> {
let n = deltas.len();
let lookback = lookback as isize;
let mut costs = Vec::with_capacity(n);
let mut chain: Vec<_> = (0..n)
.map(|i| if i > 0 { Some(i - 1) } else { None })
.collect();
if n < 2 {
return Ok(OptimizeDpResult { costs, chain });
}
for i in 0..n {
let mut best_cost = if i > 0 { costs[i - 1] } else { 0 } + 1;
costs.push(best_cost);
if i > 0 && must_encode.contains(&(i - 1)) {
continue;
}
let j_min = std::cmp::max(i as isize - lookback, -2);
let j_max = i as isize - 2;
for j in (j_min + 1..j_max + 1).rev() {
let (cost, must_encode) = if j >= 0 {
(costs[j as usize] + 1, must_encode.contains(&(j as usize)))
} else {
(1, false)
};
if cost < best_cost && can_iup_in_between(deltas, coords, tolerance, j, i as isize)? {
best_cost = cost;
costs[i] = best_cost;
chain[i] = if j >= 0 { Some(j as usize) } else { None };
}
if must_encode {
break;
}
}
}
Ok(OptimizeDpResult { costs, chain })
}
fn iup_contour_optimize(
deltas: &mut [Vec2],
coords: &mut [Point],
tolerance: f64,
) -> Result<Vec<GlyphDelta>, IupError> {
if deltas.len() != coords.len() {
return Err(IupError::DeltaCoordLengthMismatch {
num_deltas: deltas.len(),
num_coords: coords.len(),
});
}
let n = deltas.len();
let Some(first_delta) = deltas.first() else {
return Ok(Vec::new());
};
if deltas.iter().all(|d| d == first_delta) {
if *first_delta == Vec2::ZERO {
return Ok(vec![GlyphDelta::optional(0, 0); n]);
}
let (x, y) = first_delta.to_point().ot_round();
return Ok(std::iter::once(GlyphDelta::required(x, y))
.chain(std::iter::repeat(GlyphDelta::optional(x, y)))
.take(n)
.collect());
}
let must_encode = iup_must_encode(deltas, coords, tolerance)?;
let encode = if !must_encode.is_empty() {
let mid = n - 1 - must_encode.iter().max().unwrap();
deltas.rotate_right(mid);
coords.rotate_right(mid);
let must_encode: HashSet<usize> = must_encode.iter().map(|idx| (idx + mid) % n).collect();
let dp_result = iup_contour_optimize_dp(
deltas,
coords,
tolerance,
&must_encode,
iup_initial_lookback(deltas),
)?;
let mut encode = HashSet::new();
let mut i = n - 1;
loop {
encode.insert(i);
i = match dp_result.chain[i] {
Some(v) => v,
None => break,
};
}
if !encode.is_superset(&must_encode) {
return Err(IupError::AchievedInvalidState(format!(
"{encode:?} should contain {must_encode:?}"
)));
}
deltas.rotate_left(mid);
encode.iter().map(|idx| (idx + n - mid) % n).collect()
} else {
let mut deltas_twice = Vec::with_capacity(2 * n);
deltas_twice.extend_from_slice(deltas);
deltas_twice.extend_from_slice(deltas);
let mut coords_twice = Vec::with_capacity(2 * n);
coords_twice.extend_from_slice(coords);
coords_twice.extend_from_slice(coords);
let dp_result = iup_contour_optimize_dp(
&deltas_twice,
&coords_twice,
tolerance,
&must_encode,
iup_initial_lookback(deltas),
)?;
let mut best_sol = None;
let mut best_cost = (n + 1) as i32;
for start in n - 1..dp_result.costs.len() - 1 {
let mut solution = HashSet::new();
let mut i = Some(start);
while i > start.checked_sub(n) {
let idx = i.unwrap();
solution.insert(idx % n);
i = dp_result.chain[idx];
}
if i == start.checked_sub(n) {
let cost = dp_result.costs[start]
- if n < start {
dp_result.costs[start - n]
} else {
0
};
if cost <= best_cost {
best_sol = Some(solution);
best_cost = cost;
}
}
}
let encode = best_sol.ok_or(IupError::AchievedInvalidState(
"No best solution identified".to_string(),
))?;
if !encode.is_superset(&must_encode) {
return Err(IupError::AchievedInvalidState(format!(
"{encode:?} should contain {must_encode:?}"
)));
}
encode
};
Ok(deltas
.iter()
.enumerate()
.map(|(i, delta)| {
let (x, y) = delta.to_point().ot_round();
if encode.contains(&i) {
GlyphDelta::required(x, y)
} else {
GlyphDelta::optional(x, y)
}
})
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
struct IupScenario {
deltas: Vec<Vec2>,
coords: Vec<Point>,
expected_must_encode: HashSet<usize>,
}
impl IupScenario {
fn assert_must_encode(&self) {
assert_eq!(
self.expected_must_encode,
iup_must_encode(&self.deltas, &self.coords, f64::EPSILON).unwrap()
);
}
fn assert_optimize_dp(&self) {
let must_encode = iup_must_encode(&self.deltas, &self.coords, f64::EPSILON).unwrap();
let lookback = iup_initial_lookback(&self.deltas);
let r1 = iup_contour_optimize_dp(
&self.deltas,
&self.coords,
f64::EPSILON,
&must_encode,
lookback,
)
.unwrap();
let must_encode = HashSet::new();
let r2 = iup_contour_optimize_dp(
&self.deltas,
&self.coords,
f64::EPSILON,
&must_encode,
lookback,
)
.unwrap();
assert_eq!(r1, r2);
}
fn assert_optimize_contour(&self) {
let mut deltas = self.deltas.clone();
let mut coords = self.coords.clone();
iup_contour_optimize(&mut deltas, &mut coords, f64::EPSILON).unwrap();
}
}
fn iup_scenario1() -> IupScenario {
IupScenario {
deltas: vec![(0.0, 0.0).into()],
coords: vec![(1.0, 2.0).into()],
expected_must_encode: HashSet::new(),
}
}
fn iup_scenario2() -> IupScenario {
IupScenario {
deltas: vec![(0.0, 0.0).into(), (0.0, 0.0).into(), (0.0, 0.0).into()],
coords: vec![(1.0, 2.0).into(), (3.0, 2.0).into(), (2.0, 3.0).into()],
expected_must_encode: HashSet::new(),
}
}
fn iup_scenario3() -> IupScenario {
IupScenario {
deltas: vec![
(1.0, 1.0).into(),
(-1.0, 1.0).into(),
(-1.0, -1.0).into(),
(1.0, -1.0).into(),
],
coords: vec![
(0.0, 0.0).into(),
(2.0, 0.0).into(),
(2.0, 2.0).into(),
(0.0, 2.0).into(),
],
expected_must_encode: HashSet::new(),
}
}
fn iup_scenario4() -> IupScenario {
IupScenario {
deltas: vec![
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(-1.0, 0.0).into(),
],
coords: vec![
(-35.0, -152.0).into(),
(-86.0, -101.0).into(),
(-50.0, -65.0).into(),
(0.0, -116.0).into(),
(51.0, -65.0).into(),
(86.0, -99.0).into(),
(35.0, -151.0).into(),
(87.0, -202.0).into(),
(51.0, -238.0).into(),
(-1.0, -187.0).into(),
(-53.0, -239.0).into(),
(-88.0, -205.0).into(),
],
expected_must_encode: HashSet::from([11]),
}
}
fn iup_scenario5() -> IupScenario {
IupScenario {
deltas: vec![
(0.0, 0.0).into(),
(1.0, 0.0).into(),
(2.0, 0.0).into(),
(2.0, 0.0).into(),
(0.0, 0.0).into(),
(1.0, 0.0).into(),
(3.0, 0.0).into(),
(3.0, 0.0).into(),
(2.0, 0.0).into(),
(2.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-3.0, 0.0).into(),
(-1.0, 0.0).into(),
(0.0, 0.0).into(),
(0.0, 0.0).into(),
(-2.0, 0.0).into(),
(-2.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-1.0, 0.0).into(),
(-4.0, 0.0).into(),
],
coords: vec![
(330.0, 65.0).into(),
(401.0, 65.0).into(),
(499.0, 117.0).into(),
(549.0, 225.0).into(),
(549.0, 308.0).into(),
(549.0, 422.0).into(),
(549.0, 500.0).into(),
(497.0, 600.0).into(),
(397.0, 648.0).into(),
(324.0, 648.0).into(),
(271.0, 648.0).into(),
(200.0, 620.0).into(),
(165.0, 570.0).into(),
(165.0, 536.0).into(),
(165.0, 473.0).into(),
(252.0, 407.0).into(),
(355.0, 407.0).into(),
(396.0, 407.0).into(),
(396.0, 333.0).into(),
(354.0, 333.0).into(),
(249.0, 333.0).into(),
(141.0, 268.0).into(),
(141.0, 203.0).into(),
(141.0, 131.0).into(),
(247.0, 65.0).into(),
],
expected_must_encode: HashSet::from([5, 15, 24]),
}
}
fn iup_scenario6() -> IupScenario {
IupScenario {
deltas: vec![
(0.0, 0.0).into(),
(1.0, 1.0).into(),
(2.0, 2.0).into(),
(3.0, 3.0).into(),
(4.0, 4.0).into(),
(5.0, 5.0).into(),
(6.0, 6.0).into(),
(7.0, 7.0).into(),
],
coords: vec![
(0.0, 0.0).into(),
(10.0, 10.0).into(),
(20.0, 20.0).into(),
(30.0, 30.0).into(),
(40.0, 40.0).into(),
(50.0, 50.0).into(),
(60.0, 60.0).into(),
(70.0, 70.0).into(),
],
expected_must_encode: HashSet::from([]),
}
}
fn iup_scenario7() -> IupScenario {
IupScenario {
coords: vec![
(242.0, 111.0),
(314.0, 111.0),
(314.0, 317.0),
(513.0, 317.0),
(513.0, 388.0),
(314.0, 388.0),
(314.0, 595.0),
(242.0, 595.0),
(242.0, 388.0),
(43.0, 388.0),
(43.0, 317.0),
(242.0, 317.0),
(0.0, 0.0),
(557.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect(),
deltas: vec![
(-10.0, 0.0),
(25.0, 0.0),
(25.0, -18.0),
(15.0, -18.0),
(15.0, 18.0),
(25.0, 18.0),
(25.0, 1.0),
(-10.0, 1.0),
(-10.0, 18.0),
(0.0, 18.0),
(0.0, -18.0),
(-10.0, -18.0),
(0.0, 0.0),
(15.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect(),
expected_must_encode: HashSet::from([0]),
}
}
fn iup_scenario8() -> IupScenario {
IupScenario {
coords: vec![
(131.0, 430.0),
(131.0, 350.0),
(470.0, 350.0),
(470.0, 430.0),
(131.0, 330.0),
]
.into_iter()
.map(|c| c.into())
.collect(),
deltas: vec![
(-15.0, 115.0),
(-15.0, 30.0),
(124.0, 30.0),
(124.0, 115.0),
(-39.0, 26.0),
]
.into_iter()
.map(|c| c.into())
.collect(),
expected_must_encode: HashSet::from([0, 4]),
}
}
#[test]
fn iup_test_scenario01_must_encode() {
iup_scenario1().assert_must_encode();
}
#[test]
fn iup_test_scenario02_must_encode() {
iup_scenario2().assert_must_encode();
}
#[test]
fn iup_test_scenario03_must_encode() {
iup_scenario3().assert_must_encode();
}
#[test]
fn iup_test_scenario04_must_encode() {
iup_scenario4().assert_must_encode();
}
#[test]
fn iup_test_scenario05_must_encode() {
iup_scenario5().assert_must_encode();
}
#[test]
fn iup_test_scenario06_must_encode() {
iup_scenario6().assert_must_encode();
}
#[test]
fn iup_test_scenario07_must_encode() {
iup_scenario7().assert_must_encode();
}
#[test]
fn iup_test_scenario08_must_encode() {
iup_scenario8().assert_must_encode();
}
#[test]
fn iup_test_scenario01_optimize() {
iup_scenario1().assert_optimize_dp();
}
#[test]
fn iup_test_scenario02_optimize() {
iup_scenario2().assert_optimize_dp();
}
#[test]
fn iup_test_scenario03_optimize() {
iup_scenario3().assert_optimize_dp();
}
#[test]
fn iup_test_scenario04_optimize() {
iup_scenario4().assert_optimize_dp();
}
#[test]
fn iup_test_scenario05_optimize() {
iup_scenario5().assert_optimize_dp();
}
#[test]
fn iup_test_scenario06_optimize() {
iup_scenario6().assert_optimize_dp();
}
#[test]
fn iup_test_scenario07_optimize() {
iup_scenario7().assert_optimize_dp();
}
#[test]
fn iup_test_scenario08_optimize() {
iup_scenario8().assert_optimize_dp();
}
#[test]
fn iup_test_scenario01_optimize_contour() {
iup_scenario1().assert_optimize_contour();
}
#[test]
fn iup_test_scenario02_optimize_contour() {
iup_scenario2().assert_optimize_contour();
}
#[test]
fn iup_test_scenario03_optimize_contour() {
iup_scenario3().assert_optimize_contour();
}
#[test]
fn iup_test_scenario04_optimize_contour() {
iup_scenario4().assert_optimize_contour();
}
#[test]
fn iup_test_scenario05_optimize_contour() {
iup_scenario5().assert_optimize_contour();
}
#[test]
fn iup_test_scenario06_optimize_contour() {
iup_scenario6().assert_optimize_contour();
}
#[test]
fn iup_test_scenario07_optimize_contour() {
iup_scenario7().assert_optimize_contour();
}
#[test]
fn iup_test_scenario08_optimize_contour() {
iup_scenario8().assert_optimize_contour();
}
fn make_vec_of_options(deltas: &[GlyphDelta]) -> Vec<(usize, Option<Vec2>)> {
deltas
.iter()
.enumerate()
.map(|(i, delta)| {
(
i,
delta
.required
.then_some(Vec2::new(delta.x as _, delta.y as _)),
)
})
.collect()
}
#[test]
fn iup_delta_optimize_oswald_glyph_two() {
let deltas: Vec<_> = vec![
(0.0, 0.0),
(41.0, 0.0),
(41.0, 41.0),
(60.0, 41.0),
(22.0, -22.0),
(27.0, -15.0),
(38.0, -4.0),
(44.0, 2.0),
(44.0, -1.0),
(44.0, 2.0),
(29.0, 4.0),
(18.0, 4.0),
(9.0, 4.0),
(-4.0, -4.0),
(-11.0, -12.0),
(-11.0, -10.0),
(-11.0, -25.0),
(44.0, -25.0),
(44.0, -12.0),
(44.0, -20.0),
(39.0, -38.0),
(26.0, -50.0),
(16.0, -50.0),
(-5.0, -50.0),
(-13.0, -21.0),
(-13.0, 1.0),
(-13.0, 11.0),
(-13.0, 16.0),
(-13.0, 16.0),
(-12.0, 19.0),
(0.0, 42.0),
(0.0, 0.0),
(36.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect();
let coords: Vec<_> = vec![
(41.0, 0.0),
(423.0, 0.0),
(423.0, 90.0),
(167.0, 90.0),
(353.0, 374.0),
(377.0, 410.0),
(417.0, 478.0),
(442.0, 556.0),
(442.0, 608.0),
(442.0, 706.0),
(346.0, 817.0),
(248.0, 817.0),
(176.0, 817.0),
(89.0, 759.0),
(50.0, 654.0),
(50.0, 581.0),
(50.0, 553.0),
(157.0, 553.0),
(157.0, 580.0),
(157.0, 619.0),
(173.0, 687.0),
(215.0, 729.0),
(253.0, 729.0),
(298.0, 729.0),
(334.0, 665.0),
(334.0, 609.0),
(334.0, 564.0),
(309.0, 495.0),
(270.0, 433.0),
(247.0, 397.0),
(41.0, 76.0),
(0.0, 0.0),
(478.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect();
let tolerance = 0.5;
let contour_ends = vec![coords.len() - 1 - 4];
let result = iup_delta_optimize(deltas.clone(), coords, tolerance, &contour_ends).unwrap();
assert_eq!(result.len(), deltas.len());
let result = make_vec_of_options(&result);
assert_eq!(
result,
vec![
(0, None),
(1, Some(Vec2 { x: 41.0, y: 0.0 })),
(2, None),
(3, Some(Vec2 { x: 60.0, y: 41.0 })),
(4, Some(Vec2 { x: 22.0, y: -22.0 })),
(5, Some(Vec2 { x: 27.0, y: -15.0 })),
(6, Some(Vec2 { x: 38.0, y: -4.0 })),
(7, Some(Vec2 { x: 44.0, y: 2.0 })),
(8, Some(Vec2 { x: 44.0, y: -1.0 })),
(9, Some(Vec2 { x: 44.0, y: 2.0 })),
(10, Some(Vec2 { x: 29.0, y: 4.0 })),
(11, Some(Vec2 { x: 18.0, y: 4.0 })),
(12, Some(Vec2 { x: 9.0, y: 4.0 })),
(13, Some(Vec2 { x: -4.0, y: -4.0 })),
(14, Some(Vec2 { x: -11.0, y: -12.0 })),
(15, Some(Vec2 { x: -11.0, y: -10.0 })),
(16, None),
(17, Some(Vec2 { x: 44.0, y: -25.0 })),
(18, Some(Vec2 { x: 44.0, y: -12.0 })),
(19, Some(Vec2 { x: 44.0, y: -20.0 })),
(20, Some(Vec2 { x: 39.0, y: -38.0 })),
(21, Some(Vec2 { x: 26.0, y: -50.0 })),
(22, Some(Vec2 { x: 16.0, y: -50.0 })),
(23, Some(Vec2 { x: -5.0, y: -50.0 })),
(24, Some(Vec2 { x: -13.0, y: -21.0 })),
(25, Some(Vec2 { x: -13.0, y: 1.0 })),
(26, Some(Vec2 { x: -13.0, y: 11.0 })),
(27, Some(Vec2 { x: -13.0, y: 16.0 })),
(28, Some(Vec2 { x: -13.0, y: 16.0 })),
(29, None),
(30, Some(Vec2 { x: 0.0, y: 42.0 })),
(31, None),
(32, Some(Vec2 { x: 36.0, y: 0.0 })),
(33, None),
(34, None),
]
)
}
#[test]
fn iup_delta_optimize_gs_glyph_uppercase_c() {
let deltas: Vec<_> = vec![
(2.0, 0.0),
(4.0, 0.0),
(8.0, -1.0),
(10.0, -1.0),
(10.0, 0.0),
(-14.0, 25.0),
(-8.0, 34.0),
(-3.0, 38.0),
(-5.0, 35.0),
(-7.0, 35.0),
(6.0, 35.0),
(22.0, 27.0),
(29.0, 11.0),
(29.0, -1.0),
(29.0, -13.0),
(22.0, -29.0),
(8.0, -37.0),
(-3.0, -37.0),
(0.0, -37.0),
(1.0, -43.0),
(-7.0, -41.0),
(-19.0, -28.0),
(8.0, 0.0),
(8.0, 0.0),
(6.0, 0.0),
(4.0, 0.0),
(2.0, 0.0),
(0.0, 0.0),
(-5.0, 0.0),
(-8.0, 0.0),
(-10.0, 0.0),
(-10.0, 0.0),
(-8.0, 0.0),
(-5.0, 0.0),
(-1.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect();
let coords: Vec<_> = vec![
(416.0, -16.0),
(476.0, -16.0),
(581.0, 17.0),
(668.0, 75.0),
(699.0, 112.0),
(637.0, 172.0),
(609.0, 139.0),
(542.0, 91.0),
(463.0, 65.0),
(416.0, 65.0),
(339.0, 65.0),
(209.0, 137.0),
(131.0, 269.0),
(131.0, 358.0),
(131.0, 448.0),
(209.0, 579.0),
(339.0, 651.0),
(416.0, 651.0),
(458.0, 651.0),
(529.0, 631.0),
(590.0, 589.0),
(617.0, 556.0),
(678.0, 615.0),
(646.0, 652.0),
(566.0, 704.0),
(471.0, 732.0),
(416.0, 732.0),
(337.0, 732.0),
(202.0, 675.0),
(101.0, 574.0),
(45.0, 438.0),
(45.0, 279.0),
(101.0, 142.0),
(202.0, 41.0),
(337.0, -16.0),
(0.0, 0.0),
(741.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|c| c.into())
.collect();
let tolerance = 0.5;
let contour_ends = vec![coords.len() - 1 - 4];
let result = iup_delta_optimize(deltas.clone(), coords, tolerance, &contour_ends).unwrap();
let result = make_vec_of_options(&result);
assert_eq!(
result,
vec![
(0, None),
(1, Some(Vec2 { x: 4.0, y: 0.0 })),
(2, Some(Vec2 { x: 8.0, y: -1.0 })),
(3, Some(Vec2 { x: 10.0, y: -1.0 })),
(4, Some(Vec2 { x: 10.0, y: 0.0 })),
(5, Some(Vec2 { x: -14.0, y: 25.0 })),
(6, Some(Vec2 { x: -8.0, y: 34.0 })),
(7, Some(Vec2 { x: -3.0, y: 38.0 })),
(8, Some(Vec2 { x: -5.0, y: 35.0 })),
(9, Some(Vec2 { x: -7.0, y: 35.0 })),
(10, Some(Vec2 { x: 6.0, y: 35.0 })),
(11, Some(Vec2 { x: 22.0, y: 27.0 })),
(12, Some(Vec2 { x: 29.0, y: 11.0 })),
(13, None),
(14, Some(Vec2 { x: 29.0, y: -13.0 })),
(15, Some(Vec2 { x: 22.0, y: -29.0 })),
(16, Some(Vec2 { x: 8.0, y: -37.0 })),
(17, Some(Vec2 { x: -3.0, y: -37.0 })),
(18, Some(Vec2 { x: 0.0, y: -37.0 })),
(19, Some(Vec2 { x: 1.0, y: -43.0 })),
(20, Some(Vec2 { x: -7.0, y: -41.0 })),
(21, Some(Vec2 { x: -19.0, y: -28.0 })),
(22, Some(Vec2 { x: 8.0, y: 0.0 })),
(23, Some(Vec2 { x: 8.0, y: 0.0 })),
(24, None),
(25, Some(Vec2 { x: 4.0, y: 0.0 })),
(26, None),
(27, None),
(28, None),
(29, None),
(30, None),
(31, Some(Vec2 { x: -10.0, y: 0.0 })),
(32, None),
(33, None),
(34, None),
(35, None),
(36, None),
(37, None),
(38, None),
]
)
}
#[test]
fn bug_662() {
let deltas = vec![
Vec2 { x: 73.0, y: 0.0 },
Vec2 { x: 158.0, y: 448.0 },
Vec2 { x: 154.0, y: 448.0 },
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 765.0, y: 0.0 },
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 0.0, y: 0.0 },
];
let coords = [
(73.0, 0.0),
(158.0, 448.0),
(154.0, 448.0),
(0.0, 0.0),
(765.0, 0.0),
(0.0, 0.0),
(0.0, 0.0),
]
.into_iter()
.map(|(x, y)| Point::new(x, y))
.collect::<Vec<_>>();
let contour_ends = vec![2];
iup_delta_optimize(deltas, coords, 0.5, &contour_ends).unwrap();
}
#[test]
fn bug_fontc_1116() {
let mut deltas = vec![
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 0.0, y: 3.0 },
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 2.0, y: 3.0 },
Vec2 { x: 2.0, y: 0.0 },
Vec2 { x: 1.0, y: 3.0 },
Vec2 { x: 1.0, y: 0.0 },
Vec2 { x: 0.0, y: 0.0 },
Vec2 { x: 0.0, y: 1.0 },
Vec2 { x: 1.0, y: 0.0 },
Vec2 { x: 1.0, y: 0.0 },
];
let mut coords = [
(0.0, 0.0),
(0.0, 3.0),
(0.0, 0.0),
(0.0, 0.0),
(2.0, 3.0),
(2.0, 0.0),
(1.0, 3.0),
(1.0, 0.0),
(0.0, 0.0),
(0.0, 1.0),
(1.0, 0.0),
(1.0, 0.0),
]
.into_iter()
.map(|(x, y)| Point::new(x, y))
.collect::<Vec<_>>();
iup_contour_optimize(&mut deltas, &mut coords, 0.5).unwrap();
}
}