From a107e62bcc283d837109a20d9e40202acad97b7c Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:13:55 +0100 Subject: [PATCH] Fix missing end arrowhead when a closed subpath follows an open one Found while testing crates/pathops/src/points.rs:89-92 (Styled::arrowheads) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/pathops/src/lib.rs | 23 +++++++++++++++++++++++ crates/pathops/src/points.rs | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/pathops/src/lib.rs b/crates/pathops/src/lib.rs index f4b30e10e..1d6dd9998 100644 --- a/crates/pathops/src/lib.rs +++ b/crates/pathops/src/lib.rs @@ -693,6 +693,29 @@ mod tests { assert_ne!(explicit, layer_only); } + #[test] + fn layer_end_arrow_is_drawn_when_a_closed_run_follows_the_open_one() { + let verts = [(0.0, 0.0), (50.0, 0.0), (0.0, 100.0), (50.0, 100.0), (25.0, 150.0)]; + let net = VectorNetwork { + vertices: verts + .iter() + .enumerate() + .map(|(i, &(x, y))| figcraft_doc::Vertex { cap: (i == 0).then_some(StrokeCap::Round), ..figcraft_doc::Vertex::new(x, y) }) + .collect(), + segments: vec![ + figcraft_doc::Segment::line(0, 1), + figcraft_doc::Segment::line(2, 3), + figcraft_doc::Segment::line(3, 4), + figcraft_doc::Segment::line(4, 2), + ], + ..Default::default() + }; + let path = BezPath::new(); + let plain = StrokeProps { weight: 2.0, ..Default::default() }; + let arrow = StrokeProps { cap_end: StrokeCap::TriangleArrow, ..plain.clone() }; + assert_ne!(stroke_outline_of(&path, &arrow, false, Some(&net)), stroke_outline_of(&path, &plain, false, Some(&net))); + } + #[test] fn equal_joins_do_not_break_closed_stroke_runs() { let path = sq(0.0, 0.0, 20.0); diff --git a/crates/pathops/src/points.rs b/crates/pathops/src/points.rs index 05f58216d..650f309f4 100644 --- a/crates/pathops/src/points.rs +++ b/crates/pathops/src/points.rs @@ -86,8 +86,8 @@ impl Styled { /// and last end (as without point styles). pub(crate) fn arrowheads(&self, weight: f64, props: &StrokeProps) -> BezPath { let mut out = BezPath::new(); - let last = self.runs.len().saturating_sub(1); - for (k, run) in self.runs.iter().enumerate().filter(|(_, r)| !r.closed) { + let last = self.runs.iter().filter(|r| !r.closed).count().saturating_sub(1); + for (k, run) in self.runs.iter().filter(|r| !r.closed).enumerate() { let start = self.cap_at(run.start, if k == 0 { props.cap_start } else { StrokeCap::None }); let end = self.cap_at(run.end, if k == last { props.cap_end } else { StrokeCap::None }); for (at_end, cap) in [(false, start), (true, end)] {