Documentation
Output options
renderToBytes(doc, options) turns a document into bytes. Everything on this page is optional -
renderToBytes(doc) on its own already produces a valid PDF.
import { renderToBytes } from "@jasy/pdf";
const bytes = await renderToBytes(doc, {
title: "Q3 report",
accessible: true,
});
Files inside the PDF
A PDF can carry other files. That is how a ZUGFeRD invoice hides its XML inside the page a human reads, and you can use it for anything else: the CSV a table was built from, the contract a quote refers to, the source of a chart.
const bytes = await renderToBytes(doc, {
attachments: [
{
name: "figures.csv",
data: new TextEncoder().encode("month,revenue\n2026-06,42000\n"),
mimeType: "text/csv",
relationship: "Source",
description: "The numbers behind the chart on page 2",
},
],
});
| Field | Meaning |
|---|---|
name | the file name a reader shows, e.g. "figures.csv" |
data | the bytes (Uint8Array) |
mimeType | e.g. "text/csv"; default "application/octet-stream" |
relationship | /AFRelationship: "Data", "Source", "Alternative", "Supplement" |
description | free text shown next to the file |
relationship says what the file IS to the document rather than how it is stored, and a validator
checks it: ZUGFeRD requires "Data" for its factur-x.xml, because the XML is the invoice, not a
supplement to it. Attachments are written as associated files, which is what PDF/A-3 and PDF 2.0
require - a plain /EmbeddedFiles entry would not satisfy either.
Building a PDF/A yourself
@jasy/e-invoice does all of this for you, and for an invoice you should let it.
These options exist because the same machinery is useful for any archival PDF - a contract, a report
that has to survive a decade:
const bytes = await renderToBytes(doc, {
pdfVersion: "1.7", // PDF/A-3 needs it; default "1.4"
documentId: true, // a trailer /ID, which PDF/A requires
outputIntent: sRGBProfileBytes, // an ICC profile, so colour is defined
xmp: xmpPacket, // the XMP that DECLARES the conformance level
standardFonts: false, // no Helvetica: PDF/A wants every font embedded
fonts: { Helvetica: myEmbeddedFace },
});
Two of those are easy to get wrong:
standardFonts: falseis the point, not a detail. PDF/A forbids relying on the reader's fonts, and the standard 14 are exactly that. Turning them off means every font name you use must arrive throughfonts- which is why@jasy/e-invoiceships the Liberation faces under the namesHelvetica,TimesandCourier.xmpis what a validator reads. The conformance level lives in the XMP packet, not in the structure. Getting the structure right and the packet wrong produces a file that claims nothing.
documentId writes a deterministic /ID: it is a hash of the objects, not a random number and not
the clock. That is what lets the same document render byte-identically twice, so an archived file stays
re-derivable years later.
Typography and size
| Option | Default | What it does |
|---|---|---|
fonts | - | embedded faces, keyed by the name used in Text({ font }) |
kerning | true | space glyph pairs from the font's own data; false is the older output |
compress | true | FlateDecode the streams; false gives a greppable, readable PDF |
compress: false is a debugging tool worth knowing about: the content stream becomes plain text, so
you can open the PDF in an editor and read the operators jasy wrote.
Telling you what happened
const bytes = await renderToBytes(doc, {
onOverflow: "warn",
onMissingGlyphs: (chars) => report(`not drawable: ${chars.join(" ")}`),
});
onOverflow- what to do when a single block is taller than a whole page and cannot be broken."error"(default) throws,"warn"logs,"ignore"is silent. It is clipped either way: the alternative is a pagination loop that never ends.onMissingGlyphs- called once with any characters no font could draw. They are removed rather than written as the font's.notdefbox, which PDF/A forbids. Deliberately a callback and not a policy: a missing tick mark must never stop an invoice from being produced, but your application should be able to say which character went missing.
Metadata and access
| Option | What it does |
|---|---|
title | the document title - used by readers, and required for a conformant tagged PDF |
lang | document language, e.g. "de-DE" (default "en-US") |
accessible | emit a tagged structure tree - see Accessibility |
encrypt | AES-256 password protection - see Encryption |
encrypt and PDF/A are mutually exclusive, and jasy says so rather than producing a file that fails
validation later: PDF/A forbids encryption, so encrypting a ZUGFeRD invoice throws.