Documentation
Creating PDFs
PDFs in Vue
PDFs in Nuxt
PDFs in React
PDFs in Angular
Data & assets
Your data
A document is a component, so your data arrives as props. map, conditions, your own components,
context - anything that produces a tree produces a PDF.
import { Divider, Document, Page, Row, Text } from "@jasy/react";
type Line = { id: number; name: string; price: number };
export function Statement({ lines }: { lines: Line[] }) {
const total = lines.reduce((sum, l) => sum + l.price, 0);
return (
<Document size={11}>
<Page size="A4" margin={48} gap={8}>
{lines.map((line) => (
<Row key={line.id} justify="between">
<Text>{line.name}</Text>
<Text>{line.price} EUR</Text>
</Row>
))}
<Divider />
<Row justify="between">
<Text bold>Total</Text>
<Text bold>{total} EUR</Text>
</Row>
</Page>
</Document>
);
}
A PDF is rendered once, not kept alive like a page: load your data before renderToPdf and pass it
in. An effect that sets state after the first render (useEffect fetching data) comes too late - the
document has been read by then.
Custom fonts
The standard PDF fonts (Helvetica, Times, Courier) need nothing. To use your own, register the font on
<Document fonts> under a name, then use that name as a font. In the browser, fetch the bytes first:
const inter = new Uint8Array(await (await fetch("/fonts/Inter.woff2")).arrayBuffer());
const bytes = await renderToPdf(<Report font={inter} />);
function Report({ font }: { font: Uint8Array }) {
return (
<Document fonts={{ Inter: font }} font="Inter">
<Page size="A4" margin={48}>
<Text size={24} bold>
Set in Inter
</Text>
</Page>
</Document>
);
}
The container does not matter: .ttf, .woff and .woff2 are the same thing here - jasy unwraps
the wrapper itself. The font is subsetted on the way into the PDF, so the file stays small. For weights,
register one file per style: fonts={{ Inter: { normal, bold, italic } }}.
On a server a plain path works too - fonts={{ Inter: "./fonts/Inter.woff2" }} is the whole story.
In the browser there is no filesystem to point at, so it is bytes.
Images
<Image src> takes the image bytes as a Uint8Array, a URL, or - on a server - a path. JPEG and PNG
both work, and PNG transparency composites over whatever sits behind it, not a white box.
const logo = new Uint8Array(await (await fetch("/logo.png")).arrayBuffer());
<Image src={logo} width={120} height={40} fit="contain" />;
A URL is fetched while the document is prepared, before the layout runs, so its size is known when the page is laid out.
Render options
renderToPdf (and renderToPdfString) take the engine's render options as their second argument:
const bytes = await renderToPdf(<Invoice {...data} />, {
onOverflow: "warn",
accessible: true,
encrypt: { userPassword: "secret" },
onDiagnostic: (d) => console.log(d.code, d.message),
});
onOverflow- what happens when a single unbreakable element is taller than the page:"error"(default) throws,"warn"places it clipped and logs,"ignore"places it clipped.accessible- a tagged PDF with a structure tree for screen readers, see Accessibility.encrypt- AES-256 password protection, see Encryption.onDiagnostic- everything the engine has to say about the document, one call per finding, see Output.
A descriptor for a server
toDocumentDescriptor(element) stops before the PDF and returns the engine's descriptor: a plain
{ type, props, children } tree. To post it as JSON, encode font and image bytes yourself - JSON has no
Uint8Array - and decode them on the other side. Functions do not survive JSON at all, so a document
with a Canvas, a Flow or a PageBuilder goes through renderToPdf.