caj2pdf-core 0.8.1

Platform-neutral CAJ-family conversion core
Documentation

caj2pdf-rust

Convert CNKI CAJ-family documents (CAJ, KDH, HN, C8) to PDF from the command line, Node.js or the browser. MIT-licensed Rust with a WebAssembly build. Memory stays bounded: input is read by range and output is written as a stream. Releases are v0.x: CLI behavior and APIs may still change, and HN/C8 support is experimental.

Quick start

Download the CLI archive for your platform from GitHub Releases, extract it, and run:

caj2pdf paper.caj                  # writes paper.pdf next to the input
caj2pdf paper.caj -o out.pdf       # explicit output; --force replaces a file
caj2pdf inspect paper.caj --json   # format, page count, bookmarks

Or install it from crates.io with Rust 1.88 or newer and curl:

cargo install --locked caj2pdf-cli

Releases also carry a container image (Docker usage) and a JavaScript tarball for Node.js 22+ and browsers (JS API). The JavaScript package is also on npm:

npm install caj2pdf-rust

Supported operating systems and CPUs are listed in the platform matrix.

The JavaScript API runs each conversion in a Worker. It reads a browser File, Blob or OPFS file handle, or a Node.js path, descriptor or Blob, and writes to a stream:

import { convert, loadModule, webWritableSink } from "caj2pdf-rust";

const writer = (await (await showSaveFilePicker()).createWritable()).getWriter();
await convert(await loadModule(), file, webWritableSink(writer), { signal });
await writer.close();

What converts

Input Status
PDF Supported: copied, with documented repairs. The TTKN server profile requires an explicit response.
CAJ Supported, with its bookmarks.
KDH Supported for embedded PDFs.
HN-A Experimental: scanned page images and bookmarks; no text layer.
C8, HN-B Experimental: image pages, and native text pages with automatic bundled fonts (below). No bookmarks yet.
CAA Recognized target descriptor: inspection only; obtain the referenced document to convert it.
TEB Recognized, conversion not implemented; detection does not establish encryption or recoverability.

The support matrix is the source of truth, with the verified profiles and remaining differences. Unsupported content fails with a located error instead of dropping pages.

Extensions do not determine the format: the measured .nh sample contains HN-A bytes and converts through that path (433 pages, 365 bookmarks; validation). Historical CAJViewer documentation also names CAS, but no authentic sample has been found; CAS research remains open.

The measured C8 subset includes the NJU title, resource-control and line-segment profiles documented in provenance. Other record/style combinations can still be refused.

Fonts for C8 and HN-B text pages

CLI and JavaScript conversions automatically use the measured CAJViewer font preset, including mode-specific parameters. No font installation or extra conversion arguments are needed. Native binaries work offline; Node reads fonts from the package, and the browser downloads and caches them on first use.

The resource pack adds about 58.6 MiB before compression. It stays outside the core/WASM module, and ranged reads keep it outside the WASM heap. Complete original font programs also increase the size of native-text PDFs. See bundled fonts for browser hosting, offline source builds and custom-font overrides. Explicit fonts remain available through --fonts DIR or JavaScript hnc8.fonts.

Limits

  • C8/HN-B outlines are not verified, so those PDFs have no outline and the CLI prints a warning. --no-bookmarks silences it.
  • HN-A pages are images. Run an OCR tool such as ocrmypdf for search.
  • Substitute fonts do not reproduce the CAJViewer typography pixel for pixel. A measured HN-B private-use glyph may use a visual approximation with an explicit warning; its private-use code is retained in PDF ActualText.
  • TEB files cannot be converted.

Library

Add the Rust library with cargo add caj2pdf-core.

The caj2pdf-core crate is the engine behind the CLI and the WASM build. It reads any RangedSource (SeekableSource over a File, or a byte slice) and writes any std::io::Write:

use caj2pdf_core::{ConversionOptions, Limits, NeverCancel, convert, native::SeekableSource};
use std::{fs::File, io::BufWriter};

let mut input = SeekableSource::new(File::open("paper.caj")?)?;
let mut output = BufWriter::new(File::create("paper.pdf")?);
let report = convert(&mut input, &mut output, ConversionOptions::default(),
                     &Limits::default(), &mut NeverCancel)?;

convert detects the format and converts it; inspect returns the format, page count, outline and structure. ConversionOptions selects bookmarks, damaged-CAJ substitution and C8/HN-B fonts; a Progress receives the detected format and read progress and can cancel. The I/O architecture describes the contract.

More

Contributing, provenance and license

Source code is MIT. Code must be original: do not copy from the Python or Go converters or from decoders under other licenses. The provenance record lists format references, the external test corpus rules and the dependency review. Sample documents are not part of this repository; the separate CAJSamples collection is an optional compatibility corpus. See CONTRIBUTING.md for checks, commit style and the merge gate, and CHANGELOG.md for changes. The project's public language is English.