github yfedoseev/pdf_oxide v0.3.64
v0.3.64 | Composite-CJK page rendering — a bundled Droid Sans fallback now paints embed-less Type 0 fonts and the Adobe predefined CIDFont collections — plus a §11 transparency compositing surface with optional lcms2 colour management, cross-document font-cache correctness, valid annotation appearance streams, and math/CJK text-extraction polish (prime-notation spacing, signed unit exponents, CJK bracket spacing, table-header Markdown).

latest releases: v0.3.77, go/v0.3.77, v0.3.76...
2 months ago

Added

  • Substitute Adobe predefined CIDFonts with a bundled CJK fallback when rendering (#730) — a composite (Type 0) font that references an Adobe predefined character collection (Adobe-Japan1, Adobe-GB1, Adobe-CNS1, Adobe-KR — e.g. Ryumin-Light, GothicBBB-Medium, STSong-Light, HYSMyeongJo-Medium) without embedding glyph outlines previously painted nothing. ISO 32000-2 §9.7.5.2 requires a conforming reader to ship those collections; the page renderer now maps CID → Unicode (via the bundled cid_mappings tables) → a Droid Sans Fallback glyph, producing a real sans-serif rendering even when the document asked for Mincho / Gothic. Gated behind the opt-in cjk-render-fallback feature (~3.4 MB), independent of cjk-form-fonts.
  • Bundled CJK fallback for embed-less composite fonts on CJK-fontless hosts — the same cjk-render-fallback feature loads Droid Sans Fallback into the page renderer's font database so composite fonts that reference a glyph collection but embed no outlines still paint real glyphs instead of blank on machines with no system CJK font installed.
  • §11 transparency compositing surface and a pluggable IccBackend trait (#674) — adds an ISO 32000-1 §11 transparency surface to the rendering pipeline and an IccBackend colour-management abstraction with an optional lcms2-backed implementation, alongside the default qcms path.

Fixed

  • Cross-document font cache now keys Type0/Identity-H fonts by /ToUnicode content (#733, extends #595, #597, #598) — the cross-document cache hardening in #595/#597/#598 folds the /ToUnicode reference (object id/gen) into the font identity hash and keeps canonical subset fonts (AAAAAA+) out of the shared cache. This extends that coverage to two cases the reference-based key doesn't reach: a non-canonical subset tag such as /CIDFont+F1 (emitted by some generators) stays eligible for cross-document sharing, and PDFs produced from a common template reuse the same /ToUnicode object number — so two genuinely different fonts that merely share a /BaseFont name produced an identical key. Processed in one long-lived process, a later document was then served an earlier font's parsed FontInfo and its glyphs decoded through the wrong /ToUnicode — a constant-offset cipher (SUMMARY6800$5<) or control/PUA characters — though each document extracted correctly in isolation. The identity hash now folds the /ToUnicode stream bytes, the embedded /FontFile{,2,3} bytes, the descendant /Subtype, and a stream-form descendant /CIDToGIDMap, so same-named-but-different fonts get distinct keys regardless of subset-tag form or object reuse, while genuinely identical fonts still deduplicate across documents (the cache's purpose is preserved). Same bug class as the /Widths poisoning fixed in #598.
  • Watermark annotations rendered as nothing in compliant viewers (#713) — a watermark's /AP appearance was serialized as a stream nested directly inside the annotation dictionary (/AP <</N <<…>> stream … endstream>>). A PDF stream must be an indirect object (ISO 32000-1:2008 §7.3.8); the inline form is invalid, so spec-compliant readers (e.g. MuPDF/PyMuPDF) rejected the annotation with "invalid key in dict" and the watermark never appeared — even though the bytes were present in the file. A shared hoist_appearance_streams helper now lifts nested /N, /D, and /R appearance streams (including named-state sub-dictionaries) into freshly allocated indirect objects and replaces the slot with a reference, applied on both the DocumentBuilder writer and the existing-page DocumentEditor::save_page paths. Verified end-to-end with MuPDF: the watermark now parses and renders on both paths.
  • Fixed Python type stubs leaking the pyo3 Py<Self> receiver as a positional parameter (#728) — methods implemented in Rust with a by-value receiver (fn page(slf_handle: Py<Self>, …) — the idiom pyo3 uses to hand a method an owned handle to its own instance) were emitted by the rylai stub generator with that receiver re-exposed alongside the injected self.
  • Bookmark titles encoded in UTF-16BE/LE decoded incorrectly (#729) — PDF outline /Title strings may be UTF-16BE/LE with a leading BOM or PDFDocEncoding; decoding now routes through optional_content::decode_pdf_text_string so non-Latin bookmark labels read correctly.
  • A damaged ICC profile no longer aborts the OutputIntents scan (#712, #731) — a single broken entry in /OutputIntents previously stopped the whole colour-profile scan; the scan now skips the damaged profile, logs it, and continues so the remaining intents are still read.
  • Spurious space inside prime-notation numbers (math/astronomy) — a prime's narrow metric advance (w₀, ISO 32000-1 §9.4.4) is small relative to its inked form, so the geometric word-break heuristic injected a space before a following decimal or period: 0′′.280′′. 28, Y′.Y ′ .. Arc-second values and prime-suffixed tokens (0′′.28, 1′′.47, δnn′.) now stay intact, while genuine feet-and-inches like 5′ 6″ are preserved. Surfaced by the py-pdf/benchmarks text-extraction corpus.
  • Signed unit exponents wrongly rewritten to Unicode subscripts — the super/subscript pass synthesized Unicode sub/superscript glyphs from glyph geometry, overriding the authoritative ToUnicode decode (§9.10) and firing inconsistently on identical occurrences; a scientific unit exponent such as s−1 could become s₋₁. A run that opens with a minus/hyphen sign and contains a digit is now left as ASCII, matching the plaintext convention every reference extractor follows.
  • Markdown table header cells no longer force-bolded — emitted table headers (| **CRN** | **Type** |) dropped the ** wrappers so header text matches the source weight (| CRN | Type |).
  • Spurious spaces between CJK/Hangul text and brackets — a stray space a producer inserted between an ideograph/Hangul syllable and an adjacent bracket ((Xa, Za) 的(Xa, Za)的, 消灯 (0%)消灯(0%), 관찰되었다 (Fig. 3)관찰되었다(Fig. 3)) is dropped; full-width CJK brackets are left alone.
  • Line-end hyphen joins kept space-free — a word split across a line break by a hyphen no longer gains a stray space at the join.

Changed

  • Dependencies / CItaiki-e/install-action 2.81.8 → 2.81.9 (#717); CI hardening for single-OS flake isolation and network-setup retries (macOS Homebrew, crates.io publish-check). The page renderer is now exercised by a dedicated CI tier (rendering,test-support,cjk-render-fallback), which previously compiled to zero tests under the default feature set.

Thanks

  • RayVR (Ray V. Roberts) — contributed bundled-fallback substitution of Adobe predefined CIDFonts and the §11 transparency compositing surface with the optional lcms2 ICC backend (#730, #674).
  • @regularkevvv (Kevin Castro) — reported and fixed the cross-document font-cache /ToUnicode key collision that could serve one document's font to another in a long-lived process (#733).
  • Norbert Preining — fixed invalid inline annotation /AP appearance streams, the leaked pyo3 Py<Self> receiver parameter in the generated .pyi, and UTF-16BE/LE bookmark-title decoding (#713, #728, #729).
  • Mike Beschastnov — hardened the OutputIntents scan to skip a damaged ICC profile instead of aborting (#712, #731).

Installation

Rust (crates.io)

cargo add pdf_oxide

Python (PyPI)

pip install pdf_oxide

JavaScript/WASM (npm)

npm install pdf-oxide-wasm

CLI (Homebrew)

brew install yfedoseev/tap/pdf-oxide

CLI (Scoop — Windows)

scoop bucket add pdf-oxide https://github.com/yfedoseev/scoop-pdf-oxide
scoop install pdf-oxide

CLI (Shell installer)

curl -fsSL https://raw.githubusercontent.com/yfedoseev/pdf_oxide/main/install.sh | sh

CLI (cargo-binstall)

cargo binstall pdf_oxide_cli

MCP Server (for AI assistants)

cargo install pdf_oxide_mcp

Pre-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.

Don't miss a new pdf_oxide release

NewReleases is sending notifications on new releases.