@lupinum/nuxt-pdf 0.4.0-beta.3 → 0.4.0-beta.5
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 +34 -0
- package/CONFORMANCE.md +10 -4
- package/README.md +28 -0
- package/dist/agent/AGENTS.md +48 -0
- package/dist/agent/manifest.json +215 -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 +90 -0
- package/dist/agent/pages/docs/getting-started/quickstart.md +126 -0
- package/dist/agent/pages/docs/getting-started.md +61 -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 +124 -0
- package/dist/build.mjs +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +14 -11
- package/dist/runtime/components/stubs.d.ts +22 -10
- package/dist/runtime/components/stubs.js +22 -27
- package/dist/runtime/server/assets/resolve-asset.js +2 -40
- package/dist/runtime/server/preview.js +1 -1
- package/dist/shared/{nuxt-pdf.DMC_Rdsz.mjs → nuxt-pdf.C-E8MM_K.mjs} +1 -0
- package/dist/shared/{nuxt-pdf.D3hUPqOJ.mjs → nuxt-pdf.CjVyPF31.mjs} +2 -2
- package/dist/test.mjs +1 -1
- package/package.json +17 -13
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Draw SVG graphics"
|
|
3
|
+
description: "The SVG primitive set, direct presentation props, scoped definitions, SVG text, and the supported numeric boundary."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/svg-graphics"
|
|
5
|
+
route: "/docs/guides/svg-graphics"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Draw SVG graphics
|
|
13
|
+
|
|
14
|
+
> The SVG primitive set, direct presentation props, scoped definitions, SVG text, and the supported numeric boundary.
|
|
15
|
+
|
|
16
|
+
Nuxt PDF ships a set of SVG drawing primitives that render through the same
|
|
17
|
+
layout and render engine as the document primitives. A `PdfSvg` participates in
|
|
18
|
+
normal page flow as a flex leaf, measured from its `viewBox` aspect ratio.
|
|
19
|
+
|
|
20
|
+
## The primitive set
|
|
21
|
+
|
|
22
|
+
| Component | Role |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `PdfSvg` | SVG root; a flex leaf in page flow |
|
|
25
|
+
| `PdfG` | A group; presentation attributes cascade to children |
|
|
26
|
+
| `PdfPath` | A path from a `d` string |
|
|
27
|
+
| `PdfRect` | A rectangle (`x`, `y`, `width`, `height`, `rx`, `ry`) |
|
|
28
|
+
| `PdfCircle` | A circle (`cx`, `cy`, `r`) |
|
|
29
|
+
| `PdfEllipse` | An ellipse (`cx`, `cy`, `rx`, `ry`) |
|
|
30
|
+
| `PdfLine` | A line (`x1`, `y1`, `x2`, `y2`) |
|
|
31
|
+
| `PdfPolyline` | An open polyline from a `points` string |
|
|
32
|
+
| `PdfPolygon` | A closed polygon from a `points` string |
|
|
33
|
+
| `PdfDefs` | Holds referenceable defs (gradients, clip paths) |
|
|
34
|
+
| `PdfClipPath` | A clip path referenced by `clipPath="url(#id)"` |
|
|
35
|
+
| `PdfLinearGradient` / `PdfRadialGradient` | Gradients referenced by `fill="url(#id)"` |
|
|
36
|
+
| `PdfStop` | A gradient color stop |
|
|
37
|
+
| `PdfTspan` | A text span inside an SVG `PdfText` |
|
|
38
|
+
|
|
39
|
+
Full prop tables are in the [primitive reference](/raw/docs/reference/primitives.md).
|
|
40
|
+
|
|
41
|
+
## A basic drawing
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<PdfSvg viewBox="0 0 120 120" :style="{ width: 120, height: 120 }">
|
|
45
|
+
<PdfRect x="8" y="8" width="104" height="104" rx="12" fill="#eef2ee" />
|
|
46
|
+
<PdfCircle cx="60" cy="60" r="34" fill="#315d3b" />
|
|
47
|
+
<PdfPath d="M44 62 l12 12 l22 -26" stroke="#ffffff" stroke-width="6" fill="none" />
|
|
48
|
+
</PdfSvg>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Presentation is direct, not a style cascade
|
|
52
|
+
|
|
53
|
+
Set SVG paint and geometry with direct props. `PdfG` and the shape primitives
|
|
54
|
+
do not accept a generic `style` prop, so there is no prop-versus-style
|
|
55
|
+
precedence to remember. `transform` is also a direct SVG prop; page-flow
|
|
56
|
+
primitives such as `PdfView` put transforms in `PdfStyle` instead.
|
|
57
|
+
|
|
58
|
+
`PdfSvg` is the exception only at the page-flow boundary: its `style` sizes and
|
|
59
|
+
positions the SVG root in the surrounding PDF layout. It does not become a bag
|
|
60
|
+
of SVG presentation attributes. SVG `PdfText` may use `style` for text metrics
|
|
61
|
+
such as `fontFamily` and `fontSize`, but its paint color is the direct `fill`
|
|
62
|
+
prop.
|
|
63
|
+
|
|
64
|
+
Kebab-case attributes written in Vue templates (`stroke-width`, `stop-color`)
|
|
65
|
+
are coerced to the camelCase keys the renderer reads, so both forms work:
|
|
66
|
+
|
|
67
|
+
```vue
|
|
68
|
+
<!-- stroke-width and strokeWidth are equivalent on SVG nodes -->
|
|
69
|
+
<PdfLine x1="0" y1="0" x2="100" y2="0" stroke="#333" stroke-width="2" />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Presentation attributes set on a `PdfG` cascade to its children:
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<PdfG fill="#315d3b" transform="translate(10 10) rotate(15)">
|
|
76
|
+
<PdfRect x="0" y="0" width="20" height="20" />
|
|
77
|
+
<PdfRect x="30" y="0" width="20" height="20" />
|
|
78
|
+
</PdfG>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Numbers and transforms
|
|
82
|
+
|
|
83
|
+
Geometry accepts finite numbers or numeric strings; percentage strings are
|
|
84
|
+
accepted only by props typed as `PdfSvgLength`. `strokeWidth` does not accept a
|
|
85
|
+
percentage. Widths and radii must be non-negative, opacity and gradient-stop
|
|
86
|
+
values must be between `0` and `1` (or `0%` and `100%`), and a `viewBox` must
|
|
87
|
+
contain four finite numbers with positive width and height.
|
|
88
|
+
|
|
89
|
+
The transform surface accepts one to three space-separated
|
|
90
|
+
`translate(...)` or `rotate(...)` operations with unitless numeric arguments.
|
|
91
|
+
Scale, matrix, skew, angle units, and arbitrary transform strings are rejected.
|
|
92
|
+
|
|
93
|
+
Explicit zeroes keep SVG meaning despite truthiness fallbacks in the pinned
|
|
94
|
+
serializer:
|
|
95
|
+
|
|
96
|
+
- `fillOpacity="0"` paints fully transparent;
|
|
97
|
+
- `strokeWidth="0"` paints no stroke (not a PDF hairline); and
|
|
98
|
+
- a zero linear-gradient `x2`, or zero radial-gradient `cx`, `cy`, `fx`, `fy`,
|
|
99
|
+
or `r`, remains zero.
|
|
100
|
+
|
|
101
|
+
Nuxt PDF repairs those values after layout on the disposable resolved tree,
|
|
102
|
+
immediately before serialization. This is an engine-boundary correction, not a
|
|
103
|
+
second authoring format.
|
|
104
|
+
|
|
105
|
+
## Gradients and clip paths
|
|
106
|
+
|
|
107
|
+
Define a gradient or clip path inside a `PdfDefs` and reference it by `url(#id)`:
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<PdfSvg viewBox="0 0 200 100" :style="{ width: 200, height: 100 }">
|
|
111
|
+
<PdfDefs>
|
|
112
|
+
<PdfLinearGradient id="brand" x1="0" y1="0" x2="1" y2="0">
|
|
113
|
+
<PdfStop offset="0" stop-color="#315d3b" />
|
|
114
|
+
<PdfStop offset="1" stop-color="#7bbf88" />
|
|
115
|
+
</PdfLinearGradient>
|
|
116
|
+
</PdfDefs>
|
|
117
|
+
<PdfRect x="0" y="0" width="200" height="100" fill="url(#brand)" />
|
|
118
|
+
</PdfSvg>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
<info icon="lucide:info">
|
|
122
|
+
A `url(#id)` reference resolves only against a `PdfDefs` in the same `PdfSvg`
|
|
123
|
+
subtree. Definition ids must be safe and unique within that SVG, and each SVG
|
|
124
|
+
accepts at most one `PdfDefs`. A missing, malformed, or incompatible reference
|
|
125
|
+
(for example a clip path used as a fill) fails with `PDF_TREE_INVALID`; it is
|
|
126
|
+
never rendered as an accidental fallback. The same definition id may be reused
|
|
127
|
+
in another `PdfSvg` because scopes are independent.
|
|
128
|
+
</info>
|
|
129
|
+
|
|
130
|
+
## SVG text
|
|
131
|
+
|
|
132
|
+
`PdfText` has two context-specific contracts. In page flow it accepts wrapping,
|
|
133
|
+
pagination, bookmark, destination, and dynamic-text props. Inside `PdfSvg`, both
|
|
134
|
+
`x` and `y` are required, `fill` is the direct paint prop, and page-flow-only
|
|
135
|
+
props are rejected. SVG text may hold `PdfTspan` children, which join and chain
|
|
136
|
+
along the x-axis:
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<PdfSvg viewBox="0 0 200 40" :style="{ width: 200, height: 40 }">
|
|
140
|
+
<PdfText :x="0" :y="24" fill="#18251d">
|
|
141
|
+
<PdfTspan>Total </PdfTspan>
|
|
142
|
+
<PdfTspan fill="#315d3b">EUR 1,250.00</PdfTspan>
|
|
143
|
+
</PdfText>
|
|
144
|
+
</PdfSvg>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The text content is preserved in the extracted page text, so it remains
|
|
148
|
+
selectable and testable. `PdfTspan` accepts only
|
|
149
|
+
`x`, `y`, and `fill`; it has no generic `style`, stroke, transform, or other
|
|
150
|
+
shape presentation props.
|
|
151
|
+
|
|
152
|
+
## Nesting rules and what is not claimed
|
|
153
|
+
|
|
154
|
+
`PdfSvg` is a valid child of `PdfPage` and `PdfView` but is rejected directly
|
|
155
|
+
inside `PdfText`. Leaf shapes stay childless. Each container accepts only its
|
|
156
|
+
valid children; invalid nesting fails with a targeted diagnostic.
|
|
157
|
+
|
|
158
|
+
Within SVG, the following are **not** claimed in the current alpha:
|
|
159
|
+
|
|
160
|
+
- `Marker` (`markerStart` / `markerMid` / `markerEnd`);
|
|
161
|
+
- alternate `gradientUnits`, `gradientTransform`, gradient inheritance, and
|
|
162
|
+
`preserveAspectRatio` modes;
|
|
163
|
+
- radial-gradient inner radius (`fr`), which the pinned renderer hardcodes to
|
|
164
|
+
zero; and
|
|
165
|
+
- SVG image _files_ as an image source.
|
|
@@ -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`.
|