@lupinum/nuxt-pdf 0.4.0-beta.4 → 0.4.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/CONFORMANCE.md +4 -4
  3. package/README.md +54 -12
  4. package/dist/agent/AGENTS.md +44 -0
  5. package/dist/agent/pages/docs/examples/supported-behavior.md +75 -0
  6. package/dist/agent/pages/docs/examples.md +163 -0
  7. package/dist/agent/pages/docs/getting-started/installation.md +75 -0
  8. package/dist/agent/pages/docs/getting-started/quickstart.md +126 -0
  9. package/dist/agent/pages/docs/getting-started.md +63 -0
  10. package/dist/agent/pages/docs/guides/contents-links-bookmarks.md +123 -0
  11. package/dist/agent/pages/docs/guides/deployment.md +118 -0
  12. package/dist/agent/pages/docs/guides/errors-and-debugging.md +90 -0
  13. package/dist/agent/pages/docs/guides/images-and-fonts.md +102 -0
  14. package/dist/agent/pages/docs/guides/recipes.md +258 -0
  15. package/dist/agent/pages/docs/guides/remote-images.md +72 -0
  16. package/dist/agent/pages/docs/guides/reusable-components.md +81 -0
  17. package/dist/agent/pages/docs/guides/standalone-node.md +107 -0
  18. package/dist/agent/pages/docs/guides/svg-graphics.md +165 -0
  19. package/dist/agent/pages/docs/guides/testing.md +119 -0
  20. package/dist/agent/pages/docs/learn/document-tree.md +91 -0
  21. package/dist/agent/pages/docs/learn/runtime-and-data.md +63 -0
  22. package/dist/agent/pages/docs/learn/styling-and-layout.md +79 -0
  23. package/dist/agent/pages/docs/learn/templates-and-registry.md +96 -0
  24. package/dist/agent/pages/docs/reference/composables.md +55 -0
  25. package/dist/agent/pages/docs/reference/define-pdf.md +96 -0
  26. package/dist/agent/pages/docs/reference/errors-and-limits.md +89 -0
  27. package/dist/agent/pages/docs/reference/module-options.md +104 -0
  28. package/dist/agent/pages/docs/reference/page-sizes.md +54 -0
  29. package/dist/agent/pages/docs/reference/primitives.md +216 -0
  30. package/dist/agent/pages/docs/reference/registry.md +185 -0
  31. package/dist/agent/pages/docs/reference/standalone.md +53 -0
  32. package/dist/agent/pages/docs/reference/styles.md +176 -0
  33. package/dist/agent/pages/docs/reference/test-utilities.md +128 -0
  34. package/dist/module.json +1 -1
  35. package/dist/module.mjs +1 -1
  36. package/dist/test.mjs +13 -1
  37. package/package.json +27 -48
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: "Test a PDF"
3
+ description: "Assert against your own templates with a real render using @lupinum/nuxt-pdf/test, plus a realistic Vitest example."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/guides/testing"
5
+ route: "/docs/guides/testing"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Test a PDF
13
+
14
+ > Assert against your own templates with a real render using @lupinum/nuxt-pdf/test, plus a realistic Vitest example.
15
+
16
+ The utilities Nuxt PDF is tested with ship as `@lupinum/nuxt-pdf/test`. They run
17
+ a **real render** through the same pipeline that your server uses. This pipeline includes asset
18
+ resolution, font registration, and single-pass or multi-pass layout. You assert against actual
19
+ output, not a mocked tree. There is one parser, and it is the one the package's
20
+ own suite runs on.
21
+
22
+ ## Setup
23
+
24
+ The parser and rasterizer load `pdfjs-dist` and `@napi-rs/canvas` lazily. Install
25
+ them as dev dependencies of the project under test:
26
+
27
+ ```bash [pnpm]
28
+ pnpm add -D pdfjs-dist @napi-rs/canvas
29
+ ```
30
+
31
+ The helpers are runner-agnostic: they throw a `PdfAssertionError` with an
32
+ actionable message rather than depending on Vitest or Jest.
33
+
34
+ ## A realistic example
35
+
36
+ ```ts [test/invoice.test.ts]
37
+ import { describe, it } from 'vitest'
38
+ import { expectPdf, renderPdfSfc } from '@lupinum/nuxt-pdf/test'
39
+
40
+ describe('invoice.vue', () => {
41
+ it('renders the customer, a terms link, and an outline', async () => {
42
+ const { parsed } = await renderPdfSfc(
43
+ './pdfs/invoice.vue',
44
+ { invoice: { customer: 'Acme Corp', number: 'INV-001', total: 'EUR 1,250.00' } },
45
+ { fonts: [{ family: 'Invoice Sans', src: 'InvoiceSans-Regular.ttf' }] },
46
+ )
47
+
48
+ expectPdf(parsed)
49
+ .toHavePageCount(2)
50
+ .toContainText('Invoice for Acme Corp', { page: 1 })
51
+ .toHaveLink({ destination: 'terms', page: 1 })
52
+ .toHaveLink({ url: 'https://example.com/' })
53
+ .toHaveOutline([{ title: 'Terms' }])
54
+ })
55
+ })
56
+ ```
57
+
58
+ `renderPdfSfc(file, props, options)` compiles nested SFC imports, discovers local
59
+ images, bundles declared fonts, mounts through the real registry pipeline, and
60
+ returns `{ bytes, parsed, result }`. Use `renderPdfTemplate(Component, props)`
61
+ when the test already has an ordinary Vue component. Both helpers accept the
62
+ same partial `remote` and `limits` options as `nuxt.config`; the SFC helper also
63
+ accepts the same local `fonts` declarations.
64
+
65
+ ## Assertions
66
+
67
+ `expectPdf(parsed)` returns a chainable expectation:
68
+
69
+ | Assertion | Checks |
70
+ | --- | --- |
71
+ | `toHavePageCount(n)` | The document has `n` pages |
72
+ | `toContainText(text, { page? })` | The extracted text contains `text` (optionally on a page) |
73
+ | `toHaveLink({ destination? , url? , page? })` | A named-destination or external-URL link annotation exists |
74
+ | `toHaveOutline(shape)` | The bookmark outline matches the given title hierarchy |
75
+
76
+ `parsePdf` exposes the same data directly. The data includes page text, page count, flattened
77
+ link annotations, and the outline. Use it when you want to assert without the fluent
78
+ API.
79
+
80
+ ## Testing a server route
81
+
82
+ `parsePdf` also accepts a `PdfRenderResult` straight from the registry, so route
83
+ tests read naturally:
84
+
85
+ ```ts
86
+ import { parsePdf } from '@lupinum/nuxt-pdf/test'
87
+ import { pdf } from '#pdf'
88
+
89
+ const parsed = await parsePdf(await pdf.invoice.render(props))
90
+ ```
91
+
92
+ ## Pixel-level regressions
93
+
94
+ For visual regressions, `comparePdfSnapshot` follows a **reviewed-baseline**
95
+ policy: it writes per-page PNG baselines into a directory when
96
+ `UPDATE_PDF_BASELINES=1` (or `{ update: true }`) is set, and otherwise compares
97
+ each page against them within a pixel threshold:
98
+
99
+ ```ts
100
+ import { comparePdfSnapshot, renderPdfSfc } from '@lupinum/nuxt-pdf/test'
101
+
102
+ const { bytes } = await renderPdfSfc('./pdfs/invoice.vue', props)
103
+ await comparePdfSnapshot(bytes, './test/baselines/invoice')
104
+ ```
105
+
106
+ A failed comparison writes expected, actual, and diff PNGs for every changed
107
+ page plus `metrics.json` under `reports/pdf-snapshots`. Upload that directory in
108
+ CI so the failure can be inspected without reproducing it locally. Semantic
109
+ geometry checks are available through `parsePdf(...).pages[n].textRuns`; compare
110
+ coordinates with a tolerance instead of exact equality.
111
+
112
+ <info icon="lucide:info">
113
+ Raster baselines are environment-sensitive. Review them when they change and
114
+ commit them for review, exactly as you would a visual snapshot. Nuxt PDF does
115
+ not claim byte-identical output across machines.
116
+ </info>
117
+
118
+ The full set of exports and options is in the
119
+ [test utilities reference](/raw/docs/reference/test-utilities.md).
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: "The document tree"
3
+ description: "PDF primitives, valid nesting, Vue composition, and dynamic text."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/learn/document-tree"
5
+ route: "/docs/learn/document-tree"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # The document tree
13
+
14
+ > PDF primitives, valid nesting, Vue composition, and dynamic text.
15
+
16
+ Nuxt PDF renders a closed tree of PDF primitives. It does not render HTML or DOM
17
+ components.
18
+
19
+ ## Root and page structure
20
+
21
+ Each template has one `PdfDocument` root and at least one `PdfPage`:
22
+
23
+ ```vue
24
+ <PdfDocument>
25
+ <PdfPage>
26
+ <PdfView>
27
+ <PdfText>Quarterly report</PdfText>
28
+ </PdfView>
29
+ </PdfPage>
30
+ </PdfDocument>
31
+ ```
32
+
33
+ The main primitives are:
34
+
35
+ | Primitive | Role |
36
+ | --- | --- |
37
+ | `PdfDocument` | Document root and metadata |
38
+ | `PdfPage` | One fixed or wrapping page |
39
+ | `PdfView` | Flex layout container |
40
+ | `PdfText` | Text content and inline text runs |
41
+ | `PdfImage` | PNG or JPEG image |
42
+ | `PdfLink` | External or internal link |
43
+ | `PdfNote` | PDF note annotation |
44
+
45
+ The [primitive reference](/raw/docs/reference/primitives.md) lists the closed prop and
46
+ nesting surface. The [SVG guide](/raw/docs/guides/svg-graphics.md) covers vector
47
+ graphics.
48
+
49
+ <warning icon="lucide:triangle-alert">
50
+ Put all visible text inside `PdfText`. Text placed directly under `PdfPage` or
51
+ `PdfView` fails with `PDF_TREE_INVALID`; it is not discarded.
52
+ </warning>
53
+
54
+ ## Vue composition
55
+
56
+ Use local Vue components, typed props, slots, `v-if`, and keyed `v-for`. Keep
57
+ components owned by one document under a document-specific directory:
58
+
59
+ ```vue [pdfs/components/invoice/InvoiceLine.vue]
60
+ <script setup lang="ts">
61
+ defineProps<{ label: string; amount: string }>()
62
+ </script>
63
+
64
+ <template>
65
+ <PdfView :style="{ flexDirection: 'row', justifyContent: 'space-between' }">
66
+ <PdfText>{{ label }}</PdfText>
67
+ <PdfText>{{ amount }}</PdfText>
68
+ </PdfView>
69
+ </template>
70
+ ```
71
+
72
+ Import the component from the template. Nuxt app plugins, global components,
73
+ directives, and app-level provides do not enter the PDF tree.
74
+
75
+ ## Dynamic page text
76
+
77
+ Use a synchronous `render` callback for values known during pagination:
78
+
79
+ ```vue
80
+ <PdfText
81
+ fixed
82
+ :render="({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`"
83
+ />
84
+ ```
85
+
86
+ `fixed` repeats the node on each page. The callback returns a string or number.
87
+ For a table of contents that reads destination page numbers, use
88
+ [`usePdfPageNumbers()`](/raw/docs/guides/contents-links-bookmarks.md).
89
+
90
+ Invalid nesting, unknown primitive props, DOM attributes, `Teleport`, and
91
+ `v-show` fail instead of producing incomplete PDF content.
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: "Runtime and data loading"
3
+ description: "Where PDF templates execute and how data enters a render."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/learn/runtime-and-data"
5
+ route: "/docs/learn/runtime-and-data"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Runtime and data loading
13
+
14
+ > Where PDF templates execute and how data enters a render.
15
+
16
+ Each render creates a fresh Vue runtime-core application inside the Node
17
+ process. Nuxt PDF mounts the document tree, lays it out, serializes the PDF, and
18
+ then unmounts the Vue application.
19
+
20
+ ## Load data before rendering
21
+
22
+ Fetch and authorize data in normal server code. Pass the completed values
23
+ through typed props:
24
+
25
+ ```ts
26
+ import { pdf } from '#pdf'
27
+
28
+ export default defineEventHandler(async (event) => {
29
+ const id = getRouterParam(event, 'id')
30
+ const invoice = await loadAuthorizedInvoice(event, id)
31
+ const result = await pdf.invoice.render({ invoice })
32
+
33
+ return result.response()
34
+ })
35
+ ```
36
+
37
+ This keeps request context, credentials, and database access in the Nuxt server
38
+ where they belong. The template receives only the document data it needs.
39
+
40
+ ## Isolated Vue context
41
+
42
+ PDF templates can use local imports, Vue reactivity, lifecycle hooks, and
43
+ provide/inject within the document tree. They do not inherit:
44
+
45
+ - Nuxt app plugins or app-level provides;
46
+ - global application components or directives;
47
+ - browser globals such as `window` and `document`;
48
+ - `onServerPrefetch` as a data-loading mechanism.
49
+
50
+ Async setup, top-level `await`, and `defineAsyncComponent` are rejected because
51
+ layout cannot wait for document content to appear later.
52
+
53
+ ## Render stages and limits
54
+
55
+ One render covers metadata resolution, Vue mount, asset admission, layout,
56
+ optional page-number passes, serialization, and output collection. The module
57
+ applies one deadline and resource budget to that operation.
58
+
59
+ The deadline is checked between engine stages. It cannot interrupt a
60
+ synchronous layout stage midway. Do not run untrusted template code in the
61
+ process. Use the [errors and limits reference](/raw/docs/reference/errors-and-limits.md)
62
+ to size admitted data, and the [deployment guide](/raw/docs/guides/deployment.md) to
63
+ plan timeouts and queues.
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: "Styling and layout"
3
+ description: "The PDF style model, flex layout, units, inheritance, and shared geometry."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/learn/styling-and-layout"
5
+ route: "/docs/learn/styling-and-layout"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Styling and layout
13
+
14
+ > The PDF style model, flex layout, units, inheritance, and shared geometry.
15
+
16
+ The `style` prop accepts a typed PDF style object. It resembles a subset of CSS,
17
+ but no browser stylesheet or cascade is involved.
18
+
19
+ ## Core rules
20
+
21
+ - Use camelCase keys such as `backgroundColor` and `marginTop`.
22
+ - Unitless numbers are PDF points.
23
+ - Length strings accept `pt`, `in`, `mm`, `cm`, and `px` where the property
24
+ supports them.
25
+ - Percentages are strings such as `'50%'`.
26
+ - The default flex direction is `column`.
27
+ - Font family, size, weight, style, line height, color, opacity, and text
28
+ alignment can inherit through the document tree. Set other layout explicitly.
29
+
30
+ ```vue
31
+ <PdfView
32
+ :style="{
33
+ alignItems: 'center',
34
+ backgroundColor: '#f4f6f4',
35
+ flexDirection: 'row',
36
+ justifyContent: 'space-between',
37
+ padding: 12,
38
+ }"
39
+ >
40
+ <PdfText>Invoice total</PdfText>
41
+ <PdfText>EUR 1,250.00</PdfText>
42
+ </PdfView>
43
+ ```
44
+
45
+ ## Type shared styles
46
+
47
+ Use `satisfies PdfStyle` so TypeScript checks keys and values without widening
48
+ the object:
49
+
50
+ ```ts [pdfs/components/theme.ts]
51
+ import type { PdfStyle } from '@lupinum/nuxt-pdf'
52
+
53
+ export const eyebrow = {
54
+ color: '#566159',
55
+ fontSize: 7,
56
+ letterSpacing: 1.4,
57
+ textTransform: 'uppercase',
58
+ } satisfies PdfStyle
59
+ ```
60
+
61
+ Style arrays merge left to right. Nested arrays are flattened, and `false`,
62
+ `null`, and `undefined` entries are ignored:
63
+
64
+ ```vue
65
+ <PdfText :style="[base, isTotal && total]">
66
+ {{ amount }}
67
+ </PdfText>
68
+ ```
69
+
70
+ ## Share geometry once
71
+
72
+ When a table header and its rows must align, define the column widths in one
73
+ TypeScript module and import that definition from both components. A table is a
74
+ column of rows. Each row uses `flexDirection: 'row'` and the same fixed widths.
75
+ The header and body then cannot drift apart.
76
+
77
+ The [style reference](/raw/docs/reference/styles.md) contains the exact property,
78
+ unit, inheritance, and primitive matrix. The [recipe guide](/raw/docs/guides/recipes.md)
79
+ shows tables, totals, headers, and page-break patterns.
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: "Templates and the registry"
3
+ description: "How files under pdfs become typed server-side render functions."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/learn/templates-and-registry"
5
+ route: "/docs/learn/templates-and-registry"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Templates and the registry
13
+
14
+ > How files under pdfs become typed server-side render functions.
15
+
16
+ A PDF template is a Vue Single File Component under `pdfs/`. Nuxt PDF discovers
17
+ the file and creates one typed entry in the server-only `#pdf` registry.
18
+
19
+ Use `Pdf*` components inside PDF templates. In development, using one in an
20
+ application component reports an error. Move it under `pdfs/`, or replace it
21
+ with an HTML component.
22
+
23
+ ## Template discovery
24
+
25
+ The relative file path becomes the registry key:
26
+
27
+ | Template file | Registry entry |
28
+ | --- | --- |
29
+ | `pdfs/invoice.vue` | `pdf.invoice` |
30
+ | `pdfs/report.vue` | `pdf.report` |
31
+ | `pdfs/reports/monthly.vue` | `pdf['reports/monthly']` |
32
+
33
+ Three directories have a different purpose and do not become registry entries:
34
+
35
+ | Directory | Contents |
36
+ | --- | --- |
37
+ | `pdfs/components` | Local Vue components and shared TypeScript modules |
38
+ | `pdfs/assets` | Local PNG and JPEG files |
39
+ | `pdfs/fonts` | Local TTF, OTF, and WOFF2 files |
40
+
41
+ A project template replaces a same-key template from an extended Nuxt layer.
42
+ Duplicate keys in the same layer fail during module setup.
43
+
44
+ ## One definition per template
45
+
46
+ Each discovered template calls `definePdf()` exactly once at the top level of
47
+ `<script setup>`. It declares metadata and development preview data:
48
+
49
+ ```vue [pdfs/report.vue]
50
+ <script setup lang="ts">
51
+ type ReportProps = {
52
+ id: string
53
+ title: string
54
+ }
55
+
56
+ defineProps<ReportProps>()
57
+
58
+ definePdf<ReportProps>({
59
+ title: props => props.title,
60
+ filename: props => `report-${props.id}.pdf`,
61
+ sampleData: { id: 'sample', title: 'Sample report' },
62
+ })
63
+ </script>
64
+ ```
65
+
66
+ `title` and `filename` can be strings or synchronous functions of the render
67
+ props. `sampleData` and named scenarios are removed from production output.
68
+ They never become a server data source.
69
+
70
+ The [definePdf reference](/raw/docs/reference/define-pdf.md) lists every option and
71
+ its metadata precedence.
72
+
73
+ ## Typed rendering
74
+
75
+ Import the generated registry from server code:
76
+
77
+ ```ts [server/api/report.get.ts]
78
+ import { pdf } from '#pdf'
79
+
80
+ export default defineEventHandler(async () => {
81
+ const result = await pdf.report.render({
82
+ id: 'Q2-2026',
83
+ title: 'Quarterly report',
84
+ })
85
+
86
+ return result.response()
87
+ })
88
+ ```
89
+
90
+ The registry infers the required props from `defineProps<ReportProps>()`. Load
91
+ request, database, and API data before `render()`, then pass the resolved values
92
+ as props.
93
+
94
+ Use `renderPdf(key, props)` when the template key is only known at runtime. The
95
+ [registry reference](/raw/docs/reference/registry.md) documents its overloads, result
96
+ methods, and errors.
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: "Composables"
3
+ description: "The auto-imported usePdfPageNumbers map that activates multi-pass layout."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/composables"
5
+ route: "/docs/reference/composables"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Composables
13
+
14
+ > The auto-imported usePdfPageNumbers map that activates multi-pass layout.
15
+
16
+ ## usePdfPageNumbers
17
+
18
+ ```ts
19
+ const pageNumbers = usePdfPageNumbers()
20
+ // pageNumbers: Readonly<Record<string, number | undefined>>
21
+ ```
22
+
23
+ Auto-imported inside PDF templates and the components they render. Returns a
24
+ **readonly, reactive** map from each destination `id` to the 1-based page it
25
+ finally lands on.
26
+
27
+ Reading it is the only signal that turns on the multi-pass layout loop. The
28
+ engine lays the document out repeatedly, feeding each pass's destination-page map back
29
+ in, until the numbers stabilize.
30
+
31
+ ### Behavior
32
+
33
+ - **First pass is `undefined`.** Every entry is `undefined` until the loop has
34
+ located the destination. A template must support a missing number. Keep a
35
+ fallback such as `pageNumbers[id] ?? ''`.
36
+ - **First page of a section.** A number resolves to the page the destination's
37
+ node _starts_ on, even when the section spans pages.
38
+ - **Throws outside a render.** Calling it anywhere but a PDF template's setup
39
+ throws a `PDF_TEMPLATE_INVALID` error, rather than returning an empty map that
40
+ could be mistaken for first-pass state.
41
+
42
+ ```vue
43
+ <script setup lang="ts">
44
+ const pageNumbers = usePdfPageNumbers()
45
+ </script>
46
+
47
+ <template>
48
+ <PdfLink :href="`#${section.id}`">
49
+ {{ section.title }} {{ pageNumbers[section.id] ?? '' }}
50
+ </PdfLink>
51
+ </template>
52
+ ```
53
+
54
+ See [Contents, links & bookmarks](/raw/docs/guides/contents-links-bookmarks.md) for
55
+ convergence semantics and `maxPasses`.
@@ -0,0 +1,96 @@
1
+ ---
2
+ title: "definePdf"
3
+ description: "The compile-time definition for render metadata, development preview data, and the multi-pass limit."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/define-pdf"
5
+ route: "/docs/reference/define-pdf"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # definePdf
13
+
14
+ > The compile-time definition for render metadata, development preview data, and the multi-pass limit.
15
+
16
+ Each `pdfs/*.vue` template calls `definePdf` exactly once. It is a compile-time
17
+ macro (like `defineProps`); it declares render metadata and development-only
18
+ preview data and returns nothing.
19
+
20
+ ```ts
21
+ definePdf<Props>({
22
+ title: props => `Report ${props.id}`,
23
+ filename: props => `report-${props.id}.pdf`,
24
+ language: 'en-GB',
25
+ maxPasses: 5,
26
+ sampleData: { id: 'sample' },
27
+ scenarios: {
28
+ long: { id: 'long-report' },
29
+ },
30
+ })
31
+ ```
32
+
33
+ ## Options
34
+
35
+ | Option | Type | Default | Description |
36
+ | --- | --- | --- | --- |
37
+ | `title` | `string \| (props) => string` | None | Document title. A static string or a function of props. |
38
+ | `filename` | `string \| (props) => string` | None | Download filename, sanitized before use in `content-disposition`. |
39
+ | `language` | `string` | None | Document language tag written to the info dictionary (for example `"en-US"`). |
40
+ | `maxPasses` | `number` | `5` | Maximum layout passes before the multi-pass loop declares non-convergence and throws `PDF_LIMIT_EXCEEDED`. A positive integer. |
41
+ | `sampleData` | `Props` | None | Props used for the default development preview. |
42
+ | `scenarios` | `Record<string, Props>` | None | Named prop sets. Each set becomes a preview tab and a `?scenario=` query. |
43
+
44
+ The `Props` type parameter ties `title`, `filename`, `sampleData`, and each
45
+ `scenario` to the SFC's own props, so a mismatch is a type error.
46
+
47
+ <info icon="lucide:info">
48
+ `maxPasses` is only relevant to documents that read `usePdfPageNumbers()`.
49
+ Every other document renders in a single pass regardless of the value.
50
+ </info>
51
+
52
+ ## Metadata resolution
53
+
54
+ `title` and `filename` functions receive the render props and must return a
55
+ string synchronously. They are resolved at render time. When `title` or
56
+ `language` is present, it is authoritative and overrides the corresponding prop
57
+ authored on `PdfDocument`. When it is absent, the `PdfDocument` value remains the
58
+ rendered fallback. The authoritative values are reapplied after every
59
+ page-number update, so a multi-pass document cannot restore stale root metadata
60
+ between layout passes.
61
+
62
+ `resolveMetadata(props)` on the generated template handle resolves only the
63
+ `definePdf` values without mounting the component. `filename` controls the
64
+ default response filename; it is not a `PdfDocument` prop.
65
+
66
+ ## Preview data is development-only
67
+
68
+ `sampleData` and `scenarios` are used only by the development preview. They are
69
+ never exposed by the public `PdfTemplate` handle.
70
+
71
+ Production compilation keeps only `title`, `filename`, `language`, and
72
+ `maxPasses`. Preview expressions and routes are absent from the Nitro server
73
+ output. Keep preview data representative and disposable. Do not use it as
74
+ production runtime state or a place for secrets.
75
+
76
+ `definePdf()` is module-scoped, like Vue's other hoisted compiler macros. Its
77
+ values may be inline or imported from another module, but they cannot reference
78
+ a variable declared locally in `<script setup>`; that fails compilation instead
79
+ of producing a development-only `ReferenceError`. Keep imported preview-data
80
+ modules free of side effects. Production removes an unused import only when
81
+ evaluating that module has no required behavior.
82
+
83
+ ## Scenarios in the preview
84
+
85
+ ```ts
86
+ definePdf<Props>({
87
+ sampleData: { id: 'sample' },
88
+ scenarios: {
89
+ long: { id: 'long-report' },
90
+ empty: { id: 'no-sections' },
91
+ },
92
+ })
93
+ ```
94
+
95
+ The `long` scenario is available at `/_pdf/report?scenario=long`. An unknown
96
+ scenario name returns a 404 listing the available names.
@@ -0,0 +1,89 @@
1
+ ---
2
+ title: "Errors and limits"
3
+ description: "Error classes, exact codes, render-budget defaults, and when each failure occurs."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/errors-and-limits"
5
+ route: "/docs/reference/errors-and-limits"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Errors and limits
13
+
14
+ > Error classes, exact codes, render-budget defaults, and when each failure occurs.
15
+
16
+ Configuration and asset setup can fail before rendering starts. Public render
17
+ failures use `NuxtPdfError` and one code from `PDF_ERROR_CODES`.
18
+
19
+ ## Error classes and codes
20
+
21
+ | Failure | Stage | Inspect or change |
22
+ | --- | --- | --- |
23
+ | `TypeError` | Module setup | Read the message and correct the module option, template definition, local asset, or configured font. No render or template error code exists yet. |
24
+ | `PDF_TEMPLATE_NOT_FOUND` | Render dispatch | Check the template's relative filename and generated registry key. |
25
+ | `PDF_TEMPLATE_INVALID` | Template execution | Move `definePdf()` or `usePdfPageNumbers()` to its supported template/render scope. |
26
+ | `PDF_TREE_INVALID` | Vue mount or tree validation | Correct the named root, nesting rule, primitive prop, SVG reference, or text placement. |
27
+ | `PDF_LAYOUT_ERROR` | Layout | Check geometry and registered font family, weight, and style. |
28
+ | `PDF_LIMIT_EXCEEDED` | Any bounded render stage | Read the named limit and cap. Reduce the input or change that one configured budget. |
29
+ | `PDF_ASSET_INVALID` | Image admission | Replace invalid admitted PNG or JPEG bytes, dimensions, or declared format. |
30
+ | `PDF_ASSET_BLOCKED` | Image source or request policy | Check the source form, HTTPS allowlist, redirects, and per-hop timeout. |
31
+ | `PDF_RENDER_ERROR` | Render | Inspect the attached message and template identity for an uncategorized engine failure. |
32
+
33
+ `NuxtPdfError` exposes `code`, `templateKey`, and, in development,
34
+ `templateFile`. Its message names the template. Production leaves out the
35
+ relative source path.
36
+
37
+ `PDF_LIMIT_EXCEEDED` covers every configured time and resource budget. An
38
+ oversized local or remote image uses this code. Allowlist and unsupported-source
39
+ policy failures use `PDF_ASSET_BLOCKED`. Invalid bytes that passed the source
40
+ policy use `PDF_ASSET_INVALID`.
41
+
42
+ ## Render limits
43
+
44
+ Every field is an optional positive safe integer. Omitting the `limits` object
45
+ or one field keeps the default below. Invalid configuration throws `TypeError`
46
+ during module setup.
47
+
48
+ | Field | Default | Bounded work |
49
+ | --- | --- | --- |
50
+ | `timeoutMs` | `30_000` | Metadata resolution, mount, assets, every layout pass, serialization, and output collection. |
51
+ | `maxPages` | `2_000` | Laid-out page count. |
52
+ | `maxNodes` | `50_000` | Mounted document-tree nodes. |
53
+ | `maxTreeDepth` | `128` | Mounted document-tree depth. |
54
+ | `maxTextCharacters` | `2_000_000` | Unicode code points in text nodes. |
55
+ | `maxImages` | `256` | Image nodes. |
56
+ | `maxImageBytes` | `10_485_760` | Source bytes for one unique image. |
57
+ | `maxTotalImageBytes` | `33_554_432` | Source bytes across unique images. |
58
+ | `maxImagePixels` | `25_000_000` | Decoded pixels for one unique image. |
59
+ | `maxTotalImagePixels` | `100_000_000` | Decoded pixels across unique images. |
60
+ | `maxRemoteRequests` | `32` | Remote requests, including redirect hops. |
61
+ | `maxRemoteConcurrency` | `4` | Simultaneous remote requests. |
62
+ | `maxOutputBytes` | `67_108_864` | Completed PDF bytes. |
63
+
64
+ Configure overrides in `nuxt.config.ts`:
65
+
66
+ ```ts [nuxt.config.ts]
67
+ export default defineNuxtConfig({
68
+ pdf: {
69
+ limits: {
70
+ maxPages: 500,
71
+ timeoutMs: 20_000,
72
+ },
73
+ },
74
+ })
75
+ ```
76
+
77
+ The timeout is one deadline for the complete public render. It is checked
78
+ between engine stages and cannot interrupt synchronous layout midway. A single
79
+ stage can therefore finish after the deadline before the error is reported.
80
+
81
+ Tree and content limits are checked after Vue mounts and after each destination
82
+ page-number update. Image budgets apply before engine admission. Output is
83
+ collected into one completed buffer and discarded if `maxOutputBytes` is
84
+ exceeded.
85
+
86
+ `maxPasses` is a per-template `definePdf()` option, not a module limit. Its
87
+ default is `5`. A table of contents normally settles after destination numbers
88
+ feed into the next layout pass. Geometry that keeps changing fails with
89
+ `PDF_LIMIT_EXCEEDED`.