From fd0be1aca044d5e1fccec374f3fe6bd71c3343a8 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 9 Nov 2026 19:57:27 -0800 Subject: [PATCH] 3D: a profile's empty contours count toward its point limit revolve_curve_samples counted a profile's contours but never compared the count with the limit, so a profile made only of empty or one-anchor subpaths passed, although each contour takes one profile point. HoleSearch::new then built one entry for each subpath without a limit, and mesh export skips revolve_curve_samples when curve_samples is given. Both now refuse a profile with more than 16,384 contours as too complex. --- crates/three-d/src/lib.rs | 8 ++++++-- crates/three-d/src/tests/profile_limits.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/three-d/src/lib.rs b/crates/three-d/src/lib.rs index 479481c2a..fa22c10fb 100644 --- a/crates/three-d/src/lib.rs +++ b/crates/three-d/src/lib.rs @@ -480,10 +480,10 @@ pub fn revolve_curve_samples<'a>(paths: impl IntoIterator, let (mut anchors, mut contours, mut lines, mut curves, mut caps) = (0usize, 0usize, 0usize, 0usize, 0usize); for sp in paths.into_iter().flat_map(|p| &p.subpaths) { anchors = anchors.saturating_add(sp.anchors.len()); - if anchors > MAX_PROFILE_POINTS { + contours += 1; + if anchors > MAX_PROFILE_POINTS || contours > MAX_PROFILE_POINTS { return Err(TOO_COMPLEX_PROFILE.into()); } - contours += 1; let segments = if sp.closed { sp.anchors.len() } else { sp.anchors.len().saturating_sub(1) }; for i in 0..segments { if sp.segment_is_line(i) { @@ -551,6 +551,10 @@ struct HoleSearch { impl HoleSearch { fn new(path: &PathData, rule: FillRule) -> Result { + // Each contour takes a profile point, as in `revolve_curve_samples`. + if path.subpaths.len() > MAX_PROFILE_POINTS { + return Err(TOO_COMPLEX_PROFILE.into()); + } let contours: Vec<(BezPath, Rect, Option)> = path .subpaths .iter() diff --git a/crates/three-d/src/tests/profile_limits.rs b/crates/three-d/src/tests/profile_limits.rs index 5eeb10f2a..148cf4fcc 100644 --- a/crates/three-d/src/tests/profile_limits.rs +++ b/crates/three-d/src/tests/profile_limits.rs @@ -68,6 +68,17 @@ fn too_many_anchors_are_refused_as_a_profile() { assert_eq!(refused, TOO_COMPLEX_PROFILE); } +#[test] +fn every_contour_takes_a_point_even_an_empty_one() { + // Contours of one anchor and of none, with no segment between them. + let lone = |i: usize| SubPath::new(if i.is_multiple_of(2) { vec![] } else { vec![Anchor::corner(Point::new(100.0, i as f64))] }, false); + let path = PathData::new((0..=MAX_PROFILE_POINTS).map(lone).collect()); + assert_eq!(revolve_curve_samples([&path], Revolve::default()), Err(TOO_COMPLEX_PROFILE.to_string())); + assert_eq!(hole_segments(&path, FillRule::NonZero), Err(TOO_COMPLEX_PROFILE.to_string()), "before any contour is built"); + let given = Revolve { curve_samples: Some(CURVE_SAMPLES), ..Revolve::default() }; + assert_eq!(revolve_mesh(&path, FillRule::NonZero, path.bounds().unwrap(), given).map(|_| ()), Err(TOO_COMPLEX_PROFILE.to_string())); +} + #[test] fn a_profile_at_the_point_limit_is_taken_and_one_past_it_is_refused() { let straight = |anchors: usize| {