@lupinum/nuxt-pdf 0.4.0-beta.1 → 0.4.0-beta.3
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 +36 -0
- package/CHANGELOG.md +35 -0
- package/CONFORMANCE.md +18 -5
- package/dist/build.d.mts +18 -0
- package/dist/build.mjs +220 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -218
- 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/server/engine/fonts.js +1 -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 +9 -1
- package/dist/runtime/server/registry.d.ts +1 -1
- package/dist/runtime/server/registry.js +9 -1
- 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/server.d.mts +4 -0
- package/dist/server.mjs +3 -0
- package/dist/shared/nuxt-pdf.D3hUPqOJ.mjs +203 -0
- package/dist/shared/{nuxt-pdf._V584vX4.mjs → nuxt-pdf.DMC_Rdsz.mjs} +59 -10
- package/dist/test.mjs +1 -1
- package/package.json +17 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import FontStore from "@react-pdf/font";
|
|
2
|
-
const isBundledFontSource = (src) => /^data:font\/(?:otf|ttf);base64,[A-Za-z0-9+/]+={0,2}$/.test(src);
|
|
2
|
+
const isBundledFontSource = (src) => /^data:font\/(?:otf|ttf|woff2);base64,[A-Za-z0-9+/]+={0,2}$/.test(src);
|
|
3
3
|
export const createPdfFontStore = (fonts = []) => {
|
|
4
4
|
const fontStore = new FontStore();
|
|
5
5
|
for (const font of fonts) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DocumentNode, type SafeDocumentNode } from '@react-pdf/layout';
|
|
2
2
|
import { type PdfFontStore } from './fonts.js';
|
|
3
|
+
import type { PdfLayoutWarning } from '../../shared/template.js';
|
|
3
4
|
import { type RenderLimits } from '../render-limits.js';
|
|
4
5
|
export interface PdfEngineOptions {
|
|
5
6
|
compress?: boolean;
|
|
@@ -16,6 +17,12 @@ type DocumentMetadata = DocumentNode['props'];
|
|
|
16
17
|
export declare const layoutPdfTree: (document: DocumentNode, fontStore: PdfFontStore, limits?: RenderLimits) => Promise<SafeDocumentNode>;
|
|
17
18
|
/** The number of laid-out pages in a serialized document. */
|
|
18
19
|
export declare const countPages: (layout: SafeDocumentNode) => number;
|
|
20
|
+
/**
|
|
21
|
+
* Find blocks that cannot fit on any ordinary page because pagination is not
|
|
22
|
+
* allowed to split them. No text, ids, or other authored content enters the
|
|
23
|
+
* public diagnostics object.
|
|
24
|
+
*/
|
|
25
|
+
export declare const collectLayoutWarnings: (layout: SafeDocumentNode) => PdfLayoutWarning[];
|
|
19
26
|
/**
|
|
20
27
|
* Map every `id` (named destination) to the **first** 1-based page it appears
|
|
21
28
|
* on. `id` is the key `render/src/operations/setDestination.ts` emits and that a
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
PDF_PRIMITIVES
|
|
14
14
|
} from "../../authoring.js";
|
|
15
|
+
import { PDF_PRIMITIVE_NAMES } from "../../renderer/index.js";
|
|
15
16
|
import {
|
|
16
17
|
enforceMaxPages
|
|
17
18
|
} from "../render-limits.js";
|
|
@@ -117,6 +118,52 @@ const visitPageNodes = (page, visit) => {
|
|
|
117
118
|
};
|
|
118
119
|
const documentPages = (layout) => layout.children ?? [];
|
|
119
120
|
export const countPages = (layout) => documentPages(layout).length;
|
|
121
|
+
const NON_WRAPPING_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
122
|
+
PDF_PRIMITIVES.Image,
|
|
123
|
+
PDF_PRIMITIVES.Note,
|
|
124
|
+
PDF_PRIMITIVES.Svg
|
|
125
|
+
]);
|
|
126
|
+
const WARNING_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
127
|
+
PDF_PRIMITIVES.Image,
|
|
128
|
+
PDF_PRIMITIVES.Link,
|
|
129
|
+
PDF_PRIMITIVES.Note,
|
|
130
|
+
PDF_PRIMITIVES.Svg,
|
|
131
|
+
PDF_PRIMITIVES.Text,
|
|
132
|
+
PDF_PRIMITIVES.View
|
|
133
|
+
]);
|
|
134
|
+
const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
135
|
+
const roundedPoints = (value) => Math.round(value * 100) / 100;
|
|
136
|
+
const pageContentHeight = (page) => {
|
|
137
|
+
const style = page.style;
|
|
138
|
+
const pageHeight = finiteNumber(style?.height) ?? finiteNumber(page.box.height);
|
|
139
|
+
if (pageHeight === void 0) return void 0;
|
|
140
|
+
const paddingTop = finiteNumber(page.box.paddingTop) ?? finiteNumber(style?.paddingTop) ?? 0;
|
|
141
|
+
const paddingBottom = finiteNumber(page.box.paddingBottom) ?? finiteNumber(style?.paddingBottom) ?? 0;
|
|
142
|
+
return pageHeight - paddingTop - paddingBottom;
|
|
143
|
+
};
|
|
144
|
+
const cannotWrap = (node) => NON_WRAPPING_NODE_TYPES.has(node.type) || node.props.wrap === false;
|
|
145
|
+
export const collectLayoutWarnings = (layout) => {
|
|
146
|
+
const warnings = [];
|
|
147
|
+
documentPages(layout).forEach((pageNode, pageIndex) => {
|
|
148
|
+
if (!("props" in pageNode)) return;
|
|
149
|
+
const page = pageNode;
|
|
150
|
+
const availableHeight = pageContentHeight(page);
|
|
151
|
+
if (availableHeight === void 0 || availableHeight <= 0) return;
|
|
152
|
+
visitPageNodes(page, (node) => {
|
|
153
|
+
if (node === page || node.props.fixed === true || !WARNING_NODE_TYPES.has(node.type) || !cannotWrap(node)) return;
|
|
154
|
+
const nodeHeight = finiteNumber(node.box.height);
|
|
155
|
+
if (nodeHeight === void 0 || nodeHeight <= availableHeight + 1e-3) return;
|
|
156
|
+
warnings.push({
|
|
157
|
+
code: "PDF_UNBREAKABLE_NODE_OVERFLOW",
|
|
158
|
+
pageNumber: pageIndex + 1,
|
|
159
|
+
nodeType: PDF_PRIMITIVE_NAMES[node.type],
|
|
160
|
+
nodeHeight: roundedPoints(nodeHeight),
|
|
161
|
+
availableHeight: roundedPoints(availableHeight)
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
return warnings;
|
|
166
|
+
};
|
|
120
167
|
const nodeId = (node) => {
|
|
121
168
|
const id = node.props?.id;
|
|
122
169
|
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
@@ -2,4 +2,4 @@ export { createPdfPreviewEntry, createPdfRegistry, createPdfTemplate, } from './
|
|
|
2
2
|
export { NuxtPdfError, PDF_ERROR_CODES, } from '../shared/errors.js';
|
|
3
3
|
export type { PdfErrorCode } from '../shared/errors.js';
|
|
4
4
|
export type { PdfPreviewEntry, PdfPreviewEntryOptions, PdfRegistry, PdfRegistryEntries, } from './registry.js';
|
|
5
|
-
export type { PdfRenderDiagnostics, PdfRenderResult, PdfComponentProps, PdfTemplate, } from '../shared/template.js';
|
|
5
|
+
export type { PdfLayoutWarning, PdfRenderDiagnostics, PdfRenderResult, PdfComponentProps, PdfTemplate, } from '../shared/template.js';
|
|
@@ -99,6 +99,10 @@ const htmlResponse = (title, content, status = 200) => new Response(`<!doctype h
|
|
|
99
99
|
.font-faces .label { color: #8b948b; font-size: 0.78rem; letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 8px; }
|
|
100
100
|
.font-faces ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
|
|
101
101
|
.font-faces li { color: #d7ddd7; font-size: 0.85rem; line-height: 1.5; }
|
|
102
|
+
.layout-warnings { border: 1px solid #765b23; background: #211b0f; border-left: 3px solid #e2b653; border-radius: 10px; padding: 12px 16px; margin-bottom: 16px; }
|
|
103
|
+
.layout-warnings .label { color: #e8c977; font-size: 0.78rem; letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 8px; }
|
|
104
|
+
.layout-warnings ul { margin: 0; padding-left: 20px; display: grid; gap: 6px; }
|
|
105
|
+
.layout-warnings li { color: #f0dfb2; font-size: 0.85rem; line-height: 1.5; }
|
|
102
106
|
.error { border: 1px solid #6b2f2b; background: #1d1210; border-left: 3px solid #ef786f; border-radius: 10px; padding: 18px 20px; }
|
|
103
107
|
.error h2 { margin: 0 0 12px; font-size: 1.1rem; color: #f3d7d3; }
|
|
104
108
|
.error dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0 0 12px; }
|
|
@@ -173,13 +177,17 @@ const diagnosticsPanel = (diagnostics) => {
|
|
|
173
177
|
stat("Size", formatBytes(diagnostics.byteLength)),
|
|
174
178
|
stat("Pages", String(diagnostics.pageCount)),
|
|
175
179
|
stat("Layout passes", String(diagnostics.passes)),
|
|
180
|
+
stat("Layout warnings", String(diagnostics.layoutWarnings.length)),
|
|
176
181
|
stat("Font faces", String(diagnostics.registeredFontFaces.length))
|
|
177
182
|
].join("");
|
|
183
|
+
const warnings = diagnostics.layoutWarnings.length > 0 ? `<div class="layout-warnings"><div class="label">Layout warnings</div><ul>${diagnostics.layoutWarnings.map(
|
|
184
|
+
(warning) => `<li><strong>${escapeHtml(warning.nodeType)}</strong> on page ${warning.pageNumber} is ${warning.nodeHeight} pt tall, but only ${warning.availableHeight} pt is available. Allow it to wrap or make it smaller.</li>`
|
|
185
|
+
).join("")}</ul></div>` : "";
|
|
178
186
|
const fonts = diagnostics.registeredFontFaces.length > 0 ? `<div class="font-faces"><div class="label">Registered font faces</div><ul>${diagnostics.registeredFontFaces.map((face) => {
|
|
179
187
|
const attributes = [face.fontWeight, face.fontStyle].filter((value) => value !== void 0);
|
|
180
188
|
return `<li>${escapeHtml(face.family)}${attributes.length > 0 ? ` \u2014 ${escapeHtml(attributes.join(" "))}` : ""}</li>`;
|
|
181
189
|
}).join("")}</ul></div>` : "";
|
|
182
|
-
return `<div class="diagnostics">${stats}</div>${fonts}`;
|
|
190
|
+
return `<div class="diagnostics">${stats}</div>${warnings}${fonts}`;
|
|
183
191
|
};
|
|
184
192
|
const errorDetails = (error, fallbackKey, fallbackFile) => {
|
|
185
193
|
const code = error instanceof NuxtPdfError ? error.code : "PDF_RENDER_ERROR";
|
|
@@ -4,7 +4,7 @@ import { type PdfImageAssetMap } from './assets/resolve-asset.js';
|
|
|
4
4
|
import type { RemoteAssetPolicy } from './assets/remote.js';
|
|
5
5
|
import { type PdfRenderLimits } from './render-limits.js';
|
|
6
6
|
import type { BundledPdfFontDescriptor } from '../fonts.js';
|
|
7
|
-
export type { PdfRenderDiagnostics } from '../shared/template.js';
|
|
7
|
+
export type { PdfLayoutWarning, PdfRenderDiagnostics, } from '../shared/template.js';
|
|
8
8
|
export interface PdfTemplateRuntimeOptions {
|
|
9
9
|
assets?: PdfImageAssetMap;
|
|
10
10
|
file?: string;
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
createPdfImageResolutionState,
|
|
11
11
|
resolvePdfImageAssets
|
|
12
12
|
} from "./assets/resolve-asset.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
collectLayoutWarnings,
|
|
15
|
+
countPages,
|
|
16
|
+
renderDocument
|
|
17
|
+
} from "./engine/render-document.js";
|
|
14
18
|
import { renderDocumentMultiPass } from "./engine/layout-passes.js";
|
|
15
19
|
import {
|
|
16
20
|
createRenderLimits,
|
|
@@ -140,6 +144,7 @@ const renderTemplate = async (component, props, metadata, options, maxPasses, li
|
|
|
140
144
|
);
|
|
141
145
|
return {
|
|
142
146
|
bytes: result2.bytes,
|
|
147
|
+
layoutWarnings: collectLayoutWarnings(result2.layout),
|
|
143
148
|
metadata: completedMetadata(live.document, metadata),
|
|
144
149
|
passes: result2.passes,
|
|
145
150
|
pageCount: countPages(result2.layout)
|
|
@@ -150,6 +155,7 @@ const renderTemplate = async (component, props, metadata, options, maxPasses, li
|
|
|
150
155
|
const result = await renderDocument(document, { fontStore, limits });
|
|
151
156
|
return {
|
|
152
157
|
bytes: result.bytes,
|
|
158
|
+
layoutWarnings: collectLayoutWarnings(result.layout),
|
|
153
159
|
metadata: completedMetadata(mounted.document, metadata),
|
|
154
160
|
passes: 1,
|
|
155
161
|
pageCount: countPages(result.layout)
|
|
@@ -176,6 +182,7 @@ export const createPdfTemplate = (key, component, options = {}) => {
|
|
|
176
182
|
const definitionMetadata = resolveMetadata(ref, definition, props);
|
|
177
183
|
const {
|
|
178
184
|
bytes,
|
|
185
|
+
layoutWarnings,
|
|
179
186
|
metadata,
|
|
180
187
|
passes,
|
|
181
188
|
pageCount
|
|
@@ -189,6 +196,7 @@ export const createPdfTemplate = (key, component, options = {}) => {
|
|
|
189
196
|
);
|
|
190
197
|
const result = createPdfRenderResult(bytes, metadata, {
|
|
191
198
|
durationMs: performance.now() - start,
|
|
199
|
+
layoutWarnings,
|
|
192
200
|
pageCount,
|
|
193
201
|
passes,
|
|
194
202
|
registeredFontFaces: (options.fonts ?? []).map((font) => ({
|
|
@@ -43,6 +43,7 @@ export const createPdfRenderResult = (source, metadata, diagnostics) => {
|
|
|
43
43
|
const completedDiagnostics = Object.freeze({
|
|
44
44
|
byteLength: bytes.byteLength,
|
|
45
45
|
durationMs: diagnostics.durationMs,
|
|
46
|
+
layoutWarnings: Object.freeze(diagnostics.layoutWarnings.map((warning) => Object.freeze({ ...warning }))),
|
|
46
47
|
pageCount: diagnostics.pageCount,
|
|
47
48
|
passes: diagnostics.passes,
|
|
48
49
|
registeredFontFaces: Object.freeze(diagnostics.registeredFontFaces.map((face) => Object.freeze({ ...face })))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { NuxtPdfError, PDF_ERROR_CODES, } from './errors.js';
|
|
2
2
|
export type { NuxtPdfErrorOptions, PdfErrorCode, } from './errors.js';
|
|
3
3
|
export { PDF_DEFINITION_PROPERTY, } from './template.js';
|
|
4
|
-
export type { PdfComponent, PdfComponentProps, PdfDefinition, PdfDisposition, PdfMetadataValue, PdfRenderDiagnostics, PdfRenderResult, PdfResponseInit, PdfTemplate, ResolvedPdfMetadata, } from './template.js';
|
|
4
|
+
export type { PdfComponent, PdfComponentProps, PdfDefinition, PdfDisposition, PdfLayoutWarning, PdfMetadataValue, PdfRenderDiagnostics, PdfRenderResult, PdfResponseInit, PdfTemplate, ResolvedPdfMetadata, } from './template.js';
|
|
@@ -41,8 +41,16 @@ export interface PdfRenderDiagnostics {
|
|
|
41
41
|
readonly byteLength: number;
|
|
42
42
|
readonly pageCount: number;
|
|
43
43
|
readonly passes: number;
|
|
44
|
+
readonly layoutWarnings: readonly PdfLayoutWarning[];
|
|
44
45
|
readonly registeredFontFaces: readonly PdfRegisteredFontFace[];
|
|
45
46
|
}
|
|
47
|
+
export interface PdfLayoutWarning {
|
|
48
|
+
readonly code: 'PDF_UNBREAKABLE_NODE_OVERFLOW';
|
|
49
|
+
readonly pageNumber: number;
|
|
50
|
+
readonly nodeType: 'PdfImage' | 'PdfLink' | 'PdfNote' | 'PdfSvg' | 'PdfText' | 'PdfView';
|
|
51
|
+
readonly nodeHeight: number;
|
|
52
|
+
readonly availableHeight: number;
|
|
53
|
+
}
|
|
46
54
|
export interface PdfRegisteredFontFace {
|
|
47
55
|
readonly family: string;
|
|
48
56
|
readonly fontStyle?: string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { PdfRegistry, PdfRegistryEntries, createPdfRegistry, createPdfTemplate } from '../dist/runtime/server/registry.js';
|
|
2
|
+
export { NuxtPdfError, PDF_ERROR_CODES, PdfErrorCode } from '../dist/runtime/shared/errors.js';
|
|
3
|
+
export { usePdfPageNumbers } from '../dist/runtime/composables/index.js';
|
|
4
|
+
export { PdfComponentProps, PdfDefinition, PdfRenderResult, PdfTemplate } from '../dist/runtime/shared/template.js';
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { PDF_STUB_NAMES } from '../../dist/runtime/components/stubs.js';
|
|
2
|
+
import { joinURL } from 'ufo';
|
|
3
|
+
import { Buffer } from 'node:buffer';
|
|
4
|
+
import { pdfImageFormatFromKey, loadPdfImageAsset } from '../../dist/runtime/server/assets/resolve-asset.js';
|
|
5
|
+
|
|
6
|
+
const generateAuthoringTypes = (componentsImport, composablesImport, definePdfImport) => `declare global {
|
|
7
|
+
const definePdf: typeof import(${JSON.stringify(definePdfImport)})['definePdf']
|
|
8
|
+
const usePdfPageNumbers: typeof import(${JSON.stringify(composablesImport)})['usePdfPageNumbers']
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module 'vue' {
|
|
12
|
+
interface GlobalComponents {
|
|
13
|
+
${PDF_STUB_NAMES.map((name) => ` ${name}: typeof import(${JSON.stringify(componentsImport)})['${name}']`).join("\n")}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const compareText = (left, right) => {
|
|
21
|
+
if (left < right) return -1;
|
|
22
|
+
if (left > right) return 1;
|
|
23
|
+
return 0;
|
|
24
|
+
};
|
|
25
|
+
const quote = (value) => JSON.stringify(value);
|
|
26
|
+
const generatePdfPreviewConfig = (baseURL, buildAssetsDir) => `export const hmrClientPath = ${quote(joinURL(
|
|
27
|
+
baseURL.replace(/^\.\//, "/") || "/",
|
|
28
|
+
buildAssetsDir,
|
|
29
|
+
"@vite/client"
|
|
30
|
+
))}
|
|
31
|
+
`;
|
|
32
|
+
const importPath = (filePath) => filePath.replaceAll("\\", "/");
|
|
33
|
+
const runtimeOptionsSource = (options) => {
|
|
34
|
+
const assets = options.assets ?? [];
|
|
35
|
+
const fonts = options.fonts ?? [];
|
|
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
|
+
}
|
|
44
|
+
if (assets.length === 0 && fonts.length === 0 && !remote && !limits) return [];
|
|
45
|
+
const lines = ["", "const __pdfRuntimeOptions = Object.freeze({"];
|
|
46
|
+
if (assets.length > 0) {
|
|
47
|
+
lines.push(" assets: Object.freeze({");
|
|
48
|
+
for (const asset of assets) {
|
|
49
|
+
const entry = asset.root !== void 0 ? `{ format: ${quote(asset.format)}, root: ${quote(asset.root)} }` : `{ dataB64: ${quote(asset.dataB64 ?? "")}, format: ${quote(asset.format)} }`;
|
|
50
|
+
lines.push(` ${quote(asset.key)}: Object.freeze(${entry}),`);
|
|
51
|
+
}
|
|
52
|
+
lines.push(" }),");
|
|
53
|
+
}
|
|
54
|
+
if (fonts.length > 0) {
|
|
55
|
+
lines.push(" fonts: Object.freeze([");
|
|
56
|
+
for (const font of fonts) {
|
|
57
|
+
lines.push(` Object.freeze(${JSON.stringify(font)}),`);
|
|
58
|
+
}
|
|
59
|
+
lines.push(" ]),");
|
|
60
|
+
}
|
|
61
|
+
if (remote) {
|
|
62
|
+
lines.push(` remote: Object.freeze(${JSON.stringify(remote)}),`);
|
|
63
|
+
}
|
|
64
|
+
if (limits) {
|
|
65
|
+
lines.push(` limits: Object.freeze(${JSON.stringify(limits)}),`);
|
|
66
|
+
}
|
|
67
|
+
lines.push("})");
|
|
68
|
+
return lines;
|
|
69
|
+
};
|
|
70
|
+
const orderedTemplates = (templates) => {
|
|
71
|
+
const result = [...templates].sort(
|
|
72
|
+
(left, right) => compareText(left.key, right.key)
|
|
73
|
+
);
|
|
74
|
+
const keys = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const template of result) {
|
|
76
|
+
const duplicate = keys.get(template.key);
|
|
77
|
+
if (duplicate) {
|
|
78
|
+
throw new TypeError(
|
|
79
|
+
`Cannot generate duplicate PDF template key "${template.key}" from "${duplicate.filePath}" and "${template.filePath}".`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
keys.set(template.key, template);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
const validateOptions = (options) => {
|
|
87
|
+
if (options.runtimeImport.trim() === "") {
|
|
88
|
+
throw new TypeError("runtimeImport is required to generate the PDF registry.");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const generatePdfRuntimeRegistry = (templates, options) => {
|
|
92
|
+
validateOptions(options);
|
|
93
|
+
const ordered = orderedTemplates(templates);
|
|
94
|
+
const runtimeImports = options.development ? "createPdfPreviewEntry, createPdfRegistry, createPdfTemplate" : "createPdfRegistry, createPdfTemplate";
|
|
95
|
+
const lines = [
|
|
96
|
+
`import { ${runtimeImports} } from ${quote(options.runtimeImport)}`
|
|
97
|
+
];
|
|
98
|
+
ordered.forEach((template, index) => {
|
|
99
|
+
lines.push(
|
|
100
|
+
`import __pdfTemplate${index} from ${quote(importPath(template.filePath))}`
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
const runtimeOptions = runtimeOptionsSource(options);
|
|
104
|
+
lines.push(...runtimeOptions, "", "const registry = createPdfRegistry({");
|
|
105
|
+
ordered.forEach((template, index) => {
|
|
106
|
+
const file = quote(`pdfs/${template.relativePath}`);
|
|
107
|
+
const runtimeArgument = options.development ? runtimeOptions.length > 0 ? `, { ...__pdfRuntimeOptions, file: ${file} }` : `, { file: ${file} }` : runtimeOptions.length > 0 ? ", __pdfRuntimeOptions" : "";
|
|
108
|
+
lines.push(
|
|
109
|
+
` ${quote(template.key)}: createPdfTemplate(${quote(template.key)}, __pdfTemplate${index}${runtimeArgument}),`
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
lines.push("})");
|
|
113
|
+
if (options.development) {
|
|
114
|
+
lines.push("", "export const pdfPreview = Object.freeze({");
|
|
115
|
+
ordered.forEach((template, index) => {
|
|
116
|
+
lines.push(
|
|
117
|
+
` ${quote(template.key)}: createPdfPreviewEntry(registry.pdf[${quote(template.key)}], __pdfTemplate${index}, { file: ${quote(`pdfs/${template.relativePath}`)} }),`
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
lines.push("})");
|
|
121
|
+
}
|
|
122
|
+
lines.push(
|
|
123
|
+
"",
|
|
124
|
+
"export const pdf = registry.pdf",
|
|
125
|
+
"export const renderPdf = registry.renderPdf",
|
|
126
|
+
"export const getPdfTemplate = registry.getPdfTemplate",
|
|
127
|
+
"export const pdfTemplateKeys = registry.pdfTemplateKeys",
|
|
128
|
+
`export { NuxtPdfError, PDF_ERROR_CODES } from ${quote(options.runtimeImport)}`,
|
|
129
|
+
""
|
|
130
|
+
);
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
};
|
|
133
|
+
const generatePdfRegistryTypes = (templates, options) => {
|
|
134
|
+
validateOptions(options);
|
|
135
|
+
const ordered = orderedTemplates(templates);
|
|
136
|
+
const lines = [
|
|
137
|
+
`import type { PdfComponentProps, PdfRenderResult, PdfTemplate } from ${quote(options.runtimeImport)}`
|
|
138
|
+
];
|
|
139
|
+
ordered.forEach((template, index) => {
|
|
140
|
+
lines.push(
|
|
141
|
+
"",
|
|
142
|
+
`type PdfComponent${index} = typeof import(${quote(importPath(template.filePath))})['default']`,
|
|
143
|
+
`type PdfProps${index} = PdfComponentProps<PdfComponent${index}>`
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
lines.push("", "export declare const pdf: {");
|
|
147
|
+
ordered.forEach((template, index) => {
|
|
148
|
+
lines.push(
|
|
149
|
+
` readonly ${quote(template.key)}: PdfTemplate<PdfProps${index}>`
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
lines.push("}", "");
|
|
153
|
+
if (ordered.length === 0) {
|
|
154
|
+
lines.push(
|
|
155
|
+
"export declare function renderPdf(name: never, props: never): Promise<PdfRenderResult>",
|
|
156
|
+
"export declare function getPdfTemplate(name: never): never"
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
ordered.forEach((template, index) => {
|
|
160
|
+
lines.push(
|
|
161
|
+
`export declare function renderPdf(name: ${quote(template.key)}, props: PdfProps${index}): Promise<PdfRenderResult>`
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
lines.push(
|
|
165
|
+
"export declare function renderPdf(name: string, props: Record<string, unknown>, escapeHatch: { readonly unsafe: true }): Promise<PdfRenderResult>"
|
|
166
|
+
);
|
|
167
|
+
lines.push("");
|
|
168
|
+
ordered.forEach((template, index) => {
|
|
169
|
+
lines.push(
|
|
170
|
+
`export declare function getPdfTemplate(name: ${quote(template.key)}): PdfTemplate<PdfProps${index}>`
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
lines.push(
|
|
175
|
+
"",
|
|
176
|
+
`export declare const pdfTemplateKeys: readonly [${ordered.map((template) => quote(template.key)).join(", ")}]`,
|
|
177
|
+
"export type PdfTemplateKey = typeof pdfTemplateKeys[number]",
|
|
178
|
+
`export { NuxtPdfError, PDF_ERROR_CODES } from ${quote(options.runtimeImport)}`,
|
|
179
|
+
`export type { PdfErrorCode } from ${quote(options.runtimeImport)}`,
|
|
180
|
+
""
|
|
181
|
+
);
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
async function preparePdfImageAssets(images, limits, development) {
|
|
186
|
+
const entries = [];
|
|
187
|
+
for (const image of images) {
|
|
188
|
+
const format = pdfImageFormatFromKey(image.key);
|
|
189
|
+
if (development) {
|
|
190
|
+
entries.push({ format, key: image.key, root: image.rootDir });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const loaded = await loadPdfImageAsset(image.key, {
|
|
194
|
+
roots: [image.rootDir],
|
|
195
|
+
maxBytes: limits.maxImageBytes,
|
|
196
|
+
maxPixels: limits.maxImagePixels
|
|
197
|
+
});
|
|
198
|
+
entries.push({ dataB64: Buffer.from(loaded.data).toString("base64"), format, key: image.key });
|
|
199
|
+
}
|
|
200
|
+
return entries;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export { generateAuthoringTypes as a, generatePdfRegistryTypes as b, generatePdfPreviewConfig as c, generatePdfRuntimeRegistry as g, preparePdfImageAssets as p };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, realpath, open, stat, readFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve, join, relative, isAbsolute, posix, sep, dirname, extname, win32 } from 'node:path';
|
|
3
3
|
import { Buffer } from 'node:buffer';
|
|
4
|
-
import { existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { parse, compileScript, compileTemplate, babelParse } from '@vue/compiler-sfc';
|
|
6
6
|
import remapping from '@jridgewell/remapping';
|
|
7
7
|
import { transform } from 'esbuild';
|
|
@@ -185,6 +185,7 @@ const discoverPdfImageFiles = async (layers, isIgnored) => {
|
|
|
185
185
|
};
|
|
186
186
|
|
|
187
187
|
const DEFAULT_MAX_PDF_FONT_BYTES = 5 * 1024 * 1024;
|
|
188
|
+
const DEFAULT_MAX_PDF_DECOMPRESSED_FONT_BYTES = 20 * 1024 * 1024;
|
|
188
189
|
const FONT_WEIGHT_VALUES = {
|
|
189
190
|
black: 900,
|
|
190
191
|
bold: 700,
|
|
@@ -347,13 +348,53 @@ const validateDeclaration = (declaration) => {
|
|
|
347
348
|
};
|
|
348
349
|
};
|
|
349
350
|
const detectFontFormat = (bytes) => {
|
|
350
|
-
if (bytes.byteLength <
|
|
351
|
+
if (bytes.byteLength < 4) return void 0;
|
|
351
352
|
const isTrueType = bytes[0] === 0 && bytes[1] === 1 && bytes[2] === 0 && bytes[3] === 0;
|
|
352
353
|
const isOpenType = bytes[0] === 79 && bytes[1] === 84 && bytes[2] === 84 && bytes[3] === 79;
|
|
354
|
+
const isWoff2 = bytes[0] === 119 && bytes[1] === 79 && bytes[2] === 70 && bytes[3] === 50;
|
|
353
355
|
if (isTrueType) return "ttf";
|
|
354
356
|
if (isOpenType) return "otf";
|
|
357
|
+
if (isWoff2) return "woff2";
|
|
355
358
|
return void 0;
|
|
356
359
|
};
|
|
360
|
+
const validateWoff2Structure = (source, bytes) => {
|
|
361
|
+
if (bytes.byteLength < 48) {
|
|
362
|
+
throw fontError(source, "the WOFF2 header is corrupt or truncated.");
|
|
363
|
+
}
|
|
364
|
+
const data = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
365
|
+
const flavor = data.readUInt32BE(4);
|
|
366
|
+
const declaredLength = data.readUInt32BE(8);
|
|
367
|
+
const tableCount = data.readUInt16BE(12);
|
|
368
|
+
const reserved = data.readUInt16BE(14);
|
|
369
|
+
const decompressedSize = data.readUInt32BE(16);
|
|
370
|
+
const compressedSize = data.readUInt32BE(20);
|
|
371
|
+
const isTrueType = flavor === 65536;
|
|
372
|
+
const isOpenType = flavor === 1330926671;
|
|
373
|
+
if (!isTrueType && !isOpenType) {
|
|
374
|
+
throw fontError(source, "the WOFF2 font must wrap a TTF or OTF font.");
|
|
375
|
+
}
|
|
376
|
+
if (declaredLength !== bytes.byteLength || tableCount < 1 || tableCount > 4096 || reserved !== 0 || compressedSize < 1 || compressedSize > bytes.byteLength - 48) {
|
|
377
|
+
throw fontError(source, "the WOFF2 header is corrupt or truncated.");
|
|
378
|
+
}
|
|
379
|
+
if (decompressedSize < 12 || decompressedSize > DEFAULT_MAX_PDF_DECOMPRESSED_FONT_BYTES) {
|
|
380
|
+
throw fontError(
|
|
381
|
+
source,
|
|
382
|
+
`the decompressed WOFF2 font exceeds the ${DEFAULT_MAX_PDF_DECOMPRESSED_FONT_BYTES}-byte limit.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const metadataOffset = data.readUInt32BE(28);
|
|
386
|
+
const metadataLength = data.readUInt32BE(32);
|
|
387
|
+
const metadataOriginalLength = data.readUInt32BE(36);
|
|
388
|
+
const privateOffset = data.readUInt32BE(40);
|
|
389
|
+
const privateLength = data.readUInt32BE(44);
|
|
390
|
+
const metadataAbsent = metadataOffset === 0 && metadataLength === 0 && metadataOriginalLength === 0;
|
|
391
|
+
const metadataPresent = metadataOffset >= 48 && metadataLength > 0 && metadataOriginalLength > 0 && metadataOffset + metadataLength <= bytes.byteLength;
|
|
392
|
+
const privateAbsent = privateOffset === 0 && privateLength === 0;
|
|
393
|
+
const privatePresent = privateOffset >= 48 && privateLength > 0 && privateOffset + privateLength <= bytes.byteLength;
|
|
394
|
+
if (!metadataAbsent && !metadataPresent || !privateAbsent && !privatePresent) {
|
|
395
|
+
throw fontError(source, "the WOFF2 optional data blocks are corrupt.");
|
|
396
|
+
}
|
|
397
|
+
};
|
|
357
398
|
const validateSfntStructure = (source, bytes, format) => {
|
|
358
399
|
const data = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
359
400
|
const tableCount = data.readUInt16BE(4);
|
|
@@ -380,20 +421,21 @@ const validateSfntStructure = (source, bytes, format) => {
|
|
|
380
421
|
};
|
|
381
422
|
const fontFormat = (source, bytes) => {
|
|
382
423
|
const extension = extname(source).toLowerCase();
|
|
383
|
-
if (extension !== ".otf" && extension !== ".ttf") {
|
|
384
|
-
throw fontError(source, "only .ttf and .
|
|
424
|
+
if (extension !== ".otf" && extension !== ".ttf" && extension !== ".woff2") {
|
|
425
|
+
throw fontError(source, "only .ttf, .otf, and .woff2 files are supported.");
|
|
385
426
|
}
|
|
386
|
-
if (bytes.byteLength <
|
|
387
|
-
throw fontError(source, "the file is too small to be a
|
|
427
|
+
if (bytes.byteLength < 4) {
|
|
428
|
+
throw fontError(source, "the file is too small to be a supported font.");
|
|
388
429
|
}
|
|
389
430
|
const format = detectFontFormat(bytes);
|
|
390
431
|
if (!format) {
|
|
391
|
-
throw fontError(source, "the file has an unsupported
|
|
432
|
+
throw fontError(source, "the file has an unsupported font signature.");
|
|
392
433
|
}
|
|
393
434
|
if (`.${format}` !== extension) {
|
|
394
|
-
throw fontError(source, "the file extension does not match its
|
|
435
|
+
throw fontError(source, "the file extension does not match its font signature.");
|
|
395
436
|
}
|
|
396
|
-
|
|
437
|
+
if (format === "woff2") validateWoff2Structure(source, bytes);
|
|
438
|
+
else validateSfntStructure(source, bytes, format);
|
|
397
439
|
return format;
|
|
398
440
|
};
|
|
399
441
|
const readFont = async (source, filePath, maxBytes) => {
|
|
@@ -451,6 +493,10 @@ const bundlePdfFonts = async (declarations, options) => {
|
|
|
451
493
|
};
|
|
452
494
|
|
|
453
495
|
const authoringImportContexts = /* @__PURE__ */ new Map();
|
|
496
|
+
const compilerFileSystem = {
|
|
497
|
+
fileExists: (file) => statSync(file, { throwIfNoEntry: false })?.isFile() ?? false,
|
|
498
|
+
readFile: (file) => readFileSync(file, "utf8")
|
|
499
|
+
};
|
|
454
500
|
class PdfSfcCompileError extends Error {
|
|
455
501
|
column;
|
|
456
502
|
filename;
|
|
@@ -840,6 +886,7 @@ function assertMetadataHoistable(source, descriptor, filename, macro) {
|
|
|
840
886
|
try {
|
|
841
887
|
const validation = parsePdfSfc(validationSource, filename);
|
|
842
888
|
compileScript(validation, {
|
|
889
|
+
fs: compilerFileSystem,
|
|
843
890
|
id: "nuxt-pdf-metadata-hoist",
|
|
844
891
|
sourceMap: false
|
|
845
892
|
});
|
|
@@ -934,6 +981,7 @@ function compileComponent(descriptor, filename, isProduction) {
|
|
|
934
981
|
if (descriptor.scriptSetup) {
|
|
935
982
|
try {
|
|
936
983
|
const result = compileScript(descriptor, {
|
|
984
|
+
fs: compilerFileSystem,
|
|
937
985
|
genDefaultAs: COMPONENT_VARIABLE,
|
|
938
986
|
id: "nuxt-pdf",
|
|
939
987
|
inlineTemplate: true,
|
|
@@ -959,6 +1007,7 @@ function compileComponent(descriptor, filename, isProduction) {
|
|
|
959
1007
|
if (descriptor.script) {
|
|
960
1008
|
try {
|
|
961
1009
|
const script = compileScript(descriptor, {
|
|
1010
|
+
fs: compilerFileSystem,
|
|
962
1011
|
genDefaultAs: COMPONENT_VARIABLE,
|
|
963
1012
|
id: "nuxt-pdf",
|
|
964
1013
|
isProd: isProduction,
|
|
@@ -1129,4 +1178,4 @@ function isRecord(value) {
|
|
|
1129
1178
|
return typeof value === "object" && value !== null;
|
|
1130
1179
|
}
|
|
1131
1180
|
|
|
1132
|
-
export {
|
|
1181
|
+
export { discoverPdfImageFiles as a, bundlePdfFonts as b, compilePdfSfc as c, discoverPdfTemplates as d, discoverPdfComponentFiles as e, createPdfSfcPlugin as f, classifyPdfWatchEvent as g, templateKeyFromRelativePath as t };
|
package/dist/test.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { mkdir, writeFile, rm, readFile, readdir } from 'node:fs/promises';
|
|
|
10
10
|
import { resolve, dirname, join, relative, basename } from 'node:path';
|
|
11
11
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
12
12
|
import { build } from 'esbuild';
|
|
13
|
-
import { t as templateKeyFromRelativePath,
|
|
13
|
+
import { t as templateKeyFromRelativePath, a as discoverPdfImageFiles, b as bundlePdfFonts, c as compilePdfSfc } from './shared/nuxt-pdf.DMC_Rdsz.mjs';
|
|
14
14
|
import { pdfImageFormatFromKey } from '../dist/runtime/server/assets/resolve-asset.js';
|
|
15
15
|
import '@vue/compiler-sfc';
|
|
16
16
|
import '@jridgewell/remapping';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lupinum/nuxt-pdf",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.3",
|
|
4
4
|
"description": "Author and render PDFs with Vue components in Nuxt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"./test": {
|
|
31
31
|
"types": "./dist/test.d.mts",
|
|
32
32
|
"import": "./dist/test.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./build": {
|
|
35
|
+
"types": "./dist/build.d.mts",
|
|
36
|
+
"import": "./dist/build.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./server": {
|
|
39
|
+
"types": "./dist/server.d.mts",
|
|
40
|
+
"import": "./dist/server.mjs"
|
|
33
41
|
}
|
|
34
42
|
},
|
|
35
43
|
"main": "./dist/module.mjs",
|
|
@@ -76,15 +84,15 @@
|
|
|
76
84
|
"test:package-metadata": "node scripts/check-api-report.mjs",
|
|
77
85
|
"test:performance": "node --expose-gc ./node_modules/vitest/vitest.mjs run --config vitest.performance.config.ts",
|
|
78
86
|
"test:prepare": "pnpm dev:prepare && nuxt prepare test/fixtures/layers",
|
|
79
|
-
"test:production": "vitest run --config vitest.production.config.ts",
|
|
87
|
+
"test:production": "pnpm build && vitest run --config vitest.production.config.ts",
|
|
80
88
|
"test:quickstart": "node scripts/test-package-quickstart.mjs",
|
|
81
89
|
"test:raster": "vitest run --config vitest.raster.config.ts",
|
|
82
90
|
"test:version-headings": "node scripts/check-version-headings.mjs",
|
|
83
|
-
"test:workflows": "node scripts/check-workflow-policy.mjs",
|
|
91
|
+
"test:workflows": "node scripts/check-workflow-policy.mjs && node scripts/test-npm-recovery.mjs && node scripts/test-release-recovery.mjs",
|
|
84
92
|
"check:vercel": "node scripts/check-vercel-config.mjs",
|
|
85
93
|
"test:serverless": "vitest run --config vitest.serverless.config.ts",
|
|
86
94
|
"test:watch": "vitest watch",
|
|
87
|
-
"typecheck": "nuxt prepare && vue-tsc --noEmit && nuxt prepare test/fixtures/basic && vue-tsc --noEmit -p test/fixtures/basic/tsconfig.json && pnpm
|
|
95
|
+
"typecheck": "pnpm test:prepare && nuxt prepare && vue-tsc --noEmit && nuxt prepare test/fixtures/basic && vue-tsc --noEmit -p test/fixtures/basic/tsconfig.json && pnpm --dir playground exec vue-tsc --noEmit",
|
|
88
96
|
"audit:all": "pnpm audit",
|
|
89
97
|
"verify": "pnpm audit:all && pnpm check",
|
|
90
98
|
"check": "pnpm format:check && pnpm check:vercel && pnpm typecheck && pnpm test:docs && pnpm test:version-headings && pnpm test:workflows && pnpm test:dependencies && pnpm test && pnpm test:production && pnpm test:serverless && pnpm build && pnpm test:package-metadata && pnpm dev:build && pnpm test:artifact"
|
|
@@ -97,11 +105,13 @@
|
|
|
97
105
|
"@react-pdf/pdfkit": "5.1.1",
|
|
98
106
|
"@react-pdf/primitives": "4.3.0",
|
|
99
107
|
"@react-pdf/render": "4.5.1",
|
|
100
|
-
"@vue/compiler-sfc": "3.5.
|
|
108
|
+
"@vue/compiler-sfc": "3.5.42",
|
|
101
109
|
"esbuild": "0.28.1",
|
|
102
110
|
"h3": "1.15.11",
|
|
103
111
|
"ufo": "1.6.4",
|
|
104
|
-
"unimport": "6.3.0"
|
|
112
|
+
"unimport": "6.3.0",
|
|
113
|
+
"typescript": "~5.9.3",
|
|
114
|
+
"vue-tsc": "^3.3.3"
|
|
105
115
|
},
|
|
106
116
|
"peerDependencies": {
|
|
107
117
|
"@napi-rs/canvas": "^0.1.97",
|
|
@@ -137,10 +147,8 @@
|
|
|
137
147
|
"publint": "^0.3.21",
|
|
138
148
|
"react": "19.2.0",
|
|
139
149
|
"source-code-pro": "2.42.0",
|
|
140
|
-
"typescript": "~5.9.3",
|
|
141
150
|
"vitest": "^4.1.8",
|
|
142
|
-
"vue": "3.5.
|
|
143
|
-
"vue-tsc": "^3.3.3",
|
|
151
|
+
"vue": "3.5.42",
|
|
144
152
|
"yaml": "2.9.0"
|
|
145
153
|
}
|
|
146
154
|
}
|