@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,185 @@
1
+ ---
2
+ title: "Registry and render result"
3
+ description: "The typed #pdf server module, runtime dispatch, and completed render conversions."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/registry"
5
+ route: "/docs/reference/registry"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Registry and render result
13
+
14
+ > The typed #pdf server module, runtime dispatch, and completed render conversions.
15
+
16
+ Nuxt generates an inspectable `#pdf` module from the discovered templates. It is
17
+ a server-only module for Nitro code and is typed from each SFC's props. Do not
18
+ import it from a client component or other browser code.
19
+
20
+ ```ts
21
+ import { pdf, renderPdf, pdfTemplateKeys } from '#pdf'
22
+ ```
23
+
24
+ ## pdf
25
+
26
+ `pdf` is an object keyed by template name. Property access infers each SFC's
27
+ props. Every value has exactly three public members:
28
+
29
+ ```ts
30
+ interface PdfTemplate<Props extends object> {
31
+ readonly key: string
32
+ resolveMetadata(props: Props): {
33
+ title?: string
34
+ filename?: string
35
+ language?: string
36
+ }
37
+ render(props: Props): Promise<PdfRenderResult>
38
+ }
39
+ ```
40
+
41
+ The handle does not expose the compile-time definition, preview data, source
42
+ file, or a second preview render method.
43
+
44
+ ```ts
45
+ await pdf.invoice.render({ invoice })
46
+ await pdf.report.render({ report })
47
+ ```
48
+
49
+ Nested template paths keep their slash keys (for example
50
+ `pdf['reports/monthly']`).
51
+
52
+ ## renderPdf
53
+
54
+ `renderPdf(name, props)` is the functional form. A **literal** template name
55
+ infers its props:
56
+
57
+ ```ts
58
+ await renderPdf('invoice', { invoice })
59
+ ```
60
+
61
+ A runtime string cannot be type-checked, so it requires the explicit
62
+ unknown-props escape hatch:
63
+
64
+ ```ts
65
+ await renderPdf(templateName, untypedProps, { unsafe: true })
66
+ ```
67
+
68
+ <warning icon="lucide:triangle-alert">
69
+ `{ unsafe: true }` does **not** validate the props; it only makes the loss of
70
+ static typing visible at the call site. An unknown template name still fails with
71
+ `PDF_TEMPLATE_NOT_FOUND`.
72
+ </warning>
73
+
74
+ `pdfTemplateKeys` is the array of discovered template names.
75
+
76
+ `getPdfTemplate(name)` returns that typed three-member handle for a literal name,
77
+ or `undefined` for an unknown name. Reach for it when you need resolved
78
+ `definePdf` metadata without rendering:
79
+
80
+ ```ts
81
+ import { getPdfTemplate } from '#pdf'
82
+
83
+ const template = getPdfTemplate('invoice')
84
+ const { title, filename } = template.resolveMetadata(props)
85
+ ```
86
+
87
+ `resolveMetadata()` evaluates the `title` and `filename` declared by
88
+ `definePdf`, and returns its `language`. It does not mount the component, so an
89
+ authored `PdfDocument` fallback is not part of this result.
90
+
91
+ `NuxtPdfError` and `PDF_ERROR_CODES` are also re-exported from `#pdf`, so
92
+ server code can branch on render failures without extra imports. See
93
+ [Errors and limits](/raw/docs/reference/errors-and-limits.md).
94
+
95
+ ## The render result
96
+
97
+ Both `pdf.name.render(props)` and `renderPdf(...)` resolve to a
98
+ `PdfRenderResult`. The document is rendered once; every conversion reuses that
99
+ completed, immutable byte buffer:
100
+
101
+ ```ts
102
+ const result = await pdf.invoice.render({ invoice })
103
+
104
+ result.metadata // frozen title, filename, and language for this render
105
+ await result.toUint8Array() // Promise<Uint8Array>
106
+ await result.toBuffer() // Promise<Buffer>
107
+ await result.response() // Promise<Response>
108
+ ```
109
+
110
+ `result.metadata` is the frozen `title`, `filename`, and `language` resolved for
111
+ this render: `definePdf` values win when present, otherwise authored
112
+ `PdfDocument` fallbacks. `title` and `language` describe PDF Info fields;
113
+ `filename` is response/download metadata and is not written into PDF Info.
114
+ `result.diagnostics` is one frozen object shared with the development preview:
115
+
116
+ | Field | Meaning |
117
+ | --- | --- |
118
+ | `durationMs` | Wall time for the whole public render, including metadata evaluation |
119
+ | `byteLength` | Exact output byte count |
120
+ | `pageCount` | Number of rendered pages |
121
+ | `passes` | Layout passes used (`1` normally, more for a TOC) |
122
+ | `layoutWarnings` | Frozen, structured warnings for layout that rendered but needs attention |
123
+ | `registeredFontFaces` | Frozen family/weight/style facts; never source bytes or paths |
124
+
125
+ Diagnostics contain no document content, props, or resource URLs. An
126
+ application can send them to its metrics layer.
127
+
128
+ `layoutWarnings` is empty for a healthy layout. Nuxt PDF currently reports
129
+ `PDF_UNBREAKABLE_NODE_OVERFLOW` when a non-fixed node cannot wrap and is taller
130
+ than the page's usable content height. The warning identifies the page and
131
+ primitive and includes both heights in points, without copying text or ids from
132
+ the document. Allow the node to wrap or make it small enough to fit on one page.
133
+
134
+ ## Development and production boundary
135
+
136
+ The development preview keeps `sampleData`, named `scenarios`, and the relative
137
+ source file outside the public template handle. It calls the same public
138
+ `render(props)` method as application code and displays that completed result.
139
+ There is no separate preview renderer or second document render.
140
+
141
+ Editing a template rebuilds its registry and refreshes the document with its
142
+ diagnostics. The preview keeps the selected scenario.
143
+
144
+ In a production build the SFC transform reconstructs the render definition from
145
+ only `title`, `filename`, `language`, and `maxPasses`. `sampleData` and
146
+ `scenarios` are structurally omitted before Nitro bundles the template; they are
147
+ not public handle properties and are not merely hidden behind runtime checks.
148
+ The development preview data and routes are also absent. Packed-consumer checks
149
+ verify this boundary and keep the renderer and template SFCs out of the client
150
+ output.
151
+
152
+ Metadata is hoisted: inline values and imports are supported, while references
153
+ to bindings declared locally in `<script setup>` are compile errors. Imported
154
+ preview-data modules must be side-effect-free so their unused production imports
155
+ can be eliminated.
156
+
157
+ ### response(init)
158
+
159
+ `response()` returns a `Response` with `content-type: application/pdf`, the
160
+ exact `content-length`, and a sanitized, encoded-length-bounded
161
+ `content-disposition` filename:
162
+
163
+ ```ts
164
+ await result.response({
165
+ disposition: 'inline', // 'attachment' (default) | 'inline'
166
+ filename: 'invoice.pdf', // sanitized before writing the header
167
+ headers: { 'cache-control': 'no-store' },
168
+ })
169
+ ```
170
+
171
+ | Field | Type | Description |
172
+ | --- | --- | --- |
173
+ | `disposition` | `'attachment' \| 'inline'` | `content-disposition` type; defaults to `attachment` |
174
+ | `filename` | `string` | Download filename; sanitized before use; defaults to template metadata or `document.pdf` |
175
+ | `headers` | `HeadersInit` | Extra response headers merged in |
176
+ | `status`, `statusText` | `number` / `string` | Standard `ResponseInit` fields |
177
+
178
+ Caller-provided `content-type`, `content-length`, and `content-disposition`
179
+ headers are replaced with the truthful values from the completed result.
180
+
181
+ ## Regenerating the registry
182
+
183
+ The registry is written during module setup and regenerated whenever templates
184
+ change in development. If `#pdf` imports appear untyped in your editor, run
185
+ `nuxt prepare` once to refresh the generated types.
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: "Standalone build and runtime"
3
+ description: "Production registry generation and Node runtime exports outside Nitro."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/standalone"
5
+ route: "/docs/reference/standalone"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Standalone build and runtime
13
+
14
+ > Production registry generation and Node runtime exports outside Nitro.
15
+
16
+ ## Build options
17
+
18
+ `@lupinum/nuxt-pdf/build` exports the `buildPdfRegistry` function and the
19
+ `BuildPdfRegistryOptions` type. The function resolves to `Promise<void>`.
20
+
21
+ | Option | Type | Behavior |
22
+ | --- | --- | --- |
23
+ | `rootDir` | `string` | Required application root containing `pdfs/`. Relative to the working directory. |
24
+ | `outDir` | `string` | Required dedicated output directory inside `rootDir` and outside `pdfs/`. Relative paths use `rootDir`. An existing directory must carry Nuxt PDF's ownership marker. |
25
+ | `fonts` | `readonly PdfFontDeclaration[]` | Defaults to no custom fonts. Sources resolve under `pdfs/fonts/`. |
26
+ | `limits` | `PdfLimitsOptions` | Existing render and image-admission limits. Omitted fields use the engine defaults. |
27
+ | `remote` | `RemoteAssetOptions` | Existing remote-image policy. Denied by default. |
28
+
29
+ The build rejects unknown options, missing templates, invalid resources, invalid
30
+ templates, and type errors. A successful build emits `index.mjs`, `index.d.mts`,
31
+ and the declarations needed for inferred props. It uses Vue's type checker at
32
+ build time; consumers use ordinary TypeScript. See
33
+ [standalone rendering](/raw/docs/guides/standalone-node.md) for the procedure and
34
+ output-directory replacement rules.
35
+
36
+ ## Server exports
37
+
38
+ `@lupinum/nuxt-pdf/server` is a Node-only production entry. It has no Nuxt virtual
39
+ imports or source compiler. Generated registries use `createPdfTemplate` to bind
40
+ a compiled component to its admitted resources, then `createPdfRegistry` to
41
+ assemble named template handles. Application code should use the generated
42
+ registry instead of constructing this resource configuration manually.
43
+
44
+ `PdfRegistryEntries` describes the readonly named handles. `PdfRegistry`
45
+ describes their runtime dispatch. `PdfComponentProps` extracts authored Vue
46
+ props. `PdfDefinition` describes compile-time metadata. `PdfTemplate` and
47
+ `PdfRenderResult` describe the public handle and completed bytes. The entry
48
+ also exports `usePdfPageNumbers`, `NuxtPdfError`, `PDF_ERROR_CODES`, and
49
+ `PdfErrorCode` with their existing behavior.
50
+
51
+ No export sends email, stores a document, authenticates a caller, or selects a
52
+ backend provider. The runtime preserves the current Node requirements and
53
+ [resource limits](/raw/docs/reference/errors-and-limits.md).
@@ -0,0 +1,176 @@
1
+ ---
2
+ title: "Styles"
3
+ description: "Accepted style keys, values, units, inheritance, and primitive support."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/styles"
5
+ route: "/docs/reference/styles"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Styles
13
+
14
+ > Accepted style keys, values, units, inheritance, and primitive support.
15
+
16
+ `PdfStyle` is Nuxt PDF's framework-owned authoring contract. It
17
+ contains the behavior this package tests and supports; it does not re-export
18
+ the wider upstream stylesheet types and it is not browser CSS.
19
+
20
+ ```ts
21
+ import type { PdfStyle } from '@lupinum/nuxt-pdf'
22
+
23
+ const pageStyle = {
24
+ color: '#18251d',
25
+ fontFamily: 'Invoice Sans',
26
+ fontSize: 10,
27
+ paddingHorizontal: 48,
28
+ paddingVertical: 40,
29
+ } satisfies PdfStyle
30
+ ```
31
+
32
+ Use `satisfies`, not a type assertion. It preserves the object's useful
33
+ inferred type while making `vue-tsc` reject unknown keys, invalid enums, and
34
+ unsupported units.
35
+
36
+ `PdfStyle` belongs to page-flow layout and text metrics. SVG paint and geometry
37
+ use direct props: `PdfG` and shapes do not accept `style`, `PdfSvg.style` places
38
+ the SVG root in page flow, and SVG `PdfText.style` controls text metrics while
39
+ its direct `fill` prop controls paint.
40
+
41
+ ## Values and units
42
+
43
+ | Type | Accepted values | Notes |
44
+ | --- | --- | --- |
45
+ | `PdfLength` | A number, or `pt`, `px`, `in`, `mm`, `cm`, `rem`, `vh`, or `vw` (for example `12`, `'12pt'`, `'4mm'`) | A unitless number is a PDF point. `px` uses the page DPI; the default is 72. |
46
+ | `PdfPercentage` | A numeric percentage string such as `'50%'` | Accepted only by properties whose type includes percentages. |
47
+ | Color | A color string such as `'#18251d'`, `'rgb(24, 37, 29)'`, or `'red'` | There are no CSS variables or global cascade. |
48
+ | Enum | One of the literals listed below | Browser-only values are rejected even when a similarly named CSS property exists. |
49
+
50
+ `rem` uses the engine's 18-point base. `vw` and `vh` resolve against the PDF
51
+ page container, not a browser window. Percentages resolve against the relevant
52
+ parent dimension.
53
+
54
+ `em`, `ch`, `ex`, `calc()`, `var()`, container queries, and arbitrary CSS
55
+ strings are not part of the contract.
56
+
57
+ ## Dimensions and positioning
58
+
59
+ These keys are valid on page-flow primitives (`PdfPage`, `PdfView`, `PdfText`,
60
+ `PdfImage`, `PdfLink`, and `PdfNote`). `width` and `height` also size `PdfSvg`
61
+ inside page flow.
62
+
63
+ | Property | Accepted value | Inherited | Behavior |
64
+ | --- | --- | --- | --- |
65
+ | `width`, `height` | `PdfLength \| PdfPercentage` | No | Sets the resolved box size. |
66
+ | `minWidth`, `minHeight`, `maxWidth`, `maxHeight` | `PdfLength \| PdfPercentage` | No | Constrains the resolved box size. |
67
+ | `position` | `'relative' \| 'absolute'` | No | Use the component `fixed` prop to repeat content across pages. Browser `fixed` and `static` values are not accepted. |
68
+ | `top`, `right`, `bottom`, `left` | `PdfLength \| PdfPercentage` | No | Offsets a positioned node. |
69
+
70
+ ## Flex layout
71
+
72
+ Flex properties primarily belong on `PdfPage` and `PdfView`. Text, links,
73
+ images, notes, and SVGs can be flex items. The engine defaults to a `column`
74
+ direction.
75
+
76
+ | Property | Accepted value | Inherited | Behavior |
77
+ | --- | --- | --- | --- |
78
+ | `flexDirection` | `'row' \| 'row-reverse' \| 'column' \| 'column-reverse'` | No | Controls the main axis. The default is `column`. |
79
+ | `flex`, `flexGrow`, `flexShrink` | `number` | No | Controls available-space growth and shrinkage. |
80
+ | `flexBasis` | `PdfLength \| PdfPercentage \| 'auto'` | No | Sets the initial main-axis size. |
81
+ | `flexWrap` | `'nowrap' \| 'wrap' \| 'wrap-reverse'` | No | Controls flex-line wrapping. |
82
+ | `alignItems` | `'flex-start' \| 'flex-end' \| 'center' \| 'stretch' \| 'baseline'` | No | Aligns children on the cross axis. |
83
+ | `justifyContent` | `'flex-start' \| 'flex-end' \| 'center' \| 'space-between' \| 'space-around' \| 'space-evenly'` | No | Distributes children on the main axis. |
84
+ | `gap` | `PdfLength` | No | Adds space between flex children. |
85
+
86
+ There is no grid, browser block layout, floats, selectors, or CSS cascade.
87
+
88
+ ## Box model
89
+
90
+ The box model is valid on page-flow primitives. Percentages resolve through
91
+ the engine's PDF layout container, not a browser viewport.
92
+
93
+ | Property | Accepted value | Inherited | Behavior |
94
+ | --- | --- | --- | --- |
95
+ | `margin`, `marginHorizontal`, `marginVertical`, `marginTop`, `marginRight`, `marginBottom`, `marginLeft` | `PdfLength \| PdfPercentage \| 'auto'` | No | Sets space outside the box. |
96
+ | `padding`, `paddingHorizontal`, `paddingVertical`, `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft` | `PdfLength \| PdfPercentage` | No | Sets space inside the box. Padding does not accept `auto`. |
97
+
98
+ ## Borders and paint
99
+
100
+ | Property | Accepted value | Inherited | Behavior |
101
+ | --- | --- | --- | --- |
102
+ | `backgroundColor` | color string | Only through nested `PdfText` runs | Paints the page-flow box background. |
103
+ | `color` | color string | Yes | Sets text color on text-bearing primitives. |
104
+ | `opacity` | `number` | Yes | Sets node opacity. |
105
+ | `borderWidth`, `borderTopWidth`, `borderRightWidth`, `borderBottomWidth`, `borderLeftWidth` | `PdfLength` | No | Sets all or individual border widths. |
106
+ | `borderColor`, `borderTopColor`, `borderRightColor`, `borderBottomColor`, `borderLeftColor` | color string | No | Sets all or individual border colors. |
107
+ | `borderStyle`, `borderTopStyle`, `borderRightStyle`, `borderBottomStyle`, `borderLeftStyle` | `'solid' \| 'dashed' \| 'dotted'` | No | Sets all or individual border styles. |
108
+ | `borderRadius` | `PdfLength` | No | Page-flow primitives. Percent shorthand is not accepted because the engine requires a resolved number. |
109
+ | `borderTopLeftRadius`, `borderTopRightRadius`, `borderBottomRightRadius`, `borderBottomLeftRadius` | `PdfLength \| PdfPercentage` | No | Page-flow primitives; edge radii are resolved independently. |
110
+
111
+ Full CSS border strings such as `'1px solid red'` are not public authoring
112
+ syntax. Use the explicit width, style, and color keys so invalid values remain
113
+ type-checkable.
114
+
115
+ ## Text
116
+
117
+ Text keys apply to `PdfText` and `PdfLink`. They may be set on ancestor
118
+ `PdfPage`/`PdfView` nodes when the table marks them inherited.
119
+
120
+ | Property | Accepted value | Inherited | Behavior |
121
+ | --- | --- | --- | --- |
122
+ | `fontFamily` | `string` | Yes | Selects one registered family or a built-in engine family. |
123
+ | `fontSize` | `PdfLength` | Yes | Sets glyph size. |
124
+ | `fontStyle` | `'normal' \| 'italic' \| 'oblique'` | Yes | Selects the matching registered face. |
125
+ | `fontWeight` | `number` or `thin`, `hairline`, `ultralight`, `extralight`, `light`, `normal`, `medium`, `semibold`, `demibold`, `bold`, `ultrabold`, `extrabold`, `heavy`, `black` | Yes | Selects the matching registered numeric weight. |
126
+ | `letterSpacing` | `PdfLength` | Yes | Adds space between glyphs and affects wrapping. |
127
+ | `lineHeight` | unitless multiplier, `PdfLength`, or `PdfPercentage` | Yes for static text | Static text supports it. Dynamic `render` text uses font-default line spacing; see [Supported behavior](/raw/docs/examples/supported-behavior.md). |
128
+ | `maxLines` | `number` | No | Limits rendered lines. Use `textOverflow: 'ellipsis'` for an ellipsis. |
129
+ | `textAlign` | `'left' \| 'right' \| 'center' \| 'justify'` | Yes | Aligns text inside its line box. |
130
+ | `textDecoration` | `'none' \| 'underline' \| 'line-through'` or both decoration names separated by a space | Yes | Applies one or both decorations. |
131
+ | `textDecorationColor` | color string | No | `PdfText` and `PdfLink`. |
132
+ | `textDecorationStyle` | `'solid' \| 'dashed' \| 'dotted'` | No | `PdfText` and `PdfLink`. |
133
+ | `textOverflow` | `'ellipsis'` | No | Adds an ellipsis when `maxLines` truncates text. |
134
+ | `textTransform` | `'none' \| 'capitalize' \| 'lowercase' \| 'uppercase'` | Yes | Transforms rendered text casing. |
135
+
136
+ `wordSpacing` is not supported. The pinned text layout pipeline accepts
137
+ the key but does not apply it to glyph advances or wrapping. Nuxt PDF does not
138
+ type a no-op as supported authoring; use `letterSpacing` instead.
139
+
140
+ ## Images and transforms
141
+
142
+ | Property | Accepted value | Inherited | Behavior |
143
+ | --- | --- | --- | --- |
144
+ | `objectFit` | `'contain' \| 'cover'` | No | Fits a `PdfImage` inside its resolved box. |
145
+ | `transform` | One to three space-separated `rotate(...)`, `scale(...)`, or `translate(...)` operations | No | Applies to page-flow primitives. Rotate accepts degrees or radians. Translate uses unitless PDF points. SVG transforms remain direct SVG props. |
146
+
147
+ ## Style arrays
148
+
149
+ `PdfStyleValue` is either one `PdfStyle` or a recursively nested readonly array.
150
+ Arrays merge left-to-right. `false`, `null`, and `undefined` entries are ignored,
151
+ which makes Vue conditionals direct and type-safe:
152
+
153
+ ```ts
154
+ import type { PdfStyle, PdfStyleValue } from '@lupinum/nuxt-pdf'
155
+
156
+ const base = { fontSize: 10 } satisfies PdfStyle
157
+ const total = { fontWeight: 700 } satisfies PdfStyle
158
+
159
+ const style = [base, [isTotal && total], null] satisfies PdfStyleValue
160
+ ```
161
+
162
+ Numbers, strings, `true`, functions, and arbitrary objects are not valid array
163
+ entries.
164
+
165
+ ## Exported style types
166
+
167
+ Import these types from `@lupinum/nuxt-pdf`:
168
+
169
+ | Export | Describes |
170
+ | --- | --- |
171
+ | `PdfStyle` | One style object |
172
+ | `PdfStyleValue` | One style object or a nested style array |
173
+ | `PdfStyleEntry` | One accepted entry in a style array |
174
+ | `PdfLength` | A point number or supported length string |
175
+ | `PdfPercentage` | A numeric percentage string |
176
+ | `PdfLengthOrPercentage` | A supported length or percentage |
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: "Test utilities"
3
+ description: "The render, parse, assert, and snapshot exports of @lupinum/nuxt-pdf/test."
4
+ url: "https://nuxt-pdf.lupinum.com/docs/reference/test-utilities"
5
+ route: "/docs/reference/test-utilities"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Test utilities
13
+
14
+ > The render, parse, assert, and snapshot exports of @lupinum/nuxt-pdf/test.
15
+
16
+ `@lupinum/nuxt-pdf/test` exposes the helpers Nuxt PDF is tested with. They are
17
+ runner-agnostic (they throw `PdfAssertionError`, not Vitest/Jest matchers) and
18
+ load `pdfjs-dist` and `@napi-rs/canvas` lazily. See
19
+ [Testing your PDFs](/raw/docs/guides/testing.md) for a worked example.
20
+
21
+ ## renderPdfTemplate
22
+
23
+ ```ts
24
+ const { bytes, parsed } = await renderPdfTemplate(Component, props)
25
+ ```
26
+
27
+ Renders a Vue PDF component through the real registry pipeline, with or without
28
+ a `definePdf` block. Returns the raw `bytes`, immutable `result`, and a `parsed`
29
+ document. Options use the same input shapes as `pdf.remote` and `pdf.limits`, so
30
+ partial limits and URL-prefix allowlists can be shared with `nuxt.config`.
31
+
32
+ Use `renderPdfSfc` when the document depends on discovered local images or
33
+ custom font files. Prepared asset maps, embedded font descriptors, and template
34
+ attribution are registry internals and are not public test configuration.
35
+
36
+ ## renderPdfSfc
37
+
38
+ ```ts
39
+ const rendered = await renderPdfSfc('./pdfs/invoice.vue', props, {
40
+ fonts: [{ family: 'Invoice Sans', src: 'InvoiceSans-Regular.ttf' }],
41
+ })
42
+ ```
43
+
44
+ Compiles a real template and its nested child SFCs with the production
45
+ compiler, discovers and validates `pdfs/assets`, and bundles the declared faces
46
+ from `pdfs/fonts`. Its options mirror `pdf.fonts`, `pdf.remote`, and `pdf.limits`;
47
+ the application root and template identity are inferred from the `pdfs/*.vue`
48
+ path. `loadPdfSfc` exposes only the compile step when a test needs the Vue
49
+ component separately.
50
+
51
+ ## parsePdf
52
+
53
+ ```ts
54
+ const parsed = await parsePdf(input)
55
+ ```
56
+
57
+ Accepts PDF bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) **or** a
58
+ `PdfRenderResult` straight from the `#pdf` registry. Returns page text, page
59
+ count, flattened link annotations (named destination or external URL), and the
60
+ outline hierarchy with its initial expanded/collapsed state.
61
+
62
+ ## expectPdf
63
+
64
+ ```ts
65
+ expectPdf(parsed)
66
+ .toHavePageCount(2)
67
+ .toContainText('Invoice for Acme Corp', { page: 1 })
68
+ .toHaveLink({ destination: 'terms', page: 1 })
69
+ .toHaveLink({ url: 'https://example.com/' })
70
+ .toHaveOutline([{ title: 'Terms', expanded: true }])
71
+ ```
72
+
73
+ A chainable expectation that throws `PdfAssertionError` with an actionable
74
+ message on failure.
75
+
76
+ | Method | Checks |
77
+ | --- | --- |
78
+ | `toHavePageCount(n)` | Page count equals `n` |
79
+ | `toContainText(text, { page? })` | Extracted text contains `text` |
80
+ | `toHaveLink({ destination?, url?, page? })` | A matching link annotation exists |
81
+ | `toHaveOutline(shape)` | The outline matches the title hierarchy and any requested expansion states |
82
+
83
+ ## rasterizePdf and comparePdfSnapshot
84
+
85
+ ```ts
86
+ await comparePdfSnapshot(bytes, './test/baselines/invoice')
87
+ ```
88
+
89
+ `rasterizePdf` renders pages to PNGs; `comparePdfSnapshot` implements the
90
+ reviewed-baseline flow. It writes per-page baselines when
91
+ `UPDATE_PDF_BASELINES=1` (or `{ update: true }`) and otherwise compares within a
92
+ pixel threshold.
93
+
94
+ `parsePdf` also returns page dimensions and normalized `textRuns` with text,
95
+ position, size, font name, direction, width, and height. Use tolerant numeric
96
+ assertions for geometry because font and raster backends can vary slightly
97
+ between supported environments.
98
+
99
+ When a raster comparison fails, the helper checks every page and writes
100
+ `expected`, `actual`, and `diff` PNGs plus a machine-readable `metrics.json` to
101
+ `reports/pdf-snapshots/<baseline-name>`. Pass `{ artifactDir }` to choose a
102
+ different CI artifact directory.
103
+
104
+ ## All exports
105
+
106
+ | Export | Kind | Purpose |
107
+ | --- | --- | --- |
108
+ | `renderPdfTemplate` | function | Render a component through the real pipeline |
109
+ | `renderPdfSfc` | function | Compile and render a real nested `pdfs/*.vue` graph |
110
+ | `loadPdfSfc` | function | Compile a PDF SFC graph into a Vue component |
111
+ | `parsePdf` | function | Parse bytes or a render result into a `ParsedPdf` |
112
+ | `rasterizePdf` | function | Render pages to PNG images |
113
+ | `comparePdfSnapshot` | function | Reviewed-baseline raster comparison |
114
+ | `toPdfBytes` | function | Normalize inputs to PDF bytes |
115
+ | `expectPdf` | function | Fluent, runner-agnostic assertions |
116
+ | `PdfAssertionError` | class | Thrown by assertions on failure |
117
+ | `PdfComponentProps`, `RenderedPdfTemplate`, `RenderPdfTemplateOptions`, `RenderPdfSfcOptions` | types | Typed component props and render results/options |
118
+ | `ParsedPdf`, `ParsedPdfPage`, `ParsedPdfTextRun`, `ParsedPdfLink`, `PdfOutlineItem`, `PdfInput`, `PdfPageImage` | types | Parsed PDF structures |
119
+ | `PdfExpectation`, `LinkQuery`, `OutlineShape`, `ToContainTextOptions` | types | Assertion inputs and chain type |
120
+ | `RasterizePdfOptions`, `ComparePdfSnapshotOptions`, `PdfSnapshotResult` | types | Raster and snapshot options/results |
121
+
122
+ `pdfjs-dist` and `@napi-rs/canvas` are optional peer dependencies; install them
123
+ in the project under test. They are absent from the module's production
124
+ dependency graph.
package/dist/build.mjs CHANGED
@@ -5,9 +5,9 @@ import { join, relative, dirname, resolve, isAbsolute } from 'node:path';
5
5
  import { promisify } from 'node:util';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { build } from 'esbuild';
8
- import { d as discoverPdfTemplates, a as discoverPdfImageFiles, b as bundlePdfFonts, c as compilePdfSfc } from './shared/nuxt-pdf.DMC_Rdsz.mjs';
8
+ import { d as discoverPdfTemplates, a as discoverPdfImageFiles, b as bundlePdfFonts, c as compilePdfSfc } from './shared/nuxt-pdf.C-E8MM_K.mjs';
9
9
  import ts from 'typescript';
10
- import { p as preparePdfImageAssets, g as generatePdfRuntimeRegistry, a as generateAuthoringTypes, b as generatePdfRegistryTypes } from './shared/nuxt-pdf.D3hUPqOJ.mjs';
10
+ import { p as preparePdfImageAssets, g as generatePdfRuntimeRegistry, a as generateAuthoringTypes, b as generatePdfRegistryTypes } from './shared/nuxt-pdf.CjVyPF31.mjs';
11
11
  import { normalizeRemoteAssetPolicy } from '../dist/runtime/server/assets/remote.js';
12
12
  import { normalizePdfLimits, DEFAULT_PDF_RENDER_LIMITS } from '../dist/runtime/server/render-limits.js';
13
13
  import 'node:buffer';
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/nuxt-pdf",
3
- "version": "0.4.0-beta.3",
3
+ "version": "0.4.0-beta.5",
4
4
  "configKey": "pdf",
5
5
  "docs": "https://nuxt-pdf.lupinum.com",
6
6
  "compatibility": {
package/dist/module.mjs CHANGED
@@ -2,9 +2,9 @@ import { stat } from 'node:fs/promises';
2
2
  import { join, isAbsolute, resolve, relative } from 'node:path';
3
3
  import { defineNuxtModule, createResolver, getLayerDirectories, createIsIgnored, addServerTemplate, addTemplate, addImports, addTypeTemplate, logger, addServerHandler, addComponent } from '@nuxt/kit';
4
4
  import { joinURL } from 'ufo';
5
- import { d as discoverPdfTemplates, e as discoverPdfComponentFiles, a as discoverPdfImageFiles, b as bundlePdfFonts, f as createPdfSfcPlugin, g as classifyPdfWatchEvent } from './shared/nuxt-pdf.DMC_Rdsz.mjs';
6
- import { p as preparePdfImageAssets, g as generatePdfRuntimeRegistry, b as generatePdfRegistryTypes, a as generateAuthoringTypes, c as generatePdfPreviewConfig } from './shared/nuxt-pdf.D3hUPqOJ.mjs';
7
- import { PDF_STUB_NAMES } from '../dist/runtime/components/stubs.js';
5
+ import { d as discoverPdfTemplates, e as discoverPdfComponentFiles, a as discoverPdfImageFiles, b as bundlePdfFonts, f as createPdfSfcPlugin, g as classifyPdfWatchEvent } from './shared/nuxt-pdf.C-E8MM_K.mjs';
6
+ import { p as preparePdfImageAssets, g as generatePdfRuntimeRegistry, b as generatePdfRegistryTypes, a as generateAuthoringTypes, c as generatePdfPreviewConfig } from './shared/nuxt-pdf.CjVyPF31.mjs';
7
+ import * as pdfStubs from '../dist/runtime/components/stubs.js';
8
8
  import { normalizeRemoteAssetPolicy } from '../dist/runtime/server/assets/remote.js';
9
9
  import { normalizePdfLimits, DEFAULT_PDF_RENDER_LIMITS } from '../dist/runtime/server/render-limits.js';
10
10
  import 'node:buffer';
@@ -16,7 +16,7 @@ import 'unimport';
16
16
  import '../dist/runtime/shared/template.js';
17
17
  import '../dist/runtime/server/assets/resolve-asset.js';
18
18
 
19
- const version = "0.4.0-beta.3";
19
+ const version = "0.4.0-beta.5";
20
20
 
21
21
  const existingDirectories = async (directories) => {
22
22
  const result = [];
@@ -143,6 +143,14 @@ const module$1 = defineNuxtModule({
143
143
  nuxt.hook("vite:serverCreated", (viteServer, environment) => {
144
144
  if (environment.isClient) clientViteServer = viteServer;
145
145
  });
146
+ const refreshPreview = () => clientViteServer?.ws.send({
147
+ type: "custom",
148
+ event: "nuxt-pdf:update",
149
+ data: {}
150
+ });
151
+ nuxt.hook("nitro:init", (nitro) => {
152
+ nitro.hooks.hook("dev:reload", refreshPreview);
153
+ });
146
154
  nuxt.hook("builder:watch", async (event, path) => {
147
155
  const absolutePath = isAbsolute(path) ? path : resolve(nuxt.options.srcDir, path);
148
156
  const action = classifyPdfWatchEvent(
@@ -158,12 +166,7 @@ const module$1 = defineNuxtModule({
158
166
  await nuxt.callHook("restart");
159
167
  return;
160
168
  }
161
- if (action !== "refresh") return;
162
- clientViteServer?.ws.send({
163
- type: "custom",
164
- event: "nuxt-pdf:update",
165
- data: {}
166
- });
169
+ if (action === "refresh" && /\.(?:png|jpe?g)$/i.test(absolutePath)) refreshPreview();
167
170
  });
168
171
  addServerTemplate({
169
172
  filename: "#pdf-preview-config",
@@ -182,7 +185,7 @@ const module$1 = defineNuxtModule({
182
185
  view: { type: "iframe", src: joinURL(nuxt.options.app.baseURL || "/", "_pdf") }
183
186
  });
184
187
  });
185
- for (const name of PDF_STUB_NAMES) {
188
+ for (const name of Object.keys(pdfStubs)) {
186
189
  addComponent({
187
190
  name,
188
191
  export: name,