Added
- PDF/A conversion exposed in the Java binding, with idiomatic Kotlin/Scala/Clojure facades (
PdfAConverter,ConversionResult/ConversionAction/ActionType/ConversionErrorinfyi.oxide.pdf.compliance) — completes PDF/A conversion coverage across every first-party binding (#948).
Fixed
Editor / redaction persistence
PdfDocument.save()silently discarded DOM edits (set_text(),remove_element(),erase_header()/erase_footer()/erase_artifacts()) made to a page loaded from an existing PDF — the overlay-merge path that reconciles DOM edits back into a page's/Contentson save had a defect that dropped the edit entirely; edits made via the DOM API now persist throughsave()/save_page()(#940).- A page whose original
/Contentswas already a multi-entry array (ISO 32000-1 §7.7.3.3) kept a dangling reference to its other original streams after destructive redaction — the merge step that replaces/Contentswith the redacted stream assumed a single original stream at array position 0; it now replaces every original content reference, keeping only genuine overlay/addition streams. The same defect class was independently hit through two different triggers:set_text()/remove_element()on a page whose/Contentswas already an array, andadd_text()combined withapply_redactions_destructive()on the same page (#940, #941, #799). add_text()overlay font registered under the raw font name while the content stream emitted themap_font_name()-transformed name, leaving a dangling/Tfresource for bold/italic overlays, generic family names (Arial,sans-serif), and Symbol/ZapfDingbats — registration now keys off the exact name theTfoperator emits, and/Resources//Resources/Fontare resolved through indirect references (common for pages loaded from existing documents) (#941).save_page()discarded overlay text staged by a priorsave_page()call on the same page —overlay_additionsnow accumulates across calls instead of being overwritten (#941).
Rendering
- CCITT Group 3/4
/ImageMaskXObjects were treated as raw stencil rows instead of decoded — compressed fax data is now actually decompressed;DecodeParms/filter-chain handling,K < 0Group 4 semantics, and allocation on oversized/malformed input are also corrected (#935, #939). - Separation/DeviceN colour spaces with a genuinely multi-channel (N>1) Type 0 (sampled) tint transform rendered black or dropped shapes entirely — the sampled-function evaluator only ever handled a single input dimension, forwarding just the first
scnoperand and silently falling back togray = 1 − components[0]; it now performs full N-dimensional multilinear interpolation across the sample grid (ISO 32000-1 §7.10.2's general algorithm, of which the prior 1-D case is the N=1 special case), gated by aMAX_SAMPLED_FUNCTION_DIMS = 8bound against a pathological/Sizearray (#849, #859). - Non-CCITT 1-bit
/DeviceGrayimages (uncompressed orFlateDecode) were force-fed through the CCITT decompressor and silently dropped when decompression failed — the CCITT path is now gated on the XObject's filter actually beingCCITTFaxDecode; the non-CCITT case is unpacked directly, folding/Decode [1 0]inversion the same way the CCITT path already does (#860). - Inline images (
BI…ID…EI) were parsed and classified as a paint operator but never actually painted — the renderer's operator dispatch had no match arm forOperator::InlineImage; it now expands the abbreviated dictionary keys and routes through the samerender_image/render_image_maskpath used forDo-invoked image XObjects (#860). - Spatial table-cell ownership misattributed text near cell boundaries — stale per-glyph offsets outside a text span's bounding box are now rejected for singleton spans, and exact half-open internal grid intervals keep superscripts and boundary-adjacent text in their correct geometric cell; outer-edge tolerance near the table boundary is unchanged (#937, #938).
Performance
- Repeated
search()/search_page()calls on the same document re-extracted and re-postprocessed every page's full spans on every call — a multi-pattern scan over one document costO(searches × full extraction)with no benefit from repetition, since the existing span cache is bounded to 8 entries. An unbounded, lighter-weight per-page search index (page text + span bounding boxes only, no font/glyph data) is now built lazily on first use and reused across calls;prepare_search()/clear_search_index()give callers control over eager population and memory reclamation. Measured: repeatsearch()calls on a 60-page document dropped from ~30ms to ~20-50µs per call after the first (#936).
Changed / Dependencies
- Test/CI stability: two
ocr-feature-gated CCITT diagnostic test files still calledfax0.2'su16decode_g4signature after the crate's 0.3 API bump (u32), breakingcargo clippy --all-targets --features rendering,barcodes,signatures,ocr— exactly the combo CONTRIBUTING.md's recommended pre-commit hook uses (#945).
Contributors
Rendering fixes from @Goldziher — CCITT /ImageMask decoding (#935, #939) and spatial table-cell ownership (#937, #938). Redaction/overlay persistence from @thomnico — the add_text()/destructive-redaction interaction (#941, #799, superseding an earlier iteration). Thank you!
Issues reported by:
- @lightedlogic — #940 (
save()doesn't persist DOM edits made viaset_text()/remove_element()) - @ankursri494 — #936 (repeated
search()calls on the same document don't get faster) - @ultrasaurus — #945 (recommended pre-commit hook fails on
main) - @bfchiheb — #947 (PDF/A conversion missing from the Java binding)
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.