Example gallery
Six playground templates and the technique that each template demonstrates.
The playground under playground/pdfs/ ships six complete templates. Each is an
ordinary .vue single-file component discovered by the module, rendered through
the same registry in development and production, and covered by an end-to-end
rendering test. Selected visually load-bearing showcases additionally compare
reviewed raster baselines. Run pnpm dev and open /_pdf to browse them, or
read each source alongside the excerpts below.
renderPdfSfc compiles the same nested child-SFC graph as the production Nuxt
module. The packed-package gate exercises typed child props, a local image, and
multiple local font faces.At a glance
| Template | Source | Demonstrates |
|---|---|---|
| Invoice | pdfs/invoice.vue | Typed props, computed totals, a fixed footer, dynamic page count |
| Report | pdfs/report.vue | usePdfPageNumbers table of contents, internal links, bookmarks |
| Annual report | pdfs/annual-report.vue | SVG bar / line / donut charts and tables drawn from Views |
| Certificate | pdfs/certificate.vue | SVG ornament craft with an engraver's border and a seal |
| E-book | pdfs/ebook.vue | usePdfPageNumbers with chapter-aware running matter |
| Menu | pdfs/menu.vue | Pure typography, diacritics, dotted leaders, tabular prices |
E-book with chapter-aware running matter
The Reed Line is a 21-page digest novella. It reads the resolved page map from
usePdfPageNumbers twice: once to print a
Contents page with real page numbers, and once for a running foot that names the
current chapter on every page it repeats onto. The chapter is derived from
measured pagination, never hard-coded.
const pageNumbers = usePdfPageNumbers()
// Derive the current chapter for any page from the resolved start-page map.
const chapterAt = (pageNumber: number): Ebook['chapters'][number] => {
let current = ebook.chapters[0]!
for (const chapter of ebook.chapters) {
const start = pageNumbers[chapter.id]
if (typeof start === 'number' && start <= pageNumber) current = chapter
}
return current
}The foot is a single fixed node whose :render closes over chapterAt, so one
callback prints the right chapter and folio on every page:
<PdfText
fixed
:render="({ pageNumber }) => `${chapterAt(pageNumber).title} · ${pageNumber}`"
:style="footStyle"
/>Annual report with SVG charts and View tables
The Fieldnote Studio annual report draws a grouped bar chart, a line chart with a
gradient area tint, and a donut. It uses the SVG primitives,
with every figure computed from one canonical YearFigures source. The donut
segments come from a pure geometry model and render as a v-for of paths:
<PdfSvg :viewBox="`0 0 ${donut.size} ${donut.size}`" :style="{ width: donut.size, height: donut.size }">
<PdfPath
v-for="segment in donut.segments"
:key="segment.id"
:d="segment.d"
:fill="segment.fill"
:transform="segment.transform"
/>
</PdfSvg>The financial statement has no first-class table primitive. It uses
View rows with right-aligned tabular numerals, hairline rules above totals, and
parenthesised outflows. The quarter and sector breakdowns provably sum to the
yearly totals (asserted in the template's test).
Certificate with SVG ornaments
The certificate is a single A4 landscape page whose decoration is drawn entirely
from primitives: a double-rule engraver's border with corner diamonds, and a
concentric seal with two rules, a 32-dot beaded coin edge, a five-point star, and a
single bronze RadialGradient used once.
<script setup lang="ts">
// Evenly spaced dots in the band between the two rules.
const beads = Array.from({ length: 32 }, (_, index) => {
const angle = (index / 32) * Math.PI * 2 - Math.PI / 2
return { cx: 50 + 45.2 * Math.cos(angle), cy: 50 + 45.2 * Math.sin(angle) }
})
</script>
<template>
<PdfSvg view-box="0 0 100 100">
<PdfDefs>
<PdfRadialGradient id="seal-bronze" cx="0.5" cy="0.38" r="0.62">
<PdfStop offset="0" stop-color="#E7CC86" />
<PdfStop offset="0.55" stop-color="#C9A24B" />
<PdfStop offset="1" stop-color="#8A6A2F" />
</PdfRadialGradient>
</PdfDefs>
<PdfCircle
v-for="(bead, index) in beads"
:key="`bead-${index}`"
:cx="bead.cx"
:cy="bead.cy"
:r="0.75"
/>
</PdfSvg>
</template>The decorative layers are absolutely positioned, so a long recipient name wraps at spaces to two centred lines and the page can never spill to a second sheet.
Menu with typography and diacritics
The Gasthaus Alpenrose card is two A5 pages of typography without borders or
boxes. German dish names carry their umlauts through rendering and extraction,
and a flex-1 dotted leader ties each name to a right-aligned tabular price:
<PdfView :style="{ alignItems: 'flex-end', flexDirection: 'row' }">
<PdfText :style="{ fontFamily: 'Inter', fontSize: 10, fontWeight: 600 }">
{{ dish.name }}
</PdfText>
<PdfView :style="{ flex: 1, marginHorizontal: 7, marginBottom: 2,
borderBottomWidth: 0.75, borderBottomStyle: 'dotted', borderBottomColor: leader }" />
<PdfText :style="{ fontFamily: 'Inter', fontSize: 10, textAlign: 'right', width: 32 }">
{{ formatMenuPrice(dish.price) }}
</PdfText>
</PdfView>Prices use the locale format. For example, 9.5 becomes 9,50. The whole card
is data-driven, so the winter scenario swaps every dish and pour without
touching the template.
sampleData and at least one named scenario, so the dev
preview at /_pdf can render each one without a live route. See
define-pdf for the definition shape.