From ecdc90cdc45fe26fd3370bdf79d538521ec9cfdd Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Mon, 26 Oct 2026 03:15:35 +0000 Subject: [PATCH] =?UTF-8?q?#3866:=20review=20=E2=80=94=20port=20the=20clip?= =?UTF-8?q?ped-cell=20lookup=20test=20(nesting,=20first=20in=20page=20orde?= =?UTF-8?q?r,=20other=20story/row)=20to=20the=20layout=20crate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/layout/src/display.rs | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/layout/src/display.rs b/crates/layout/src/display.rs index 827ad8f73..f537790f9 100644 --- a/crates/layout/src/display.rs +++ b/crates/layout/src/display.rs @@ -2188,3 +2188,53 @@ fn upright_east_asian(items: Vec) -> Vec { } out } + +#[cfg(test)] +mod tests_clips { + use super::*; + use wordcraft_doc::Path as DocPath; + + /// A cell whose area is told apart by `x`. + fn cell(story: StoryRef, table: Vec, row: usize, cell: usize, clip: bool, x: f32) -> Placed { + Placed::Cell { rect: Rect::new(x, 0.0, 1.0, 1.0), covered: false, table: DocPath(table), row, cell, story, clip } + } + + /// Which cell (by its `x`) clips a line at `path` of `story`. + fn found(clips: &Clips, story: StoryRef, path: &[u32]) -> Option { + cell_clip(clips, story, path).map(|r| r.x) + } + + /// A line in a cell cut off at its edge is drawn with that cell's clip, found by path in an + /// index built once per page: every line used to look through every clipped cell on the page, + /// so a page of exact-height rows cost lines x cells. + #[test] + fn a_line_finds_its_clipped_cell_by_path() { + let mut page = Page { + items: vec![cell(StoryRef::Body, vec![0], 0, 1, false, 1.0), cell(StoryRef::Body, vec![2], 1, 0, true, 2.0)], + ..Default::default() + }; + // A nested clipped cell inside the second one; the outer one comes first in the page. + page.items.push(cell(StoryRef::Body, vec![2, 1, 0, 3], 0, 0, true, 3.0)); + page.footer.push(cell(StoryRef::Part(7), vec![0], 0, 0, true, 4.0)); + // The page's cells in header, footer, items order, as the PDF export and layout see them. + let all: Vec = page.header.iter().chain(&page.footer).chain(&page.items).cloned().collect(); + let clips = clipped_cells(&all); + assert_eq!(found(&clips, StoryRef::Body, &[2, 1, 0, 0]), Some(2.0)); + assert_eq!(found(&clips, StoryRef::Body, &[2, 1, 0, 3, 0, 0, 1]), Some(2.0), "the first cell in page order wins, as in layout"); + assert_eq!(found(&clips, StoryRef::Body, &[0, 0, 1, 0]), None, "that cell isn't clipped"); + assert_eq!(found(&clips, StoryRef::Body, &[2, 0, 0, 0]), None, "another row"); + assert_eq!(found(&clips, StoryRef::Part(7), &[0, 0, 0, 2]), Some(4.0)); + assert_eq!(found(&clips, StoryRef::Body, &[0, 0, 0, 2]), None, "another story"); + } + + /// Thousands of clipped cells and as many lines: a lookup each, not a pass over the cells each. + #[test] + fn looking_up_many_clipped_cells_takes_linear_time() { + let make = |n: usize| (0..n).map(|r| cell(StoryRef::Body, vec![0], r, 0, true, r as f32)).collect::>(); + crate::tests_linear::assert_linear_steps("clip lookups", 1000, make, |cells| { + let clips = clipped_cells(cells); + let hits = (0..cells.len() as u32).filter(|r| cell_clip(&clips, StoryRef::Body, &[0, *r, 0, 0]).is_some()).count(); + assert_eq!(hits, cells.len()); + }); + } +}