Add two comprehensive integration tests to validate the ParentTree resolver: 1. test_parent_tree_annotation_with_struct_parent: - Creates a body paragraph StructElem - Creates ParentTree with page array (MCID 0 -> body, MCID 1 -> orphan/null) - Creates ParentTree with annotation entry (key 100 -> body) - Verifies MCID resolution returns correct map and orphans - Verifies annotation /StructParent resolution returns the body ref - Verifies the referenced StructElem is in the tree 2. test_parent_tree_off_by_one_missing_entries: - Creates ParentTree with sparse array (only 3 entries for potentially more MCIDs) - Verifies non-null entries are correctly mapped - Verifies null entries are recorded as orphans - Documents that MCIDs beyond array length would be detected in Phase 7.1.4 Also export ParentTreeResolver and ParentTreeEntry from parser module for use by the block builder in Phase 7.1.4. All 67 struct_tree tests pass (18 ParentTree-specific tests). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|---|---|---|
| .cargo | ||
| .ci/argo-workflows | ||
| .config | ||
| .git-hooks | ||
| .github/ISSUE_TEMPLATE | ||
| benches | ||
| crates | ||
| distribution | ||
| docs | ||
| examples | ||
| fuzz | ||
| notes | ||
| pdftract-dotnet | ||
| pdftract-go | ||
| pdftract-java | ||
| pdftract-node | ||
| profiles/builtin | ||
| proptest-regressions | ||
| scripts | ||
| src | ||
| templates/sdk-skeleton | ||
| tests | ||
| tools | ||
| xtask | ||
| .gitignore | ||
| .needle-predispatch-sha | ||
| .nextest.toml | ||
| .renovaterc.json | ||
| audit.toml | ||
| Cargo-dist.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| clippy.toml | ||
| conformance_test | ||
| CONTRIBUTING.md | ||
| Cross.toml | ||
| deny.toml | ||
| Dockerfile | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| mod | ||
| pdftract-test-merged.cdx.json | ||
| README.md | ||
| SECURITY.md | ||
| test_api_null.c | ||
| test_empty | ||
| test_empty.c | ||
| test_flate.rs | ||
| test_trailer_parsing.rs | ||
pdftract
A PDF text extraction library that gets the hard parts right.
What it does
- Correct reading order — layout regions are segmented and sequenced before text is emitted, handling multi-column pages, sidebars, footnotes, and mixed-layout documents without relying on PDF operator order
- Font encoding recovery — when
ToUnicodeCMaps are absent, wrong, or incomplete, pdftract works through a layered recovery pipeline: glyph name lookup via the Adobe Glyph List, font fingerprinting against known metrics and embedded checksums, and glyph outline shape matching - Structure tree extraction — PDF/UA and PDF/A documents encode their logical structure (headings, paragraphs, lists, tables, reading order) in a
StructTree; pdftract reads this directly when present, producing accurate semantic output at no extra cost - Per-page hybrid routing — each page is independently classified and routed to the appropriate pipeline: vector text extraction, full OCR, or assisted OCR where vector hints improve raster accuracy
- Structured output with provenance — the primary output is JSON carrying per-span bounding boxes, font name, size, and confidence score alongside the extracted text, not a flat string dump
Output
{
"pages": [
{
"page": 1,
"blocks": [
{ "kind": "heading", "text": "Introduction", "bbox": [72, 680, 400, 700] },
{ "kind": "paragraph", "text": "...", "bbox": [72, 640, 540, 670] }
],
"spans": [
{ "text": "Introduction", "bbox": [72, 680, 400, 700], "font": "Times-Bold", "size": 14.0, "confidence": 0.99 }
]
}
],
"metadata": { "title": "...", "author": "...", "page_count": 10 }
}
Usage
pdftract extract invoice.pdf # structured JSON to stdout
pdftract extract invoice.pdf --text # plain text to stdout
pdftract extract invoice.pdf --output out.json
pdftract serve --port 8080 # HTTP service: POST /extract
Architecture
Rust core with PyO3 Python bindings and a CLI binary. The same binary runs as a command-line tool or as an HTTP microservice — the container deployment is just pdftract serve.
See docs/research/ for technical deep-dives into the PDF specification, font encoding, glyph Unicode recovery, and tagged PDF structure. See docs/notes/ for SDK invocation examples in Python, Node.js, Go, Ruby, Java, Rust, and Bash.
Verifying Releases
All releases are signed using Sigstore keyless signing with OIDC from the iad-ci cluster. This provides cryptographic proof that artifacts were produced by the official CI/CD pipeline and haven't been tampered with.
Verify Binary Archives
To verify downloaded binary archives:
# Download release artifacts
gh release download vX.Y.Z --dir /tmp/pdftract-release
# Verify the SHA256SUMS signature
cosign verify-blob \
--certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
--certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
--signature SHA256SUMS.sig \
--certificate SHA256SUMS.pem \
SHA256SUMS
# Verify individual artifacts against checksums
sha256sum -c SHA256SUMS
Verify Docker Images
To verify Docker images before running them:
# Verify the main image
cosign verify \
--certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
--certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
ghcr.io/jedarden/pdftract:X.Y.Z
# Verify the OCR variant
cosign verify \
--certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
--certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
ghcr.io/jedarden/pdftract:ocr-X.Y.Z
# Verify the full variant
cosign verify \
--certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
--certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
ghcr.io/jedarden/pdftract:full-X.Y.Z
View SLSA Provenance
Each Docker image includes SLSA provenance attestation:
cosign verify-attestation \
--certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
--certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
--type slsaprovenance \
ghcr.io/jedarden/pdftract:X.Y.Z
The provenance includes the build configuration, source commit, and builder identity.
Security
For responsible disclosure of security vulnerabilities, please email security@jedarden.com. See SECURITY.md for our disclosure policy, supported versions, and PGP key for encrypted reports.
PGP Key: The public key for security@jedarden.com is available at docs/security/pgp-public-key.asc.
NOTE: The PGP key is currently a placeholder. The security contact must generate and publish a 4096-bit RSA key for
security@jedarden.com. Seedocs/security/pgp-public-key.ascfor generation instructions.
Status
Early development. See docs/plan/ for the implementation roadmap.