Implement repair_hyphenation() that detects and repairs end-of-line
hyphenation within blocks. Joins hyphenated words across line breaks
when the hyphen is at the column right edge and the continuation
starts with a lowercase letter.
Key features:
- Detects hyphens: -, ‐ (U+2010), ‑ (U+2011), soft hyphen (U+00AD)
- Right-edge detection: span bbox.x1 within 5% of column width
- Lowercase continuation check to avoid joining sentences
- Column-aware: only joins spans in same column
- Cleans up empty spans/lines after repair
Adds HasBBox and HyphenableSpan traits for flexible span types.
Includes 9 comprehensive tests covering all acceptance criteria.
Fixes pre-existing test cases in schema module (missing column field).
Closes: pdftract-5o6hx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement cluster_spans_into_lines for Phase 4.2 line formation.
Groups spans into lines by baseline proximity using threshold 0.5 * median_font_size.
- Add HasFontSize trait for types with font_size
- Implement cluster_spans_into_lines function
- Compute baseline for each span
- Sort by baseline ASC
- Sweep and cluster within threshold
- Emit Line per cluster
- Sort spans by x0 within each line
- Add finalize_line_cluster helper
- Export new items from layout module
Tests: All 11 acceptance criteria tests pass
- Spans baselines 100, 100.5, 105 with median 12: one line
- Spans baselines 100, 110 with median 12: two lines
- Superscript stays on same line as base text
- Empty input produces empty output
- Threshold is 0.5 * median_font_size (not hardcoded)
Closes: pdftract-6bwq4
Implement Phase 4.4 code block classification for detecting indented
monospace code blocks.
Features:
- is_monospace_font_name: Check font name for monospace indicators
(mono, courier, code, fixed, console - case-insensitive)
- is_fixed_pitch_flag: Check FontDescriptor bit 0 (FixedPitch)
- classify_code: Classify block as code if all spans monospace AND
indented ≥ 2em from column baseline
- classify_page_code_blocks: Post-processing pass to upgrade paragraph
blocks to code kind
Acceptance criteria:
- All-Courier, indented 24pt, font_size 12pt (2em=24): Code ✓
- All-monospace, not indented: NOT Code ✓
- Mixed serif+monospace: NOT Code ✓
- One serif span at end: NOT Code ✓
- FixedPitch flag set, no "Mono" in name: STILL Code ✓
Closes: pdftract-8n270
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement compile-time phf::Set of 20,000 common English words for
dictionary coverage scoring in readability analysis (Phase 4.7).
Key changes:
- Added wordlist-en-20k.txt (20k frequency-sorted English words)
- Extended build.rs to generate phf::Set from wordlist
- Added layout/wordlist.rs module with is_english_word() API
- Added wordlist benchmarks (< 100 ns lookup achieved)
Test results:
- All 9 unit tests pass
- Benchmarks: 13-62 ns per lookup (well under 100 ns requirement)
- Binary size: Estimated ~200-220 KB (within 250 KB limit)
Closes: pdftract-9wevc
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>
Implement char-weighted median aggregation of per-span readability
scores into a page-level score stored in extraction_quality.readability.
Algorithm:
- Collect (score, char_count) pairs from spans
- Sort by score ascending
- Walk sorted list accumulating character counts
- Return score at half-total-char position
Acceptance criteria:
- Single span: returns its score
- Multiple spans: char-weighted median (longer spans count more)
- Empty page: returns 0.0
- All-perfect: returns 1.0
Closes: pdftract-oh30a
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add Phase 4 caption classification for detecting figure captions.
Implements classify_caption() which identifies blocks as captions when:
- Small font size (median < page body median)
- Follows Figure block within 2 line heights
- Same column as Figure
Module: crates/pdftract-core/src/layout/caption.rs
Acceptance criteria:
- Block immediately below Figure, small font, same column → kind: Caption
- Block 5 lines below Figure → NOT Caption (gap too large)
- Block with body-size font below Figure → NOT Caption (font not smaller)
- Block in different column from Figure → NOT Caption
Tests: 9/9 passed covering all acceptance criteria plus edge cases.
Closes: pdftract-xzfkt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>