diff --git a/apps/vectorcraft-cli/src/perf.rs b/apps/vectorcraft-cli/src/perf.rs index b77222834..3ff82512d 100644 --- a/apps/vectorcraft-cli/src/perf.rs +++ b/apps/vectorcraft-cli/src/perf.rs @@ -214,12 +214,13 @@ fn pointing(n: usize, rows: &mut Vec<(String, f64)>) -> Result<(), String> { format!("smart guide targets in view ({n} paths)"), median_ms(5, || drop(vectorcraft_engine::tools::guides::Targets::collect(&doc, &[], Some(view_rect)))), )); - // Shift-clicking one more object into a selection of half the document. + // Adding one more object to a selection of half the document: the command a shift-click ends + // in, without the pointer handling and hit test before it. let ids: Vec = doc.layers[0].children().map(|c| c.iter().map(|n| n.id.0).collect()).unwrap_or_default(); s.execute("select.set", &json!({"ids": ids[..ids.len() / 2]})).map_err(err)?; let mut k = ids.len() / 2; rows.push(( - format!("shift-click into a {}-object selection", ids.len() / 2), + format!("select.toggle in a {}-object selection", ids.len() / 2), median_ms(5, || { k += 1; let _ = s.execute("select.toggle", &json!({"id": ids[k % ids.len()]})); diff --git a/crates/geom/src/path.rs b/crates/geom/src/path.rs index b0b9f3cbe..59a8f3066 100644 --- a/crates/geom/src/path.rs +++ b/crates/geom/src/path.rs @@ -562,13 +562,21 @@ impl PathData { } let mut bbox: Option = None; for sp in &self.subpaths { - // (a closed subpath's last segment ends on its first anchor: its `ClosePath` adds no line) for i in 0..sp.segment_count() { let c = sp.segment(i); // `to_bezpath_into` writes a line where neither handle is out, else a cubic. let seg = segment_bounds(if sp.segment_is_line(i) { PathSeg::Line(kurbo::Line::new(c.p0, c.p3)) } else { PathSeg::Cubic(c) }); bbox = Some(bbox.map_or(seg, |bb| bb.union(seg))); } + // A closed subpath's last segment ends on its first anchor, so its `ClosePath` adds no + // line, unless that anchor is NaN: it then differs from itself, and kurbo closes with one. + if sp.closed + && let Some(a) = sp.anchors.first() + && (a.p.x.is_nan() || a.p.y.is_nan()) + { + let seg = segment_bounds(PathSeg::Line(kurbo::Line::new(a.p, a.p))); + bbox = Some(bbox.map_or(seg, |bb| bb.union(seg))); + } } Some(bbox.unwrap_or_default()) } @@ -764,6 +772,15 @@ mod tests { let lone = PathData::new(vec![SubPath::polyline(&[Point::new(5.0, 5.0)], false), SubPath::polyline(&[Point::new(9.0, 1.0)], true)]); assert_eq!(lone.bounds(), Some(Rect::ZERO)); assert_eq!(bits(lone.bounds()), bits(reference(&lone))); + // A NaN anchor differs from itself: kurbo closes such a subpath with a line of its own. + let nan = Point::new(f64::NAN, 1.0); + let closed = |pts: &[Point]| SubPath::polyline(pts, true); + for pd in [ + PathData::new(vec![closed(&[nan]), closed(&[Point::new(5.0, 5.0)])]), + PathData::new(vec![closed(&[nan, Point::new(3.0, 4.0), Point::new(6.0, 1.0)])]), + ] { + assert_eq!(bits(pd.bounds()), bits(reference(&pd))); + } } fn square() -> PathData {