fix(columns): Skip table detection on wide multi-column text pages

Multi-column text layouts (e.g., creditor matrices with 3 address columns)
were incorrectly claimed by body-font table detection. Now runs column
detection before table detection and skips tables on pages where all
detected columns are wider than 150pt, letting group_into_lines handle
the column-by-column reading order correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Abimael Martell
2026-02-16 09:52:58 -08:00
co-authored by Claude Opus 4.6
parent 73ade64fac
commit d8f341bafc
2 changed files with 20 additions and 4 deletions
+4 -4
View File
@@ -2109,9 +2109,9 @@ fn effective_width(item: &TextItem) -> f32 {
/// Represents a column region on a page /// Represents a column region on a page
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ColumnRegion { pub(crate) struct ColumnRegion {
x_min: f32, pub(crate) x_min: f32,
x_max: f32, pub(crate) x_max: f32,
} }
/// Detect column boundaries on a page using a horizontal projection profile. /// Detect column boundaries on a page using a horizontal projection profile.
@@ -2119,7 +2119,7 @@ struct ColumnRegion {
/// Builds an occupancy histogram across the page width and finds empty valleys /// Builds an occupancy histogram across the page width and finds empty valleys
/// (gutters) where no text exists. Validates valleys with vertical consistency /// (gutters) where no text exists. Validates valleys with vertical consistency
/// checks to avoid false positives. /// checks to avoid false positives.
fn detect_columns(items: &[TextItem], page: u32) -> Vec<ColumnRegion> { pub(crate) fn detect_columns(items: &[TextItem], page: u32) -> Vec<ColumnRegion> {
const BIN_WIDTH: f32 = 2.0; const BIN_WIDTH: f32 = 2.0;
const MIN_GUTTER_WIDTH: f32 = 8.0; const MIN_GUTTER_WIDTH: f32 = 8.0;
const MIN_VERTICAL_SPAN_RATIO: f32 = 0.30; const MIN_VERTICAL_SPAN_RATIO: f32 = 0.30;
+16
View File
@@ -190,6 +190,22 @@ pub fn to_markdown_from_items(items: Vec<TextItem>, options: MarkdownOptions) ->
let group = page_groups.get(&page).unwrap(); let group = page_groups.get(&page).unwrap();
let page_items: Vec<TextItem> = group.iter().map(|(_, item)| (*item).clone()).collect(); let page_items: Vec<TextItem> = group.iter().map(|(_, item)| (*item).clone()).collect();
// Skip table detection on pages with clear multi-column text layout.
// Column detection in group_into_lines handles these correctly, and
// table detection would incorrectly claim columnar text as table rows.
// Only skip when all detected columns are wide (>150pt ~ 2 inches),
// indicating true text columns rather than narrow table columns.
let columns = crate::extractor::detect_columns(&page_items, page);
if columns.len() >= 2 {
let min_col_width = columns
.iter()
.map(|c| c.x_max - c.x_min)
.fold(f32::INFINITY, f32::min);
if min_col_width > 150.0 {
continue;
}
}
let tables = detect_tables(&page_items, base_size); let tables = detect_tables(&page_items, base_size);
for table in tables { for table in tables {