Skip to content

v1.6.0 — decrypt, stream, fill, chart, extract, re-secure

Latest

Choose a tag to compare

@Nizoka Nizoka released this 19 Jul 19:57
· 1 commit to main since this release
f88c3b0

pdfnative v1.6.0

Released 2026-07-19

A feature release that completes both v1.6.0 roadmap items — a Standard Security
Handler reader/decryptor and streaming page-tree manipulation — and adds
six differentiating capabilities: fill & flatten of existing AcroForm PDFs,
native vector charts, text extraction, the completed encrypted round
trip
(re-encrypt merge/split output + encrypted incremental updates, both
pulled forward from v1.7), and an expanded colour-emoji subset. All
additions are additive and opt-in; unchanged code paths remain
byte-identical to v1.5.0 (guarded by new page-tree golden fixtures). Zero
runtime dependencies preserved. 100% backward-compatible with v1.5.0.

Highlights

  • Full encrypted round trip: open → edit → extract → re-secure.
    openPdf(bytes, { password }) decrypts RC4, AES-128 and AES-256 documents
    transparently; the merge/split API ingests encrypted sources and
    re-encrypts its output
    (MergeOptions.encrypt — including password
    rotation in one call); fillForm / flattenForm / addAnnotation append
    encrypted objects to encrypted documents under their existing scheme.
    No other zero-dependency JS library closes this loop.
    Guide →
  • Text extraction. extractText() decodes any PDF's content streams into
    per-page reading-order Unicode text plus optional positioned runs —
    /ToUnicode, /Differences and WinAnsi/MacRoman decoding, encrypted
    documents included, hard memory caps for untrusted input. Built for RAG/AI
    pipelines and search indexing. Guide →
  • Constant-memory streaming merge/split. streamMergedPdfs /
    streamSplitPdf / streamExtractPages emit fixed-size chunks, byte-identical
    to the buffered functions. Guide →
  • Fill & flatten existing forms. readFormFields / fillForm /
    flattenForm operate on any AcroForm PDF via non-destructive incremental
    update — encrypted ones included. Guide →
  • Native vector charts. A new chart block draws bar / horizontal-bar /
    line / pie / donut charts as pure PDF path operators. Guide →
  • 1167-glyph colour-emoji subset (up from 221) — now covering the complete
    assigned Transport & Map block and the full Misc Symbols & Pictographs
    ranges the header documents, within a ~4 MB budget.
    Guide →

Added

  • feat(parser): Standard Security Handler reader/decryptor. openPdf(bytes, { password }) decrypts RC4 (V1–V4), AES-128 (V4/R4), and AES-256 (V5/R6)
    documents — strings and streams — with user/owner-password authentication and
    crypt-filter dispatch. New reader.encryption; typed PdfPasswordError /
    PdfEncryptionUnsupportedError. The page-tree API accepts PdfSourceInput
    ({ bytes, password }) and MergeOptions.password; output is always
    unencrypted. New src/parser/pdf-decrypt.ts.
  • feat(parser): streaming page-tree manipulation. streamMergedPdfs(),
    streamSplitPdf(), streamExtractPages() (StreamMergeOptions.chunkSize,
    default 64 KiB) yield the assembled document as chunks, holding only the
    cross-reference offsets in memory; composable with streamToFile().
  • feat(core): fill & flatten existing AcroForm PDFs. readFormFields(),
    fillForm() (regenerated self-contained Helvetica appearances; checkbox/radio
    preserve their own /AP states), flattenForm() (stamps appearances, drops
    the interactive layer). Incremental update preserves prior signatures. Typed
    FormFieldNotFoundError / FormValueTypeError / FormUnsupportedError. New
    src/core/pdf-form-fill.ts.
  • feat(core): native vector charts. ChartBlock (bar / barH / line /
    pie / donut) rendered as pure PDF path operators — multi-series, legend,
    "nice" 1/2/5×10ⁿ ticks, gridlines, negative values, injection-safe colours,
    and a tagged /Figure + /Alt (PDF/A-safe). New src/core/pdf-chart.ts.
  • feat(fonts): curated colour-emoji subset expanded from 221 to 1167
    single-codepoint glyphs (~4.0 MB) — complete assigned Transport & Map block
    (U+1F680–1F6FF), Misc Symbols & Pictographs through U+1F53D plus clocks and
    emoji-presentation stragglers — with a build-time size guard. Flag / ZWJ /
    skin-tone sequences remain out of scope (use npx pdfnative-build-emoji-font --download --all for full coverage).
  • feat(crypto): new internal decryptor primitives — an incremental MD5
    hasher (lifting the internal one-shot MD5 512 MB ceiling) and AES-CBC /
    AES-ECB decryption routines powering openPdf. Internal to the parser;
    the public crypto surface is unchanged.
  • feat(parser): text extraction. extractText(bytes, options?) returns
    per-page reading-order Unicode text and optional positioned runs. Decodes
    /ToUnicode CMaps, /Encoding /Differences (compact AGL subset), and
    WinAnsi/MacRoman base tables; recurses Form XObjects; works on encrypted
    documents (options.password); hard maxTextLength cap for untrusted
    input. New src/parser/pdf-text-extract.ts.
  • feat(parser): re-encrypt page-tree output. MergeOptions.encrypt
    (AES-128 default / AES-256) re-encrypts mergePdfs / splitPdf /
    extractPages and streaming output under fresh passwords/permissions —
    password rotation in one call, CSPRNG required, RC4 never emitted.
  • feat(parser|core): encrypted incremental update. fillForm /
    flattenForm / PdfModifier.addAnnotation append objects encrypted under
    the document's existing scheme (RC4 / AES-128 / AES-256); the incremental
    trailer carries /Encrypt forward; addRawObject fails fast on encrypted
    documents.

API additions

Symbol Kind Purpose
openPdf(bytes, { password }) option Decrypt an encrypted PDF on open
reader.encryption property { algorithm, revision, authenticatedAs } or null
PdfSourceInput, MergeOptions.password type/option Ingest encrypted merge/split sources
PdfPasswordError, PdfEncryptionUnsupportedError class Decryptor error model
streamMergedPdfs, streamSplitPdf, streamExtractPages fn Constant-memory page-tree streaming
StreamMergeOptions, SplitPdfStream type Streaming options / per-range output
readFormFields, fillForm, flattenForm fn Read / fill / flatten existing forms
ParsedFormField, FillFormOptions, FlattenFormOptions type Form-fill surface
FormFieldNotFoundError, FormValueTypeError, FormUnsupportedError class Form-fill error model
ChartBlock, ChartSeries, ChartType type Native chart block
renderChartBlock, estimateChartHeight, niceTicks fn Chart rendering primitives
extractText(bytes, options?) fn Per-page reading-order text extraction
ExtractTextOptions, ExtractedPageText, ExtractedTextRun type Text-extraction surface
MergeOptions.encrypt option Re-encrypt merge/split/extract output (AES)
FillFormOptions.password, FlattenFormOptions.password option Encrypted incremental fill/flatten (now supported)

Fixed

  • fix(crypto): computeHashR6 now uses the required SHA-256/384/512 rotation
    (ISO 32000-2 Algorithm 2.B) instead of SHA-256 everywhere, so AES-256 (R6)
    output is spec-compliant. The decryptor keeps a legacy-hash fallback so
    documents written by pdfnative ≤ 1.5.0 still open.
  • fix(core): encrypted documents now encrypt all strings (Info metadata,
    annotation /Contents, outline titles, URIs), not just streams — a
    spec-compliant reader previously decrypted those strings to garbage. The
    /Encrypt dict and trailer /ID stay exempt; non-encrypted output is
    unchanged.
  • fix(chart): bar/line values are clamped to the plot band, so an explicit
    axis.yMin/yMax excluding part of the data never draws outside the chart.
  • fix(fonts): the curated colour-emoji list omitted ranges its own header
    claimed (rest of Misc Symbols & Pictographs, Transport & Map), leaving 15
    tofu in color-emoji-basic.pdf. Coverage completed; the data test now
    hard-asserts full curated-cmap coverage and cross-checks the showcase
    generator so the sample can never regress.
  • fix(parser): the incremental modifier's trailer now carries /Encrypt
    forward (previously dropped — any incremental update on an encrypted
    document produced an unreadable appended revision).
  • fix(shaping): the Arrows block (U+2190–U+21FF) is now classified as math
    by isMathCodepoint/detectCharLang, routing arrows to the bundled Noto
    Sans Math font like the operator blocks.
  • fix(parser): the xref-table reader tolerates stray blank lines between
    subsections and before trailer (seen in hand-assembled real-world PDFs,
    accepted by desktop readers).
  • fix(samples): math/math-symbols.pdf rendered every math symbol as ?
    since v1.5.0 (the generator omitted the math font from fontEntries); fixed
    and guarded by an end-to-end no-? extraction test. The three
    digital-signature-*.pdf samples no longer emit a stray blank line in their
    hand-assembled xref table. charts/charts-tagged.pdf now embeds Noto Sans
    (its base-14 text failed veraPDF's PDF/A-2b font-embedding rule).

Downstream integration notes

  • pdfnative-mcp / pdfnative-cli / pdfnative-react: re-pin to
    pdfnative@^1.6.0. The decryptor lets merge_pdfs / split_pdf ingest
    encrypted inputs, fillForm / flattenForm unblock a fill_form tool,
    extractText upgrades extract_text from best-effort to full
    content-stream decoding (encrypted PDFs included), and
    MergeOptions.encrypt enables password-rotation / re-protection tools.

Security

  • No known vulnerabilities fixed in this release. The R6 hash and all-strings
    encryption fixes make pdfnative's own encrypted output correctly decryptable by
    compliant readers; the decryptor performs constant-time password comparison and
    rejects unsupported handlers rather than failing open.

Compatibility

  • Zero runtime dependencies. Node ≥ 22. All new features are additive/opt-in;
    unchanged code paths are byte-identical to v1.5.0 (page-tree golden fixtures).
  • AES-256 (R6) encrypted output changes bit-for-bit from v1.5.0 because the
    R6 hash is now spec-correct (encryption is non-deterministic regardless); older
    files still open via the decryptor's legacy-hash fallback.

Verification

  • npm run typecheck:all — clean (src + tests + scripts)
  • npm run lint — clean
  • npm test — 104 files / 2379 tests, all green (+11 files / +161 vs v1.5.0)
  • npm run test:visual — pixel-diff regression green
  • npm run build — ESM + CJS + .d.ts, dist verified
  • npm run test:generate — 227 sample PDFs (forms/, charts/, parser/ added; encrypted fill/annotate/re-encrypt variants)
  • npm run validate:pdfa — veraPDF green

Install

npm install pdfnative@1.6.0

Upgrade

No breaking changes. Drop-in replacement for v1.5.0.

Links