Skip to content

Releases: Nizoka/pdfnative

v1.3.0 — COLRv1 colour emoji, USE-lite shaper integration, true constant-memory streaming, UAX #9 X4–X5 overrides, pixel-diff visual regression, #48 CP-1252 fix

08 Jun 17:53
309d515

Choose a tag to compare

Closes issue #48 (CP-1252
extended characters not extractable under base-14 Helvetica) and delivers the
complete v1.3.0 roadmap with zero deferrals: COLRv1 colour emoji,
USE-lite shaper integration, true constant-memory streaming, UAX #9 X4–X5
character-level overrides, and a dual-mode pixel-diff visual-regression suite.
100% backward-compatible — every new feature is additive or opt-in, and
pre-existing PDFs are byte-identical for unchanged code paths.

Still zero runtime dependencies. 71 test files / 1982 tests, all green.

Highlights

  • feat(shaping): Telugu script (te). A new pure-JS GSUB/GPOS
    mini-shaper (src/shaping/telugu-shaper.ts) brings Telugu (~95 M speakers,
    ISO 15924 Telu, U+0C00–U+0C7F) to pdfnative’s 16 existing scripts. It builds
    virama-mediated conjunct clusters, forms subjoined-consonant ligatures via the
    shared gsub-driver, and positions above/below vowel signs and modifiers via
    the shared gpos-positioner — with no reph and no pre-base reordering
    (Telugu specifics). Bundled font pdfnative/fonts/noto-telugu-data.js (Noto
    Sans Telugu, OFL-1.1). Real-font shaping of తెలుగు / నమస్తే / క్షి /
    శ్రీ / జ్ఞ produces zero .notdef and correct conjuncts. Opt-in via
    registerFont('te', () => import('pdfnative/fonts/noto-telugu-data.js')).
    (src/shaping/telugu-shaper.ts)

  • feat(shaping): Five underserved scripts — Amharic/Ethiopic (am),
    Sinhala (si), Tibetan (bo), Khmer (km), Myanmar (my).
    Five new
    pure-JS mini-shapers extend pdfnative from 17 to 22 Unicode scripts,
    following the Telugu model (shared gsub-driver + gpos-positioner,
    zero-dependency, pure functions). Ethiopic (U+1200–U+137F) is a syllabic
    abugida needing no reordering — detection + font routing only.
    Sinhala (src/shaping/sinhala-shaper.ts, U+0D80–U+0DFF) builds
    virama conjuncts, reorders the pre-base kombuva (U+0D9A-class), and
    decomposes two-part vowels. Tibetan
    (src/shaping/tibetan-shaper.ts, U+0F00–U+0FFF) performs vertical
    subjoined-consonant stacking. Khmer
    (src/shaping/khmer-shaper.ts, U+1780–U+17FF) is USE-lite — coeng
    subscripts, pre-base vowels, two-part vowel decomposition. Myanmar
    (src/shaping/myanmar-shaper.ts, U+1000–U+109F) is USE-lite — medials,
    pre-base medial-ra (U+103C) and e-vowel (U+1031), virama stacking. Khmer and
    Myanmar are pragmatic USE-lite implementations with documented limitations
    (two-part-vowel MultipleSubst is handled JS-side via shaper decomposition
    tables, not the OpenType extractor). Bundled fonts (all OFL-1.1):
    noto-ethiopic-data.js, noto-sinhala-data.js, noto-tibetan-data.js
    (Noto Serif Tibetan), noto-khmer-data.js, noto-myanmar-data.js. Opt-in via
    registerFont('am'|'si'|'bo'|'km'|'my', () => import('pdfnative/fonts/...')).

  • feat(core): Opt-in Unicode normalization (layout.normalize).
    PdfLayoutOptions.normalize?: 'NFC'|'NFD'|'NFKC'|'NFKD'|false (default
    false) applies native String.prototype.normalize to text before encoding,
    so decomposed input (e.g. combining diacritics) composes to the form the
    embedded font expects. Off by default → byte-identical output for existing
    callers. (src/core/encoding-context.ts)

  • fix(crypto): CSPRNG-only randomness. PDF encryption now throws
    if no cryptographically secure random source (crypto.getRandomValues) is
    available, instead of silently falling back to Math.random. Encryption keys
    and IVs are never derived from a non-CSPRNG source.
    (src/core/pdf-encrypt.ts)

  • feat(core): Configurable document block limit (layout.maxBlocks).
    The previously hard-coded 10 000-block safety cap in assembleDocumentParts()
    is now configurable and the default raised to 100 000 (DEFAULT_MAX_BLOCKS).
    Large reports (e.g. multi-thousand-page medical or financial documents) no
    longer hit a spurious ceiling; callers can raise or lower it per document via
    layout.maxBlocks. The over-limit error now names the active limit and how to
    change it. (src/core/pdf-document.ts, src/core/pdf-layout.ts)

  • feat(parser): validatePdfUA() — PDF/UA structural validator. A new
    read-only, zero-byte-risk developer gate (ISO 14289-1) that parses a PDF and
    checks /MarkInfo /Marked, /StructTreeRoot + /ParentTree, /Metadata,
    /Lang, and per-page /MCID uniqueness. Returns
    { valid, errors, warnings }. Complements (does not replace) veraPDF.
    (src/parser/pdf-ua-validator.ts)

  • fix(shaping, colour emoji): No more tofu from selectors/joiners.
    Emoji variation selectors (VS-15/VS-16), the ZWJ/ZWNJ, and Fitzpatrick
    skin-tone modifiers that no registered font covers are now dropped during
    run-splitting
    instead of resolving to .notdef (the  box). Joiners are
    still preserved when an Indic shaper font maps them. New isZeroWidthFormat()
    predicate. (src/shaping/multi-font.ts, src/shaping/script-registry.ts)

  • fix(core, colour emoji): Computed Form /BBox (no clipping).
    renderColorGlyph() now derives each colour-glyph Form /BBox from the
    transformed contour bounds rather than the hard-coded em box, so colour emoji
    that dip below the baseline are no longer clipped. (src/core/pdf-color-glyph.ts)

  • feat(fonts): COLRv1 colour emoji. Noto Color Emoji (OFL-1.1) is
    bundleable as a curated subset (pdfnative/fonts/noto-color-emoji-data.js,
    221 colour glyphs, 936 KB). COLR v0 solid layers and COLR v1 linear / radial
    gradients render as native PDF Form XObjects (/Shading Type 2/3 +
    /ExtGState constant-alpha), one indirect object per unique glyph,
    forward-referenced into every page /XObject. The COLR / CPAL / glyf
    parsers are self-written and zero-dependency. Opt-in:
    registerFont('emoji', () => import('pdfnative/fonts/noto-color-emoji-data.js')).
    When not registered, monochrome emoji (Noto Emoji, v1.1.0) is unchanged and
    documents are byte-identical. Sweep gradients + Porter-Duff compositing fall
    back gracefully to monochrome (documented limitation). (src/core/color-emoji.ts, src/fonts/colr-parser.ts, src/fonts/glyf-outline.ts)

  • feat(core): True constant-memory streaming. buildPDFStreamTrue()
    and buildDocumentPDFStreamTrue() assemble the PDF into its raw object/
    framing parts and yield fixed-size Uint8Array chunks while freeing each
    part as it is emitted — the fully-joined PDF binary never materialises in
    memory
    . Peak memory is bounded by the chunk size plus the single largest
    part (a content stream or embedded font subset). Byte-identical output to
    buildDocumentPDFBytes() / buildPDFBytes(). The v1.2.0 object-boundary
    variants (*StreamPageByPage) and fixed-size variants (*Stream) are
    retained. (src/core/pdf-stream-writer.ts)

  • feat(shaping): UAX #9 X4–X5 overrides. resolveBidiRuns() now
    performs full character-level direction overrides inside LRO / RLO scopes —
    every codepoint within the scope is forced to strong L (LRO) or strong R
    (RLO) before the W/N/L rules run, not merely the base paragraph direction
    (the v1.2.0 behaviour). Nested embeddings and isolates recurse correctly.
    (src/shaping/bidi.ts)

  • feat(shaping): USE-lite shaper integration. The v1.2.0 cluster
    classifier (classifyUseCategory()) is now the joiner-classification
    authority across the Devanagari, Bengali, and Tamil shapers. Orphan ZWJ /
    ZWNJ no longer reach the cmap as .notdef; ZWJ between a halant/pulli and
    the next consonant continues a conjunct (half-form, eyelash-ra, ya-phalaa)
    while ZWNJ breaks it keeping a visible virama. (src/shaping/use-lite.ts)

  • test(visual): Dual-mode pixel-diff visual regression. Two
    complementary guards over self-contained extreme-script fixtures (Tamil,
    Bengali + Devanagari, Arabic) built with the real bundled fonts:

    1. a glyph-position snapshot that extracts every show operator's font,
      size, baseline x/y, and glyph IDs into a committed JSON baseline; and
    2. a rendered-glyph pixel diff that parses the embedded FontFile2
      outlines, scan-fills the shaped glyphs at their positions into a
      grayscale bitmap, and compares against a committed PNG baseline
      (≤1% pixel tolerance) using a self-written, zero-dependency grayscale PNG
      encoder/decoder.

    A CI workflow (visual-regression.yml)
    runs both, gated on src/shaping/**, src/fonts/**, src/core/**, and
    fonts/**. (tests/visual/)

  • fix(fonts, #48):
    CP-1252 extended characters. Base-14 Helvetica text now carries a
    /ToUnicode CMap, so the Windows-1252 high range (€ ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ
    Ž ' ' " " • – — ˜ ™ š › œ ž Ÿ) is correctly extractable and searchable in any
    viewer. When a latin font is registered (Noto Sans VF), these glyphs
    additionally embed and render so the Euro sign et al. are visible, not
    viewer-tofu. Falls back to the correct WinAnsi byte (byte-stable) when no
    latin font is registered. (src/fonts/encoding.ts)

  • fix(core, tagged PDF): Per-line MCID allocation in wrapped table cells
    and multi-line table captions. Previously a single MCID was allocated per
    ...

Read more

v1.2.0 — addSignaturePlaceholder, ASN.1 fix, page-by-page streaming, UAX #9 embeddings, USE-lite classifier, smart tables

27 May 21:04
5312a53

Choose a tag to compare

Closes issues #45 (addSignaturePlaceholder() API) and #46 (X.509 issuer/subject DN slice corruption), ships object-boundary page-by-page streaming, completes UAX #9 with embedding controls (LRE/RLE/LRO/RLO/PDF), and lands a USE-lite cluster classifier for future Indic shaper rewires. 100% backward-compatible. Every new feature is additive or opt-in. Pre-existing PDFs are byte-identical for unchanged code paths.

Two roadmap items intentionally slip to v1.3.0: COLRv1 colour emoji (renderer needs PDF shading-dictionary polish) and pixel-diff visual regression (PNG-baseline tooling). Monochrome emoji from v1.1.0 is unchanged. The USE-lite classifier ships as a public API in v1.2.0; rewiring the Devanagari/Bengali/Tamil shapers to consume it is the v1.3.0 follow-up.

Highlights

  • feat(crypto, #45): new addSignaturePlaceholder(pdfBytes, options?) API — inject an AcroForm + invisible signature widget plus a /Sig dictionary into any existing PDF via an incremental update (ISO 32000-1 §7.5.6). Idempotent: returns the input unchanged when an /FT /Sig widget already exists. Enables the one-call signPdfBytes(addSignaturePlaceholder(buildDocumentPDFBytes(...))) ergonomic that downstream tooling (pdfnative-cli) previously shipped as a local workaround.
  • fix(crypto, #46): parseCertificate() issuer and subject raw slices now correctly begin with the ASN.1 SEQUENCE tag 0x30. ASN.1 decodeAt() only patched direct-child offsets, so grandchildren carried offsets relative to their parent's value buffer rather than the original DER — producing malformed slices that broke CMS IssuerAndSerialNumber parsing in Adobe Reader and openssl-cms. decodeAt() now walks descendants recursively to absolutise every offset; a defensive raw[0] === 0x30 assertion lives at the parseName() boundary.
  • feat(core): buildDocumentPDFStreamPageByPage() and buildPDFStreamPageByPage() — emit an existing PDF binary as an AsyncGenerator<Uint8Array> chunked at PDF object boundaries (\nendobj\n). Useful for streaming the assembled PDF over HTTP / Node WriteStream without holding the full body in memory beyond a single chunk. Internal page-by-page assembly (one page object at a time before the final binary exists) remains a v1.3 target — flagged in the JSDoc.
  • feat(shaping): UAX #9 explicit embeddings — normalizeBidiEmbeddings() rewrites LRE / RLE / LRO / RLO / PDF (U+202A–U+202E) to their sealed-isolate equivalents (LRI / RLI / PDI) using a stack with max depth 125 before the BiDi resolver runs. resolveBidiRuns() invokes the normaliser internally, so existing callers gain support transparently. Combined with the v1.1.0 isolates work, pdfnative now handles every UAX #9 directional control in common use. Character-level direction overrides inside LRO/RLO scopes (UAX #9 X4–X5) are simplified — only the base direction is normalised; full override tracking is deferred until users demand it.
  • feat(shaping): USE-lite cluster classifier in src/shaping/use-lite.tsclassifyUseCategory(cp) + classifyClusters(cps) return per-cluster { base, reph, prebase, postbase, premarks, postmarks } with per-script tables for Devanagari, Bengali, and Tamil. Public API ready to ship; consumed by the v1.3.0 shaper rewire.
  • feat(core): Smart tables — planner-driven table rendering with automatic wrap-on-overflow, multi-page slicing with repeated headers, optional zebra striping, captions, and configurable minimum row height / cell padding. Six new optional TableBlock fields ship: wrap ('auto' | 'always' | 'never', default 'auto'), repeatHeader (default true), zebra, caption, minRowHeight, cellPadding. Existing tables that fit on one page are byte-identical to v1.1.0 output. Tagged-mode (/Table, /TR, /TH, /TD, /Caption) is preserved across slices via a shared structure-tree accumulator (ISO 14289-1 §7.10.6). See docs/guides/tables.md.

Fixed

  • fix(core, tables): renderTable no longer hardcodes the 4th column (i === 3) as the Amount column with Helvetica-Bold + credit/debit colouring. The styling is now driven by the explicit, opt-in ColumnDef.kind === 'amount' field. Combined with the wrap-aware truncate (next bullet), this resolves the table-smart-autofit.pdf clipping where the Notes column was unintentionally rendered bold and the auto-fit planner — measuring with regular metrics — sized the column too narrowly. The legacy buildPDF() financial path keeps the historical i === 3 heuristic for byte-identical v1.0/v1.1 output. (src/core/pdf-renderers.ts, src/types/pdf-types.ts)
  • fix(core, tables): emitCell only applies the v1.1 character-truncate (mx / mxH) when wrap: 'never'. Under wrap: 'auto' (the v1.2.0 default) and wrap: 'always' the planner has already sized the column to fit the text; the redundant char-truncate previously truncated text that genuinely fits, producing spurious ellipses in auto-fitted tables. (src/core/pdf-renderers.ts)
  • fix(crypto, #46): ASN.1 decodeAt() now recursively rewrites every descendant node's offset to be absolute against the original DER buffer. Previously, only direct children were patched, so parseName()'s fullDer.subarray(node.offset, …) returned a slice off by exactly the offset of the parent's value field. CMS signatures using these slices in IssuerAndSerialNumber now validate in Adobe Reader, openssl-cms, and pdfnative's own verify path. Defensive raw[0] === 0x30 assertion added at the parseName() boundary to catch any future regression. (src/crypto/asn1.ts, src/crypto/x509.ts)
  • fix(shaping): invisible Unicode bidirectional formatting characters (LRM/RLM U+200E/F, LRE/RLE/PDF/LRO/RLO U+202A–E, LRI/RLI/FSI/PDI U+2066–9) are now stripped at the encoder boundary. The BiDi resolver consumed them when it ran, but it only runs on RTL paragraphs — pure-LTR text containing an orphan PDF or isolate marker would otherwise reach the cmap as .notdef and render as tofu (􀀀). New public stripBidiControls(text) helper exported from the root; applied transparently in pdfString(), helveticaWidth(), and the Unicode encoding context's textRuns() / ps(). Zero behaviour change on text without control characters. (src/shaping/bidi.ts, src/fonts/encoding.ts, src/core/encoding-context.ts)- fix(fonts, tables): right- and centre-aligned bold text — table headers (Helvetica-Bold via enc.f2) and table captions — are now measured with Adobe Helvetica-Bold AFM advance widths instead of Helvetica-Regular. Pre-1.2.0, the renderer measured "Amount" at ~25.44pt (Regular) but the glyphs actually rendered ~30.22pt wide (Bold) at 8pt, so the trailing glyph overshot the column boundary by ~2pt and the t was clipped or overhung into the neighbour column. Fix: new helveticaBoldWidth(str, sz) public function in src/fonts/encoding.ts and an opt-in bold flag on txtR/txtC/txtRTagged/txtCTagged in src/core/pdf-text.ts. Wired through smart-table headers (src/core/pdf-renderers.ts), legacy buildPDF() headers (src/core/pdf-builder.ts), and autoFitColumns header measurement (src/core/pdf-column-fit.ts). Visual: the t of Amount now sits comfortably inside the column on every table sample. Unicode/CIDFont mode uses per-font metrics and is unaffected. (src/fonts/encoding.ts, src/core/pdf-text.ts, src/core/pdf-renderers.ts, src/core/pdf-builder.ts, src/core/pdf-column-fit.ts)

Added

  • feat(types, tables): new optional ColumnDef.kind?: 'amount' field. Opt-in replacement for the pre-1.2.0 hardcoded i === 3 heuristic in renderTable — when set, data cells in the column render in Helvetica-Bold with credit/debit colouring driven by row.type. Reserved enum (further kind values may be added in future minor releases). (src/types/pdf-types.ts)
  • feat(core, mcp): new PDF_A_CONFORMANCE_TARGETS = ['pdfa1b', 'pdfa2b', 'pdfa2u', 'pdfa3b'] as const and PdfAConformanceTarget type exported from the root. Single source of truth for tooling — the pdfnative-mcp server's add_table / generate_basic_pdf tool schemas can import { PDF_A_CONFORMANCE_TARGETS } from 'pdfnative' and feed the array straight into their JSON-schema enum: field instead of hardcoding string literals. Materially improves how Gemini-CLI and other LLM agents discover the legal pdfA values. (src/core/pdf-tags.ts)
  • feat(crypto, #45): addSignaturePlaceholder(pdfBytes, options?) exported from the root. Options: placeholderBytes (default 16 384), fieldName (default 'Signature1'), pageIndex (default 0), signingTime / name / reason / location / contactInfo (forwarded to the /Sig dictionary). Throws on encrypted input. Idempotent on already-signed PDFs (verified by a dedicated test case + sample generator). (src/core/pdf-sig-placeholder.ts)
  • refactor(crypto): new SigDictMetadata interface in src/core/pdf-signature.ts — the metadata-only subset of PdfSignOptions (name, reason, location, contactInfo, `signi...
Read more

v1.1.0 — PDF/A Latin embedding, BiDi isolates, Arabic harakat, emoji

29 Apr 22:49
62f8c2e

Choose a tag to compare

Released 2026-04-30

Maximalist minor release. Closes the two largest open epics — issue #28 (PDF/A Latin font embedding) and issue #25 (full UAX #9 BiDi isolates + GPOS MarkBasePos for Arabic harakat) — and adds first-class monochrome emoji support, auto-fit table columns, and per-cell clipping. Folds the alpha.1 / alpha.2 medium-term items into a single stable cut.

100% backward-compatible. All new features are opt-in and gated on font registration or explicit table flags. Pre-existing PDFs are byte-identical. 1726 tests green across 48 files.

Highlights

  • fonts(latin): Noto Sans VF (OFL-1.1) is now bundleable as a fallback for PDF/A documents that use non-WinAnsi Latin (curly quotes, em-dash, ellipsis…). Opt-in via registerFont('latin', () => import('pdfnative/fonts/noto-sans-data.js')). Automatically activates for PDF/A modes when the encoding context detects characters outside WinAnsi. Closes #28.
  • shaping(bidi): UAX #9 isolate handling — LRI / RLI / FSI / PDI (U+2066–U+2069) are now honoured. Mixed-script paragraphs containing isolated runs are resolved correctly with full recursion. Three-tier dispatcher: public resolveBidiRuns() finds outermost isolate pairs, resolveBidiRunsForced() recurses with forced level, resolveBidiCore() runs the W1–W7 / N1–N2 / L2 pipeline.
  • shaping(arabic): GPOS MarkBasePos applied to transparent marks (harakat — fatha, kasra, damma, sukun, shadda, …). Marks now anchor on the preceding base glyph using font-provided GPOS anchor data, falling back to (0, 0) when absent. Closes the visual half of #25.
  • shaping(drivers): new shared gsub-driver.ts (tryLigature()) and gpos-positioner.ts (positionMarkOnBase()) modules. Bengali, Tamil, Devanagari, and Arabic shapers now route through a single GSUB lookup helper and a single GPOS anchor helper instead of three duplicated implementations.
  • shaping(emoji): monochrome emoji via Noto Emoji (OFL-1.1, 1891 glyphs). Opt-in via registerFont('emoji', () => import('pdfnative/fonts/noto-emoji-data.js')). Detection covers the full BMP/SMP emoji ranges (U+1F300–U+1FAFF, U+2600–U+27BF, …) plus Fitzpatrick modifiers (U+1F3FB–U+1F3FF), ZWJ (U+200D), and VS-15 / VS-16 (U+FE0E / U+FE0F). Multi-font run splitting routes emoji codepoints to the registered 'emoji' font automatically.
  • core(table): TableBlock.autoFitColumns (alpha.2) and TableBlock.clipCells (alpha.2) now part of the stable surface. Defaults preserve v1.0.x byte output.

Fixed (PDF/A conformance hardening)

  • pdfa(font embedding): Object 3 (/F1) and Object 4 (/F2) are now Type0 redirector dicts pointing to the embedded CIDFontType2 / FontFile2 chain when a Latin font entry is registered — eliminating unembedded Helvetica / Helvetica-Bold standard-14 references that broke veraPDF (ISO 19005-1 §6.3.4 / ISO 19005-2 §6.2.11.4.1).
  • pdfa(xmp utf-8): XMP metadata streams now go through a binary-safe UTF-8 encoder (utf8EncodeBinaryString()) before toBytes(), preserving em-dash, ellipsis, smart quotes, CJK in <dc:title> and matching /Info /Title byte-for-byte (ISO 19005-1 §6.7.3 t1).
  • pdfa(xmp parity): buildXMPMetadata() emits <dc:description> and <pdf:Keywords> whenever /Info /Subject and /Info /Keywords are set, satisfying ISO 19005-1 §6.7.3 t4 / t5 parity rules. Unblocks PDF/A-1b validation for documents carrying subject or keywords metadata.
  • pdfa(encoding fallback): createEncodingContext(fontEntries, pdfA=true) disables the WinAnsi/Helvetica fallback. Characters outside the primary CIDFont's cmap render as .notdef instead of routing to an unembedded Type1 font.
  • pdfa(annotations /F 4): Link annotations (/Subtype /Link, both /URI and /GoTo) and form widgets (/Subtype /Widget) now emit /F 4 (Print flag set, NoView/Hidden/Invisible cleared) per ISO 19005-2 §6.5.3 / veraPDF rule 6.3.2-1. Required on every annotation in PDF/A-2 / PDF/A-3.
  • scripts(samples): Five PDF/A-claiming sample generators (barcode-tagged, compressed-tagged-pdfa2b, header-footer-tagged, tagged-accessibility-complex, toc-tagged) now register a latin font entry so the generated samples pass veraPDF rule 6.2.11.4.1-1. The pdfa-variants and pdfa-latin-embedding generators were already wired in alpha.1.
  • ci(verapdf): veraPDF validation is now blocking on PRs and pushes — no more continue-on-error. validate-pdfa.ts auto-detects PDF/A claims via XMP pdfaid:part, so non-PDF/A samples never trigger CI failures.

Added

  • fonts(latin): fonts/noto-sans-data.{js,d.ts} — Noto Sans VF subsetted, 4515 glyphs, 3094 cmap entries. OFL-1.1.
  • fonts(emoji): fonts/noto-emoji-data.{js,d.ts} — Noto Emoji monochrome, 1891 glyphs, 1489 cmap entries. OFL-1.1.
  • shaping(bidi): isolate support — LRI (U+2066), RLI (U+2067), FSI (U+2068), PDI (U+2069) classified as BN and recursed. Nested isolates supported. Unmatched isolates fall through gracefully.
  • shaping(arabic): MarkBasePos applied to transparent (joining type 'T') marks. lastBaseGid tracking through the shaping pipeline including lam-alef ligatures.
  • shaping(drivers): src/shaping/gsub-driver.ts exporting tryLigature(gids, ligatures) and src/shaping/gpos-positioner.ts exporting getBaseAnchor, getMarkAnchor, getMark2MarkAnchor, positionMarkOnBase. Bengali / Tamil / Devanagari / Arabic shapers refactored to use them.
  • shaping(emoji): EMOJI_RANGES, isEmojiCodepoint, containsEmoji, FITZPATRICK_START/END, ZWJ, VS15, VS16 exported from src/shaping/script-registry.ts. detectCharLang() returns 'emoji' for emoji codepoints; detectFallbackLangs() adds 'emoji' automatically.
  • scripts(download-fonts): Noto Emoji entry in the manifest for reproducible npm run download:fonts.
  • tests: tests/shaping/phase2-shaping.test.ts (24 tests, GSUB driver + GPOS positioner + BiDi isolates + Arabic GPOS), tests/shaping/emoji.test.ts (15 tests, ranges + predicates + script-detect integration + baked module shape), tests/fonts/pdfa-latin-embedding.test.ts (PDF/A Latin embedding integration).

Changed

  • shaping(bidi): resolveBidiRuns() rewritten as a recursive isolate-aware dispatcher. Behaviour unchanged for inputs without isolate characters — output is byte-identical for all pre-v1.1.0 fixtures.
  • shaping(types): fixPunctuationAffinity and fixBracketPairing widened to readonly number[] to match the new core pipeline. No public API impact.
  • shaping(bengali, tamil, devanagari): local tryLigature definitions removed. Shapers now declare a thin tryLig(gids) closure that forwards to the shared driver. Output bytes unchanged.

Documentation

  • New release notes (this file).
  • README, ROADMAP, and .github/copilot-instructions.md updated to reflect new modules, emoji support, and PDF/A Latin embedding.
  • New emoji guide at pdfnative.dev/guides/emoji.html.
  • PDF/A guide refreshed with Latin embedding example.

Deferred to v1.2.0

  • Full UAX #9 embeddings (LRE / RLE / LRO / RLO / PDF) — isolates ship now, embeddings remain rare in practice and require a deeper level-stack refactor.
  • True page-by-page constant-memory streaming (buildDocumentPDFStreamPageByPage()).
  • COLRv1 colour emoji (this release ships monochrome only).

Upgrade

npm install pdfnative@1.1.0

Opt into the new font modules as needed:

import { registerFont } from 'pdfnative';

// PDF/A documents that need non-WinAnsi Latin fallback
registerFont('latin', () => import('pdfnative/fonts/noto-sans-data.js'));

// Emoji rendering (monochrome)
registerFont('emoji', () => import('pdfnative/fonts/noto-emoji-data.js'));

No code changes required for users who don't register 'latin' or 'emoji' — pre-existing PDFs are byte-identical.

Credits

  • Noto Sans, Noto Emoji © Google LLC, licensed under SIL Open Font License 1.1.
  • UAX #9 reference: Unicode Bidirectional Algorithm.
  • ISO 32000-1:2008 §9.7 (CIDFont), §9.10 (ToUnicode), §14.8 (PDF/UA), ISO 19005-2 (PDF/A-2).

v1.0.5 — watermark centering & CLI docs

27 Apr 19:08
ee728af

Choose a tag to compare

Released 2026-04-27

Patch release. Two bug fixes in the watermark module (vertical centering and Unicode encoding) plus first-class documentation for the official command-line interface, pdfnative-cli. 100% backward-compatible with v1.0.4 — no public API surface changes, no breaking changes, no byte-shift in non-watermarked PDFs.

Highlights

  • fix(watermark): text watermarks are now mathematically centered. The previous offset ignored cap-height; affected output now matches the visual center of the page in both axes.
  • fix(watermark): Unicode watermarks (Arabic, Hebrew, CJK, Devanagari, Bengali, Tamil, Cyrillic, Greek, Georgian, Armenian) now render correctly when the document uses a CIDFont. Watermark text is encoded through the active encoding context instead of being forced through WinAnsi.
  • docs(cli): new CLI Guide documents pdfnative-cli — the official render / sign / inspect command-line tool. Covers installation, security model, CI/CD pipelines, and library-vs-CLI decision guidance.
  • docs(architecture): Ecosystem section now spans both companion packages (pdfnative-cli and pdfnative-mcp) with parallel treatment in README.md and the architecture guide.

Fixed

  • fix(core/watermark): vertical offset of text watermarks now derives from the active font's capHeight / unitsPerEm ratio (with a 0.718 fallback matching Helvetica) instead of the prior -fontSize/2. Glyphs are now centered on their cap-height baseline midpoint, eliminating the visible vertical drift previously observed at large rotation angles. (src/core/pdf-watermark.ts)
  • fix(core/watermark): watermark text is now encoded via enc.ps() (the document's active encoding context) instead of pdfString() (WinAnsi-only). Documents using a CIDFont (Identity-H) now emit watermarks as 2-byte hex GIDs and render correctly across all 16 supported scripts. (src/core/pdf-watermark.ts)

Added

  • feat(tests): 6 new regression tests in tests/core/pdf-watermark.test.ts covering cap-height-based vertical offset, horizontal centering, Latin WinAnsi encoding, Unicode CIDFont 2-byte GID hex encoding, font-metric-driven offset in Unicode mode, and rotation invariance of the visual centering bounding box.

Documentation

  • docs(cli): new CLI Guide (docs/guides/cli.html + docs/guides/cli.md).
  • docs(index): new CLI feature card on pdfnative.dev homepage with dedicated icon (.fi-cli).
  • docs(quickstart): new "Command line — pdfnative-cli" section in the Quick Start guide plus link from the Next steps list.
  • docs(architecture): Ecosystem section in architecture guide now documents pdfnative-cli alongside pdfnative-mcp with parallel diagrams.
  • docs(readme): README.md now lists both companion packages in the Ecosystem section, includes a pdfnative-cli npm version badge, and a CLI bullet in the Highlights list.

Deferred

  • #28 (PDF/A Latin font embedding): integration of an embedded Latin font (e.g. Liberation Sans / Arimo) for PDF/A documents has been deferred to v1.1.0. The change requires object renumbering across multiple builders and ships ~30–60 KB of additional bytes per PDF/A output, which is out of scope for a patch release. Tracking issue: #28.

Install

npm install pdfnative@1.0.5

Upgrade

No breaking changes. Drop-in replacement for v1.0.4. Documents that did not use watermarks produce byte-identical output to v1.0.4. Documents using watermarks will produce visually-corrected output (vertical centering, Unicode encoding) — verify visual regression baselines if your test suite pixel-compares watermarked PDFs.

Links

v1.0.4 — PDF/A conformance hardening (trailer /ID, /Info ↔ XMP parity, veraPDF CI)

25 Apr 19:29
4b15d22

Choose a tag to compare

Released 2026-04-25

v1.0.4 is a patch release that begins a multi-step PDF/A conformance
hardening sweep. Two of the five real bugs found by the official veraPDF
reference validator on medical-800p.pdf and barcode-showcase.pdf are
fixed in this release; a third (Latin font embedding under PDF/A) is
scheduled for v1.0.5 and tracked transparently in
issue.

A new CI workflow installs the veraPDF CLI on every PR and validates
every sample PDF that claims PDF/A — preventing future regressions.

100 % API-backward-compatible with v1.0.3.

Highlights

  • Trailer /ID is now emitted on every PDF (was previously
    encrypted-only). Fixes ISO 19005-1 §6.1.3 / ISO 32000-1 §14.4.
  • /Info CreationDate and xmp:CreateDate share a single timestamp
    source and now both carry timezone offsets, eliminating veraPDF rule
    6.7.3 t1 mismatches.
  • dc:creator is emitted only when an author is present and is
    XML-escaped, fixing rule 6.7.3 mismatches on documents without an
    explicit author.
  • New npm run validate:pdfa script + verapdf.yml CI workflow
    protect the project against PDF/A regressions going forward.

Fixed

  • fix(core): trailer /ID always emitted (previously encrypted-only).
    Unencrypted ID is MD5("pdfnative|" + title + "|" + creationDate + "|" + totalObjs) for deterministic regeneration. (src/core/pdf-assembler.ts)
  • fix(core): /Info CreationDatexmp:CreateDate byte-equivalent
    via new buildPdfMetadata() helper; both include local timezone offset
    (D:YYYYMMDDHHmmSS+HH'mm' and ISO 8601 ±HH:MM). Closes veraPDF rule
    6.7.3 t1. (src/core/pdf-tags.ts)
  • fix(core): XMP dc:creator emitted conditionally and XML-escaped;
    matches /Info /Author exactly when present. Closes false-positive
    rule 6.7.3 mismatches on author-less documents. (src/core/pdf-tags.ts)
  • fix(core): XMP packet adds matching xmp:ModifyDate and
    xmp:MetadataDate (ISO 19005-2 §6.7.3 best practice).

Added

  • feat(scripts): scripts/validate-pdfa.ts — batch veraPDF runner.
    Walks test-output/, detects PDF/A claims via XMP, invokes veraPDF CLI,
    reports compliant/failed counts. Skips silently when veraPDF is missing.
    Prints per-OS install hints (macOS / Linux / Windows / online demo)
    and a Scanned N; M claim PDF/A, K skipped (not PDF/A) summary line.
  • feat(ci): .github/workflows/verapdf.yml — installs veraPDF CLI on
    Temurin 17, regenerates samples, runs npm run validate:pdfa. Pinned
    veraPDF version 1.26.5. Supports workflow_dispatch so the
    workflow can be triggered manually against any branch from the
    GitHub Actions UI before opening a pull request.
  • feat(scripts): npm run validate:pdfa script.
  • test(core): tests/core/pdf-trailer-id.test.ts
    — 18 new tests covering trailer /ID, date format with timezone,
    XMP ↔ /Info parity, dc:creator escaping & conditional emission.
  • docs(guides): new Installing veraPDF locally + Troubleshooting
    sections in docs/guides/pdfa.html
    document the install path on each OS, the online demo as a no-install
    fallback, and why plain ISO 32000-1 files are auto-skipped instead of
    being forced through a PDF/A profile.
  • docs(landing): new "Designed for low-impact computing" section
    on docs/index.html — factual sustainability
    bullets only (zero deps, on-device generation, no telemetry,
    tree-shakeable ESM, streaming output, external tooling stays
    external). No carbon claims, no badges we cannot verify.
  • docs(readme): two factual bullets added to Highlights covering
    on-device generation and the absence of network calls.

Changed

  • chore(meta): version bump to 1.0.4.
  • chore(meta): existing CreationDate regex tests updated to allow
    the new mandatory timezone offset.

Documentation

Known limitations

  • Latin font embedding under PDF/A modes is not yet implemented.
    Files generated with tagged: true | 'pdfa1b' | 'pdfa2b' | 'pdfa2u' | 'pdfa3b' still emit unembedded standard 14 Helvetica and therefore
    still fail veraPDF rule 6.3.4. The pdfaid:part claim in XMP must be
    treated as aspirational until v1.0.5 lands. The CI guardrail expects
    this and is wired to start blocking PRs once v1.0.5 ships.

Verification

  • npm run typecheck:all — clean
  • npm run lint — 0 errors
  • npm run test — 1 624 / 1 624 passing (1 606 prior + 18 new)
  • npm run build — ESM + CJS + DTS produced

Migration

No code changes required. Outputs may differ byte-for-byte (new /ID
array, timezone in CreationDate, conditional dc:creator). If your
test fixtures snapshot full PDF bytes, regenerate them.

FAQ

Why does veraPDF still report failures on doc-lists.pdf (or any
other plain sample)?
Those samples are not generated with
tagged: true, so they do not claim PDF/A in their XMP packet. The
veraPDF online demo lets you pick a profile manually, which then
forces the file through PDF/A rules and surfaces unrelated failures
by design (missing XMP, unembedded fonts, DeviceRGB without an
OutputIntent — all expected for an ISO 32000-1 document). The
npm run validate:pdfa wrapper avoids this trap by skipping every
file that does not declare pdfaid:part in XMP. See the
Troubleshooting section
for details.

Is this a breaking change? No public API breaks. Output bytes
do change (new /ID, timezone offsets in CreationDate,
conditional dc:creator). Consumers that snapshot full PDF bytes
in their test fixtures will need to regenerate those snapshots.
SemVer-wise this is a patch.

v1.0.3 — extreme-script regression baseline, layout fix, playgrounds

25 Apr 14:23
dc773b0

Choose a tag to compare

Released 2026-04-25

Patch release adding extreme-script visual regression baselines, fixing a heading layout overflow on long compound titles, and shipping two new interactive playgrounds. No public API change — fully backward-compatible with v1.0.2.

Highlights

  • Heading overflow fix — long titles such as
    "Test Bengali + Devanagari ULTRA EXTREME — Shaping & Positioning — pdfnative"
    no longer overflow the right margin; wrapText() now hard-breaks single
    overlong tokens at character boundaries when no whitespace breakpoint
    exists.
  • Extreme shaping samples — four new visual baselines under
    test-output/extreme/ covering BiDi (Arabic + Hebrew + Thai + Latin +
    digits), Tamil ultra-conjuncts, Bengali + Devanagari reph + multi-halant
    ligature chains, and isolated Arabic harakat.
  • Two new playgroundsextreme-scripts.html for live shaping
    stress-tests with editable presets, and medical-800.html showcasing
    off-thread generation of an 800-page synthetic clinical report via Web
    Worker + buildDocumentPDFStream.
  • Honest scoping — deeper shaping issues exposed by the new baselines
    (Arabic isolated harakat anchoring, Thai mark stacking on tall
    consonants, multi-stage Indic ligature chains, 3+ RTL-script BiDi) are
    documented as known limitations and tracked for the v1.1.0 minor
    release. They require GPOS table re-extraction in the pre-built font
    data modules and exceed the scope of a SemVer-patch.

Fixed

  • fix(core/layout): wrapText() now hard-breaks single overlong
    tokens at character boundaries when no whitespace breakpoint exists.
    Long headings and titles previously could overflow the right margin
    when no segment fit. Code points are honored so surrogate pairs and
    combining sequences remain intact at slice boundaries.
    (src/core/pdf-renderers.ts)
  • fix(docs): guides/architecture.html and guides/faq.html footer
    links from docs/index.html previously 404'd because only .md files
    existed under docs/guides/ and .nojekyll disables auto-rendering.
    Each guide now ships as a real HTML page with a clean URL.

Added

  • feat(samples): new extreme-shaping.ts generator producing four
    visual-regression baselines under test-output/extreme/:
    extreme-bidi.pdf, extreme-tamil.pdf,
    extreme-bengali-devanagari.pdf, extreme-arabic-harakat.pdf.
    Wired into scripts/generate-samples.ts.
  • test(integration): tests/integration/extreme-shaping.test.ts
    five end-to-end builds covering the same extreme inputs to guard
    against pipeline regressions (1 605 tests total, up from 1 600).
  • test(core): new regression tests for wrapText confirming
    character-level hard-break of overlong tokens and multi-line wrapping
    of long em-dash titles.
  • docs(playgrounds): new interactive playground
    docs/playgrounds/extreme-scripts.html for stress-testing BiDi, Tamil
    conjuncts, Bengali + Devanagari ligatures, and Arabic harakat directly
    in the browser, with editable presets and a code preview.
  • docs(playgrounds): new docs/playgrounds/medical-800.html — Web
    Worker showcase generating an 800-page synthetic clinical report using
    buildDocumentPDFStream, with live progress, byte/chunk counters,
    optional Tagged PDF (PDF/A-2b), and a main-thread comparison toggle.
    All patient data is generated client-side from a seeded RNG — no real
    PHI.
  • docs(landing): static HTML guide pages (quickstart.html,
    architecture.html, faq.html, troubleshooting.html,
    accessibility.html) plus a guides index at /guides/.
  • docs(guides): new quickstart.md covering Node.js, browser,
    multi-language, Web Worker, and streaming setups in a single page; new
    accessibility.md covering tagged PDF, PDF/UA, PDF/A variants,
    structure tree contents, alt-text discipline, and validation tooling.
    Rewrote faq.md with sectioned topics and ten concrete code snippets.
  • docs(readme): added a "Documentation" pointer block linking to the
    guides and to pdfnative.dev. Added Indic document samples
    (doc-bengali, doc-tamil, doc-devanagari) to the Document Builder
    Samples table. Added a "Citing pdfnative" section with BibTeX pointing
    to CITATION.cff.

Changed

  • docs(landing): added project-status badges to the pdfnative.dev
    hero (CI, CodeQL, OpenSSF Scorecard, npm version, monthly downloads,
    bundle size, zero deps, TypeScript strict, npm provenance, MIT) to
    mirror the README and surface supply-chain signals upfront.
  • docs(landing): rebuilt the "Try It Live" panel as a curated
    10-example gallery (Quick Start, Financial, TOC, Barcode, SVG,
    Watermark, Forms, PDF/A, Multi-language with lazy fonts, Streaming).
    The runtime now supports top-level await, dynamic import(…), and
    exposes streamDocumentPdf, registerFonts, loadFontData, and
    signPdfBytes.
  • docs(landing): added "Guides" and "Playgrounds" entries in the
    navbar and refreshed the footer with direct links to every guide and
    both new playgrounds.
  • docs(landing): synced the test counter to 1 605+ tests.

Known limitations (tracked for v1.1.0)

The new extreme samples surface deeper shaping issues that are tracked
for the next minor release. They require either GPOS table re-extraction
in the pre-built font data modules under fonts/ or new OpenType lookup
implementations in the shaping pipeline, which exceed the scope of a
SemVer-patch:

  • Arabic isolated harakat: تشكيل without a base consonant fall back
    to default mark positioning rather than precise font-anchored
    placement. Visible in extreme-arabic-harakat.pdf.
  • Thai tall-consonant mark stacking: ป ฝ ฟ ฬ with three or more
    combining marks may overlap with the current font anchor data.
  • Multi-stage Indic ligatures: ক্ষ্ম, क्ष्म, ஸ்ரீ are matched
    greedily; some deeply-nested sequences fall back to non-ligated forms.
  • 3+ script BiDi paragraphs: Arabic + Hebrew + Thai + Latin + digits
    in one paragraph may exhibit non-canonical run ordering at boundaries
    with neutrals.

The full follow-up plan is tracked in
release-notes/draft-issue-v1.1.0-shaping-epic.md.

Install

npm install pdfnative@1.0.3

Upgrade

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

The only behavioral change is wrapText(), which now hard-breaks
overlong tokens that previously overflowed the page. Existing layouts
that relied on overflow behavior (none expected — overflow was always
visually broken) should re-render with proper wrapping.

Links

v1.0.2 — metadata, samples & governance

24 Apr 18:57
1d7b662

Choose a tag to compare

v1.0.2 — metadata, samples & governance

Released 2026-04-24

Documentation, samples, governance, and npm metadata polish. No runtime code changes — 100% backward-compatible with v1.0.1.

Highlights

  • npm discoverabilitydescription now enumerates the 16 supported scripts and headline features; keywords expanded from 13 → 27 entries.
  • Devanagari sample — new doc-devanagari.pdf completes the Indic sample triad (Bengali, Tamil, Devanagari).
  • 16-script multi-language showcasedoc-multi-language.pdf covers all supported scripts in one PDF (up from EN/AR/JA).
  • Governance — new maintenance issue template and a standardized release notes template for future releases.

Changed

  • chore(meta): enriched package.json. description now enumerates the 16 supported scripts (Arabic, Hebrew, Thai, CJK, Devanagari, Bengali, Tamil, Cyrillic, Greek, Georgian, Armenian, Latin) and headline features (BiDi, PDF/A-1b/2b/3b, AES encryption, digital signatures, AcroForm, barcodes, SVG). keywords expanded from 13 to 27 entries (adds arabic, hebrew, bengali, tamil, devanagari, bidi, pdf-a, tagged-pdf, accessibility, encryption, digital-signature, acroform, barcode, qr-code).
  • docs(contributing): CONTRIBUTING.md branch strategy — default branch corrected from master to main, added chore/* convention for maintenance and release branches.

Fixed

  • fix(docs): README.md multi-font table — Bengali and Tamil rows were concatenated on a single line with literal \n characters instead of real newlines, rendering as broken markdown on npmjs.com and GitHub.
  • fix(samples): doc-devanagari.pdf heading "अध्याय १: परिचय" used a Bengali digit one (U+09E7) instead of a Devanagari digit one (U+0967), producing a .notdef tofu box in the rendered PDF.
  • fix(build): VS Code ts-server reported a spurious Cannot find name 'path' on scripts/generators/document-builder.ts. Root cause: the root tsconfig.json only includes src/**, so the editor fell back to a config without @types/node. Added scripts/tsconfig.json extending tsconfig.scripts.json so the editor picks up node types for sample generators. CLI (npm run typecheck:scripts) was already green.

Added

  • feat(samples): new generateDevanagariDoc() in scripts/generators/document-builder.ts producing test-output/document/doc-devanagari.pdf with GSUB conjuncts, reph reordering, matra reordering, and split vowels.
  • feat(samples): doc-multi-language.pdf now covers all 16 supported scripts (Latin, Greek, Cyrillic, Turkish, Vietnamese, Polish, Georgian, Armenian, Thai, Devanagari, Bengali, Tamil, Japanese, Chinese, Korean, Arabic, Hebrew) in a single document.
  • docs(governance): new .github/ISSUE_TEMPLATE/maintenance.md template for release tasks, metadata updates, and governance work.
  • docs(governance): new release-notes/TEMPLATE.md standardizing future release notes (section structure, conventional commit prefixes, SemVer classification, publication workflow).

Install

npm install pdfnative@1.0.2

Upgrade

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

Links

v1.0.1 — Fix CP1252 bullet encoding

23 Apr 11:45
2f6e8be

Choose a tag to compare

v1.0.1 — Fix CP1252 bullet encoding

This is a patch release fixing a regression where bullet list items rendered
as ? in the default WinAnsi/Helvetica mode, and completing the full CP1252
character set for the toWinAnsi() encoding function.

No API changes. No breaking changes. Upgrade is a drop-in replacement.


Bug Fixes

fix(encoding): bullet list items rendered as ? in default mode ([#1])

Every { type: 'list', style: 'bullet' } block without fontEntries
(the default Helvetica/WinAnsi mode) produced ? instead of in the
generated PDF.

Root cause: toWinAnsi() in src/fonts/encoding.ts was missing the
CP1252 mapping for the bullet character (U+2022 → 0x95).
The list renderer in pdf-renderers.ts hard-codes '\u2022' as the bullet
marker, which must survive toWinAnsi() — the absent mapping caused every
default-mode bullet list to render ? instead.

Fix: Added U+2022 → \x95 to toWinAnsi().

Note for users with fontEntries: this bug does not affect CIDFont /
Identity-H mode — glyph IDs are resolved directly from the font cmap
and toWinAnsi() is never called. Only default Helvetica mode was impacted.


fix(encoding): complete CP1252 0x80–0x9F character table

CP1252 defines 27 characters in the 0x80–0x9F range that are absent from
ISO-8859-1. v1.0.0 mapped only 8 of them (€ – — ' ' " " …). This release
adds the remaining 18:

Unicode Char CP1252 Description
U+2022 0x95 Bullet (primary fix)
U+201A 0x82 Single low-9 quotation mark
U+0192 ƒ 0x83 Latin small f with hook
U+201E 0x84 Double low-9 quotation mark
U+2020 0x86 Dagger
U+2021 0x87 Double dagger
U+02C6 ˆ 0x88 Modifier letter circumflex accent
U+2030 0x89 Per mille sign
U+0160 Š 0x8A Latin capital S with caron
U+2039 0x8B Single left-pointing angle quotation mark
U+0152 Œ 0x8C Latin capital ligature OE
U+017D Ž 0x8E Latin capital Z with caron
U+02DC ˜ 0x98 Small tilde
U+2122 0x99 Trade mark sign
U+0161 š 0x9A Latin small s with caron
U+203A 0x9B Single right-pointing angle quotation mark
U+0153 œ 0x9C Latin small ligature oe
U+017E ž 0x9E Latin small z with caron
U+0178 Ÿ 0x9F Latin capital Y with diaeresis

All of these previously fell through to the ? replacement path. This fix
is purely additive — no existing mapping has changed.


fix(docs): landing page live demo content

The live demo on pdfnative.dev used (U+2713)
in table cells. This character has no CP1252 equivalent and cannot be
encoded in WinAnsi mode — it rendered as ?. Replaced with ASCII Yes.

Unrelated financial API properties (type: 'credit', pointed: false)
that were incorrectly included in the buildDocumentPDFBytes() demo have
also been removed.


Testing

  • 1600 tests passing (up from 1588 in v1.0.0)
  • 12 new test cases covering U+2022 → \x95 and all 18 additional
    CP1252 0x80–0x9F mappings added in this release
  • 141 sample PDFs regenerated — bullet lists visually verified

Upgrading

npm install pdfnative@1.0.1

No code changes required. This release is a transparent drop-in upgrade.


Full Changelog

v1.0.0...v1.0.1

v1.0.0 — Initial Release

20 Apr 20:10

Choose a tag to compare

v1.0.0 — Initial Release

pdfnative — Pure native PDF generation. Zero vendor dependencies. ISO 32000-1 (PDF 1.7) compliant.

This is the first stable release of pdfnative, extracted from plika.app where it has
been powering production multi-language PDF generation across 16 Unicode scripts.


Install

npm install pdfnative

Signed provenance — this release is published via GitHub Actions OIDC with npm --provenance.
Build attestation is verifiable at npmjs.com/package/pdfnative.


What's in this release

Zero-dependency PDF engine

Built from scratch in pure TypeScript — no PDFKit, no Puppeteer, no native binaries, no runtime
dependencies. Works in Node.js ≥ 22, browsers, Deno, Bun, and Web Workers out of the box.
Dual ESM + CJS build, tree-shakeable with sideEffects: false.

16 Unicode scripts with OpenType shaping

Full glyph shaping pipelines for Thai (GSUB + GPOS mark-to-base/mark-to-mark), Arabic
(positional GSUB: isol/init/medi/fina + lam-alef), Devanagari, Bengali, Tamil, and Unicode BiDi
(UAX #9) layout including bracket pairing and punctuation affinity.

Supported scripts: Thai, Japanese, Chinese (SC), Korean, Greek, Devanagari, Turkish, Vietnamese,
Polish, Arabic, Hebrew, Cyrillic, Georgian, Armenian, Bengali, Tamil.

Tagged PDF / PDF/A compliance

Conformance level Standard
Tagged PDF / PDF/UA ISO 14289-1
PDF/A-1b ISO 19005-1
PDF/A-2b (default) ISO 19005-2
PDF/A-2u ISO 19005-2
PDF/A-3b + embedded files ISO 19005-3

Full structure tree (/Document → /Table → /TR → /TH|/TD, /H1–H3, /P, /L, /Figure,
/Link, /TOC → /TOCI), /ActualText on every marked content sequence, XMP metadata, sRGB ICC
OutputIntent.

Encryption, Signatures & Forms

  • AES-128 / AES-256 (V4/R4, V5/R6) with owner + user passwords and granular permission
    bitmask
  • CMS/PKCS#7 digital signatures (ISO 32000-1 §12.8) — RSA PKCS#1 v1.5 and ECDSA P-256 with
    X.509 DER parsing; all crypto is zero-dependency pure TypeScript
  • AcroForm interactive fields — text, multiline, checkbox, radio, dropdown, listbox with full
    /AP appearance streams (ISO 32000-1 §12.7)

Document builder

12 block types for free-form document composition:
HeadingBlock, ParagraphBlock, ListBlock, TableBlock, ImageBlock, LinkBlock,
SpacerBlock, PageBreakBlock, TocBlock, BarcodeBlock, SvgBlock, FormFieldBlock.

Header/footer templates with {page} / {pages} / {date} / {title} placeholders, text and
image watermarks with transparency, table of contents with dot leaders and internal /GoTo links.

Barcodes, SVG & Streaming

Five barcode formats as pure PDF path operators (zero image dependency): Code 128, EAN-13, QR Code
(ISO 18004), Data Matrix ECC 200, PDF417. SVG element rendering (7 types). AsyncGenerator
streaming output with configurable chunk size.

PDF Parser & Modifier

Read and incrementally modify existing PDFs — tokenizer, xref table/stream parser, FlateDecode
inflate, object parser with discriminated union type guards, PdfReader, and PdfModifier with
non-destructive incremental /Prev chain.


Security

Three CWE mitigations are included and exposed as public API:

Mitigation API Default
CWE-674 — parser recursion MAX_PARSE_DEPTH 1000 levels
CWE-400 — zip-bomb / inflate setMaxInflateOutputSize() / DEFAULT_MAX_INFLATE_OUTPUT 100 MB
CWE-400 — xref chain DoS MAX_XREF_CHAIN 100 hops + cycle detection

URL validation blocks javascript:, file:, and data: schemes as well as control characters
(U+0000–U+001F, U+007F–U+009F) in link annotations. Color values are validated and sanitized
before interpolation into PDF content streams.


Quality metrics

Metric Value
Tests 1588+ across 40 files
Statement coverage 95%+
Fuzz / edge-case scenarios 48
Runtime dependencies 0
Node.js requirement ≥ 22
CI matrix Node 22 + 24
Provenance Signed via GitHub Actions OIDC
Supply-chain OpenSSF Scorecard monitored

Breaking changes

None — this is the initial stable release.


Full changelog

See CHANGELOG.md for the
complete list of added features, fixes, and security hardening details.


Links