@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
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
extname,
|
|
5
|
+
isAbsolute,
|
|
6
|
+
relative,
|
|
7
|
+
resolve,
|
|
8
|
+
sep
|
|
9
|
+
} from "node:path";
|
|
10
|
+
import { PDF_PRIMITIVES } from "../../authoring.js";
|
|
11
|
+
import {
|
|
12
|
+
PDF_ASSET_ERROR_CODES,
|
|
13
|
+
PdfAssetError
|
|
14
|
+
} from "./errors.js";
|
|
15
|
+
import {
|
|
16
|
+
createRemoteRequestState,
|
|
17
|
+
fetchRemoteResource,
|
|
18
|
+
matchesAllowlist,
|
|
19
|
+
redactUrl
|
|
20
|
+
} from "./remote.js";
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_PDF_MAX_IMAGE_BYTES,
|
|
23
|
+
DEFAULT_PDF_MAX_IMAGE_PIXELS,
|
|
24
|
+
DEFAULT_PDF_RENDER_LIMITS,
|
|
25
|
+
createRenderLimits
|
|
26
|
+
} from "../render-limits.js";
|
|
27
|
+
export {
|
|
28
|
+
PDF_ASSET_ERROR_CODES,
|
|
29
|
+
PdfAssetError
|
|
30
|
+
} from "./errors.js";
|
|
31
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
32
|
+
const fail = (code, message, cause) => {
|
|
33
|
+
throw new PdfAssetError(code, message, { cause });
|
|
34
|
+
};
|
|
35
|
+
const blocked = (message) => fail(PDF_ASSET_ERROR_CODES.Blocked, message);
|
|
36
|
+
const invalid = (message, cause) => fail(PDF_ASSET_ERROR_CODES.Invalid, message, cause);
|
|
37
|
+
const limitExceeded = (maxBytes) => fail(
|
|
38
|
+
PDF_ASSET_ERROR_CODES.LimitExceeded,
|
|
39
|
+
`The PDF image exceeds the ${maxBytes}-byte source limit.`
|
|
40
|
+
);
|
|
41
|
+
const imageLimitExceeded = (message) => fail(PDF_ASSET_ERROR_CODES.LimitExceeded, message);
|
|
42
|
+
const resolveMaxBytes = (value) => {
|
|
43
|
+
const maxBytes = value ?? DEFAULT_PDF_MAX_IMAGE_BYTES;
|
|
44
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
45
|
+
invalid("The PDF image byte limit must be a positive safe integer.");
|
|
46
|
+
}
|
|
47
|
+
return maxBytes;
|
|
48
|
+
};
|
|
49
|
+
const canonicalAssetKey = (source) => {
|
|
50
|
+
if (!source || source.includes("\0")) {
|
|
51
|
+
invalid("The PDF image path is invalid.");
|
|
52
|
+
}
|
|
53
|
+
const slashPath = source.replaceAll("\\", "/");
|
|
54
|
+
const hasScheme = /^[a-z][a-z\d+.-]*:/i.test(slashPath);
|
|
55
|
+
const isWindowsAbsolute = /^[a-z]:\//i.test(slashPath);
|
|
56
|
+
if (hasScheme || isAbsolute(source) || isWindowsAbsolute || slashPath.startsWith("/")) {
|
|
57
|
+
blocked("PDF images must use a relative local asset path.");
|
|
58
|
+
}
|
|
59
|
+
const segments = slashPath.split("/");
|
|
60
|
+
if (segments.includes("..")) {
|
|
61
|
+
blocked("Parent-directory traversal is blocked for PDF images.");
|
|
62
|
+
}
|
|
63
|
+
const key = segments.filter((segment) => segment && segment !== ".").join("/");
|
|
64
|
+
if (!key) {
|
|
65
|
+
invalid("The PDF image path is invalid.");
|
|
66
|
+
}
|
|
67
|
+
return key;
|
|
68
|
+
};
|
|
69
|
+
const formatFromExtension = (key) => {
|
|
70
|
+
const extension = extname(key).toLowerCase();
|
|
71
|
+
if (extension === ".png") return "png";
|
|
72
|
+
if (extension === ".jpg" || extension === ".jpeg") return "jpg";
|
|
73
|
+
return invalid("PDF images must be PNG or JPEG files.");
|
|
74
|
+
};
|
|
75
|
+
const claimedFormat = (value) => {
|
|
76
|
+
if (value === void 0) return void 0;
|
|
77
|
+
if (typeof value !== "string") {
|
|
78
|
+
return invalid("The PDF image format is invalid.");
|
|
79
|
+
}
|
|
80
|
+
const normalized = value.toLowerCase();
|
|
81
|
+
if (normalized === "png") return "png";
|
|
82
|
+
if (normalized === "jpg" || normalized === "jpeg") return "jpg";
|
|
83
|
+
return invalid("PDF images must use the PNG or JPEG format.");
|
|
84
|
+
};
|
|
85
|
+
const asBuffer = (value) => {
|
|
86
|
+
if (Buffer.isBuffer(value)) return Buffer.from(value);
|
|
87
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
88
|
+
if (value instanceof ArrayBuffer) return Buffer.from(new Uint8Array(value));
|
|
89
|
+
return void 0;
|
|
90
|
+
};
|
|
91
|
+
const PNG_SIGNATURE = Buffer.from([
|
|
92
|
+
137,
|
|
93
|
+
80,
|
|
94
|
+
78,
|
|
95
|
+
71,
|
|
96
|
+
13,
|
|
97
|
+
10,
|
|
98
|
+
26,
|
|
99
|
+
10
|
|
100
|
+
]);
|
|
101
|
+
const inspectPng = (data) => {
|
|
102
|
+
if (data.byteLength < 33 || !data.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
let height = 0;
|
|
106
|
+
let offset = 8;
|
|
107
|
+
let sawHeader = false;
|
|
108
|
+
let sawImageData = false;
|
|
109
|
+
let width = 0;
|
|
110
|
+
while (offset + 12 <= data.byteLength) {
|
|
111
|
+
const length = data.readUInt32BE(offset);
|
|
112
|
+
const end = offset + 12 + length;
|
|
113
|
+
if (end > data.byteLength) return void 0;
|
|
114
|
+
const type = data.toString("ascii", offset + 4, offset + 8);
|
|
115
|
+
if (!sawHeader) {
|
|
116
|
+
if (type !== "IHDR" || length !== 13) return void 0;
|
|
117
|
+
width = data.readUInt32BE(offset + 8);
|
|
118
|
+
height = data.readUInt32BE(offset + 12);
|
|
119
|
+
if (width < 1 || height < 1) return void 0;
|
|
120
|
+
const bitDepth = data[offset + 16];
|
|
121
|
+
const colorType = data[offset + 17];
|
|
122
|
+
const validDepths = colorType === 0 ? [1, 2, 4, 8, 16] : colorType === 2 ? [8, 16] : colorType === 3 ? [1, 2, 4, 8] : colorType === 4 || colorType === 6 ? [8, 16] : [];
|
|
123
|
+
if (bitDepth === void 0 || !validDepths.includes(bitDepth) || data[offset + 18] !== 0 || data[offset + 19] !== 0 || data[offset + 20] !== 0 && data[offset + 20] !== 1) return void 0;
|
|
124
|
+
sawHeader = true;
|
|
125
|
+
} else if (type === "IHDR") return void 0;
|
|
126
|
+
if (type === "IDAT") sawImageData = true;
|
|
127
|
+
offset = end;
|
|
128
|
+
if (type === "IEND") {
|
|
129
|
+
return length === 0 && sawImageData && offset === data.byteLength ? { height, width } : void 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return void 0;
|
|
133
|
+
};
|
|
134
|
+
const hasJpegSignature = (data) => {
|
|
135
|
+
if (data.byteLength < 4 || data[0] !== 255 || data[1] !== 216 || data[2] !== 255) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return data[data.byteLength - 2] === 255 && data[data.byteLength - 1] === 217;
|
|
139
|
+
};
|
|
140
|
+
const isJpegStartOfFrame = (marker) => marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
141
|
+
const inspectJpeg = (data) => {
|
|
142
|
+
if (!hasJpegSignature(data)) return void 0;
|
|
143
|
+
let dimensions;
|
|
144
|
+
let offset = 2;
|
|
145
|
+
while (offset + 2 < data.byteLength) {
|
|
146
|
+
while (offset < data.byteLength && data[offset] === 255) offset += 1;
|
|
147
|
+
const marker = data[offset];
|
|
148
|
+
offset += 1;
|
|
149
|
+
if (marker === void 0 || marker === 217) break;
|
|
150
|
+
if (marker === 218) return dimensions;
|
|
151
|
+
if (marker === 216 || marker >= 208 && marker <= 215) continue;
|
|
152
|
+
if (offset + 2 > data.byteLength) return void 0;
|
|
153
|
+
const length = data.readUInt16BE(offset);
|
|
154
|
+
if (length < 2 || offset + length > data.byteLength) return void 0;
|
|
155
|
+
if (isJpegStartOfFrame(marker)) {
|
|
156
|
+
if (length < 7) return void 0;
|
|
157
|
+
const height = data.readUInt16BE(offset + 3);
|
|
158
|
+
const width = data.readUInt16BE(offset + 5);
|
|
159
|
+
if (width < 1 || height < 1) return void 0;
|
|
160
|
+
dimensions = { height, width };
|
|
161
|
+
}
|
|
162
|
+
offset += length;
|
|
163
|
+
}
|
|
164
|
+
return void 0;
|
|
165
|
+
};
|
|
166
|
+
const inspectImage = (data) => {
|
|
167
|
+
const png = inspectPng(data);
|
|
168
|
+
if (png) return { ...png, format: "png" };
|
|
169
|
+
const jpeg = inspectJpeg(data);
|
|
170
|
+
if (jpeg) return { ...jpeg, format: "jpg" };
|
|
171
|
+
return void 0;
|
|
172
|
+
};
|
|
173
|
+
const validateImageBytes = (value, maxBytes, expectedFormat, maxPixels = DEFAULT_PDF_MAX_IMAGE_PIXELS) => {
|
|
174
|
+
const data = asBuffer(value);
|
|
175
|
+
if (!data) {
|
|
176
|
+
return invalid("The PDF image source does not contain bytes.");
|
|
177
|
+
}
|
|
178
|
+
if (data.byteLength > maxBytes) return limitExceeded(maxBytes);
|
|
179
|
+
const inspected = inspectImage(data);
|
|
180
|
+
if (!inspected || expectedFormat && inspected.format !== expectedFormat) {
|
|
181
|
+
return invalid(
|
|
182
|
+
"The PDF image structure, file extension, and declared format must agree."
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const pixels = inspected.width * inspected.height;
|
|
186
|
+
if (!Number.isSafeInteger(pixels) || pixels > maxPixels) {
|
|
187
|
+
return imageLimitExceeded(
|
|
188
|
+
`The PDF image has ${pixels} decoded pixels, exceeding pdf.limits.maxImagePixels (${maxPixels}).`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return Object.freeze({ data, ...inspected, pixels });
|
|
192
|
+
};
|
|
193
|
+
const canonicalRoots = async (roots) => {
|
|
194
|
+
if (!Array.isArray(roots) || roots.length === 0) {
|
|
195
|
+
return blocked("At least one local PDF asset root must be configured.");
|
|
196
|
+
}
|
|
197
|
+
const result = [];
|
|
198
|
+
for (const root of roots) {
|
|
199
|
+
if (typeof root !== "string" || !isAbsolute(root)) {
|
|
200
|
+
return invalid("Configured PDF asset roots must be absolute paths.");
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const canonicalRoot = await realpath(root);
|
|
204
|
+
const rootStat = await stat(canonicalRoot);
|
|
205
|
+
if (!rootStat.isDirectory()) {
|
|
206
|
+
return invalid("A configured PDF asset root is not a directory.");
|
|
207
|
+
}
|
|
208
|
+
if (!result.includes(canonicalRoot)) result.push(canonicalRoot);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof PdfAssetError) throw error;
|
|
211
|
+
return invalid(
|
|
212
|
+
"A configured PDF asset root cannot be read.",
|
|
213
|
+
error
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
};
|
|
219
|
+
const isInsideRoot = (root, candidate) => {
|
|
220
|
+
const relativePath = relative(root, candidate);
|
|
221
|
+
return relativePath !== "" && relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
222
|
+
};
|
|
223
|
+
const isMissingFileError = (error) => {
|
|
224
|
+
if (!(error instanceof Error) || !("code" in error)) return false;
|
|
225
|
+
return error.code === "ENOENT" || error.code === "ENOTDIR";
|
|
226
|
+
};
|
|
227
|
+
export const loadPdfImageAsset = async (relativePath, options) => {
|
|
228
|
+
const key = canonicalAssetKey(relativePath);
|
|
229
|
+
const expectedFormat = formatFromExtension(key);
|
|
230
|
+
const maxBytes = resolveMaxBytes(options.maxBytes);
|
|
231
|
+
const maxPixels = options.maxPixels ?? DEFAULT_PDF_MAX_IMAGE_PIXELS;
|
|
232
|
+
const roots = await canonicalRoots(options.roots);
|
|
233
|
+
for (const root of roots) {
|
|
234
|
+
let candidate;
|
|
235
|
+
try {
|
|
236
|
+
candidate = await realpath(resolve(root, ...key.split("/")));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (isMissingFileError(error)) continue;
|
|
239
|
+
return invalid("The local PDF image cannot be resolved.", error);
|
|
240
|
+
}
|
|
241
|
+
if (!isInsideRoot(root, candidate)) {
|
|
242
|
+
return blocked(
|
|
243
|
+
"The local PDF image resolves outside its configured asset root."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
let fileStat;
|
|
247
|
+
try {
|
|
248
|
+
fileStat = await stat(candidate);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return invalid("The local PDF image cannot be inspected.", error);
|
|
251
|
+
}
|
|
252
|
+
if (!fileStat.isFile()) {
|
|
253
|
+
return invalid("The local PDF image is not a regular file.");
|
|
254
|
+
}
|
|
255
|
+
if (fileStat.size > maxBytes) return limitExceeded(maxBytes);
|
|
256
|
+
try {
|
|
257
|
+
const image = validateImageBytes(
|
|
258
|
+
await readFile(candidate),
|
|
259
|
+
maxBytes,
|
|
260
|
+
expectedFormat,
|
|
261
|
+
maxPixels
|
|
262
|
+
);
|
|
263
|
+
return Object.freeze({ key, ...image });
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof PdfAssetError) throw error;
|
|
266
|
+
return invalid("The local PDF image cannot be read.", error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return invalid(
|
|
270
|
+
"The local PDF image was not found in a configured asset root."
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
const resolveLocalImage = (source, assets, maxBytes, maxPixels, declaredFormat) => {
|
|
274
|
+
const key = canonicalAssetKey(source);
|
|
275
|
+
const pathFormat = formatFromExtension(key);
|
|
276
|
+
const sourceFormat = claimedFormat(declaredFormat);
|
|
277
|
+
if (sourceFormat && sourceFormat !== pathFormat) {
|
|
278
|
+
return invalid("The PDF image path and declared format must agree.");
|
|
279
|
+
}
|
|
280
|
+
if (!hasOwn(assets, key)) {
|
|
281
|
+
return invalid(
|
|
282
|
+
"The local PDF image was not included in the generated asset map."
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const asset = assets[key];
|
|
286
|
+
if (!asset || typeof asset !== "object") {
|
|
287
|
+
return invalid("The generated PDF image asset is invalid.");
|
|
288
|
+
}
|
|
289
|
+
const assetFormat = claimedFormat(asset.format);
|
|
290
|
+
if (!assetFormat || assetFormat !== pathFormat) {
|
|
291
|
+
return invalid(
|
|
292
|
+
"The generated PDF image format does not match its asset key."
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return validateImageBytes(asset.data, maxBytes, assetFormat, maxPixels);
|
|
296
|
+
};
|
|
297
|
+
const isRemoteCandidate = (source) => /^https?:/i.test(source);
|
|
298
|
+
const imageResolutionCacheKey = (source) => {
|
|
299
|
+
if (typeof source === "string") return `string\0${source}`;
|
|
300
|
+
if (!source || typeof source !== "object") return source;
|
|
301
|
+
const record = source;
|
|
302
|
+
if (typeof record.uri === "string") {
|
|
303
|
+
return isRemoteCandidate(record.uri) ? `remote\0${record.uri}` : `local\0${record.uri}\0${String(record.format ?? "")}`;
|
|
304
|
+
}
|
|
305
|
+
return source;
|
|
306
|
+
};
|
|
307
|
+
const resolveRemoteImage = async (url, remote, remoteState, inflight, maxBytes, maxPixels) => {
|
|
308
|
+
if (!remote) {
|
|
309
|
+
return blocked(
|
|
310
|
+
`Remote PDF image fetching is disabled. Set pdf.remote.allow to fetch "${redactUrl(url)}".`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (!matchesAllowlist(url, remote)) {
|
|
314
|
+
return blocked(
|
|
315
|
+
`The PDF image "${redactUrl(url)}" is not permitted by pdf.remote.allow.`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const bytes = await fetchRemoteResource(url, {
|
|
319
|
+
policy: remote,
|
|
320
|
+
maxBytes,
|
|
321
|
+
state: remoteState,
|
|
322
|
+
inflight
|
|
323
|
+
});
|
|
324
|
+
return validateImageBytes(bytes, maxBytes, void 0, maxPixels);
|
|
325
|
+
};
|
|
326
|
+
const resolveImageSource = async (source, assets, maxBytes, maxPixels, remote, remoteState, inflight) => {
|
|
327
|
+
if (typeof source === "function") {
|
|
328
|
+
return blocked("Dynamic PDF image source functions are blocked.");
|
|
329
|
+
}
|
|
330
|
+
if (typeof source === "string") {
|
|
331
|
+
return isRemoteCandidate(source) ? resolveRemoteImage(source, remote, remoteState, inflight, maxBytes, maxPixels) : resolveLocalImage(source, assets, maxBytes, maxPixels);
|
|
332
|
+
}
|
|
333
|
+
const bytes = asBuffer(source);
|
|
334
|
+
if (bytes) return validateImageBytes(bytes, maxBytes, void 0, maxPixels);
|
|
335
|
+
if (!source || typeof source !== "object") {
|
|
336
|
+
return invalid("The PDF image source is invalid.");
|
|
337
|
+
}
|
|
338
|
+
const record = source;
|
|
339
|
+
if (typeof record.then === "function") {
|
|
340
|
+
return blocked("Asynchronous PDF image sources are blocked.");
|
|
341
|
+
}
|
|
342
|
+
if (hasOwn(record, "uri")) {
|
|
343
|
+
if (typeof record.uri !== "string") {
|
|
344
|
+
return invalid("The PDF image URI is invalid.");
|
|
345
|
+
}
|
|
346
|
+
if (isRemoteCandidate(record.uri)) {
|
|
347
|
+
if (Object.keys(record).some((key) => key !== "uri")) {
|
|
348
|
+
return blocked("PDF image request options are blocked for remote assets.");
|
|
349
|
+
}
|
|
350
|
+
return resolveRemoteImage(
|
|
351
|
+
record.uri,
|
|
352
|
+
remote,
|
|
353
|
+
remoteState,
|
|
354
|
+
inflight,
|
|
355
|
+
maxBytes,
|
|
356
|
+
maxPixels
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const allowedKeys = /* @__PURE__ */ new Set(["format", "uri"]);
|
|
360
|
+
if (Object.keys(record).some((key) => !allowedKeys.has(key))) {
|
|
361
|
+
return blocked("PDF image request options are blocked for local assets.");
|
|
362
|
+
}
|
|
363
|
+
return resolveLocalImage(
|
|
364
|
+
record.uri,
|
|
365
|
+
assets,
|
|
366
|
+
maxBytes,
|
|
367
|
+
maxPixels,
|
|
368
|
+
record.format
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (hasOwn(record, "data")) {
|
|
372
|
+
const format = claimedFormat(record.format);
|
|
373
|
+
if (!format) {
|
|
374
|
+
return invalid("A byte-backed PDF image source must declare its format.");
|
|
375
|
+
}
|
|
376
|
+
return validateImageBytes(record.data, maxBytes, format, maxPixels);
|
|
377
|
+
}
|
|
378
|
+
return invalid(
|
|
379
|
+
"The PDF image source must be bytes or a bundled local path."
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
const resolveImageBuffer = (source, assets, limits, remote, remoteState, inflight, resolved, budget) => {
|
|
383
|
+
const key = imageResolutionCacheKey(source);
|
|
384
|
+
const existing = resolved.get(key);
|
|
385
|
+
if (existing) return existing;
|
|
386
|
+
const promise = resolveImageSource(
|
|
387
|
+
source,
|
|
388
|
+
assets,
|
|
389
|
+
limits.maxImageBytes,
|
|
390
|
+
limits.maxImagePixels,
|
|
391
|
+
remote,
|
|
392
|
+
remoteState,
|
|
393
|
+
inflight
|
|
394
|
+
).then((image) => {
|
|
395
|
+
budget.bytes += image.data.byteLength;
|
|
396
|
+
if (budget.bytes > limits.maxTotalImageBytes) {
|
|
397
|
+
return imageLimitExceeded(
|
|
398
|
+
`PDF image sources exceed pdf.limits.maxTotalImageBytes (${limits.maxTotalImageBytes}).`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
budget.pixels += image.pixels;
|
|
402
|
+
if (budget.pixels > limits.maxTotalImagePixels) {
|
|
403
|
+
return imageLimitExceeded(
|
|
404
|
+
`PDF images exceed pdf.limits.maxTotalImagePixels (${limits.maxTotalImagePixels}).`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return image.data;
|
|
408
|
+
});
|
|
409
|
+
resolved.set(key, promise);
|
|
410
|
+
return promise;
|
|
411
|
+
};
|
|
412
|
+
const collectImageNodes = (document) => {
|
|
413
|
+
const images = [];
|
|
414
|
+
const pending = [document];
|
|
415
|
+
while (pending.length > 0) {
|
|
416
|
+
const node = pending.pop();
|
|
417
|
+
if (node.type === PDF_PRIMITIVES.Image) images.push(node);
|
|
418
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
419
|
+
const child = node.children[index];
|
|
420
|
+
if (child && child.type !== PDF_PRIMITIVES.TextInstance) {
|
|
421
|
+
pending.push(child);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return images;
|
|
426
|
+
};
|
|
427
|
+
export const createPdfImageResolutionState = (limits) => ({
|
|
428
|
+
budget: { bytes: 0, pixels: 0 },
|
|
429
|
+
inflight: /* @__PURE__ */ new Map(),
|
|
430
|
+
remote: createRemoteRequestState(limits),
|
|
431
|
+
resolved: /* @__PURE__ */ new Map()
|
|
432
|
+
});
|
|
433
|
+
export const resolvePdfImageAssets = async (document, options) => {
|
|
434
|
+
const limits = options.limits ?? createRenderLimits({
|
|
435
|
+
...DEFAULT_PDF_RENDER_LIMITS
|
|
436
|
+
});
|
|
437
|
+
if (!options.assets || typeof options.assets !== "object") {
|
|
438
|
+
return invalid("A generated PDF image asset map is required.");
|
|
439
|
+
}
|
|
440
|
+
const state = options.state ?? createPdfImageResolutionState(limits);
|
|
441
|
+
const targets = [];
|
|
442
|
+
for (const node of collectImageNodes(document)) {
|
|
443
|
+
if (hasOwn(node.props, "srcSet") && node.props.srcSet !== void 0) {
|
|
444
|
+
return blocked("PDF image srcSet sources are blocked.");
|
|
445
|
+
}
|
|
446
|
+
const hasSrc = hasOwn(node.props, "src") && node.props.src !== void 0;
|
|
447
|
+
const hasSource = hasOwn(node.props, "source") && node.props.source !== void 0;
|
|
448
|
+
if (hasSrc === hasSource) {
|
|
449
|
+
return invalid("Each PDF image must have exactly one src or source prop.");
|
|
450
|
+
}
|
|
451
|
+
targets.push({ node, prop: hasSrc ? "src" : "source" });
|
|
452
|
+
}
|
|
453
|
+
if (targets.length > limits.maxImages) {
|
|
454
|
+
return imageLimitExceeded(
|
|
455
|
+
`PDF mounted ${targets.length} images, exceeding pdf.limits.maxImages (${limits.maxImages}).`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
let resolved;
|
|
459
|
+
try {
|
|
460
|
+
resolved = await Promise.all(targets.map(
|
|
461
|
+
(target) => resolveImageBuffer(
|
|
462
|
+
target.node.props[target.prop],
|
|
463
|
+
options.assets,
|
|
464
|
+
limits,
|
|
465
|
+
options.remote,
|
|
466
|
+
state.remote,
|
|
467
|
+
state.inflight,
|
|
468
|
+
state.resolved,
|
|
469
|
+
state.budget
|
|
470
|
+
)
|
|
471
|
+
));
|
|
472
|
+
} catch (error) {
|
|
473
|
+
limits.abortController.abort(error);
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
targets.forEach((target, index) => {
|
|
477
|
+
const data = resolved[index];
|
|
478
|
+
target.node.props[target.prop] = data;
|
|
479
|
+
state.resolved.set(data, Promise.resolve(data));
|
|
480
|
+
});
|
|
481
|
+
return document;
|
|
482
|
+
};
|