Highlights
render_page_fit()now ships in all five bindings (Rust core +
Python, Node.js / TypeScript, C#, Go). Picks the largest DPI such
that both rendered dimensions fit inside a target pixel box,
preserving aspect ratio. No more "what DPI hits 1024×768?" math
on the caller's side. Fixes #441,
closes #448.- Idiomatic page iteration parity across bindings. Rust gets
page_indices(), Python gets.pages, Node.js gets
[Symbol.asyncIterator](the sync[Symbol.iterator]was already
there). C#Pagesand GoPages()were already shipped. Closes
#447. - WASI build target —
cargo build --target wasm32-wasip1now
builds the lib cleanly on stable Rust. Unblocks @RALaBarge's
externalpdf-oxide-wasistdin→stdout wrapper and any other
consumer wanting to embed pdf_oxide in a sandboxed WASI runtime.
CI now gates that the WASI build stays green. Closes
#214. - Spurious-table fix on dense word grids — Roland's #405 lands
via cherry-pick. A newhas_split_modal_column_groupsvalidator
inspects the column co-occurrence graph across modal rows and
rejects candidates whose populated columns split into two or more
disconnected components — the signature of two adjacent text
flows mis-clustered as one table. Composes cleanly with v0.3.42's
Table::is_real_gridfilter. Validated against the 86-PDF
cross-build corpus: 888 / 888 byte-equal — zero observable change
on common documents, the gate's value is in the safety net for
adversarial cases.
Fixes
- #456 —
PdfDocument::open(path)now populatessource_bytes,
unblockingconvert_to_pdf_a(), the C FFIpdf_document_get_source_bytes,
and any other API that re-reads the in-memory copy. Path-loaded
documents previously got an emptyVec<u8>and hit
"Invalid PDF header: File is empty (0 bytes read)"from the
PDF/A converter. Reported by @potatochipcoconut on PR #445. - #451 — Standard14 PostScript fonts with no open-source
equivalent (Symbol,ZapfDingbats) are now downgraded from
hardFontNotEmbeddederrors to a newKnownUnembeddableFont
warning during PDF/A conversion. A document that's otherwise
compliant no longer fails solely because of one symbolic font. - #395 — closed; verified the off-by-one C#
ExceptionMapper
fix in v0.3.38 actually resolves the reportedRenderPage→
SignatureException [8500]. Added a Rust regression test that
opens @gevorgter's exact reproducer PDF and assertsrender_page
succeeds. The fixture is pinned inpdf_oxide_tests. - #462 — dropped the
scripts/modernize_stubs.pypost-processor
and thepython_version = "3.8"setting fromrylai.toml. Rylai's
default already emits PEP-585 / PEP-604 syntax with
from __future__ import annotationsat the top, so post-processing
was duplicate work in opposite directions. Runtime support for
Python 3.8/3.9 is unaffected —.pyistubs are type-checker
artifacts, never imported at runtime. Reported by @monchin with a
clean diagnosis of the root cause.
Behavior changes
PdfDocument::open(path)now reads the file once into memory
rather than streaming viaBufReader<File>. The doc comment
already promised "Reads the entire file into memory"; this makes
it true. Memory usage onopen()is now equivalent to
from_bytes(std::fs::read(path)?). Required by #456; the
streaming reader was a partial optimisation no caller could rely
on (every code path that touchedsource_bytesalready required
the in-memory copy).PdfReaderenum collapsed to a single in-memory variant —
removed unusedFilevariant.std::io::{Read, Seek, BufRead, …}
imports are no longer cfg-gated, which is what unblocked the
wasm32-wasip1 build target.
Dependencies
- Batch-applied 9 dependabot bumps onto
release/v0.3.43:
CI workflows (golangci-lint-actionv7→v9,setup-go5.5→6.4,
setup-node4.4→6.4,github-scriptSHA refresh,
scorecard-action2.4.0→2.4.3), Go (testify1.8→1.11 — was
declared but unimported, dropped entirely), JS (rimraf5→6 —
@types/nodedeferred to a follow-up after a TypeScript-strict
shake-out), Python (onnx≥1.14→≥1.19.1). - The RustCrypto 0.8 stack (
pkcs8 0.11,spki 0.8,der 0.8,
digest 0.11,crypto-common 0.2,block-buffer 0.12) stays
pinned —rsa 0.10andp256/p384 0.14are still RC upstream.
See the existing pin note atCargo.toml:185-187.
Internal
- New
wasm32-wasip1build smoke check in.github/workflows/ci.yml
alongside the existingwasm32-unknown-unknownjob. - Regenerated SBOMs (
pdf_oxide_cli/sbom.cdx.json,
pdf_oxide_mcp/sbom.cdx.json) for 0.3.43. - New regression tests:
tests/test_issue_456_path_open_source_bytes.rstests/test_issue_447_page_indices.rstests/test_issue_395_render_page.rs
- New unit tests on
compliance::converter::downgrade_known_unembeddable_fonts.
Validation
86-PDF stratified corpus comparison (academic, mixed, forms,
government, newspapers, theses, plus the three #211 fixtures), 888
sampled (pdf, page, method) triples across extract_text,
to_plain_text, to_markdown, to_html:
- v0.3.43 vs v0.3.42 — 888 / 888 byte-equal, zero deltas
- v0.3.43 vs PyPI v0.3.41 — 860 equal, 28 reorder/de-dup, 0 real
content losses (same profile as v0.3.42's regression report)
Community contributors
This release exists because of the community. Special thanks to:
- @RolandWArnold — landed
the spurious-table fix in #405.
After iterating away from an earlier density-gate framing, the
shipped form ishas_split_modal_column_groups: a connected-
component check on the column co-occurrence graph across modal
rows that flags two-flow grids the regular-row-ratio gate
accepts. Roland's doc-comment explicitly flags it as a heuristic,
making it easy to revisit later. The fix composes with v0.3.42's
struct-tree-aware reading-order rewire without any merge conflict. - @RALaBarge — built an
external WASI binary wrapper for pdf_oxide
(pdf-oxide-wasi)
and reported in #214
that it required nightly Rust because of an internal
ceil_char_boundarycall. That call was already removed; this
release fixes the second hidden blocker (cfg-gatedstd::io
imports) and adds CI gating so the WASI target stays green. - @gevorgter — flagged two
rendering-area gaps: the C# binding's misleading
SignatureExceptiononRenderPage(#395,
fixed in v0.3.38, regression-guarded here) and the lack of a
pixel-dimension render API (#441,
closed byrender_page_fitshipping in all five bindings). - @potatochipcoconut —
surfaced theconvert_to_pdf_afailure on path-loaded documents
while testing PR #445; the investigation traced it to the empty
source_bytesfield and produced the one-line fix in this
release (#456). - @monchin — pointed out
(#462) that
scripts/modernize_stubs.pywas redundant work because rylai itself
controls the typing flavour via itspython_versionsetting, and
noted thatoffice/barcodes/ocrfeature alignment between
rylai.tomland the released wheel is worth a follow-up. The
cleaner stub pipeline ships in this release.
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.