Skip to main content

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

TemplateSourceDemonstrates
Invoicepdfs/invoice.vueTyped props, computed totals, a fixed footer, dynamic page count
Reportpdfs/report.vueusePdfPageNumbers table of contents, internal links, bookmarks
Annual reportpdfs/annual-report.vueSVG bar / line / donut charts and tables drawn from Views
Certificatepdfs/certificate.vueSVG ornament craft with an engraver's border and a seal
E-bookpdfs/ebook.vueusePdfPageNumbers with chapter-aware running matter
Menupdfs/menu.vuePure 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.

pdfs/ebook.vue
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:

pdfs/ebook.vue
<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:

pdfs/annual-report.vue
<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.

pdfs/components/certificate/CertificateSeal.vue
<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.

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:

pdfs/menu.vue
<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.

Every template ships 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.