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.
@@ -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$2 = 10;
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$2) + 4 : 0;
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$2;
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$1 = 10;
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$1;
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$1) - 4;
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$1;
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$1) + 4;
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/render/matrix.ts
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
- * Concatenate two transforms: the result applies `m` **first**, then `n`
32184
- * (i.e. `point · m · n`). This matches the PDF `cm` operator, where the new
32185
- * CTM is `cmMatrix × CTM`.
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
- * The magnitude by which `m` scales lengths the geometric mean of the
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
- function meanScale(m) {
32218
- const det = m[0] * m[3] - m[1] * m[2];
32219
- return Math.sqrt(Math.abs(det));
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
- * @module render/interpreter
32226
+ * Report whether shared-memory concurrency is usable in this runtime.
32225
32227
  *
32226
- * Executes a parsed PDF content stream through a graphics-state machine,
32227
- * producing a resolution-independent {@link DisplayList} (page space, y-up).
32228
- * This is the foundation the rasterizer and Canvas adapter render from.
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
- * Supported operators: graphics state (`q`/`Q`/`cm`/`w`/`J`/`j`/`gs`), path
32231
- * construction (`m`/`l`/`c`/`v`/`y`/`re`/`h`), path painting
32232
- * (`f`/`F`/`f*`/`S`/`s`/`B`/`B*`/`b`/`b*`/`n`), clipping (`W`/`W*`), color
32233
- * (`rg`/`RG`/`g`/`G`/`k`/`K`/`cs`/`CS`/`sc`/`scn`/`SC`/`SCN`), text
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
- * @packageDocumentation
32239
+ * @returns `true` iff shared-memory primitives can be constructed and used.
32239
32240
  */
32240
- function initialState$1() {
32241
- return {
32242
- ctm: identity(),
32243
- fill: [
32244
- 0,
32245
- 0,
32246
- 0,
32247
- 255
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
- * Execute a parsed content stream into a {@link DisplayList}.
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
- * @param operators - Output of {@link parseContentStream}.
32332
- * @param options - Page dimensions and (optional) resources/registry.
32258
+ * All mutations use `Atomics`, so increments from concurrent agents never
32259
+ * lose updates.
32333
32260
  */
32334
- function interpretContentStream(operators, options) {
32335
- const items = [];
32336
- const origin = options.origin ?? [0, 0];
32337
- interpretInto(items, operators, initialState$1(), options, 0);
32338
- return {
32339
- items,
32340
- width: options.width,
32341
- height: options.height,
32342
- origin
32343
- };
32344
- }
32345
- const MAX_FORM_DEPTH = 12;
32346
- function interpretInto(items, operators, startState, options, depth) {
32347
- const stack = [];
32348
- let gs = startState;
32349
- let path = [];
32350
- let cur = null;
32351
- let curUser = [0, 0];
32352
- let curPage = [0, 0];
32353
- let pendingClip = null;
32354
- let tm = identity();
32355
- let tlm = identity();
32356
- const startSub = (x, y) => {
32357
- if (cur && cur.length >= 4) path.push({
32358
- points: cur,
32359
- closed: false
32360
- });
32361
- curUser = [x, y];
32362
- curPage = applyToPoint(gs.ctm, x, y);
32363
- cur = [curPage[0], curPage[1]];
32364
- };
32365
- const lineTo = (x, y) => {
32366
- if (!cur) startSub(x, y);
32367
- curUser = [x, y];
32368
- curPage = applyToPoint(gs.ctm, x, y);
32369
- cur.push(curPage[0], curPage[1]);
32370
- };
32371
- const finishPath = () => {
32372
- if (cur && cur.length >= 4) path.push({
32373
- points: cur,
32374
- closed: false
32375
- });
32376
- const result = path;
32377
- path = [];
32378
- cur = null;
32379
- return result;
32380
- };
32381
- const applyClipIfPending = (subs) => {
32382
- if (pendingClip) {
32383
- gs = {
32384
- ...gs,
32385
- clip: subs.map((s) => ({
32386
- points: s.points.slice(),
32387
- closed: true
32388
- }))
32389
- };
32390
- pendingClip = null;
32391
- }
32392
- };
32393
- const pushOpen = (closed) => {
32394
- if (cur && cur.length >= 4) path.push({
32395
- points: cur,
32396
- closed
32397
- });
32398
- cur = null;
32399
- };
32400
- for (const { operator, operands } of operators) switch (operator) {
32401
- case "q":
32402
- stack.push(cloneState(gs));
32403
- break;
32404
- case "Q":
32405
- if (stack.length) gs = stack.pop();
32406
- break;
32407
- case "cm":
32408
- gs = {
32409
- ...gs,
32410
- ctm: multiply([
32411
- num$2(operands[0]),
32412
- num$2(operands[1]),
32413
- num$2(operands[2]),
32414
- num$2(operands[3]),
32415
- num$2(operands[4]),
32416
- num$2(operands[5])
32417
- ], gs.ctm)
32418
- };
32419
- break;
32420
- case "w":
32421
- gs = {
32422
- ...gs,
32423
- lineWidth: num$2(operands[0])
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-B0gmgVEs.cjs.map
40296
+ //# sourceMappingURL=src-aDYhSnal.cjs.map