Add image_coverage_fraction signal evaluator that computes the union
image coverage fraction from individual image XObject areas.
- Computes total image coverage as sum of image_xobject_areas
- Divides by page area (width * height) to get coverage fraction
- Clamps to [0.0, 1.0] to handle overlapping images (defensive)
- Returns Some(Vote::scanned(0.85)) if fraction > 0.85
Implementation uses sum for simplicity (overestimates coverage when
images overlap), which is acceptable for the 0.85 threshold as it's
a conservative signal. Can be revisited with Klee's algorithm for
greater accuracy if needed.
Acceptance criteria PASS:
✓ Page with one image covering 90% area → Some(Vote { 0.85, Scanned })
✓ Page with multiple small images totaling 50% → None (below threshold)
✓ Page with no images → None
✓ Coverage clamped to 1.0 on overlapping images
Also includes pre-existing infrastructure:
- tr3_op_count field in PageContext
- image_xobject_areas field in PageContext
- all_tr3_with_full_page_image function
- CharDensityRatioSignal evaluator
These were necessary dependencies for the new evaluator to function.
Refs: Plan section Phase 5.1.2, coordinator pdftract-22p
5.2 KiB
5.2 KiB
Rust SDK
The Rust SDK is the pdftract-core crate. It provides native PDF text extraction with zero-copy memory mapping and streaming support.
Installation
Add to your Cargo.toml:
[dependencies]
pdftract-core = "1.0"
For OCR support, enable the ocr feature:
[dependencies]
pdftract-core = { version = "1.0", features = ["ocr"] }
Basic Extraction
use pdftract_core::{extract, ExtractionOptions};
fn main() -> anyhow::Result<()> {
let opts = ExtractionOptions::default();
let result = extract("document.pdf", &opts)?;
for (i, page) in result.pages.iter().enumerate() {
println!("Page {}: {} spans", i + 1, page.spans.len());
for span in &page.spans {
println!(" {}", span.text);
}
}
Ok(())
}
Streaming Extraction
For large PDFs, stream pages one at a time to keep memory usage bounded:
use pdftract_core::{extract_stream, ExtractionOptions};
use std::path::Path;
fn main() -> anyhow::Result<()> {
let opts = ExtractionOptions::default();
let pages = extract_stream(Path::new("large_document.pdf"), &opts)?;
for page_result in pages {
let page = page_result?;
println!("Page {}: {} spans", page.index, page.spans.len());
}
Ok(())
}
Options
ExtractionOptions
| Field | Type | Default | Use Case |
|---|---|---|---|
receipts |
ReceiptsMode |
Off |
Generate cryptographic receipts |
max_parallel_pages |
usize |
4 |
Control memory for concurrent page processing |
memory_budget_mb |
usize |
512 |
Target peak RSS in MB |
full_render |
bool |
false |
Enable PDFium rendering (requires full-render feature) |
ocr_dpi_override |
Option<u32> |
None |
Override automatic DPI selection |
ocr_language |
Vec<String> |
vec!["eng"] |
Tesseract language codes |
markdown_anchors |
bool |
false |
Emit HTML comment anchors in Markdown |
max_decompress_bytes |
u64 |
512 MiB |
Bomb limit for decompressed streams |
output |
OutputOptions |
default() |
Output filtering options |
pages |
Option<String> |
None |
Page range (e.g., "1-5,7,12-") |
password |
Option<SecretString> |
None |
PDF password for encrypted documents |
OutputOptions
| Field | Type | Default | Use Case |
|---|---|---|---|
include_invisible |
bool |
false |
Include invisible text in output |
extract_forms |
bool |
true |
Extract AcroForm fields |
extract_attachments |
bool |
true |
Extract embedded attachments |
Receipts
Generate cryptographic receipts for verification:
use pdftract_core::{extract, ExtractionOptions};
use pdftract_core::options::ReceiptsMode;
fn main() -> anyhow::Result<()> {
let opts = ExtractionOptions {
receipts: ReceiptsMode::Lite,
..Default::default()
};
let result = extract("document.pdf", &opts)?;
// Receipts are embedded in page metadata
if let Some(receipt) = &result.pages[0].receipt {
println!("Receipt: {}", receipt);
}
Ok(())
}
Remote PDFs
With the remote feature, fetch PDFs via HTTP:
use pdftract_core::{extract, ExtractionOptions};
use std::path::Path;
fn main() -> anyhow::Result<()> {
let opts = ExtractionOptions::default();
let result = extract(Path::new("https://example.com/document.pdf"), &opts)?;
Ok(())
}
Error Handling
Most functions return anyhow::Result<T> which wraps various error types:
use pdftract_core::{extract, ExtractionOptions};
use std::path::Path;
fn main() {
let opts = ExtractionOptions::default();
match extract(Path::new("document.pdf"), &opts) {
Ok(result) => {
println!("Extracted {} pages", result.pages.len());
}
Err(e) => {
eprintln!("Extraction failed: {}", e);
// Inspect error chain
for cause in e.chain() {
eprintln!(" caused by: {}", cause);
}
}
}
}
Feature Flags
| Feature | Adds | Default |
|---|---|---|
serde |
JSON serialization support | ✓ |
decrypt |
Decryption of encrypted PDFs | ✓ |
quick-xml |
Conformance detection via XML metadata | ✓ |
ocr |
Tesseract OCR for scanned documents | - |
full-render |
PDFium-based rendering (requires ocr) |
- |
remote |
HTTP range fetching for remote PDFs | - |
profiles |
Extraction profiles | - |
receipts |
Cryptographic receipt generation | - |
cjk |
CJK text extraction via predefined CMap registry | - |
schemars |
JSON Schema generation | - |
Source Types
The SDK supports multiple source types via the PdfSource trait:
use pdftract_core::source::{FileSource, MmapSource, MemorySource};
// Memory-mapped source (zero-copy for large files)
let source = MmapSource::open("document.pdf")?;
// In-memory source (for byte buffers)
let data = std::fs::read("document.pdf")?;
let source = MemorySource::new(data);
// Standard file source
let source = FileSource::open("document.pdf")?;