Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66103742c3 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@firecrawl/pdf-inspector",
|
||||
"version": "1.8.2",
|
||||
"version": "1.8.3",
|
||||
"description": "Fast PDF classification and text extraction. Detect text-based vs scanned PDFs, extract text by region with quality checks. Native Rust performance via napi-rs.",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
|
||||
+26
-6
@@ -2152,13 +2152,15 @@ fn try_expand_multi_row_cells(
|
||||
/// * `phantom_empty_row` — a row whose every cell is empty, surrounded
|
||||
/// above and below by rows with content. SLANet sometimes emits an
|
||||
/// extra row that doesn't correspond to any visible PDF row.
|
||||
/// * `multi_row_in_cell` — at least one `rowspan==1` cell encloses
|
||||
/// PDF text items that cluster into two distinct visual lines
|
||||
/// * `multi_row_in_cell` — at least one non-label `rowspan==1` cell
|
||||
/// encloses PDF text items that cluster into two distinct visual lines
|
||||
/// separated by a whitespace gap larger than the line height. Cells
|
||||
/// declared as `rowspan>1` are excluded since they are *expected*
|
||||
/// to span multiple lines. SLANet's row under-detection on
|
||||
/// tightly-packed tables produces the rowspan==1-but-multi-line
|
||||
/// pattern (the FNBO failure mode).
|
||||
/// to span multiple lines. First-row/first-column wraps are ignored
|
||||
/// unless the in-place row expansion has enough support to repair them,
|
||||
/// because those are often legitimate wrapped headers or row labels.
|
||||
/// SLANet's row under-detection on tightly-packed tables produces the
|
||||
/// rowspan==1-but-multi-line pattern (the FNBO failure mode).
|
||||
fn detect_tsr_quality_issue(
|
||||
buffer: &[u8],
|
||||
input: &TsrTableInput,
|
||||
@@ -2216,6 +2218,8 @@ fn detect_tsr_quality_issue(
|
||||
};
|
||||
let expanded_cells =
|
||||
try_expand_multi_row_cells(cells, &items, page_h, coords, adaptive_threshold);
|
||||
let first_row = cells.iter().map(|cell| cell.row).min().unwrap_or(0);
|
||||
let first_col = cells.iter().map(|cell| cell.col).min().unwrap_or(0);
|
||||
|
||||
for cell in cells {
|
||||
// rowspan>1 cells are intentionally multi-line — skip them.
|
||||
@@ -2229,14 +2233,30 @@ fn detect_tsr_quality_issue(
|
||||
if cell_items.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if cluster_tsr_cell_text_lines(cell_items).len() >= 2 {
|
||||
if cluster_tsr_cell_text_lines(cell_items).len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if expanded_cells.is_some() {
|
||||
return Ok(Some(TsrQualityIssue::MultiRowInCell { expanded_cells }));
|
||||
}
|
||||
if !is_wrapped_tsr_label_cell(cell, first_row, first_col) {
|
||||
return Ok(Some(TsrQualityIssue::MultiRowInCell {
|
||||
expanded_cells: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn is_wrapped_tsr_label_cell(
|
||||
cell: &tables::StructuredCell,
|
||||
first_row: usize,
|
||||
first_col: usize,
|
||||
) -> bool {
|
||||
cell.is_header || cell.row == first_row || cell.col == first_col
|
||||
}
|
||||
|
||||
/// Auto-fallback variant of [`extract_tables_with_structure_mem`]:
|
||||
/// runs the TSR-hybrid path, checks the resulting cells for known
|
||||
/// SLANet detection pathologies (phantom rows, multi-row-in-cell text),
|
||||
|
||||
BIN
Binary file not shown.
@@ -2532,6 +2532,61 @@ fn test_auto_expands_under_counted_vector_grid_rows() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_keeps_wrapped_header_vector_grid_doc51() {
|
||||
use pdf_inspector::{extract_tables_with_structure_auto_mem, TsrTableInput};
|
||||
|
||||
let buf = std::fs::read("tests/fixtures/government_positions_women.pdf").unwrap();
|
||||
let crop = [0.0, 0.0, 612.0, 792.0];
|
||||
let grid = detect_vector_grid_in_region_mem(&buf, 0, crop, 200.0)
|
||||
.unwrap()
|
||||
.expect("expected doc 51 vector grid");
|
||||
assert_eq!(
|
||||
grid.cell_bboxes.len(),
|
||||
36,
|
||||
"doc 51 should have a 9x4 vector grid"
|
||||
);
|
||||
|
||||
let results = extract_tables_with_structure_auto_mem(
|
||||
&buf,
|
||||
&[TsrTableInput {
|
||||
page: 0,
|
||||
crop_pdf_pt_bbox: crop,
|
||||
render_dpi: 200.0,
|
||||
structure_tokens: grid.structure_tokens,
|
||||
cell_bboxes: grid.cell_bboxes,
|
||||
}],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
let r = &results[0];
|
||||
assert!(
|
||||
r.fallback_reason.is_none(),
|
||||
"wrapped header/label text should not trigger heuristic fallback: {:?}\n{}",
|
||||
r.fallback_reason,
|
||||
r.markdown
|
||||
);
|
||||
let md = &r.markdown;
|
||||
assert!(md.contains("Government Position"), "missing header: {md}");
|
||||
assert!(
|
||||
md.contains("Aquino Administration"),
|
||||
"missing Aquino header: {md}"
|
||||
);
|
||||
assert!(
|
||||
md.contains("Ramos Administration"),
|
||||
"missing Ramos header: {md}"
|
||||
);
|
||||
assert!(
|
||||
md.contains("City Municipal Councilor"),
|
||||
"row label was truncated: {md}"
|
||||
);
|
||||
assert!(
|
||||
!md.contains("|Position||Administration"),
|
||||
"heuristic fallback split the header row: {md}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_returns_empty_inputs() {
|
||||
use pdf_inspector::extract_tables_with_structure_auto_mem;
|
||||
|
||||
Reference in New Issue
Block a user