Repository navigation
pdfnative-react v1.3.0
Released 2026-09-22
Typography, CMYK and PDF/X-4, reproducible output, and the hardened
repository. This release tracks the
pdfnative engine's 1.8.0
and exposes every one of its authoring capabilities through both doors — JSX
and DocSpec: the typography engine (twelve opt-in keys, align="justify",
keepWithNext / splittable), CMYK colours on every colour position and
colour bars on the printer's marks, the PDF/X-4 conformance claim with a CMYK
or Gray output intent, a pinnable creationDate that makes the same document
render to the same bytes on every host, and 27 bundled Unicode scripts. Twelve
new lint rules (37 in all) turn every new engine constraint into a finding
before it is a throw.
The repository itself adopts the engineering standard of pdfnative 1.8.0,
pdfnative-cli 1.5.0 and pdfnative-mcp 1.7.0: one quality gate, a byte-exact
sample baseline held on Linux, Windows and macOS, an in-process PDF/X-4 gate
beside the blocking veraPDF gate, verified documentation, nine hardened
workflows with five required checks, Trusted Publishing with an SBOM and a
provenance attestation, and a committed Claude Code layer.
No public API was removed or renamed. Every new behaviour is opt-in: a
document that uses none of it compiles to the same DocumentParams, and four
superset proofs (runtime exports, JSON Schema, capability manifest, the frozen
v1.2.0 samples compiled against the 1.3.0 source) run on every push.
Compatibility — read this first
npm install pdfnative-react@^1.3.0 pdfnative@^1.8.0 react@^19| Requirement | 1.2.0 | 1.3.0 |
|---|---|---|
pdfnative (peer) |
^1.7.0 |
^1.8.0 |
| React (peer) | ^19.0.0 |
^19.0.0 |
| Node.js | >=22 |
>=22 |
The peer floor moves because layout.typography, layout.pdfx, CMYK colour
operands, print.marks.colourBars and setDeflateRawImpl do not exist before
engine 1.8.0 — an older engine would throw mid-render or silently write RGB.
doctor() now tells a 1.7.x engine apart from a missing one (a third
capability probe, setDefaultCreationDate, probed newest-first) and says
exactly what to upgrade.
Rendering behaviour also inherits engine 1.8.0's correctness fixes, with no
code change here: every embedded TrueType subset changes bytes (hinting
tables kept, head.checkSumAdjustment computed); printer's marks stop 0.5 pt
short of the trim line; Indic, Khmer, Myanmar, Thai/Lao and
Latin-with-combining-marks text takes its designed mark positions; soft
hyphens no longer render mid-word; /ToUnicode maps conjuncts to their source
code points. Documents on base-14 fonts that use none of those are
byte-identical. Every date is now written in UTC (+00'00') — the same
instant, a different offset string.
Highlights
Typography
<Document
fontEntries={fontEntries}
typography={{
splitParagraphs: true, orphans: 2, widows: 2,
keepHeadingsWithNext: { minLines: 3 },
unitBinding: true, bindShortWords: true,
opticalMargins: true, kerning: true, fontFeatures: ['smcp', 'onum'],
}}
>
<Heading level={2} keepWithNext>Results</Heading>
<Paragraph align="justify" splittable>…</Paragraph>
</Document>typography is a <Document> prop and a DocSpec field (sugar over
layout.typography; an explicit layout wins; unset means byte-identical
output). All twelve engine keys — splitParagraphs, orphans, widows,
keepHeadingsWithNext, unitBinding, bindShortWords, punctuationSpacing
('fr', 'fr-CA' or rules), opticalMargins, metrics, fontFeatures,
kerning, hyphenationLanguage — with a compile-time lock
(TypographyPropsCoverTypographyOptions) so the next engine key is a build
error here. ParagraphAlign = Align | 'justify' keeps Align three-valued
for images, barcodes, SVG and charts. The limits the engine states are stated
here too, in JSDoc, in the schema and as the lint rule
L_TYPOGRAPHY_INEFFECTIVE: kerning, features and the 'fr' narrow space need
a registered font; tnum / lnum are no-ops on Noto Sans; no hyphenation
dictionary ships (soft hyphens work; setHyphenationProvider is re-exported).
Guide: docs/TYPOGRAPHY.md; samples text/typography-engine.tsx,
text/typography-french.tsx.
CMYK colour and colour bars
Every colour position — color on headings, paragraphs, sections and links,
chart palettes and series, table borders and zebra stripes, watermarks,
headers and footers, layout.colors — accepts [c, m, y, k] in percent or
'c m y k' beside hex and RGB; the Color type admits the four-element
tuple, and validateSpec and the schema ($defs.color) accept both forms.
print.marks.colourBars (true or { tints, size }) draws the process
colours in the bleed strip; L_PRINT_COLOUR_BARS warns when the strip is too
thin. outputIntent becomes a <Document> prop and accepts CMYK and Gray
profiles; L_CMYK_INTENT_MISMATCH warns about CMYK content under a non-CMYK
intent. Guide: docs/PRINT.md; sample layout/cmyk.tsx.
PDF/X-4 — and how to check it
<Document pdfx="pdfx4" fontEntries={fontEntries} metadata={{ trapped: 'False' }}
outputIntent={{ iccProfile: pressProfile, outputConditionIdentifier: 'FOGRA39' }}
print={{ bleed: 14.17, marks: { crop: true, colourBars: true } }}>pdfx="pdfx4" writes the PDF/X-4 identification, a /GTS_PDFX output
intent, a TrimBox per page and /Trapped. The six constraints the engine
enforces by throwing — the target, no tagged, no encryption, a printer
(prtr) output intent, a known trapping state, a TrimBox or an ArtBox —
are the six L_PDFX_* lint rules, in the engine's order and with the
engine's wording; L_PDFX_NO_FONTS and L_PDFX_ANNOTATIONS mirror its
diagnostics, and L_OUTPUT_INTENT_PROFILE catches a malformed ICC profile
from the bytes. validatePdfX is deliberately not re-exported (golden
rule 7): docs/RECIPES.md shows the one-line engine call, and states that a
valid result means the structural prerequisites hold — not that a certified
preflight passed. Sample layout/print-pdfx4.tsx renders a press sheet and
checks it.
Reproducible bytes on every host
<Document creationDate="2026-01-01T00:00:00Z"> // or a Date; or setDefaultCreationDate(date) for the processPin the creation instant and the same document renders to the same bytes on
every host, in every time zone: dates are UTC, the {date} placeholder and
the trailer /ID follow the pin. An unparseable string is E_INPUT, never a
silent fallback to the clock. The library reads no environment variable —
a pipeline exporting SOURCE_DATE_EPOCH applies it in one line of its own.
Encrypted output is not reproducible by design. renderToResponse({ etag: true })
is therefore a stable validator across hosts when the document is pinned.
Guide: docs/REPRODUCIBLE.md; sample quality/reproducible.tsx.
27 scripts
The five scripts engine 1.8.0 adds (Lao, Tai Tham, New Tai Lue, Tai Le, Cham)
and the four Latin aliases (Hausa, Yoruba, Igbo, Swahili) work through
resolveFonts unchanged; sample fonts/scripts-27.tsx renders all nine.
Linting: 25 → 37 rules
Twelve rules added, twenty now pre-empt an engine throw, five mirror an
engine diagnostic (a throw under layout.strict). A 1.2.0 document trips
none of the twelve: each needs a 1.3.0 input. toErrorEnvelope classifies
the engine's input errors — PDF/X coherence, print geometry, output intent,
chart — as E_INPUT through the exported ENGINE_INPUT_ERROR_PREFIXES
table.
Engineered like the engine
npm run gate is the one definition of green: type-check of the sources,
tests, samples, scripts and the frozen v1.2.0 samples; lint; build; the built
artefacts probed ('use client' on the client bundle only, no console.log),
bundled for a browser and packed through publint and
@arethetypeswrong/cli; 38 samples rendered in their own processes with the
instant pinned and held to a byte baseline; coverage; 27 documentation rules;
a 16-file PDF/A and PDF/X corpus validated in-process (validatePdfX) and by
veraPDF. CI runs it on Linux (Node 22 and 24), Windows and macOS; five checks
are required on main; releases go through npm Trusted Publishing with a
build-provenance attestation and a CycloneDX SBOM. tests/regression/engine-surface.json
maps all 85 bullets of the engine's 1.8.0 changelog to the tests and samples
that exercise them here, or to a waiver with a reason.
Agents: the Claude Code layer
.claude/settings.json (no attribution trailer, Read denied on generated
files, HITL commands denied for Bash and PowerShell), a fail-closed guard hook,
rules generated from .github/instructions/, the release-audit skill, and
.github/ai-governance.json describing all of it. CLAUDE.md imports
AGENTS.md, which is under 120 lines; docs/AGENT_CONTRACT.md gains
§8 "Reproducible output for agents" and the grammar in one table.
Under the hood
src/registry.tsgainsDOC_SPEC_FIELDS(18 fields, compile-time locked
tokeyof DocSpec);validateSpecandcapabilityManifest().specFields
derive from it.- Six engine helpers re-exported unchanged (
setDeflateRawImpl,wrapZlib,
setDefaultCreationDate,getDefaultCreationDate,
setHyphenationProvider,getHyphenationProvider) and eleven types. L_OUTPUT_INTENT_IGNOREDno longer fires underpdfx;L_PRINT_BOXES
keeps delegating to the engine's validator.- vitest runs under
TZ=UTCwithpool: 'forks';typesVersionsadded for
theclientsubpath;samples/agent/visual-verify.tsxwrites to the
current directory.
What is deliberately not here
validatePdfXas an export — golden rule 7; the recipe is one line.- A
pdfnative-reactMCP server — dropped from the roadmap; pdfnative-mcp
covers the model. - A React Native renderer, an external visual tier, PDF/X-1a / X-3 /
PDF/A-4, a hyphenation-provider sample —ROADMAP.md§ Later.
Install
npm install pdfnative-react@1.3.0 pdfnative@1.8.0 react@^19Upgrade
No breaking changes. Drop-in replacement for v1.2.0, once the engine is at
1.8.0.
Migrating from v1.2.0
- Rebaseline once if you compare bytes. TrueType subsets, printer's marks
and shaped scripts change bytes (each was previously wrong); base-14-only
documents on a UTC host are unchanged. - Dates are UTC (
+00'00'/+00:00); same instant. {date}follows a pinnedcreationDate; unpinned documents are unchanged.setDeflateImplrejects raw DEFLATE — pass zlib output, or use
setDeflateRawImpl(pdfnative #78).- Hand-made ICC stubs in
outputIntentare rejected (acsp+ size field). PdfDiagnosticCodegained six codes — an exhaustiveswitchwith anever
check needs them.- New lint rules add nothing to existing documents;
report.okfor a 1.2.0
document is unchanged. ColorandParagraphAlignwidened;Alignunchanged. A consumer that
reads aColorinto a three-tuple variable needs a length guard.- For fork maintainers:
VERAPDF_REQUIRED=1→--require-all;.mjscorpus
scripts →npx tsx scripts/<name>.ts;npm installruns no lifecycle
scripts; the drift-gate grep in AGENTS.md is replaced bynpm run verify:docs.
The sample byte baseline is created by this release (every entry since: "1.3.0"); there is no earlier reference to rebaseline from.
Validation
Figures from the release branch (Windows 11, Node 22.17.0, veraPDF greenfield
1.30.2), as printed:
| Command | Result |
|---|---|
npx tsx scripts/gate.ts --publish --require-all |
14 passed, 0 skipped — every step green, including validate:pdfa with veraPDF |
npm run test:coverage |
811 tests across 43 files; 96.24 % statements, 92.38 % branches, 98.05 % functions, 97.2 % lines (thresholds 90 / 84 / 92 / 90) |
npm run build && npm run test:generate && npm run verify:samples |
38 tracked samples match the baseline (38 byte-exact, 0 semantic); generated twice, byte-identical |
npm run corpus:pdfa && npm run validate:pdfa |
11 PASS, 2 XFAIL, 0 FAIL, 0 XPASS, 0 INFRA (of 13 validated) — veraPDF greenfield 1.30.2 |
npm run validate:pdfx |
2 PASS, 1 XFAIL, 0 FAIL, 0 XPASS (of 3 validated) |
npm run verify:docs |
27 rules passed |
npm audit --audit-level=high |
found 0 vulnerabilities |
| Legacy fixtures through the published 1.2.0 (engine 1.7.0) and the 1.3.0 build | 8 DocSpecs: 6 byte-identical after date normalisation; 2 differ — both draw print.marks (the inherited 0.5 pt clearance fix, Upgrade note 1) |
Two-time-zone proof (tests/regression/reproducible-build.test.ts) |
Pacific/Kiritimati and America/Los_Angeles render the same bytes through dist/index.js |
Downstream / ecosystem notes
- pdfnative:
docs/assets/ecosystem.jsonpackages.pdfnative-react→ 1.3.0
(peer^1.8.0); the React cells ofdocs/data/surfaces.jsonfor typography,
CMYK, output intents, PDF/X-4,validatePdfX(via recipe), colour bars and
reproducible output;docs/guides/react.md("22 scripts" → 27, v1.3.0). - pdfnative-cli, pdfnative-mcp: the
pdfnative-reactrow of their
ecosystem.json.
Links
- CHANGELOG: ../CHANGELOG.md
- Full diff: v1.2.0...v1.3.0
- pdfnative 1.8.0 release notes: https://github.com/Nizoka/pdfnative/blob/main/release-notes/v1.8.0.md
- pdfnative-cli 1.5.0 release notes: https://github.com/Nizoka/pdfnative-cli/blob/main/release-notes/v1.5.0.md
- pdfnative-mcp 1.7.0 release notes: https://github.com/Nizoka/pdfnative-mcp/blob/main/release-notes/v1.7.0.md
- Agent contract: ../docs/AGENT_CONTRACT.md