@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.
- package/CHANGELOG.md +18 -0
- package/CONFORMANCE.md +4 -4
- package/README.md +54 -12
- package/dist/agent/AGENTS.md +44 -0
- package/dist/agent/pages/docs/examples/supported-behavior.md +75 -0
- package/dist/agent/pages/docs/examples.md +163 -0
- package/dist/agent/pages/docs/getting-started/installation.md +75 -0
- package/dist/agent/pages/docs/getting-started/quickstart.md +126 -0
- package/dist/agent/pages/docs/getting-started.md +63 -0
- package/dist/agent/pages/docs/guides/contents-links-bookmarks.md +123 -0
- package/dist/agent/pages/docs/guides/deployment.md +118 -0
- package/dist/agent/pages/docs/guides/errors-and-debugging.md +90 -0
- package/dist/agent/pages/docs/guides/images-and-fonts.md +102 -0
- package/dist/agent/pages/docs/guides/recipes.md +258 -0
- package/dist/agent/pages/docs/guides/remote-images.md +72 -0
- package/dist/agent/pages/docs/guides/reusable-components.md +81 -0
- package/dist/agent/pages/docs/guides/standalone-node.md +107 -0
- package/dist/agent/pages/docs/guides/svg-graphics.md +165 -0
- package/dist/agent/pages/docs/guides/testing.md +119 -0
- package/dist/agent/pages/docs/learn/document-tree.md +91 -0
- package/dist/agent/pages/docs/learn/runtime-and-data.md +63 -0
- package/dist/agent/pages/docs/learn/styling-and-layout.md +79 -0
- package/dist/agent/pages/docs/learn/templates-and-registry.md +96 -0
- package/dist/agent/pages/docs/reference/composables.md +55 -0
- package/dist/agent/pages/docs/reference/define-pdf.md +96 -0
- package/dist/agent/pages/docs/reference/errors-and-limits.md +89 -0
- package/dist/agent/pages/docs/reference/module-options.md +104 -0
- package/dist/agent/pages/docs/reference/page-sizes.md +54 -0
- package/dist/agent/pages/docs/reference/primitives.md +216 -0
- package/dist/agent/pages/docs/reference/registry.md +185 -0
- package/dist/agent/pages/docs/reference/standalone.md +53 -0
- package/dist/agent/pages/docs/reference/styles.md +176 -0
- package/dist/agent/pages/docs/reference/test-utilities.md +128 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/test.mjs +13 -1
- 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`.
|