use super::*;
#[test]
fn evenodd_reuses_paths_that_need_no_reversal() {
use std::borrow::Cow;
let mut builder = PathBuilder::new();
square(&mut builder, 0.0, 0.0, 30.0);
let single = builder.clone().finish().unwrap();
square(&mut builder, 50.0, 0.0, 10.0);
let disjoint = builder.finish().unwrap();
let mut nested = PathBuilder::new();
square(&mut nested, 0.0, 0.0, 30.0);
square(&mut nested, 10.0, 10.0, 10.0);
let nested = nested.finish().unwrap();
let normalized = normalize_evenodd(&nested);
assert!(matches!(normalized, Cow::Owned(_)));
assert_ne!(signs(&normalized)[0], signs(&normalized)[1]);
for path in [&single, &disjoint, &normalized[0]] {
let result = normalize_evenodd(path);
assert!(matches!(result, Cow::Borrowed(_)));
assert!(std::ptr::eq(&result[0], path));
}
}
fn signs(paths: &[TinyPath]) -> Vec<i8> {
let mut out = Vec::new();
for path in paths {
let mut contours = Vec::new();
decompose(path, &mut contours);
for mut c in contours {
flatten_contour(&mut c);
out.push(sign(c.area2));
}
}
out
}
fn square(b: &mut PathBuilder, x: f32, y: f32, s: f32) {
b.move_to(x, y);
b.line_to(x, y + s);
b.line_to(x + s, y + s);
b.line_to(x + s, y);
b.close();
}
fn segment_repr(paths: &[TinyPath]) -> Vec<String> {
let mut out = Vec::new();
for path in paths {
for seg in path.segments() {
out.push(match seg {
PathSegment::MoveTo(p) => format!("M {} {}", p.x, p.y),
PathSegment::LineTo(p) => format!("L {} {}", p.x, p.y),
PathSegment::QuadTo(c, p) => format!("Q {} {} {} {}", c.x, c.y, p.x, p.y),
PathSegment::CubicTo(a, b, p) => {
format!("C {} {} {} {} {} {}", a.x, a.y, b.x, b.y, p.x, p.y)
}
PathSegment::Close => "Z".to_string(),
});
}
}
out
}
#[test]
fn nested_same_wound_contour_is_reversed_into_a_hole() {
let mut b = PathBuilder::new();
square(&mut b, 0.0, 0.0, 30.0); square(&mut b, 10.0, 10.0, 10.0); let out = normalize_winding(vec![b.finish().unwrap()]);
let s = signs(&out);
assert_eq!(s.len(), 2);
assert_ne!(
s[0], s[1],
"a contained same-wound contour must become an opposite-wound hole"
);
}
#[test]
fn overlapping_but_unnested_contours_stay_unioned() {
let mut b = PathBuilder::new();
square(&mut b, 0.0, 0.0, 30.0); square(&mut b, 20.0, 20.0, 30.0); let out = normalize_winding(vec![b.finish().unwrap()]);
let s = signs(&out);
assert_eq!(s.len(), 2);
assert_eq!(
s[0], s[1],
"overlapping non-nested contours must stay same-wound (union, not a hole)"
);
}
#[test]
fn concave_outer_rejects_inner_edges_that_cross_outside() {
let mut b = PathBuilder::new();
b.move_to(0.0, 0.0);
b.line_to(0.0, 30.0);
b.line_to(10.0, 30.0);
b.line_to(10.0, 10.0);
b.line_to(20.0, 10.0);
b.line_to(20.0, 30.0);
b.line_to(30.0, 30.0);
b.line_to(30.0, 0.0);
b.close();
b.move_to(5.0, 15.0);
b.line_to(5.0, 25.0);
b.line_to(25.0, 25.0);
b.line_to(25.0, 15.0);
b.close();
let out = normalize_winding(vec![b.finish().unwrap()]);
let s = signs(&out);
assert_eq!(s.len(), 2);
assert_eq!(s[0], s[1], "crossing edges must keep the contours unioned");
}
#[test]
fn already_correct_hole_is_left_unchanged() {
let mut b = PathBuilder::new();
b.move_to(0.0, 0.0);
b.line_to(0.0, 30.0);
b.line_to(30.0, 30.0);
b.line_to(30.0, 0.0);
b.close();
b.move_to(10.0, 10.0);
b.line_to(20.0, 10.0);
b.line_to(20.0, 20.0);
b.line_to(10.0, 20.0);
b.close();
let input = b.finish().unwrap();
let before = signs(std::slice::from_ref(&input));
let after = signs(&normalize_winding(vec![input]));
assert_eq!(before, after, "an already-correct hole must be idempotent");
assert_ne!(after[0], after[1]);
}
#[test]
fn single_contour_is_untouched() {
assert!(normalize_winding(Vec::new()).is_empty());
let mut b = PathBuilder::new();
square(&mut b, 0.0, 0.0, 30.0);
let input = b.finish().unwrap();
assert_eq!(
signs(&normalize_winding(vec![input.clone()])),
signs(std::slice::from_ref(&input))
);
}
#[test]
fn noop_inputs_preserve_exact_segments() {
let mut single = PathBuilder::new();
square(&mut single, 0.0, 0.0, 30.0);
let mut open = PathBuilder::new();
open.move_to(0.0, 0.0);
open.line_to(30.0, 30.0);
let mut move_only = PathBuilder::new();
move_only.move_to(50.0, 50.0);
square(&mut move_only, 0.0, 0.0, 30.0);
let mut overlap = PathBuilder::new();
square(&mut overlap, 0.0, 0.0, 30.0);
square(&mut overlap, 20.0, 20.0, 30.0);
let mut hole = PathBuilder::new();
hole.move_to(0.0, 0.0);
hole.line_to(0.0, 30.0);
hole.line_to(30.0, 30.0);
hole.line_to(30.0, 0.0);
hole.close();
hole.move_to(10.0, 10.0);
hole.line_to(20.0, 10.0);
hole.line_to(20.0, 20.0);
hole.line_to(10.0, 20.0);
hole.close();
for input in [
single.finish().unwrap(),
open.finish().unwrap(),
move_only.finish().unwrap(),
overlap.finish().unwrap(),
hole.finish().unwrap(),
] {
let before = segment_repr(std::slice::from_ref(&input));
let after = segment_repr(&normalize_winding(vec![input]));
assert_eq!(
before, after,
"a glyph needing no reversal must be returned byte-identical"
);
}
}
#[test]
fn nested_contours_across_paths_are_normalized() {
let mut outer = PathBuilder::new();
square(&mut outer, 0.0, 0.0, 30.0);
let mut inner = PathBuilder::new();
square(&mut inner, 10.0, 10.0, 10.0);
let signs = signs(&normalize_winding(vec![
outer.finish().unwrap(),
inner.finish().unwrap(),
]));
assert_eq!(signs.len(), 2);
assert_ne!(signs[0], signs[1]);
}
#[test]
fn multi_level_nesting_alternates() {
let mut b = PathBuilder::new();
square(&mut b, 0.0, 0.0, 40.0);
square(&mut b, 5.0, 5.0, 30.0);
square(&mut b, 10.0, 10.0, 20.0);
square(&mut b, 15.0, 15.0, 10.0);
let s = signs(&normalize_winding(vec![b.finish().unwrap()]));
assert_eq!(s.len(), 4);
assert_ne!(s[0], s[1], "depth 0 vs 1");
assert_ne!(s[1], s[2], "depth 1 vs 2");
assert_ne!(s[2], s[3], "depth 2 vs 3");
assert_eq!(s[0], s[2], "even depths share orientation");
assert_eq!(s[1], s[3], "odd depths share orientation");
}