From 51bab664091a9faba3ffceed6ce6c8e580febd25 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 05:42:05 -0800 Subject: [PATCH] Envelopes: document how a straight segment's handles count in the top object corner test A top object's corner test treats a straight segment as having its handles a third of the way along it, so an anchor between two straight segments is a corner only when it turns out of the outline. docs/mcp.md and the doc comment now say so, and a new test covers an inward anchor between straight segments. The bulging-curve corner test checks against a fixed point instead of the same nearest-point routine the code uses. --- crates/doc/src/live.rs | 19 ++++++++++++++----- crates/doc/src/live/mesh.rs | 5 ++++- docs/mcp.md | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/doc/src/live.rs b/crates/doc/src/live.rs index 24c6edc5a..4a864a6e9 100644 --- a/crates/doc/src/live.rs +++ b/crates/doc/src/live.rs @@ -262,18 +262,17 @@ mod tests { fn a_top_object_corner_inside_a_bulging_curve_is_the_nearest_outline_point() { // The top left anchor's handle bulges out past the corner, so the anchor turns inward // there and the corner falls inside the curve. - let top = CubicBez::new((0.0, 0.0), (-50.0, -50.0), (150.0, 0.0), (200.0, 0.0)); + // The outline's bounding box starts at (-7.957, -22.222); the top curve's point nearest + // that corner, found by sampling the curve 2,000,000 times, is (-5.072093, -18.127254). let path = outline(&[ ((0.0, 0.0), (0.0, 0.0), (-50.0, -50.0)), ((200.0, 0.0), (150.0, 0.0), (200.0, 0.0)), ((200.0, 100.0), (200.0, 100.0), (200.0, 100.0)), ((0.0, 100.0), (0.0, 100.0), (0.0, 100.0)), ]); - use vectorcraft_geom::kurbo::ParamCurveNearest; - let b = path.subpaths[0].segment(3).bounding_box().union(top.bounding_box()); - let want = top.eval(top.nearest(Point::new(b.x0, b.y0), 1e-12).t); + let want = Point::new(-5.072093, -18.127254); let tl = Coons::from_top_object(&path, None).unwrap().corners()[0]; - assert!(tl.distance(want) < 1e-6, "{tl:?}, want {want:?}"); + assert!(tl.distance(want) < 1e-5, "{tl:?}, want {want:?}"); assert!(tl.distance(Point::ZERO) > 1.0, "{tl:?}"); } @@ -316,6 +315,16 @@ mod tests { } } + #[test] + fn a_top_object_corner_skips_an_inward_anchor_between_straight_segments() { + // A 200 × 100 outline of straight segments with a notch at its top left: (40,40) is the + // anchor nearest the box corner, but it turns into the outline, so (60,0) is the corner. + let c = |p: (f64, f64)| (p, p, p); + let path = outline(&[c((60.0, 0.0)), c((200.0, 0.0)), c((200.0, 100.0)), c((0.0, 100.0)), c((0.0, 60.0)), c((40.0, 40.0))]); + let tl = Coons::from_top_object(&path, None).unwrap().corners()[0]; + assert!(tl.distance(Point::new(60.0, 0.0)) < 1e-9, "{tl:?}"); + } + #[test] fn a_top_object_corner_tie_goes_to_the_first_anchor() { // Both chamfer anchors lie 30 from the box corner: the first one along the outline, in diff --git a/crates/doc/src/live/mesh.rs b/crates/doc/src/live/mesh.rs index 899939d9c..ca08a3a2a 100644 --- a/crates/doc/src/live/mesh.rs +++ b/crates/doc/src/live/mesh.rs @@ -354,7 +354,10 @@ const CORNER_END_SNAP: f64 = 0.005; /// Is the anchor at `a`, reached by handle `h_in` and left by handle `h_out` on an outline that /// runs clockwise on screen, a corner of a top object? A handle of [`SHORT_HANDLE`] or shorter /// makes it one. Otherwise its tangent directions must differ by [`SMOOTH_TURN`] or more and turn -/// clockwise, so it points out of the outline. +/// clockwise, so it points out of the outline. A straight segment's handles here are the ones +/// [`Coons::from_top_object`] gives it, a third of the way along, so an anchor between two +/// straight segments longer than three times [`SHORT_HANDLE`] is a corner only when it turns out +/// of the outline. fn top_object_corner_anchor(h_in: Point, a: Point, h_out: Point) -> bool { let (vin, vout) = (a - h_in, h_out - a); let (len_in, len_out) = (vin.hypot(), vout.hypot()); diff --git a/docs/mcp.md b/docs/mcp.md index 1af872bbb..6b34a088e 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -6619,7 +6619,9 @@ arcLower, arcUpper, arch, bulge, shellLower, shellUpper, flag, wave, fish, rise, A top object's outline is split into four sides at four corners. Each corner is the anchor nearest a corner of the outline's bounding box among the anchors within 47.5% of the box's width and height from that box corner that count as corners: an anchor with a handle 0.01 pt long or shorter, or one whose tangent unit vectors differ by 0.01 or more in -x or y and turn out of the outline. On a tie, the anchor met first along the outline in the order it was drawn wins. +x or y and turn out of the outline. In this test a straight segment counts as having its handles a third of the way +along it, so an anchor between two straight segments longer than 0.03 pt is a corner only when it turns out of the +outline. On a tie, the anchor met first along the outline in the order it was drawn wins. Without such an anchor, the corner is the outline's point nearest the box corner, moved onto the anchor at the end of its segment when within 0.5% of the segment's parameter. Each piece of a side between anchors and corners takes a share of the content's width or height in proportion to the length of its control polygon, spread along the piece by