Skip to content

v0.5.0

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Oct 06:51
· 74 commits to main since this release
aa11e81

v0.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-damaged explicitly permits reported blank substitutions for eligible
    damaged CAJ pages. Exit 3 means partial output was committed. JS callers use
    allowDamaged: true and inspect omittedPages; 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, so PdfIndex::object_location
    returns UnsupportedFormat for those members. Memory remains bounded
    under Limits; 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, absolute bigint input offset).
  • Rust breaking change: ConversionOptions adds allow_damaged;
    ConversionReport adds omitted_pages and is no longer Copy.
    HN/C8 report wrappers containing it are also no longer Copy.
    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; Rust substituted_glyphs and JavaScript
    substitutedGlyphs report the count. This does not identify its semantics.
  • Rust breaking change: ConversionReport adds substituted_glyphs: u64.
    Migrate complete report literals by adding substituted_glyphs: 0 or using
    ..ConversionReport::default(). JavaScript reports add a bigint property;
    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.