Skip to content

Commit 4bb35b9

Browse files
authored
Warning on unhandled fill behavior (#123)
* Warning on even-odd intersecting path * Self intersecting star test
1 parent 559de5b commit 4bb35b9

1 file changed

Lines changed: 75 additions & 1 deletion

File tree

star/src/turtle/elements/fill.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use log::warn;
12
use lyon_geom::{LineSegment, Point};
23

34
use crate::turtle::elements::{DrawCommand, FillPolygon, FillRule, Stroke};
@@ -161,6 +162,25 @@ pub(crate) fn into_fill_polygons(subpaths: Vec<Stroke>, fill_rule: FillRule) ->
161162
})
162163
.collect();
163164

165+
// TODO: EvenOdd fill for overlapping but non-nested subpaths isn't handled.
166+
// We need to XOR without flattening somehow.
167+
if fill_rule == FillRule::EvenOdd {
168+
let bboxes: Vec<_> = subpaths.iter().map(|s| s.bounding_box()).collect();
169+
for i in 0..subpaths.len() {
170+
for j in (i + 1)..subpaths.len() {
171+
let boxes_overlap = bboxes[i].intersects(&bboxes[j]);
172+
let neither_contains_the_other =
173+
!containers[i].contains(&j) && !containers[j].contains(&i);
174+
if boxes_overlap && neither_contains_the_other {
175+
warn!(
176+
"`even-odd` fill rule with overlapping (non-nested) subpaths is not implemented! this won't be drawn correctly"
177+
);
178+
break;
179+
}
180+
}
181+
}
182+
}
183+
164184
// Classify each subpath as outer (contributes filled area) or hole (removes it).
165185
let is_outer: Vec<Option<bool>> = match fill_rule {
166186
FillRule::EvenOdd => containers
@@ -186,7 +206,8 @@ pub(crate) fn into_fill_polygons(subpaths: Vec<Stroke>, fill_rule: FillRule) ->
186206
if winding_inside == 0 {
187207
Some(false)
188208
} else {
189-
// Ignore (why?)
209+
// Attempting to fill a region inside an already-filled region.
210+
// If `winding_inside` was zero, it would be a hole.
190211
None
191212
}
192213
}
@@ -363,4 +384,57 @@ mod tests {
363384
// they should be classified as two independent outer contours.
364385
assert_eq!(polygons.len(), 2);
365386
}
387+
388+
/// Pentagram (5-pointed star) drawn as a single self-intersecting M…Z subpath
389+
///
390+
/// The path visits the five outer tips in "skip-one" order, crossing itself five times
391+
/// and enclosing an inner pentagon whose fill depends on the fill rule:
392+
///
393+
/// - EvenOdd: hole in the center (incorrect currently)
394+
/// - NonZero: filled
395+
#[test]
396+
#[ignore = "self-intersecting single subpaths are not yet handled"]
397+
fn test_self_intersecting_pentagram() {
398+
let star = Stroke::new(
399+
Point::new(110.0, 45.0),
400+
vec![
401+
DrawCommand::LineTo {
402+
from: Point::new(110.0, 45.0),
403+
to: Point::new(162.0, 195.0),
404+
},
405+
DrawCommand::LineTo {
406+
from: Point::new(162.0, 195.0),
407+
to: Point::new(24.0, 100.0),
408+
},
409+
DrawCommand::LineTo {
410+
from: Point::new(24.0, 100.0),
411+
to: Point::new(196.0, 100.0),
412+
},
413+
DrawCommand::LineTo {
414+
from: Point::new(196.0, 100.0),
415+
to: Point::new(58.0, 195.0),
416+
},
417+
DrawCommand::LineTo {
418+
from: Point::new(58.0, 195.0),
419+
to: Point::new(110.0, 45.0),
420+
},
421+
],
422+
);
423+
424+
let evenodd = into_fill_polygons(vec![star.clone()], FillRule::EvenOdd);
425+
assert_eq!(evenodd.len(), 1, "EvenOdd: expected one outer contour");
426+
assert_eq!(
427+
evenodd[0].holes.len(),
428+
1,
429+
"EvenOdd: expected one hole (the inner pentagon)"
430+
);
431+
432+
let nonzero = into_fill_polygons(vec![star], FillRule::NonZero);
433+
assert_eq!(nonzero.len(), 1, "NonZero: expected one outer contour");
434+
assert_eq!(
435+
nonzero[0].holes.len(),
436+
0,
437+
"NonZero: expected no holes (inner pentagon is filled)"
438+
);
439+
}
366440
}

0 commit comments

Comments
 (0)