github yfedoseev/pdf_oxide v0.3.5
v0.3.5 | Performance, 3,830-PDF Stability & Error Recovery

latest releases: v0.3.74, go/v0.3.74, v0.3.73...
4 months ago

v0.3.5 Release

This release delivers major performance improvements, 100% pass rate on 3,830 PDFs, comprehensive error recovery for 28+ real-world PDF failures, and spec-correct rendering — the biggest stability release to date.

⚡ Performance

  • Font caching across pages — Document-level cache keyed by ObjectRef avoids re-parsing shared fonts on every page. For a 1000-page document sharing 20 fonts, this reduces font parsing from 40,000 operations to 20
  • Page object cachingget_page() caches resolved page objects, eliminating repeated page tree traversal for multi-page extraction
  • Structure tree caching — Structure tree result cached after first access, avoiding redundant parsing on every extract_text() call
  • BT operator early-outextract_spans(), extract_chars() skip the full text extraction pipeline for image-only pages with no text operators
  • Larger I/O bufferBufReader capacity increased from 8 KB to 256 KB for files >100 MB, reducing syscall overhead on giant PDFs
  • Xref reconstruction threshold removed — Eliminated xref.len() < 5 heuristic that caused 5-13s delays on valid portfolio PDFs

✅ Verified — 3,830-PDF Corpus

Corpus PDFs Pass Rate
veraPDF (PDF/A compliance) 2,907 100%
Mozilla pdf.js 897 100%
SafeDocs (targeted edge cases) 26 100%
Total 3,830 100%
Metric Result
p50 latency 0.6ms
p90 latency 3.0ms
p99 latency 33ms
Under 10ms 97.6% of PDFs
Timeouts 0
Panics 0

🔐 Added — Encryption

  • Owner password authentication (Algorithm 7 for R≤4, Algorithm 12 for R≥5)
    • R≤4: MD5 hash chain → RC4 key → decrypts /O to recover user password
    • R≥5: SHA-256 with SASLprep normalization and owner validation/key salts
  • R≥5 user password verification — Full AES-256 with SHA-256 validation/key salts per PDF spec §7.6.4.3.3
  • Public APIPdfDocument::authenticate(password) exposed for Rust and Python

📋 Added — PDF/A & PDF/X Compliance Validation

  • PDF/A: XMP metadata validation, color space scanning for device-dependent operators, AFRelationship validation for embedded files
  • PDF/X: Page box relationships (TrimBox ⊆ BleedBox ⊆ MediaBox), ExtGState transparency detection, ICC profile validation, device-dependent color detection

🎨 Added — Rendering

  • Spec-correct clipping (PDF §8.5.4) — Clip state scoped to q/Q save/restore via clip stack; new clips intersect with existing clip region; W/W* no longer consume the current path; clip mask applied to all painting operations including text and images
  • Glyph advance width — Text position advances per PDF spec §9.4.4: tx = (w0/1000 × Tfs + Tc + Tw) × Th
  • Form XObject rendering/Matrix transform, resource inheritance, recursive content stream execution

🛡️ Fixed — Error Recovery (28+ real-world PDFs)

  • Missing objects resolve to Null — Per PDF spec §7.3.10, unresolvable indirect references return Null instead of errors (fixes 16 files)
  • Lenient header version parsing — Valid headers with unusual version strings no longer rejected
  • Non-standard encryption — V=1,R=3 combinations handled leniently
  • Non-dictionary Resources — Invalid /Resources entries treated as empty resources
  • Null nodes in page tree — Non-dictionary child nodes gracefully skipped
  • Corrupt content streams — Malformed streams return empty content instead of errors
  • Enhanced page tree scanning/Resources+/Parent heuristic and /Kids direct resolution added as fallback

🖼️ Fixed — Image Extraction

  • Content stream processingextract_images() now finds Do operator calls, extracting images previously missed
  • Nested Form XObjects — Recursive extraction with cycle detection
  • Inline imagesBI...ID...EI parsing with abbreviation expansion per PDF spec
  • 4-corner affine transform — Image bounding boxes correctly handle rotation, shear, and negative scaling
  • ColorSpace indirect references — Resolved before extraction (e.g., 7 0 R)

🔧 Fixed — Parser & Page Access

  • Multi-line object headers1 0\nobj format from Google PDFs now supported
  • Extended header search — Search window extended from 1024 to 8192 bytes
  • Missing /Contents entry — Returns empty content instead of erroring
  • Cyclic page tree detection — Tracks visited nodes to prevent stack overflow
  • Pages without /Type — Found via /MediaBox or /Contents keys
  • DoS protection/Count validated against PDF spec Annex C.2 limit (8,388,607)

🔒 Fixed — Encryption & Content Streams

  • Short encryption key — AES decryption with undersized keys returns error instead of panic
  • Xref stream hardening — Invalid entry sizes or out-of-bounds data no longer cause panics
  • Dictionary-as-Stream fallback — Bare dictionaries treated as empty streams
  • Filter abbreviationsAHx, A85, LZW, Fl, RL, CCF, DCT and case-insensitive matching
  • Operator limit — Configurable limit (default 1,000,000) prevents pathological slowdowns

🧹 Code Quality

  • Structure tree indirect referencesObjectRef variants resolved at parse time
  • Lexer R token disambiguation1 0 RG correctly parsed as color operator
  • Stream whitespace trimming — Only strips CR/LF, no longer strips NUL/spaces from binary data
  • 8 previously ignored tests un-ignored and fixed

🏆 Community Contributors

🥇 @SeanPedersen — Huge thanks to Sean for reporting multiple issues (#41, #44, #45, #46) that drove the entire stability focus of this release. His real-world testing uncovered a parser bug with Google-generated PDFs, image extraction failures, and performance problems — each triggering deep investigation and significant fixes. 🙏🔍

📥 Installation

Rust (crates.io)

cargo add pdf_oxide

Python (PyPI)

pip install pdf_oxide

Pre-built Binaries
Download archives for Linux, macOS, and Windows from the assets below.


Release Date: February 15, 2026
Branch: main
Tag: v0.3.5
Closes: #41, #44, #45, #46, #47#62, #63#79

Don't miss a new pdf_oxide release

NewReleases is sending notifications on new releases.