Documentation
Data & assets
Reactive data
The whole point of authoring in Vue is that your data drives the document. v-for, v-if, computed
and slots all work - the renderer walks the same tree Vue builds, so anything that produces a tree
produces a PDF.
<script setup lang="ts">
import { computed } from "vue";
import { Document, Page, Row, Text, Divider } from "@jasy/vue";
const props = defineProps<{ lines: { id: number; name: string; price: number }[] }>();
const total = computed(() => props.lines.reduce((sum, l) => sum + l.price, 0));
</script>
<template>
<Document :size="11">
<Page :size="'A4'" :margin="48" :gap="8">
<Row v-for="line in lines" :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>
</template>
Custom fonts
The standard PDF fonts (Helvetica, Times, Courier) need nothing. To use your own, register the font
bytes on <Document :fonts> under a name, then reference that name as a font:
<script setup lang="ts">
import { ref, onMounted } from "vue";
const font = ref<Uint8Array>();
onMounted(async () => {
font.value = new Uint8Array(await (await fetch("/fonts/Inter.ttf")).arrayBuffer());
});
</script>
<template>
<Document v-if="font" :fonts="{ Inter: font }" font="Inter">
<Page :size="'A4'" :margin="48">
<Text :size="24" bold>Set in Inter</Text>
</Page>
</Document>
</template>
The container does not matter: .ttf, .woff and .woff2 are the same thing here - jasy unwraps
the wrapper itself. Full Unicode comes with it, and the font is subsetted on the way into the PDF, so
the file stays small. For weights, register one file per style as
{ Inter: { normal, bold, italic } }.
With a bundler you do not have to fetch by hand - ask it for the URL and let jasy do the rest:
<script setup lang="ts">
import interUrl from "~/assets/Inter.woff2?url";
const doc = ref();
onMounted(async () => {
// addFontFromUrl resolves AT registration, so a dead link fails here, not mid-render
await doc.value.addFontFromUrl("Inter", interUrl);
});
</script>
On the server (Nuxt's definePdfHandler, or any Node process) a plain path works too - there
:fonts="{ Inter: '/path/to/Inter.woff2' }" is the whole story. In the browser there is no filesystem
to point at, so it is bytes or a URL.
Images
<Image :src> takes the image bytes as a Uint8Array. JPEG and PNG both work, and PNG transparency is
honoured (it composites over whatever sits behind it, not a white box). Fetch the bytes the same way as
a font:
<script setup lang="ts">
import { ref, onMounted } from "vue";
const logo = ref<Uint8Array>();
onMounted(async () => {
logo.value = new Uint8Array(await (await fetch("/logo.png")).arrayBuffer());
});
</script>
<template>
<Image v-if="logo" :src="logo" :width="120" :height="40" :fit="'contain'" />
</template>
In Node, read the file into a Uint8Array (for example with fs.readFileSync) and pass it the same
way.
Render options
renderToPdf (and renderToPdfString) take a third argument for engine options. The most useful is
onOverflow, which decides what happens when a single unbreakable element is taller than the page:
const bytes = await renderToPdf(Invoice, props, { onOverflow: "warn" });
"error"(default) - throw, so an impossible layout never slips out silently."warn"- place it anyway (clipped) and log."ignore"- place it anyway, no log.