diff --git a/crates/engine/src/cmd/addons/cutting/weed/geometry.rs b/crates/engine/src/cmd/addons/cutting/weed/geometry.rs index cf7df51d80..203329fe51 100644 --- a/crates/engine/src/cmd/addons/cutting/weed/geometry.rs +++ b/crates/engine/src/cmd/addons/cutting/weed/geometry.rs @@ -209,6 +209,10 @@ impl<'a> Reader<'a> { /// A path, compound path or type as its outlines. fn shape(&mut self, n: &Node) -> Result, String> { + // Each anchor is at least one point, so a path past the budget errs before it is copied. + if anchors(n) > MAX_POINTS.saturating_sub(self.points) { + return Err(too_many()); + } let Some((path, rule)) = node_path(n) else { return Ok(None) }; let polys = self.polys(path.to_bezpath())?; Ok((!polys.is_empty()).then_some(Region::Shape { polys, rule })) @@ -269,6 +273,15 @@ fn too_many() -> String { format!("the selected art has too many outline points to measure (more than {MAX_POINTS})") } +/// The anchors of a path or compound path node, read without copying its path; 0 for type. +fn anchors(n: &Node) -> usize { + match &n.kind { + NodeKind::Path { path, .. } => path.anchor_count(), + NodeKind::Compound { children, .. } => children.iter().fold(0, |sum, c| sum.saturating_add(anchors(c))), + _ => 0, + } +} + /// A subpath through `points`, `None` for fewer than two points or one that isn't finite. fn poly(points: Vec, closed: bool) -> Option { if points.len() < 2 || points.iter().any(|p| !p.x.is_finite() || !p.y.is_finite()) {