Repository navigation
v0.5.0
Pre-releasev0.5.0 — synchronous core, Worker APIs and more document profiles
This unstable release replaces the asynchronous Rust core and scratch bitmap
stores with synchronous ranged reads, sequential writes and per-image,
per-dictionary or per-page allocations bounded by Limits. Browser and Node.js
conversion runs in a Worker. The CLI retains its conversion commands and adds
explicit damaged-CAJ conversion. Update native API consumers and JavaScript
call sites together with the matching WASM build.
Breaking changes and migration
This table describes the final v0.5.0 API. The changelog
records individual removals, error-message changes and intermediate refactors.
| Surface / old usage | v0.5.0 migration |
|---|---|
Rust asynchronous source/decoder calls, SequentialSink and scratch workspaces |
Remove .await; provide RangedSource (or native::SeekableSource) and std::io::Write. Remove scratch resources. Keep inputs unchanged for the entire operation; HN/C8 payload SHA-256 rechecks are removed. |
| Separate format dispatch | Call the root convert, inspect, inspect_pages, read_outline or index_pdf facade. ConversionOptions is borrowed and carries format/font policy. Prefer ..ConversionOptions::default() in literals. |
| Codec-specific budget structures | Use one Limits; former per-codec budget parameters are removed. max_image_pixels and max_symbols cap decoded bitmaps and dictionaries; max_allocation_bytes caps individual retained buffers. |
| Codec-specific error enums | Match Error.kind (ErrorKind), then use offset, context and reason. Removed error payloads are not available. HN/C8 I/O/cancellation now uses JS IO/CANCELLED. |
| Complete report literals or copying reports | ConversionReport gains outline, application_info, images, omitted_pages and substituted_glyphs; use Default where appropriate. Reports containing omissions are no longer Copy; borrow or clone explicitly. DocumentInfo.page_count is Option<u32>. |
JS instance/exports and {size, readAt} wrappers |
Pass the WebAssembly.Module from loadModule() and a browser File/Blob/OPFS handle, or a Node path/URL/descriptor/Blob. Remove blobSource, fileHandleSource and hnc8.scratch. Font sources use the same input types. |
| Custom raw WASM poll/resume hosts | Replace caj2pdf_start/caj2pdf_io_* with synchronous caj2pdf_convert/caj2pdf_inspect and the five host imports in the ABI reference. Rust hosts use engine::Session/Host, not Engine/Request. |
TrueTypeFont, whole-font embedding |
Use OpenTypeFont, supply a face index (0 for a single face), and call embed_font after the last draw before finish. Fonts are subsetted/compressed; CFF and collection faces are supported. |
| Caller-provided QM/MQ tables | Remove CLI --qm-states/--mq-states and JS hnc8.qmStates/mqStates. Built-in standard codec states are used. Removed selected-image helpers are replaced by document conversion; see the changelog for low-level decoder removals. |
| CLI native-text input without font options | Installed fonts are discovered automatically. Use --no-system-fonts for the old no-search behavior, or pass explicit font paths. JS never searches installed fonts. |
| Output byte identity and early size failures | Font/content compression, font subsetting, top-first type-0 image matrices and the outline trailer /ID change PDF bytes. Output limits can fail during sequential writing; stage output if atomic publication is required. Do not compare old/new PDF hashes as a fidelity test. |
// Before: convert(instance, blobSource(file), sink, { hnc8: { scratch } })
// v0.5.0: file is a browser File; sink is a supported sequential sink.
const module = await loadModule();
await convert(module, file, sink, { signal });For prompt browser cancellation and sink backpressure use a cross-origin
isolated page (SharedArrayBuffer). Otherwise cancellation terminates the
Worker. Spooled inputs are now a Node path or OPFS handle. See the
JS examples and native architecture.
Conversion improvements and remaining limits
- More measured CAJ/PDF/KDH profiles: bounded source-path metadata repair,
absent optional link appearances, equivalent opacity resources, interrupted
live prefixes, duplicate identical page boxes, PNG Up xrefs and Flate
object-stream metadata. Unknown or contradictory profiles still fail. - More measured HN-A, C8 and HN-B profiles, including generic-only JBIG2,
empty symbol content, uncompressed images, full-page JPEG region records,
native article records and HN-B type-3 images after text. --allow-damagedexplicitly permits reported blank substitutions for eligible
damaged CAJ pages. Exit 3 means partial output was committed. JS callers use
allowDamaged: trueand inspectomittedPages; default conversion stays strict.- Native text needs caller/installed fonts; no fonts are bundled. Substitute
fonts are not original-font fidelity. A measured private-use HN-B character
may use a reported visual substitute while preserving U+E6C7 in ActualText. - C8/HN-B bookmarks remain unverified and omitted with a warning (#303).
Image pages receive no OCR. TEB remains unsupported. Corpus recovery and
source-fidelity work (#406, #414, #415, #420) remains open.
Validation and artifacts
The release workflow reruns locked native/WASM quality gates, the complete
platform inventory, actual npm tarball tests on Node and Chromium, and
artifact provenance verification. Windows/macOS binaries remain unsigned.
GitHub assets include native CLI archives, JS/WASM, OCI and SHA256SUMS;
verify them using the provenance guide.
The registry packages are caj2pdf-core, caj2pdf-cli (binary caj2pdf) and
npm caj2pdf-rust. caj2pdf-wasm remains unpublished as a Rust crate.
Initial registry publication uses owner authentication; trusted publishers
are configured after bootstrap. A GitHub release alone is not proof that a
registry upload succeeded. Release policy documents
this transition and the required clean-install checks.
The conformance record separates corpus conversion,
runtime parity, glyph/image checks and selected source-render comparisons.
The 1,232 accepted originals at #408 passed native/Node/Chromium parity;
that is historical source evidence, not a fresh full-corpus run of this tag.
Optional external corpus tests for this release are NOT_RUN unless a separate
receipt records them. Filtered guest rendering tests are not compatibility
passes; their portable conversion checks and host smoke tests are separate.
The #346 source-size target remains unmet (37.9k vs 30k at its recorded
checkpoint); this release does not claim it is complete.
Detailed conversion checkpoints
The entries below preserve the recorded scope and timing of each pre-release
validation checkpoint; later checkpoints supersede earlier failure counts.
Unescaped CAJ source-path metadata (#419)
- A measured 139-page CAJ original now converts after two malformed QITE
source-path strings are preserved as hexadecimal strings; all 35 source
bookmarks remain intact. All incoming
references must be confined to retained Page metadata; rendering strings
and unmeasured profiles remain strict. - The repair uses bounded reads, source rechecks and existing sequential
reconstruction. No public API or dependency changes. See the
scoped conformance evidence.
Absent optional CAJ link appearances (#417)
- A measured 109-page CAJ original now converts after proving two optional
Link appearance targets absent. Its live destinations, 574 source streams
and 70 source bookmarks remain intact. Required references and unmeasured
appearance/border profiles remain strict errors. - Existing bounded metadata reads, retained repair budgets and sequential
reconstruction apply. No public API or product dependency changes. See the
scoped source comparison.
Equivalent PDF opacity resources (#412)
- Two KDH originals now convert after proving duplicate ExtGState references
carry exactly equal direct CA/ca values. Unknown or conflicting resources
remain errors; equal-looking rounded numbers are never used as proof. - All 14 pages and 151 streams match independent source decoding, and output
bytes agree across native, Node.js and Chromium. See the
checkpoint.
No public API or dependency changes; target reads, metadata and incremental
repairs remain bounded. Existing v0.4.0 artifacts are unchanged.
Interrupted live PDF prefixes (#410)
- A measured seven-page KDH file now converts after bounded validation and
whitespace replacement of inactive object prefixes. Only exact prefixes of
xref-selected complete non-stream dictionaries/integers qualify; conflicts,
stream objects and unrelated gap contents remain errors. - Native, Node.js and Chromium outputs match; all page renders, text, geometry
and 22 raw streams match the independent source PDF. See the
checkpoint.
No public API or dependencies change; reads, parser metadata and retained
patches remain bounded under existing limits.
Corpus runtime and order verification (#406)
- All 1,232 accepted originals at PR #408 pass native/Node/Chromium byte parity
and browser cleanup. All 26 formerly failing applicable order checks pass
after correcting bitmap row conventions and adding native glyph checks. - The checkpoint
records the scope and remaining source-fidelity/refusal work. Conversion and
runtime parity do not close #406 or change released v0.4.0 artifacts.
Identical CAJ fragment page boxes (#407)
- CAJ reconstruction now removes the second of two identical direct
MediaBox values on a Page or Pages dictionary while copying that object.
Conflicting values and other duplicate-key profiles remain errors. - Four original documents (308 pages, 218 outlines) pass native/Node/Chromium,
qpdf and the source-content checks.
Stream bytes and object identities are preserved. No public API shapes or
dependencies change; reconstruction retains only fixed-size span metadata
under the existing allocation limits.
PNG Up xrefs and compressed PDF metadata (#402, #404)
- The measured KDH/PDF profile now converts through bounded PNG Up xref
decoding and Flate object-stream metadata loading. Revision precedence and
original stream bytes are preserved; unknown neighboring profiles fail. - The original 10-page sample passes native, Node and Chromium with identical
output bytes. All page renders at 72 dpi, text, geometry and 40 raw streams
match the unchanged decoded PDF; qpdf is clean. See the
checkpoint. - No public API shapes or dependencies change. Newly accepted compressed
objects have no standalone source span, soPdfIndex::object_location
returnsUnsupportedFormatfor those members. Memory remains bounded
underLimits; this does not enable other PNG algorithms or general
object-stream profiles. Existing v0.4.0 artifacts are unchanged.
GitHub corpus validation (#385)
- The full 1,277-document native rerun after #386–#394 adds 99 conversion
passes, with zero conversion regressions: 1,227 converted, 39 failed/strict
refusals and 11 explicitly unsupported. Qpdf reports 1,226 clean outputs
and one retained source-content warning. - The checkpoint
links the pinned per-input results, all 50 refusal classifications and
remaining fidelity limits. Full-corpus JavaScript/visual parity is NOT_RUN;
26 ancillary image-order failures remain visible. These are unreleased
source results and do not change existing v0.4.0 artifacts.
C8 native article records (#391)
- The measured decoration, unequal CJK title, style-5 book-title marks and
arrow, table-line records and metadata value now preserve the complete
four-page article. Unknown neighboring profiles remain explicit errors. - Native, Node and Chromium outputs match. The checkpoint
records full glyph-order and scoped geometry checks, including the default
decoration substitution limit. No API shape, dependency or memory-allocation
class changes.
C8 generic-only JBIG2 images (#392)
- HN/C8 type-3 images with the measured page-information plus full-page
generic-region profile now decode through the existing bounded row decoder.
Apparently blank images retain their actual pixels; unknown profiles and
corrupt bodies still fail. - All 9 affected documents (224 pages) convert. The checkpoint
records full image-pixel comparisons and neighboring-page checks. No native,
CLI or JavaScript API shapes or dependencies change.
C8 uncompressed image records (#390)
- C8 pages starting with the measured single unmarked image record now use
the existing bounded tagged-record parser. Compressed frame validation and
the separate native-text path keep their existing requirements. - All 13 affected documents (227 pages) convert. The checkpoint
records source-image, page geometry and scoped rendering evidence. No native,
CLI or JavaScript API changes or new dependencies.
C8 JBIG2 empty content (#389)
- The explicit HN/C8 text-header policy now permits ordinary signed
displacement values with the measured unused refinement-template bit.
HN/C8 composition also accepts a terminal-only empty symbol dictionary
after validating zero counts and the exact marker. Strict standalone
JBIG2 decoding keeps its existing requirements. - Fifteen of the 25 affected documents now convert; ten reach separate
page-2 refusals tracked by #390/#392. All 28 affected image pixel comparisons
pass. See the checkpoint.
No native, CLI or JavaScript API shape changes.
HN-A full-page JPEG regions (#388)
- HN-A pages containing one full-page JPEG and one or two additional region
records now convert with the image, page dimensions and ordering preserved.
The parser validates the complete measured record profile and zlib frame;
it does not substitute blank pages or drop unknown decoder failures. - No native, CLI or JavaScript API changes. See the
conformance checkpoint
for the 12-document run and scoped fidelity evidence.
Explicit damaged CAJ conversion (#297)
- CLI: opt in with
caj2pdf damaged.caj --allow-damaged -o partial.pdf.
Exit 3 means the PDF was committed with reported blank substitutions.
Default strict behavior is unchanged. - Browser and Node: pass
{ allowDamaged: true }and inspect
report.omittedPages, an array of{ pageIndex, offset }(zero-based
index, absolutebigintinput offset). - Rust breaking change:
ConversionOptionsaddsallow_damaged;
ConversionReportaddsomitted_pagesand is no longerCopy.
HN/C8 report wrappers containing it are also no longerCopy.
Migrate option literals using..ConversionOptions::default();
borrow reports or explicitly clone them. No whole-file conversion API is added. - Raw WASM: the existing start option word uses bit 0 for bookmarks and bit 1
for damaged CAJ mode. New count/index/offset exports expose omissions.
Use the JavaScript module with its matching WASM build.
See partial conversion behavior
for remaining hard errors and shared-resource limitations. This is an unstable
feature intended for the next minor release, not a change to v0.4.0 artifacts.
HN-B magnesium article conversion (#381)
- The measured 12-page HN-B profile now converts with all native glyphs and
type-3 images. Additional size/symbol/metadata rules and bilevel composition
preserve source order; unknown profiles remain errors. - One observed private-use code retains U+E6C7. If the caller's Latin font lacks
it, a documented visual substitute is drawn with the original code in PDF
ActualText. The CLI warns; Rustsubstituted_glyphsand JavaScript
substitutedGlyphsreport the count. This does not identify its semantics. - Rust breaking change:
ConversionReportaddssubstituted_glyphs: u64.
Migrate complete report literals by addingsubstituted_glyphs: 0or using
..ConversionReport::default(). JavaScript reports add abigintproperty;
use the JS package with its matching rebuilt WASM module. - See the conformance checkpoint
for selected comparisons, font limitations and NOT_RUN optional-corpus status.
This changes unreleased builds, not existing v0.4.0 artifacts.