modern-pdf-lib 0.38.0 → 0.40.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/README.md +3 -3
- package/dist/browser.cjs +23 -3
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/{index-CNtfRlyP.d.cts → index-CHhHRD6q.d.cts} +804 -5
- package/dist/index-CHhHRD6q.d.cts.map +1 -0
- package/dist/{index-CDg5gwYg.d.mts → index-CzmWmPxV.d.mts} +804 -5
- package/dist/index-CzmWmPxV.d.mts.map +1 -0
- package/dist/index.cjs +23 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-B0gmgVEs.cjs → src-aDYhSnal.cjs} +2224 -254
- package/dist/{src-DlRTrvWg.mjs → src-wZnfMPwi.mjs} +1867 -17
- package/package.json +1 -1
- package/dist/index-CDg5gwYg.d.mts.map +0 -1
- package/dist/index-CNtfRlyP.d.cts.map +0 -1
|
@@ -13223,7 +13223,7 @@ const DEFAULT_HEIGHT = 50;
|
|
|
13223
13223
|
const DEFAULT_MODULE_WIDTH = 1;
|
|
13224
13224
|
const DEFAULT_QUIET_ZONE = 10;
|
|
13225
13225
|
const DEFAULT_FONT_NAME = "Helvetica";
|
|
13226
|
-
const DEFAULT_FONT_SIZE$
|
|
13226
|
+
const DEFAULT_FONT_SIZE$3 = 10;
|
|
13227
13227
|
const DEFAULT_BORDER_WIDTH = .5;
|
|
13228
13228
|
const DEFAULT_PADDING = 4;
|
|
13229
13229
|
const DEFAULT_BAR_COLOR = {
|
|
@@ -13254,7 +13254,7 @@ function calculateBarcodeDimensions(matrix, options) {
|
|
|
13254
13254
|
const height = options?.height ?? DEFAULT_HEIGHT;
|
|
13255
13255
|
const padding = options?.padding ?? (options?.border ? DEFAULT_PADDING : 0);
|
|
13256
13256
|
const borderW = options?.border ? options.borderWidth ?? DEFAULT_BORDER_WIDTH : 0;
|
|
13257
|
-
const textHeight = options?.showText ? (options.fontSize ?? DEFAULT_FONT_SIZE$
|
|
13257
|
+
const textHeight = options?.showText ? (options.fontSize ?? DEFAULT_FONT_SIZE$3) + 4 : 0;
|
|
13258
13258
|
return {
|
|
13259
13259
|
width: (matrix.width + quietZone * 2) * moduleWidth + padding * 2 + borderW * 2,
|
|
13260
13260
|
height: height + textHeight + padding * 2 + borderW * 2
|
|
@@ -13284,7 +13284,7 @@ function renderStyledBarcode(matrix, x, y, text, options) {
|
|
|
13284
13284
|
const bgColor = options?.backgroundColor ?? DEFAULT_BG_COLOR;
|
|
13285
13285
|
const showTextBelow = options?.showText ?? false;
|
|
13286
13286
|
const fontName = options?.fontName ?? DEFAULT_FONT_NAME;
|
|
13287
|
-
const fontSize = options?.fontSize ?? DEFAULT_FONT_SIZE$
|
|
13287
|
+
const fontSize = options?.fontSize ?? DEFAULT_FONT_SIZE$3;
|
|
13288
13288
|
const textColor = options?.textColor ?? barColor;
|
|
13289
13289
|
const hasBorder = options?.border ?? false;
|
|
13290
13290
|
const borderWidth = options?.borderWidth ?? DEFAULT_BORDER_WIDTH;
|
|
@@ -15530,7 +15530,7 @@ const DEFAULT_MARGINS = {
|
|
|
15530
15530
|
left: 50,
|
|
15531
15531
|
right: 50
|
|
15532
15532
|
};
|
|
15533
|
-
const DEFAULT_FONT_SIZE$
|
|
15533
|
+
const DEFAULT_FONT_SIZE$2 = 10;
|
|
15534
15534
|
const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
|
|
15535
15535
|
/** Convert an integer to a lowercase Roman numeral string. */
|
|
15536
15536
|
function toRoman(num) {
|
|
@@ -15618,7 +15618,7 @@ function applyHeaderFooterToPage(page, options, pageNumber, totalPages, title) {
|
|
|
15618
15618
|
if (options.header && options.header.length > 0) {
|
|
15619
15619
|
const headerY = pageHeight - margins.top;
|
|
15620
15620
|
for (const item of options.header) {
|
|
15621
|
-
const fontSize = item.fontSize ?? DEFAULT_FONT_SIZE$
|
|
15621
|
+
const fontSize = item.fontSize ?? DEFAULT_FONT_SIZE$2;
|
|
15622
15622
|
const resolvedText = replaceTemplateVariables(item.text, pageNumber, totalPages, now, dateFormat, effectiveTitle);
|
|
15623
15623
|
const textWidth = estimateWidth(resolvedText, item.font, fontSize);
|
|
15624
15624
|
let x;
|
|
@@ -15634,7 +15634,7 @@ function applyHeaderFooterToPage(page, options, pageNumber, totalPages, title) {
|
|
|
15634
15634
|
});
|
|
15635
15635
|
}
|
|
15636
15636
|
if (options.separatorLine) {
|
|
15637
|
-
const lineY = headerY - (options.header[0]?.fontSize ?? DEFAULT_FONT_SIZE$
|
|
15637
|
+
const lineY = headerY - (options.header[0]?.fontSize ?? DEFAULT_FONT_SIZE$2) - 4;
|
|
15638
15638
|
page.drawLine({
|
|
15639
15639
|
start: {
|
|
15640
15640
|
x: margins.left,
|
|
@@ -15653,7 +15653,7 @@ function applyHeaderFooterToPage(page, options, pageNumber, totalPages, title) {
|
|
|
15653
15653
|
if (options.footer && options.footer.length > 0) {
|
|
15654
15654
|
const footerY = margins.bottom;
|
|
15655
15655
|
for (const item of options.footer) {
|
|
15656
|
-
const fontSize = item.fontSize ?? DEFAULT_FONT_SIZE$
|
|
15656
|
+
const fontSize = item.fontSize ?? DEFAULT_FONT_SIZE$2;
|
|
15657
15657
|
const resolvedText = replaceTemplateVariables(item.text, pageNumber, totalPages, now, dateFormat, effectiveTitle);
|
|
15658
15658
|
const textWidth = estimateWidth(resolvedText, item.font, fontSize);
|
|
15659
15659
|
let x;
|
|
@@ -15669,7 +15669,7 @@ function applyHeaderFooterToPage(page, options, pageNumber, totalPages, title) {
|
|
|
15669
15669
|
});
|
|
15670
15670
|
}
|
|
15671
15671
|
if (options.separatorLine) {
|
|
15672
|
-
const lineY = footerY + (options.footer[0]?.fontSize ?? DEFAULT_FONT_SIZE$
|
|
15672
|
+
const lineY = footerY + (options.footer[0]?.fontSize ?? DEFAULT_FONT_SIZE$2) + 4;
|
|
15673
15673
|
page.drawLine({
|
|
15674
15674
|
start: {
|
|
15675
15675
|
x: margins.left,
|
|
@@ -21869,7 +21869,7 @@ function createRangeFetcher(url, options) {
|
|
|
21869
21869
|
* @param children Child nodes for `document` / `page` containers.
|
|
21870
21870
|
* @returns A frozen-shaped {@link VNode}.
|
|
21871
21871
|
*/
|
|
21872
|
-
function h(type, props, ...children) {
|
|
21872
|
+
function h$1(type, props, ...children) {
|
|
21873
21873
|
switch (type) {
|
|
21874
21874
|
case "document": return {
|
|
21875
21875
|
type: "document",
|
|
@@ -21905,7 +21905,7 @@ function asText(value) {
|
|
|
21905
21905
|
return typeof value === "string" ? value : "";
|
|
21906
21906
|
}
|
|
21907
21907
|
/** Internal layout constants. */
|
|
21908
|
-
const DEFAULT_FONT_SIZE = 12;
|
|
21908
|
+
const DEFAULT_FONT_SIZE$1 = 12;
|
|
21909
21909
|
const DEFAULT_MARGIN = 50;
|
|
21910
21910
|
const LINE_SPACING = 1.35;
|
|
21911
21911
|
const HEADING_SCALE = [
|
|
@@ -21927,13 +21927,13 @@ const HEADING_SCALE = [
|
|
|
21927
21927
|
* @param options Optional layout overrides.
|
|
21928
21928
|
* @returns A promise resolving to the saved PDF bytes (starting `%PDF-`).
|
|
21929
21929
|
*/
|
|
21930
|
-
async function renderToPdf(root, options) {
|
|
21931
|
-
const fontSize = options?.fontSize ?? DEFAULT_FONT_SIZE;
|
|
21930
|
+
async function renderToPdf$1(root, options) {
|
|
21931
|
+
const fontSize = options?.fontSize ?? DEFAULT_FONT_SIZE$1;
|
|
21932
21932
|
const margin = options?.margin ?? DEFAULT_MARGIN;
|
|
21933
21933
|
const doc = require_pdfDocument.createPdf();
|
|
21934
21934
|
const body = await doc.embedFont(require_pdfDocument.StandardFonts.Helvetica);
|
|
21935
21935
|
const bold = await doc.embedFont(require_pdfDocument.StandardFonts.HelveticaBold);
|
|
21936
|
-
for (const pageNode of collectPages(root)) renderPage(doc, body, bold, pageNode, fontSize, margin);
|
|
21936
|
+
for (const pageNode of collectPages$1(root)) renderPage(doc, body, bold, pageNode, fontSize, margin);
|
|
21937
21937
|
return doc.save();
|
|
21938
21938
|
}
|
|
21939
21939
|
/**
|
|
@@ -21943,7 +21943,7 @@ async function renderToPdf(root, options) {
|
|
|
21943
21943
|
* are gathered into a synthesized `page`. A non-`document` root is
|
|
21944
21944
|
* itself treated as a page (wrapping a leaf if necessary).
|
|
21945
21945
|
*/
|
|
21946
|
-
function collectPages(root) {
|
|
21946
|
+
function collectPages$1(root) {
|
|
21947
21947
|
if (root.type === "document") {
|
|
21948
21948
|
const pages = [];
|
|
21949
21949
|
const loose = [];
|
|
@@ -32167,260 +32167,2110 @@ function feBlend(a, b, mode) {
|
|
|
32167
32167
|
return out;
|
|
32168
32168
|
}
|
|
32169
32169
|
//#endregion
|
|
32170
|
-
//#region src/
|
|
32171
|
-
/** The identity transform. */
|
|
32172
|
-
function identity() {
|
|
32173
|
-
return [
|
|
32174
|
-
1,
|
|
32175
|
-
0,
|
|
32176
|
-
0,
|
|
32177
|
-
1,
|
|
32178
|
-
0,
|
|
32179
|
-
0
|
|
32180
|
-
];
|
|
32181
|
-
}
|
|
32170
|
+
//#region src/runtime/sharedConcurrency.ts
|
|
32182
32171
|
/**
|
|
32183
|
-
*
|
|
32184
|
-
*
|
|
32185
|
-
*
|
|
32172
|
+
* @module runtime/sharedConcurrency
|
|
32173
|
+
*
|
|
32174
|
+
* `SharedArrayBuffer` + `Atomics` concurrency primitives for coordinating
|
|
32175
|
+
* work across Web Workers / `worker_threads`. These are thin, correctness
|
|
32176
|
+
* focused wrappers around the standard atomic operations — they add no
|
|
32177
|
+
* acceleration of their own and make no use of SIMD or any other hardware
|
|
32178
|
+
* feature. They simply expose race-free counters, flags, and a byte ring
|
|
32179
|
+
* so multiple agents can share state through a single `SharedArrayBuffer`.
|
|
32180
|
+
*
|
|
32181
|
+
* Availability caveats (feature-detected, never assumed):
|
|
32182
|
+
*
|
|
32183
|
+
* - `SharedArrayBuffer` and `Atomics` must both exist. In browsers, shared
|
|
32184
|
+
* memory additionally requires the page to be *cross-origin isolated*
|
|
32185
|
+
* (COOP+COEP headers); we treat `crossOriginIsolated === false` as
|
|
32186
|
+
* "unavailable". See {@link isSharedMemoryAvailable}.
|
|
32187
|
+
* - `Atomics.wait` is only permitted on a non-main agent (it throws on the
|
|
32188
|
+
* main browser thread and would deadlock the event loop everywhere). The
|
|
32189
|
+
* {@link SharedFlag} `wait` method feature-detects this and degrades to a
|
|
32190
|
+
* non-blocking poll rather than throwing — see its docs.
|
|
32191
|
+
*
|
|
32192
|
+
* All references below are to the ECMA-262 `Atomics` semantics and MDN:
|
|
32193
|
+
*
|
|
32194
|
+
* - `Atomics.add(ta, i, v)` atomically adds `v` and returns the **previous**
|
|
32195
|
+
* value at index `i` (the value before the addition).
|
|
32196
|
+
* - `Atomics.compareExchange(ta, i, expected, replacement)` writes
|
|
32197
|
+
* `replacement` iff the current value equals `expected`, and returns the
|
|
32198
|
+
* value that was at index `i` **before** the call (so success is detected
|
|
32199
|
+
* by `result === expected`).
|
|
32200
|
+
* - `Atomics.notify(ta, i, count)` wakes up to `count` agents blocked in
|
|
32201
|
+
* `Atomics.wait` on index `i` and returns the number actually woken (0 if
|
|
32202
|
+
* none are waiting). It is safe to call from any thread, including the
|
|
32203
|
+
* main thread.
|
|
32204
|
+
* - `Atomics.wait(ta, i, value, timeout)` blocks while the slot equals
|
|
32205
|
+
* `value`, returning `'ok'`, `'timed-out'`, or `'not-equal'`.
|
|
32186
32206
|
*/
|
|
32187
|
-
function multiply(m, n) {
|
|
32188
|
-
return [
|
|
32189
|
-
m[0] * n[0] + m[1] * n[2],
|
|
32190
|
-
m[0] * n[1] + m[1] * n[3],
|
|
32191
|
-
m[2] * n[0] + m[3] * n[2],
|
|
32192
|
-
m[2] * n[1] + m[3] * n[3],
|
|
32193
|
-
m[4] * n[0] + m[5] * n[2] + n[4],
|
|
32194
|
-
m[4] * n[1] + m[5] * n[3] + n[5]
|
|
32195
|
-
];
|
|
32196
|
-
}
|
|
32197
|
-
/** Apply a transform to a point, returning the transformed `[x, y]`. */
|
|
32198
|
-
function applyToPoint(m, x, y) {
|
|
32199
|
-
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
32200
|
-
}
|
|
32201
|
-
/** A pure translation transform. */
|
|
32202
|
-
function translation(tx, ty) {
|
|
32203
|
-
return [
|
|
32204
|
-
1,
|
|
32205
|
-
0,
|
|
32206
|
-
0,
|
|
32207
|
-
1,
|
|
32208
|
-
tx,
|
|
32209
|
-
ty
|
|
32210
|
-
];
|
|
32211
|
-
}
|
|
32212
32207
|
/**
|
|
32213
|
-
*
|
|
32214
|
-
* x- and y-axis scale factors (`sqrt(|det|)`). Used to map a user-space line
|
|
32215
|
-
* width to device space.
|
|
32208
|
+
* Bytes per `Int32Array` element. Atomics operate on 32-bit integer views.
|
|
32216
32209
|
*/
|
|
32217
|
-
|
|
32218
|
-
|
|
32219
|
-
|
|
32210
|
+
const INT32_BYTES = 4;
|
|
32211
|
+
/**
|
|
32212
|
+
* Reference to the global `SharedArrayBuffer` constructor, or `undefined`
|
|
32213
|
+
* when the runtime does not provide one. Captured via `globalThis` so the
|
|
32214
|
+
* module loads without a `ReferenceError` on platforms that omit it.
|
|
32215
|
+
*/
|
|
32216
|
+
const SharedArrayBufferCtor = globalThis.SharedArrayBuffer;
|
|
32217
|
+
/**
|
|
32218
|
+
* Allocate a fresh zero-initialised `SharedArrayBuffer` of `byteLength`
|
|
32219
|
+
* bytes, throwing a clear error if shared memory is not available.
|
|
32220
|
+
*/
|
|
32221
|
+
function allocShared(byteLength) {
|
|
32222
|
+
if (SharedArrayBufferCtor === void 0) throw new Error("sharedConcurrency: SharedArrayBuffer is not available in this runtime. Call isSharedMemoryAvailable() before constructing these primitives.");
|
|
32223
|
+
return new SharedArrayBufferCtor(byteLength);
|
|
32220
32224
|
}
|
|
32221
|
-
//#endregion
|
|
32222
|
-
//#region src/render/interpreter.ts
|
|
32223
32225
|
/**
|
|
32224
|
-
*
|
|
32226
|
+
* Report whether shared-memory concurrency is usable in this runtime.
|
|
32225
32227
|
*
|
|
32226
|
-
*
|
|
32227
|
-
*
|
|
32228
|
-
*
|
|
32228
|
+
* Returns `true` only when **both** `SharedArrayBuffer` and `Atomics`
|
|
32229
|
+
* exist, and — in a browser context that exposes `crossOriginIsolated` —
|
|
32230
|
+
* that flag is not `false`. The check is fully defensive: it performs only
|
|
32231
|
+
* `typeof` probes and never throws, so it is safe to call as a guard before
|
|
32232
|
+
* touching any other export here.
|
|
32229
32233
|
*
|
|
32230
|
-
*
|
|
32231
|
-
*
|
|
32232
|
-
*
|
|
32233
|
-
*
|
|
32234
|
-
* (`BT`/`ET`/`Tf`/`Td`/`TD`/`Tm`/`T*`/`Tc`/`Tw`/`Tz`/`TL`/`Ts`/`Tr`/`Tj`/`TJ`/`'`/`"`),
|
|
32235
|
-
* and XObjects (`Do`, recursing into form XObjects). Bézier curves are
|
|
32236
|
-
* flattened to polylines.
|
|
32234
|
+
* Note: `crossOriginIsolated` is only consulted when it is a boolean. On
|
|
32235
|
+
* Node / Deno / Bun / Workers (where it is typically `undefined`) we do not
|
|
32236
|
+
* treat its absence as a failure, since those runtimes allow shared memory
|
|
32237
|
+
* without the browser's COOP/COEP isolation requirement.
|
|
32237
32238
|
*
|
|
32238
|
-
* @
|
|
32239
|
+
* @returns `true` iff shared-memory primitives can be constructed and used.
|
|
32239
32240
|
*/
|
|
32240
|
-
function
|
|
32241
|
-
|
|
32242
|
-
|
|
32243
|
-
|
|
32244
|
-
|
|
32245
|
-
|
|
32246
|
-
|
|
32247
|
-
|
|
32248
|
-
],
|
|
32249
|
-
stroke: [
|
|
32250
|
-
0,
|
|
32251
|
-
0,
|
|
32252
|
-
0,
|
|
32253
|
-
255
|
|
32254
|
-
],
|
|
32255
|
-
lineWidth: 1,
|
|
32256
|
-
lineCap: 0,
|
|
32257
|
-
lineJoin: 0,
|
|
32258
|
-
fillAlpha: 1,
|
|
32259
|
-
strokeAlpha: 1,
|
|
32260
|
-
clip: void 0,
|
|
32261
|
-
font: void 0,
|
|
32262
|
-
fontSize: 0,
|
|
32263
|
-
charSpace: 0,
|
|
32264
|
-
wordSpace: 0,
|
|
32265
|
-
hScale: 1,
|
|
32266
|
-
leading: 0,
|
|
32267
|
-
rise: 0,
|
|
32268
|
-
renderMode: 0
|
|
32269
|
-
};
|
|
32270
|
-
}
|
|
32271
|
-
function cloneState(s) {
|
|
32272
|
-
return { ...s };
|
|
32273
|
-
}
|
|
32274
|
-
function num$2(op) {
|
|
32275
|
-
return typeof op === "number" ? op : 0;
|
|
32276
|
-
}
|
|
32277
|
-
function nameOf(op) {
|
|
32278
|
-
if (op instanceof require_pdfObjects.PdfName) return op.value.replace(/^\//, "");
|
|
32279
|
-
if (typeof op === "string") return op.replace(/^\//, "");
|
|
32280
|
-
}
|
|
32281
|
-
function clamp255(v) {
|
|
32282
|
-
return Math.max(0, Math.min(255, Math.round(v * 255)));
|
|
32283
|
-
}
|
|
32284
|
-
function grayRgba(g) {
|
|
32285
|
-
const v = clamp255(g);
|
|
32286
|
-
return [
|
|
32287
|
-
v,
|
|
32288
|
-
v,
|
|
32289
|
-
v,
|
|
32290
|
-
255
|
|
32291
|
-
];
|
|
32292
|
-
}
|
|
32293
|
-
function rgbRgba(r, g, b) {
|
|
32294
|
-
return [
|
|
32295
|
-
clamp255(r),
|
|
32296
|
-
clamp255(g),
|
|
32297
|
-
clamp255(b),
|
|
32298
|
-
255
|
|
32299
|
-
];
|
|
32300
|
-
}
|
|
32301
|
-
function cmykRgba(c, m, y, k) {
|
|
32302
|
-
const [r, g, b] = require_pdfDocument.cmykToRgb(c, m, y, k);
|
|
32303
|
-
return [
|
|
32304
|
-
clamp255(r),
|
|
32305
|
-
clamp255(g),
|
|
32306
|
-
clamp255(b),
|
|
32307
|
-
255
|
|
32308
|
-
];
|
|
32309
|
-
}
|
|
32310
|
-
/** Interpret `sc`/`scn` numeric components by arity → RGBA. */
|
|
32311
|
-
function componentsToRgba(comps) {
|
|
32312
|
-
if (comps.length === 1) return grayRgba(comps[0]);
|
|
32313
|
-
if (comps.length === 3) return rgbRgba(comps[0], comps[1], comps[2]);
|
|
32314
|
-
if (comps.length === 4) return cmykRgba(comps[0], comps[1], comps[2], comps[3]);
|
|
32315
|
-
}
|
|
32316
|
-
const BEZIER_STEPS = 16;
|
|
32317
|
-
function flattenCubic(out, p0, p1, p2, p3) {
|
|
32318
|
-
for (let i = 1; i <= BEZIER_STEPS; i++) {
|
|
32319
|
-
const t = i / BEZIER_STEPS;
|
|
32320
|
-
const mt = 1 - t;
|
|
32321
|
-
const a = mt * mt * mt;
|
|
32322
|
-
const b = 3 * mt * mt * t;
|
|
32323
|
-
const c = 3 * mt * t * t;
|
|
32324
|
-
const d = t * t * t;
|
|
32325
|
-
out.push(a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0], a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]);
|
|
32241
|
+
function isSharedMemoryAvailable() {
|
|
32242
|
+
try {
|
|
32243
|
+
if (typeof SharedArrayBuffer === "undefined") return false;
|
|
32244
|
+
if (typeof Atomics === "undefined") return false;
|
|
32245
|
+
if (globalThis.crossOriginIsolated === false) return false;
|
|
32246
|
+
return true;
|
|
32247
|
+
} catch {
|
|
32248
|
+
return false;
|
|
32326
32249
|
}
|
|
32327
32250
|
}
|
|
32328
32251
|
/**
|
|
32329
|
-
*
|
|
32252
|
+
* An atomic 32-bit integer counter backed by a single slot of an
|
|
32253
|
+
* `Int32Array` view over a `SharedArrayBuffer`. Multiple `SharedCounter`
|
|
32254
|
+
* instances constructed over the **same** buffer and index observe each
|
|
32255
|
+
* other's writes, so the counter can be shared across workers by passing
|
|
32256
|
+
* its {@link SharedCounter.buffer} through `postMessage`.
|
|
32330
32257
|
*
|
|
32331
|
-
*
|
|
32332
|
-
*
|
|
32258
|
+
* All mutations use `Atomics`, so increments from concurrent agents never
|
|
32259
|
+
* lose updates.
|
|
32333
32260
|
*/
|
|
32334
|
-
|
|
32335
|
-
|
|
32336
|
-
|
|
32337
|
-
|
|
32338
|
-
|
|
32339
|
-
|
|
32340
|
-
|
|
32341
|
-
|
|
32342
|
-
|
|
32343
|
-
|
|
32344
|
-
|
|
32345
|
-
|
|
32346
|
-
|
|
32347
|
-
|
|
32348
|
-
|
|
32349
|
-
|
|
32350
|
-
|
|
32351
|
-
|
|
32352
|
-
|
|
32353
|
-
|
|
32354
|
-
|
|
32355
|
-
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
32362
|
-
|
|
32363
|
-
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32367
|
-
|
|
32368
|
-
|
|
32369
|
-
|
|
32370
|
-
|
|
32371
|
-
|
|
32372
|
-
|
|
32373
|
-
|
|
32374
|
-
|
|
32375
|
-
|
|
32376
|
-
|
|
32377
|
-
|
|
32378
|
-
|
|
32379
|
-
|
|
32380
|
-
|
|
32381
|
-
|
|
32382
|
-
|
|
32383
|
-
|
|
32384
|
-
|
|
32385
|
-
|
|
32386
|
-
|
|
32387
|
-
|
|
32388
|
-
|
|
32389
|
-
|
|
32390
|
-
|
|
32391
|
-
|
|
32392
|
-
|
|
32393
|
-
|
|
32394
|
-
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
32404
|
-
|
|
32405
|
-
|
|
32406
|
-
|
|
32407
|
-
|
|
32408
|
-
|
|
32409
|
-
|
|
32410
|
-
|
|
32411
|
-
|
|
32412
|
-
|
|
32413
|
-
|
|
32414
|
-
|
|
32415
|
-
|
|
32416
|
-
|
|
32417
|
-
|
|
32418
|
-
|
|
32419
|
-
|
|
32420
|
-
|
|
32421
|
-
|
|
32422
|
-
|
|
32423
|
-
|
|
32261
|
+
var SharedCounter = class {
|
|
32262
|
+
/** Int32 view over the backing buffer. */
|
|
32263
|
+
#view;
|
|
32264
|
+
/** Element index of this counter's slot within {@link #view}. */
|
|
32265
|
+
#index;
|
|
32266
|
+
/** The `SharedArrayBuffer` backing this counter. */
|
|
32267
|
+
buffer;
|
|
32268
|
+
/**
|
|
32269
|
+
* @param buffer - Existing shared buffer to attach to. When omitted, a
|
|
32270
|
+
* fresh single-slot (`4`-byte) `SharedArrayBuffer` is
|
|
32271
|
+
* allocated and zero-initialised.
|
|
32272
|
+
* @param index - Element index (in `Int32` units) of the counter slot.
|
|
32273
|
+
* Defaults to `0`. Must be a non-negative integer that
|
|
32274
|
+
* fits within `buffer`.
|
|
32275
|
+
* @throws If shared memory is unavailable (when allocating), or if
|
|
32276
|
+
* `index` is out of range for the supplied `buffer`.
|
|
32277
|
+
*/
|
|
32278
|
+
constructor(buffer, index) {
|
|
32279
|
+
const slot = index ?? 0;
|
|
32280
|
+
if (!Number.isInteger(slot) || slot < 0) throw new Error(`SharedCounter: index must be a non-negative integer, received ${slot}`);
|
|
32281
|
+
const buf = buffer ?? allocShared(INT32_BYTES);
|
|
32282
|
+
if ((slot + 1) * INT32_BYTES > buf.byteLength) throw new Error(`SharedCounter: index ${slot} is out of range for a ${buf.byteLength}-byte buffer`);
|
|
32283
|
+
this.buffer = buf;
|
|
32284
|
+
this.#view = new Int32Array(buf);
|
|
32285
|
+
this.#index = slot;
|
|
32286
|
+
}
|
|
32287
|
+
/**
|
|
32288
|
+
* The current counter value, read atomically via `Atomics.load`.
|
|
32289
|
+
*/
|
|
32290
|
+
get value() {
|
|
32291
|
+
return Atomics.load(this.#view, this.#index);
|
|
32292
|
+
}
|
|
32293
|
+
/**
|
|
32294
|
+
* Atomically add `n` to the counter.
|
|
32295
|
+
*
|
|
32296
|
+
* Per `Atomics.add` semantics this returns the **previous** value — the
|
|
32297
|
+
* value the slot held *before* `n` was added — not the new total. Read
|
|
32298
|
+
* {@link SharedCounter.value} afterwards for the updated total.
|
|
32299
|
+
*
|
|
32300
|
+
* @param n - Integer addend (may be negative to subtract).
|
|
32301
|
+
* @returns The value that was stored before the addition.
|
|
32302
|
+
*/
|
|
32303
|
+
add(n) {
|
|
32304
|
+
return Atomics.add(this.#view, this.#index, n | 0);
|
|
32305
|
+
}
|
|
32306
|
+
/**
|
|
32307
|
+
* Atomically increment the counter by one.
|
|
32308
|
+
*
|
|
32309
|
+
* As with {@link SharedCounter.add}, the returned number is the
|
|
32310
|
+
* **pre-increment** value.
|
|
32311
|
+
*
|
|
32312
|
+
* @returns The value that was stored before incrementing.
|
|
32313
|
+
*/
|
|
32314
|
+
increment() {
|
|
32315
|
+
return Atomics.add(this.#view, this.#index, 1);
|
|
32316
|
+
}
|
|
32317
|
+
/**
|
|
32318
|
+
* Atomically set the counter to `next` iff it currently equals
|
|
32319
|
+
* `expected`, via `Atomics.compareExchange`.
|
|
32320
|
+
*
|
|
32321
|
+
* @param expected - The value the swap is conditional on.
|
|
32322
|
+
* @param next - The value to store when `expected` matches.
|
|
32323
|
+
* @returns The value that was at the slot **before** the call. The swap
|
|
32324
|
+
* succeeded iff this equals `expected`.
|
|
32325
|
+
*/
|
|
32326
|
+
compareExchange(expected, next) {
|
|
32327
|
+
return Atomics.compareExchange(this.#view, this.#index, expected | 0, next | 0);
|
|
32328
|
+
}
|
|
32329
|
+
};
|
|
32330
|
+
/** Slot value meaning "flag is clear / unset". */
|
|
32331
|
+
const FLAG_UNSET = 0;
|
|
32332
|
+
/** Slot value meaning "flag is set". */
|
|
32333
|
+
const FLAG_SET = 1;
|
|
32334
|
+
/**
|
|
32335
|
+
* A one-bit synchronisation gate over a single `Int32Array` slot
|
|
32336
|
+
* (`0` = unset, `1` = set), supporting `Atomics.wait` / `Atomics.notify`.
|
|
32337
|
+
*
|
|
32338
|
+
* Producers call {@link SharedFlag.set} then {@link SharedFlag.notify} to
|
|
32339
|
+
* release agents blocked in {@link SharedFlag.wait}.
|
|
32340
|
+
*
|
|
32341
|
+
* IMPORTANT — `Atomics.wait` may only block on a *non-main* agent. On the
|
|
32342
|
+
* main browser thread it throws `TypeError`, and on any main thread it
|
|
32343
|
+
* would freeze the event loop. {@link SharedFlag.wait} feature-detects
|
|
32344
|
+
* blocking support and, when blocking is not permitted, degrades to a
|
|
32345
|
+
* single non-blocking check (returning `'ok'` if already set, otherwise
|
|
32346
|
+
* `'not-equal'`) instead of throwing. {@link SharedFlag.notify} is always
|
|
32347
|
+
* safe to call from any thread.
|
|
32348
|
+
*/
|
|
32349
|
+
var SharedFlag = class {
|
|
32350
|
+
/** Int32 view over the backing buffer; the flag lives at index 0. */
|
|
32351
|
+
#view;
|
|
32352
|
+
/** The `SharedArrayBuffer` backing this flag. */
|
|
32353
|
+
buffer;
|
|
32354
|
+
/**
|
|
32355
|
+
* @param buffer - Existing shared buffer to attach to (its first `Int32`
|
|
32356
|
+
* slot is used). When omitted, a fresh `4`-byte
|
|
32357
|
+
* `SharedArrayBuffer` is allocated, starting cleared.
|
|
32358
|
+
* @throws If shared memory is unavailable when allocating, or `buffer`
|
|
32359
|
+
* is too small to hold one `Int32`.
|
|
32360
|
+
*/
|
|
32361
|
+
constructor(buffer) {
|
|
32362
|
+
const buf = buffer ?? allocShared(INT32_BYTES);
|
|
32363
|
+
if (buf.byteLength < INT32_BYTES) throw new Error(`SharedFlag: buffer must be at least ${INT32_BYTES} bytes, got ${buf.byteLength}`);
|
|
32364
|
+
this.buffer = buf;
|
|
32365
|
+
this.#view = new Int32Array(buf);
|
|
32366
|
+
}
|
|
32367
|
+
/**
|
|
32368
|
+
* Atomically set the flag. Does not itself wake waiters — call
|
|
32369
|
+
* {@link SharedFlag.notify} afterwards to release any blocked agents.
|
|
32370
|
+
*/
|
|
32371
|
+
set() {
|
|
32372
|
+
Atomics.store(this.#view, 0, FLAG_SET);
|
|
32373
|
+
}
|
|
32374
|
+
/**
|
|
32375
|
+
* Atomically clear the flag back to its unset state.
|
|
32376
|
+
*/
|
|
32377
|
+
clear() {
|
|
32378
|
+
Atomics.store(this.#view, 0, FLAG_UNSET);
|
|
32379
|
+
}
|
|
32380
|
+
/**
|
|
32381
|
+
* @returns `true` iff the flag is currently set, read via `Atomics.load`.
|
|
32382
|
+
*/
|
|
32383
|
+
isSet() {
|
|
32384
|
+
return Atomics.load(this.#view, 0) === FLAG_SET;
|
|
32385
|
+
}
|
|
32386
|
+
/**
|
|
32387
|
+
* Block until the flag becomes set, or until `timeoutMs` elapses.
|
|
32388
|
+
*
|
|
32389
|
+
* Implemented with `Atomics.wait` on the unset value: while the slot
|
|
32390
|
+
* still reads `0` (unset) the agent sleeps; a {@link SharedFlag.set} +
|
|
32391
|
+
* {@link SharedFlag.notify} from another agent wakes it.
|
|
32392
|
+
*
|
|
32393
|
+
* Blocking is only legal off the main thread. When `Atomics.wait` is not
|
|
32394
|
+
* allowed here (e.g. the main browser thread), this method does **not**
|
|
32395
|
+
* throw: it performs a single non-blocking check and returns `'ok'` if
|
|
32396
|
+
* the flag is already set, or `'not-equal'` otherwise. Callers that need
|
|
32397
|
+
* to truly block must run on a dedicated worker.
|
|
32398
|
+
*
|
|
32399
|
+
* @param timeoutMs - Optional timeout in milliseconds. Omit (or pass
|
|
32400
|
+
* `Infinity`) to wait indefinitely.
|
|
32401
|
+
* @returns `'ok'` when woken with the flag still unset at entry,
|
|
32402
|
+
* `'timed-out'` if the timeout expired, or `'not-equal'` if the
|
|
32403
|
+
* flag was already set on entry (nothing to wait for).
|
|
32404
|
+
*/
|
|
32405
|
+
wait(timeoutMs) {
|
|
32406
|
+
if (Atomics.load(this.#view, 0) === FLAG_SET) return "not-equal";
|
|
32407
|
+
if (typeof Atomics.wait !== "function") return Atomics.load(this.#view, 0) === FLAG_SET ? "ok" : "not-equal";
|
|
32408
|
+
try {
|
|
32409
|
+
const timeout = timeoutMs ?? Infinity;
|
|
32410
|
+
return Atomics.wait(this.#view, 0, FLAG_UNSET, timeout);
|
|
32411
|
+
} catch {
|
|
32412
|
+
return Atomics.load(this.#view, 0) === FLAG_SET ? "ok" : "not-equal";
|
|
32413
|
+
}
|
|
32414
|
+
}
|
|
32415
|
+
/**
|
|
32416
|
+
* Wake up to `count` agents blocked in {@link SharedFlag.wait} on this
|
|
32417
|
+
* flag, via `Atomics.notify`. Safe to call from any thread.
|
|
32418
|
+
*
|
|
32419
|
+
* @param count - Maximum number of waiters to wake. Defaults to
|
|
32420
|
+
* `Infinity` (wake all).
|
|
32421
|
+
* @returns The number of agents actually woken — `0` when none were
|
|
32422
|
+
* waiting.
|
|
32423
|
+
*/
|
|
32424
|
+
notify(count) {
|
|
32425
|
+
return Atomics.notify(this.#view, 0, count ?? Infinity);
|
|
32426
|
+
}
|
|
32427
|
+
};
|
|
32428
|
+
/**
|
|
32429
|
+
* Control-region layout, in `Int32` slots, preceding the data region:
|
|
32430
|
+
*
|
|
32431
|
+
* - slot 0 (`HEAD_SLOT`): read cursor — byte offset into the data region of
|
|
32432
|
+
* the next frame to be popped by the (single) consumer.
|
|
32433
|
+
* - slot 1 (`TAIL_SLOT`): write cursor — byte offset into the data region
|
|
32434
|
+
* at which the next frame will be pushed by the (single) producer.
|
|
32435
|
+
*
|
|
32436
|
+
* Both cursors range over `[0, dataBytes)` and wrap modulo the data size.
|
|
32437
|
+
* The buffer is empty when `head === tail` and considered full when the
|
|
32438
|
+
* next write would catch up to `head` (one frame slot is kept free to
|
|
32439
|
+
* disambiguate the full/empty states).
|
|
32440
|
+
*/
|
|
32441
|
+
const HEAD_SLOT = 0;
|
|
32442
|
+
const TAIL_SLOT = 1;
|
|
32443
|
+
/** Number of Int32 control slots ([head, tail]) ahead of the data region. */
|
|
32444
|
+
const CONTROL_SLOTS = 2;
|
|
32445
|
+
/** Byte size of the control region. */
|
|
32446
|
+
const CONTROL_BYTES = CONTROL_SLOTS * INT32_BYTES;
|
|
32447
|
+
/** Bytes used by each frame's little-endian `uint32` length header. */
|
|
32448
|
+
const LENGTH_HEADER_BYTES = 4;
|
|
32449
|
+
/**
|
|
32450
|
+
* A lock-free **single-producer / single-consumer** (SPSC) byte ring buffer
|
|
32451
|
+
* over a `SharedArrayBuffer`, suitable for streaming chunks between exactly
|
|
32452
|
+
* one producing worker and one consuming worker.
|
|
32453
|
+
*
|
|
32454
|
+
* ## Contract
|
|
32455
|
+
*
|
|
32456
|
+
* - **SPSC only.** Correctness relies on there being at most one concurrent
|
|
32457
|
+
* {@link SharedRingBuffer.push} caller (the producer) and at most one
|
|
32458
|
+
* concurrent {@link SharedRingBuffer.pop} caller (the consumer). Multiple
|
|
32459
|
+
* producers or multiple consumers are **not** supported and will corrupt
|
|
32460
|
+
* the cursors. (The producer and consumer may be different agents.)
|
|
32461
|
+
* - {@link SharedRingBuffer.push} returns `false` (writing nothing) when the
|
|
32462
|
+
* frame would not fit in the currently free space.
|
|
32463
|
+
* - {@link SharedRingBuffer.pop} returns `null` when the ring is empty, or
|
|
32464
|
+
* when the next frame is larger than the caller's `maxBytes` budget (the
|
|
32465
|
+
* frame is left intact for a later, larger pop).
|
|
32466
|
+
*
|
|
32467
|
+
* ## SAB layout
|
|
32468
|
+
*
|
|
32469
|
+
* `[ head:int32 | tail:int32 | ...dataBytes ]`
|
|
32470
|
+
*
|
|
32471
|
+
* The `head`/`tail` cursors are published with `Atomics.store` and read with
|
|
32472
|
+
* `Atomics.load`, which establishes the happens-before edge that makes the
|
|
32473
|
+
* non-atomic byte writes into the data region visible to the consumer. Each
|
|
32474
|
+
* frame is encoded as a 4-byte little-endian length header followed by that
|
|
32475
|
+
* many payload bytes; both header and payload wrap around the data region.
|
|
32476
|
+
*/
|
|
32477
|
+
var SharedRingBuffer = class SharedRingBuffer {
|
|
32478
|
+
/** Int32 view over the control slots (head/tail). */
|
|
32479
|
+
#control;
|
|
32480
|
+
/** Byte view over the data region (excludes the control slots). */
|
|
32481
|
+
#data;
|
|
32482
|
+
/** Size, in bytes, of the data region. */
|
|
32483
|
+
#size;
|
|
32484
|
+
/** The `SharedArrayBuffer` backing this ring. */
|
|
32485
|
+
buffer;
|
|
32486
|
+
/**
|
|
32487
|
+
* Allocate a new ring whose data region holds `capacityBytes` bytes
|
|
32488
|
+
* (the backing `SharedArrayBuffer` is `capacityBytes` + control overhead).
|
|
32489
|
+
*
|
|
32490
|
+
* @param capacityBytes - Size of the data region in bytes. Must be a
|
|
32491
|
+
* positive integer.
|
|
32492
|
+
* @param existingBuffer - Internal: when supplied, the ring attaches to
|
|
32493
|
+
* this already-allocated buffer instead of creating
|
|
32494
|
+
* a fresh one (used by {@link SharedRingBuffer.fromBuffer}).
|
|
32495
|
+
* Its data region must equal `capacityBytes`.
|
|
32496
|
+
* @throws If `capacityBytes` is not a positive integer, or shared memory
|
|
32497
|
+
* is unavailable.
|
|
32498
|
+
*/
|
|
32499
|
+
constructor(capacityBytes, existingBuffer) {
|
|
32500
|
+
if (!Number.isInteger(capacityBytes) || capacityBytes <= 0) throw new Error(`SharedRingBuffer: capacityBytes must be a positive integer, received ${capacityBytes}`);
|
|
32501
|
+
const buf = existingBuffer ?? allocShared(CONTROL_BYTES + capacityBytes);
|
|
32502
|
+
this.buffer = buf;
|
|
32503
|
+
this.#control = new Int32Array(buf, 0, CONTROL_SLOTS);
|
|
32504
|
+
this.#data = new Uint8Array(buf, CONTROL_BYTES, capacityBytes);
|
|
32505
|
+
this.#size = capacityBytes;
|
|
32506
|
+
}
|
|
32507
|
+
/**
|
|
32508
|
+
* Attach a second `SharedRingBuffer` view to an existing buffer — e.g.
|
|
32509
|
+
* the consumer side wrapping a buffer received from the producer via
|
|
32510
|
+
* `postMessage`. No cursors are reset; the view observes whatever the
|
|
32511
|
+
* other side has already published.
|
|
32512
|
+
*
|
|
32513
|
+
* @param buffer - A `SharedArrayBuffer` previously created by a
|
|
32514
|
+
* `SharedRingBuffer` constructor.
|
|
32515
|
+
* @returns A `SharedRingBuffer` sharing `buffer`'s cursors and data.
|
|
32516
|
+
* @throws If `buffer` is too small to contain the control region.
|
|
32517
|
+
*/
|
|
32518
|
+
static fromBuffer(buffer) {
|
|
32519
|
+
if (buffer.byteLength <= CONTROL_BYTES) throw new Error(`SharedRingBuffer.fromBuffer: buffer too small (${buffer.byteLength} bytes)`);
|
|
32520
|
+
return new SharedRingBuffer(buffer.byteLength - CONTROL_BYTES, buffer);
|
|
32521
|
+
}
|
|
32522
|
+
/** @internal Free bytes available for a new frame's payload + header. */
|
|
32523
|
+
#freeBytes(head, tail) {
|
|
32524
|
+
if (tail >= head) return this.#size - (tail - head) - 1;
|
|
32525
|
+
return head - tail - 1;
|
|
32526
|
+
}
|
|
32527
|
+
/**
|
|
32528
|
+
* Push a single byte frame onto the ring (producer side).
|
|
32529
|
+
*
|
|
32530
|
+
* The frame is stored as a length-prefixed record. Returns `false`
|
|
32531
|
+
* without modifying the ring if there is not enough free space for the
|
|
32532
|
+
* header plus payload.
|
|
32533
|
+
*
|
|
32534
|
+
* @param bytes - Payload to enqueue (may be empty).
|
|
32535
|
+
* @returns `true` if enqueued, `false` if the ring was too full.
|
|
32536
|
+
*/
|
|
32537
|
+
push(bytes) {
|
|
32538
|
+
const head = Atomics.load(this.#control, HEAD_SLOT);
|
|
32539
|
+
const tail = Atomics.load(this.#control, TAIL_SLOT);
|
|
32540
|
+
if (LENGTH_HEADER_BYTES + bytes.length > this.#freeBytes(head, tail)) return false;
|
|
32541
|
+
let cursor = tail;
|
|
32542
|
+
const len = bytes.length;
|
|
32543
|
+
cursor = this.#writeByte(cursor, len & 255);
|
|
32544
|
+
cursor = this.#writeByte(cursor, len >>> 8 & 255);
|
|
32545
|
+
cursor = this.#writeByte(cursor, len >>> 16 & 255);
|
|
32546
|
+
cursor = this.#writeByte(cursor, len >>> 24 & 255);
|
|
32547
|
+
for (let i = 0; i < len; i += 1) cursor = this.#writeByte(cursor, bytes[i] ?? 0);
|
|
32548
|
+
Atomics.store(this.#control, TAIL_SLOT, cursor);
|
|
32549
|
+
return true;
|
|
32550
|
+
}
|
|
32551
|
+
/**
|
|
32552
|
+
* Pop the next byte frame from the ring (consumer side).
|
|
32553
|
+
*
|
|
32554
|
+
* @param maxBytes - Maximum payload size the caller is willing to receive.
|
|
32555
|
+
* If the next frame's payload exceeds this, the frame is
|
|
32556
|
+
* left in place and `null` is returned.
|
|
32557
|
+
* @returns The dequeued payload as a fresh `Uint8Array`, or `null` when
|
|
32558
|
+
* the ring is empty or the next frame exceeds `maxBytes`.
|
|
32559
|
+
*/
|
|
32560
|
+
pop(maxBytes) {
|
|
32561
|
+
const head = Atomics.load(this.#control, HEAD_SLOT);
|
|
32562
|
+
if (head === Atomics.load(this.#control, TAIL_SLOT)) return null;
|
|
32563
|
+
let cursor = head;
|
|
32564
|
+
const b0 = this.#readByte(cursor);
|
|
32565
|
+
cursor = (cursor + 1) % this.#size;
|
|
32566
|
+
const b1 = this.#readByte(cursor);
|
|
32567
|
+
cursor = (cursor + 1) % this.#size;
|
|
32568
|
+
const b2 = this.#readByte(cursor);
|
|
32569
|
+
cursor = (cursor + 1) % this.#size;
|
|
32570
|
+
const b3 = this.#readByte(cursor);
|
|
32571
|
+
cursor = (cursor + 1) % this.#size;
|
|
32572
|
+
const len = (b0 | b1 << 8 | b2 << 16 | b3 << 24) >>> 0;
|
|
32573
|
+
if (len > maxBytes) return null;
|
|
32574
|
+
const out = new Uint8Array(len);
|
|
32575
|
+
for (let i = 0; i < len; i += 1) {
|
|
32576
|
+
out[i] = this.#readByte(cursor);
|
|
32577
|
+
cursor = (cursor + 1) % this.#size;
|
|
32578
|
+
}
|
|
32579
|
+
Atomics.store(this.#control, HEAD_SLOT, cursor);
|
|
32580
|
+
return out;
|
|
32581
|
+
}
|
|
32582
|
+
/**
|
|
32583
|
+
* Write one byte at `cursor` (already within `[0, size)`) and return the
|
|
32584
|
+
* next cursor position, wrapping around the data region.
|
|
32585
|
+
*/
|
|
32586
|
+
#writeByte(cursor, value) {
|
|
32587
|
+
this.#data[cursor] = value;
|
|
32588
|
+
return (cursor + 1) % this.#size;
|
|
32589
|
+
}
|
|
32590
|
+
/**
|
|
32591
|
+
* Read one byte at `cursor` (already within `[0, size)`). The `?? 0` only
|
|
32592
|
+
* satisfies `noUncheckedIndexedAccess`; `cursor` is always in range.
|
|
32593
|
+
*/
|
|
32594
|
+
#readByte(cursor) {
|
|
32595
|
+
return this.#data[cursor] ?? 0;
|
|
32596
|
+
}
|
|
32597
|
+
};
|
|
32598
|
+
//#endregion
|
|
32599
|
+
//#region src/runtime/memoryBudget.ts
|
|
32600
|
+
/**
|
|
32601
|
+
* @module runtime/memoryBudget
|
|
32602
|
+
*
|
|
32603
|
+
* A memory budget / cap used to bound the processing of *untrusted* PDFs as a
|
|
32604
|
+
* defence against decompression bombs and out-of-memory (OOM) attacks.
|
|
32605
|
+
*
|
|
32606
|
+
* ## What this is — and what it is NOT
|
|
32607
|
+
*
|
|
32608
|
+
* {@link MemoryBudget} is a **pure accounting guard**. It is a plain integer
|
|
32609
|
+
* counter with a ceiling. It does **NOT** measure real process memory (RSS),
|
|
32610
|
+
* heap usage, `performance.memory`, or anything reported by the host. It makes
|
|
32611
|
+
* **no** claim of measuring actual memory and performs **no** acceleration of
|
|
32612
|
+
* any kind — there are no workers, SIMD, threads, `Atomics`, or
|
|
32613
|
+
* `SharedArrayBuffer` involved.
|
|
32614
|
+
*
|
|
32615
|
+
* The intended usage pattern is *predictive*: before a caller allocates a
|
|
32616
|
+
* large buffer (for example, the expected output length of an inflated /
|
|
32617
|
+
* decoded stream, which a malicious PDF can declare as enormous), it reports
|
|
32618
|
+
* that size to the budget via {@link MemoryBudget.allocate} (or
|
|
32619
|
+
* {@link MemoryBudget.tryAllocate}). If the reported size would push total
|
|
32620
|
+
* tracked usage past the configured limit, the allocation is rejected
|
|
32621
|
+
* **before** the real memory is ever requested — so a bomb is stopped early
|
|
32622
|
+
* instead of being materialised and crashing the runtime.
|
|
32623
|
+
*
|
|
32624
|
+
* Because it is pure accounting, its accuracy is exactly as good as the sizes
|
|
32625
|
+
* callers report. It is a budget, not a profiler.
|
|
32626
|
+
*
|
|
32627
|
+
* ## Runtime
|
|
32628
|
+
*
|
|
32629
|
+
* This module uses only ECMAScript primitives (numbers, classes, closures).
|
|
32630
|
+
* It assumes no Node-only globals and runs unchanged on Node, Deno, Bun,
|
|
32631
|
+
* Cloudflare Workers, and browsers.
|
|
32632
|
+
*/
|
|
32633
|
+
/**
|
|
32634
|
+
* Thrown by {@link MemoryBudget.allocate} (and {@link MemoryBudget.withAllocation})
|
|
32635
|
+
* when a requested allocation would exceed the configured limit.
|
|
32636
|
+
*
|
|
32637
|
+
* Carries the numbers involved so callers can surface a precise diagnostic:
|
|
32638
|
+
*
|
|
32639
|
+
* - {@link requested} — the size that was being allocated.
|
|
32640
|
+
* - {@link used} — the tracked usage *before* the rejected allocation.
|
|
32641
|
+
* - {@link limit} — the configured ceiling.
|
|
32642
|
+
*/
|
|
32643
|
+
var MemoryBudgetExceededError = class extends Error {
|
|
32644
|
+
/** The allocation size, in bytes, that was rejected. */
|
|
32645
|
+
requested;
|
|
32646
|
+
/** The configured budget ceiling, in bytes. */
|
|
32647
|
+
limit;
|
|
32648
|
+
/** The tracked usage, in bytes, at the moment of rejection. */
|
|
32649
|
+
used;
|
|
32650
|
+
/**
|
|
32651
|
+
* @param requested - The size, in bytes, that was being allocated.
|
|
32652
|
+
* @param used - The tracked usage, in bytes, before the allocation.
|
|
32653
|
+
* @param limit - The configured ceiling, in bytes.
|
|
32654
|
+
*/
|
|
32655
|
+
constructor(requested, used, limit) {
|
|
32656
|
+
super(`Memory budget exceeded: requested ${requested} byte(s) with ${used} of ${limit} byte(s) already in use (would total ${used + requested}).`);
|
|
32657
|
+
this.name = "MemoryBudgetExceededError";
|
|
32658
|
+
this.requested = requested;
|
|
32659
|
+
this.used = used;
|
|
32660
|
+
this.limit = limit;
|
|
32661
|
+
}
|
|
32662
|
+
};
|
|
32663
|
+
/**
|
|
32664
|
+
* Validate a configured limit. The limit must be a finite number strictly
|
|
32665
|
+
* greater than zero; `NaN`, negative values, and `Infinity` are rejected.
|
|
32666
|
+
*
|
|
32667
|
+
* @throws {TypeError} If the limit is not a finite number `> 0`.
|
|
32668
|
+
*/
|
|
32669
|
+
function assertValidLimit(limitBytes) {
|
|
32670
|
+
if (typeof limitBytes !== "number" || !Number.isFinite(limitBytes) || limitBytes <= 0) throw new TypeError(`MemoryBudget: limitBytes must be a finite number greater than 0, received ${String(limitBytes)}.`);
|
|
32671
|
+
}
|
|
32672
|
+
/**
|
|
32673
|
+
* Validate a size passed to {@link MemoryBudget.allocate},
|
|
32674
|
+
* {@link MemoryBudget.tryAllocate}, {@link MemoryBudget.release}, or
|
|
32675
|
+
* {@link MemoryBudget.withAllocation}. The size must be a finite,
|
|
32676
|
+
* non-negative number; `NaN`, negative values, and `Infinity` are rejected.
|
|
32677
|
+
*
|
|
32678
|
+
* @throws {TypeError} If the size is not a finite number `>= 0`.
|
|
32679
|
+
*/
|
|
32680
|
+
function assertValidSize(bytes, method) {
|
|
32681
|
+
if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes < 0) throw new TypeError(`MemoryBudget.${method}: bytes must be a finite, non-negative number, received ${String(bytes)}.`);
|
|
32682
|
+
}
|
|
32683
|
+
/**
|
|
32684
|
+
* A bounded memory accounting guard. See the {@link module:runtime/memoryBudget module documentation}
|
|
32685
|
+
* for the precise semantics — in particular, that this tracks *reported*
|
|
32686
|
+
* sizes and does not measure real RSS.
|
|
32687
|
+
*/
|
|
32688
|
+
var MemoryBudget = class {
|
|
32689
|
+
/** The configured ceiling, in bytes. */
|
|
32690
|
+
#limit;
|
|
32691
|
+
/** The optional pre-throw notification callback. */
|
|
32692
|
+
#onExceed;
|
|
32693
|
+
/** Currently tracked usage, in bytes. Always `0 <= used <= limit`. */
|
|
32694
|
+
#used = 0;
|
|
32695
|
+
/**
|
|
32696
|
+
* @param options - The {@link MemoryBudgetOptions}. `limitBytes` must be a
|
|
32697
|
+
* finite number greater than `0`.
|
|
32698
|
+
* @throws {TypeError} If `limitBytes` is not a finite number `> 0`.
|
|
32699
|
+
*/
|
|
32700
|
+
constructor(options) {
|
|
32701
|
+
assertValidLimit(options.limitBytes);
|
|
32702
|
+
this.#limit = options.limitBytes;
|
|
32703
|
+
this.#onExceed = options.onExceed;
|
|
32704
|
+
}
|
|
32705
|
+
/** Currently tracked usage, in bytes. */
|
|
32706
|
+
get used() {
|
|
32707
|
+
return this.#used;
|
|
32708
|
+
}
|
|
32709
|
+
/** The configured ceiling, in bytes. */
|
|
32710
|
+
get limit() {
|
|
32711
|
+
return this.#limit;
|
|
32712
|
+
}
|
|
32713
|
+
/** Bytes still available before the limit is reached (never negative). */
|
|
32714
|
+
get remaining() {
|
|
32715
|
+
return this.#limit - this.#used;
|
|
32716
|
+
}
|
|
32717
|
+
/**
|
|
32718
|
+
* Reserve `bytes` against the budget.
|
|
32719
|
+
*
|
|
32720
|
+
* If `used + bytes` would exceed the limit, {@link onExceed} (if supplied)
|
|
32721
|
+
* is invoked first and then a {@link MemoryBudgetExceededError} is thrown;
|
|
32722
|
+
* tracked usage is left unchanged in that case.
|
|
32723
|
+
*
|
|
32724
|
+
* @param bytes - The size to reserve. Must be a finite number `>= 0`.
|
|
32725
|
+
* @throws {TypeError} If `bytes` is negative, `NaN`, or `Infinity`.
|
|
32726
|
+
* @throws {MemoryBudgetExceededError} If the allocation would exceed the limit.
|
|
32727
|
+
*/
|
|
32728
|
+
allocate(bytes) {
|
|
32729
|
+
assertValidSize(bytes, "allocate");
|
|
32730
|
+
if (this.#used + bytes > this.#limit) {
|
|
32731
|
+
this.#onExceed?.({
|
|
32732
|
+
requested: bytes,
|
|
32733
|
+
used: this.#used,
|
|
32734
|
+
limit: this.#limit
|
|
32735
|
+
});
|
|
32736
|
+
throw new MemoryBudgetExceededError(bytes, this.#used, this.#limit);
|
|
32737
|
+
}
|
|
32738
|
+
this.#used += bytes;
|
|
32739
|
+
}
|
|
32740
|
+
/**
|
|
32741
|
+
* Non-throwing variant of {@link allocate}. Reserves `bytes` and returns
|
|
32742
|
+
* `true` on success, or returns `false` (leaving usage unchanged) if the
|
|
32743
|
+
* allocation would exceed the limit. {@link onExceed} is **not** invoked.
|
|
32744
|
+
*
|
|
32745
|
+
* @param bytes - The size to reserve. Must be a finite number `>= 0`.
|
|
32746
|
+
* @returns `true` if reserved, `false` if it would exceed the limit.
|
|
32747
|
+
* @throws {TypeError} If `bytes` is negative, `NaN`, or `Infinity`.
|
|
32748
|
+
*/
|
|
32749
|
+
tryAllocate(bytes) {
|
|
32750
|
+
assertValidSize(bytes, "tryAllocate");
|
|
32751
|
+
if (this.#used + bytes > this.#limit) return false;
|
|
32752
|
+
this.#used += bytes;
|
|
32753
|
+
return true;
|
|
32754
|
+
}
|
|
32755
|
+
/**
|
|
32756
|
+
* Return `bytes` to the budget. Usage is clamped at `0`, so over-releasing
|
|
32757
|
+
* (or releasing more than was reserved) never produces a negative value.
|
|
32758
|
+
*
|
|
32759
|
+
* @param bytes - The size to release. Must be a finite number `>= 0`.
|
|
32760
|
+
* @throws {TypeError} If `bytes` is negative, `NaN`, or `Infinity`.
|
|
32761
|
+
*/
|
|
32762
|
+
release(bytes) {
|
|
32763
|
+
assertValidSize(bytes, "release");
|
|
32764
|
+
this.#used = Math.max(0, this.#used - bytes);
|
|
32765
|
+
}
|
|
32766
|
+
/** Reset tracked usage to zero. The limit is unchanged. */
|
|
32767
|
+
reset() {
|
|
32768
|
+
this.#used = 0;
|
|
32769
|
+
}
|
|
32770
|
+
/**
|
|
32771
|
+
* Reserve `bytes`, run `fn`, and release `bytes` in a `finally` block so the
|
|
32772
|
+
* reservation is returned even if `fn` throws.
|
|
32773
|
+
*
|
|
32774
|
+
* If the reservation itself would exceed the limit, `fn` is never invoked
|
|
32775
|
+
* and a {@link MemoryBudgetExceededError} is thrown (nothing to release).
|
|
32776
|
+
*
|
|
32777
|
+
* @typeParam T - The return type of `fn`.
|
|
32778
|
+
* @param bytes - The size to reserve for the duration of `fn`.
|
|
32779
|
+
* @param fn - The work to run while the reservation is held.
|
|
32780
|
+
* @returns Whatever `fn` returns.
|
|
32781
|
+
* @throws {TypeError} If `bytes` is negative, `NaN`, or `Infinity`.
|
|
32782
|
+
* @throws {MemoryBudgetExceededError} If the reservation would exceed the limit.
|
|
32783
|
+
*/
|
|
32784
|
+
withAllocation(bytes, fn) {
|
|
32785
|
+
this.allocate(bytes);
|
|
32786
|
+
try {
|
|
32787
|
+
return fn();
|
|
32788
|
+
} finally {
|
|
32789
|
+
this.release(bytes);
|
|
32790
|
+
}
|
|
32791
|
+
}
|
|
32792
|
+
};
|
|
32793
|
+
/**
|
|
32794
|
+
* Create a {@link MemoryBudget} with the given ceiling.
|
|
32795
|
+
*
|
|
32796
|
+
* The limit must be a finite number strictly greater than `0`; `NaN`,
|
|
32797
|
+
* negative values, and `Infinity` are all rejected with a {@link TypeError}.
|
|
32798
|
+
* (An infinite budget is intentionally disallowed: the whole point is to cap
|
|
32799
|
+
* untrusted input, and `Infinity` would silently disable the guard.)
|
|
32800
|
+
*
|
|
32801
|
+
* @param limitBytes - The ceiling, in bytes. Must be finite and `> 0`.
|
|
32802
|
+
* @returns A new {@link MemoryBudget}.
|
|
32803
|
+
* @throws {TypeError} If `limitBytes` is not a finite number `> 0`.
|
|
32804
|
+
*/
|
|
32805
|
+
function createMemoryBudget(limitBytes) {
|
|
32806
|
+
return new MemoryBudget({ limitBytes });
|
|
32807
|
+
}
|
|
32808
|
+
//#endregion
|
|
32809
|
+
//#region src/runtime/runtimeCapabilities.ts
|
|
32810
|
+
/**
|
|
32811
|
+
* SIMD probe: a function `() -> v128` whose body is `i32.const 0; i32x4.splat;
|
|
32812
|
+
* i8x16.popcnt`. The `v128` result type and the `0xfd …` SIMD opcodes only
|
|
32813
|
+
* validate on a host implementing the SIMD MVP. Verified to validate on a
|
|
32814
|
+
* SIMD-capable host and to be rejected by a host lacking SIMD.
|
|
32815
|
+
*/
|
|
32816
|
+
const WASM_SIMD_MODULE = new Uint8Array([
|
|
32817
|
+
0,
|
|
32818
|
+
97,
|
|
32819
|
+
115,
|
|
32820
|
+
109,
|
|
32821
|
+
1,
|
|
32822
|
+
0,
|
|
32823
|
+
0,
|
|
32824
|
+
0,
|
|
32825
|
+
1,
|
|
32826
|
+
5,
|
|
32827
|
+
1,
|
|
32828
|
+
96,
|
|
32829
|
+
0,
|
|
32830
|
+
1,
|
|
32831
|
+
123,
|
|
32832
|
+
3,
|
|
32833
|
+
2,
|
|
32834
|
+
1,
|
|
32835
|
+
0,
|
|
32836
|
+
10,
|
|
32837
|
+
10,
|
|
32838
|
+
1,
|
|
32839
|
+
8,
|
|
32840
|
+
0,
|
|
32841
|
+
65,
|
|
32842
|
+
0,
|
|
32843
|
+
253,
|
|
32844
|
+
15,
|
|
32845
|
+
253,
|
|
32846
|
+
98,
|
|
32847
|
+
11
|
|
32848
|
+
]);
|
|
32849
|
+
/**
|
|
32850
|
+
* Threads/atomics probe: a function whose body executes
|
|
32851
|
+
* `memory.atomic.notify` (`0xfe 0x00`) against a **shared** memory
|
|
32852
|
+
* (`flags = 0x03` ⇒ shared with a maximum). The shared-memory limits and the
|
|
32853
|
+
* `0xfe` atomic opcode only validate on a threads-capable host. Verified to
|
|
32854
|
+
* validate on such a host.
|
|
32855
|
+
*/
|
|
32856
|
+
const WASM_THREADS_MODULE = new Uint8Array([
|
|
32857
|
+
0,
|
|
32858
|
+
97,
|
|
32859
|
+
115,
|
|
32860
|
+
109,
|
|
32861
|
+
1,
|
|
32862
|
+
0,
|
|
32863
|
+
0,
|
|
32864
|
+
0,
|
|
32865
|
+
1,
|
|
32866
|
+
4,
|
|
32867
|
+
1,
|
|
32868
|
+
96,
|
|
32869
|
+
0,
|
|
32870
|
+
0,
|
|
32871
|
+
3,
|
|
32872
|
+
2,
|
|
32873
|
+
1,
|
|
32874
|
+
0,
|
|
32875
|
+
5,
|
|
32876
|
+
4,
|
|
32877
|
+
1,
|
|
32878
|
+
3,
|
|
32879
|
+
1,
|
|
32880
|
+
1,
|
|
32881
|
+
10,
|
|
32882
|
+
13,
|
|
32883
|
+
1,
|
|
32884
|
+
11,
|
|
32885
|
+
0,
|
|
32886
|
+
65,
|
|
32887
|
+
0,
|
|
32888
|
+
65,
|
|
32889
|
+
0,
|
|
32890
|
+
254,
|
|
32891
|
+
0,
|
|
32892
|
+
2,
|
|
32893
|
+
0,
|
|
32894
|
+
26,
|
|
32895
|
+
11
|
|
32896
|
+
]);
|
|
32897
|
+
/**
|
|
32898
|
+
* Bulk-memory probe: a function whose body executes `memory.fill`
|
|
32899
|
+
* (`0xfc 0x0b`) against a defined memory. The `0xfc 0x0b` opcode only validates
|
|
32900
|
+
* on a bulk-memory-capable host. Verified to validate with the memory section
|
|
32901
|
+
* present and to be rejected when the memory section is removed (proving
|
|
32902
|
+
* `memory.fill` is the discriminating instruction).
|
|
32903
|
+
*/
|
|
32904
|
+
const WASM_BULK_MEMORY_MODULE = new Uint8Array([
|
|
32905
|
+
0,
|
|
32906
|
+
97,
|
|
32907
|
+
115,
|
|
32908
|
+
109,
|
|
32909
|
+
1,
|
|
32910
|
+
0,
|
|
32911
|
+
0,
|
|
32912
|
+
0,
|
|
32913
|
+
1,
|
|
32914
|
+
4,
|
|
32915
|
+
1,
|
|
32916
|
+
96,
|
|
32917
|
+
0,
|
|
32918
|
+
0,
|
|
32919
|
+
3,
|
|
32920
|
+
2,
|
|
32921
|
+
1,
|
|
32922
|
+
0,
|
|
32923
|
+
5,
|
|
32924
|
+
3,
|
|
32925
|
+
1,
|
|
32926
|
+
0,
|
|
32927
|
+
1,
|
|
32928
|
+
10,
|
|
32929
|
+
13,
|
|
32930
|
+
1,
|
|
32931
|
+
11,
|
|
32932
|
+
0,
|
|
32933
|
+
65,
|
|
32934
|
+
0,
|
|
32935
|
+
65,
|
|
32936
|
+
0,
|
|
32937
|
+
65,
|
|
32938
|
+
0,
|
|
32939
|
+
252,
|
|
32940
|
+
11,
|
|
32941
|
+
0,
|
|
32942
|
+
11
|
|
32943
|
+
]);
|
|
32944
|
+
/**
|
|
32945
|
+
* `true` iff `WebAssembly` is an object exposing a callable `validate`.
|
|
32946
|
+
* `WebAssembly.validate` itself never throws for a well-formed buffer, so this
|
|
32947
|
+
* gate is what makes every WASM probe safe.
|
|
32948
|
+
*/
|
|
32949
|
+
function hasWasm() {
|
|
32950
|
+
try {
|
|
32951
|
+
return typeof WebAssembly === "object" && WebAssembly !== null && typeof WebAssembly.validate === "function";
|
|
32952
|
+
} catch {
|
|
32953
|
+
return false;
|
|
32954
|
+
}
|
|
32955
|
+
}
|
|
32956
|
+
/**
|
|
32957
|
+
* Validate a feature-detect module, returning `false` on any failure. Never
|
|
32958
|
+
* throws — a non-WASM host or a malformed buffer simply yields `false`.
|
|
32959
|
+
*/
|
|
32960
|
+
function validatesModule(bytes) {
|
|
32961
|
+
try {
|
|
32962
|
+
if (!hasWasm()) return false;
|
|
32963
|
+
return WebAssembly.validate(bytes);
|
|
32964
|
+
} catch {
|
|
32965
|
+
return false;
|
|
32966
|
+
}
|
|
32967
|
+
}
|
|
32968
|
+
/** `true` iff the named global is defined and not `undefined`. */
|
|
32969
|
+
function hasGlobal(name) {
|
|
32970
|
+
try {
|
|
32971
|
+
return globalThis[name] !== void 0;
|
|
32972
|
+
} catch {
|
|
32973
|
+
return false;
|
|
32974
|
+
}
|
|
32975
|
+
}
|
|
32976
|
+
/**
|
|
32977
|
+
* Best-effort logical-core count. Prefers `navigator.hardwareConcurrency`
|
|
32978
|
+
* (browsers, Workers, Deno, Bun, modern Node), then Node's `os.cpus().length`
|
|
32979
|
+
* if a synchronously-resolvable `os` module is reachable, then `1`. Always
|
|
32980
|
+
* returns a positive integer and never throws.
|
|
32981
|
+
*
|
|
32982
|
+
* Note: `os` is *not* statically imported — that would assume a Node global on
|
|
32983
|
+
* non-Node runtimes. We only consult `navigator`, which is feature-detected,
|
|
32984
|
+
* and otherwise fall back to `1`. (A dynamic `import('node:os')` is async and
|
|
32985
|
+
* cannot feed a synchronous detector, so it is intentionally not attempted
|
|
32986
|
+
* here; `navigator.hardwareConcurrency` is present on Node ≥ 21.)
|
|
32987
|
+
*/
|
|
32988
|
+
function resolveHardwareConcurrency() {
|
|
32989
|
+
try {
|
|
32990
|
+
const reported = globalThis.navigator?.hardwareConcurrency;
|
|
32991
|
+
if (typeof reported === "number" && Number.isInteger(reported) && reported > 0) return reported;
|
|
32992
|
+
} catch {}
|
|
32993
|
+
return 1;
|
|
32994
|
+
}
|
|
32995
|
+
/**
|
|
32996
|
+
* Honestly detect the perf-relevant capabilities of the current runtime.
|
|
32997
|
+
*
|
|
32998
|
+
* Every probe is feature-detected and throw-free; on a host missing a feature
|
|
32999
|
+
* the corresponding flag is `false` (or `1` for {@link
|
|
33000
|
+
* RuntimeCapabilities.hardwareConcurrency}). The result is **not** cached so a
|
|
33001
|
+
* test harness (or a host that gains a feature, e.g. cross-origin isolation
|
|
33002
|
+
* toggling) sees the live state.
|
|
33003
|
+
*
|
|
33004
|
+
* @returns A fresh {@link RuntimeCapabilities} snapshot.
|
|
33005
|
+
*/
|
|
33006
|
+
function detectRuntimeCapabilities() {
|
|
33007
|
+
const wasm = hasWasm();
|
|
33008
|
+
const sharedArrayBuffer = hasGlobal("SharedArrayBuffer");
|
|
33009
|
+
return {
|
|
33010
|
+
wasm,
|
|
33011
|
+
wasmSimd: wasm && validatesModule(WASM_SIMD_MODULE),
|
|
33012
|
+
wasmThreads: wasm && sharedArrayBuffer && validatesModule(WASM_THREADS_MODULE),
|
|
33013
|
+
wasmBulkMemory: wasm && validatesModule(WASM_BULK_MEMORY_MODULE),
|
|
33014
|
+
sharedArrayBuffer,
|
|
33015
|
+
atomics: hasGlobal("Atomics"),
|
|
33016
|
+
bigInt64Array: hasGlobal("BigInt64Array"),
|
|
33017
|
+
crossOriginIsolated: globalThis.crossOriginIsolated === true,
|
|
33018
|
+
hardwareConcurrency: resolveHardwareConcurrency()
|
|
33019
|
+
};
|
|
33020
|
+
}
|
|
33021
|
+
/**
|
|
33022
|
+
* Convenience predicate: does the host implement the WebAssembly SIMD proposal?
|
|
33023
|
+
*
|
|
33024
|
+
* Equivalent to `detectRuntimeCapabilities().wasmSimd`. A `true` result means a
|
|
33025
|
+
* SIMD-enabled module would validate here — **not** that modern-pdf-lib's
|
|
33026
|
+
* shipped WASM uses SIMD (see {@link SIMD_NOTE}).
|
|
33027
|
+
*
|
|
33028
|
+
* @returns `true` iff the SIMD feature-detect module validates.
|
|
33029
|
+
*/
|
|
33030
|
+
function isWasmSimdSupported() {
|
|
33031
|
+
return validatesModule(WASM_SIMD_MODULE);
|
|
33032
|
+
}
|
|
33033
|
+
/**
|
|
33034
|
+
* Honesty disclaimer about SIMD acceleration.
|
|
33035
|
+
*
|
|
33036
|
+
* modern-pdf-lib's bundled WebAssembly binaries are built **without** SIMD
|
|
33037
|
+
* today. {@link isWasmSimdSupported} / {@link RuntimeCapabilities.wasmSimd}
|
|
33038
|
+
* report only whether the *host* could run SIMD code — actually benefiting from
|
|
33039
|
+
* SIMD requires a SIMD-enabled rebuild of the WASM modules. This constant
|
|
33040
|
+
* exists so callers and docs do not mistake capability detection for active
|
|
33041
|
+
* acceleration.
|
|
33042
|
+
*/
|
|
33043
|
+
const SIMD_NOTE = "modern-pdf-lib's bundled WASM is currently built WITHOUT SIMD; isWasmSimdSupported()/wasmSimd report only that the host could run SIMD code. Realising SIMD acceleration requires a SIMD-enabled rebuild of the WASM modules — it is not used today.";
|
|
33044
|
+
//#endregion
|
|
33045
|
+
//#region src/jsx/jsxRuntime.ts
|
|
33046
|
+
/**
|
|
33047
|
+
* @module jsx/jsxRuntime
|
|
33048
|
+
*
|
|
33049
|
+
* A framework-agnostic **JSX / hyperscript renderer** that builds a real PDF
|
|
33050
|
+
* from a declarative element tree. It supports both the classic `h()` pragma
|
|
33051
|
+
* and the React-17+ *automatic runtime* (`jsx` / `jsxs` / `Fragment`), so you
|
|
33052
|
+
* can drive it from any JSX toolchain:
|
|
33053
|
+
*
|
|
33054
|
+
* ```jsonc
|
|
33055
|
+
* // tsconfig.json (automatic runtime)
|
|
33056
|
+
* { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "modern-pdf-lib/jsx" } }
|
|
33057
|
+
* ```
|
|
33058
|
+
* ```jsonc
|
|
33059
|
+
* // tsconfig.json (classic runtime)
|
|
33060
|
+
* { "compilerOptions": { "jsx": "react", "jsxFactory": "h", "jsxFragmentFactory": "Fragment" } }
|
|
33061
|
+
* ```
|
|
33062
|
+
*
|
|
33063
|
+
* @example Classic pragma
|
|
33064
|
+
* ```ts
|
|
33065
|
+
* import { h, renderToPdf } from 'modern-pdf-lib/jsx';
|
|
33066
|
+
* import { PageSizes } from 'modern-pdf-lib';
|
|
33067
|
+
*
|
|
33068
|
+
* const bytes = await renderToPdf(
|
|
33069
|
+
* h('document', { size: PageSizes.A4 },
|
|
33070
|
+
* h('page', null,
|
|
33071
|
+
* h('text', { size: 24, bold: true }, 'Invoice'),
|
|
33072
|
+
* h('text', null, 'Thank you for your business.'),
|
|
33073
|
+
* ),
|
|
33074
|
+
* ),
|
|
33075
|
+
* );
|
|
33076
|
+
* ```
|
|
33077
|
+
*
|
|
33078
|
+
* ## Intrinsic elements
|
|
33079
|
+
*
|
|
33080
|
+
* | Tag | Props | Meaning |
|
|
33081
|
+
* |-------------|--------------------------------------------------------------------|---------|
|
|
33082
|
+
* | `document` | `{ size? }` | Root container. `size` is the default page size. |
|
|
33083
|
+
* | `page` | `{ size? }` | Starts a new PDF page (overrides the document size). |
|
|
33084
|
+
* | `text` | `{ x?, y?, size?, color?, bold? }` | Draws one text line. |
|
|
33085
|
+
* | `view` | `{ x?, y?, width?, height?, padding?, background? }` | A block container; indents/pads its children. |
|
|
33086
|
+
* | `rect` | `{ x, y, width, height, color?, border? }` | Draws a rectangle. |
|
|
33087
|
+
*
|
|
33088
|
+
* Function components are plain `(props) => PdfNode` functions — they are
|
|
33089
|
+
* called with their props (children supplied as `props.children`) and their
|
|
33090
|
+
* return value is rendered in place. {@link Fragment} groups children without
|
|
33091
|
+
* adding any layout box.
|
|
33092
|
+
*
|
|
33093
|
+
* ## Layout model (simple, by design)
|
|
33094
|
+
*
|
|
33095
|
+
* This is **not** a flexbox engine. Layout is a deterministic *top-down block
|
|
33096
|
+
* flow* per page:
|
|
33097
|
+
*
|
|
33098
|
+
* - Each `page` maintains a running **y cursor** that starts at
|
|
33099
|
+
* `pageHeight - MARGIN_TOP` and a running **x cursor** at `MARGIN_LEFT`.
|
|
33100
|
+
* Because PDF coordinates are y-up but documents read top-down, the cursor
|
|
33101
|
+
* *decreases* as successive blocks are placed.
|
|
33102
|
+
* - A flowed `text` is drawn at the current `(x, y - ascent)` and then advances
|
|
33103
|
+
* the y cursor down by its line height (`size * LINE_HEIGHT_FACTOR`).
|
|
33104
|
+
* - A flowed `view` optionally paints its `background`, indents the x cursor by
|
|
33105
|
+
* its `padding`, lays its children out in the same top-down flow, then
|
|
33106
|
+
* restores x and advances y past the consumed block (plus padding).
|
|
33107
|
+
* - **Absolute positioning:** if an element supplies an explicit `x` *and* `y`,
|
|
33108
|
+
* it is placed at exactly that PDF coordinate (y-up, measured from the page
|
|
33109
|
+
* bottom) and does **not** consume or advance the flow cursor. A `rect`
|
|
33110
|
+
* (which requires explicit `x`/`y`) is therefore always absolute.
|
|
33111
|
+
* - Each `page` element resets both cursors and starts a fresh PDF page.
|
|
33112
|
+
*
|
|
33113
|
+
* Anything the simple model cannot express (wrapping across pages, automatic
|
|
33114
|
+
* text reflow, percentage sizing, z-index) is intentionally out of scope.
|
|
33115
|
+
*
|
|
33116
|
+
* @packageDocumentation
|
|
33117
|
+
*/
|
|
33118
|
+
/** Top margin (points) where the flow cursor starts on every page. */
|
|
33119
|
+
const MARGIN_TOP = 50;
|
|
33120
|
+
/** Left margin (points) where the flow cursor starts on every page. */
|
|
33121
|
+
const MARGIN_LEFT = 50;
|
|
33122
|
+
/** Default font size (points) for `text` when `size` is not supplied. */
|
|
33123
|
+
const DEFAULT_FONT_SIZE = 12;
|
|
33124
|
+
/** Line-height multiplier applied to a text line's font size. */
|
|
33125
|
+
const LINE_HEIGHT_FACTOR = 1.2;
|
|
33126
|
+
/**
|
|
33127
|
+
* Recursively flatten a children argument list into a flat `PdfNode[]`,
|
|
33128
|
+
* splatting nested arrays (which JSX produces for `{items.map(...)}`).
|
|
33129
|
+
*/
|
|
33130
|
+
function flattenChildren(children) {
|
|
33131
|
+
const out = [];
|
|
33132
|
+
const visit = (node) => {
|
|
33133
|
+
if (Array.isArray(node)) for (const item of node) visit(item);
|
|
33134
|
+
else out.push(node);
|
|
33135
|
+
};
|
|
33136
|
+
visit(children);
|
|
33137
|
+
return out;
|
|
33138
|
+
}
|
|
33139
|
+
/**
|
|
33140
|
+
* Classic hyperscript pragma (`jsxFactory: "h"`).
|
|
33141
|
+
*
|
|
33142
|
+
* @param type Intrinsic tag name or a function component.
|
|
33143
|
+
* @param props Props object, or `null` for no props.
|
|
33144
|
+
* @param children Zero or more child nodes (nested arrays are flattened).
|
|
33145
|
+
* @returns A {@link PdfElement}.
|
|
33146
|
+
*/
|
|
33147
|
+
function h(type, props, ...children) {
|
|
33148
|
+
return {
|
|
33149
|
+
type,
|
|
33150
|
+
props: props ?? {},
|
|
33151
|
+
children: flattenChildren(children)
|
|
33152
|
+
};
|
|
33153
|
+
}
|
|
33154
|
+
/**
|
|
33155
|
+
* Extract the flattened children from an automatic-runtime props object.
|
|
33156
|
+
* The JSX automatic runtime passes children inside `props.children`.
|
|
33157
|
+
*/
|
|
33158
|
+
function childrenFromProps(props) {
|
|
33159
|
+
if (!("children" in props)) return {
|
|
33160
|
+
rest: props,
|
|
33161
|
+
children: []
|
|
33162
|
+
};
|
|
33163
|
+
const { children, ...rest } = props;
|
|
33164
|
+
return {
|
|
33165
|
+
rest,
|
|
33166
|
+
children: flattenChildren(children)
|
|
33167
|
+
};
|
|
33168
|
+
}
|
|
33169
|
+
/**
|
|
33170
|
+
* Automatic-runtime factory for elements with a single (or no) child.
|
|
33171
|
+
*
|
|
33172
|
+
* @param type Intrinsic tag name or a function component.
|
|
33173
|
+
* @param props Props object; children (if any) live under `props.children`.
|
|
33174
|
+
* @returns A {@link PdfElement}.
|
|
33175
|
+
*/
|
|
33176
|
+
function jsx(type, props) {
|
|
33177
|
+
const { rest, children } = childrenFromProps(props);
|
|
33178
|
+
return {
|
|
33179
|
+
type,
|
|
33180
|
+
props: rest,
|
|
33181
|
+
children
|
|
33182
|
+
};
|
|
33183
|
+
}
|
|
33184
|
+
/**
|
|
33185
|
+
* Automatic-runtime factory for elements with multiple static children.
|
|
33186
|
+
*
|
|
33187
|
+
* Behaviour is identical to {@link jsx}; the JSX transform calls `jsxs` when
|
|
33188
|
+
* the children are a static array.
|
|
33189
|
+
*
|
|
33190
|
+
* @param type Intrinsic tag name or a function component.
|
|
33191
|
+
* @param props Props object; children live under `props.children`.
|
|
33192
|
+
* @returns A {@link PdfElement}.
|
|
33193
|
+
*/
|
|
33194
|
+
function jsxs(type, props) {
|
|
33195
|
+
return jsx(type, props);
|
|
33196
|
+
}
|
|
33197
|
+
/**
|
|
33198
|
+
* Fragment component — groups children without adding a layout box. Used by
|
|
33199
|
+
* the JSX transform for `<>...</>` and directly via `h(Fragment, null, ...)`.
|
|
33200
|
+
*
|
|
33201
|
+
* @param props Props whose `children` are returned for rendering.
|
|
33202
|
+
* @returns The children as a {@link PdfNode} (an array, which the
|
|
33203
|
+
* renderer flattens transparently).
|
|
33204
|
+
*/
|
|
33205
|
+
const Fragment = (props) => {
|
|
33206
|
+
return props["children"] ?? null;
|
|
33207
|
+
};
|
|
33208
|
+
/** Number prop reader that tolerates absent / non-number values. */
|
|
33209
|
+
function numProp(props, key) {
|
|
33210
|
+
const v = props[key];
|
|
33211
|
+
return typeof v === "number" ? v : void 0;
|
|
33212
|
+
}
|
|
33213
|
+
/** Resolve a `PageSize`-ish value into a concrete `[width, height]`. */
|
|
33214
|
+
function resolvePageSize(value, fallback) {
|
|
33215
|
+
if (Array.isArray(value) && value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") return [value[0], value[1]];
|
|
33216
|
+
if (value && typeof value === "object" && typeof value.width === "number" && typeof value.height === "number") return value;
|
|
33217
|
+
return fallback;
|
|
33218
|
+
}
|
|
33219
|
+
/**
|
|
33220
|
+
* Normalize an element/component (or a raw array produced by a component such
|
|
33221
|
+
* as {@link Fragment}) into a concrete, flattened array of child nodes.
|
|
33222
|
+
*/
|
|
33223
|
+
function resolveNode(input) {
|
|
33224
|
+
if (Array.isArray(input)) return flattenChildren(input);
|
|
33225
|
+
const node = input;
|
|
33226
|
+
if (node === null || node === void 0 || typeof node === "boolean") return [];
|
|
33227
|
+
if (typeof node === "string" || typeof node === "number") return [node];
|
|
33228
|
+
if (typeof node.type === "function") {
|
|
33229
|
+
const componentProps = {
|
|
33230
|
+
...node.props,
|
|
33231
|
+
children: node.children
|
|
33232
|
+
};
|
|
33233
|
+
return flattenChildren(node.type(componentProps));
|
|
33234
|
+
}
|
|
33235
|
+
return [node];
|
|
33236
|
+
}
|
|
33237
|
+
/**
|
|
33238
|
+
* Render context shared across a single document render: holds the document,
|
|
33239
|
+
* a lazily-embedded default + bold font, and the active page/cursor.
|
|
33240
|
+
*/
|
|
33241
|
+
var RenderContext = class {
|
|
33242
|
+
doc;
|
|
33243
|
+
regular;
|
|
33244
|
+
bold;
|
|
33245
|
+
constructor(doc) {
|
|
33246
|
+
this.doc = doc;
|
|
33247
|
+
}
|
|
33248
|
+
async init() {
|
|
33249
|
+
this.regular = await this.doc.embedFont("Helvetica");
|
|
33250
|
+
this.bold = await this.doc.embedFont("Helvetica-Bold");
|
|
33251
|
+
}
|
|
33252
|
+
font(bold) {
|
|
33253
|
+
const f = bold ? this.bold : this.regular;
|
|
33254
|
+
if (f === void 0) throw new Error("RenderContext.init() must be awaited before rendering.");
|
|
33255
|
+
return f;
|
|
33256
|
+
}
|
|
33257
|
+
};
|
|
33258
|
+
/**
|
|
33259
|
+
* Draw a single text line. Returns the height consumed in the flow (0 when the
|
|
33260
|
+
* element was absolutely positioned and therefore does not advance the flow).
|
|
33261
|
+
*/
|
|
33262
|
+
function renderText(ctx, page, cursor, el) {
|
|
33263
|
+
const props = el.props;
|
|
33264
|
+
const size = numProp(props, "size") ?? DEFAULT_FONT_SIZE;
|
|
33265
|
+
const bold = props["bold"] === true;
|
|
33266
|
+
const color = props["color"];
|
|
33267
|
+
const font = ctx.font(bold);
|
|
33268
|
+
const text = el.children.filter((c) => typeof c === "string" || typeof c === "number").map((c) => String(c)).join("");
|
|
33269
|
+
const absX = numProp(props, "x");
|
|
33270
|
+
const absY = numProp(props, "y");
|
|
33271
|
+
const absolute = absX !== void 0 && absY !== void 0;
|
|
33272
|
+
const lineHeight = size * LINE_HEIGHT_FACTOR;
|
|
33273
|
+
if (absolute) {
|
|
33274
|
+
page.drawText(text, {
|
|
33275
|
+
x: absX,
|
|
33276
|
+
y: absY,
|
|
33277
|
+
size,
|
|
33278
|
+
font,
|
|
33279
|
+
...color !== void 0 ? { color } : {}
|
|
33280
|
+
});
|
|
33281
|
+
return 0;
|
|
33282
|
+
}
|
|
33283
|
+
const baseline = cursor.y - size;
|
|
33284
|
+
page.drawText(text, {
|
|
33285
|
+
x: cursor.x,
|
|
33286
|
+
y: baseline,
|
|
33287
|
+
size,
|
|
33288
|
+
font,
|
|
33289
|
+
...color !== void 0 ? { color } : {}
|
|
33290
|
+
});
|
|
33291
|
+
cursor.y -= lineHeight;
|
|
33292
|
+
return lineHeight;
|
|
33293
|
+
}
|
|
33294
|
+
/**
|
|
33295
|
+
* Draw a rectangle. `rect` is always absolutely positioned (x/y required) and
|
|
33296
|
+
* therefore never advances the flow cursor.
|
|
33297
|
+
*/
|
|
33298
|
+
function renderRect(page, el) {
|
|
33299
|
+
const props = el.props;
|
|
33300
|
+
const x = numProp(props, "x") ?? 0;
|
|
33301
|
+
const y = numProp(props, "y") ?? 0;
|
|
33302
|
+
const width = numProp(props, "width") ?? 0;
|
|
33303
|
+
const height = numProp(props, "height") ?? 0;
|
|
33304
|
+
const color = props["color"];
|
|
33305
|
+
const border = props["border"];
|
|
33306
|
+
page.drawRectangle({
|
|
33307
|
+
x,
|
|
33308
|
+
y,
|
|
33309
|
+
width,
|
|
33310
|
+
height,
|
|
33311
|
+
...color !== void 0 ? { color } : {},
|
|
33312
|
+
...border !== void 0 ? {
|
|
33313
|
+
borderColor: border,
|
|
33314
|
+
borderWidth: 1
|
|
33315
|
+
} : {}
|
|
33316
|
+
});
|
|
33317
|
+
}
|
|
33318
|
+
/**
|
|
33319
|
+
* Render a `view` block container. Returns the height consumed in the flow
|
|
33320
|
+
* (0 when absolutely positioned).
|
|
33321
|
+
*/
|
|
33322
|
+
function renderView(ctx, page, cursor, el) {
|
|
33323
|
+
const props = el.props;
|
|
33324
|
+
const padding = numProp(props, "padding") ?? 0;
|
|
33325
|
+
const background = props["background"];
|
|
33326
|
+
const absX = numProp(props, "x");
|
|
33327
|
+
const absY = numProp(props, "y");
|
|
33328
|
+
const absolute = absX !== void 0 && absY !== void 0;
|
|
33329
|
+
const explicitWidth = numProp(props, "width");
|
|
33330
|
+
const explicitHeight = numProp(props, "height");
|
|
33331
|
+
if (absolute) {
|
|
33332
|
+
const localTop = absY + (explicitHeight ?? 0);
|
|
33333
|
+
const local = {
|
|
33334
|
+
x: absX + padding,
|
|
33335
|
+
y: localTop - padding
|
|
33336
|
+
};
|
|
33337
|
+
if (background !== void 0 && explicitWidth !== void 0 && explicitHeight !== void 0) page.drawRectangle({
|
|
33338
|
+
x: absX,
|
|
33339
|
+
y: absY,
|
|
33340
|
+
width: explicitWidth,
|
|
33341
|
+
height: explicitHeight,
|
|
33342
|
+
color: background
|
|
33343
|
+
});
|
|
33344
|
+
renderChildren(ctx, page, local, el.children);
|
|
33345
|
+
return 0;
|
|
33346
|
+
}
|
|
33347
|
+
const blockTop = cursor.y;
|
|
33348
|
+
const innerCursor = {
|
|
33349
|
+
x: cursor.x + padding,
|
|
33350
|
+
y: cursor.y - padding
|
|
33351
|
+
};
|
|
33352
|
+
renderChildren(ctx, page, innerCursor, el.children);
|
|
33353
|
+
const contentConsumed = cursor.y - padding - innerCursor.y;
|
|
33354
|
+
const blockHeight = explicitHeight ?? contentConsumed + padding * 2;
|
|
33355
|
+
if (background !== void 0) {
|
|
33356
|
+
const blockWidth = explicitWidth ?? page.width - cursor.x - MARGIN_LEFT;
|
|
33357
|
+
page.drawRectangle({
|
|
33358
|
+
x: cursor.x,
|
|
33359
|
+
y: blockTop - blockHeight,
|
|
33360
|
+
width: blockWidth,
|
|
33361
|
+
height: blockHeight,
|
|
33362
|
+
color: background
|
|
33363
|
+
});
|
|
33364
|
+
}
|
|
33365
|
+
cursor.y = blockTop - blockHeight;
|
|
33366
|
+
return blockHeight;
|
|
33367
|
+
}
|
|
33368
|
+
/** Render a flat list of children in top-down flow on the current page. */
|
|
33369
|
+
function renderChildren(ctx, page, cursor, children) {
|
|
33370
|
+
for (const child of children) for (const resolved of resolveNode(child)) renderFlowNode(ctx, page, cursor, resolved);
|
|
33371
|
+
}
|
|
33372
|
+
/** Render one already-resolved node into the current page's flow. */
|
|
33373
|
+
function renderFlowNode(ctx, page, cursor, node) {
|
|
33374
|
+
if (node === null || node === void 0 || typeof node === "boolean") return;
|
|
33375
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
33376
|
+
const size = DEFAULT_FONT_SIZE;
|
|
33377
|
+
const baseline = cursor.y - size;
|
|
33378
|
+
page.drawText(String(node), {
|
|
33379
|
+
x: cursor.x,
|
|
33380
|
+
y: baseline,
|
|
33381
|
+
size,
|
|
33382
|
+
font: ctx.font(false)
|
|
33383
|
+
});
|
|
33384
|
+
cursor.y -= size * LINE_HEIGHT_FACTOR;
|
|
33385
|
+
return;
|
|
33386
|
+
}
|
|
33387
|
+
if (typeof node.type === "function") {
|
|
33388
|
+
for (const resolved of resolveNode(node)) renderFlowNode(ctx, page, cursor, resolved);
|
|
33389
|
+
return;
|
|
33390
|
+
}
|
|
33391
|
+
switch (node.type) {
|
|
33392
|
+
case "text":
|
|
33393
|
+
renderText(ctx, page, cursor, node);
|
|
33394
|
+
return;
|
|
33395
|
+
case "view":
|
|
33396
|
+
renderView(ctx, page, cursor, node);
|
|
33397
|
+
return;
|
|
33398
|
+
case "rect":
|
|
33399
|
+
renderRect(page, node);
|
|
33400
|
+
return;
|
|
33401
|
+
case "page":
|
|
33402
|
+
case "document":
|
|
33403
|
+
renderChildren(ctx, page, cursor, node.children);
|
|
33404
|
+
return;
|
|
33405
|
+
default:
|
|
33406
|
+
renderChildren(ctx, page, cursor, node.children);
|
|
33407
|
+
return;
|
|
33408
|
+
}
|
|
33409
|
+
}
|
|
33410
|
+
/**
|
|
33411
|
+
* Collect the `page` elements from a `document`'s children, resolving any
|
|
33412
|
+
* function components / fragments that wrap them.
|
|
33413
|
+
*/
|
|
33414
|
+
function collectPages(children) {
|
|
33415
|
+
const pages = [];
|
|
33416
|
+
const visit = (node) => {
|
|
33417
|
+
for (const resolved of resolveNode(node)) {
|
|
33418
|
+
if (resolved === null || resolved === void 0 || typeof resolved === "boolean" || typeof resolved === "string" || typeof resolved === "number") continue;
|
|
33419
|
+
if (typeof resolved.type === "function") {
|
|
33420
|
+
visit(resolved);
|
|
33421
|
+
continue;
|
|
33422
|
+
}
|
|
33423
|
+
if (resolved.type === "page") pages.push(resolved);
|
|
33424
|
+
else for (const c of resolved.children) visit(c);
|
|
33425
|
+
}
|
|
33426
|
+
};
|
|
33427
|
+
for (const child of children) visit(child);
|
|
33428
|
+
return pages;
|
|
33429
|
+
}
|
|
33430
|
+
/**
|
|
33431
|
+
* Render a declarative element tree to a PDF document.
|
|
33432
|
+
*
|
|
33433
|
+
* The `root` should be a `document` element (or a component / fragment that
|
|
33434
|
+
* resolves to one). Each `page` child becomes a PDF page; content flows
|
|
33435
|
+
* top-down within each page per the layout model documented in the module
|
|
33436
|
+
* header.
|
|
33437
|
+
*
|
|
33438
|
+
* @param root The root {@link PdfNode} (typically a `document` element).
|
|
33439
|
+
* @returns The serialized PDF as a `Uint8Array` (begins with `"%PDF-"`).
|
|
33440
|
+
*
|
|
33441
|
+
* @example
|
|
33442
|
+
* ```ts
|
|
33443
|
+
* const bytes = await renderToPdf(
|
|
33444
|
+
* h('document', { size: PageSizes.A4 },
|
|
33445
|
+
* h('page', null, h('text', { size: 24 }, 'Hello'), h('text', null, 'World')),
|
|
33446
|
+
* ),
|
|
33447
|
+
* );
|
|
33448
|
+
* ```
|
|
33449
|
+
*/
|
|
33450
|
+
async function renderToPdf(root) {
|
|
33451
|
+
let documentEl;
|
|
33452
|
+
const stack = resolveNode(root);
|
|
33453
|
+
while (stack.length > 0) {
|
|
33454
|
+
const node = stack.shift();
|
|
33455
|
+
if (node === null || node === void 0 || typeof node === "boolean" || typeof node === "string" || typeof node === "number") continue;
|
|
33456
|
+
if (typeof node.type === "function") {
|
|
33457
|
+
stack.unshift(...resolveNode(node));
|
|
33458
|
+
continue;
|
|
33459
|
+
}
|
|
33460
|
+
if (node.type === "document") {
|
|
33461
|
+
documentEl = node;
|
|
33462
|
+
break;
|
|
33463
|
+
}
|
|
33464
|
+
documentEl = {
|
|
33465
|
+
type: "document",
|
|
33466
|
+
props: {},
|
|
33467
|
+
children: [node]
|
|
33468
|
+
};
|
|
33469
|
+
break;
|
|
33470
|
+
}
|
|
33471
|
+
const doc = require_pdfDocument.createPdf();
|
|
33472
|
+
const ctx = new RenderContext(doc);
|
|
33473
|
+
await ctx.init();
|
|
33474
|
+
const docSize = resolvePageSize(documentEl?.props["size"], require_pdfDocument.PageSizes.A4);
|
|
33475
|
+
const pageEls = documentEl ? collectPages(documentEl.children) : [];
|
|
33476
|
+
if (pageEls.length === 0) doc.addPage(docSize);
|
|
33477
|
+
for (const pageEl of pageEls) {
|
|
33478
|
+
const pageSize = resolvePageSize(pageEl.props["size"], docSize);
|
|
33479
|
+
const page = doc.addPage(pageSize);
|
|
33480
|
+
renderChildren(ctx, page, {
|
|
33481
|
+
x: MARGIN_LEFT,
|
|
33482
|
+
y: page.height - MARGIN_TOP
|
|
33483
|
+
}, pageEl.children);
|
|
33484
|
+
}
|
|
33485
|
+
return doc.save();
|
|
33486
|
+
}
|
|
33487
|
+
//#endregion
|
|
33488
|
+
//#region src/form/schemaForm.ts
|
|
33489
|
+
/**
|
|
33490
|
+
* @module form/schemaForm
|
|
33491
|
+
*
|
|
33492
|
+
* Generate a fillable AcroForm PDF directly from a JSON-Schema-like
|
|
33493
|
+
* description. Only the subset of JSON Schema that maps cleanly onto
|
|
33494
|
+
* PDF interactive form (AcroForm) field types is supported:
|
|
33495
|
+
*
|
|
33496
|
+
* | JSON Schema | AcroForm field |
|
|
33497
|
+
* | --------------------------------- | ------------------------- |
|
|
33498
|
+
* | `type: 'string'` | text field |
|
|
33499
|
+
* | `type: 'string'` + `enum: [...]` | dropdown (combo box) |
|
|
33500
|
+
* | `type: 'boolean'` | checkbox |
|
|
33501
|
+
* | `type: 'number'` / `'integer'` | text field |
|
|
33502
|
+
* | `type: 'object'` / `'array'` | text field (placeholder) |
|
|
33503
|
+
* | anything else / no `type` | text field (placeholder) |
|
|
33504
|
+
*
|
|
33505
|
+
* ### Documented limitations (intentional, honest scope)
|
|
33506
|
+
*
|
|
33507
|
+
* - **Nested objects and arrays are NOT recursed.** A property whose
|
|
33508
|
+
* `type` is `'object'` or `'array'` becomes a single flat text-field
|
|
33509
|
+
* placeholder labelled with the property name. Sub-properties of a
|
|
33510
|
+
* nested object are ignored. (Recursive / repeatable structures have
|
|
33511
|
+
* no first-class AcroForm representation.)
|
|
33512
|
+
* - **Unsupported / unknown `type` keywords** (and properties with no
|
|
33513
|
+
* `type` at all) also degrade gracefully to a plain text field, so the
|
|
33514
|
+
* generated document is always a valid, fillable PDF.
|
|
33515
|
+
* - Schema validation keywords (`minLength`, `pattern`, `minimum`,
|
|
33516
|
+
* `multipleOf`, `oneOf`, `anyOf`, `$ref`, …) are **not** enforced; they
|
|
33517
|
+
* are simply ignored. Only `type`, `properties`, `required`, `enum`,
|
|
33518
|
+
* and `title` influence the output.
|
|
33519
|
+
* - `enum` is honoured only for `type: 'string'`. Enum values of other
|
|
33520
|
+
* types fall through to their normal mapping.
|
|
33521
|
+
*
|
|
33522
|
+
* The returned {@link PdfDocument} is **not** saved — call `doc.save()`
|
|
33523
|
+
* (or any of the `saveAs*` variants) on it yourself.
|
|
33524
|
+
*
|
|
33525
|
+
* @example
|
|
33526
|
+
* ```ts
|
|
33527
|
+
* import { buildFormFromJsonSchema } from 'modern-pdf-lib/form/schemaForm';
|
|
33528
|
+
*
|
|
33529
|
+
* const { doc, fields } = buildFormFromJsonSchema({
|
|
33530
|
+
* type: 'object',
|
|
33531
|
+
* title: 'Signup',
|
|
33532
|
+
* properties: {
|
|
33533
|
+
* fullName: { type: 'string', title: 'Full Name' },
|
|
33534
|
+
* subscribe: { type: 'boolean' },
|
|
33535
|
+
* country: { type: 'string', enum: ['US', 'CA', 'MX'] },
|
|
33536
|
+
* },
|
|
33537
|
+
* required: ['fullName'],
|
|
33538
|
+
* });
|
|
33539
|
+
*
|
|
33540
|
+
* const bytes = await doc.save();
|
|
33541
|
+
* ```
|
|
33542
|
+
*/
|
|
33543
|
+
/** Vertical space (points) allotted to each row (field + gap). */
|
|
33544
|
+
const ROW_HEIGHT = 34;
|
|
33545
|
+
/** Height (points) of a text / dropdown widget. */
|
|
33546
|
+
const FIELD_HEIGHT = 20;
|
|
33547
|
+
/** Side margin (points). */
|
|
33548
|
+
const MARGIN = 48;
|
|
33549
|
+
/** Top margin (points) below which fields start on each page. */
|
|
33550
|
+
const TOP_MARGIN = 56;
|
|
33551
|
+
/** Bottom margin (points); fields below this trigger a page break. */
|
|
33552
|
+
const BOTTOM_MARGIN = 48;
|
|
33553
|
+
/** Default label-column width (points). */
|
|
33554
|
+
const DEFAULT_LABEL_WIDTH = 160;
|
|
33555
|
+
/** Body font size for labels. */
|
|
33556
|
+
const LABEL_SIZE = 11;
|
|
33557
|
+
/** Title font size. */
|
|
33558
|
+
const TITLE_SIZE = 18;
|
|
33559
|
+
/**
|
|
33560
|
+
* Determine which AcroForm field kind a property schema maps to.
|
|
33561
|
+
*
|
|
33562
|
+
* @returns The field kind plus, for dropdowns, the list of options.
|
|
33563
|
+
*/
|
|
33564
|
+
function mapPropertyToKind(prop) {
|
|
33565
|
+
const type = prop.type;
|
|
33566
|
+
if (type === "boolean") return {
|
|
33567
|
+
kind: "checkbox",
|
|
33568
|
+
options: []
|
|
33569
|
+
};
|
|
33570
|
+
if (type === "string" && Array.isArray(prop.enum) && prop.enum.length > 0) return {
|
|
33571
|
+
kind: "dropdown",
|
|
33572
|
+
options: prop.enum.map((v) => String(v))
|
|
33573
|
+
};
|
|
33574
|
+
return {
|
|
33575
|
+
kind: "text",
|
|
33576
|
+
options: []
|
|
33577
|
+
};
|
|
33578
|
+
}
|
|
33579
|
+
/**
|
|
33580
|
+
* Internal plugin that wires a freshly-built form into the document at
|
|
33581
|
+
* save time. It runs after pages are finalized so it can:
|
|
33582
|
+
*
|
|
33583
|
+
* - append each field's (pre-registered) indirect reference to its
|
|
33584
|
+
* page's `/Annots` array (`onBuildPageDict`), and
|
|
33585
|
+
* - register the `/AcroForm` dictionary on the catalog with a `/DR`
|
|
33586
|
+
* default-resources entry and `/NeedAppearances true` so viewers
|
|
33587
|
+
* generate the field appearances (`onBuildCatalog`).
|
|
33588
|
+
*
|
|
33589
|
+
* This avoids editing any shared document code: everything needed to
|
|
33590
|
+
* make `getForm()` fields round-trip is performed through the public
|
|
33591
|
+
* plugin hooks.
|
|
33592
|
+
*/
|
|
33593
|
+
function createAcroFormWiringPlugin(acroFormDict, acroFormRef, fieldRefsByPage) {
|
|
33594
|
+
return {
|
|
33595
|
+
name: "modern-pdf-lib:schemaForm-acroform-wiring",
|
|
33596
|
+
version: "1.0.0",
|
|
33597
|
+
onBuildPageDict(pageDict, pageIndex) {
|
|
33598
|
+
const refs = fieldRefsByPage.get(pageIndex);
|
|
33599
|
+
if (refs === void 0 || refs.length === 0) return;
|
|
33600
|
+
const existing = pageDict.get("/Annots");
|
|
33601
|
+
if (existing !== void 0 && existing.kind === "array") for (const ref of refs) existing.push(ref);
|
|
33602
|
+
else pageDict.set("/Annots", require_pdfObjects.PdfArray.of([...refs]));
|
|
33603
|
+
},
|
|
33604
|
+
onBuildCatalog(catalog) {
|
|
33605
|
+
if (!acroFormDict.has("/DR")) {
|
|
33606
|
+
const helvetica = new require_pdfObjects.PdfDict();
|
|
33607
|
+
helvetica.set("/Type", require_pdfObjects.PdfName.of("Font"));
|
|
33608
|
+
helvetica.set("/Subtype", require_pdfObjects.PdfName.of("Type1"));
|
|
33609
|
+
helvetica.set("/BaseFont", require_pdfObjects.PdfName.of("Helvetica"));
|
|
33610
|
+
const fontDict = new require_pdfObjects.PdfDict();
|
|
33611
|
+
fontDict.set("/Helv", helvetica);
|
|
33612
|
+
const resources = new require_pdfObjects.PdfDict();
|
|
33613
|
+
resources.set("/Font", fontDict);
|
|
33614
|
+
acroFormDict.set("/DR", resources);
|
|
33615
|
+
}
|
|
33616
|
+
if (!acroFormDict.has("/DA")) acroFormDict.set("/DA", require_pdfObjects.PdfString.literal("/Helv 0 Tf 0 g"));
|
|
33617
|
+
acroFormDict.set("/NeedAppearances", require_pdfObjects.PdfBool.TRUE);
|
|
33618
|
+
catalog.set("/AcroForm", acroFormRef);
|
|
33619
|
+
}
|
|
33620
|
+
};
|
|
33621
|
+
}
|
|
33622
|
+
/**
|
|
33623
|
+
* Build a fillable AcroForm PDF from a JSON-Schema-like description.
|
|
33624
|
+
*
|
|
33625
|
+
* For an object schema, the `properties` are iterated in insertion
|
|
33626
|
+
* order and one labelled form field is emitted per property. Fields
|
|
33627
|
+
* are stacked top-down; when the vertical cursor runs off the page a
|
|
33628
|
+
* new page is added and the cursor reset. Required fields (listed in
|
|
33629
|
+
* the schema's `required` array) are marked with a trailing asterisk in
|
|
33630
|
+
* their label.
|
|
33631
|
+
*
|
|
33632
|
+
* @param schema The JSON-Schema-like description.
|
|
33633
|
+
* @param options Optional layout options.
|
|
33634
|
+
* @returns The generated (unsaved) {@link PdfDocument} and the
|
|
33635
|
+
* list of generated field descriptors.
|
|
33636
|
+
*/
|
|
33637
|
+
function buildFormFromJsonSchema(schema, options) {
|
|
33638
|
+
const doc = require_pdfDocument.createPdf();
|
|
33639
|
+
const form = doc.getForm();
|
|
33640
|
+
const pageSize = options?.pageSize ?? [require_pdfDocument.PageSizes.A4[0], require_pdfDocument.PageSizes.A4[1]];
|
|
33641
|
+
const [pageWidth, pageHeight] = pageSize;
|
|
33642
|
+
const labelWidth = options?.labelWidth !== void 0 ? options.labelWidth : DEFAULT_LABEL_WIDTH;
|
|
33643
|
+
const fields = [];
|
|
33644
|
+
const fieldRefsByPage = /* @__PURE__ */ new Map();
|
|
33645
|
+
const registry = doc.getRegistry();
|
|
33646
|
+
const acroFormDict = form.toDict(registry);
|
|
33647
|
+
const acroFormRef = registry.register(acroFormDict);
|
|
33648
|
+
const fieldRefs = [];
|
|
33649
|
+
let pageIndex = -1;
|
|
33650
|
+
let cursorY = 0;
|
|
33651
|
+
const fieldX = MARGIN + labelWidth;
|
|
33652
|
+
const fieldWidth = Math.max(40, pageWidth - fieldX - MARGIN);
|
|
33653
|
+
/** Start a fresh page and reset the vertical cursor. */
|
|
33654
|
+
const newPage = () => {
|
|
33655
|
+
doc.addPage(pageSize);
|
|
33656
|
+
pageIndex += 1;
|
|
33657
|
+
cursorY = pageHeight - TOP_MARGIN;
|
|
33658
|
+
};
|
|
33659
|
+
newPage();
|
|
33660
|
+
const title = options?.title ?? schema.title;
|
|
33661
|
+
if (title !== void 0 && title.length > 0) {
|
|
33662
|
+
doc.getPage(pageIndex).drawText(title, {
|
|
33663
|
+
x: MARGIN,
|
|
33664
|
+
y: cursorY,
|
|
33665
|
+
size: TITLE_SIZE,
|
|
33666
|
+
color: require_pdfDocument.rgb(0, 0, 0)
|
|
33667
|
+
});
|
|
33668
|
+
cursorY -= 36;
|
|
33669
|
+
}
|
|
33670
|
+
const properties = schema.type === "object" ? schema.properties : void 0;
|
|
33671
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
33672
|
+
if (properties !== void 0) for (const [name, propSchema] of Object.entries(properties)) {
|
|
33673
|
+
const prop = propSchema ?? {};
|
|
33674
|
+
const { kind, options: dropdownOptions } = mapPropertyToKind(prop);
|
|
33675
|
+
if (cursorY - ROW_HEIGHT < BOTTOM_MARGIN) newPage();
|
|
33676
|
+
const page = doc.getPage(pageIndex);
|
|
33677
|
+
const labelText = prop.title ?? name;
|
|
33678
|
+
const required = requiredSet.has(name);
|
|
33679
|
+
const label = required ? `${labelText} *` : labelText;
|
|
33680
|
+
page.drawText(label, {
|
|
33681
|
+
x: MARGIN,
|
|
33682
|
+
y: cursorY - FIELD_HEIGHT + 6,
|
|
33683
|
+
size: LABEL_SIZE,
|
|
33684
|
+
color: required ? require_pdfDocument.rgb(.6, 0, 0) : require_pdfDocument.rgb(.1, .1, .1)
|
|
33685
|
+
});
|
|
33686
|
+
const y1 = cursorY - FIELD_HEIGHT;
|
|
33687
|
+
const y2 = cursorY;
|
|
33688
|
+
let createdDict;
|
|
33689
|
+
if (kind === "checkbox") {
|
|
33690
|
+
const cbSize = FIELD_HEIGHT;
|
|
33691
|
+
createdDict = form.createCheckbox(name, pageIndex, [
|
|
33692
|
+
fieldX,
|
|
33693
|
+
y1,
|
|
33694
|
+
fieldX + cbSize,
|
|
33695
|
+
y2
|
|
33696
|
+
]).getDict();
|
|
33697
|
+
} else if (kind === "dropdown") createdDict = form.createDropdown(name, pageIndex, [
|
|
33698
|
+
fieldX,
|
|
33699
|
+
y1,
|
|
33700
|
+
fieldX + fieldWidth,
|
|
33701
|
+
y2
|
|
33702
|
+
], dropdownOptions).getDict();
|
|
33703
|
+
else {
|
|
33704
|
+
createdDict = form.createTextField(name, pageIndex, [
|
|
33705
|
+
fieldX,
|
|
33706
|
+
y1,
|
|
33707
|
+
fieldX + fieldWidth,
|
|
33708
|
+
y2
|
|
33709
|
+
]).getDict();
|
|
33710
|
+
page.drawLine({
|
|
33711
|
+
start: {
|
|
33712
|
+
x: fieldX,
|
|
33713
|
+
y: y1
|
|
33714
|
+
},
|
|
33715
|
+
end: {
|
|
33716
|
+
x: fieldX + fieldWidth,
|
|
33717
|
+
y: y1
|
|
33718
|
+
},
|
|
33719
|
+
thickness: .5,
|
|
33720
|
+
color: require_pdfDocument.rgb(.6, .6, .6)
|
|
33721
|
+
});
|
|
33722
|
+
}
|
|
33723
|
+
const ref = registry.register(createdDict);
|
|
33724
|
+
fieldRefs.push(ref);
|
|
33725
|
+
const pageRefs = fieldRefsByPage.get(pageIndex) ?? [];
|
|
33726
|
+
pageRefs.push(ref);
|
|
33727
|
+
fieldRefsByPage.set(pageIndex, pageRefs);
|
|
33728
|
+
fields.push({
|
|
33729
|
+
name,
|
|
33730
|
+
kind
|
|
33731
|
+
});
|
|
33732
|
+
cursorY -= ROW_HEIGHT;
|
|
33733
|
+
}
|
|
33734
|
+
acroFormDict.set("/Fields", require_pdfObjects.PdfArray.of([...fieldRefs]));
|
|
33735
|
+
doc.use(createAcroFormWiringPlugin(acroFormDict, acroFormRef, fieldRefsByPage));
|
|
33736
|
+
return {
|
|
33737
|
+
doc,
|
|
33738
|
+
fields
|
|
33739
|
+
};
|
|
33740
|
+
}
|
|
33741
|
+
//#endregion
|
|
33742
|
+
//#region src/runtime/serverAdapters.ts
|
|
33743
|
+
/**
|
|
33744
|
+
* Characters that are safe to leave unencoded inside the RFC 5987
|
|
33745
|
+
* `attr-char` production used by `filename*`. Everything else is
|
|
33746
|
+
* percent-encoded from its UTF-8 bytes.
|
|
33747
|
+
*
|
|
33748
|
+
* `attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" /
|
|
33749
|
+
* "." / "^" / "_" / "`" / "|" / "~"` (RFC 5987 §3.2.1)
|
|
33750
|
+
*/
|
|
33751
|
+
const RFC5987_ATTR_CHAR = /[A-Za-z0-9!#$&+\-.^_`|~]/;
|
|
33752
|
+
/** Reusable UTF-8 encoder for the `filename*` byte serialisation. */
|
|
33753
|
+
const utf8Encoder = new TextEncoder();
|
|
33754
|
+
/**
|
|
33755
|
+
* Percent-encode a string as an RFC 5987 `value-chars` sequence: every
|
|
33756
|
+
* byte that is not an `attr-char` is written as `%HH` (upper-case hex)
|
|
33757
|
+
* from its UTF-8 representation.
|
|
33758
|
+
*
|
|
33759
|
+
* @param value The raw (possibly non-ASCII) string.
|
|
33760
|
+
* @returns The `value-chars` portion of an `ext-value`.
|
|
33761
|
+
*/
|
|
33762
|
+
function encodeRfc5987(value) {
|
|
33763
|
+
const bytes = utf8Encoder.encode(value);
|
|
33764
|
+
let out = "";
|
|
33765
|
+
for (const byte of bytes) {
|
|
33766
|
+
const char = String.fromCharCode(byte);
|
|
33767
|
+
if (byte < 128 && RFC5987_ATTR_CHAR.test(char)) out += char;
|
|
33768
|
+
else out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
33769
|
+
}
|
|
33770
|
+
return out;
|
|
33771
|
+
}
|
|
33772
|
+
/**
|
|
33773
|
+
* A small transliteration table mapping common accented Latin letters to
|
|
33774
|
+
* their closest ASCII equivalent. Used only for the *fallback* `filename`
|
|
33775
|
+
* parameter — the authoritative value is always carried by `filename*`.
|
|
33776
|
+
*
|
|
33777
|
+
* This is intentionally minimal: it covers the Latin-1 / Latin Extended-A
|
|
33778
|
+
* letters most likely to appear in document names so that legacy clients
|
|
33779
|
+
* (which ignore `filename*`) still receive a readable name.
|
|
33780
|
+
*/
|
|
33781
|
+
const TRANSLITERATIONS = new Map(Object.entries({
|
|
33782
|
+
à: "a",
|
|
33783
|
+
á: "a",
|
|
33784
|
+
â: "a",
|
|
33785
|
+
ã: "a",
|
|
33786
|
+
ä: "a",
|
|
33787
|
+
å: "a",
|
|
33788
|
+
ā: "a",
|
|
33789
|
+
ă: "a",
|
|
33790
|
+
ą: "a",
|
|
33791
|
+
ç: "c",
|
|
33792
|
+
ć: "c",
|
|
33793
|
+
č: "c",
|
|
33794
|
+
è: "e",
|
|
33795
|
+
é: "e",
|
|
33796
|
+
ê: "e",
|
|
33797
|
+
ë: "e",
|
|
33798
|
+
ē: "e",
|
|
33799
|
+
ė: "e",
|
|
33800
|
+
ę: "e",
|
|
33801
|
+
ě: "e",
|
|
33802
|
+
ì: "i",
|
|
33803
|
+
í: "i",
|
|
33804
|
+
î: "i",
|
|
33805
|
+
ï: "i",
|
|
33806
|
+
ī: "i",
|
|
33807
|
+
į: "i",
|
|
33808
|
+
ñ: "n",
|
|
33809
|
+
ń: "n",
|
|
33810
|
+
ň: "n",
|
|
33811
|
+
ò: "o",
|
|
33812
|
+
ó: "o",
|
|
33813
|
+
ô: "o",
|
|
33814
|
+
õ: "o",
|
|
33815
|
+
ö: "o",
|
|
33816
|
+
ø: "o",
|
|
33817
|
+
ō: "o",
|
|
33818
|
+
ù: "u",
|
|
33819
|
+
ú: "u",
|
|
33820
|
+
û: "u",
|
|
33821
|
+
ü: "u",
|
|
33822
|
+
ū: "u",
|
|
33823
|
+
ů: "u",
|
|
33824
|
+
ý: "y",
|
|
33825
|
+
ÿ: "y",
|
|
33826
|
+
ž: "z",
|
|
33827
|
+
ź: "z",
|
|
33828
|
+
ż: "z",
|
|
33829
|
+
ß: "ss",
|
|
33830
|
+
æ: "ae",
|
|
33831
|
+
œ: "oe",
|
|
33832
|
+
À: "A",
|
|
33833
|
+
Á: "A",
|
|
33834
|
+
Â: "A",
|
|
33835
|
+
Ã: "A",
|
|
33836
|
+
Ä: "A",
|
|
33837
|
+
Å: "A",
|
|
33838
|
+
Ç: "C",
|
|
33839
|
+
È: "E",
|
|
33840
|
+
É: "E",
|
|
33841
|
+
Ê: "E",
|
|
33842
|
+
Ë: "E",
|
|
33843
|
+
Ì: "I",
|
|
33844
|
+
Í: "I",
|
|
33845
|
+
Î: "I",
|
|
33846
|
+
Ï: "I",
|
|
33847
|
+
Ñ: "N",
|
|
33848
|
+
Ò: "O",
|
|
33849
|
+
Ó: "O",
|
|
33850
|
+
Ô: "O",
|
|
33851
|
+
Õ: "O",
|
|
33852
|
+
Ö: "O",
|
|
33853
|
+
Ø: "O",
|
|
33854
|
+
Ù: "U",
|
|
33855
|
+
Ú: "U",
|
|
33856
|
+
Û: "U",
|
|
33857
|
+
Ü: "U",
|
|
33858
|
+
Ý: "Y"
|
|
33859
|
+
}));
|
|
33860
|
+
/**
|
|
33861
|
+
* Produce an ASCII-only fallback for a filename: accented Latin letters
|
|
33862
|
+
* are transliterated, any remaining non-ASCII characters are dropped, and
|
|
33863
|
+
* control characters are stripped. The result is intended to be wrapped
|
|
33864
|
+
* in a `quoted-string` by {@link quoteFilename}.
|
|
33865
|
+
*
|
|
33866
|
+
* @param filename The original, possibly non-ASCII filename.
|
|
33867
|
+
* @returns An ASCII approximation (never empty: falls back to
|
|
33868
|
+
* `"file.pdf"` if nothing survives).
|
|
33869
|
+
*/
|
|
33870
|
+
function asciiFallback(filename) {
|
|
33871
|
+
let out = "";
|
|
33872
|
+
for (const char of filename) {
|
|
33873
|
+
const code = char.codePointAt(0) ?? 0;
|
|
33874
|
+
if (code >= 32 && code < 127) out += char;
|
|
33875
|
+
else {
|
|
33876
|
+
const replacement = TRANSLITERATIONS.get(char);
|
|
33877
|
+
if (replacement !== void 0) out += replacement;
|
|
33878
|
+
}
|
|
33879
|
+
}
|
|
33880
|
+
out = out.trim();
|
|
33881
|
+
return out.length > 0 ? out : "file.pdf";
|
|
33882
|
+
}
|
|
33883
|
+
/**
|
|
33884
|
+
* Escape a string for use inside an HTTP `quoted-string` (RFC 9110 /
|
|
33885
|
+
* RFC 2616): backslash and double-quote are backslash-escaped.
|
|
33886
|
+
*
|
|
33887
|
+
* @param value An ASCII string.
|
|
33888
|
+
* @returns The escaped contents (without the surrounding quotes).
|
|
33889
|
+
*/
|
|
33890
|
+
function quoteFilename(value) {
|
|
33891
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
33892
|
+
}
|
|
33893
|
+
/**
|
|
33894
|
+
* Returns `true` when the string contains only printable ASCII and no
|
|
33895
|
+
* characters that would be illegal in a `quoted-string` filename.
|
|
33896
|
+
*/
|
|
33897
|
+
function isSimpleAscii(filename) {
|
|
33898
|
+
for (const char of filename) {
|
|
33899
|
+
const code = char.codePointAt(0) ?? 0;
|
|
33900
|
+
if (code < 32 || code >= 127) return false;
|
|
33901
|
+
}
|
|
33902
|
+
return true;
|
|
33903
|
+
}
|
|
33904
|
+
/**
|
|
33905
|
+
* Build the value of the `Content-Disposition` header.
|
|
33906
|
+
*
|
|
33907
|
+
* @param download `true` → `attachment`; otherwise `inline`.
|
|
33908
|
+
* @param filename Optional suggested filename.
|
|
33909
|
+
* @returns A complete header value, e.g.
|
|
33910
|
+
* `attachment; filename="report.pdf"` or, for non-ASCII,
|
|
33911
|
+
* `attachment; filename="resume.pdf"; filename*=UTF-8''r%C3%A9sum%C3%A9.pdf`.
|
|
33912
|
+
*/
|
|
33913
|
+
function contentDisposition(download, filename) {
|
|
33914
|
+
const type = download ? "attachment" : "inline";
|
|
33915
|
+
if (filename === void 0 || filename.length === 0) return type;
|
|
33916
|
+
if (isSimpleAscii(filename) && !filename.includes("\"") && !filename.includes("\\")) return `${type}; filename="${filename}"`;
|
|
33917
|
+
const quoted = quoteFilename(asciiFallback(filename));
|
|
33918
|
+
if (isSimpleAscii(filename)) return `${type}; filename="${quoted}"`;
|
|
33919
|
+
return `${type}; filename="${quoted}"; filename*=UTF-8''${encodeRfc5987(filename)}`;
|
|
33920
|
+
}
|
|
33921
|
+
/**
|
|
33922
|
+
* Build the set of HTTP response headers for serving a PDF body.
|
|
33923
|
+
*
|
|
33924
|
+
* Always includes `Content-Type: application/pdf`, a numeric
|
|
33925
|
+
* `Content-Length`, and a `Content-Disposition` (`inline` by default,
|
|
33926
|
+
* `attachment` when `options.download` is `true`). `Cache-Control` and
|
|
33927
|
+
* `Last-Modified` are added only when supplied.
|
|
33928
|
+
*
|
|
33929
|
+
* Custom headers from `options.headers` are merged first so the core PDF
|
|
33930
|
+
* headers always win on conflict.
|
|
33931
|
+
*
|
|
33932
|
+
* @param byteLength Length of the PDF body in bytes (the `Content-Length`).
|
|
33933
|
+
* @param options Optional response shaping (see {@link PdfResponseOptions}).
|
|
33934
|
+
* @returns A plain object of header name → value.
|
|
33935
|
+
*/
|
|
33936
|
+
function pdfHeaders(byteLength, options) {
|
|
33937
|
+
const opts = options ?? {};
|
|
33938
|
+
const headers = { ...opts.headers ?? {} };
|
|
33939
|
+
headers["Content-Type"] = "application/pdf";
|
|
33940
|
+
headers["Content-Length"] = String(byteLength);
|
|
33941
|
+
headers["Content-Disposition"] = contentDisposition(opts.download === true, opts.filename);
|
|
33942
|
+
if (opts.cacheControl !== void 0) headers["Cache-Control"] = opts.cacheControl;
|
|
33943
|
+
if (opts.lastModified !== void 0) headers["Last-Modified"] = opts.lastModified.toUTCString();
|
|
33944
|
+
return headers;
|
|
33945
|
+
}
|
|
33946
|
+
/**
|
|
33947
|
+
* Feature-detect the Web-standard `Response` constructor.
|
|
33948
|
+
*
|
|
33949
|
+
* @throws Error when `Response` is not a constructor in the current runtime.
|
|
33950
|
+
*/
|
|
33951
|
+
function assertResponseAvailable() {
|
|
33952
|
+
if (typeof Response !== "function") throw new Error("pdfResponse: the Web-standard `Response` constructor is not available in this runtime. Use `sendPdfToNodeResponse(res, bytes)` for a classic Node `http.ServerResponse`, or upgrade to a runtime with the Fetch API (Node >=18, Deno, Bun, Cloudflare Workers, browsers).");
|
|
33953
|
+
}
|
|
33954
|
+
/**
|
|
33955
|
+
* Build a Web-standard {@link Response} that streams the given PDF bytes
|
|
33956
|
+
* to the client with the correct headers and status code.
|
|
33957
|
+
*
|
|
33958
|
+
* Works in any runtime with the Fetch API: Cloudflare Workers, Deno, Bun,
|
|
33959
|
+
* Node >=18, and browsers (e.g. inside a Service Worker).
|
|
33960
|
+
*
|
|
33961
|
+
* @param bytes The PDF body.
|
|
33962
|
+
* @param options Optional response shaping (see {@link PdfResponseOptions}).
|
|
33963
|
+
* @returns A `Response` with status `options.status ?? 200`.
|
|
33964
|
+
* @throws If the runtime lacks the `Response` constructor.
|
|
33965
|
+
*/
|
|
33966
|
+
function pdfResponse(bytes, options) {
|
|
33967
|
+
assertResponseAvailable();
|
|
33968
|
+
const opts = options ?? {};
|
|
33969
|
+
const headers = pdfHeaders(bytes.byteLength, opts);
|
|
33970
|
+
const body = bytes.slice();
|
|
33971
|
+
return new Response(body, {
|
|
33972
|
+
status: opts.status ?? 200,
|
|
33973
|
+
headers
|
|
33974
|
+
});
|
|
33975
|
+
}
|
|
33976
|
+
/**
|
|
33977
|
+
* Build a streaming Web-standard {@link Response} from a
|
|
33978
|
+
* `ReadableStream` of PDF bytes (e.g. from `doc.saveAsStream()`).
|
|
33979
|
+
*
|
|
33980
|
+
* Because the total size is generally unknown up-front, `Content-Length`
|
|
33981
|
+
* is **omitted** unless `options.byteLength` is supplied — allowing the
|
|
33982
|
+
* runtime to use chunked transfer encoding.
|
|
33983
|
+
*
|
|
33984
|
+
* @param stream A readable stream of the PDF body.
|
|
33985
|
+
* @param options Response shaping plus an optional known `byteLength`.
|
|
33986
|
+
* @returns A streaming `Response`.
|
|
33987
|
+
* @throws If the runtime lacks the `Response` constructor.
|
|
33988
|
+
*/
|
|
33989
|
+
function pdfStreamResponse(stream, options) {
|
|
33990
|
+
assertResponseAvailable();
|
|
33991
|
+
const opts = options ?? {};
|
|
33992
|
+
const headers = opts.byteLength !== void 0 ? pdfHeaders(opts.byteLength, opts) : (() => {
|
|
33993
|
+
const h = pdfHeaders(0, opts);
|
|
33994
|
+
delete h["Content-Length"];
|
|
33995
|
+
return h;
|
|
33996
|
+
})();
|
|
33997
|
+
return new Response(stream, {
|
|
33998
|
+
status: opts.status ?? 200,
|
|
33999
|
+
headers
|
|
34000
|
+
});
|
|
34001
|
+
}
|
|
34002
|
+
/**
|
|
34003
|
+
* Send a PDF body to a classic Node-style response object
|
|
34004
|
+
* (`http.ServerResponse` or Express `res`).
|
|
34005
|
+
*
|
|
34006
|
+
* This calls {@link NodeServerResponseLike.writeHead} with the status and
|
|
34007
|
+
* {@link pdfHeaders}, then {@link NodeServerResponseLike.end} with the body.
|
|
34008
|
+
*
|
|
34009
|
+
* @param res The response object (structural typing — no import).
|
|
34010
|
+
* @param bytes The PDF body.
|
|
34011
|
+
* @param options Optional response shaping (see {@link PdfResponseOptions}).
|
|
34012
|
+
*/
|
|
34013
|
+
function sendPdfToNodeResponse(res, bytes, options) {
|
|
34014
|
+
const opts = options ?? {};
|
|
34015
|
+
const headers = pdfHeaders(bytes.byteLength, opts);
|
|
34016
|
+
res.writeHead(opts.status ?? 200, headers);
|
|
34017
|
+
res.end(bytes);
|
|
34018
|
+
}
|
|
34019
|
+
//#endregion
|
|
34020
|
+
//#region src/render/matrix.ts
|
|
34021
|
+
/** The identity transform. */
|
|
34022
|
+
function identity() {
|
|
34023
|
+
return [
|
|
34024
|
+
1,
|
|
34025
|
+
0,
|
|
34026
|
+
0,
|
|
34027
|
+
1,
|
|
34028
|
+
0,
|
|
34029
|
+
0
|
|
34030
|
+
];
|
|
34031
|
+
}
|
|
34032
|
+
/**
|
|
34033
|
+
* Concatenate two transforms: the result applies `m` **first**, then `n`
|
|
34034
|
+
* (i.e. `point · m · n`). This matches the PDF `cm` operator, where the new
|
|
34035
|
+
* CTM is `cmMatrix × CTM`.
|
|
34036
|
+
*/
|
|
34037
|
+
function multiply(m, n) {
|
|
34038
|
+
return [
|
|
34039
|
+
m[0] * n[0] + m[1] * n[2],
|
|
34040
|
+
m[0] * n[1] + m[1] * n[3],
|
|
34041
|
+
m[2] * n[0] + m[3] * n[2],
|
|
34042
|
+
m[2] * n[1] + m[3] * n[3],
|
|
34043
|
+
m[4] * n[0] + m[5] * n[2] + n[4],
|
|
34044
|
+
m[4] * n[1] + m[5] * n[3] + n[5]
|
|
34045
|
+
];
|
|
34046
|
+
}
|
|
34047
|
+
/** Apply a transform to a point, returning the transformed `[x, y]`. */
|
|
34048
|
+
function applyToPoint(m, x, y) {
|
|
34049
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
34050
|
+
}
|
|
34051
|
+
/** A pure translation transform. */
|
|
34052
|
+
function translation(tx, ty) {
|
|
34053
|
+
return [
|
|
34054
|
+
1,
|
|
34055
|
+
0,
|
|
34056
|
+
0,
|
|
34057
|
+
1,
|
|
34058
|
+
tx,
|
|
34059
|
+
ty
|
|
34060
|
+
];
|
|
34061
|
+
}
|
|
34062
|
+
/**
|
|
34063
|
+
* The magnitude by which `m` scales lengths — the geometric mean of the
|
|
34064
|
+
* x- and y-axis scale factors (`sqrt(|det|)`). Used to map a user-space line
|
|
34065
|
+
* width to device space.
|
|
34066
|
+
*/
|
|
34067
|
+
function meanScale(m) {
|
|
34068
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
34069
|
+
return Math.sqrt(Math.abs(det));
|
|
34070
|
+
}
|
|
34071
|
+
//#endregion
|
|
34072
|
+
//#region src/render/interpreter.ts
|
|
34073
|
+
/**
|
|
34074
|
+
* @module render/interpreter
|
|
34075
|
+
*
|
|
34076
|
+
* Executes a parsed PDF content stream through a graphics-state machine,
|
|
34077
|
+
* producing a resolution-independent {@link DisplayList} (page space, y-up).
|
|
34078
|
+
* This is the foundation the rasterizer and Canvas adapter render from.
|
|
34079
|
+
*
|
|
34080
|
+
* Supported operators: graphics state (`q`/`Q`/`cm`/`w`/`J`/`j`/`gs`), path
|
|
34081
|
+
* construction (`m`/`l`/`c`/`v`/`y`/`re`/`h`), path painting
|
|
34082
|
+
* (`f`/`F`/`f*`/`S`/`s`/`B`/`B*`/`b`/`b*`/`n`), clipping (`W`/`W*`), color
|
|
34083
|
+
* (`rg`/`RG`/`g`/`G`/`k`/`K`/`cs`/`CS`/`sc`/`scn`/`SC`/`SCN`), text
|
|
34084
|
+
* (`BT`/`ET`/`Tf`/`Td`/`TD`/`Tm`/`T*`/`Tc`/`Tw`/`Tz`/`TL`/`Ts`/`Tr`/`Tj`/`TJ`/`'`/`"`),
|
|
34085
|
+
* and XObjects (`Do`, recursing into form XObjects). Bézier curves are
|
|
34086
|
+
* flattened to polylines.
|
|
34087
|
+
*
|
|
34088
|
+
* @packageDocumentation
|
|
34089
|
+
*/
|
|
34090
|
+
function initialState$1() {
|
|
34091
|
+
return {
|
|
34092
|
+
ctm: identity(),
|
|
34093
|
+
fill: [
|
|
34094
|
+
0,
|
|
34095
|
+
0,
|
|
34096
|
+
0,
|
|
34097
|
+
255
|
|
34098
|
+
],
|
|
34099
|
+
stroke: [
|
|
34100
|
+
0,
|
|
34101
|
+
0,
|
|
34102
|
+
0,
|
|
34103
|
+
255
|
|
34104
|
+
],
|
|
34105
|
+
lineWidth: 1,
|
|
34106
|
+
lineCap: 0,
|
|
34107
|
+
lineJoin: 0,
|
|
34108
|
+
fillAlpha: 1,
|
|
34109
|
+
strokeAlpha: 1,
|
|
34110
|
+
clip: void 0,
|
|
34111
|
+
font: void 0,
|
|
34112
|
+
fontSize: 0,
|
|
34113
|
+
charSpace: 0,
|
|
34114
|
+
wordSpace: 0,
|
|
34115
|
+
hScale: 1,
|
|
34116
|
+
leading: 0,
|
|
34117
|
+
rise: 0,
|
|
34118
|
+
renderMode: 0
|
|
34119
|
+
};
|
|
34120
|
+
}
|
|
34121
|
+
function cloneState(s) {
|
|
34122
|
+
return { ...s };
|
|
34123
|
+
}
|
|
34124
|
+
function num$2(op) {
|
|
34125
|
+
return typeof op === "number" ? op : 0;
|
|
34126
|
+
}
|
|
34127
|
+
function nameOf(op) {
|
|
34128
|
+
if (op instanceof require_pdfObjects.PdfName) return op.value.replace(/^\//, "");
|
|
34129
|
+
if (typeof op === "string") return op.replace(/^\//, "");
|
|
34130
|
+
}
|
|
34131
|
+
function clamp255(v) {
|
|
34132
|
+
return Math.max(0, Math.min(255, Math.round(v * 255)));
|
|
34133
|
+
}
|
|
34134
|
+
function grayRgba(g) {
|
|
34135
|
+
const v = clamp255(g);
|
|
34136
|
+
return [
|
|
34137
|
+
v,
|
|
34138
|
+
v,
|
|
34139
|
+
v,
|
|
34140
|
+
255
|
|
34141
|
+
];
|
|
34142
|
+
}
|
|
34143
|
+
function rgbRgba(r, g, b) {
|
|
34144
|
+
return [
|
|
34145
|
+
clamp255(r),
|
|
34146
|
+
clamp255(g),
|
|
34147
|
+
clamp255(b),
|
|
34148
|
+
255
|
|
34149
|
+
];
|
|
34150
|
+
}
|
|
34151
|
+
function cmykRgba(c, m, y, k) {
|
|
34152
|
+
const [r, g, b] = require_pdfDocument.cmykToRgb(c, m, y, k);
|
|
34153
|
+
return [
|
|
34154
|
+
clamp255(r),
|
|
34155
|
+
clamp255(g),
|
|
34156
|
+
clamp255(b),
|
|
34157
|
+
255
|
|
34158
|
+
];
|
|
34159
|
+
}
|
|
34160
|
+
/** Interpret `sc`/`scn` numeric components by arity → RGBA. */
|
|
34161
|
+
function componentsToRgba(comps) {
|
|
34162
|
+
if (comps.length === 1) return grayRgba(comps[0]);
|
|
34163
|
+
if (comps.length === 3) return rgbRgba(comps[0], comps[1], comps[2]);
|
|
34164
|
+
if (comps.length === 4) return cmykRgba(comps[0], comps[1], comps[2], comps[3]);
|
|
34165
|
+
}
|
|
34166
|
+
const BEZIER_STEPS = 16;
|
|
34167
|
+
function flattenCubic(out, p0, p1, p2, p3) {
|
|
34168
|
+
for (let i = 1; i <= BEZIER_STEPS; i++) {
|
|
34169
|
+
const t = i / BEZIER_STEPS;
|
|
34170
|
+
const mt = 1 - t;
|
|
34171
|
+
const a = mt * mt * mt;
|
|
34172
|
+
const b = 3 * mt * mt * t;
|
|
34173
|
+
const c = 3 * mt * t * t;
|
|
34174
|
+
const d = t * t * t;
|
|
34175
|
+
out.push(a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0], a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]);
|
|
34176
|
+
}
|
|
34177
|
+
}
|
|
34178
|
+
/**
|
|
34179
|
+
* Execute a parsed content stream into a {@link DisplayList}.
|
|
34180
|
+
*
|
|
34181
|
+
* @param operators - Output of {@link parseContentStream}.
|
|
34182
|
+
* @param options - Page dimensions and (optional) resources/registry.
|
|
34183
|
+
*/
|
|
34184
|
+
function interpretContentStream(operators, options) {
|
|
34185
|
+
const items = [];
|
|
34186
|
+
const origin = options.origin ?? [0, 0];
|
|
34187
|
+
interpretInto(items, operators, initialState$1(), options, 0);
|
|
34188
|
+
return {
|
|
34189
|
+
items,
|
|
34190
|
+
width: options.width,
|
|
34191
|
+
height: options.height,
|
|
34192
|
+
origin
|
|
34193
|
+
};
|
|
34194
|
+
}
|
|
34195
|
+
const MAX_FORM_DEPTH = 12;
|
|
34196
|
+
function interpretInto(items, operators, startState, options, depth) {
|
|
34197
|
+
const stack = [];
|
|
34198
|
+
let gs = startState;
|
|
34199
|
+
let path = [];
|
|
34200
|
+
let cur = null;
|
|
34201
|
+
let curUser = [0, 0];
|
|
34202
|
+
let curPage = [0, 0];
|
|
34203
|
+
let pendingClip = null;
|
|
34204
|
+
let tm = identity();
|
|
34205
|
+
let tlm = identity();
|
|
34206
|
+
const startSub = (x, y) => {
|
|
34207
|
+
if (cur && cur.length >= 4) path.push({
|
|
34208
|
+
points: cur,
|
|
34209
|
+
closed: false
|
|
34210
|
+
});
|
|
34211
|
+
curUser = [x, y];
|
|
34212
|
+
curPage = applyToPoint(gs.ctm, x, y);
|
|
34213
|
+
cur = [curPage[0], curPage[1]];
|
|
34214
|
+
};
|
|
34215
|
+
const lineTo = (x, y) => {
|
|
34216
|
+
if (!cur) startSub(x, y);
|
|
34217
|
+
curUser = [x, y];
|
|
34218
|
+
curPage = applyToPoint(gs.ctm, x, y);
|
|
34219
|
+
cur.push(curPage[0], curPage[1]);
|
|
34220
|
+
};
|
|
34221
|
+
const finishPath = () => {
|
|
34222
|
+
if (cur && cur.length >= 4) path.push({
|
|
34223
|
+
points: cur,
|
|
34224
|
+
closed: false
|
|
34225
|
+
});
|
|
34226
|
+
const result = path;
|
|
34227
|
+
path = [];
|
|
34228
|
+
cur = null;
|
|
34229
|
+
return result;
|
|
34230
|
+
};
|
|
34231
|
+
const applyClipIfPending = (subs) => {
|
|
34232
|
+
if (pendingClip) {
|
|
34233
|
+
gs = {
|
|
34234
|
+
...gs,
|
|
34235
|
+
clip: subs.map((s) => ({
|
|
34236
|
+
points: s.points.slice(),
|
|
34237
|
+
closed: true
|
|
34238
|
+
}))
|
|
34239
|
+
};
|
|
34240
|
+
pendingClip = null;
|
|
34241
|
+
}
|
|
34242
|
+
};
|
|
34243
|
+
const pushOpen = (closed) => {
|
|
34244
|
+
if (cur && cur.length >= 4) path.push({
|
|
34245
|
+
points: cur,
|
|
34246
|
+
closed
|
|
34247
|
+
});
|
|
34248
|
+
cur = null;
|
|
34249
|
+
};
|
|
34250
|
+
for (const { operator, operands } of operators) switch (operator) {
|
|
34251
|
+
case "q":
|
|
34252
|
+
stack.push(cloneState(gs));
|
|
34253
|
+
break;
|
|
34254
|
+
case "Q":
|
|
34255
|
+
if (stack.length) gs = stack.pop();
|
|
34256
|
+
break;
|
|
34257
|
+
case "cm":
|
|
34258
|
+
gs = {
|
|
34259
|
+
...gs,
|
|
34260
|
+
ctm: multiply([
|
|
34261
|
+
num$2(operands[0]),
|
|
34262
|
+
num$2(operands[1]),
|
|
34263
|
+
num$2(operands[2]),
|
|
34264
|
+
num$2(operands[3]),
|
|
34265
|
+
num$2(operands[4]),
|
|
34266
|
+
num$2(operands[5])
|
|
34267
|
+
], gs.ctm)
|
|
34268
|
+
};
|
|
34269
|
+
break;
|
|
34270
|
+
case "w":
|
|
34271
|
+
gs = {
|
|
34272
|
+
...gs,
|
|
34273
|
+
lineWidth: num$2(operands[0])
|
|
32424
34274
|
};
|
|
32425
34275
|
break;
|
|
32426
34276
|
case "J":
|
|
@@ -36084,6 +37934,12 @@ Object.defineProperty(exports, "ForeignPageError", {
|
|
|
36084
37934
|
return ForeignPageError;
|
|
36085
37935
|
}
|
|
36086
37936
|
});
|
|
37937
|
+
Object.defineProperty(exports, "Fragment", {
|
|
37938
|
+
enumerable: true,
|
|
37939
|
+
get: function() {
|
|
37940
|
+
return Fragment;
|
|
37941
|
+
}
|
|
37942
|
+
});
|
|
36087
37943
|
Object.defineProperty(exports, "InvalidColorError", {
|
|
36088
37944
|
enumerable: true,
|
|
36089
37945
|
get: function() {
|
|
@@ -36120,6 +37976,18 @@ Object.defineProperty(exports, "MdpPermission", {
|
|
|
36120
37976
|
return MdpPermission;
|
|
36121
37977
|
}
|
|
36122
37978
|
});
|
|
37979
|
+
Object.defineProperty(exports, "MemoryBudget", {
|
|
37980
|
+
enumerable: true,
|
|
37981
|
+
get: function() {
|
|
37982
|
+
return MemoryBudget;
|
|
37983
|
+
}
|
|
37984
|
+
});
|
|
37985
|
+
Object.defineProperty(exports, "MemoryBudgetExceededError", {
|
|
37986
|
+
enumerable: true,
|
|
37987
|
+
get: function() {
|
|
37988
|
+
return MemoryBudgetExceededError;
|
|
37989
|
+
}
|
|
37990
|
+
});
|
|
36123
37991
|
Object.defineProperty(exports, "MissingOnValueCheckError", {
|
|
36124
37992
|
enumerable: true,
|
|
36125
37993
|
get: function() {
|
|
@@ -36294,6 +38162,12 @@ Object.defineProperty(exports, "SARIF_SCHEMA_URI", {
|
|
|
36294
38162
|
return SARIF_SCHEMA_URI;
|
|
36295
38163
|
}
|
|
36296
38164
|
});
|
|
38165
|
+
Object.defineProperty(exports, "SIMD_NOTE", {
|
|
38166
|
+
enumerable: true,
|
|
38167
|
+
get: function() {
|
|
38168
|
+
return SIMD_NOTE;
|
|
38169
|
+
}
|
|
38170
|
+
});
|
|
36297
38171
|
Object.defineProperty(exports, "SRGB_ICC_PROFILE", {
|
|
36298
38172
|
enumerable: true,
|
|
36299
38173
|
get: function() {
|
|
@@ -36306,6 +38180,24 @@ Object.defineProperty(exports, "STANDARD_SPOT_FUNCTIONS", {
|
|
|
36306
38180
|
return STANDARD_SPOT_FUNCTIONS;
|
|
36307
38181
|
}
|
|
36308
38182
|
});
|
|
38183
|
+
Object.defineProperty(exports, "SharedCounter", {
|
|
38184
|
+
enumerable: true,
|
|
38185
|
+
get: function() {
|
|
38186
|
+
return SharedCounter;
|
|
38187
|
+
}
|
|
38188
|
+
});
|
|
38189
|
+
Object.defineProperty(exports, "SharedFlag", {
|
|
38190
|
+
enumerable: true,
|
|
38191
|
+
get: function() {
|
|
38192
|
+
return SharedFlag;
|
|
38193
|
+
}
|
|
38194
|
+
});
|
|
38195
|
+
Object.defineProperty(exports, "SharedRingBuffer", {
|
|
38196
|
+
enumerable: true,
|
|
38197
|
+
get: function() {
|
|
38198
|
+
return SharedRingBuffer;
|
|
38199
|
+
}
|
|
38200
|
+
});
|
|
36309
38201
|
Object.defineProperty(exports, "StreamingParseError", {
|
|
36310
38202
|
enumerable: true,
|
|
36311
38203
|
get: function() {
|
|
@@ -36570,6 +38462,12 @@ Object.defineProperty(exports, "buildFieldLockDict", {
|
|
|
36570
38462
|
return buildFieldLockDict;
|
|
36571
38463
|
}
|
|
36572
38464
|
});
|
|
38465
|
+
Object.defineProperty(exports, "buildFormFromJsonSchema", {
|
|
38466
|
+
enumerable: true,
|
|
38467
|
+
get: function() {
|
|
38468
|
+
return buildFormFromJsonSchema;
|
|
38469
|
+
}
|
|
38470
|
+
});
|
|
36573
38471
|
Object.defineProperty(exports, "buildFreeFormGouraudShading", {
|
|
36574
38472
|
enumerable: true,
|
|
36575
38473
|
get: function() {
|
|
@@ -36864,6 +38762,12 @@ Object.defineProperty(exports, "createAssociatedFile", {
|
|
|
36864
38762
|
return createAssociatedFile;
|
|
36865
38763
|
}
|
|
36866
38764
|
});
|
|
38765
|
+
Object.defineProperty(exports, "createMemoryBudget", {
|
|
38766
|
+
enumerable: true,
|
|
38767
|
+
get: function() {
|
|
38768
|
+
return createMemoryBudget;
|
|
38769
|
+
}
|
|
38770
|
+
});
|
|
36867
38771
|
Object.defineProperty(exports, "createRangeFetcher", {
|
|
36868
38772
|
enumerable: true,
|
|
36869
38773
|
get: function() {
|
|
@@ -36942,6 +38846,12 @@ Object.defineProperty(exports, "detectNextGenFormat", {
|
|
|
36942
38846
|
return detectNextGenFormat;
|
|
36943
38847
|
}
|
|
36944
38848
|
});
|
|
38849
|
+
Object.defineProperty(exports, "detectRuntimeCapabilities", {
|
|
38850
|
+
enumerable: true,
|
|
38851
|
+
get: function() {
|
|
38852
|
+
return detectRuntimeCapabilities;
|
|
38853
|
+
}
|
|
38854
|
+
});
|
|
36945
38855
|
Object.defineProperty(exports, "detectTransparency", {
|
|
36946
38856
|
enumerable: true,
|
|
36947
38857
|
get: function() {
|
|
@@ -37470,6 +39380,12 @@ Object.defineProperty(exports, "h", {
|
|
|
37470
39380
|
return h;
|
|
37471
39381
|
}
|
|
37472
39382
|
});
|
|
39383
|
+
Object.defineProperty(exports, "h$1", {
|
|
39384
|
+
enumerable: true,
|
|
39385
|
+
get: function() {
|
|
39386
|
+
return h$1;
|
|
39387
|
+
}
|
|
39388
|
+
});
|
|
37473
39389
|
Object.defineProperty(exports, "hasImageDecoder", {
|
|
37474
39390
|
enumerable: true,
|
|
37475
39391
|
get: function() {
|
|
@@ -37548,12 +39464,24 @@ Object.defineProperty(exports, "isLinearized", {
|
|
|
37548
39464
|
return isLinearized;
|
|
37549
39465
|
}
|
|
37550
39466
|
});
|
|
39467
|
+
Object.defineProperty(exports, "isSharedMemoryAvailable", {
|
|
39468
|
+
enumerable: true,
|
|
39469
|
+
get: function() {
|
|
39470
|
+
return isSharedMemoryAvailable;
|
|
39471
|
+
}
|
|
39472
|
+
});
|
|
37551
39473
|
Object.defineProperty(exports, "isValidLevel", {
|
|
37552
39474
|
enumerable: true,
|
|
37553
39475
|
get: function() {
|
|
37554
39476
|
return isValidLevel;
|
|
37555
39477
|
}
|
|
37556
39478
|
});
|
|
39479
|
+
Object.defineProperty(exports, "isWasmSimdSupported", {
|
|
39480
|
+
enumerable: true,
|
|
39481
|
+
get: function() {
|
|
39482
|
+
return isWasmSimdSupported;
|
|
39483
|
+
}
|
|
39484
|
+
});
|
|
37557
39485
|
Object.defineProperty(exports, "isWoff", {
|
|
37558
39486
|
enumerable: true,
|
|
37559
39487
|
get: function() {
|
|
@@ -37572,6 +39500,18 @@ Object.defineProperty(exports, "itfToOperators", {
|
|
|
37572
39500
|
return itfToOperators;
|
|
37573
39501
|
}
|
|
37574
39502
|
});
|
|
39503
|
+
Object.defineProperty(exports, "jsx", {
|
|
39504
|
+
enumerable: true,
|
|
39505
|
+
get: function() {
|
|
39506
|
+
return jsx;
|
|
39507
|
+
}
|
|
39508
|
+
});
|
|
39509
|
+
Object.defineProperty(exports, "jsxs", {
|
|
39510
|
+
enumerable: true,
|
|
39511
|
+
get: function() {
|
|
39512
|
+
return jsxs;
|
|
39513
|
+
}
|
|
39514
|
+
});
|
|
37575
39515
|
Object.defineProperty(exports, "labToRgb", {
|
|
37576
39516
|
enumerable: true,
|
|
37577
39517
|
get: function() {
|
|
@@ -37734,6 +39674,24 @@ Object.defineProperty(exports, "pdfA4Rules", {
|
|
|
37734
39674
|
return pdfA4Rules;
|
|
37735
39675
|
}
|
|
37736
39676
|
});
|
|
39677
|
+
Object.defineProperty(exports, "pdfHeaders", {
|
|
39678
|
+
enumerable: true,
|
|
39679
|
+
get: function() {
|
|
39680
|
+
return pdfHeaders;
|
|
39681
|
+
}
|
|
39682
|
+
});
|
|
39683
|
+
Object.defineProperty(exports, "pdfResponse", {
|
|
39684
|
+
enumerable: true,
|
|
39685
|
+
get: function() {
|
|
39686
|
+
return pdfResponse;
|
|
39687
|
+
}
|
|
39688
|
+
});
|
|
39689
|
+
Object.defineProperty(exports, "pdfStreamResponse", {
|
|
39690
|
+
enumerable: true,
|
|
39691
|
+
get: function() {
|
|
39692
|
+
return pdfStreamResponse;
|
|
39693
|
+
}
|
|
39694
|
+
});
|
|
37737
39695
|
Object.defineProperty(exports, "preflightPdfA", {
|
|
37738
39696
|
enumerable: true,
|
|
37739
39697
|
get: function() {
|
|
@@ -37896,6 +39854,12 @@ Object.defineProperty(exports, "renderToPdf", {
|
|
|
37896
39854
|
return renderToPdf;
|
|
37897
39855
|
}
|
|
37898
39856
|
});
|
|
39857
|
+
Object.defineProperty(exports, "renderToPdf$1", {
|
|
39858
|
+
enumerable: true,
|
|
39859
|
+
get: function() {
|
|
39860
|
+
return renderToPdf$1;
|
|
39861
|
+
}
|
|
39862
|
+
});
|
|
37899
39863
|
Object.defineProperty(exports, "reorderVisual", {
|
|
37900
39864
|
enumerable: true,
|
|
37901
39865
|
get: function() {
|
|
@@ -38004,6 +39968,12 @@ Object.defineProperty(exports, "searchTextItems", {
|
|
|
38004
39968
|
return searchTextItems;
|
|
38005
39969
|
}
|
|
38006
39970
|
});
|
|
39971
|
+
Object.defineProperty(exports, "sendPdfToNodeResponse", {
|
|
39972
|
+
enumerable: true,
|
|
39973
|
+
get: function() {
|
|
39974
|
+
return sendPdfToNodeResponse;
|
|
39975
|
+
}
|
|
39976
|
+
});
|
|
38007
39977
|
Object.defineProperty(exports, "setCertificationLevel", {
|
|
38008
39978
|
enumerable: true,
|
|
38009
39979
|
get: function() {
|
|
@@ -38323,4 +40293,4 @@ Object.defineProperty(exports, "xyzToRgb", {
|
|
|
38323
40293
|
}
|
|
38324
40294
|
});
|
|
38325
40295
|
|
|
38326
|
-
//# sourceMappingURL=src-
|
|
40296
|
+
//# sourceMappingURL=src-aDYhSnal.cjs.map
|