From f64c853a3a2f448ed295571c6953d6942d8b9bd3 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Mon, 9 Nov 2026 05:19:30 -0500 Subject: [PATCH] fix(forms): comb detection ignores repeated boxes (#6077) --- crates/forms/src/detect.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/forms/src/detect.rs b/crates/forms/src/detect.rs index 94ad7adb37..99f2447c02 100644 --- a/crates/forms/src/detect.rs +++ b/crates/forms/src/detect.rs @@ -391,6 +391,8 @@ fn label_for(words: &[Word], rect: [f64; 4], right_of: bool) -> String { /// enough: dates split into day and month groups of two cells. Cells are square or taller /// than wide; an empty table of wider cells is not a comb. fn combs(boxes: &[[f64; 4]]) -> Vec<([f64; 4], usize)> { + // A repeated box adds nothing to a row, but every repeat would be searched against every cell. + let mut seen = std::collections::HashSet::new(); let cells: Vec<[f64; 4]> = boxes .iter() .copied() @@ -398,6 +400,7 @@ fn combs(boxes: &[[f64; 4]]) -> Vec<([f64; 4], usize)> { let (w, h) = (r[2] - r[0], r[3] - r[1]); (5.0..=30.0).contains(&w) && (8.0..=40.0).contains(&h) && w <= h * 1.2 }) + .filter(|r| seen.insert(r.map(f64::to_bits))) .collect(); // `b` comes right after `a` in a row: same size, its left edge on a's right edge. let follows = |a: &[f64; 4], b: &[f64; 4]| { @@ -521,3 +524,17 @@ pub fn detect(words: &[Word], shapes: &Shapes, existing: &[[f64; 4]], taken: &[S } out } + +#[cfg(test)] +mod comb_tests { + use super::combs; + + #[test] + fn repeated_boxes_do_not_change_or_multiply_a_comb() { + let row = [[10.0, 10.0, 20.0, 22.0], [20.0, 10.0, 30.0, 22.0], [30.0, 10.0, 40.0, 22.0]]; + let mut many = row.to_vec(); + many.extend(std::iter::repeat_n(row[1], 5000)); + assert_eq!(combs(&row), vec![([10.0, 10.0, 40.0, 22.0], 3)]); + assert_eq!(combs(&many), combs(&row)); + } +}