The bead description mentioned compile errors in hash.rs from API drift,
but those errors were either already fixed or misattributed. The API usage
was already correct:
- compute_fingerprint already takes 3 arguments with source
- len() already propagates Result with ?
- read_at method already used correctly
- Catalog fields accessed via trailer correctly
Only cleanup: removed unused std::fs::File and std::io imports.
Verification: notes/bf-4mkhv.md
The extract_markdown stub was calling extract_text instead of
extract_text_fn, causing a compilation error. This fixes the
function name to match the exported function from extract_text.rs.
This completes the extract_text PyO3 entry point implementation,
which was already present in extract_text.rs and lib.rs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement per-word validation filter for assisted-OCR BrokenVector path.
Changes:
- Add SpanSource::OcrAssisted variant to hybrid.rs
- Add Span::ocr_assisted() helper method
- Implement validate_ocr_with_position_hints() in ocr.rs
- 5pt distance threshold for position validation
- 0.4 confidence cap for rejected words
- Linear scan for nearest-neighbor lookup
- Add unit tests for validation filter
Closes: pdftract-3s2i
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The detect_merged_cells function was implemented but not exported from
the table module, making it inaccessible to library users. This commit
adds the function to the public API exports.
Also adds a verification note documenting the complete implementation
and the export fix.
Acceptance criteria status:
- All 6 merged cell detection tests pass
- Public Cell.rowspan/colspan fields exist with default 1
- Absorbed cells are excluded from output
- Bbox of merged cell covers absorbed cells
- Borderless tables NO-OP with diagnostic
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add header_rows: u32 field to GridCandidate struct to store the count
of contiguous header rows detected. This completes the output requirement
"Table.header_rows: u32" from the header row detection task.
The header row detection logic was already fully implemented in cell.rs:
- Bold font detection via PostScript name patterns
- Cell-level and row-level bold detection
- Combined header detection (bold OR TH signals)
- Multi-row header counting
- Cell header flag marking
This commit only adds the field to store the header count on the
GridCandidate struct and updates constructors.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Implement header row detection for tables using two signals:
1. Bold font detection (fully implemented)
2. StructTree TH detection (stub pending MCID tracking)
Bold detection:
- is_bold_font(): detects bold fonts from PostScript name patterns
- is_cell_bold(): checks if all non-whitespace content in a cell is bold
- is_bold_header_row(): validates rows with >=2 bold cells
- count_header_rows(): counts contiguous bold headers from top
- Cell::mark_header_rows(): sets is_header_row flag on cells
TH detection (stub):
- is_th_header_row(): placeholder for StructTree TH detection
Requires MCID tracking on TableSpan (future work)
Will use ParentTree to map MCIDs to StructElems
Will verify TR > TH chain structure
Combined detection:
- is_header_row(): combines bold and TH signals
- Bold wins on conflict per body data design principle
Documentation:
- Updated table-structure-reconstruction.md with full header detection spec
- Documented implemented vs pending signals
- Added implementation notes for TH detection
Tests:
- 45 tests covering all bold detection scenarios
- Tests for multi-row headers (contiguous from top)
- Tests for single-cell row exclusion
- Tests for empty/whitespace cell handling
- Placeholder tests for TH detection
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements 7.2.3: span-to-cell assignment using centroid containment.
- Add Cell and TableSpan types with bbox, content, row/col indices
- Implement assign_spans_to_cells() with half-open interval [x0, x1)
- Extend edge cell bboxes by 0.5pt to capture spans flush to borders
- Sort cell content by (round(y0/2), x0) with 2-pt y-bucket
- Emit diagnostic when span overlaps adjacent cell by > 40%
- Handle orphan spans (returned separately, not lost)
Adjustment: Changed overlap diagnostic threshold from 50% to 40%
because with half-open intervals, it's mathematically impossible
for a span's centroid to be in one cell while overlapping another
by > 50%.
All 20 unit tests pass including critical 5×3 bordered table test.
Refs: pdftract-2oqh, plan 7.2 line 2591
Fix threshold logic in is_single_column_reflow to correctly detect
single-column paragraph reflow patterns. Changed from integer division
(< positions.len() / 2) to proper "more than half" check (* 2 < positions.len()).
Also update module documentation to reflect that borderless detection
is now implemented (7.2.2 complete).
Acceptance criteria:
- ✅ Borderless 3x3 table detected via alignment heuristic
- ✅ Unit tests: paragraph rejected, one-row rejected, vertical-gap test
- ✅ Public TableDetector::detect_borderless(&PageContext) -> Vec<GridCandidate>
- ✅ All 28 detector tests pass
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>