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:pdfaAn 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
resolveFontsno longer corrupts font-embedded output. It emitted
fontRefwithout the leading slash, so every document rendered through the
documented font path carriedBT latin 10 Tfwhere ISO 32000 requires the
name/latin— malformed for conforming readers. The new PDF/UA
round-trip test caught it: the engine'svalidatePdfUAwas rejecting this
package's own output, and the release's adversarial-review arbiter traced
the failure to the missing slash by bisection. Hand-builtfontEntries
with a correct/F3-style ref were never affected; re-render anything
produced viaresolveFontsor theoptions.fontsmap.- Unstreamable documents fail fast now. The engine's streaming path
rejects<TableOfContents>and{pages}templates, but checked this inside
the generator — sorenderToResponse(streaming by default) failed
mid-response, after the headers were sent.renderToStreamnow runs the
engine'svalidateDocumentStreamableeagerly; every streaming entry throws
a catchable error at call time. setDeflateImpljoinsinitNodeCompressionin the re-export surface, so
layout.compresscan produce real compression in browsers and workers
(without an injected DEFLATE, the engine's fallback made output slightly
larger).PdfColorsis re-exported type-only.doctor()probes the engine newest-first (validatePrintOptions, a 1.7.0
marker, thenestimateChartHeight, the 1.6.0 marker) for graded messages: a
1.6.x engine is reported as1.6.x — this release needs >= 1.7.0instead of
a generic failure.eslint-plugin-react-hooks(v7,recommended-latest) now lintssrc/; the
two deliberate latest-value-ref patterns inhooks.tsare 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'svalidatePdfUA. - The publish workflow's Trusted Publishing fix (an OIDC-capable npm is
installed and verified beforenpm publish) ships in a release for the
first time — it landed onmainright 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 signatures — is
byte-level post-processing, and golden rule 7 keeps it out of an authoring
renderer on purpose. The same goes for PdfModifier.updateMetadata, form
fill/flatten, merge/split, text extraction and validatePdfUA (which this
release does use — in the test suite, on the bytes it renders).
docs/RECIPES.md shows how to run each of them with the
engine directly on the bytes this package produces, including the new LTV
ladder.
Validation
npm run typecheck:all·npm run lint·npm test(292 tests, 18
files) ·npm run build— all green.- Coverage 95.0 % statements / 89.8 % branches / 97.8 % functions / 95.9 %
lines, against thresholds raised this release to 90/84/92/90. npm audit --omit=dev --audit-level=high: 0 vulnerabilities.- Two independent exhaustiveness audits walked the engine's 1.7.0 surface
against this package in both directions (engine → renderer and
field-by-field parity chains); every finding was fixed or dispositioned —
the full table is in the PR draft. - The new samples were executed end to end and produce valid PDFs.
npm run corpus:pdfa: 11 PDF/A-claiming files (2 negative canaries)
generated and self-checked (%PDF-magic, XMP claims match the manifest);
full veraPDF validation runs blocking in CI with the pinned 1.30.2
reference validator.