fix(tables): reject row-stripe tables with oversized cell text (#16)

Newsletter-style PDFs have decorative background rects (sidebar,
header, section bands) that pass row-stripe detection as false tables.
Reject when any cell exceeds 500 chars — real alternating-row data
tables have short cell content; layout backgrounds produce paragraph-
length "cells".

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Abimael Martell
2026-03-25 09:44:15 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 95aac6a7cd
commit d95584d7f2
+55
View File
@@ -1276,6 +1276,23 @@ fn detect_row_stripe_table(
return None;
}
// Reject if any cell has excessive text — layout background rects (sidebar,
// header, section bands) produce "cells" that contain paragraphs of body text.
// Real alternating-row-stripe data tables have short cell content.
let max_cell_len = cells
.iter()
.flat_map(|row| row.iter())
.map(|c| c.len())
.max()
.unwrap_or(0);
if max_cell_len > 500 {
debug!(
" row-stripe rejected: max cell length {} > 500 (layout background)",
max_cell_len
);
return None;
}
// No empty columns
for col in 0..num_cols {
let col_has_content = cells
@@ -1444,6 +1461,22 @@ fn detect_merged_cluster_table(
return None;
}
// Reject if any cell has excessive text — layout background rects produce
// "cells" containing paragraphs, not short data-table values.
let max_cell_len = cells
.iter()
.flat_map(|row| row.iter())
.map(|c| c.len())
.max()
.unwrap_or(0);
if max_cell_len > 500 {
debug!(
" merged-cluster rejected: max cell length {} > 500 (layout background)",
max_cell_len
);
return None;
}
// No empty columns
for col in 0..num_cols {
let col_has_content = cells
@@ -1852,6 +1885,28 @@ mod tests {
assert!(!is_row_stripe_pattern(&rects));
}
#[test]
fn test_row_stripe_rejects_layout_background_long_cells() {
// Simulate a newsletter page with wide background rects (sidebar, header, body)
// that look like row stripes but contain paragraphs of body text.
let rects = vec![
(10.0, 700.0, 550.0, 50.0), // header band
(10.0, 640.0, 550.0, 50.0), // nav band
(10.0, 200.0, 550.0, 430.0), // body background
];
let items = vec![
make_item("General News", 20.0, 650.0, 10.0),
make_item("People News", 20.0, 710.0, 10.0),
// Simulate a long body text (>500 chars) in the main content area
make_item(&"A".repeat(600), 200.0, 650.0, 10.0),
];
let result = detect_row_stripe_table(&items, &rects, 1);
assert!(
result.is_none(),
"layout background rects should not be detected as a table"
);
}
// --- propagate_merged_cells ---
#[test]