From 1935c3eac22b9c4dc62e278ef56fd0f036001c5e Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 16:03:10 +0000 Subject: [PATCH 1/2] layout: bound the snap lines a frame's grids generate --- crates/layout/src/depth_tests.rs | 12 ++++++++++++ crates/layout/src/lib.rs | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/crates/layout/src/depth_tests.rs b/crates/layout/src/depth_tests.rs index f8b597a34..b533c7e42 100644 --- a/crates/layout/src/depth_tests.rs +++ b/crates/layout/src/depth_tests.rs @@ -93,3 +93,15 @@ fn deeply_nested_fill_frames_lay_out_without_running_out_of_stack() { assert_eq!(width(&d, chain[DEEP - 1]), 10.0, "past the nesting the layout follows, layers stay"); }); } + +#[test] +fn snap_lines_are_bounded_across_a_frames_grids() { + use figcraft_doc::{Document, GridKind, LayoutGrid, NodeKind}; + let mut d = Document::new("t"); + let p = d.first_page().unwrap(); + let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 4000.0, 4000.0).unwrap(); + d.get_mut(f).unwrap().grids = vec![LayoutGrid { kind: GridKind::Grid, size: 1.0, visible: true, ..LayoutGrid::default() }; 1000]; + let (xs, ys) = crate::grid_snap_lines(&d, f); + assert!(xs.len() + ys.len() < 250_000, "{}", xs.len() + ys.len()); + assert!(xs.len() > 4000 && ys.len() > 4000, "the first grids still give their lines"); +} diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 880c6dad7..04abbc39a 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -1159,6 +1159,9 @@ pub fn grid_snap_lines(doc: &Document, frame: NodeId) -> (Vec, Vec) { grid_snap_lines_in(doc, frame, doc.world_transform(frame)) } +/// The most snap coordinates a frame's grids give together (a few dozen grids' worth of lines). +const MAX_SNAP_LINES: usize = 200_000; + /// [`grid_snap_lines`] mapped from the frame's coordinates by `xf` (the frame's world transform /// for page lines). pub fn grid_snap_lines_in(doc: &Document, frame: NodeId, xf: Affine) -> (Vec, Vec) { @@ -1166,6 +1169,10 @@ pub fn grid_snap_lines_in(doc: &Document, frame: NodeId, xf: Affine) -> (Vec= MAX_SNAP_LINES { + break; + } for r in grid_lines(g, f.width, f.height) { let a = xf * Point::new(r.x0, r.y0); let b = xf * Point::new(r.x1, r.y1); From 9d48625995fc63c99e0164e424f78bcea3ac8a47 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 18:57:25 +0000 Subject: [PATCH 2/2] layout: leave out a grid that would pass the snap budget --- crates/layout/src/depth_tests.rs | 2 +- crates/layout/src/lib.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/layout/src/depth_tests.rs b/crates/layout/src/depth_tests.rs index b533c7e42..20e022817 100644 --- a/crates/layout/src/depth_tests.rs +++ b/crates/layout/src/depth_tests.rs @@ -102,6 +102,6 @@ fn snap_lines_are_bounded_across_a_frames_grids() { let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 4000.0, 4000.0).unwrap(); d.get_mut(f).unwrap().grids = vec![LayoutGrid { kind: GridKind::Grid, size: 1.0, visible: true, ..LayoutGrid::default() }; 1000]; let (xs, ys) = crate::grid_snap_lines(&d, f); - assert!(xs.len() + ys.len() < 250_000, "{}", xs.len() + ys.len()); + assert!(xs.len() + ys.len() <= 200_000, "{}", xs.len() + ys.len()); assert!(xs.len() > 4000 && ys.len() > 4000, "the first grids still give their lines"); } diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 04abbc39a..b3d8e27f0 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -1169,11 +1169,13 @@ pub fn grid_snap_lines_in(doc: &Document, frame: NodeId, xf: Affine) -> (Vec= MAX_SNAP_LINES { + // A frame-wide budget: each grid adds up to ~17,000 lines, and a file can hold any number of grids; + // a grid that would pass it is left out whole. + let lines = grid_lines(g, f.width, f.height); + if xs.len() + ys.len() + 2 * lines.len() > MAX_SNAP_LINES { break; } - for r in grid_lines(g, f.width, f.height) { + for r in lines { let a = xf * Point::new(r.x0, r.y0); let b = xf * Point::new(r.x1, r.y1); match g.kind {