@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/CONFORMANCE.md +10 -4
  3. package/README.md +28 -0
  4. package/dist/agent/AGENTS.md +48 -0
  5. package/dist/agent/manifest.json +215 -0
  6. package/dist/agent/pages/docs/examples/supported-behavior.md +75 -0
  7. package/dist/agent/pages/docs/examples.md +163 -0
  8. package/dist/agent/pages/docs/getting-started/installation.md +90 -0
  9. package/dist/agent/pages/docs/getting-started/quickstart.md +126 -0
  10. package/dist/agent/pages/docs/getting-started.md +61 -0
  11. package/dist/agent/pages/docs/guides/contents-links-bookmarks.md +123 -0
  12. package/dist/agent/pages/docs/guides/deployment.md +118 -0
  13. package/dist/agent/pages/docs/guides/errors-and-debugging.md +90 -0
  14. package/dist/agent/pages/docs/guides/images-and-fonts.md +102 -0
  15. package/dist/agent/pages/docs/guides/recipes.md +258 -0
  16. package/dist/agent/pages/docs/guides/remote-images.md +72 -0
  17. package/dist/agent/pages/docs/guides/reusable-components.md +81 -0
  18. package/dist/agent/pages/docs/guides/standalone-node.md +107 -0
  19. package/dist/agent/pages/docs/guides/svg-graphics.md +165 -0
  20. package/dist/agent/pages/docs/guides/testing.md +119 -0
  21. package/dist/agent/pages/docs/learn/document-tree.md +91 -0
  22. package/dist/agent/pages/docs/learn/runtime-and-data.md +63 -0
  23. package/dist/agent/pages/docs/learn/styling-and-layout.md +79 -0
  24. package/dist/agent/pages/docs/learn/templates-and-registry.md +96 -0
  25. package/dist/agent/pages/docs/reference/composables.md +55 -0
  26. package/dist/agent/pages/docs/reference/define-pdf.md +96 -0
  27. package/dist/agent/pages/docs/reference/errors-and-limits.md +89 -0
  28. package/dist/agent/pages/docs/reference/module-options.md +104 -0
  29. package/dist/agent/pages/docs/reference/page-sizes.md +54 -0
  30. package/dist/agent/pages/docs/reference/primitives.md +216 -0
  31. package/dist/agent/pages/docs/reference/registry.md +185 -0
  32. package/dist/agent/pages/docs/reference/standalone.md +53 -0
  33. package/dist/agent/pages/docs/reference/styles.md +176 -0
  34. package/dist/agent/pages/docs/reference/test-utilities.md +124 -0
  35. package/dist/build.mjs +2 -2
  36. package/dist/module.json +1 -1
  37. package/dist/module.mjs +14 -11
  38. package/dist/runtime/components/stubs.d.ts +22 -10
  39. package/dist/runtime/components/stubs.js +22 -27
  40. package/dist/runtime/server/assets/resolve-asset.js +2 -40
  41. package/dist/runtime/server/preview.js +1 -1
  42. package/dist/shared/{nuxt-pdf.DMC_Rdsz.mjs → nuxt-pdf.C-E8MM_K.mjs} +1 -0
  43. package/dist/shared/{nuxt-pdf.D3hUPqOJ.mjs → nuxt-pdf.CjVyPF31.mjs} +2 -2
  44. package/dist/test.mjs +1 -1
  45. 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`.