@lupinum/nuxt-pdf 0.3.0
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/API_REPORT.md +262 -0
- package/CHANGELOG.md +168 -0
- package/CONFORMANCE.md +496 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/THIRD_PARTY_NOTICES.md +75 -0
- package/dist/module.d.mts +24 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +368 -0
- package/dist/runtime/authoring.d.ts +141 -0
- package/dist/runtime/authoring.js +25 -0
- package/dist/runtime/components/_props.d.ts +196 -0
- package/dist/runtime/components/_props.js +63 -0
- package/dist/runtime/components/document.d.ts +9 -0
- package/dist/runtime/components/document.js +26 -0
- package/dist/runtime/components/index.d.ts +4 -0
- package/dist/runtime/components/index.js +27 -0
- package/dist/runtime/components/svg.d.ts +17 -0
- package/dist/runtime/components/svg.js +50 -0
- package/dist/runtime/composables/index.d.ts +2 -0
- package/dist/runtime/composables/index.js +3 -0
- package/dist/runtime/composables/use-pdf-page-numbers.d.ts +39 -0
- package/dist/runtime/composables/use-pdf-page-numbers.js +17 -0
- package/dist/runtime/define-pdf.d.ts +6 -0
- package/dist/runtime/define-pdf.js +5 -0
- package/dist/runtime/fonts.d.ts +16 -0
- package/dist/runtime/fonts.js +0 -0
- package/dist/runtime/renderer/index.d.ts +3 -0
- package/dist/runtime/renderer/index.js +1 -0
- package/dist/runtime/renderer/node-ops.d.ts +5 -0
- package/dist/runtime/renderer/node-ops.js +281 -0
- package/dist/runtime/renderer/patch-prop.d.ts +3 -0
- package/dist/runtime/renderer/patch-prop.js +223 -0
- package/dist/runtime/renderer/render-component.d.ts +14 -0
- package/dist/runtime/renderer/render-component.js +201 -0
- package/dist/runtime/renderer/types.d.ts +33 -0
- package/dist/runtime/renderer/types.js +56 -0
- package/dist/runtime/renderer/validate-tree.d.ts +3 -0
- package/dist/runtime/renderer/validate-tree.js +428 -0
- package/dist/runtime/server/assets/errors.d.ts +12 -0
- package/dist/runtime/server/assets/errors.js +15 -0
- package/dist/runtime/server/assets/remote.d.ts +48 -0
- package/dist/runtime/server/assets/remote.js +234 -0
- package/dist/runtime/server/assets/resolve-asset.d.ts +53 -0
- package/dist/runtime/server/assets/resolve-asset.js +482 -0
- package/dist/runtime/server/engine/CONTRACTS.md +386 -0
- package/dist/runtime/server/engine/fonts.d.ts +8 -0
- package/dist/runtime/server/engine/fonts.js +19 -0
- package/dist/runtime/server/engine/layout-passes.d.ts +42 -0
- package/dist/runtime/server/engine/layout-passes.js +58 -0
- package/dist/runtime/server/engine/react-pdf-pdfkit.d.ts +7 -0
- package/dist/runtime/server/engine/render-document.d.ts +31 -0
- package/dist/runtime/server/engine/render-document.js +236 -0
- package/dist/runtime/server/index.d.ts +5 -0
- package/dist/runtime/server/index.js +9 -0
- package/dist/runtime/server/preview.d.ts +17 -0
- package/dist/runtime/server/preview.js +332 -0
- package/dist/runtime/server/registry.d.ts +41 -0
- package/dist/runtime/server/registry.js +270 -0
- package/dist/runtime/server/render-limits.d.ts +51 -0
- package/dist/runtime/server/render-limits.js +143 -0
- package/dist/runtime/server/result.d.ts +6 -0
- package/dist/runtime/server/result.js +81 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/shared/errors.d.ts +22 -0
- package/dist/runtime/shared/errors.js +22 -0
- package/dist/runtime/shared/index.d.ts +4 -0
- package/dist/runtime/shared/index.js +7 -0
- package/dist/runtime/shared/template.d.ts +63 -0
- package/dist/runtime/shared/template.js +1 -0
- package/dist/shared/nuxt-pdf.D2ZziYn4.mjs +1132 -0
- package/dist/test.d.mts +198 -0
- package/dist/test.mjs +696 -0
- package/dist/types.d.mts +13 -0
- package/package.json +135 -0
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/logo.svg" width="128" alt="Nuxt PDF">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Nuxt PDF</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Author and render PDFs with Vue components in Nuxt.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@lupinum/nuxt-pdf"><img src="https://img.shields.io/npm/v/@lupinum/nuxt-pdf?color=315d3b" alt="npm version"></a>
|
|
13
|
+
<a href="https://github.com/lupinum-dev/nuxt-pdf/actions/workflows/ci.yml"><img src="https://github.com/lupinum-dev/nuxt-pdf/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
14
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-315d3b" alt="MIT license"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
> [!IMPORTANT]
|
|
18
|
+
> Nuxt PDF is an external alpha. Its tested behavior is stable within each
|
|
19
|
+
> release, but a minor release can contain documented breaking changes before
|
|
20
|
+
> version 1.0.
|
|
21
|
+
|
|
22
|
+
Nuxt PDF discovers Vue Single File Components in `pdfs/`. It renders these
|
|
23
|
+
components on the Node server through a PDF layout engine. The client bundle
|
|
24
|
+
does not contain the renderer or document templates.
|
|
25
|
+
|
|
26
|
+
Use Nuxt PDF for structured documents such as invoices, reports,
|
|
27
|
+
certificates, tickets, and labels.
|
|
28
|
+
|
|
29
|
+
Do not use Nuxt PDF to print an existing web page, display an existing PDF, or
|
|
30
|
+
edit and sign an existing PDF.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Node.js `^22.14.0`, `^24.0.0`, or `^26.0.0`
|
|
35
|
+
- Nuxt `^4.4.8`
|
|
36
|
+
- Vue `^3.5.0`
|
|
37
|
+
|
|
38
|
+
Nuxt 3, Node 20, browser rendering, and edge rendering are outside the current
|
|
39
|
+
support boundary.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
Install the module in a Nuxt 4 application:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add @lupinum/nuxt-pdf
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Add the module to `nuxt.config.ts`:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
export default defineNuxtConfig({
|
|
53
|
+
modules: ['@lupinum/nuxt-pdf'],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Render a document
|
|
58
|
+
|
|
59
|
+
Create `pdfs/invoice.vue`:
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
type InvoiceProps = {
|
|
64
|
+
invoice: {
|
|
65
|
+
customer: string
|
|
66
|
+
number: string
|
|
67
|
+
total: string
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const props = defineProps<InvoiceProps>()
|
|
72
|
+
|
|
73
|
+
definePdf<InvoiceProps>({
|
|
74
|
+
title: ({ invoice }) => `Invoice ${invoice.number}`,
|
|
75
|
+
filename: ({ invoice }) => `invoice-${invoice.number}.pdf`,
|
|
76
|
+
sampleData: {
|
|
77
|
+
invoice: {
|
|
78
|
+
customer: 'Ada Lovelace',
|
|
79
|
+
number: 'INV-001',
|
|
80
|
+
total: 'EUR 1,250.00',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<template>
|
|
87
|
+
<PdfDocument>
|
|
88
|
+
<PdfPage size="A4" :style="{ fontSize: 11, padding: 48 }">
|
|
89
|
+
<PdfText :style="{ fontSize: 24, marginBottom: 24 }">
|
|
90
|
+
Invoice {{ props.invoice.number }}
|
|
91
|
+
</PdfText>
|
|
92
|
+
<PdfText>{{ props.invoice.customer }}</PdfText>
|
|
93
|
+
<PdfText :style="{ marginTop: 12 }">
|
|
94
|
+
Total: {{ props.invoice.total }}
|
|
95
|
+
</PdfText>
|
|
96
|
+
</PdfPage>
|
|
97
|
+
</PdfDocument>
|
|
98
|
+
</template>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Create `server/api/invoice.get.ts`:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { pdf } from '#pdf'
|
|
105
|
+
|
|
106
|
+
export default defineEventHandler(async () => {
|
|
107
|
+
const result = await pdf.invoice.render({
|
|
108
|
+
invoice: {
|
|
109
|
+
customer: 'Ada Lovelace',
|
|
110
|
+
number: 'INV-001',
|
|
111
|
+
total: 'EUR 1,250.00',
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
return result.response()
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Start Nuxt. Open these development routes:
|
|
120
|
+
|
|
121
|
+
- `/_pdf` lists the discovered templates.
|
|
122
|
+
- `/_pdf/invoice` shows the document preview.
|
|
123
|
+
- `/api/invoice` returns the production-style PDF response.
|
|
124
|
+
|
|
125
|
+
Restart `nuxt dev` after you first enable the module. This action generates
|
|
126
|
+
the typed `#pdf` registry.
|
|
127
|
+
|
|
128
|
+
## Authoring model
|
|
129
|
+
|
|
130
|
+
PDF templates use typed props, interpolation, `v-if`, keyed `v-for`, slots,
|
|
131
|
+
and local Vue components. Nuxt PDF provides document, page, text, image, link,
|
|
132
|
+
note, and SVG primitives.
|
|
133
|
+
|
|
134
|
+
Templates run in an isolated Vue application on the Node server. They do not
|
|
135
|
+
inherit Nuxt plugins, app-level provides, browser globals, or DOM components.
|
|
136
|
+
Load request data before the render and pass it through typed props.
|
|
137
|
+
|
|
138
|
+
Nuxt PDF supports local images and fonts. Remote images are disabled until an
|
|
139
|
+
operator configures an explicit HTTPS allowlist.
|
|
140
|
+
|
|
141
|
+
Read the [documentation](https://nuxt-pdf.lupinum.com) for authoring, assets,
|
|
142
|
+
links, bookmarks, tables of contents, testing, and the complete API reference.
|
|
143
|
+
Read [CONFORMANCE.md](./CONFORMANCE.md) for the exact tested behavior and known
|
|
144
|
+
limitations.
|
|
145
|
+
|
|
146
|
+
## Test a document
|
|
147
|
+
|
|
148
|
+
Install the optional test dependencies:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pnpm add -D pdfjs-dist @napi-rs/canvas
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Render the real template in a test:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { expectPdf, renderPdfSfc } from '@lupinum/nuxt-pdf/test'
|
|
158
|
+
|
|
159
|
+
const { parsed } = await renderPdfSfc('./pdfs/invoice.vue', {
|
|
160
|
+
invoice: {
|
|
161
|
+
customer: 'Ada Lovelace',
|
|
162
|
+
number: 'INV-001',
|
|
163
|
+
total: 'EUR 1,250.00',
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
expectPdf(parsed)
|
|
168
|
+
.toHavePageCount(1)
|
|
169
|
+
.toContainText('Invoice INV-001')
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The test entry can also inspect links, bookmarks, page geometry, and raster
|
|
173
|
+
baselines.
|
|
174
|
+
|
|
175
|
+
## Develop Nuxt PDF
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm install --frozen-lockfile
|
|
179
|
+
pnpm dev
|
|
180
|
+
pnpm check
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Use `pnpm docs:dev` to run the documentation site. Use
|
|
184
|
+
`pnpm release:verify` only for release preparation.
|
|
185
|
+
|
|
186
|
+
Read [CONTRIBUTING.md](./CONTRIBUTING.md) before you open a pull request.
|
|
187
|
+
Maintainers use [MAINTAINING.md](./MAINTAINING.md) for release and recovery
|
|
188
|
+
procedures.
|
|
189
|
+
|
|
190
|
+
## Support and security
|
|
191
|
+
|
|
192
|
+
- Read the [documentation](https://nuxt-pdf.lupinum.com).
|
|
193
|
+
- Join the [Lupinum OSS Discord](https://discord.gg/RPH6SeA36N).
|
|
194
|
+
- Open a [GitHub issue](https://github.com/lupinum-dev/nuxt-pdf/issues) for a
|
|
195
|
+
reproducible defect.
|
|
196
|
+
- Follow [SECURITY.md](./SECURITY.md) for a private vulnerability report.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
[MIT](./LICENSE) © Lupinum OG and contributors.
|
|
201
|
+
|
|
202
|
+
Third-party attributions are in
|
|
203
|
+
[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## React PDF
|
|
4
|
+
|
|
5
|
+
Nuxt PDF uses the published lower-level React PDF engine packages and carries
|
|
6
|
+
paired test fixtures derived from the React PDF repository at commit
|
|
7
|
+
`d41a8207fb06a56e60fcb53ac0e18ce27e7d32d6`.
|
|
8
|
+
|
|
9
|
+
React PDF is distributed under the following MIT license:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2017-present Diego Muracciole <diegomuracciole@gmail.com>
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The exact engine packages and versions are recorded in
|
|
36
|
+
`src/runtime/server/engine/CONTRACTS.md`; the package build carries that record
|
|
37
|
+
at `dist/runtime/server/engine/CONTRACTS.md`. Their own package distributions
|
|
38
|
+
remain subject to their included licenses.
|
|
39
|
+
|
|
40
|
+
## Fixture provenance
|
|
41
|
+
|
|
42
|
+
The following files originate from the React PDF test suite at the commit
|
|
43
|
+
above and are retained only as local conformance, integration, or playground
|
|
44
|
+
fixtures:
|
|
45
|
+
|
|
46
|
+
| Nuxt PDF fixture | React PDF source |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `test/fixtures/assets/Roboto-Regular.ttf` | `packages/layout/tests/assets/font.ttf` |
|
|
49
|
+
| `test/fixtures/assets/sample.png` | `packages/layout/tests/assets/test.png` |
|
|
50
|
+
|
|
51
|
+
The copies under `test/fixtures/basic/pdfs/` use the same fixture bytes. The
|
|
52
|
+
playground copies the font as `pdfs/fonts/Roboto-Regular.ttf` and the image as
|
|
53
|
+
`pdfs/assets/alpine.png`.
|
|
54
|
+
|
|
55
|
+
The font identifies itself as Roboto Regular, copyright 2011 Google Inc., by
|
|
56
|
+
Christian Robertson, and as licensed under the Apache License 2.0:
|
|
57
|
+
<https://www.apache.org/licenses/LICENSE-2.0>.
|
|
58
|
+
|
|
59
|
+
These fixture binaries are not included in the published Nuxt PDF package.
|
|
60
|
+
|
|
61
|
+
## Source Code Pro test dependency
|
|
62
|
+
|
|
63
|
+
The development-only `source-code-pro` npm package supplies OTF and variable
|
|
64
|
+
font fixtures for executable compatibility tests. Source Code Pro is Copyright
|
|
65
|
+
2010–2023 Adobe and distributed under the SIL Open Font License 1.1. It is not
|
|
66
|
+
included in the published Nuxt PDF package; its license is included in the npm
|
|
67
|
+
dependency at `source-code-pro/LICENSE.md`.
|
|
68
|
+
|
|
69
|
+
## Noto international typography subsets
|
|
70
|
+
|
|
71
|
+
The test-only international typography fixture contains small FontTools subsets
|
|
72
|
+
of Noto Sans SC Variable, Noto Sans Arabic Variable, and Noto Emoji Variable.
|
|
73
|
+
They retain only the characters used by the executable calibration document,
|
|
74
|
+
are distributed under the SIL Open Font License 1.1 included beside the files,
|
|
75
|
+
and are not included in the published Nuxt PDF package.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { RemoteAssetOptions } from '../dist/runtime/server/assets/remote.js';
|
|
3
|
+
export { RemoteAssetOptions } from '../dist/runtime/server/assets/remote.js';
|
|
4
|
+
import { PdfLimitsOptions } from '../dist/runtime/server/render-limits.js';
|
|
5
|
+
export { PdfLimitsOptions } from '../dist/runtime/server/render-limits.js';
|
|
6
|
+
import { PdfFontDeclaration } from '../dist/runtime/fonts.js';
|
|
7
|
+
export { PdfFontDeclaration, PdfFontStyle, PdfFontWeight, PdfFontWeightName } from '../dist/runtime/fonts.js';
|
|
8
|
+
export { PdfLength, PdfLengthOrPercentage, PdfPercentage, PdfStyle, PdfStyleEntry, PdfStyleValue } from '../dist/runtime/authoring.js';
|
|
9
|
+
export { PdfBaseProps, PdfBookmark, PdfCircleProps, PdfClipPathProps, PdfDefsProps, PdfDocumentProps, PdfEllipseProps, PdfGProps, PdfImageProps, PdfImageSource, PdfLineProps, PdfLinearGradientProps, PdfLinkProps, PdfNoteProps, PdfPageDimension, PdfPageProps, PdfPageSize, PdfPageSizeName, PdfPageUnit, PdfPathProps, PdfPolygonProps, PdfPolylineProps, PdfRadialGradientProps, PdfRectProps, PdfStopProps, PdfSvgLength, PdfSvgNumber, PdfSvgPresentationProps, PdfSvgProps, PdfSvgTransform, PdfSvgTransformOperation, PdfTextProps, PdfTspanProps, PdfViewProps } from '../dist/runtime/components/index.js';
|
|
10
|
+
|
|
11
|
+
interface ModuleOptions {
|
|
12
|
+
fonts?: readonly PdfFontDeclaration[];
|
|
13
|
+
remote?: RemoteAssetOptions;
|
|
14
|
+
/**
|
|
15
|
+
* Canonical render, tree, image, remote-request, and output budgets. Every
|
|
16
|
+
* field is an optional positive safe integer; omitted fields use the
|
|
17
|
+
* documented built-in defaults.
|
|
18
|
+
*/
|
|
19
|
+
limits?: PdfLimitsOptions;
|
|
20
|
+
}
|
|
21
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
22
|
+
|
|
23
|
+
export { _default as default };
|
|
24
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { join, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { defineNuxtModule, createResolver, getLayerDirectories, createIsIgnored, addServerTemplate, addTemplate, addImports, addTypeTemplate, addServerHandler } from '@nuxt/kit';
|
|
4
|
+
import { d as discoverPdfTemplates, a as discoverPdfComponentFiles, b as discoverPdfImageFiles, c as bundlePdfFonts, e as createPdfSfcPlugin, f as classifyPdfWatchEvent } from './shared/nuxt-pdf.D2ZziYn4.mjs';
|
|
5
|
+
import { Buffer } from 'node:buffer';
|
|
6
|
+
import { joinURL } from 'ufo';
|
|
7
|
+
import { normalizeRemoteAssetPolicy } from '../dist/runtime/server/assets/remote.js';
|
|
8
|
+
import { loadPdfImageAsset } from '../dist/runtime/server/assets/resolve-asset.js';
|
|
9
|
+
import { normalizePdfLimits, DEFAULT_PDF_RENDER_LIMITS } from '../dist/runtime/server/render-limits.js';
|
|
10
|
+
import 'node:fs';
|
|
11
|
+
import '@vue/compiler-sfc';
|
|
12
|
+
import '@jridgewell/remapping';
|
|
13
|
+
import 'esbuild';
|
|
14
|
+
import 'unimport';
|
|
15
|
+
import '../dist/runtime/shared/template.js';
|
|
16
|
+
|
|
17
|
+
const compareText = (left, right) => {
|
|
18
|
+
if (left < right) return -1;
|
|
19
|
+
if (left > right) return 1;
|
|
20
|
+
return 0;
|
|
21
|
+
};
|
|
22
|
+
const quote$1 = (value) => JSON.stringify(value);
|
|
23
|
+
const generatePdfPreviewConfig = (baseURL, buildAssetsDir) => `export const hmrClientPath = ${quote$1(joinURL(
|
|
24
|
+
baseURL.replace(/^\.\//, "/") || "/",
|
|
25
|
+
buildAssetsDir,
|
|
26
|
+
"@vite/client"
|
|
27
|
+
))}
|
|
28
|
+
`;
|
|
29
|
+
const importPath = (filePath) => filePath.replaceAll("\\", "/");
|
|
30
|
+
const runtimeOptionsSource = (options) => {
|
|
31
|
+
const assets = options.assets ?? [];
|
|
32
|
+
const fonts = options.fonts ?? [];
|
|
33
|
+
const { remote, limits } = options;
|
|
34
|
+
if (assets.length === 0 && fonts.length === 0 && !remote && !limits) return [];
|
|
35
|
+
const lines = ["", "const __pdfRuntimeOptions = Object.freeze({"];
|
|
36
|
+
if (assets.length > 0) {
|
|
37
|
+
lines.push(" assets: Object.freeze({");
|
|
38
|
+
for (const asset of assets) {
|
|
39
|
+
lines.push(
|
|
40
|
+
` ${quote$1(asset.key)}: Object.freeze({ data: __pdfBuffer.from(${quote$1(Buffer.from(asset.data).toString("base64"))}, 'base64'), format: ${quote$1(asset.format)} }),`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
lines.push(" }),");
|
|
44
|
+
}
|
|
45
|
+
if (fonts.length > 0) {
|
|
46
|
+
lines.push(" fonts: Object.freeze([");
|
|
47
|
+
for (const font of fonts) {
|
|
48
|
+
lines.push(` Object.freeze(${JSON.stringify(font)}),`);
|
|
49
|
+
}
|
|
50
|
+
lines.push(" ]),");
|
|
51
|
+
}
|
|
52
|
+
if (remote) {
|
|
53
|
+
lines.push(` remote: Object.freeze(${JSON.stringify(remote)}),`);
|
|
54
|
+
}
|
|
55
|
+
if (limits) {
|
|
56
|
+
lines.push(` limits: Object.freeze(${JSON.stringify(limits)}),`);
|
|
57
|
+
}
|
|
58
|
+
lines.push("})");
|
|
59
|
+
return lines;
|
|
60
|
+
};
|
|
61
|
+
const orderedTemplates = (templates) => {
|
|
62
|
+
const result = [...templates].sort(
|
|
63
|
+
(left, right) => compareText(left.key, right.key)
|
|
64
|
+
);
|
|
65
|
+
const keys = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const template of result) {
|
|
67
|
+
const duplicate = keys.get(template.key);
|
|
68
|
+
if (duplicate) {
|
|
69
|
+
throw new TypeError(
|
|
70
|
+
`Cannot generate duplicate PDF template key "${template.key}" from "${duplicate.filePath}" and "${template.filePath}".`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
keys.set(template.key, template);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
const validateOptions = (options) => {
|
|
78
|
+
if (options.runtimeImport.trim() === "") {
|
|
79
|
+
throw new TypeError("runtimeImport is required to generate the PDF registry.");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const generatePdfRuntimeRegistry = (templates, options) => {
|
|
83
|
+
validateOptions(options);
|
|
84
|
+
const ordered = orderedTemplates(templates);
|
|
85
|
+
const runtimeImports = options.development ? "createPdfPreviewEntry, createPdfRegistry, createPdfTemplate" : "createPdfRegistry, createPdfTemplate";
|
|
86
|
+
const lines = [
|
|
87
|
+
`import { ${runtimeImports} } from ${quote$1(options.runtimeImport)}`
|
|
88
|
+
];
|
|
89
|
+
if ((options.assets?.length ?? 0) > 0) {
|
|
90
|
+
lines.push("import { Buffer as __pdfBuffer } from 'node:buffer'");
|
|
91
|
+
}
|
|
92
|
+
ordered.forEach((template, index) => {
|
|
93
|
+
lines.push(
|
|
94
|
+
`import __pdfTemplate${index} from ${quote$1(importPath(template.filePath))}`
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
const runtimeOptions = runtimeOptionsSource(options);
|
|
98
|
+
lines.push(...runtimeOptions, "", "const registry = createPdfRegistry({");
|
|
99
|
+
ordered.forEach((template, index) => {
|
|
100
|
+
const file = quote$1(`pdfs/${template.relativePath}`);
|
|
101
|
+
const runtimeArgument = options.development ? runtimeOptions.length > 0 ? `, { ...__pdfRuntimeOptions, file: ${file} }` : `, { file: ${file} }` : runtimeOptions.length > 0 ? ", __pdfRuntimeOptions" : "";
|
|
102
|
+
lines.push(
|
|
103
|
+
` ${quote$1(template.key)}: createPdfTemplate(${quote$1(template.key)}, __pdfTemplate${index}${runtimeArgument}),`
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
lines.push("})");
|
|
107
|
+
if (options.development) {
|
|
108
|
+
lines.push("", "export const pdfPreview = Object.freeze({");
|
|
109
|
+
ordered.forEach((template, index) => {
|
|
110
|
+
lines.push(
|
|
111
|
+
` ${quote$1(template.key)}: createPdfPreviewEntry(registry.pdf[${quote$1(template.key)}], __pdfTemplate${index}, { file: ${quote$1(`pdfs/${template.relativePath}`)} }),`
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
lines.push("})");
|
|
115
|
+
}
|
|
116
|
+
lines.push(
|
|
117
|
+
"",
|
|
118
|
+
"export const pdf = registry.pdf",
|
|
119
|
+
"export const renderPdf = registry.renderPdf",
|
|
120
|
+
"export const getPdfTemplate = registry.getPdfTemplate",
|
|
121
|
+
"export const pdfTemplateKeys = registry.pdfTemplateKeys",
|
|
122
|
+
`export { NuxtPdfError, PDF_ERROR_CODES } from ${quote$1(options.runtimeImport)}`,
|
|
123
|
+
""
|
|
124
|
+
);
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
};
|
|
127
|
+
const generatePdfRegistryTypes = (templates, options) => {
|
|
128
|
+
validateOptions(options);
|
|
129
|
+
const ordered = orderedTemplates(templates);
|
|
130
|
+
const lines = [
|
|
131
|
+
`import type { PdfComponentProps, PdfRenderResult, PdfTemplate } from ${quote$1(options.runtimeImport)}`
|
|
132
|
+
];
|
|
133
|
+
ordered.forEach((template, index) => {
|
|
134
|
+
lines.push(
|
|
135
|
+
"",
|
|
136
|
+
`type PdfComponent${index} = typeof import(${quote$1(importPath(template.filePath))})['default']`,
|
|
137
|
+
`type PdfProps${index} = PdfComponentProps<PdfComponent${index}>`
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
lines.push("", "export declare const pdf: {");
|
|
141
|
+
ordered.forEach((template, index) => {
|
|
142
|
+
lines.push(
|
|
143
|
+
` readonly ${quote$1(template.key)}: PdfTemplate<PdfProps${index}>`
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
lines.push("}", "");
|
|
147
|
+
if (ordered.length === 0) {
|
|
148
|
+
lines.push(
|
|
149
|
+
"export declare function renderPdf(name: never, props: never): Promise<PdfRenderResult>",
|
|
150
|
+
"export declare function getPdfTemplate(name: never): never"
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
ordered.forEach((template, index) => {
|
|
154
|
+
lines.push(
|
|
155
|
+
`export declare function renderPdf(name: ${quote$1(template.key)}, props: PdfProps${index}): Promise<PdfRenderResult>`
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
lines.push(
|
|
159
|
+
"export declare function renderPdf(name: string, props: Record<string, unknown>, escapeHatch: { readonly unsafe: true }): Promise<PdfRenderResult>"
|
|
160
|
+
);
|
|
161
|
+
lines.push("");
|
|
162
|
+
ordered.forEach((template, index) => {
|
|
163
|
+
lines.push(
|
|
164
|
+
`export declare function getPdfTemplate(name: ${quote$1(template.key)}): PdfTemplate<PdfProps${index}>`
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
lines.push(
|
|
169
|
+
"",
|
|
170
|
+
`export declare const pdfTemplateKeys: readonly [${ordered.map((template) => quote$1(template.key)).join(", ")}]`,
|
|
171
|
+
"export type PdfTemplateKey = typeof pdfTemplateKeys[number]",
|
|
172
|
+
`export { NuxtPdfError, PDF_ERROR_CODES } from ${quote$1(options.runtimeImport)}`,
|
|
173
|
+
`export type { PdfErrorCode } from ${quote$1(options.runtimeImport)}`,
|
|
174
|
+
""
|
|
175
|
+
);
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const quote = (value) => JSON.stringify(value);
|
|
180
|
+
const existingDirectories = async (directories) => {
|
|
181
|
+
const result = [];
|
|
182
|
+
for (const directory of directories) {
|
|
183
|
+
try {
|
|
184
|
+
if ((await stat(directory)).isDirectory()) result.push(directory);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error.code !== "ENOENT") throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
const generateAuthoringTypes = (componentsImport) => `declare module 'vue' {
|
|
192
|
+
interface GlobalComponents {
|
|
193
|
+
PdfDocument: typeof import(${quote(componentsImport)})['PdfDocument']
|
|
194
|
+
PdfImage: typeof import(${quote(componentsImport)})['PdfImage']
|
|
195
|
+
PdfLink: typeof import(${quote(componentsImport)})['PdfLink']
|
|
196
|
+
PdfNote: typeof import(${quote(componentsImport)})['PdfNote']
|
|
197
|
+
PdfPage: typeof import(${quote(componentsImport)})['PdfPage']
|
|
198
|
+
PdfText: typeof import(${quote(componentsImport)})['PdfText']
|
|
199
|
+
PdfView: typeof import(${quote(componentsImport)})['PdfView']
|
|
200
|
+
PdfSvg: typeof import(${quote(componentsImport)})['PdfSvg']
|
|
201
|
+
PdfG: typeof import(${quote(componentsImport)})['PdfG']
|
|
202
|
+
PdfPath: typeof import(${quote(componentsImport)})['PdfPath']
|
|
203
|
+
PdfRect: typeof import(${quote(componentsImport)})['PdfRect']
|
|
204
|
+
PdfCircle: typeof import(${quote(componentsImport)})['PdfCircle']
|
|
205
|
+
PdfEllipse: typeof import(${quote(componentsImport)})['PdfEllipse']
|
|
206
|
+
PdfLine: typeof import(${quote(componentsImport)})['PdfLine']
|
|
207
|
+
PdfPolyline: typeof import(${quote(componentsImport)})['PdfPolyline']
|
|
208
|
+
PdfPolygon: typeof import(${quote(componentsImport)})['PdfPolygon']
|
|
209
|
+
PdfDefs: typeof import(${quote(componentsImport)})['PdfDefs']
|
|
210
|
+
PdfClipPath: typeof import(${quote(componentsImport)})['PdfClipPath']
|
|
211
|
+
PdfLinearGradient: typeof import(${quote(componentsImport)})['PdfLinearGradient']
|
|
212
|
+
PdfRadialGradient: typeof import(${quote(componentsImport)})['PdfRadialGradient']
|
|
213
|
+
PdfStop: typeof import(${quote(componentsImport)})['PdfStop']
|
|
214
|
+
PdfTspan: typeof import(${quote(componentsImport)})['PdfTspan']
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export {}
|
|
219
|
+
`;
|
|
220
|
+
const module$1 = defineNuxtModule({
|
|
221
|
+
meta: {
|
|
222
|
+
name: "nuxt-pdf",
|
|
223
|
+
configKey: "pdf",
|
|
224
|
+
compatibility: {
|
|
225
|
+
nuxt: "^4.4.8"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
defaults: {
|
|
229
|
+
fonts: []
|
|
230
|
+
},
|
|
231
|
+
async setup(options, nuxt) {
|
|
232
|
+
const resolver = createResolver(import.meta.url);
|
|
233
|
+
const runtimeImport = resolver.resolve("./runtime/server/index");
|
|
234
|
+
const sharedImport = resolver.resolve("./runtime/shared/index");
|
|
235
|
+
const componentsImport = resolver.resolve("./runtime/components/index");
|
|
236
|
+
const composablesImport = resolver.resolve("./runtime/composables/index");
|
|
237
|
+
const definePdfImport = resolver.resolve("./runtime/define-pdf");
|
|
238
|
+
const previewHandler = resolver.resolve("./runtime/server/preview");
|
|
239
|
+
const layers = getLayerDirectories(nuxt).map(
|
|
240
|
+
(directories, index) => ({
|
|
241
|
+
rootDir: directories.root,
|
|
242
|
+
name: index === 0 ? "project" : `layer-${index}`
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
const isIgnored = createIsIgnored(nuxt);
|
|
246
|
+
const templates = await discoverPdfTemplates(layers, isIgnored);
|
|
247
|
+
const componentFiles = await discoverPdfComponentFiles(layers, isIgnored);
|
|
248
|
+
const limits = normalizePdfLimits(options.limits);
|
|
249
|
+
const resolvedLimits = limits ?? DEFAULT_PDF_RENDER_LIMITS;
|
|
250
|
+
const imageFiles = await discoverPdfImageFiles(layers, isIgnored);
|
|
251
|
+
const assets = await Promise.all(imageFiles.map(
|
|
252
|
+
(image) => loadPdfImageAsset(image.key, {
|
|
253
|
+
roots: [image.rootDir],
|
|
254
|
+
maxBytes: resolvedLimits.maxImageBytes,
|
|
255
|
+
maxPixels: resolvedLimits.maxImagePixels
|
|
256
|
+
})
|
|
257
|
+
));
|
|
258
|
+
const fontRoots = await existingDirectories(
|
|
259
|
+
layers.map((layer) => join(layer.rootDir, "pdfs", "fonts"))
|
|
260
|
+
);
|
|
261
|
+
const remote = normalizeRemoteAssetPolicy(options.remote);
|
|
262
|
+
const fonts = await bundlePdfFonts(options.fonts ?? [], { fontRoots });
|
|
263
|
+
const pdfSfcFiles = new Map(
|
|
264
|
+
componentFiles.map((file) => [file, "component"])
|
|
265
|
+
);
|
|
266
|
+
for (const template of templates) {
|
|
267
|
+
pdfSfcFiles.set(template.filePath, "template");
|
|
268
|
+
}
|
|
269
|
+
for (const layer of layers) {
|
|
270
|
+
nuxt.options.watch.push(join(layer.rootDir, "pdfs"));
|
|
271
|
+
}
|
|
272
|
+
addServerTemplate({
|
|
273
|
+
filename: "#pdf",
|
|
274
|
+
getContents: () => generatePdfRuntimeRegistry(templates, {
|
|
275
|
+
assets,
|
|
276
|
+
development: nuxt.options.dev,
|
|
277
|
+
fonts,
|
|
278
|
+
remote,
|
|
279
|
+
limits,
|
|
280
|
+
runtimeImport
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
const registryTypeTemplate = addTemplate({
|
|
284
|
+
filename: "types/nuxt-pdf-registry.ts",
|
|
285
|
+
getContents: () => generatePdfRegistryTypes(templates, {
|
|
286
|
+
runtimeImport: sharedImport
|
|
287
|
+
}),
|
|
288
|
+
write: true
|
|
289
|
+
});
|
|
290
|
+
const addRegistryTypePath = (config) => {
|
|
291
|
+
const compilerOptions = config.compilerOptions ||= {};
|
|
292
|
+
const paths = compilerOptions.paths ||= {};
|
|
293
|
+
paths["#pdf"] = [registryTypeTemplate.dst];
|
|
294
|
+
};
|
|
295
|
+
nuxt.hook("prepare:types", ({ nodeTsConfig, tsConfig }) => {
|
|
296
|
+
addRegistryTypePath(tsConfig);
|
|
297
|
+
addRegistryTypePath(nodeTsConfig);
|
|
298
|
+
});
|
|
299
|
+
const nitroTypescript = nuxt.options.nitro.typescript ||= {};
|
|
300
|
+
const nitroTsConfig = nitroTypescript.tsConfig ||= {};
|
|
301
|
+
addRegistryTypePath(nitroTsConfig);
|
|
302
|
+
addImports({
|
|
303
|
+
name: "usePdfPageNumbers",
|
|
304
|
+
as: "usePdfPageNumbers",
|
|
305
|
+
from: composablesImport
|
|
306
|
+
});
|
|
307
|
+
addImports({
|
|
308
|
+
name: "definePdf",
|
|
309
|
+
as: "definePdf",
|
|
310
|
+
from: definePdfImport
|
|
311
|
+
});
|
|
312
|
+
addTypeTemplate({
|
|
313
|
+
filename: "types/nuxt-pdf-authoring.d.ts",
|
|
314
|
+
getContents: () => generateAuthoringTypes(
|
|
315
|
+
componentsImport
|
|
316
|
+
),
|
|
317
|
+
write: true
|
|
318
|
+
}, { nitro: true, node: true, nuxt: true });
|
|
319
|
+
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
320
|
+
const rollupConfig = nitroConfig.rollupConfig ||= {};
|
|
321
|
+
const existingPlugins = rollupConfig.plugins == null ? [] : Array.isArray(rollupConfig.plugins) ? rollupConfig.plugins : [rollupConfig.plugins];
|
|
322
|
+
rollupConfig.plugins = [
|
|
323
|
+
createPdfSfcPlugin({
|
|
324
|
+
files: pdfSfcFiles,
|
|
325
|
+
isProduction: !nuxt.options.dev,
|
|
326
|
+
composablesImport
|
|
327
|
+
}),
|
|
328
|
+
...existingPlugins
|
|
329
|
+
];
|
|
330
|
+
});
|
|
331
|
+
if (nuxt.options.dev) {
|
|
332
|
+
let clientViteServer;
|
|
333
|
+
nuxt.hook("vite:serverCreated", (viteServer, environment) => {
|
|
334
|
+
if (environment.isClient) clientViteServer = viteServer;
|
|
335
|
+
});
|
|
336
|
+
nuxt.hook("builder:watch", async (event, path) => {
|
|
337
|
+
const absolutePath = isAbsolute(path) ? path : resolve(nuxt.options.srcDir, path);
|
|
338
|
+
const action = classifyPdfWatchEvent(
|
|
339
|
+
event,
|
|
340
|
+
absolutePath,
|
|
341
|
+
layers,
|
|
342
|
+
isIgnored
|
|
343
|
+
);
|
|
344
|
+
if (action === "restart") {
|
|
345
|
+
await nuxt.callHook("restart");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (action !== "refresh") return;
|
|
349
|
+
clientViteServer?.ws.send({
|
|
350
|
+
type: "custom",
|
|
351
|
+
event: "nuxt-pdf:update",
|
|
352
|
+
data: {}
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
addServerTemplate({
|
|
356
|
+
filename: "#pdf-preview-config",
|
|
357
|
+
getContents: () => generatePdfPreviewConfig(
|
|
358
|
+
nuxt.options.app.baseURL,
|
|
359
|
+
nuxt.options.app.buildAssetsDir
|
|
360
|
+
)
|
|
361
|
+
});
|
|
362
|
+
addServerHandler({ route: "/_pdf", handler: previewHandler });
|
|
363
|
+
addServerHandler({ route: "/_pdf/**", handler: previewHandler });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
export { module$1 as default };
|