Skip to content

Releases: Nizoka/pdfnative-react

v1.2.0 — Charts v2, print production, and the conformance channel

Choose a tag to compare

@Nizoka Nizoka released this 28 Aug 18:35
19130b0

pdfnative-react v1.2.0

Released 2026-08-26

Charts v2, print production, and the conformance channel. This release tracks
the pdfnative engine's 1.7.0
and keeps a promise this package made in writing: since 1.1.0,
docs/CHARTS.md said that when the engine shipped "Charts v2", the new
capabilities would "reach this package as new ChartProps fields". They
shipped; here they are — together with print-production page geometry, the
PDF/A conformance diagnostics channel, seven new lint rules, and the quality
backlog deferred from the 1.1.0 review.

Everything is additive. No public API was removed or changed.

Compatibility — read this first

npm install pdfnative-react@^1.2.0 pdfnative@^1.7.0 react@^19
Requirement 1.1.0 1.2.0
pdfnative (peer) ^1.6.0 ^1.7.0
React (peer) ^19.0.0 ^19.0.0
Node.js >=22 >=22

The peer floor moves because the four new chart kinds, layout.print and the
diagnostics channel do not exist before engine 1.7.0 — an older engine would
throw mid-render on the new authoring surface. doctor() now tells a 1.6.x
engine apart from a missing one and says exactly what to upgrade.

Rendering behaviour also inherits engine 1.7.0's correctness fixes, with no
code change here: RTL digit runs keep logical order, paired delimiters mirror
and Arabic/Persian letterforms join per UAX #9 (Arabic-script documents render
differently — and correctly), colour-emoji flag and ZWJ sequences resolve,
form documents gain a complete /ToUnicode map (their bytes change; form text
becomes searchable), and crowded chart x-labels are strided automatically
(labelStride: 1 restores the old draw-everything behaviour).

Highlights

Charts v2

Nine chart kinds — bar, barH, line, pie, donut, and now
stackedBar, stackedBarH, area and scatter — all still pure vector PDF
operators, PDF/A-safe, no chart library.

<Chart
    chartType="line"
    xAxis={{ type: 'time' }}
    series={[
        { label: 'Revenue (k€)', values: [210, 245, 262], xValues: ['2026-01', '2026-02', '2026-03'] },
        { label: 'Margin (%)', values: [12.1, 13.4, 15.2], xValues: ['2026-01', '2026-02', '2026-03'], yAxis: 'right' },
    ]}
    axis2={{ yMin: 0, yMax: 20 }}
    dataLabels={{ decimals: 1 }}
    altText="Revenue and margin both rise across Q1 2026."
/>

New surface: axis.scale ('linear' | 'log'), a secondary right axis
(axis2 + ChartSeries.yAxis), xAxis ('category' | 'linear' | 'time',
UTC-deterministic time ticks) with per-point ChartSeries.xValues, per-point
dataLabels, and x-label collision control (labelStride, labelRotation).
Full DocSpec and JSON-schema parity, as always — the compile-time
ChartPropsCoversChartBlock lock made the peer bump a build error until every
new engine field reached the props, the spec grammar and the schema.
Guide · sample

Print production

<Document
    print={{ bleed: 9, marks: true }}
    metadata={{ trapped: 'False' }}
    layout={{ viewerPreferences: { duplex: 'duplexFlipLongEdge', printPageRange: [[1, 4]] } }}
>

The print prop is document-level sugar in the exact mold of watermark /
header / footer / attachments / tagged: bleed/trim/art/crop boxes (or
the one-line bleed shorthand), vector printer's marks outside the TrimBox,
and large-format /UserUnit. metadata.trapped writes /Trapped with XMP
parity; the print-dialog viewer preferences (duplex, pickTrayByPDFSize,
printPageRange, numCopies) ride on layout.viewerPreferences; and
layout.outputIntent accepts a caller-supplied RGB ICC profile for tagged
output. Sample

The conformance channel

Engine 1.7.0 stops silently stamping PDF/A claims it cannot honour: an
unembedded font or a CMYK image under a PDF/A target now surfaces as a
diagnostic (PDFA_NO_FONT_ENTRIES, PDFA_UNEMBEDDED_FORM_FONT,
PDFA_DEVICE_CMYK_IMAGE). Reach it through any layout door:
layout.onDiagnostic receives diagnostics programmatically, and
layout.strict: true — the JSON-safe switch — escalates them to thrown
errors. lintDocument remains the pre-render tier; the channel sees what only
a render can see. Guide · sample

Linting: 18 → 25 rules

Thirteen rules now pre-empt an exception the engine would raise mid-render.
The three new chart rules (L_CHART_LOG_SCALE, L_CHART_X_AXIS,
L_CHART_LABELS) mirror every Charts v2 constraint; L_VIEWER_PRINT_RANGE
covers the print-dialog preferences; L_OUTPUT_INTENT_IGNORED and
L_TAGGED_FORM_FONTS warn about the two silent traps. L_PRINT_BOXES takes
a different approach worth naming: instead of re-stating the engine's
print-geometry rules, it calls the engine's own validatePrintOptions in a
try/catch and reports its message verbatim — zero duplicated rules, zero
drift, by construction. Guide

Proven PDF/A, not just declared: the veraPDF gate

npm run validate:pdfa

An 11-file corpus — rendered through the built package, across both authoring
doors and all four conformance targets, including this release's dual-axis
charts and print production under a PDF/A claim — is validated with the
pinned veraPDF reference validator, locally and as a
blocking CI and pre-publish gate. Two negative canaries the validator
must reject keep the gate honest: an unexpected pass is fatal, so a validator
that accepts everything can never turn CI green. Same hardened runner design
as the rest of the ecosystem (manifest-driven, fail-closed in CI,
SHA-256-verified installer). Guide

Agents that can see their work

The agent contract gains tier 5 — verifying the rendered output:
extractText for text truth, validatePdfUA/veraPDF for conformance truth,
and for vision-capable agents, rasterize and look — render, rasterize with
a standard external tool (pdftoppm or mutool; nothing is bundled), read
the PNG, and judge the page against your intent. Geometry tells you where
blocks landed; only looking tells you whether the page communicates.
Contract · sample

Server rendering: HTTP caching

return renderToResponse(<Invoice />, {
    cacheControl: 'private, max-age=60',
    etag: `"invoice-${id}-${updatedAt}"`,   // or etag: true to derive from the bytes
});

cacheControl and etag are opt-in; defaults are unchanged. etag: true
derives a strong validator from the rendered bytes (and therefore implies
buffering); a validator derived from your own data is cheaper because it lets
your handler answer If-None-Match with a 304 before rendering at all.
Guide

Errors carry their cause

PdfReactError and PdfStructureError accept the standard ES2022
ErrorOptions, so wrapped failures keep the original reachable via
error.cause. The JSON envelope is unchanged — a cause may hold
non-serializable state, so it deliberately stays out.

Under the hood

  • resolveFonts no longer corrupts font-embedded output. It emitted
    fontRef without the leading slash, so every document rendered through the
    documented font path carried BT latin 10 Tf where ISO 32000 requires the
    name /latin — malformed for conforming readers. The new PDF/UA
    round-trip test caught it: the engine's validatePdfUA was rejecting this
    package's own output, and the release's adversarial-review arbiter traced
    the failure to the missing slash by bisection. Hand-built fontEntries
    with a correct /F3-style ref were never affected; re-render anything
    produced via resolveFonts or the options.fonts map.
  • Unstreamable documents fail fast now. The engine's streaming path
    rejects <TableOfContents> and {pages} templates, but checked this inside
    the generator — so renderToResponse (streaming by default) failed
    mid-response, after the headers were sent. renderToStream now runs the
    engine's validateDocumentStreamable eagerly; every streaming entry throws
    a catchable error at call time.
  • setDeflateImpl joins initNodeCompression in the re-export surface, so
    layout.compress can produce real compression in browsers and workers
    (without an injected DEFLATE, the engine's fallback made output slightly
    larger). PdfColors is re-exported type-only.
  • doctor() probes the engine newest-first (validatePrintOptions, a 1.7.0
    marker, then estimateChartHeight, the 1.6.0 marker) for graded messages: a
    1.6.x engine is reported as 1.6.x — this release needs >= 1.7.0 instead of
    a generic failure.
  • eslint-plugin-react-hooks (v7, recommended-latest) now lints src/; the
    two deliberate latest-value-ref patterns in hooks.ts are documented with
    per-site justifications rather than rule-wide opt-outs.
  • New tests: deterministic structural fuzzing of validateSpec (seeded PRNG —
    it must never throw on arbitrary malformed input), and the PDF/UA
    round-trip deferred from the 1.1.0 review — render tagged output, then
    validate the finished bytes with the engine's validatePdfUA.
  • The publish workflow's Trusted Publishing fix (an OIDC-capable npm is
    installed and verified before npm publish) ships in a release for the
    first time — it landed on main right after v1.1.0, whose publish it
    un-broke.

What is deliberately not here

Engine 1.7.0's headline feature — complete PAdES LTV signing (B-B → B-LTA),
document timestamps, /DSS revocation material, multiple sign...

Read more

v1.1.0 — Charts, server rendering, and an autonomous agent surface

Choose a tag to compare

@Nizoka Nizoka released this 26 Jul 21:28
4ac6f1b

pdfnative-react v1.1.0

Released 2026-07-25

Charts, server rendering, and an agent surface complete enough to drive the
package without a human.

Tracks the pdfnative 1.6.0
engine release. Everything in the public API is additive — but two
install-time floors moved
, so read the next section first.

Compatibility — read this first

npm install pdfnative-react@^1.1.0 pdfnative@^1.6.0 react@^19
Requirement 1.0.0 1.1.0
pdfnative peer ^1.5.0 ^1.6.0
Node.js >=20 >=22
React ^19.0.0 ^19.0.0 (unchanged)

Why the engine floor moved. <Chart> compiles to a chart block, which
does not exist before pdfnative 1.6.0. A 1.5 engine would receive an unknown
block type and silently drop or mis-render it. A loud install-time requirement
is better than a quiet wrong PDF.

Why the Node floor moved. It is inherited, not invented:
pdfnative@1.6.0 itself requires Node ≥ 22, so any compliant install is already
there. We now say so.

No API was removed, renamed, or changed in a backward-incompatible way.
docSpecSchema() and docSpecSchemaId() still work. PdfStructureError is
still importable from every path it was, and is still the same class object, so
instanceof is unaffected.

Security — re-render anything you encrypted

Two engine fixes arrive with the ^1.6.0 floor, and both affect documents
this package produced. If you have ever shipped a document with
layout.encryption, re-render it.

  • Encrypted documents leaked their outline, link URIs and metadata. Before
    engine 1.6.0 only streams were encrypted; strings were not. Because
    <Document outline="auto"> derives bookmark titles from every <Heading>, a
    password-protected document produced here disclosed its section headings, its
    <Link url> targets and its metadata to anyone who opened the file without
    the password.
  • AES-256 output was not spec-compliant. The engine's R6 hash used SHA-256
    for every round instead of the SHA-256/384/512 rotation ISO 32000-2
    Algorithm 2.B requires, so algorithm: 'aes256' files written on engine
    ≤ 1.5.0 were unreadable by strictly compliant readers. Output changes
    bit-for-bit; the engine keeps a legacy fallback so old files still open.

Neither is a defect in pdfnative-react's own code, and nothing you do at the
wrapper level worked around them — the fix is the engine upgrade this release
requires. See the Security section of the CHANGELOG.

Highlights

Charts

<Chart
    chartType="bar"
    series={[{ label: '2026', values: [15400, 21200, 29800, 38600] }]}
    categories={['Q1', 'Q2', 'Q3', 'Q4']}
    title="Revenue by quarter"
    altText="Revenue rises each quarter from 15.4k to 38.6k."
/>

Five types — bar, barH, line, pie, donut — drawn as pure PDF path
operators. No rasterisation, no chart library, no new runtime dependency, and
the output is real vector art that stays sharp at any zoom and passes PDF/A.
Multi-series, legends, "nice" axis ticks, gridlines, markers, palette overrides,
negative values, and a tagged-PDF /Figure + /Alt.

The matching DocSpec tuple is ['chart', { chartType, series, … }].

Guide · sample

Serving a PDF

// app/invoice/[id]/route.tsx
export async function GET() {
    return renderToResponse(<Invoice />, { fileName: 'invoice.pdf' });
}

renderToResponse returns a web-standard Response. Because Response is a
platform primitive rather than a framework type, the same code runs unchanged on
Node, the Edge runtime, Deno, Bun and Cloudflare Workers.

Streams by default — the body is a ReadableStream fed by the engine's
page-by-page generator, so peak memory stays flat and the client receives bytes
immediately. buffered: true switches to one buffer and adds Content-Length.
Content-Disposition follows RFC 6266, including filename* for non-ASCII
names.

Guide · sample

A client subpath, so RSC apps need no wrapper

import { PDFViewer, usePdf } from 'pdfnative-react/client';

pdfnative-react/client ships with the 'use client' directive already
applied — usePdf, usePdfStream, PDFViewer, PDFDownloadLink and
BlobProvider. The root barrel still exports them for apps with no RSC
boundary, and stays unmarked on purpose, because renderToResponse has to
remain server-safe.

One boundary this does not move: importing the package from a Server Component
or a 'use server' file still fails at module load, because the reconciler
needs createContext and React's react-server condition does not provide it.
Use a Route Handler — which is what the example above is.

Two packaging fixes ship alongside it. The bundle now keeps the node: prefix
on its dynamic node:fs/promises import, without which Deno and Cloudflare
nodejs_compat could not resolve it — so the edge runtimes listed above now
genuinely build. And importing pure data no longer pulls in the React
reconciler: import { version } went from 10 137 bytes to 3 216, as did
validateSpec, schema() and capabilityManifest(). The build fails if either
regresses.

Document-level page furniture

<Document
    watermark="DRAFT"
    header={{ left: 'Acme Inc', right: '{date}' }}
    footer={{ center: '{title}', right: 'Page {page} of {pages}' }}
    tagged="pdfa3b"
    attachments={[{ filename: 'data.xml', data, mimeType: 'application/xml' }]}
/>

These PdfLayoutOptions fields already worked, as an opaque and entirely
undocumented layout pass-through. They are now first-class props, with types,
schema coverage, samples and tests. {page}, {pages}, {date} and {title}
resolve at render time.

They are props rather than components on purpose: they are page furniture, not
blocks in the flow, and a component would mean a host tag with no corresponding
pdfnative block. An explicit layout prop still wins over all of them.

Sample

Linting

const report = lintDocument(<Invoice />);
// { ok, findings: [{ code, severity, message, blockIndex?, hint? }], counts }

Eighteen deterministic rules with stable L_* codes — 10 error, 7 warning,
1 info — covering accessibility (missing alt text, tables without headers,
skipped heading levels, unlabelled form fields) and, more valuably, eight
constraints the engine would otherwise enforce by throwing mid-render
:

Rule Would otherwise
L_CHART_EMPTY Throw — no series, or a series with no values
L_CHART_SERIES Throw — pie/donut need exactly one series
L_CHART_CATEGORIES Throw — series length must match categories
L_CHART_VALUES Throw — non-finite, or negative in a pie/donut
L_CHART_POINTS Throw — 10 000-point ceiling
L_ATTACHMENTS_NEED_PDFA3 Throw — attachments require tagged="pdfa3b"
L_TAGGED_ENCRYPTED Throw — PDF/A and encryption are mutually exclusive
L_MAX_BLOCKS_EXCEEDED Throw — past maxBlocks, default 100 000

Two more catch output that renders successfully but is wrong:
L_EMPTY_DOCUMENT (a blank page) and L_TAGGED_NO_FONTS (a PDF/A file veraPDF
rejects for a non-embedded font).

It runs on the compiled document model, so JSX and DocSpec share one
implementation, and it is pure — no console output, no throwing.

Guide · sample

An agent surface that can actually run alone

Until now an agent could author cheaply, via DocSpec, but could not check
the environment, discover the API, or verify its own output. That is closed:

doctor();                  // will this environment work? never throws
capabilityManifest();      // every component, block, entry point, error code
schema('list');            // seven subjects, each with a versioned $id
validateSpec(json);        // path-anchored findings, no JSON-Schema engine needed
lintSpec(spec);            // accessibility + engine legality

Plus a stable E_* error taxonomy: every error carries a code and serializes
to { ok: false, error: { code, message } }. Branch on the code — messages are
reworded between releases, codes are not.

The human-in-the-loop governance contract now ships as runtime capability too
(aiGovernancePolicy, agentRulesText, validateIssueDraft), so an agent
working from an installed package — with no repository checkout — can read the
rules it must follow. llms.txt is now in the published tarball for the same
reason.

Four dry-run tiers, cheapest first:

Tier Call Catches
1 validateSpec Malformed shape
2 compileSpec Structure that cannot map onto the model
3 lintSpec Accessibility, and engine constraints that would throw
4 inspectSpec Pagination and geometry

Contract · sample

Under the hood: one table, no drift

The hard part of shipping a machine-readable API description is that it rots.
src/registry.ts now holds the block grammar, the component list and the lint
rules as single-source tables; the JSON Schema, validateSpec and the capability
manifest all derive from them.

Two independent locks make omission a failure rather than a silent gap:

  • Compile-timeAssert<Equals<…>> types mean adding a member to
    BlockSpec or HostTag without registering it fails npm run typecheck.
  • Test-timetests/registry.test.ts pins the exact ordered contents, and
    tests/agent.test.tsx asserts every nam...
Read more

v1.0.0 — Stable release

Choose a tag to compare

@Nizoka Nizoka released this 17 Jul 19:23
f5910c6

pdfnative-react v1.0.0 — Stable release

The first stable release of pdfnative-react, the declarative React
renderer for the pdfnative PDF
engine. The public API is now covered by semantic versioning. This release
integrates the authoring features the engine added through 1.5.0 and ships the
conveniences originally planned for 0.4.0.

Breaking change

  • pdfnative is now a peer dependency (^1.5.0), not a bundled one.
    Install it alongside the wrapper:

    npm install pdfnative-react pdfnative react

    This lets your app pin the engine version and matches how pdfnative is
    already treated as external in the build. The engine floor is 1.5.0.

Highlights

  • Bookmarks & page labels. <Document outline={…}|"auto"> builds the
    viewer's bookmark tree; pageLabels controls page numbering (e.g. roman
    front matter). Both PDF/A-safe.
  • <Section> — a heading grouped with its content (title, level,
    color, break).
  • Nested lists<Item> may nest a child <List>, directly nested
    <Item> children, or use the items data prop.
  • Richer tablescellBorders, cellVAlign, per-column vAlign and
    kind: 'amount'.
  • Layout inspection & debugginginspectDocument / inspectSpec return
    page/block geometry without rendering; layout.debug overlays the boxes.
  • Viewer preferenceslayout.viewerPreferences (page mode/layout,
    toolbar visibility, displayDocTitle, …).
  • Constant-memory file outputrenderToFileStream /
    renderSpecToFileStream, preserving outline and page labels.
  • Font & asset ergonomicsresolveFonts(map) and options.fonts
    (async entry points); validateFontData(data) to sanity-check a custom font
    before embedding; fromUrl / fromBase64 for <Image> bytes.
  • SVG <text> renders as native, selectable PDF text.
  • AI-governance / human-in-the-loop contract.github/ai-governance.json,
    .github/AGENT_RULES.md, docs/AI_GOVERNANCE.md, a
    .github/drafts/ staging area, and a npm run verify:issue CLI. AI agents act
    strictly as draftsmen; the package makes no autonomous GitHub writes or network
    calls.
  • 11 new samples and matching tests.

Quick start

npm install pdfnative-react pdfnative react
import { Document, Section, renderToBytes } from 'pdfnative-react';

const bytes = renderToBytes(
  <Document title="Report" outline="auto">
    <Section title="Summary" level={1}>
      <p>Revenue grew 14% quarter over quarter.</p>
    </Section>
  </Document>,
);

Requirements

  • pdfnative ^1.5.0 (peer dependency)
  • React ^19.0.0
  • Node.js >=20

Scope

pdfnative-react covers document authoring. For byte-level post-processing —
merge/split, annotations, digital signatures, crypto providers, or font
compilation — use the pdfnative engine directly on the bytes this library
produces.

Full changelog: CHANGELOG.md.

v0.2.0 — First implemented release

Choose a tag to compare

@Nizoka Nizoka released this 14 Jun 08:10
6ca7371

pdfnative-react v0.2.0 — First implemented release

This is the first functional release of pdfnative-react, the declarative
React renderer for the pdfnative
PDF engine. (v0.1.0 only reserved the package name.)

Highlights

  • Write PDFs as components. A custom React reconciler compiles your JSX tree
    — synchronously, with no DOM — into a pdfnative DocumentParams object and
    renders real PDF bytes.
  • Declarative block flow, honestly modelled. Components map 1:1 onto the
    pdfnative blocks. There is intentionally no <View>/flexbox engine.
  • Full block coverage: Document, Page, Heading, Paragraph/Text,
    List/Item, Table/Row/Cell, Image, Link, Spacer, PageBreak,
    TableOfContents/Toc, Barcode, Svg, and FormField.
  • Four render targets: renderToBytes, renderToBlob, renderToStream
    (true constant-memory streaming), and renderToFile (Node).
  • React hooks & preview components: usePdf, usePdfStream, PDFViewer,
    PDFDownloadLink, and BlobProvider — familiar to @react-pdf/renderer
    users for easy migration.
  • Token-frugal AI authoring: a compact DocSpec grammar (compileSpec,
    renderSpec*) that compiles to the same PDF as the equivalent JSX, with a
    versioned Draft 2020-12 JSON Schema (docSpecSchema()) for self-validation.
  • Exhaustive, type-checked samples/ covering every 0.2.0 capability, plus
    a CycloneDX SBOM attached to each release and provenance-signed npm publishes.
  • Typed, tested, tree-shakeable: strict TypeScript, dual ESM + CJS builds,
    source maps, and a tested public surface.

Quick start

npm install pdfnative-react pdfnative react
import { Document, Heading, Paragraph, renderToBytes } from 'pdfnative-react';

const bytes = renderToBytes(
  <Document title="Hello">
    <Heading>Invoice</Heading>
    <Paragraph>Thank you for your business.</Paragraph>
  </Document>,
);

Requirements

  • React ^19.0.0
  • Node.js >=20

Notes

  • React 18 support is planned (see ROADMAP.md).
  • See the Knowledge Base for the compile pipeline
    and the react-reconciler version contract.

Full changelog: CHANGELOG.md.