@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,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Add images and fonts"
|
|
3
|
+
description: "Bundle local PNG, JPEG, TTF, OTF, and WOFF2 resources with a PDF template."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/images-and-fonts"
|
|
5
|
+
route: "/docs/guides/images-and-fonts"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Add images and fonts
|
|
13
|
+
|
|
14
|
+
> Bundle local PNG, JPEG, TTF, OTF, and WOFF2 resources with a PDF template.
|
|
15
|
+
|
|
16
|
+
Keep local images under `pdfs/assets` and fonts under `pdfs/fonts`. Nuxt PDF
|
|
17
|
+
validates and embeds them in the Nitro server output.
|
|
18
|
+
|
|
19
|
+
In development, each render reads and validates local image files again.
|
|
20
|
+
Image edits take effect without restarting the server.
|
|
21
|
+
|
|
22
|
+
## Add a local image
|
|
23
|
+
|
|
24
|
+
Put a PNG or JPEG in `pdfs/assets`. Set `PdfImage.src` to the path relative to
|
|
25
|
+
that directory:
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<PdfImage
|
|
29
|
+
src="brand/logo.png"
|
|
30
|
+
:style="{ height: 40, objectFit: 'contain', width: 120 }"
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Public rendering accepts local paths, admitted PNG or JPEG byte sources, and
|
|
35
|
+
allowlisted HTTPS URLs. It blocks `data:` URL strings, absolute filesystem
|
|
36
|
+
paths, parent traversal, symlink escapes, and SVG files used as image sources.
|
|
37
|
+
|
|
38
|
+
Use [`PdfSvg`](/raw/docs/guides/svg-graphics.md) for vector graphics authored in the
|
|
39
|
+
document tree.
|
|
40
|
+
|
|
41
|
+
## Register a local font
|
|
42
|
+
|
|
43
|
+
Put a TTF, OTF, or WOFF2 file in `pdfs/fonts`, then register one entry for each weight
|
|
44
|
+
and style used by the document:
|
|
45
|
+
|
|
46
|
+
```ts [nuxt.config.ts]
|
|
47
|
+
export default defineNuxtConfig({
|
|
48
|
+
modules: ['@lupinum/nuxt-pdf'],
|
|
49
|
+
pdf: {
|
|
50
|
+
fonts: [
|
|
51
|
+
{
|
|
52
|
+
family: 'Invoice Sans',
|
|
53
|
+
src: 'InvoiceSans-Regular.ttf',
|
|
54
|
+
fontStyle: 'normal',
|
|
55
|
+
fontWeight: 400,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
family: 'Invoice Sans',
|
|
59
|
+
src: 'InvoiceSans-Bold.ttf',
|
|
60
|
+
fontStyle: 'normal',
|
|
61
|
+
fontWeight: 700,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Set the family on a parent so text descendants inherit it:
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<PdfPage :style="{ fontFamily: 'Invoice Sans' }">
|
|
72
|
+
<PdfText>Invoice</PdfText>
|
|
73
|
+
</PdfPage>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If a requested family, weight, or style was not registered, rendering fails
|
|
77
|
+
with `PDF_LAYOUT_ERROR`. If the configured file is invalid, module setup throws
|
|
78
|
+
a `TypeError` before a template render starts.
|
|
79
|
+
|
|
80
|
+
## Check language coverage
|
|
81
|
+
|
|
82
|
+
A font must contain the glyphs used by the document. Latin Extended, Greek,
|
|
83
|
+
Cyrillic, and custom hyphenation are covered by the tested corpus with supplied
|
|
84
|
+
fonts. CJK, combining marks, Arabic, bidirectional text, and variable fonts need
|
|
85
|
+
application-specific semantic and raster tests. Emoji and fallback font chains
|
|
86
|
+
are not supported; use an admitted image for an emoji graphic.
|
|
87
|
+
|
|
88
|
+
## Resource validation
|
|
89
|
+
|
|
90
|
+
The module checks four things:
|
|
91
|
+
|
|
92
|
+
- **Structure:** images must decode as PNG or JPEG; fonts need a valid TTF, OTF,
|
|
93
|
+
or WOFF2 structure and matching extension.
|
|
94
|
+
- **Size:** a local image is limited to 10 MB and a font to 5 MB.
|
|
95
|
+
- **Location:** the resolved path must remain inside its declared root.
|
|
96
|
+
- **Embedding:** validated bytes travel in the Nitro server output, so
|
|
97
|
+
production rendering does not read these files from disk.
|
|
98
|
+
|
|
99
|
+
Development reloads local image bytes for the next preview render. The
|
|
100
|
+
[module-options reference](/raw/docs/reference/module-options.md) lists the exact font
|
|
101
|
+
fields. Use the [remote-image guide](/raw/docs/guides/remote-images.md) for HTTPS
|
|
102
|
+
sources.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Production recipes"
|
|
3
|
+
description: "Copyable Vue patterns for invoices, statements, reports, repeating matter, and payment QR codes."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/recipes"
|
|
5
|
+
route: "/docs/guides/recipes"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Production recipes
|
|
13
|
+
|
|
14
|
+
> Copyable Vue patterns for invoices, statements, reports, repeating matter, and payment QR codes.
|
|
15
|
+
|
|
16
|
+
These are application recipes, not extra framework primitives. They compose the
|
|
17
|
+
same small `PdfView`/`PdfText` surface used by the tested playground documents.
|
|
18
|
+
|
|
19
|
+
Use this page as a pattern index:
|
|
20
|
+
|
|
21
|
+
- [Repeating header, footer, and page numbers](#repeating-header-footer-and-page-numbers)
|
|
22
|
+
- [Address and key/value blocks](#address-or-party-block)
|
|
23
|
+
- [Invoice lines, totals, and shared table columns](#invoice-lines-and-totals)
|
|
24
|
+
- [Dot leaders](#dot-leaders-for-contents-and-price-lists)
|
|
25
|
+
- [Payment details and QR graphics](#payment-details-and-qr)
|
|
26
|
+
- [Headings and continuation headers](#keep-a-heading-with-what-follows)
|
|
27
|
+
- [Reports with a table of contents](#report-sections-and-a-table-of-contents)
|
|
28
|
+
|
|
29
|
+
Read [Build reusable document components](/raw/docs/guides/reusable-components.md)
|
|
30
|
+
before turning a recipe into a shared component. Exact style values remain in
|
|
31
|
+
the [style reference](/raw/docs/reference/styles.md).
|
|
32
|
+
|
|
33
|
+
## Repeating header, footer, and page numbers
|
|
34
|
+
|
|
35
|
+
A `fixed` node repeats when one authored page wraps into multiple output pages.
|
|
36
|
+
Keep the page padding large enough that flowing content never overlaps it.
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<PdfPage :style="{ paddingBottom: 52, paddingHorizontal: 40, paddingTop: 64 }">
|
|
40
|
+
<PdfView fixed :style="{ left: 40, position: 'absolute', right: 40, top: 24 }">
|
|
41
|
+
<PdfText>Quarterly statement</PdfText>
|
|
42
|
+
</PdfView>
|
|
43
|
+
<PdfText
|
|
44
|
+
fixed
|
|
45
|
+
:render="({ pageNumber, totalPages }) => `Page ${pageNumber} / ${totalPages}`"
|
|
46
|
+
:style="{ bottom: 24, position: 'absolute', right: 40 }"
|
|
47
|
+
/>
|
|
48
|
+
<slot />
|
|
49
|
+
</PdfPage>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Address or party block
|
|
53
|
+
|
|
54
|
+
Use a typed child component so the invoice template remains readable.
|
|
55
|
+
|
|
56
|
+
```vue [pdfs/components/PartyBlock.vue]
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
defineProps<{ label: string, lines: string[] }>()
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<PdfView :style="{ width: 220 }">
|
|
63
|
+
<PdfText :style="{ color: '#68736B', fontSize: 8, marginBottom: 6 }">
|
|
64
|
+
{{ label }}
|
|
65
|
+
</PdfText>
|
|
66
|
+
<PdfText v-for="line in lines" :key="line" :style="{ fontSize: 10 }">
|
|
67
|
+
{{ line }}
|
|
68
|
+
</PdfText>
|
|
69
|
+
</PdfView>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Key/value details
|
|
74
|
+
|
|
75
|
+
Keep widths explicit; PDF rows should not depend on browser table layout.
|
|
76
|
+
|
|
77
|
+
```vue
|
|
78
|
+
<PdfView
|
|
79
|
+
v-for="entry in details"
|
|
80
|
+
:key="entry.label"
|
|
81
|
+
:style="{ flexDirection: 'row', marginBottom: 6 }"
|
|
82
|
+
>
|
|
83
|
+
<PdfText :style="{ color: '#68736B', width: 110 }">{{ entry.label }}</PdfText>
|
|
84
|
+
<PdfText :style="{ flex: 1 }">{{ entry.value }}</PdfText>
|
|
85
|
+
</PdfView>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Invoice lines and totals
|
|
89
|
+
|
|
90
|
+
Make each line indivisible with `wrap="false"`; keyed rows then move as a unit
|
|
91
|
+
instead of losing a description or amount at a page break.
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<PdfView
|
|
95
|
+
v-for="line in invoice.lines"
|
|
96
|
+
:key="line.id"
|
|
97
|
+
:wrap="false"
|
|
98
|
+
:style="{ borderBottomColor: '#DDE3DE', borderBottomWidth: 1, flexDirection: 'row', paddingVertical: 10 }"
|
|
99
|
+
>
|
|
100
|
+
<PdfText :style="{ flex: 1 }">{{ line.description }}</PdfText>
|
|
101
|
+
<PdfText :style="{ textAlign: 'right', width: 90 }">{{ money(line.total) }}</PdfText>
|
|
102
|
+
</PdfView>
|
|
103
|
+
<PdfView :wrap="false" :style="{ marginLeft: 'auto', marginTop: 16, width: 220 }">
|
|
104
|
+
<PdfText :style="{ fontWeight: 700, textAlign: 'right' }">Total {{ money(total) }}</PdfText>
|
|
105
|
+
</PdfView>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Tables with a shared column spec
|
|
109
|
+
|
|
110
|
+
A table is a column of rows. Give every row the same `flexDirection: 'row'`
|
|
111
|
+
and fixed cell widths, and put those widths in one module. The header and the
|
|
112
|
+
body then cannot drift apart when a column changes.
|
|
113
|
+
|
|
114
|
+
```ts [pdfs/components/columns.ts]
|
|
115
|
+
export const columns = {
|
|
116
|
+
amount: { align: 'right', width: 86 },
|
|
117
|
+
description: { width: 246 },
|
|
118
|
+
quantity: { align: 'right', width: 52 },
|
|
119
|
+
} as const
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<PdfView :style="{ flexDirection: 'row', paddingBottom: 8 }" :wrap="false">
|
|
124
|
+
<PdfText :style="{ fontSize: 7, width: columns.description.width }">Description</PdfText>
|
|
125
|
+
<PdfText :style="{ fontSize: 7, textAlign: 'right', width: columns.quantity.width }">Qty</PdfText>
|
|
126
|
+
<PdfText :style="{ fontSize: 7, textAlign: 'right', width: columns.amount.width }">Amount</PdfText>
|
|
127
|
+
</PdfView>
|
|
128
|
+
<PdfView
|
|
129
|
+
v-for="line in invoice.lines"
|
|
130
|
+
:key="line.id"
|
|
131
|
+
:wrap="false"
|
|
132
|
+
:style="{ borderBottomColor: '#DDE3DE', borderBottomWidth: 1, flexDirection: 'row', paddingVertical: 10 }"
|
|
133
|
+
>
|
|
134
|
+
<PdfText :style="{ flex: 1 }">{{ line.description }}</PdfText>
|
|
135
|
+
<PdfText :style="{ textAlign: 'right', width: columns.amount.width }">{{ money(line.total) }}</PdfText>
|
|
136
|
+
</PdfView>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Keep body cells inside a `wrap="false"` row so a long line moves as a unit.
|
|
140
|
+
|
|
141
|
+
## Dot leaders for contents and price lists
|
|
142
|
+
|
|
143
|
+
A dotted rule that stretches between a label and a number is one view with a
|
|
144
|
+
dotted bottom border. Wrap each entry in a row with `alignItems: 'flex-end'`
|
|
145
|
+
and let the leader take the remaining space:
|
|
146
|
+
|
|
147
|
+
```vue [pdfs/components/common/DotLeader.vue]
|
|
148
|
+
<script setup lang="ts">
|
|
149
|
+
defineProps<{
|
|
150
|
+
baseline?: number
|
|
151
|
+
color: string
|
|
152
|
+
gap?: number
|
|
153
|
+
width?: number
|
|
154
|
+
}>()
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<template>
|
|
158
|
+
<PdfView
|
|
159
|
+
:style="{
|
|
160
|
+
borderBottomColor: color,
|
|
161
|
+
borderBottomStyle: 'dotted',
|
|
162
|
+
borderBottomWidth: width ?? 1,
|
|
163
|
+
flex: 1,
|
|
164
|
+
marginBottom: baseline ?? 3,
|
|
165
|
+
marginHorizontal: gap ?? 7,
|
|
166
|
+
}"
|
|
167
|
+
/>
|
|
168
|
+
</template>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```vue
|
|
172
|
+
<PdfView :style="{ alignItems: 'flex-end', flexDirection: 'row' }">
|
|
173
|
+
<PdfText>{{ section.title }}</PdfText>
|
|
174
|
+
<DotLeader color="#CDD5CF" />
|
|
175
|
+
<PdfText>{{ pageNumbers[section.id] ?? '' }}</PdfText>
|
|
176
|
+
</PdfView>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The `baseline` offset sits the dots on the text baseline; start at `2` to `4`
|
|
180
|
+
and adjust against your font size.
|
|
181
|
+
|
|
182
|
+
## Payment details and QR
|
|
183
|
+
|
|
184
|
+
Generate the payment payload in setup code, turn consecutive dark QR modules
|
|
185
|
+
into one SVG path, and keep the payment panel together with `wrap="false"`.
|
|
186
|
+
|
|
187
|
+
```vue
|
|
188
|
+
<PdfView :wrap="false" :style="{ flexDirection: 'row', marginTop: 20 }">
|
|
189
|
+
<PdfView :style="{ flex: 1 }">
|
|
190
|
+
<PdfText>IBAN {{ payment.iban }}</PdfText>
|
|
191
|
+
<PdfText>BIC {{ payment.bic }}</PdfText>
|
|
192
|
+
<PdfText>Reference {{ invoice.number }}</PdfText>
|
|
193
|
+
</PdfView>
|
|
194
|
+
<PdfSvg :viewBox="`0 0 ${qrSize} ${qrSize}`" :style="{ height: 72, width: 72 }">
|
|
195
|
+
<PdfPath :d="qrPath" fill="#000000" />
|
|
196
|
+
</PdfSvg>
|
|
197
|
+
</PdfView>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Keep a heading with what follows
|
|
201
|
+
|
|
202
|
+
`minPresenceAhead` asks pagination to move the heading when too little usable
|
|
203
|
+
space remains. Use `wrap="false"` only when the whole section is genuinely
|
|
204
|
+
small enough to fit on a page.
|
|
205
|
+
|
|
206
|
+
```vue
|
|
207
|
+
<PdfView :style="{ marginTop: 24 }">
|
|
208
|
+
<PdfText :min-presence-ahead="54" :style="{ fontSize: 15, marginBottom: 10 }">
|
|
209
|
+
Payment terms
|
|
210
|
+
</PdfText>
|
|
211
|
+
<PdfText>{{ terms }}</PdfText>
|
|
212
|
+
</PdfView>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Long rows with continuation headers
|
|
216
|
+
|
|
217
|
+
Put the header and rows in one wrapping page. The fixed header repeats on every
|
|
218
|
+
continuation page; page padding reserves its space.
|
|
219
|
+
|
|
220
|
+
```vue
|
|
221
|
+
<PdfPage :style="{ paddingHorizontal: 40, paddingTop: 72 }">
|
|
222
|
+
<PdfView fixed :style="{ flexDirection: 'row', left: 40, position: 'absolute', right: 40, top: 36 }">
|
|
223
|
+
<PdfText :style="{ flex: 1 }">Description</PdfText>
|
|
224
|
+
<PdfText :style="{ textAlign: 'right', width: 90 }">Amount</PdfText>
|
|
225
|
+
</PdfView>
|
|
226
|
+
<InvoiceLine v-for="line in lines" :key="line.id" :line="line" />
|
|
227
|
+
</PdfPage>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Report sections and a table of contents
|
|
231
|
+
|
|
232
|
+
Read the page map only where the document needs it. The first pass returns no
|
|
233
|
+
number, so keep an empty fallback; Nuxt PDF rerenders until the map stabilizes.
|
|
234
|
+
|
|
235
|
+
```vue
|
|
236
|
+
<script setup lang="ts">
|
|
237
|
+
const pageNumbers = usePdfPageNumbers()
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
<template>
|
|
241
|
+
<PdfPage>
|
|
242
|
+
<PdfLink v-for="section in sections" :key="section.id" :href="`#${section.id}`">
|
|
243
|
+
{{ section.title }} ..... {{ pageNumbers[section.id] ?? '' }}
|
|
244
|
+
</PdfLink>
|
|
245
|
+
</PdfPage>
|
|
246
|
+
<PdfPage v-for="section in sections" :key="section.id">
|
|
247
|
+
<PdfView :id="section.id" :bookmark="section.title">
|
|
248
|
+
<PdfText :style="{ fontSize: 22 }">{{ section.title }}</PdfText>
|
|
249
|
+
<PdfText>{{ section.body }}</PdfText>
|
|
250
|
+
</PdfView>
|
|
251
|
+
</PdfPage>
|
|
252
|
+
</template>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The [links and table-of-contents guide](/raw/docs/guides/contents-links-bookmarks.md)
|
|
256
|
+
explains convergence and invalid destinations. Use the
|
|
257
|
+
[testing guide](/raw/docs/guides/testing.md) to protect document text, links, page
|
|
258
|
+
counts, and reviewed raster output.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Configure remote images"
|
|
3
|
+
description: "Admit specific HTTPS image paths and understand the request boundary."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/remote-images"
|
|
5
|
+
route: "/docs/guides/remote-images"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Configure remote images
|
|
13
|
+
|
|
14
|
+
> Admit specific HTTPS image paths and understand the request boundary.
|
|
15
|
+
|
|
16
|
+
Remote image fetching is off by default. Enable it only for HTTPS locations
|
|
17
|
+
that the application operator controls or trusts.
|
|
18
|
+
|
|
19
|
+
## Add an allowlist
|
|
20
|
+
|
|
21
|
+
Configure exact URL prefixes with a trailing slash:
|
|
22
|
+
|
|
23
|
+
```ts [nuxt.config.ts]
|
|
24
|
+
export default defineNuxtConfig({
|
|
25
|
+
modules: ['@lupinum/nuxt-pdf'],
|
|
26
|
+
pdf: {
|
|
27
|
+
remote: {
|
|
28
|
+
allow: [
|
|
29
|
+
'https://cdn.example.com/brand/',
|
|
30
|
+
'https://images.example.com/logos/',
|
|
31
|
+
],
|
|
32
|
+
timeoutMs: 10_000,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use an admitted URL in the template:
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<PdfImage
|
|
42
|
+
src="https://cdn.example.com/brand/logo.png"
|
|
43
|
+
:style="{ height: 40, width: 120 }"
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The module follows at most three redirects and checks the allowlist at each
|
|
48
|
+
hop. An allowed host cannot redirect to a disallowed location.
|
|
49
|
+
|
|
50
|
+
## Understand the boundary
|
|
51
|
+
|
|
52
|
+
- Allowlist entries use `https://host/path/`. Wildcards, credentials, query
|
|
53
|
+
strings, fragments, and missing trailing slashes are rejected.
|
|
54
|
+
- Requested image URLs can contain a query, but errors do not include it.
|
|
55
|
+
- Requests use `GET` without application headers, cookies, or credentials.
|
|
56
|
+
- PNG and JPEG signatures and dimensions are checked before layout. A response
|
|
57
|
+
`Content-Type` cannot admit other bytes.
|
|
58
|
+
- Per-image bytes, total bytes, decoded pixels, request count, concurrency, and
|
|
59
|
+
the full render deadline use the shared `pdf.limits` budget.
|
|
60
|
+
- Duplicate URLs are fetched once per render. No cross-render image cache is
|
|
61
|
+
promised.
|
|
62
|
+
|
|
63
|
+
<warning icon="lucide:shield-alert">
|
|
64
|
+
The allowlist controls hosts and paths. It does not provide private-IP or
|
|
65
|
+
DNS-rebinding protection. Do not allow user-controlled hosts. Authenticated
|
|
66
|
+
requests, custom headers, proxies, and remote fonts are not supported.
|
|
67
|
+
</warning>
|
|
68
|
+
|
|
69
|
+
`PDF_ASSET_BLOCKED` means the source form, allowlist, redirect, or timeout
|
|
70
|
+
policy rejected the request. `PDF_ASSET_INVALID` means admitted bytes were not a
|
|
71
|
+
valid PNG or JPEG. Size and request-budget failures use `PDF_LIMIT_EXCEEDED`.
|
|
72
|
+
See [Errors and limits](/raw/docs/reference/errors-and-limits.md) for the exact codes.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Build reusable document components"
|
|
3
|
+
description: "Organize repeated PDF sections without creating a second component system."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/reusable-components"
|
|
5
|
+
route: "/docs/guides/reusable-components"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Build reusable document components
|
|
13
|
+
|
|
14
|
+
> Organize repeated PDF sections without creating a second component system.
|
|
15
|
+
|
|
16
|
+
PDF components are ordinary local Vue components whose rendered roots are Nuxt
|
|
17
|
+
PDF primitives. Extract a component when it owns a meaningful document section
|
|
18
|
+
or repeats in more than one place.
|
|
19
|
+
|
|
20
|
+
## Keep ownership visible
|
|
21
|
+
|
|
22
|
+
Put document-specific components next to their document family:
|
|
23
|
+
|
|
24
|
+
```bash [Application files]
|
|
25
|
+
pdfs/
|
|
26
|
+
├── invoice.vue
|
|
27
|
+
└── components/
|
|
28
|
+
└── invoice/
|
|
29
|
+
├── InvoiceAddress.vue
|
|
30
|
+
├── InvoiceLine.vue
|
|
31
|
+
└── columns.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Move a component to `pdfs/components/` only when several templates use it. This
|
|
35
|
+
keeps invoice-specific decisions out of a generic component API.
|
|
36
|
+
|
|
37
|
+
## Pass typed document data
|
|
38
|
+
|
|
39
|
+
```vue [pdfs/components/invoice/InvoiceAddress.vue]
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
defineProps<{
|
|
42
|
+
label: string
|
|
43
|
+
lines: string[]
|
|
44
|
+
}>()
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<PdfView>
|
|
49
|
+
<PdfText :style="{ fontSize: 8, marginBottom: 4 }">
|
|
50
|
+
{{ label }}
|
|
51
|
+
</PdfText>
|
|
52
|
+
<PdfText v-for="line in lines" :key="line">
|
|
53
|
+
{{ line }}
|
|
54
|
+
</PdfText>
|
|
55
|
+
</PdfView>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Import it directly in the template. Use keyed `v-for`, slots, and `v-if` as you
|
|
60
|
+
would in another Vue component. Do not pass DOM attributes such as `class` or
|
|
61
|
+
`aria-*`; the PDF tree has no DOM.
|
|
62
|
+
|
|
63
|
+
## Share table geometry
|
|
64
|
+
|
|
65
|
+
Put widths used by both a header and row in one TypeScript module:
|
|
66
|
+
|
|
67
|
+
```ts [pdfs/components/invoice/columns.ts]
|
|
68
|
+
export const invoiceColumns = {
|
|
69
|
+
description: '58%',
|
|
70
|
+
quantity: '14%',
|
|
71
|
+
price: '14%',
|
|
72
|
+
total: '14%',
|
|
73
|
+
} as const
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A table is a column of rows. Give the header and every row the same
|
|
77
|
+
`flexDirection: 'row'` and column widths. The header and body then cannot drift
|
|
78
|
+
apart.
|
|
79
|
+
|
|
80
|
+
The [production recipes](/raw/docs/guides/recipes.md) show address blocks, shared
|
|
81
|
+
columns, totals, continuation headers, and table-of-contents patterns.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Render outside Nitro"
|
|
3
|
+
description: "Compile PDF templates once and render the generated registry in a Node worker."
|
|
4
|
+
url: "https://nuxt-pdf.lupinum.com/docs/guides/standalone-node"
|
|
5
|
+
route: "/docs/guides/standalone-node"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Render outside Nitro
|
|
13
|
+
|
|
14
|
+
> Compile PDF templates once and render the generated registry in a Node worker.
|
|
15
|
+
|
|
16
|
+
Use the standalone build when a Node worker owns document rendering outside
|
|
17
|
+
Nitro. Keep the Nuxt module for authoring and preview. Both paths use the same
|
|
18
|
+
compiler, resource admission, registry, and PDF engine.
|
|
19
|
+
|
|
20
|
+
## Build the registry
|
|
21
|
+
|
|
22
|
+
Keep trusted templates in `pdfs/` at the application root. Import application
|
|
23
|
+
helpers explicitly with relative paths. The standalone build does not read
|
|
24
|
+
Nuxt configuration, aliases, auto-imports, or layer configuration. PDF primitives,
|
|
25
|
+
`definePdf`, and `usePdfPageNumbers` remain available in templates.
|
|
26
|
+
Package imports must provide compiled JavaScript. Do not import source-only
|
|
27
|
+
packages or add runtime filesystem reads to templates or their helpers.
|
|
28
|
+
|
|
29
|
+
```ts [scripts/build-pdfs.mjs]
|
|
30
|
+
import { buildPdfRegistry } from '@lupinum/nuxt-pdf/build'
|
|
31
|
+
|
|
32
|
+
await buildPdfRegistry({
|
|
33
|
+
rootDir: process.cwd(),
|
|
34
|
+
outDir: './generated/pdfs',
|
|
35
|
+
fonts: [{ family: 'Invoice Sans', src: 'Roboto-Regular.ttf' }],
|
|
36
|
+
limits: { maxPages: 100, maxOutputBytes: 8_000_000 },
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Put the configured font in `pdfs/fonts/`. Put local images in `pdfs/assets/`.
|
|
41
|
+
Remove the `fonts` option if the templates use only built-in fonts.
|
|
42
|
+
|
|
43
|
+
Run this script before the backend build:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node scripts/build-pdfs.mjs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The output directory contains `index.mjs`, `index.d.mts`, a `types/` declaration
|
|
50
|
+
tree, and an ownership marker. Deploy the generated runtime and its package
|
|
51
|
+
dependencies. Keep the declarations for backend type checking. No Vue source,
|
|
52
|
+
test loader, or build compiler is needed to execute this generated registry.
|
|
53
|
+
The build tools remain package dependencies but are not imported by the runtime.
|
|
54
|
+
|
|
55
|
+
Use a dedicated output directory inside the application. Never put hand-written
|
|
56
|
+
files in it. Successful rebuilds replace its generated contents. Compilation
|
|
57
|
+
failures preserve the previous output. An existing nonempty directory without
|
|
58
|
+
the ownership marker is rejected. Choose another directory after this error.
|
|
59
|
+
Do not run concurrent builds into one output directory.
|
|
60
|
+
|
|
61
|
+
## Render validated data
|
|
62
|
+
|
|
63
|
+
The generated `pdf`, `renderPdf`, `getPdfTemplate`, and `pdfTemplateKeys` exports
|
|
64
|
+
have the same behavior as the [Nitro registry](/raw/docs/reference/registry.md).
|
|
65
|
+
Import the `.mjs` path. Ordinary TypeScript resolves its adjacent declarations.
|
|
66
|
+
|
|
67
|
+
```ts [workers/render-invoice.ts]
|
|
68
|
+
import { pdf } from '../generated/pdfs/index.mjs'
|
|
69
|
+
|
|
70
|
+
export async function renderInvoice() {
|
|
71
|
+
const result = await pdf.invoice.render({
|
|
72
|
+
customer: 'Example customer',
|
|
73
|
+
number: '2026-001',
|
|
74
|
+
lines: [],
|
|
75
|
+
})
|
|
76
|
+
return result.toUint8Array()
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Match the props to your own invoice template. Validate request data and check
|
|
81
|
+
authorization before rendering. Props are statically typed, not a runtime
|
|
82
|
+
validation schema. Never accept template source, filesystem paths, build
|
|
83
|
+
options, or unrestricted image URLs from a caller.
|
|
84
|
+
|
|
85
|
+
Local assets and configured fonts are validated and embedded during the build.
|
|
86
|
+
Remote images remain denied unless the build supplies an explicit `remote`
|
|
87
|
+
policy. Existing resource limits and error codes apply to each render. Fix the
|
|
88
|
+
named resource or lower input size after an admission or limit error.
|
|
89
|
+
|
|
90
|
+
The application owns jobs, snapshots, storage, document issuance, and delivery.
|
|
91
|
+
For an issued document, store and reuse completed bytes. Two fresh renders are
|
|
92
|
+
not promised to be byte-identical.
|
|
93
|
+
|
|
94
|
+
## Verify the deployment
|
|
95
|
+
|
|
96
|
+
Standalone Node rendering is tested locally with embedded fonts and images,
|
|
97
|
+
Unicode text, pagination, and concurrent requests. That is not a deployed
|
|
98
|
+
Convex or other serverless-runtime certification. Edge workers remain unsupported.
|
|
99
|
+
|
|
100
|
+
Before adoption, measure your largest expected document on the target runtime.
|
|
101
|
+
Check cold and warm duration, memory use, output limits, failed asset requests,
|
|
102
|
+
and concurrent isolation. Keep the previously tested Node route available for
|
|
103
|
+
new renders until this proof passes. Do not regenerate issued PDFs during a
|
|
104
|
+
runtime migration.
|
|
105
|
+
|
|
106
|
+
See [deployment guidance](/raw/docs/guides/deployment.md) for the runtime matrix and
|
|
107
|
+
operational limits.
|