v0.3.22 | Thread-Safe PdfDocument, Async API, Performance, Community Fixes
Breaking Changes
None. All changes are backward-compatible.
Features
- Thread-safe
PdfDocument— Send + Sync (#302) — replaced all 16RefCell<T>withMutex<T>andCell<usize>withAtomicUsize.PdfDocumentcan now safely cross thread boundaries. RemovesunsendablefromPdfDocument,FormField, andPdfPagePython classes. Enablesasyncio.to_thread(), free-threaded Python (cp314t), and thread pool usage withoutRuntimeError. Reported by @FireMasterK (#298). AsyncPdfDocument,AsyncPdf,AsyncOfficeConverter(#217) — complete async API with auto-generated method wrappers. All sync methods are available as async. Requested by @j-mendez.- Free-threaded Python support (#296) —
#[pymodule(gil_used = false)]declares GIL-free compatibility for cp314t. Requested by @pcen. - Word/line segmentation thresholds (#249) —
extract_words()andextract_text_lines()accept optionalword_gap_threshold,line_gap_threshold, andprofilekwargs. Newpage_layout_params()method andExtractionProfileclass expose adaptive parameters. Contributed by @tboser.
Bug Fixes
- CLI split/merge blank pages (#297) — merge now writes merged page refs; split now filters removed pages from Kids. Reported by @Suleman-Elahi.
- Rendering: skip malformed images (#299, #300) — images with missing
/ColorSpaceor invalid dimensions are skipped with a warning instead of crashing the page render. Also handles malformed images inside Form XObjects. Reported by @FireMasterK. - Structure tree cycle SIGSEGV (#301) — cyclic
/Kindirect references in malformed tagged PDFs caused stack overflow. A visited-object set now breaks cycles. Contributed by @hoesler. horizontal_strategy: 'lines'text fallback gate (#290) — settinghorizontal_strategytolinesnow correctly suppresses text-based row detection. Each axis is checked independently. Contributed by @hoesler.vertical_strategyPython parsing (#290) —vertical_strategywas never read from the Pythontable_settingsdict, always defaulting toBoth. Contributed by @hoesler.
Performance
- Cache structure tree — parsed once and cached; non-tagged PDFs skip parsing via MarkInfo check.
- Cache decompressed page content stream — avoids re-decompression when multiple extractors access the same page.
- Shared XObject stream cache for path extraction — reuses decompressed Form XObject streams already cached by text extraction.
- Cached XObject dictionary for path extraction — avoids re-resolving Resources -> XObject dict chain on every Do operator.
- Byte-level path extraction parser — skips BT/ET text blocks and parses path/state/color operators without Object allocation.
- Allocation-free graphics state for paths — Copy-only state struct eliminates heap allocations on q/Q save/restore.
- Index-based font tracking in prescan — replaces String cloning on every q operator with index into font table.
- Prescan: drop Do positions when Do-dominated — prevents region merging that defeats the prescan optimization.
- Reuse spans in table detection — reuses pre-extracted spans instead of re-parsing the content stream.
- Pre-filter non-table paths — filters to lines/rectangles before the detection pipeline.
- O(1) MCID lookup — HashSet instead of linear search for marked-content identifier matching.
- O(log n) page tree traversal — uses /Count to skip subtrees instead of linear counting.
- Lazy page tree population — defers bulk page tree walk until needed.
Dependencies
- Bump
zip8.5.0 -> 8.5.1 - Bump
pdfium-render0.8.37 -> 0.9.0 - Bump
tokenizers0.15.2 -> 0.22.2
Community Contributors
Thank you to everyone who reported issues and contributed PRs for this release!
- @hoesler — Structure tree cycle SIGSEGV fix (#301) and table strategy gating fix (#290). Two high-quality PRs with tests and clean code.
- @tboser — Word/line segmentation thresholds feature (#249). Well-designed API with 14 tests and responsive to review feedback.
- @FireMasterK — Reported thread-safety crash (#298), rendering crashes with missing ColorSpace (#299) and invalid image dimensions (#300). Three critical bug reports that drove the Send+Sync refactor.
- @Suleman-Elahi — Reported CLI split/merge blank pages bug (#297) with clear reproduction steps.
- @pcen — Requested free-threaded Python compatibility (#296).
- @j-mendez — Requested async Python API (#217).
Installation
Rust (crates.io)
cargo add pdf_oxidePython (PyPI)
pip install pdf_oxideJavaScript/WASM (npm)
npm install pdf-oxide-wasmCLI (Homebrew)
brew install yfedoseev/tap/pdf-oxideCLI (Scoop — Windows)
scoop bucket add pdf-oxide https://github.com/yfedoseev/scoop-pdf-oxide
scoop install pdf-oxideCLI (Shell installer)
curl -fsSL https://raw.githubusercontent.com/yfedoseev/pdf_oxide/main/install.sh | shCLI (cargo-binstall)
cargo binstall pdf_oxide_cliMCP Server (for AI assistants)
cargo install pdf_oxide_mcpPre-built Binaries
Download archives for Linux, macOS, and Windows from the assets below. Each archive includes both pdf-oxide (CLI) and pdf-oxide-mcp (MCP server).
Platform Support
| Platform | Architecture | Archive |
|---|---|---|
| Linux | x86_64 (glibc) | pdf_oxide-linux-x86_64-*.tar.gz |
| Linux | x86_64 (musl) | pdf_oxide-linux-x86_64-musl-*.tar.gz |
| Linux | ARM64 | pdf_oxide-linux-aarch64-*.tar.gz |
| macOS | x86_64 (Intel) | pdf_oxide-macos-x86_64-*.tar.gz |
| macOS | ARM64 (Apple Silicon) | pdf_oxide-macos-aarch64-*.tar.gz |
| Windows | x86_64 | pdf_oxide-windows-x86_64-*.zip |
Changelog
See CHANGELOG.md for full details.