use crate::types::splash_round;
#[derive(Clone, Debug)]
pub struct XPathAdjust {
pub first_pt: usize,
pub last_pt: usize,
pub vert: bool,
pub x0a: f64,
pub x0b: f64,
pub x0: f64,
pub xma: f64,
pub xmb: f64,
pub xm: f64,
pub x1a: f64,
pub x1b: f64,
pub x1: f64,
}
impl XPathAdjust {
#[must_use]
pub fn new(
first_pt: usize,
last_pt: usize,
vert: bool,
adj0: f64,
adj1: f64,
adjust_lines: bool,
line_pos_i: i32,
) -> Self {
debug_assert!(adj0 <= adj1, "adj0 must be <= adj1");
let mid = adj0.midpoint(adj1);
let mut r0 = f64::from(splash_round(adj0));
let mut r1 = f64::from(splash_round(adj1));
if r0.to_bits() == r1.to_bits() {
if adjust_lines {
r0 = f64::from(line_pos_i);
r1 = f64::from(line_pos_i) + 1.0;
} else {
r1 += 1.0;
}
}
Self {
first_pt,
last_pt,
vert,
x0a: adj0 - 0.01,
x0b: adj0 + 0.01,
x0: r0,
xma: mid - 0.01,
xmb: mid + 0.01,
xm: r0.midpoint(r1),
x1a: adj1 - 0.01,
x1b: adj1 + 0.01,
x1: r1 - 0.01,
}
}
}
#[inline]
pub fn stroke_adjust(adj: &XPathAdjust, x: &mut f64, y: &mut f64) {
let v = if adj.vert { x } else { y };
if *v > adj.x0a && *v < adj.x0b {
*v = adj.x0;
} else if *v > adj.xma && *v < adj.xmb {
*v = adj.xm;
} else if *v > adj.x1a && *v < adj.x1b {
*v = adj.x1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snaps_to_x0() {
let adj = XPathAdjust::new(0, 1, true, 1.0, 3.0, false, 0);
let mut x = 1.005;
let mut y = 0.0;
stroke_adjust(&adj, &mut x, &mut y);
assert!((x - 1.0).abs() < 1e-10, "x={x}");
}
#[test]
fn snaps_to_xm() {
let adj = XPathAdjust::new(0, 1, true, 1.0, 3.0, false, 0);
let mut x = 2.0; let mut y = 0.0;
stroke_adjust(&adj, &mut x, &mut y);
assert!((x - 2.0).abs() < 1e-10, "x={x}"); }
#[test]
fn snaps_to_x1() {
let adj = XPathAdjust::new(0, 1, true, 1.0, 3.0, false, 0);
let mut x = 3.005;
let mut y = 0.0;
stroke_adjust(&adj, &mut x, &mut y);
assert!((x - 2.99).abs() < 1e-10, "x={x}");
}
#[test]
fn no_snap_outside_windows() {
let adj = XPathAdjust::new(0, 1, true, 1.0, 3.0, false, 0);
let mut x = 5.0;
let mut y = 0.0;
stroke_adjust(&adj, &mut x, &mut y);
assert!((x - 5.0).abs() < 1e-10);
}
#[test]
fn horizontal_adjusts_y() {
let adj = XPathAdjust::new(0, 1, false, 2.0, 4.0, false, 0);
let mut x = 0.0;
let mut y = 2.005;
stroke_adjust(&adj, &mut x, &mut y);
assert!((y - 2.0).abs() < 1e-10, "y={y}");
assert!((x - 0.0).abs() < 1e-10); }
#[test]
fn snaps_only_within_open_interval() {
let adj = XPathAdjust::new(0, 1, true, 1.0, 3.0, false, 0);
let boundary = adj.x0a;
let mut x = boundary;
let mut y = 0.0;
stroke_adjust(&adj, &mut x, &mut y);
assert!(
(x - boundary).abs() < 1e-15,
"coordinate exactly at x0a boundary must not be snapped, got x={x}"
);
let mut x = adj.x0b;
let boundary_b = adj.x0b;
stroke_adjust(&adj, &mut x, &mut y);
assert!(
(x - boundary_b).abs() < 1e-15,
"coordinate exactly at x0b boundary must not be snapped, got x={x}"
);
}
}