@lupinum/nuxt-pdf 0.3.1 → 0.4.0-beta.2
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 +48 -0
- package/CONFORMANCE.md +17 -12
- package/README.md +54 -9
- package/dist/module.json +4 -3
- package/dist/module.mjs +66 -22
- package/dist/runtime/components/_props.d.ts +7 -13
- package/dist/runtime/components/stubs.d.ts +10 -0
- package/dist/runtime/components/stubs.js +37 -0
- package/dist/runtime/fonts.d.ts +1 -1
- package/dist/runtime/renderer/index.d.ts +1 -0
- package/dist/runtime/renderer/index.js +1 -0
- package/dist/runtime/renderer/patch-prop.d.ts +3 -1
- package/dist/runtime/renderer/patch-prop.js +1 -3
- package/dist/runtime/renderer/validate-tree.js +5 -8
- package/dist/runtime/server/assets/resolve-asset.d.ts +23 -5
- package/dist/runtime/server/assets/resolve-asset.js +86 -13
- package/dist/runtime/server/engine/fonts.d.ts +1 -0
- package/dist/runtime/server/engine/fonts.js +14 -1
- package/dist/runtime/server/engine/render-document.d.ts +7 -0
- package/dist/runtime/server/engine/render-document.js +47 -0
- package/dist/runtime/server/index.d.ts +1 -1
- package/dist/runtime/server/preview.js +11 -3
- package/dist/runtime/server/registry.d.ts +1 -1
- package/dist/runtime/server/registry.js +11 -3
- package/dist/runtime/server/result.js +1 -0
- package/dist/runtime/shared/index.d.ts +1 -1
- package/dist/runtime/shared/template.d.ts +8 -0
- package/dist/shared/{nuxt-pdf.D2ZziYn4.mjs → nuxt-pdf.D0zmsct0.mjs} +51 -9
- package/dist/test.mjs +7 -10
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,54 @@ All notable changes to `@lupinum/nuxt-pdf` are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres
|
|
5
5
|
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.4.0-beta.2 - 2026-08-27
|
|
8
|
+
|
|
9
|
+
[compare changes](https://github.com/lupinum-dev/nuxt-pdf/compare/v0.4.0-beta.1...v0.4.0-beta.2)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **fonts:** Support WOFF2 assets ([ad50390](https://github.com/lupinum-dev/nuxt-pdf/commit/ad50390))
|
|
14
|
+
- **diagnostics:** Explain unbreakable layout overflow ([a5ff0b7](https://github.com/lupinum-dev/nuxt-pdf/commit/a5ff0b7))
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **ci:** Harden on-demand Vercel reporting ([#35](https://github.com/lupinum-dev/nuxt-pdf/pull/35))
|
|
19
|
+
- **release:** Repair from certified source evidence ([#36](https://github.com/lupinum-dev/nuxt-pdf/pull/36))
|
|
20
|
+
- **release:** Separate publication from repair ([#37](https://github.com/lupinum-dev/nuxt-pdf/pull/37))
|
|
21
|
+
- **types:** Expose PDF authoring globals ([c427310](https://github.com/lupinum-dev/nuxt-pdf/commit/c427310))
|
|
22
|
+
|
|
23
|
+
### CI
|
|
24
|
+
|
|
25
|
+
- **vercel:** Cut library preview build usage ([#34](https://github.com/lupinum-dev/nuxt-pdf/pull/34))
|
|
26
|
+
|
|
27
|
+
## 0.4.0-beta.1 - 2026-08-22
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Add strict PDF primitive stubs so invalid props fail during authoring instead
|
|
32
|
+
of reaching the renderer.
|
|
33
|
+
- Add safer local and remote image admission, configured-font validation, and
|
|
34
|
+
clearer development preview diagnostics.
|
|
35
|
+
- Add source-derived documentation checks for primitive props, error codes,
|
|
36
|
+
page sizes, render limits, public exports, routes, and complete examples.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Rebuild the documentation around a first-render tutorial, focused task
|
|
41
|
+
guides, complete API reference, production guidance, and compatibility
|
|
42
|
+
evidence.
|
|
43
|
+
- Expand the playground with reusable invoice components, production document
|
|
44
|
+
recipes, and stronger preview scenarios.
|
|
45
|
+
- Refresh the documentation design with stable Ginko packages and the Nuxt
|
|
46
|
+
visual language.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Keep release workflows tied to the exact verified artifact and independent
|
|
51
|
+
of unnecessary GitHub tokens.
|
|
52
|
+
- Build documentation when no previous Vercel deployment baseline exists.
|
|
53
|
+
- Prepare Nuxt-generated types before clean package and documentation builds.
|
|
54
|
+
|
|
7
55
|
## 0.3.1 - 2026-08-13
|
|
8
56
|
|
|
9
57
|
### Fixed
|
package/CONFORMANCE.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Nuxt PDF 0.
|
|
1
|
+
# Nuxt PDF 0.4.0-beta.2 conformance
|
|
2
2
|
|
|
3
3
|
Nuxt PDF claims behavioral compatibility for a deliberately small, tested
|
|
4
4
|
corpus. It does not claim full React PDF API or test-suite compatibility.
|
|
5
5
|
|
|
6
6
|
## Version boundary
|
|
7
7
|
|
|
8
|
-
| Layer | 0.
|
|
8
|
+
| Layer | 0.4.0-beta.2 boundary |
|
|
9
9
|
|---|---|
|
|
10
10
|
| Node.js | `^22.14.0`, `^24.0.0`, or `^26.0.0` |
|
|
11
11
|
| Nuxt | `^4.4.8` |
|
|
@@ -158,7 +158,9 @@ claimed.
|
|
|
158
158
|
**Images** (`test/corpus/images.test.ts`):
|
|
159
159
|
|
|
160
160
|
- JPEG file paths, base64 `data:` URLs, and `{ data, format }` buffer sources all
|
|
161
|
-
decode
|
|
161
|
+
decode in the lower-engine paired fixture. The public resource boundary
|
|
162
|
+
accepts bundled paths, admitted byte sources, and allowlisted HTTPS images;
|
|
163
|
+
it blocks `data:` URL strings on every public render path;
|
|
162
164
|
- explicit width+height, single-dimension aspect scaling, and percent width
|
|
163
165
|
against the page content box size the laid-out box (reviewed baseline);
|
|
164
166
|
- `objectFit` `contain` letterboxes and `cover` crops while the box stays fixed
|
|
@@ -202,7 +204,7 @@ checking.
|
|
|
202
204
|
|
|
203
205
|
### Vue and Nuxt authoring
|
|
204
206
|
|
|
205
|
-
The 0.
|
|
207
|
+
The 0.4.0-beta.2 tests verify:
|
|
206
208
|
|
|
207
209
|
- `PdfDocument`, `PdfPage`, `PdfView`, `PdfText`, `PdfImage`, `PdfLink`, and
|
|
208
210
|
`PdfNote`;
|
|
@@ -215,8 +217,9 @@ The 0.3.1 tests verify:
|
|
|
215
217
|
- framework-owned `PdfStyle` and exact primitive prop types checked through a
|
|
216
218
|
real Vue SFC, plus closed per-primitive runtime allowlists that reject
|
|
217
219
|
unknown, DOM/event, removed, and wrong-host props without echoing values;
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
+
- a required `src` for `PdfImage`, a required `href` for `PdfLink` (an `href`
|
|
221
|
+
starting with `#` is an internal destination), and context-specific
|
|
222
|
+
page-flow/SVG `PdfText` invariants;
|
|
220
223
|
- one completed render held behind immutable byte, buffer, and `Response`
|
|
221
224
|
conversions, with exact frozen resolved metadata and one frozen, content-free
|
|
222
225
|
diagnostics object shared by the public result and development preview; the
|
|
@@ -287,7 +290,7 @@ and exposes it through one composable and existing props. The tested boundary:
|
|
|
287
290
|
`PDF_LIMIT_EXCEEDED` `NuxtPdfError` attributed to the template key and file
|
|
288
291
|
through the same boundary as every other render failure.
|
|
289
292
|
- **Named destinations resolve to a section's first page.** A node's `id` becomes
|
|
290
|
-
a named destination; a `PdfLink` `
|
|
293
|
+
a named destination; a `PdfLink` `href="#id"` jumps to it. When the id sits on a
|
|
291
294
|
node that spans a page boundary, both the printed number and the jump target
|
|
292
295
|
resolve to the section's **first** page (a deliberate divergence from React PDF,
|
|
293
296
|
whose last-writer-wins destination table points at the last page). This holds on
|
|
@@ -317,11 +320,13 @@ and exposes it through one composable and existing props. The tested boundary:
|
|
|
317
320
|
|
|
318
321
|
### Local resources
|
|
319
322
|
|
|
320
|
-
The module validates
|
|
321
|
-
|
|
323
|
+
The module validates configured resources during the Nuxt build. Production
|
|
324
|
+
builds embed validated image bytes in the server bundle; development builds
|
|
325
|
+
point at the source files and re-read them per render, so an edited image
|
|
326
|
+
shows up without a restart. The tested boundary includes:
|
|
322
327
|
|
|
323
328
|
- PNG and JPEG extension/signature validation and source byte limits;
|
|
324
|
-
- TTF and
|
|
329
|
+
- TTF, OTF, and WOFF2 signature/extension/structure validation,
|
|
325
330
|
registration validation, source byte limits, and source-removal rendering;
|
|
326
331
|
- explicit local `pdfs/assets` and `pdfs/fonts` roots;
|
|
327
332
|
- rejection of absolute paths, traversal, missing assets, ambiguous sources,
|
|
@@ -432,7 +437,7 @@ parser, not two. Claimed:
|
|
|
432
437
|
Verified end-to-end against a real rendered template, including assertion
|
|
433
438
|
failure messages, in `test/test-utils-public.test.ts`.
|
|
434
439
|
|
|
435
|
-
## Explicitly not claimed in 0.
|
|
440
|
+
## Explicitly not claimed in 0.4.0-beta.2
|
|
436
441
|
|
|
437
442
|
- Full React PDF component, hook, browser-helper, or test-suite parity.
|
|
438
443
|
- React runtime compatibility or React-shaped dynamic callback results.
|
|
@@ -446,7 +451,7 @@ failure messages, in `test/test-utils-public.test.ts`.
|
|
|
446
451
|
- Nuxt 3, Node 20, or versions outside the table above.
|
|
447
452
|
- Authenticated remote fetches, request headers/bodies, credentialed requests,
|
|
448
453
|
proxies, or private-IP/DNS-rebinding protection. Opt-in allowlisted remote
|
|
449
|
-
images
|
|
454
|
+
images are claimed above. Remote fonts are not supported.
|
|
450
455
|
- SVG image files (as an image source), SVG `Marker`, alternate gradient
|
|
451
456
|
coordinate systems/transforms/inheritance, and `preserveAspectRatio` modes.
|
|
452
457
|
Radial-gradient inner radius (`fr`) is also absent because the pinned renderer
|
package/README.md
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/public/icon-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/public/icon-light.svg">
|
|
5
|
+
<img src="docs/public/icon-light.svg" width="128" alt="Nuxt PDF icon">
|
|
6
|
+
</picture>
|
|
3
7
|
</p>
|
|
4
8
|
|
|
5
|
-
<h1 align="center">
|
|
9
|
+
<h1 align="center">
|
|
10
|
+
<picture>
|
|
11
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/public/wordmark-dark.svg">
|
|
12
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/public/wordmark-light.svg">
|
|
13
|
+
<img src="docs/public/wordmark-light.svg" width="256" alt="Nuxt PDF">
|
|
14
|
+
</picture>
|
|
15
|
+
</h1>
|
|
6
16
|
|
|
7
17
|
<p align="center">
|
|
8
18
|
Author and render PDFs with Vue components in Nuxt.
|
|
9
19
|
</p>
|
|
10
20
|
|
|
11
21
|
<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=
|
|
22
|
+
<a href="https://www.npmjs.com/package/@lupinum/nuxt-pdf"><img src="https://img.shields.io/npm/v/@lupinum/nuxt-pdf?color=00DC82" alt="npm version"></a>
|
|
13
23
|
<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-
|
|
24
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-00DC82" alt="MIT license"></a>
|
|
25
|
+
<a href="https://discord.gg/RPH6SeA36N"><img src="https://img.shields.io/badge/Discord-18181B?logo=discord" alt="Discord"></a>
|
|
26
|
+
<a href="https://deepwiki.com/lupinum-dev/nuxt-pdf"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
15
27
|
</p>
|
|
16
28
|
|
|
17
29
|
> [!IMPORTANT]
|
|
18
|
-
> Nuxt PDF is
|
|
30
|
+
> Nuxt PDF is in public beta. Its tested behavior is stable within each
|
|
19
31
|
> release, but a minor release can contain documented breaking changes before
|
|
20
32
|
> version 1.0.
|
|
21
33
|
|
|
@@ -65,7 +77,7 @@ export default defineNuxtConfig({
|
|
|
65
77
|
|
|
66
78
|
Create `pdfs/invoice.vue`:
|
|
67
79
|
|
|
68
|
-
```vue
|
|
80
|
+
```vue [pdfs/invoice.vue]
|
|
69
81
|
<script setup lang="ts">
|
|
70
82
|
type InvoiceProps = {
|
|
71
83
|
invoice: {
|
|
@@ -80,6 +92,7 @@ const props = defineProps<InvoiceProps>()
|
|
|
80
92
|
definePdf<InvoiceProps>({
|
|
81
93
|
title: ({ invoice }) => `Invoice ${invoice.number}`,
|
|
82
94
|
filename: ({ invoice }) => `invoice-${invoice.number}.pdf`,
|
|
95
|
+
language: 'en-GB',
|
|
83
96
|
sampleData: {
|
|
84
97
|
invoice: {
|
|
85
98
|
customer: 'Ada Lovelace',
|
|
@@ -123,14 +136,42 @@ export default defineEventHandler(async () => {
|
|
|
123
136
|
})
|
|
124
137
|
```
|
|
125
138
|
|
|
126
|
-
Start Nuxt
|
|
139
|
+
Start Nuxt:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm dev
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Download the PDF from a second terminal:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
curl -o invoice.pdf http://localhost:3000/api/invoice
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Open these development routes:
|
|
127
152
|
|
|
128
153
|
- `/_pdf` lists the discovered templates.
|
|
129
154
|
- `/_pdf/invoice` shows the document preview.
|
|
130
155
|
- `/api/invoice` returns the production-style PDF response.
|
|
131
156
|
|
|
132
|
-
|
|
133
|
-
|
|
157
|
+
Nuxt PDF regenerates the typed `#pdf` registry whenever templates change. If
|
|
158
|
+
your editor still shows `#pdf` as untyped, run `nuxt prepare` once to refresh
|
|
159
|
+
the generated types.
|
|
160
|
+
|
|
161
|
+
## Discord
|
|
162
|
+
|
|
163
|
+
Join the Lupinum OSS community to discuss Nuxt PDF, ask questions, and share
|
|
164
|
+
what you build.
|
|
165
|
+
|
|
166
|
+
<p align="center">
|
|
167
|
+
<a href="https://discord.gg/RPH6SeA36N">
|
|
168
|
+
<picture>
|
|
169
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/public/discord-dark.svg">
|
|
170
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/public/discord-light.svg">
|
|
171
|
+
<img src="docs/public/discord-light.svg" width="500" alt="Join the Lupinum OSS Discord">
|
|
172
|
+
</picture>
|
|
173
|
+
</a>
|
|
174
|
+
</p>
|
|
134
175
|
|
|
135
176
|
## How it works
|
|
136
177
|
|
|
@@ -181,6 +222,10 @@ assets, links, bookmarks, tables of contents, testing, and the complete API
|
|
|
181
222
|
reference. Read [CONFORMANCE.md](./CONFORMANCE.md) for the exact tested behavior
|
|
182
223
|
and known limitations.
|
|
183
224
|
|
|
225
|
+
Coding agents should start with
|
|
226
|
+
[`/llms.txt`](https://nuxt-pdf.lupinum.com/llms.txt). It links to concise raw
|
|
227
|
+
Markdown for common tasks, reference pages, errors, and compatibility evidence.
|
|
228
|
+
|
|
184
229
|
## Contributing and development
|
|
185
230
|
|
|
186
231
|
```bash
|
package/dist/module.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "nuxt-pdf",
|
|
2
|
+
"name": "@lupinum/nuxt-pdf",
|
|
3
|
+
"version": "0.4.0-beta.2",
|
|
3
4
|
"configKey": "pdf",
|
|
5
|
+
"docs": "https://nuxt-pdf.lupinum.com",
|
|
4
6
|
"compatibility": {
|
|
5
|
-
"nuxt": "
|
|
7
|
+
"nuxt": ">=4.4.8"
|
|
6
8
|
},
|
|
7
|
-
"version": "0.3.1",
|
|
8
9
|
"builder": {
|
|
9
10
|
"@nuxt/module-builder": "1.0.2",
|
|
10
11
|
"unbuild": "unknown"
|
package/dist/module.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
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
2
|
import { Buffer } from 'node:buffer';
|
|
3
|
+
import { join, isAbsolute, resolve, relative } from 'node:path';
|
|
4
|
+
import { defineNuxtModule, createResolver, getLayerDirectories, createIsIgnored, addServerTemplate, addTemplate, addImports, addTypeTemplate, logger, addServerHandler, addComponent } from '@nuxt/kit';
|
|
6
5
|
import { joinURL } from 'ufo';
|
|
6
|
+
import { d as discoverPdfTemplates, a as discoverPdfComponentFiles, b as discoverPdfImageFiles, c as bundlePdfFonts, e as createPdfSfcPlugin, f as classifyPdfWatchEvent } from './shared/nuxt-pdf.D0zmsct0.mjs';
|
|
7
|
+
import { pdfImageFormatFromKey, loadPdfImageAsset } from '../dist/runtime/server/assets/resolve-asset.js';
|
|
8
|
+
import { PDF_STUB_NAMES } from '../dist/runtime/components/stubs.js';
|
|
7
9
|
import { normalizeRemoteAssetPolicy } from '../dist/runtime/server/assets/remote.js';
|
|
8
|
-
import { loadPdfImageAsset } from '../dist/runtime/server/assets/resolve-asset.js';
|
|
9
10
|
import { normalizePdfLimits, DEFAULT_PDF_RENDER_LIMITS } from '../dist/runtime/server/render-limits.js';
|
|
10
11
|
import 'node:fs';
|
|
11
12
|
import '@vue/compiler-sfc';
|
|
@@ -14,6 +15,8 @@ import 'esbuild';
|
|
|
14
15
|
import 'unimport';
|
|
15
16
|
import '../dist/runtime/shared/template.js';
|
|
16
17
|
|
|
18
|
+
const version = "0.4.0-beta.2";
|
|
19
|
+
|
|
17
20
|
const compareText = (left, right) => {
|
|
18
21
|
if (left < right) return -1;
|
|
19
22
|
if (left > right) return 1;
|
|
@@ -31,14 +34,20 @@ const runtimeOptionsSource = (options) => {
|
|
|
31
34
|
const assets = options.assets ?? [];
|
|
32
35
|
const fonts = options.fonts ?? [];
|
|
33
36
|
const { remote, limits } = options;
|
|
37
|
+
for (const asset of assets) {
|
|
38
|
+
if (asset.root !== void 0 === (asset.dataB64 !== void 0)) {
|
|
39
|
+
throw new TypeError(
|
|
40
|
+
`PDF image asset "${asset.key}" must declare exactly one of root or dataB64.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
34
44
|
if (assets.length === 0 && fonts.length === 0 && !remote && !limits) return [];
|
|
35
45
|
const lines = ["", "const __pdfRuntimeOptions = Object.freeze({"];
|
|
36
46
|
if (assets.length > 0) {
|
|
37
47
|
lines.push(" assets: Object.freeze({");
|
|
38
48
|
for (const asset of assets) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
49
|
+
const entry = asset.root !== void 0 ? `{ format: ${quote$1(asset.format)}, root: ${quote$1(asset.root)} }` : `{ dataB64: ${quote$1(asset.dataB64 ?? "")}, format: ${quote$1(asset.format)} }`;
|
|
50
|
+
lines.push(` ${quote$1(asset.key)}: Object.freeze(${entry}),`);
|
|
42
51
|
}
|
|
43
52
|
lines.push(" }),");
|
|
44
53
|
}
|
|
@@ -86,9 +95,6 @@ const generatePdfRuntimeRegistry = (templates, options) => {
|
|
|
86
95
|
const lines = [
|
|
87
96
|
`import { ${runtimeImports} } from ${quote$1(options.runtimeImport)}`
|
|
88
97
|
];
|
|
89
|
-
if ((options.assets?.length ?? 0) > 0) {
|
|
90
|
-
lines.push("import { Buffer as __pdfBuffer } from 'node:buffer'");
|
|
91
|
-
}
|
|
92
98
|
ordered.forEach((template, index) => {
|
|
93
99
|
lines.push(
|
|
94
100
|
`import __pdfTemplate${index} from ${quote$1(importPath(template.filePath))}`
|
|
@@ -188,7 +194,12 @@ const existingDirectories = async (directories) => {
|
|
|
188
194
|
}
|
|
189
195
|
return result;
|
|
190
196
|
};
|
|
191
|
-
const generateAuthoringTypes = (componentsImport) => `declare
|
|
197
|
+
const generateAuthoringTypes = (componentsImport, composablesImport, definePdfImport) => `declare global {
|
|
198
|
+
const definePdf: typeof import(${quote(definePdfImport)})['definePdf']
|
|
199
|
+
const usePdfPageNumbers: typeof import(${quote(composablesImport)})['usePdfPageNumbers']
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
declare module 'vue' {
|
|
192
203
|
interface GlobalComponents {
|
|
193
204
|
PdfDocument: typeof import(${quote(componentsImport)})['PdfDocument']
|
|
194
205
|
PdfImage: typeof import(${quote(componentsImport)})['PdfImage']
|
|
@@ -219,10 +230,12 @@ export {}
|
|
|
219
230
|
`;
|
|
220
231
|
const module$1 = defineNuxtModule({
|
|
221
232
|
meta: {
|
|
222
|
-
name: "nuxt-pdf",
|
|
233
|
+
name: "@lupinum/nuxt-pdf",
|
|
234
|
+
version: version,
|
|
223
235
|
configKey: "pdf",
|
|
236
|
+
docs: "https://nuxt-pdf.lupinum.com",
|
|
224
237
|
compatibility: {
|
|
225
|
-
nuxt: "
|
|
238
|
+
nuxt: ">=4.4.8"
|
|
226
239
|
}
|
|
227
240
|
},
|
|
228
241
|
defaults: {
|
|
@@ -248,13 +261,24 @@ const module$1 = defineNuxtModule({
|
|
|
248
261
|
const limits = normalizePdfLimits(options.limits);
|
|
249
262
|
const resolvedLimits = limits ?? DEFAULT_PDF_RENDER_LIMITS;
|
|
250
263
|
const imageFiles = await discoverPdfImageFiles(layers, isIgnored);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
264
|
+
const assetEntries = [];
|
|
265
|
+
for (const image of imageFiles) {
|
|
266
|
+
const format = pdfImageFormatFromKey(image.key);
|
|
267
|
+
if (nuxt.options.dev) {
|
|
268
|
+
assetEntries.push({ format, key: image.key, root: image.rootDir });
|
|
269
|
+
} else {
|
|
270
|
+
const loaded = await loadPdfImageAsset(image.key, {
|
|
271
|
+
roots: [image.rootDir],
|
|
272
|
+
maxBytes: resolvedLimits.maxImageBytes,
|
|
273
|
+
maxPixels: resolvedLimits.maxImagePixels
|
|
274
|
+
});
|
|
275
|
+
assetEntries.push({
|
|
276
|
+
dataB64: Buffer.from(loaded.data).toString("base64"),
|
|
277
|
+
format,
|
|
278
|
+
key: image.key
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
258
282
|
const fontRoots = await existingDirectories(
|
|
259
283
|
layers.map((layer) => join(layer.rootDir, "pdfs", "fonts"))
|
|
260
284
|
);
|
|
@@ -272,7 +296,7 @@ const module$1 = defineNuxtModule({
|
|
|
272
296
|
addServerTemplate({
|
|
273
297
|
filename: "#pdf",
|
|
274
298
|
getContents: () => generatePdfRuntimeRegistry(templates, {
|
|
275
|
-
assets,
|
|
299
|
+
assets: assetEntries,
|
|
276
300
|
development: nuxt.options.dev,
|
|
277
301
|
fonts,
|
|
278
302
|
remote,
|
|
@@ -312,7 +336,9 @@ const module$1 = defineNuxtModule({
|
|
|
312
336
|
addTypeTemplate({
|
|
313
337
|
filename: "types/nuxt-pdf-authoring.d.ts",
|
|
314
338
|
getContents: () => generateAuthoringTypes(
|
|
315
|
-
componentsImport
|
|
339
|
+
componentsImport,
|
|
340
|
+
composablesImport,
|
|
341
|
+
definePdfImport
|
|
316
342
|
),
|
|
317
343
|
write: true
|
|
318
344
|
}, { nitro: true, node: true, nuxt: true });
|
|
@@ -342,6 +368,9 @@ const module$1 = defineNuxtModule({
|
|
|
342
368
|
isIgnored
|
|
343
369
|
);
|
|
344
370
|
if (action === "restart") {
|
|
371
|
+
logger.info(
|
|
372
|
+
`nuxt-pdf: ${relative(nuxt.options.rootDir, absolutePath) || absolutePath} changed; restarting to rebuild the PDF registry\u2026`
|
|
373
|
+
);
|
|
345
374
|
await nuxt.callHook("restart");
|
|
346
375
|
return;
|
|
347
376
|
}
|
|
@@ -361,6 +390,21 @@ const module$1 = defineNuxtModule({
|
|
|
361
390
|
});
|
|
362
391
|
addServerHandler({ route: "/_pdf", handler: previewHandler });
|
|
363
392
|
addServerHandler({ route: "/_pdf/**", handler: previewHandler });
|
|
393
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
394
|
+
tabs.push({
|
|
395
|
+
name: "nuxt-pdf",
|
|
396
|
+
title: "PDF",
|
|
397
|
+
icon: "i-lucide-file-text",
|
|
398
|
+
view: { type: "iframe", src: joinURL(nuxt.options.app.baseURL || "/", "_pdf") }
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
for (const name of PDF_STUB_NAMES) {
|
|
402
|
+
addComponent({
|
|
403
|
+
name,
|
|
404
|
+
export: name,
|
|
405
|
+
filePath: resolver.resolve("./runtime/components/stubs")
|
|
406
|
+
});
|
|
407
|
+
}
|
|
364
408
|
}
|
|
365
409
|
}
|
|
366
410
|
});
|
|
@@ -71,22 +71,16 @@ export type PdfImageSource = string | Uint8Array | ArrayBuffer | {
|
|
|
71
71
|
uri: string;
|
|
72
72
|
format?: 'png' | 'jpg';
|
|
73
73
|
};
|
|
74
|
-
type
|
|
74
|
+
export type PdfImageProps = PdfBaseProps & PdfBookmarkProp & {
|
|
75
|
+
/** Local bundled path, remote allowlisted URL, or inline bytes. */
|
|
75
76
|
src: PdfImageSource;
|
|
76
|
-
source?: never;
|
|
77
|
-
} | {
|
|
78
|
-
src?: never;
|
|
79
|
-
source: PdfImageSource;
|
|
80
77
|
};
|
|
81
|
-
export type
|
|
82
|
-
|
|
78
|
+
export type PdfLinkProps = PdfBaseProps & {
|
|
79
|
+
/**
|
|
80
|
+
* Link target. A string starting with `#` jumps to the element whose `id`
|
|
81
|
+
* matches the rest of the string; every other value opens as an external URL.
|
|
82
|
+
*/
|
|
83
83
|
href: string;
|
|
84
|
-
src?: never;
|
|
85
|
-
} | {
|
|
86
|
-
href?: never;
|
|
87
|
-
src: string;
|
|
88
|
-
};
|
|
89
|
-
export type PdfLinkProps = PdfBaseProps & PdfLinkTarget & {
|
|
90
84
|
wrap?: boolean;
|
|
91
85
|
hitSlop?: {
|
|
92
86
|
top?: number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineComponent } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Development-only guards for the global `Pdf*` component names. Types declare
|
|
4
|
+
* these names globally so `pdfs/*.vue` templates typecheck, which means an
|
|
5
|
+
* accidental `<PdfText>` inside an ordinary app component also typechecks —
|
|
6
|
+
* while resolving to nothing at runtime. Registering these stubs turns that
|
|
7
|
+
* silent failure into an immediate, actionable error.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PDF_STUB_NAMES: readonly ["PdfCircle", "PdfClipPath", "PdfDefs", "PdfDocument", "PdfEllipse", "PdfG", "PdfImage", "PdfLine", "PdfLinearGradient", "PdfLink", "PdfNote", "PdfPage", "PdfPath", "PdfPolygon", "PdfPolyline", "PdfRadialGradient", "PdfRect", "PdfStop", "PdfSvg", "PdfText", "PdfTspan", "PdfView"];
|
|
10
|
+
export declare const createPdfStubs: () => Record<string, ReturnType<typeof defineComponent>>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineComponent } from "vue";
|
|
2
|
+
import { NuxtPdfError, PDF_ERROR_CODES } from "../shared/errors.js";
|
|
3
|
+
export const PDF_STUB_NAMES = [
|
|
4
|
+
"PdfCircle",
|
|
5
|
+
"PdfClipPath",
|
|
6
|
+
"PdfDefs",
|
|
7
|
+
"PdfDocument",
|
|
8
|
+
"PdfEllipse",
|
|
9
|
+
"PdfG",
|
|
10
|
+
"PdfImage",
|
|
11
|
+
"PdfLine",
|
|
12
|
+
"PdfLinearGradient",
|
|
13
|
+
"PdfLink",
|
|
14
|
+
"PdfNote",
|
|
15
|
+
"PdfPage",
|
|
16
|
+
"PdfPath",
|
|
17
|
+
"PdfPolygon",
|
|
18
|
+
"PdfPolyline",
|
|
19
|
+
"PdfRadialGradient",
|
|
20
|
+
"PdfRect",
|
|
21
|
+
"PdfStop",
|
|
22
|
+
"PdfSvg",
|
|
23
|
+
"PdfText",
|
|
24
|
+
"PdfTspan",
|
|
25
|
+
"PdfView"
|
|
26
|
+
];
|
|
27
|
+
const stubMessage = (name) => `<${name}> only works inside a discovered pdfs/*.vue template rendered by @lupinum/nuxt-pdf. Move this component under pdfs/, or replace it with ordinary Vue/HTML components.`;
|
|
28
|
+
const createPdfStub = (name) => defineComponent({
|
|
29
|
+
name,
|
|
30
|
+
setup() {
|
|
31
|
+
throw new NuxtPdfError(PDF_ERROR_CODES.TemplateInvalid, stubMessage(name));
|
|
32
|
+
},
|
|
33
|
+
render: () => null
|
|
34
|
+
});
|
|
35
|
+
export const createPdfStubs = () => Object.fromEntries(
|
|
36
|
+
PDF_STUB_NAMES.map((name) => [name, createPdfStub(name)])
|
|
37
|
+
);
|
package/dist/runtime/fonts.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface PdfFontDeclaration {
|
|
|
7
7
|
fontStyle?: PdfFontStyle;
|
|
8
8
|
fontWeight?: PdfFontWeight;
|
|
9
9
|
}
|
|
10
|
-
export type PdfFontDataUrl = `data:font/${'otf' | 'ttf'};base64,${string}`;
|
|
10
|
+
export type PdfFontDataUrl = `data:font/${'otf' | 'ttf' | 'woff2'};base64,${string}`;
|
|
11
11
|
export interface BundledPdfFontDescriptor {
|
|
12
12
|
family: string;
|
|
13
13
|
src: PdfFontDataUrl;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { mountPdfComponent } from './render-component.js';
|
|
2
|
+
export { PDF_PRIMITIVE_NAMES } from './types.js';
|
|
2
3
|
export type { MountedPdfComponent, PdfComponentProps, } from './render-component.js';
|
|
3
4
|
export type { PdfDocumentNode, PdfElementNode, PdfNode, PdfRoot, PdfTextInstance, } from './types.js';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { RendererOptions } from 'vue';
|
|
2
|
-
import { type PdfHostElement, type PdfHostNode } from './types.js';
|
|
2
|
+
import { type PdfElementType, type PdfHostElement, type PdfHostNode } from './types.js';
|
|
3
|
+
/** Closed runtime prop surface; TypeScript alone cannot reject Vue attrs. */
|
|
4
|
+
export declare const PDF_PROP_KEYS: Record<PdfElementType, ReadonlySet<string>>;
|
|
3
5
|
export declare const patchPdfProp: RendererOptions<PdfHostNode, PdfHostElement>['patchProp'];
|
|
@@ -26,7 +26,7 @@ const SVG_PRESENTATION_PROP_NAMES = [
|
|
|
26
26
|
"strokeWidth",
|
|
27
27
|
"transform"
|
|
28
28
|
];
|
|
29
|
-
const PDF_PROP_KEYS = {
|
|
29
|
+
export const PDF_PROP_KEYS = {
|
|
30
30
|
[PDF_PRIMITIVES.Document]: exactPropNames()([
|
|
31
31
|
"author",
|
|
32
32
|
"creationDate",
|
|
@@ -67,14 +67,12 @@ const PDF_PROP_KEYS = {
|
|
|
67
67
|
[PDF_PRIMITIVES.Image]: exactPropNames()([
|
|
68
68
|
...BASE_PROP_NAMES,
|
|
69
69
|
...BOOKMARK_PROP_NAMES,
|
|
70
|
-
"source",
|
|
71
70
|
"src"
|
|
72
71
|
]),
|
|
73
72
|
[PDF_PRIMITIVES.Link]: exactPropNames()([
|
|
74
73
|
...BASE_PROP_NAMES,
|
|
75
74
|
"hitSlop",
|
|
76
75
|
"href",
|
|
77
|
-
"src",
|
|
78
76
|
"wrap"
|
|
79
77
|
]),
|
|
80
78
|
[PDF_PRIMITIVES.Note]: exactPropNames()(BASE_PROP_NAMES),
|
|
@@ -256,18 +256,15 @@ const validatePage = (node) => {
|
|
|
256
256
|
};
|
|
257
257
|
const validateLinkAndImageSources = (node) => {
|
|
258
258
|
if (node.type === PDF_PRIMITIVES.Image) {
|
|
259
|
-
if (hasProp(node, "src")
|
|
260
|
-
treeInvalid('<PdfImage> requires
|
|
259
|
+
if (!hasProp(node, "src")) {
|
|
260
|
+
treeInvalid('<PdfImage> requires a "src" prop.');
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
if (node.type === PDF_PRIMITIVES.Link) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (hasHref === hasSrc) {
|
|
267
|
-
treeInvalid('<PdfLink> requires exactly one of "href" or "src".');
|
|
264
|
+
if (!hasProp(node, "href")) {
|
|
265
|
+
treeInvalid('<PdfLink> requires an "href" prop.');
|
|
268
266
|
}
|
|
269
|
-
|
|
270
|
-
if (typeof target !== "string" || target.trim() === "") {
|
|
267
|
+
if (typeof node.props.href !== "string" || node.props.href.trim() === "") {
|
|
271
268
|
treeInvalid("<PdfLink> targets must be non-empty strings.");
|
|
272
269
|
}
|
|
273
270
|
}
|
|
@@ -4,14 +4,23 @@ import { type RemoteAssetPolicy, type RemoteRequestState } from './remote.js';
|
|
|
4
4
|
import { type RenderLimits } from '../render-limits.js';
|
|
5
5
|
export { PDF_ASSET_ERROR_CODES, PdfAssetError, type PdfAssetErrorCode, } from './errors.js';
|
|
6
6
|
export type PdfImageFormat = 'jpg' | 'png';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* One discovered local image. In development the entry points at the source
|
|
9
|
+
* file on disk so edits show up without a restart; in production the validated
|
|
10
|
+
* bytes travel as base64 inside the server bundle so every Nitro preset stays
|
|
11
|
+
* self-contained. Both forms resolve through the same validation boundary.
|
|
12
|
+
*/
|
|
13
|
+
export type PdfImageAssetEntry = Readonly<{
|
|
14
|
+
format: PdfImageFormat;
|
|
15
|
+
root: string;
|
|
16
|
+
} | {
|
|
17
|
+
dataB64: string;
|
|
9
18
|
format: PdfImageFormat;
|
|
10
19
|
}>;
|
|
11
|
-
export type
|
|
20
|
+
export type PdfImageAssetMap = Readonly<Record<string, PdfImageAssetEntry>>;
|
|
21
|
+
export type LoadedPdfImageAsset = Readonly<{
|
|
12
22
|
key: string;
|
|
13
|
-
}
|
|
14
|
-
export type PdfImageAssetMap = Readonly<Record<string, PdfImageAsset>>;
|
|
23
|
+
}> & ResolvedPdfImageAsset;
|
|
15
24
|
export interface LoadPdfImageAssetOptions {
|
|
16
25
|
roots: readonly string[];
|
|
17
26
|
maxBytes?: number;
|
|
@@ -28,6 +37,13 @@ export interface ResolvePdfImageAssetsOptions {
|
|
|
28
37
|
*/
|
|
29
38
|
state?: PdfImageResolutionState;
|
|
30
39
|
}
|
|
40
|
+
type ResolvedPdfImageAsset = Readonly<{
|
|
41
|
+
data: Buffer;
|
|
42
|
+
format: PdfImageFormat;
|
|
43
|
+
height: number;
|
|
44
|
+
pixels: number;
|
|
45
|
+
width: number;
|
|
46
|
+
}>;
|
|
31
47
|
type ImageBudgetState = {
|
|
32
48
|
bytes: number;
|
|
33
49
|
pixels: number;
|
|
@@ -39,6 +55,8 @@ export interface PdfImageResolutionState {
|
|
|
39
55
|
readonly remote: RemoteRequestState;
|
|
40
56
|
readonly resolved: ImageResolutionCache;
|
|
41
57
|
}
|
|
58
|
+
/** Format for a discovered asset key, validated at module setup time. */
|
|
59
|
+
export declare const pdfImageFormatFromKey: (key: string) => PdfImageFormat;
|
|
42
60
|
/**
|
|
43
61
|
* Loads and validates one local image while the Nuxt module still has access to
|
|
44
62
|
* source files. Generated server code should store the returned bytes under
|