@embedpdf/engine-services 3.0.0-next.1 → 3.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -226,8 +226,10 @@ var EventHub = class {
226
226
  };
227
227
  }
228
228
  publish(event) {
229
- const { serverId } = event.origin;
230
- if (serverId !== null) this.serverId = this.serverId === null ? serverId : Math.max(this.serverId, serverId);
229
+ if ("origin" in event) {
230
+ const { serverId } = event.origin;
231
+ if (serverId !== null) this.serverId = this.serverId === null ? serverId : Math.max(this.serverId, serverId);
232
+ }
231
233
  for (const listener of [...this.listeners]) try {
232
234
  listener(event);
233
235
  } catch (err) {
@@ -1164,13 +1166,34 @@ var MetadataMutator = class {
1164
1166
  };
1165
1167
  //#endregion
1166
1168
  //#region src/runtime/memory/structs.ts
1169
+ /** Generated from `EPDF_CHAR_GEOMETRY`; pinned by native static_asserts. */
1170
+ const EPDF_CHAR_GEOMETRY_LAYOUT = {
1171
+ bytes: 124,
1172
+ offsets: {
1173
+ looseBox: 0,
1174
+ tightBox: 16,
1175
+ looseQuad: 32,
1176
+ tightQuad: 64,
1177
+ matrix: 96,
1178
+ flags: 120
1179
+ },
1180
+ flags: {
1181
+ hasTightBox: 1,
1182
+ hasLooseQuad: 2,
1183
+ hasTightQuad: 4,
1184
+ upright: 8,
1185
+ space: 16,
1186
+ empty: 32,
1187
+ synthesized: 64
1188
+ }
1189
+ };
1167
1190
  /** Decode an `FS_RECTF` struct that a native call wrote into `ptr`. */
1168
- function readRectF(mem, ptr) {
1191
+ function readRectF(mem, ptr, byteOffset = 0) {
1169
1192
  return {
1170
- left: readF32(mem, ptr, 0),
1171
- top: readF32(mem, ptr, 4),
1172
- right: readF32(mem, ptr, 8),
1173
- bottom: readF32(mem, ptr, 12)
1193
+ left: readF32(mem, ptr, byteOffset),
1194
+ top: readF32(mem, ptr, byteOffset + 4),
1195
+ right: readF32(mem, ptr, byteOffset + 8),
1196
+ bottom: readF32(mem, ptr, byteOffset + 12)
1174
1197
  };
1175
1198
  }
1176
1199
  /** Decode an `FS_SIZEF` struct that a native call wrote into `ptr`. */
@@ -1706,6 +1729,42 @@ var PagesInserter = class {
1706
1729
  cache: null
1707
1730
  };
1708
1731
  }
1732
+ /**
1733
+ * Create `count` blank pages of `size` at `destIndex`. Same mutation
1734
+ * contract as `insert`, but native creation (`FPDFPage_New`) instead of a
1735
+ * deep copy: no source document, so none of the retain-until-close
1736
+ * lifetime hazard above. Each page gets an empty content stream
1737
+ * (`FPDFPage_GenerateContent`) so the saved PDF renders identically in
1738
+ * third-party viewers.
1739
+ */
1740
+ insertBlank(spec, destIndex, signal) {
1741
+ throwIfAborted(signal);
1742
+ const { size } = spec;
1743
+ const count = spec.count ?? 1;
1744
+ if (!Number.isFinite(size?.width) || !Number.isFinite(size?.height) || size.width <= 0 || size.height <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank size must have positive finite dimensions, got ${size?.width}x${size?.height}`);
1745
+ if (!Number.isInteger(count) || count < 1 || count > _embedpdf_engine_core_runtime.PAGE_INSERT_BLANK_MAX_COUNT) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank count ${count} out of range [1, ${_embedpdf_engine_core_runtime.PAGE_INSERT_BLANK_MAX_COUNT}]`);
1746
+ const { fn } = this.runtime;
1747
+ const destPtr = this.session.requireDocPtr();
1748
+ const beforeCount = fn.FPDF_GetPageCount(destPtr);
1749
+ const at = destIndex ?? beforeCount;
1750
+ if (!Number.isInteger(at) || at < 0 || at > beforeCount) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank destIndex ${at} out of range [0, ${beforeCount}]`);
1751
+ for (let i = 0; i < count; i++) {
1752
+ const pagePtr = fn.FPDFPage_New(destPtr, at + i, size.width, size.height);
1753
+ if (!pagePtr) {
1754
+ for (let j = 0; j < i; j++) fn.FPDFPage_Delete(destPtr, at);
1755
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_New rejected page ${i + 1}/${count} at index ${at + i}`);
1756
+ }
1757
+ fn.FPDFPage_GenerateContent(pagePtr);
1758
+ fn.FPDF_ClosePage(pagePtr);
1759
+ }
1760
+ this.session.refreshPageRegistry();
1761
+ const layout = new PagesReader(this.runtime, this.session).read(signal);
1762
+ return {
1763
+ insertedPageObjectNumbers: layout.pages.slice(at, at + count).map((page) => page.pageObjectNumber),
1764
+ layout,
1765
+ cache: null
1766
+ };
1767
+ }
1709
1768
  };
1710
1769
  //#endregion
1711
1770
  //#region src/features/annotations/internal/read/annotationReadContext.ts
@@ -2154,6 +2213,49 @@ function readQuadPoints(fn, mem, annotPtr) {
2154
2213
  });
2155
2214
  }
2156
2215
  //#endregion
2216
+ //#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
2217
+ /**
2218
+ * Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
2219
+ * the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
2220
+ *
2221
+ * - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
2222
+ * kind (box AND vertex); the plugin converts it back to its CW-content
2223
+ * `rot` once at the repository seam.
2224
+ * - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
2225
+ * free-text); a vertex annotation never carries one (its points are the
2226
+ * visual), so vertex readers don't ask for it.
2227
+ *
2228
+ * Both are absent on un-rotated annotations (and on everything authored before
2229
+ * rotation shipped), so both reads return `undefined` and the DTO field stays
2230
+ * unset.
2231
+ */
2232
+ const KEY_ROTATION$1 = "Rotation";
2233
+ const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
2234
+ /** The advisory/portable rotation scalar (degrees, PDF convention), or
2235
+ * `undefined` when absent or zero. */
2236
+ function readAnnotationRotation(fn, mem, annotPtr) {
2237
+ if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
2238
+ return withScratch(mem, 4, (buf) => {
2239
+ if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
2240
+ const v = readF32(mem, buf);
2241
+ return v ? v : void 0;
2242
+ });
2243
+ }
2244
+ /** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
2245
+ function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
2246
+ if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
2247
+ return withScratch(mem, 16, (buf) => {
2248
+ if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
2249
+ const r = readRectF(mem, buf);
2250
+ return {
2251
+ left: r.left,
2252
+ bottom: r.bottom,
2253
+ right: r.right,
2254
+ top: r.top
2255
+ };
2256
+ });
2257
+ }
2258
+ //#endregion
2157
2259
  //#region src/features/annotations/internal/textEditIntent.ts
2158
2260
  /** Adobe-compatible `/IT` names, normalized at the engine-core boundary. */
2159
2261
  const IT_REPLACE = "Replace";
@@ -2188,13 +2290,17 @@ function readCaret(fn, mem, annotPtr, base) {
2188
2290
  const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
2189
2291
  const rd = readRectangleDifferences(fn, mem, annotPtr);
2190
2292
  const intent = caretIntentFromName(readIntent(fn, mem, annotPtr));
2293
+ const rotation = readAnnotationRotation(fn, mem, annotPtr);
2294
+ const unrotatedRect = readAnnotationUnrotatedRect(fn, mem, annotPtr);
2191
2295
  return {
2192
2296
  ...base,
2193
2297
  subtype: "caret",
2194
2298
  intent,
2195
2299
  color,
2196
2300
  opacity,
2197
- rectDifferences: rd
2301
+ rectDifferences: rd,
2302
+ ...rotation != null ? { rotation } : {},
2303
+ ...unrotatedRect ? { unrotatedRect } : {}
2198
2304
  };
2199
2305
  }
2200
2306
  //#endregion
@@ -2612,49 +2718,6 @@ function readFilledStyleExtras(fn, mem, annotPtr) {
2612
2718
  };
2613
2719
  }
2614
2720
  //#endregion
2615
- //#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
2616
- /**
2617
- * Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
2618
- * the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
2619
- *
2620
- * - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
2621
- * kind (box AND vertex); the plugin converts it back to its CW-content
2622
- * `rot` once at the repository seam.
2623
- * - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
2624
- * free-text); a vertex annotation never carries one (its points are the
2625
- * visual), so vertex readers don't ask for it.
2626
- *
2627
- * Both are absent on un-rotated annotations (and on everything authored before
2628
- * rotation shipped), so both reads return `undefined` and the DTO field stays
2629
- * unset.
2630
- */
2631
- const KEY_ROTATION$1 = "Rotation";
2632
- const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
2633
- /** The advisory/portable rotation scalar (degrees, PDF convention), or
2634
- * `undefined` when absent or zero. */
2635
- function readAnnotationRotation(fn, mem, annotPtr) {
2636
- if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
2637
- return withScratch(mem, 4, (buf) => {
2638
- if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
2639
- const v = readF32(mem, buf);
2640
- return v ? v : void 0;
2641
- });
2642
- }
2643
- /** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
2644
- function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
2645
- if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
2646
- return withScratch(mem, 16, (buf) => {
2647
- if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
2648
- const r = readRectF(mem, buf);
2649
- return {
2650
- left: r.left,
2651
- bottom: r.bottom,
2652
- right: r.right,
2653
- top: r.top
2654
- };
2655
- });
2656
- }
2657
- //#endregion
2658
2721
  //#region src/features/annotations/internal/read/readFreeTextAnnotation.ts
2659
2722
  /** Default `/DA` colour (black) when an annotation has no default appearance. */
2660
2723
  const DEFAULT_FREETEXT_COLOR$1 = {
@@ -3107,6 +3170,47 @@ function readStampName(fn, mem, annotPtr) {
3107
3170
  return readAnnotString(fn, mem, annotPtr, "Name");
3108
3171
  }
3109
3172
  //#endregion
3173
+ //#region src/features/annotations/internal/annotationState.ts
3174
+ /**
3175
+ * `/State` + `/StateModel` spelling maps — ISO 32000 §12.5.6.3, Table 174.
3176
+ *
3177
+ * Known values are wire-stable lowercase (the house vocabulary — same
3178
+ * rationale as `AnnotationReplyType` / `AnnotationBorderStyle`); the PDF
3179
+ * stores the capitalized Table 174 spellings. Unknown values — custom
3180
+ * Acrobat state models and their states — pass through VERBATIM in both
3181
+ * directions, so foreign review workflows survive a round trip.
3182
+ */
3183
+ const STATE_MODEL_TO_PDF = {
3184
+ review: "Review",
3185
+ marked: "Marked"
3186
+ };
3187
+ const STATE_TO_PDF = {
3188
+ accepted: "Accepted",
3189
+ rejected: "Rejected",
3190
+ cancelled: "Cancelled",
3191
+ completed: "Completed",
3192
+ none: "None",
3193
+ marked: "Marked",
3194
+ unmarked: "Unmarked"
3195
+ };
3196
+ const PDF_TO_STATE_MODEL = invert(STATE_MODEL_TO_PDF);
3197
+ const PDF_TO_STATE = invert(STATE_TO_PDF);
3198
+ function stateModelToPdf(value) {
3199
+ return STATE_MODEL_TO_PDF[value] ?? value;
3200
+ }
3201
+ function stateModelFromPdf(value) {
3202
+ return PDF_TO_STATE_MODEL[value] ?? value;
3203
+ }
3204
+ function stateToPdf(value) {
3205
+ return STATE_TO_PDF[value] ?? value;
3206
+ }
3207
+ function stateFromPdf(value) {
3208
+ return PDF_TO_STATE[value] ?? value;
3209
+ }
3210
+ function invert(map) {
3211
+ return Object.fromEntries(Object.entries(map).map(([wire, pdf]) => [pdf, wire]));
3212
+ }
3213
+ //#endregion
3110
3214
  //#region src/features/annotations/internal/read/readTextAnnotation.ts
3111
3215
  /** Default `/C` — matches the generator's yellow note fill and the writer default. */
3112
3216
  const DEFAULT_NOTE_COLOR$1 = {
@@ -3121,12 +3225,16 @@ function readText(fn, mem, annotPtr, base) {
3121
3225
  const ca = readAnnotOpacity(fn, mem, annotPtr);
3122
3226
  const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
3123
3227
  const icon = NOTE_CODE_TO_ICON[fn.EPDFAnnot_GetName(annotPtr)] ?? DEFAULT_NOTE_ICON;
3228
+ const stateRaw = readAnnotString(fn, mem, annotPtr, "State");
3229
+ const stateModelRaw = readAnnotString(fn, mem, annotPtr, "StateModel");
3124
3230
  return {
3125
3231
  ...base,
3126
3232
  subtype: "text",
3127
3233
  icon,
3128
3234
  color,
3129
- opacity
3235
+ opacity,
3236
+ state: stateRaw === null ? null : stateFromPdf(stateRaw),
3237
+ stateModel: stateModelRaw === null ? null : stateModelFromPdf(stateModelRaw)
3130
3238
  };
3131
3239
  }
3132
3240
  //#endregion
@@ -3841,6 +3949,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
3841
3949
  const rect = readAnnotRect(fn, mem, annotPtr);
3842
3950
  const flags = readAnnotFlags(fn, annotPtr);
3843
3951
  const contents = readAnnotString(fn, mem, annotPtr, "Contents");
3952
+ const subject = readAnnotString(fn, mem, annotPtr, "Subj");
3844
3953
  const author = readAnnotString(fn, mem, annotPtr, "T");
3845
3954
  const createdRaw = readAnnotString(fn, mem, annotPtr, "CreationDate");
3846
3955
  const modifiedRaw = readAnnotString(fn, mem, annotPtr, "M");
@@ -3857,6 +3966,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
3857
3966
  flags,
3858
3967
  rect,
3859
3968
  contents,
3969
+ subject,
3860
3970
  author,
3861
3971
  created: createdRaw ? pdfDateToIso(createdRaw) : null,
3862
3972
  modified: modifiedRaw ? pdfDateToIso(modifiedRaw) : null,
@@ -4131,19 +4241,31 @@ const ANNOT_SUBTYPE_WIDGET = 20;
4131
4241
  /** `FPDF_ANNOT_FREETEXT` — free-text annotation subtype code. */
4132
4242
  const ANNOT_SUBTYPE_FREETEXT = 3;
4133
4243
  /**
4134
- * BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13): the kinds
4135
- * whose v3 writers put rotation in the AP `/Matrix` + `/EMBD_Metadata`
4136
- * `/UnrotatedRect`. Only these are eligible for rotation-stripped appearance
4137
- * rendering — vertex kinds (line/polyline/polygon/ink) pre-rotate their
4138
- * geometry, so their rasters must stay on the classic path.
4244
+ * BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13, caret 14):
4245
+ * the kinds whose v3 writers put rotation in the AP `/Matrix` +
4246
+ * `/EMBD_Metadata` `/UnrotatedRect`. Only these are eligible for
4247
+ * rotation-stripped appearance rendering — vertex kinds
4248
+ * (line/polyline/polygon/ink) pre-rotate their geometry, so their rasters must
4249
+ * stay on the classic path. This set MUST cover every kind whose reader
4250
+ * surfaces the rotation pair: `fromDTO` (plugin-annotation repository) mirrors
4251
+ * this exact condition to re-apply the stripped rotation as `apRot`.
4139
4252
  */
4140
4253
  const BOX_FAMILY_SUBTYPES = /* @__PURE__ */ new Set([
4141
4254
  3,
4142
4255
  5,
4143
4256
  6,
4144
- 13
4257
+ 13,
4258
+ 14
4145
4259
  ]);
4146
4260
  /**
4261
+ * Per-appearance output-pixel ceiling (see the clamp in `renderOne`).
4262
+ * 16 M px ≈ a 4000² raster ≈ 64 MB of transient RGBA — safely inside the
4263
+ * wasm heap for the one-at-a-time batch loop, while a page-sized annotation
4264
+ * stays crisp to roughly 5–6× before its appearance starts stretching.
4265
+ * Small annotations (the overwhelming majority) never come near it.
4266
+ */
4267
+ const APPEARANCE_PIXEL_CLAMP = 16e6;
4268
+ /**
4147
4269
  * Maps an `AnnotationAppearanceMode` onto the PDFium appearance-mode int and
4148
4270
  * the `EPDFAnnot_GetAvailableAppearanceModes` bit it occupies.
4149
4271
  * N -> mode 0 / bit 1, R -> mode 1 / bit 2, D -> mode 2 / bit 4.
@@ -4242,13 +4364,15 @@ var AnnotationAppearanceReader = class {
4242
4364
  fn.EPDFAnnot_GenerateFormFieldAP(annotPtr);
4243
4365
  if (!fn.EPDFAnnot_HasAppearanceStream(annotPtr, modeInt)) return null;
4244
4366
  } else return null;
4367
+ const rectArea = Math.max(1, (rect.right - rect.left) * (rect.top - rect.bottom));
4368
+ const effScale = Math.min(scale, Math.sqrt(APPEARANCE_PIXEL_CLAMP / rectArea));
4245
4369
  return rasterize(this.runtime, {
4246
4370
  rect,
4247
4371
  page,
4248
4372
  rotation,
4249
4373
  viewport: {
4250
4374
  kind: "scale",
4251
- scale
4375
+ scale: effScale
4252
4376
  },
4253
4377
  ...maxOutputPixels !== void 0 ? { maxOutputPixels } : {},
4254
4378
  background: "transparent",
@@ -4363,7 +4487,9 @@ function setAnnotFlags(fn, annotPtr, partial) {
4363
4487
  if (!fn.FPDFAnnot_SetFlags(annotPtr, next)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFAnnot_SetFlags returned false");
4364
4488
  }
4365
4489
  /**
4366
- * Write `/Rect` via `FPDFAnnot_SetRect`. FS_RECTF layout is
4490
+ * Write `/Rect` without touching `/AP`. Appearance preservation/regeneration
4491
+ * belongs to AnnotationMutator; a geometry primitive must not silently rewrite
4492
+ * an existing appearance `/BBox`. FS_RECTF layout is
4367
4493
  * `{ left, top, right, bottom }` (top > bottom in PDF coords).
4368
4494
  */
4369
4495
  function setAnnotRect(fn, mem, annotPtr, rect) {
@@ -4373,12 +4499,40 @@ function setAnnotRect(fn, mem, annotPtr, rect) {
4373
4499
  mem.poke(buf, "f32", rect.top, 4);
4374
4500
  mem.poke(buf, "f32", rect.right, 8);
4375
4501
  mem.poke(buf, "f32", rect.bottom, 12);
4376
- if (!fn.FPDFAnnot_SetRect(annotPtr, buf)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFAnnot_SetRect returned false");
4502
+ if (!fn.EPDFAnnot_SetRect(annotPtr, buf)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFAnnot_SetRect returned false");
4377
4503
  } finally {
4378
4504
  mem.free(buf);
4379
4505
  }
4380
4506
  }
4381
4507
  /**
4508
+ * Write a UTF-16 string entry into an annotation dictionary. The write
4509
+ * side of `readAnnotString`.
4510
+ */
4511
+ function writeAnnotString(fn, mem, annotPtr, key, value) {
4512
+ const ptr = mem.writeU16String(value);
4513
+ try {
4514
+ fn.FPDFAnnot_SetStringValue(annotPtr, key, ptr);
4515
+ } finally {
4516
+ mem.free(ptr);
4517
+ }
4518
+ }
4519
+ /**
4520
+ * Three-state string write: `null` REMOVES the dictionary entry.
4521
+ *
4522
+ * Clearing goes through `EPDFAnnot_RemoveKey` because
4523
+ * `FPDFAnnot_SetStringValue` with a NULL value writes an EMPTY string —
4524
+ * it never removes. True removal is what keeps the read side honest:
4525
+ * `readAnnotString` returns `null` iff the key is absent, so a cleared
4526
+ * field reads back as `null`, not `''`.
4527
+ */
4528
+ function writeAnnotStringOrClear(fn, mem, annotPtr, key, value) {
4529
+ if (value === null) {
4530
+ fn.EPDFAnnot_RemoveKey(annotPtr, key);
4531
+ return;
4532
+ }
4533
+ writeAnnotString(fn, mem, annotPtr, key, value);
4534
+ }
4535
+ /**
4382
4536
  * Set `/BS /S` (border style code) and `/BS /W` (width) in one call via
4383
4537
  * `EPDFAnnot_SetBorderStyle`. `styleCode` is a raw
4384
4538
  * `FPDF_ANNOT_BORDER_STYLE` enum value — the string<->code mapping lives
@@ -4577,8 +4731,8 @@ function setCalloutLine(fn, mem, annotPtr, points) {
4577
4731
  //#region src/features/annotations/internal/write/writeAnnotationBase.ts
4578
4732
  /**
4579
4733
  * Write the annotation-wide base fields shared by every Draft
4580
- * (contents/nm). The kind-specific writer calls this BEFORE its own
4581
- * field writes; order doesn't actually matter at the PDF level, but
4734
+ * (contents/subject/nm). The kind-specific writer calls this BEFORE its
4735
+ * own field writes; order doesn't actually matter at the PDF level, but
4582
4736
  * keeping the base first makes the writers symmetric with the readers.
4583
4737
  *
4584
4738
  * `/T` (author display) is stamped by the mutator from
@@ -4589,13 +4743,14 @@ function setCalloutLine(fn, mem, annotPtr, points) {
4589
4743
  * annotation; the writer does not.
4590
4744
  */
4591
4745
  function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
4592
- if (draft.contents !== void 0) writeStringOrClear(fn, mem, annotPtr, "Contents", draft.contents);
4593
- if (draft.nm !== void 0 && draft.nm.length > 0) writeString(fn, mem, annotPtr, "NM", draft.nm);
4746
+ if (draft.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", draft.contents);
4747
+ if (draft.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", draft.subject);
4748
+ if (draft.nm !== void 0 && draft.nm.length > 0) writeAnnotString(fn, mem, annotPtr, "NM", draft.nm);
4594
4749
  if (draft.flags !== void 0) setAnnotFlags(fn, annotPtr, draft.flags);
4595
4750
  }
4596
4751
  /**
4597
4752
  * Write the annotation-wide base fields shared by every Patch
4598
- * (contents). /T is bound at creation and not patchable — see
4753
+ * (contents/subject). /T is bound at creation and not patchable — see
4599
4754
  * `AnnotationPatchBase`. /NM is monotonic per annotation; the mutator
4600
4755
  * may stamp /NM opportunistically on a weak annotation via
4601
4756
  * `writeAnnotationNm`.
@@ -4606,7 +4761,8 @@ function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
4606
4761
  * "..." -> set the entry to that value
4607
4762
  */
4608
4763
  function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
4609
- if (patch.contents !== void 0) writeStringOrClear(fn, mem, annotPtr, "Contents", patch.contents);
4764
+ if (patch.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", patch.contents);
4765
+ if (patch.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", patch.subject);
4610
4766
  if (patch.flags !== void 0) setAnnotFlags(fn, annotPtr, patch.flags);
4611
4767
  }
4612
4768
  /**
@@ -4617,7 +4773,7 @@ function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
4617
4773
  */
4618
4774
  function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
4619
4775
  if (displayName.length === 0) return;
4620
- writeString(fn, mem, annotPtr, "T", displayName);
4776
+ writeAnnotString(fn, mem, annotPtr, "T", displayName);
4621
4777
  }
4622
4778
  /**
4623
4779
  * Stamp /NM on an annotation that didn't have one. Used by the mutator
@@ -4626,7 +4782,7 @@ function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
4626
4782
  */
4627
4783
  function writeAnnotationNm(fn, mem, annotPtr, nm) {
4628
4784
  if (nm.length === 0) return;
4629
- writeString(fn, mem, annotPtr, "NM", nm);
4785
+ writeAnnotString(fn, mem, annotPtr, "NM", nm);
4630
4786
  }
4631
4787
  /**
4632
4788
  * Stamp /M (modification date) on an annotation. /M is a standard
@@ -4643,22 +4799,7 @@ function writeAnnotationNm(fn, mem, annotPtr, nm) {
4643
4799
  * Format: `D:YYYYMMDDHHmmSSOHH'mm'` (see formatPdfDate).
4644
4800
  */
4645
4801
  function writeAnnotationModified(fn, mem, annotPtr, now = /* @__PURE__ */ new Date()) {
4646
- writeString(fn, mem, annotPtr, "M", formatPdfDate(now));
4647
- }
4648
- function writeString(fn, mem, annotPtr, key, value) {
4649
- const ptr = mem.writeU16String(value);
4650
- try {
4651
- fn.FPDFAnnot_SetStringValue(annotPtr, key, ptr);
4652
- } finally {
4653
- mem.free(ptr);
4654
- }
4655
- }
4656
- function writeStringOrClear(fn, mem, annotPtr, key, value) {
4657
- if (value === null) {
4658
- fn.FPDFAnnot_SetStringValue(annotPtr, key, _embedpdf_engine_runtime.NULL_PTR);
4659
- return;
4660
- }
4661
- writeString(fn, mem, annotPtr, key, value);
4802
+ writeAnnotString(fn, mem, annotPtr, "M", formatPdfDate(now));
4662
4803
  }
4663
4804
  //#endregion
4664
4805
  //#region src/features/annotations/internal/identity/captureOrStampStableId.ts
@@ -4830,50 +4971,229 @@ function readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, index, revis
4830
4971
  return reader(fn, mem, annotPtr, base, subtypeCode, ctx);
4831
4972
  }
4832
4973
  //#endregion
4833
- //#region src/features/annotations/internal/write/writeCaretAnnotation.ts
4834
- /** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
4835
- const DEFAULT_CARET_COLOR = {
4836
- r: 255,
4837
- g: 0,
4838
- b: 0
4839
- };
4840
- /** Default opacity, set explicitly so reads always round-trip the same value. */
4841
- const DEFAULT_OPACITY$4 = 1;
4974
+ //#region src/features/annotations/internal/write/writeEmbedMetadata.ts
4842
4975
  /**
4843
- * Apply a caret draft to a freshly-created annotation. Caret carries no
4844
- * geometry of its own beyond `/Rect`. Order:
4845
- * 1. base author-metadata (contents/nm/flags)
4846
- * 2. `/Rect` (required — supplied by the caller; never derived)
4847
- * 3. `/C` color + `/CA` opacity
4848
- * 4. `/RD` rectangle differences (optional)
4976
+ * Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
4977
+ *
4978
+ * This module owns ONLY the vendor-extension dictionary. Standard PDF
4979
+ * base fields (/T, /M, /NM, /Contents) are handled by
4980
+ * `writers/annotations/base.ts` — /EMBD_Metadata is a separate
4981
+ * dictionary nested under the annotation that carries cloud-side
4982
+ * identity bookkeeping which has no PDF-spec equivalent.
4983
+ *
4984
+ * Schema (v1):
4985
+ * /EMBD_Metadata <<
4986
+ * /SchemaVersion 1 % EmbedPDF metadata schema version
4987
+ * /UserID (acme/alice) % identity that created the annotation
4988
+ * /GroupID (engineering) % group the annotation belongs to
4989
+ * /CreatedBy (acme/alice) % same as UserID on create
4990
+ * /UpdatedBy (acme/alice) % last editor; refreshed on every update
4991
+ * >>
4992
+ *
4993
+ * Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
4994
+ *
4995
+ * PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
4996
+ * shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
4997
+ * strings via the same `mem.writeU16String` pattern the base writer
4998
+ * uses for /T.
4849
4999
  */
4850
- function applyCaretDraft(fn, mem, annotPtr, draft) {
4851
- applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
4852
- setAnnotRect(fn, mem, annotPtr, draft.rect);
4853
- setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_CARET_COLOR);
4854
- setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY$4);
4855
- if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
4856
- if (draft.rectDifferences != null) setRectangleDifferences(fn, annotPtr, draft.rectDifferences);
4857
- }
5000
+ const KEY_SCHEMA_VERSION = "SchemaVersion";
5001
+ const KEY_USER_ID = "UserID";
5002
+ const KEY_GROUP_ID = "GroupID";
5003
+ const KEY_CREATED_BY = "CreatedBy";
5004
+ const KEY_UPDATED_BY = "UpdatedBy";
4858
5005
  /**
4859
- * Apply a caret patch to an existing annotation. Only present fields are
4860
- * touched.
5006
+ * Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
5007
+ * actor has no identity fields — the worker still emits the standard
5008
+ * /M timestamp via the base writer; only the vendor dictionary is
5009
+ * skipped.
5010
+ *
5011
+ * actor.userId → /UserID, /CreatedBy, /UpdatedBy
5012
+ * actor.groupId → /GroupID
5013
+ *
5014
+ * /SchemaVersion is written whenever any field is written.
4861
5015
  */
4862
- function applyCaretPatch(fn, mem, annotPtr, patch) {
4863
- applyAnnotationBasePatch(fn, mem, annotPtr, patch);
4864
- if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
4865
- if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
4866
- if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
4867
- if (patch.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(patch.intent));
4868
- if (patch.rectDifferences === null) clearRectangleDifferences(fn, annotPtr);
4869
- else if (patch.rectDifferences !== void 0) setRectangleDifferences(fn, annotPtr, patch.rectDifferences);
5016
+ function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
5017
+ if (!actor || !actor.userId && !actor.groupId) return;
5018
+ fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
5019
+ if (actor.userId) {
5020
+ setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
5021
+ setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
5022
+ setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
5023
+ }
5024
+ if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
4870
5025
  }
4871
5026
  /**
4872
- * Type-narrowing predicate used by the writer registry to pick the caret
4873
- * writer for a draft/patch's `subtype`.
5027
+ * Apply /EMBD_Metadata refreshes on update. Two fields can change on
5028
+ * update; everything else (UserID, CreatedBy) is immutable.
5029
+ *
5030
+ * actor.userId → /UpdatedBy (refreshed every update where the
5031
+ * caller has an identity)
5032
+ * actor.groupId → /GroupID (reassigned only when the patch
5033
+ * explicitly contains a new groupId; route runs
5034
+ * `checkSetGroup` first)
5035
+ *
5036
+ * If the target annotation has no EMBD_Metadata yet (created before
5037
+ * the cloud started stamping, or by a non-cloud writer), we seed
5038
+ * /SchemaVersion so subsequent readers can find the marker.
5039
+ *
5040
+ * No-op when the actor has neither userId nor groupId.
4874
5041
  */
4875
- function isCaretSubtype(subtype) {
4876
- return subtype === "caret";
5042
+ function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
5043
+ if (!actor || !actor.userId && !actor.groupId) return;
5044
+ if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
5045
+ if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
5046
+ if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
5047
+ }
5048
+ function setMetadataString(fn, mem, annotPtr, key, value) {
5049
+ const ptr = mem.writeU16String(value);
5050
+ try {
5051
+ fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
5052
+ } finally {
5053
+ mem.free(ptr);
5054
+ }
5055
+ }
5056
+ //#endregion
5057
+ //#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
5058
+ /**
5059
+ * Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
5060
+ * PDFium's native AP generator reads to bake a ROTATED appearance:
5061
+ *
5062
+ * /EMBD_Metadata <<
5063
+ * /Rotation 45 % degrees, PDF convention (CCW)
5064
+ * /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
5065
+ * >>
5066
+ *
5067
+ * The family split (see the v3 plan):
5068
+ * - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
5069
+ * With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
5070
+ * box-sized appearance about the box centre, and `/Rect` is the enclosing
5071
+ * AABB. This is the portable, externally-correct rotation.
5072
+ * - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
5073
+ * The points are already rotated (they are the visual), so a lone
5074
+ * `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
5075
+ * `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
5076
+ * reconstruct an oriented selection box + offer reset.
5077
+ *
5078
+ * Both fields follow the engine-wide tri-state law ("a patch touches what it
5079
+ * states, preserves what it omits"):
5080
+ * - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
5081
+ * - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
5082
+ * (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
5083
+ * the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
5084
+ * survive).
5085
+ * - a value → SET the key. Setting a nonzero rotation with no
5086
+ * `/UnrotatedRect` (neither in the patch nor already on the annotation)
5087
+ * is an unsatisfiable state for the AP generator and throws `InvalidArg`.
5088
+ *
5089
+ * MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
5090
+ * `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
5091
+ */
5092
+ const KEY_ROTATION = "Rotation";
5093
+ const KEY_UNROTATED_RECT = "UnrotatedRect";
5094
+ /** Seed `/SchemaVersion` when we are about to create the dict by writing the
5095
+ * first transform key, so the marker readers look for is always present. */
5096
+ function ensureSchemaVersion(fn, annotPtr) {
5097
+ if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
5098
+ }
5099
+ function setRotation(fn, annotPtr, rotation) {
5100
+ ensureSchemaVersion(fn, annotPtr);
5101
+ fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
5102
+ }
5103
+ function setUnrotatedRect(fn, mem, annotPtr, rect) {
5104
+ ensureSchemaVersion(fn, annotPtr);
5105
+ const buf = mem.alloc(16);
5106
+ try {
5107
+ mem.poke(buf, "f32", rect.left, 0);
5108
+ mem.poke(buf, "f32", rect.top, 4);
5109
+ mem.poke(buf, "f32", rect.right, 8);
5110
+ mem.poke(buf, "f32", rect.bottom, 12);
5111
+ fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
5112
+ } finally {
5113
+ mem.free(buf);
5114
+ }
5115
+ }
5116
+ /**
5117
+ * Write transform metadata for a BOX kind (square/circle/free-text/stamp),
5118
+ * tri-state per field: `undefined` never touches the document (safe to call on
5119
+ * every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
5120
+ * unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
5121
+ * generator needs the pair — and throws instead of writing a broken state.
5122
+ */
5123
+ function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
5124
+ if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
5125
+ if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
5126
+ setRotation(fn, annotPtr, t.rotation);
5127
+ } else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
5128
+ if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
5129
+ else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
5130
+ }
5131
+ /**
5132
+ * Write the advisory `/Rotation` scalar for a VERTEX kind
5133
+ * (line/polyline/polygon/ink), tri-state: `undefined` never touches the
5134
+ * document, `null`/`0` clears, a value sets. Whenever it does write, any
5135
+ * `/UnrotatedRect` is defensively cleared — that key is an impossible state on
5136
+ * a vertex kind and must never accidentally drive the AP generator.
5137
+ */
5138
+ function writeVertexTransformMetadata(fn, annotPtr, t) {
5139
+ if (t.rotation === void 0) return;
5140
+ if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
5141
+ else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
5142
+ fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
5143
+ }
5144
+ //#endregion
5145
+ //#region src/features/annotations/internal/write/writeCaretAnnotation.ts
5146
+ /** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
5147
+ const DEFAULT_CARET_COLOR = {
5148
+ r: 255,
5149
+ g: 0,
5150
+ b: 0
5151
+ };
5152
+ /** Default opacity, set explicitly so reads always round-trip the same value. */
5153
+ const DEFAULT_OPACITY$4 = 1;
5154
+ /**
5155
+ * Apply a caret draft to a freshly-created annotation. Caret carries no
5156
+ * geometry of its own beyond `/Rect`. Order:
5157
+ * 1. base author-metadata (contents/nm/flags)
5158
+ * 2. `/Rect` (required — supplied by the caller; never derived)
5159
+ * 3. `/C` color + `/CA` opacity
5160
+ * 4. `/RD` rectangle differences (optional)
5161
+ */
5162
+ function applyCaretDraft(fn, mem, annotPtr, draft) {
5163
+ applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
5164
+ setAnnotRect(fn, mem, annotPtr, draft.rect);
5165
+ writeBoxTransformMetadata(fn, mem, annotPtr, {
5166
+ rotation: draft.rotation,
5167
+ unrotatedRect: draft.unrotatedRect
5168
+ });
5169
+ setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_CARET_COLOR);
5170
+ setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY$4);
5171
+ if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
5172
+ if (draft.rectDifferences != null) setRectangleDifferences(fn, annotPtr, draft.rectDifferences);
5173
+ }
5174
+ /**
5175
+ * Apply a caret patch to an existing annotation. Only present fields are
5176
+ * touched.
5177
+ */
5178
+ function applyCaretPatch(fn, mem, annotPtr, patch) {
5179
+ applyAnnotationBasePatch(fn, mem, annotPtr, patch);
5180
+ if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
5181
+ if (patch.rotation !== void 0 || patch.unrotatedRect !== void 0) writeBoxTransformMetadata(fn, mem, annotPtr, {
5182
+ rotation: patch.rotation,
5183
+ unrotatedRect: patch.unrotatedRect
5184
+ });
5185
+ if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
5186
+ if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
5187
+ if (patch.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(patch.intent));
5188
+ if (patch.rectDifferences === null) clearRectangleDifferences(fn, annotPtr);
5189
+ else if (patch.rectDifferences !== void 0) setRectangleDifferences(fn, annotPtr, patch.rectDifferences);
5190
+ }
5191
+ /**
5192
+ * Type-narrowing predicate used by the writer registry to pick the caret
5193
+ * writer for a draft/patch's `subtype`.
5194
+ */
5195
+ function isCaretSubtype(subtype) {
5196
+ return subtype === "caret";
4877
5197
  }
4878
5198
  //#endregion
4879
5199
  //#region src/features/annotations/internal/write/writeFileAttachmentAnnotation.ts
@@ -5040,177 +5360,6 @@ function applyFilledStylePatch(fn, mem, annotPtr, patch) {
5040
5360
  applyGeometryStylePatch(fn, mem, annotPtr, patch);
5041
5361
  }
5042
5362
  //#endregion
5043
- //#region src/features/annotations/internal/write/writeEmbedMetadata.ts
5044
- /**
5045
- * Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
5046
- *
5047
- * This module owns ONLY the vendor-extension dictionary. Standard PDF
5048
- * base fields (/T, /M, /NM, /Contents) are handled by
5049
- * `writers/annotations/base.ts` — /EMBD_Metadata is a separate
5050
- * dictionary nested under the annotation that carries cloud-side
5051
- * identity bookkeeping which has no PDF-spec equivalent.
5052
- *
5053
- * Schema (v1):
5054
- * /EMBD_Metadata <<
5055
- * /SchemaVersion 1 % EmbedPDF metadata schema version
5056
- * /UserID (acme/alice) % identity that created the annotation
5057
- * /GroupID (engineering) % group the annotation belongs to
5058
- * /CreatedBy (acme/alice) % same as UserID on create
5059
- * /UpdatedBy (acme/alice) % last editor; refreshed on every update
5060
- * >>
5061
- *
5062
- * Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
5063
- *
5064
- * PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
5065
- * shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
5066
- * strings via the same `mem.writeU16String` pattern the base writer
5067
- * uses for /T.
5068
- */
5069
- const KEY_SCHEMA_VERSION = "SchemaVersion";
5070
- const KEY_USER_ID = "UserID";
5071
- const KEY_GROUP_ID = "GroupID";
5072
- const KEY_CREATED_BY = "CreatedBy";
5073
- const KEY_UPDATED_BY = "UpdatedBy";
5074
- /**
5075
- * Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
5076
- * actor has no identity fields — the worker still emits the standard
5077
- * /M timestamp via the base writer; only the vendor dictionary is
5078
- * skipped.
5079
- *
5080
- * actor.userId → /UserID, /CreatedBy, /UpdatedBy
5081
- * actor.groupId → /GroupID
5082
- *
5083
- * /SchemaVersion is written whenever any field is written.
5084
- */
5085
- function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
5086
- if (!actor || !actor.userId && !actor.groupId) return;
5087
- fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
5088
- if (actor.userId) {
5089
- setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
5090
- setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
5091
- setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
5092
- }
5093
- if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
5094
- }
5095
- /**
5096
- * Apply /EMBD_Metadata refreshes on update. Two fields can change on
5097
- * update; everything else (UserID, CreatedBy) is immutable.
5098
- *
5099
- * actor.userId → /UpdatedBy (refreshed every update where the
5100
- * caller has an identity)
5101
- * actor.groupId → /GroupID (reassigned only when the patch
5102
- * explicitly contains a new groupId; route runs
5103
- * `checkSetGroup` first)
5104
- *
5105
- * If the target annotation has no EMBD_Metadata yet (created before
5106
- * the cloud started stamping, or by a non-cloud writer), we seed
5107
- * /SchemaVersion so subsequent readers can find the marker.
5108
- *
5109
- * No-op when the actor has neither userId nor groupId.
5110
- */
5111
- function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
5112
- if (!actor || !actor.userId && !actor.groupId) return;
5113
- if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
5114
- if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
5115
- if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
5116
- }
5117
- function setMetadataString(fn, mem, annotPtr, key, value) {
5118
- const ptr = mem.writeU16String(value);
5119
- try {
5120
- fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
5121
- } finally {
5122
- mem.free(ptr);
5123
- }
5124
- }
5125
- //#endregion
5126
- //#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
5127
- /**
5128
- * Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
5129
- * PDFium's native AP generator reads to bake a ROTATED appearance:
5130
- *
5131
- * /EMBD_Metadata <<
5132
- * /Rotation 45 % degrees, PDF convention (CCW)
5133
- * /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
5134
- * >>
5135
- *
5136
- * The family split (see the v3 plan):
5137
- * - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
5138
- * With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
5139
- * box-sized appearance about the box centre, and `/Rect` is the enclosing
5140
- * AABB. This is the portable, externally-correct rotation.
5141
- * - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
5142
- * The points are already rotated (they are the visual), so a lone
5143
- * `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
5144
- * `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
5145
- * reconstruct an oriented selection box + offer reset.
5146
- *
5147
- * Both fields follow the engine-wide tri-state law ("a patch touches what it
5148
- * states, preserves what it omits"):
5149
- * - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
5150
- * - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
5151
- * (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
5152
- * the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
5153
- * survive).
5154
- * - a value → SET the key. Setting a nonzero rotation with no
5155
- * `/UnrotatedRect` (neither in the patch nor already on the annotation)
5156
- * is an unsatisfiable state for the AP generator and throws `InvalidArg`.
5157
- *
5158
- * MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
5159
- * `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
5160
- */
5161
- const KEY_ROTATION = "Rotation";
5162
- const KEY_UNROTATED_RECT = "UnrotatedRect";
5163
- /** Seed `/SchemaVersion` when we are about to create the dict by writing the
5164
- * first transform key, so the marker readers look for is always present. */
5165
- function ensureSchemaVersion(fn, annotPtr) {
5166
- if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
5167
- }
5168
- function setRotation(fn, annotPtr, rotation) {
5169
- ensureSchemaVersion(fn, annotPtr);
5170
- fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
5171
- }
5172
- function setUnrotatedRect(fn, mem, annotPtr, rect) {
5173
- ensureSchemaVersion(fn, annotPtr);
5174
- const buf = mem.alloc(16);
5175
- try {
5176
- mem.poke(buf, "f32", rect.left, 0);
5177
- mem.poke(buf, "f32", rect.top, 4);
5178
- mem.poke(buf, "f32", rect.right, 8);
5179
- mem.poke(buf, "f32", rect.bottom, 12);
5180
- fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
5181
- } finally {
5182
- mem.free(buf);
5183
- }
5184
- }
5185
- /**
5186
- * Write transform metadata for a BOX kind (square/circle/free-text/stamp),
5187
- * tri-state per field: `undefined` never touches the document (safe to call on
5188
- * every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
5189
- * unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
5190
- * generator needs the pair — and throws instead of writing a broken state.
5191
- */
5192
- function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
5193
- if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
5194
- if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
5195
- setRotation(fn, annotPtr, t.rotation);
5196
- } else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
5197
- if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
5198
- else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
5199
- }
5200
- /**
5201
- * Write the advisory `/Rotation` scalar for a VERTEX kind
5202
- * (line/polyline/polygon/ink), tri-state: `undefined` never touches the
5203
- * document, `null`/`0` clears, a value sets. Whenever it does write, any
5204
- * `/UnrotatedRect` is defensively cleared — that key is an impossible state on
5205
- * a vertex kind and must never accidentally drive the AP generator.
5206
- */
5207
- function writeVertexTransformMetadata(fn, annotPtr, t) {
5208
- if (t.rotation === void 0) return;
5209
- if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
5210
- else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
5211
- fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
5212
- }
5213
- //#endregion
5214
5363
  //#region src/features/annotations/internal/write/writeFreeTextAnnotation.ts
5215
5364
  /**
5216
5365
  * Default `/DA` colour for free text: black (border + default text). Unlike
@@ -5980,13 +6129,18 @@ const DEFAULT_OPACITY = 1;
5980
6129
  * Apply a text (sticky-note) draft. The visual is entirely generator-owned:
5981
6130
  * the mutator's closing `regenerateAppearance` bakes the 20×20 note icon
5982
6131
  * from `/C` + `/Name` (GenerateTextAP), so this writer only records state.
6132
+ * `/State` + `/StateModel` are dictionary-only (ISO 32000 §12.5.6.3) and
6133
+ * never reach the generator.
5983
6134
  */
5984
6135
  function applyTextDraft(fn, mem, annotPtr, draft) {
6136
+ if (draft.state !== void 0 && draft.stateModel === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "text draft: state requires stateModel (ISO 32000 §12.5.6.3)");
5985
6137
  applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
5986
6138
  setAnnotRect(fn, mem, annotPtr, draft.rect);
5987
6139
  setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_NOTE_COLOR);
5988
6140
  setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY);
5989
6141
  setNoteIcon(fn, annotPtr, draft.icon ?? "note");
6142
+ if (draft.stateModel !== void 0) writeAnnotString(fn, mem, annotPtr, "StateModel", stateModelToPdf(draft.stateModel));
6143
+ if (draft.state !== void 0) writeAnnotString(fn, mem, annotPtr, "State", stateToPdf(draft.state));
5990
6144
  }
5991
6145
  function applyTextPatch(fn, mem, annotPtr, patch) {
5992
6146
  applyAnnotationBasePatch(fn, mem, annotPtr, patch);
@@ -5994,6 +6148,8 @@ function applyTextPatch(fn, mem, annotPtr, patch) {
5994
6148
  if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
5995
6149
  if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
5996
6150
  if (patch.icon !== void 0) setNoteIcon(fn, annotPtr, patch.icon);
6151
+ if (patch.stateModel !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "StateModel", patch.stateModel === null ? null : stateModelToPdf(patch.stateModel));
6152
+ if (patch.state !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "State", patch.state === null ? null : stateToPdf(patch.state));
5997
6153
  }
5998
6154
  function isTextSubtype(subtype) {
5999
6155
  return subtype === "text";
@@ -6971,17 +7127,35 @@ var RedactionApplier = class {
6971
7127
  }
6972
7128
  applyAllOnPage(pagePtr) {
6973
7129
  const { fn, mem } = this.runtime;
7130
+ const hasRedactions = this.pageHasRedactAnnotations(pagePtr);
6974
7131
  return withScratch(mem, 4, (countPtr) => {
6975
- if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr)) return {
6976
- status: "unchanged",
6977
- removed: 0
6978
- };
7132
+ if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr)) {
7133
+ if (hasRedactions) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "native page redaction apply failed");
7134
+ return {
7135
+ status: "unchanged",
7136
+ removed: 0
7137
+ };
7138
+ }
6979
7139
  return {
6980
7140
  status: "applied",
6981
7141
  removed: readI32(mem, countPtr) >>> 0
6982
7142
  };
6983
7143
  });
6984
7144
  }
7145
+ pageHasRedactAnnotations(pagePtr) {
7146
+ const { fn } = this.runtime;
7147
+ const count = fn.FPDFPage_GetAnnotCount(pagePtr);
7148
+ for (let index = 0; index < count; index++) {
7149
+ const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, index);
7150
+ if (!annotPtr) continue;
7151
+ try {
7152
+ if ((0, _embedpdf_engine_core_runtime.subtypeFromCode)(fn.FPDFAnnot_GetSubtype(annotPtr)) === "redact") return true;
7153
+ } finally {
7154
+ fn.FPDFPage_CloseAnnot(annotPtr);
7155
+ }
7156
+ }
7157
+ return false;
7158
+ }
6985
7159
  applyRefsOnPage(pagePtr, refs) {
6986
7160
  const { fn, mem } = this.runtime;
6987
7161
  let removed = 0;
@@ -7007,23 +7181,26 @@ var RedactionApplier = class {
7007
7181
  //#region src/features/text/PageTextReader.ts
7008
7182
  /**
7009
7183
  * Per-page slow-path text reader. Acquires a `pagePtr` from the
7010
- * `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`),
7011
- * extracts the full UTF-16 buffer with `FPDFText_GetText`, decodes,
7012
- * and releases everything in reverse order.
7184
+ * `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`), extracts
7185
+ * the page text, and releases everything in reverse order.
7186
+ *
7187
+ * Index spaces (see engine-core `text/charmap.ts`): `charCount` is the
7188
+ * CHARACTER space (`FPDFText_CountChars` — the space geometry runs tile);
7189
+ * `text` is the TEXT projection. The two diverge when a character
7190
+ * contributes zero text units (non-printing) or two (supplementary plane);
7191
+ * `charMap` anchors encode exactly those deviations.
7013
7192
  *
7014
- * Memory model:
7015
- * - `count` is in UTF-16 code units (FPDFText's "char").
7016
- * - The buffer needs `(count + 1) * 2` bytes (PDFium writes a
7017
- * trailing NUL terminator).
7018
- * - `mem.readU16String` walks the buffer up to the NUL and decodes
7019
- * to a JS string, faithfully preserving surrogate pairs.
7193
+ * Both come from the fork's batched extension calls, which share one
7194
+ * encoding authority (`FX_UTF16Encode`) so text and map can never
7195
+ * disagree:
7020
7196
  *
7021
- * Errors:
7022
- * - `EPDFDoc_LoadPageByObjectNumber` returning 0 is `NotFound`
7023
- * (raised by `PagePtrPool.acquire`).
7024
- * - `FPDFText_LoadPage` returning 0 is `RuntimeUnavailable` — should
7025
- * not happen on a healthy page; treat it as a runtime bug rather
7026
- * than user error.
7197
+ * - `EPDFText_GetTextFull` — true UTF-16LE extraction; supplementary
7198
+ * characters arrive as surrogate pairs instead of being silently
7199
+ * dropped by `FPDFText_GetText`'s UCS-2 encoder. Called once with an
7200
+ * upper-bound buffer (2 units per character + NUL), so no measure
7201
+ * round-trip.
7202
+ * - `EPDFText_GetCharToTextMap` — the anchors, measure-then-fill. Zero
7203
+ * anchors is the common (identity) page and skips the fill call.
7027
7204
  */
7028
7205
  var PageTextReader = class {
7029
7206
  constructor(runtime, session) {
@@ -7032,7 +7209,7 @@ var PageTextReader = class {
7032
7209
  }
7033
7210
  read(pageObjectNumber, signal) {
7034
7211
  throwIfAborted(signal);
7035
- const { fn, mem } = this.runtime;
7212
+ const { fn } = this.runtime;
7036
7213
  const pool = this.session.pagePool();
7037
7214
  const pagePtr = pool.acquire(pageObjectNumber);
7038
7215
  try {
@@ -7041,10 +7218,18 @@ var PageTextReader = class {
7041
7218
  if (!textPagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `FPDFText_LoadPage returned null for page object ${pageObjectNumber}`);
7042
7219
  try {
7043
7220
  throwIfAborted(signal);
7044
- const charCount = fn.FPDFText_CountChars(textPagePtr);
7045
- return {
7046
- text: readPageText(fn, mem, textPagePtr, charCount),
7047
- charCount: Math.max(charCount, 0)
7221
+ const charCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
7222
+ const text = readTextFull(this.runtime, textPagePtr, charCount);
7223
+ const charMap = readCharMap(this.runtime, textPagePtr, pageObjectNumber);
7224
+ const violation = (0, _embedpdf_engine_core_runtime.charMapViolation)(charCount, text.length, charMap);
7225
+ if (violation !== null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `char map invariant violated for page object ${pageObjectNumber}: ${violation}`);
7226
+ return charMap.length > 0 ? {
7227
+ text,
7228
+ charCount,
7229
+ charMap
7230
+ } : {
7231
+ text,
7232
+ charCount
7048
7233
  };
7049
7234
  } finally {
7050
7235
  fn.FPDFText_ClosePage(textPagePtr);
@@ -7054,21 +7239,65 @@ var PageTextReader = class {
7054
7239
  }
7055
7240
  }
7056
7241
  };
7057
- function readPageText(fn, mem, textPagePtr, charCount) {
7242
+ /**
7243
+ * Full-fidelity page text in one call: the buffer is sized at the maximum
7244
+ * possible expansion (every character a surrogate pair, plus the NUL), so
7245
+ * the returned requirement always fits and the measure round-trip is
7246
+ * skipped. The return value counts UTF-16 units INCLUDING the terminator.
7247
+ */
7248
+ function readTextFull(runtime, textPagePtr, charCount) {
7058
7249
  if (charCount <= 0) return "";
7059
- return withScratch(mem, (charCount + 1) * 2, (buf) => {
7060
- if (fn.FPDFText_GetText(textPagePtr, 0, charCount, buf) <= 0) return "";
7061
- return mem.readU16String(buf);
7250
+ const { fn, mem } = runtime;
7251
+ const bufUnits = charCount * 2 + 1;
7252
+ return withScratch(mem, bufUnits * 2, (buf) => {
7253
+ const written = fn.EPDFText_GetTextFull(textPagePtr, buf, bufUnits);
7254
+ if (written <= 1) return "";
7255
+ return decodeUtf16Le(mem.readBytes(buf, (written - 1) * 2));
7256
+ });
7257
+ }
7258
+ function decodeUtf16Le(bytes) {
7259
+ const units = new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
7260
+ let out = "";
7261
+ const CHUNK = 8192;
7262
+ for (let i = 0; i < units.length; i += CHUNK) out += String.fromCharCode(...units.subarray(i, Math.min(i + CHUNK, units.length)));
7263
+ return out;
7264
+ }
7265
+ /** Anchor struct layout: two little-endian i32s (see EPDF_CHAR_MAP_ANCHOR). */
7266
+ const ANCHOR_BYTES = 8;
7267
+ function readCharMap(runtime, textPagePtr, pageObjectNumber) {
7268
+ const { fn, mem } = runtime;
7269
+ const count = fn.EPDFText_GetCharToTextMap(textPagePtr, _embedpdf_engine_runtime.NULL_PTR, 0);
7270
+ if (count < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `EPDFText_GetCharToTextMap failed for page object ${pageObjectNumber}`);
7271
+ if (count === 0) return [];
7272
+ return withScratch(mem, count * ANCHOR_BYTES, (buf) => {
7273
+ fn.EPDFText_GetCharToTextMap(textPagePtr, buf, count);
7274
+ const bytes = mem.readBytes(buf, count * ANCHOR_BYTES);
7275
+ const words = new Int32Array(bytes.buffer, bytes.byteOffset, count * 2);
7276
+ const anchors = new Array(count);
7277
+ for (let i = 0; i < count; i++) anchors[i] = [words[i * 2], words[i * 2 + 1]];
7278
+ return anchors;
7062
7279
  });
7063
7280
  }
7064
7281
  //#endregion
7065
7282
  //#region src/features/geometry/PageGeometryReader.ts
7066
7283
  /**
7284
+ * Mid-object orientation drift that forces a run split (~0.5°). Real content
7285
+ * keeps one char matrix per text object; this guards the exotic cases so a
7286
+ * run's `rotation` is always representative of every glyph in it.
7287
+ */
7288
+ const ROTATION_SPLIT_TOLERANCE = .0087;
7289
+ /**
7067
7290
  * Geometry-only text layout reader.
7068
7291
  *
7069
7292
  * Emits geometry in PDF user space (y-up edges) — the canonical engine
7070
7293
  * geometry. The viewer converts to content/view space via the page geometry
7071
7294
  * matrix; this reader applies NO Y-flip or device transform.
7295
+ *
7296
+ * One `EPDFText_GetCharGeometry` call per glyph supplies boxes, oriented
7297
+ * cells, and flags together; `buildRunsFromRawGlyphs` then groups glyphs
7298
+ * into the upright/rotated run union. Upright runs are byte-identical to the
7299
+ * pre-orientation reader (quads the native layer offers are deliberately
7300
+ * dropped — the dominant case never pays for orientation).
7072
7301
  */
7073
7302
  var PageGeometryReader = class {
7074
7303
  constructor(runtime, session) {
@@ -7077,7 +7306,7 @@ var PageGeometryReader = class {
7077
7306
  }
7078
7307
  read(pageObjectNumber, signal) {
7079
7308
  throwIfAborted(signal);
7080
- const { fn } = this.runtime;
7309
+ const { fn, mem } = this.runtime;
7081
7310
  const pool = this.session.pagePool();
7082
7311
  const pagePtr = pool.acquire(pageObjectNumber);
7083
7312
  try {
@@ -7087,12 +7316,27 @@ var PageGeometryReader = class {
7087
7316
  try {
7088
7317
  throwIfAborted(signal);
7089
7318
  const glyphCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
7090
- const glyphs = [];
7091
- for (let i = 0; i < glyphCount; i++) {
7092
- throwIfAborted(signal);
7093
- glyphs.push(this.readGlyphInfo(textPagePtr, i));
7319
+ const records = [];
7320
+ const geometryPtr = mem.alloc(EPDF_CHAR_GEOMETRY_LAYOUT.bytes);
7321
+ try {
7322
+ let prevObjectKey = null;
7323
+ for (let i = 0; i < glyphCount; i++) {
7324
+ throwIfAborted(signal);
7325
+ const objectKey = fn.FPDFText_GetTextObject(textPagePtr, i);
7326
+ const record = {
7327
+ objectKey,
7328
+ ...this.readGlyphRaw(textPagePtr, i, geometryPtr)
7329
+ };
7330
+ if (objectKey !== prevObjectKey) {
7331
+ record.fontSize = fn.FPDFText_GetFontSize(textPagePtr, i);
7332
+ prevObjectKey = objectKey;
7333
+ }
7334
+ records.push(record);
7335
+ }
7336
+ } finally {
7337
+ mem.free(geometryPtr);
7094
7338
  }
7095
- return { runs: this.buildRunsFromGlyphs(glyphs, textPagePtr) };
7339
+ return { runs: buildRunsFromRawGlyphs(records) };
7096
7340
  } finally {
7097
7341
  fn.FPDFText_ClosePage(textPagePtr);
7098
7342
  }
@@ -7100,96 +7344,206 @@ var PageGeometryReader = class {
7100
7344
  pool.release(pageObjectNumber);
7101
7345
  }
7102
7346
  }
7103
- buildRunsFromGlyphs(glyphs, textPagePtr) {
7104
- const { fn } = this.runtime;
7105
- const runs = [];
7106
- let current = null;
7107
- let curObjPtr = null;
7108
- let bounds = null;
7109
- for (let i = 0; i < glyphs.length; i++) {
7110
- const glyph = glyphs[i];
7111
- const objPtr = fn.FPDFText_GetTextObject(textPagePtr, i);
7112
- if (objPtr !== curObjPtr) {
7113
- curObjPtr = objPtr;
7114
- current = {
7115
- rect: { ...glyph.looseBox },
7116
- charStart: i,
7117
- glyphs: [],
7118
- fontSize: fn.FPDFText_GetFontSize(textPagePtr, i)
7119
- };
7120
- bounds = { ...glyph.looseBox };
7121
- runs.push(current);
7347
+ readGlyphRaw(textPagePtr, charIndex, geometryPtr) {
7348
+ const { fn, mem } = this.runtime;
7349
+ if (!fn.EPDFText_GetCharGeometry(textPagePtr, charIndex, geometryPtr)) return emptyRawGlyph();
7350
+ const { offsets, flags: bits } = EPDF_CHAR_GEOMETRY_LAYOUT;
7351
+ const native = readI32(mem, geometryPtr, offsets.flags) >>> 0;
7352
+ if (native & bits.empty) return emptyRawGlyph();
7353
+ const upright = Boolean(native & bits.upright);
7354
+ const raw = {
7355
+ flags: native & bits.space ? 1 : 0,
7356
+ looseBox: (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.looseBox)),
7357
+ upright,
7358
+ rotation: 0,
7359
+ ascentFlip: false
7360
+ };
7361
+ if (native & bits.hasTightBox) raw.tightBox = (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.tightBox));
7362
+ if (native & bits.hasLooseQuad) {
7363
+ raw.looseQuad = readQuad(mem, geometryPtr, offsets.looseQuad);
7364
+ if (native & bits.hasTightQuad) raw.tightQuad = readQuad(mem, geometryPtr, offsets.tightQuad);
7365
+ if (!upright) {
7366
+ const a = readF32(mem, geometryPtr, offsets.matrix);
7367
+ const b = readF32(mem, geometryPtr, offsets.matrix + 4);
7368
+ const c = readF32(mem, geometryPtr, offsets.matrix + 8);
7369
+ const d = readF32(mem, geometryPtr, offsets.matrix + 12);
7370
+ raw.rotation = Math.atan2(b, a);
7371
+ raw.ascentFlip = a * d - b * c < 0;
7122
7372
  }
7123
- current.glyphs.push(toSlimGlyph(glyph));
7124
- if (glyph.isEmpty) continue;
7125
- bounds.left = Math.min(bounds.left, glyph.looseBox.left);
7126
- bounds.bottom = Math.min(bounds.bottom, glyph.looseBox.bottom);
7127
- bounds.right = Math.max(bounds.right, glyph.looseBox.right);
7128
- bounds.top = Math.max(bounds.top, glyph.looseBox.top);
7129
- current.rect = { ...bounds };
7130
7373
  }
7131
- return runs;
7374
+ return raw;
7132
7375
  }
7133
- readGlyphInfo(textPagePtr, charIndex) {
7134
- const { fn, mem } = this.runtime;
7135
- const rectPtr = mem.alloc(16);
7136
- const tLeftPtr = mem.alloc(8);
7137
- const tRightPtr = mem.alloc(8);
7138
- const tBottomPtr = mem.alloc(8);
7139
- const tTopPtr = mem.alloc(8);
7140
- const ptrs = [
7141
- rectPtr,
7142
- tLeftPtr,
7143
- tRightPtr,
7144
- tBottomPtr,
7145
- tTopPtr
7146
- ];
7147
- try {
7148
- if (!fn.FPDFText_GetLooseCharBox(textPagePtr, charIndex, rectPtr)) return emptyGlyph();
7149
- const left = Number(mem.peek(rectPtr, "f32", 0));
7150
- const top = Number(mem.peek(rectPtr, "f32", 4));
7151
- const right = Number(mem.peek(rectPtr, "f32", 8));
7152
- const bottom = Number(mem.peek(rectPtr, "f32", 12));
7153
- if (left === right || top === bottom) return emptyGlyph();
7154
- const glyph = { looseBox: (0, _embedpdf_engine_core_runtime.normalizePdfRect)({
7155
- left,
7156
- bottom,
7157
- right,
7158
- top
7159
- }) };
7160
- if (fn.FPDFText_GetCharBox(textPagePtr, charIndex, tLeftPtr, tRightPtr, tBottomPtr, tTopPtr)) {
7161
- const tLeft = Number(mem.peek(tLeftPtr, "f64"));
7162
- const tRight = Number(mem.peek(tRightPtr, "f64"));
7163
- glyph.tightBox = (0, _embedpdf_engine_core_runtime.normalizePdfRect)({
7164
- left: tLeft,
7165
- bottom: Number(mem.peek(tBottomPtr, "f64")),
7166
- right: tRight,
7167
- top: Number(mem.peek(tTopPtr, "f64"))
7168
- });
7169
- }
7170
- glyph.isSpace = fn.FPDFText_GetUnicode(textPagePtr, charIndex) === 32;
7171
- return glyph;
7172
- } finally {
7173
- for (const ptr of ptrs) mem.free(ptr);
7376
+ };
7377
+ /**
7378
+ * Group per-glyph records into the run union. Pure — exported for tests.
7379
+ *
7380
+ * Runs split on text-object change (the legacy rule) and on orientation
7381
+ * change between classifiable glyphs (θ drift / mixed orientation). A run's
7382
+ * variant is decided by its FIRST classifiable glyph:
7383
+ * - empty glyphs never classify (they adopt the run's variant);
7384
+ * - real glyphs WITHOUT an oriented cell (singular matrix, synthesized
7385
+ * /ActualText pieces) classify as upright — box-only data degrades to
7386
+ * exactly the legacy behavior;
7387
+ * - real glyphs with a non-upright matrix classify as rotated.
7388
+ * Runs that never see a classifiable glyph emit as upright (degenerate,
7389
+ * legacy behavior).
7390
+ */
7391
+ function buildRunsFromRawGlyphs(records) {
7392
+ const runs = [];
7393
+ let buffer = [];
7394
+ let charStart = 0;
7395
+ let fontSize;
7396
+ let objectKey = null;
7397
+ let cls;
7398
+ const flush = (nextStart) => {
7399
+ if (buffer.length > 0) {
7400
+ runs.push(materializeRun(buffer, charStart, fontSize, cls));
7401
+ buffer = [];
7402
+ cls = void 0;
7403
+ }
7404
+ charStart = nextStart;
7405
+ };
7406
+ for (let i = 0; i < records.length; i++) {
7407
+ const record = records[i];
7408
+ if (record.objectKey !== objectKey) {
7409
+ flush(i);
7410
+ objectKey = record.objectKey;
7411
+ fontSize = record.fontSize;
7412
+ }
7413
+ const recordClass = classOf(record);
7414
+ if (recordClass) {
7415
+ if (cls && !sameClass(cls, recordClass)) flush(i);
7416
+ if (!cls) cls = recordClass;
7174
7417
  }
7418
+ buffer.push(record);
7419
+ }
7420
+ flush(records.length);
7421
+ return runs;
7422
+ }
7423
+ function classOf(record) {
7424
+ if (record.flags & 2) return void 0;
7425
+ if (record.looseQuad && !record.upright) return {
7426
+ upright: false,
7427
+ rotation: record.rotation,
7428
+ ascentFlip: record.ascentFlip
7429
+ };
7430
+ return { upright: true };
7431
+ }
7432
+ function sameClass(a, b) {
7433
+ if (a.upright || b.upright) return a.upright === b.upright;
7434
+ if (a.ascentFlip !== b.ascentFlip) return false;
7435
+ const delta = a.rotation - b.rotation;
7436
+ return Math.abs(Math.atan2(Math.sin(delta), Math.cos(delta))) <= ROTATION_SPLIT_TOLERANCE;
7437
+ }
7438
+ function materializeRun(buffer, charStart, fontSize, cls) {
7439
+ if (cls && !cls.upright) {
7440
+ const glyphs = buffer.map((g) => g.looseQuad ? {
7441
+ looseQuad: g.looseQuad,
7442
+ flags: g.flags,
7443
+ ...g.tightQuad ? { tightQuad: g.tightQuad } : {}
7444
+ } : {
7445
+ looseQuad: zeroQuad(),
7446
+ flags: g.flags
7447
+ });
7448
+ return {
7449
+ rect: runBounds(buffer, (g) => g.looseQuad ? (0, _embedpdf_engine_core_runtime.pdfQuadBounds)(g.looseQuad) : ZERO_RECT),
7450
+ charStart,
7451
+ glyphs,
7452
+ rotation: cls.rotation,
7453
+ ascentFlip: cls.ascentFlip,
7454
+ ...fontSize !== void 0 ? { fontSize } : {}
7455
+ };
7456
+ }
7457
+ const glyphs = buffer.map((g) => ({
7458
+ looseBox: g.looseBox,
7459
+ flags: g.flags,
7460
+ ...g.tightBox ? { tightBox: g.tightBox } : {}
7461
+ }));
7462
+ return {
7463
+ rect: runBounds(buffer, (g) => g.looseBox),
7464
+ charStart,
7465
+ glyphs,
7466
+ ...fontSize !== void 0 ? { fontSize } : {}
7467
+ };
7468
+ }
7469
+ /** Legacy run-rect algorithm: seed from the first glyph, expand on non-empty. */
7470
+ function runBounds(buffer, boundsOf) {
7471
+ const seed = boundsOf(buffer[0]);
7472
+ const rect = {
7473
+ left: seed.left,
7474
+ bottom: seed.bottom,
7475
+ right: seed.right,
7476
+ top: seed.top
7477
+ };
7478
+ for (const g of buffer) {
7479
+ if (g.flags & 2) continue;
7480
+ const b = boundsOf(g);
7481
+ rect.left = Math.min(rect.left, b.left);
7482
+ rect.bottom = Math.min(rect.bottom, b.bottom);
7483
+ rect.right = Math.max(rect.right, b.right);
7484
+ rect.top = Math.max(rect.top, b.top);
7175
7485
  }
7486
+ return rect;
7487
+ }
7488
+ const ZERO_RECT = {
7489
+ left: 0,
7490
+ bottom: 0,
7491
+ right: 0,
7492
+ top: 0
7176
7493
  };
7177
- function emptyGlyph() {
7494
+ function zeroQuad() {
7495
+ return {
7496
+ p1: {
7497
+ x: 0,
7498
+ y: 0
7499
+ },
7500
+ p2: {
7501
+ x: 0,
7502
+ y: 0
7503
+ },
7504
+ p3: {
7505
+ x: 0,
7506
+ y: 0
7507
+ },
7508
+ p4: {
7509
+ x: 0,
7510
+ y: 0
7511
+ }
7512
+ };
7513
+ }
7514
+ function emptyRawGlyph() {
7178
7515
  return {
7516
+ flags: 2,
7179
7517
  looseBox: {
7180
7518
  left: 0,
7181
7519
  bottom: 0,
7182
7520
  right: 0,
7183
7521
  top: 0
7184
7522
  },
7185
- isEmpty: true
7523
+ upright: true,
7524
+ rotation: 0,
7525
+ ascentFlip: false
7186
7526
  };
7187
7527
  }
7188
- function toSlimGlyph(glyph) {
7528
+ function readQuad(mem, ptr, byteOffset) {
7529
+ const f = (offset) => readF32(mem, ptr, byteOffset + offset);
7189
7530
  return {
7190
- looseBox: glyph.looseBox,
7191
- flags: glyph.isEmpty ? 2 : glyph.isSpace ? 1 : 0,
7192
- ...glyph.tightBox ? { tightBox: glyph.tightBox } : {}
7531
+ p1: {
7532
+ x: f(0),
7533
+ y: f(4)
7534
+ },
7535
+ p2: {
7536
+ x: f(8),
7537
+ y: f(12)
7538
+ },
7539
+ p3: {
7540
+ x: f(16),
7541
+ y: f(20)
7542
+ },
7543
+ p4: {
7544
+ x: f(24),
7545
+ y: f(28)
7546
+ }
7193
7547
  };
7194
7548
  }
7195
7549
  //#endregion
@@ -8562,7 +8916,7 @@ function emptyResult(index, status) {
8562
8916
  //#endregion
8563
8917
  //#region src/features/search/internal/pageCorpusCache.ts
8564
8918
  /**
8565
- * Per-session, per-page search corpus: the page's extracted text plus its
8919
+ * Per-session, per-page search corpus: the page's text snapshot plus its
8566
8920
  * DEFAULT fold (the one literal queries with default options search).
8567
8921
  * This is the local engine's in-memory equivalent of the server's corpus
8568
8922
  * artifacts — same fold version, same shape, built lazily on first search
@@ -8587,19 +8941,20 @@ function acquirePageCorpus(runtime, session, pageObjectNumber, signal) {
8587
8941
  cache.set(session, pages);
8588
8942
  }
8589
8943
  const hit = pages.get(pageObjectNumber);
8590
- if (hit && hit.seq === seq) return hit.folded;
8591
- const text = new PageTextReader(runtime, session).read(pageObjectNumber, signal).text;
8592
- const folded = (0, _embedpdf_engine_core_runtime.foldText)(text);
8593
- pages.delete(pageObjectNumber);
8594
- pages.set(pageObjectNumber, {
8944
+ if (hit && hit.seq === seq) return hit;
8945
+ const snapshot = new PageTextReader(runtime, session).read(pageObjectNumber, signal);
8946
+ const entry = {
8595
8947
  seq,
8596
- folded
8597
- });
8948
+ folded: (0, _embedpdf_engine_core_runtime.foldText)(snapshot.text),
8949
+ snapshot
8950
+ };
8951
+ pages.delete(pageObjectNumber);
8952
+ pages.set(pageObjectNumber, entry);
8598
8953
  if (pages.size > MAX_CACHED_PAGES) {
8599
8954
  const oldest = pages.keys().next().value;
8600
8955
  if (oldest !== void 0) pages.delete(oldest);
8601
8956
  }
8602
- return folded;
8957
+ return entry;
8603
8958
  }
8604
8959
  //#endregion
8605
8960
  //#region src/features/search/internal/searchCursor.ts
@@ -8708,19 +9063,23 @@ var SearchReader = class {
8708
9063
  throwIfAborted(signal);
8709
9064
  const pon = order[scanned].pageObjectNumber;
8710
9065
  const corpus = acquirePageCorpus(this.runtime, this.session, pon, signal);
9066
+ const text = corpus.snapshot.text;
8711
9067
  let ranges;
8712
- if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(corpus.original, query);
8713
- else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(corpus.original, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
8714
- else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus, query);
9068
+ if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(text, query);
9069
+ else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(text, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
9070
+ else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus.folded, query);
8715
9071
  if (ranges.length > 0) {
8716
- const geometry = new PageGeometryReader(this.runtime, this.session).read(pon, signal);
8717
- for (const range of ranges) matches.push({
8718
- pageObjectNumber: pon,
8719
- charStart: range.start,
8720
- charCount: range.length,
8721
- rects: (0, _embedpdf_engine_core_runtime.searchRectsForRange)(geometry, range.start, range.length),
8722
- ...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(corpus.original, range) } : {}
8723
- });
9072
+ const layout = (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
9073
+ for (const range of ranges) {
9074
+ const chars = (0, _embedpdf_engine_core_runtime.charRangeForTextOffsets)(corpus.snapshot, range.start, range.start + range.length);
9075
+ matches.push({
9076
+ pageObjectNumber: pon,
9077
+ charStart: chars.start,
9078
+ charCount: chars.end - chars.start,
9079
+ segments: (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(layout, chars.start, chars.end - chars.start),
9080
+ ...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(text, range) } : {}
9081
+ });
9082
+ }
8724
9083
  }
8725
9084
  scanned++;
8726
9085
  pagesThisSlice++;
@@ -9001,9 +9360,10 @@ function validatePatchValue(key, value) {
9001
9360
  * worker wire shape from @embedpdf/engine-core.
9002
9361
  */
9003
9362
  var WorkerHost = class {
9004
- constructor(runtime, post) {
9363
+ constructor(runtime, post, options = {}) {
9005
9364
  this.runtime = runtime;
9006
9365
  this.post = post;
9366
+ this.options = options;
9007
9367
  _defineProperty(this, "sessions", /* @__PURE__ */ new Map());
9008
9368
  _defineProperty(this, "baseDocuments", void 0);
9009
9369
  _defineProperty(this, "aborts", /* @__PURE__ */ new Map());
@@ -9029,6 +9389,10 @@ var WorkerHost = class {
9029
9389
  this.aborts.get(msg.jobId)?.abort();
9030
9390
  return;
9031
9391
  }
9392
+ if (msg.kind === "pages.renderEncoded" || msg.kind === "document.renderPageFileEncoded" || msg.kind === "annotations.renderAppearancesEncoded") {
9393
+ this.receiveEncoded(msg);
9394
+ return;
9395
+ }
9032
9396
  const ctrl = new AbortController();
9033
9397
  this.aborts.set(msg.jobId, ctrl);
9034
9398
  let resultPack;
@@ -9132,6 +9496,9 @@ var WorkerHost = class {
9132
9496
  case "pages.insert":
9133
9497
  resultPack = this.handlePagesInsert(msg, ctrl.signal);
9134
9498
  break;
9499
+ case "pages.insertBlank":
9500
+ resultPack = this.handlePagesInsertBlank(msg, ctrl.signal);
9501
+ break;
9135
9502
  case "attachments.list":
9136
9503
  resultPack = this.handleAttachmentsList(msg, ctrl.signal);
9137
9504
  break;
@@ -9424,6 +9791,17 @@ var WorkerHost = class {
9424
9791
  result
9425
9792
  }, req.artifactPath);
9426
9793
  }
9794
+ handlePagesInsertBlank(req, signal) {
9795
+ const session = this.requireSession(req);
9796
+ const result = new PagesInserter(this.runtime, session).insertBlank({
9797
+ size: req.size,
9798
+ count: req.count
9799
+ }, req.destIndex, signal);
9800
+ return this.finishMutation(session, {
9801
+ tag: "pages.insertBlank",
9802
+ result
9803
+ }, req.artifactPath);
9804
+ }
9427
9805
  handleAttachmentsList(req, signal) {
9428
9806
  const session = this.requireSession(req);
9429
9807
  return (0, _embedpdf_engine_core_runtime.wirePack)({
@@ -9516,6 +9894,105 @@ var WorkerHost = class {
9516
9894
  raster
9517
9895
  }, [raster.data]);
9518
9896
  }
9897
+ async receiveEncoded(msg) {
9898
+ if (!this.options.imageEncoder) {
9899
+ this.post((0, _embedpdf_engine_core_runtime.wirePack)({
9900
+ kind: "reject",
9901
+ jobId: msg.jobId,
9902
+ error: (0, _embedpdf_engine_core_runtime.serializeError)(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this engine has no image encoder (the *.renderEncoded kinds are cloud-server surface)"))
9903
+ }, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
9904
+ return;
9905
+ }
9906
+ const ctrl = new AbortController();
9907
+ this.aborts.set(msg.jobId, ctrl);
9908
+ try {
9909
+ let resultPack;
9910
+ switch (msg.kind) {
9911
+ case "pages.renderEncoded":
9912
+ resultPack = await this.handlePagesRenderEncoded(msg, ctrl.signal);
9913
+ break;
9914
+ case "document.renderPageFileEncoded":
9915
+ resultPack = await this.handleDocumentRenderPageFileEncoded(msg, ctrl.signal);
9916
+ break;
9917
+ case "annotations.renderAppearancesEncoded":
9918
+ resultPack = await this.handleAnnotationsRenderAppearancesEncoded(msg, ctrl.signal);
9919
+ break;
9920
+ }
9921
+ this.post((0, _embedpdf_engine_core_runtime.wirePack)({
9922
+ kind: "resolve",
9923
+ jobId: msg.jobId,
9924
+ result: resultPack.payload
9925
+ }, resultPack.transfer));
9926
+ } catch (err) {
9927
+ this.post((0, _embedpdf_engine_core_runtime.wirePack)({
9928
+ kind: "reject",
9929
+ jobId: msg.jobId,
9930
+ error: (0, _embedpdf_engine_core_runtime.serializeError)(err)
9931
+ }, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
9932
+ } finally {
9933
+ this.aborts.delete(msg.jobId);
9934
+ }
9935
+ }
9936
+ async encodeRaster(raster, encode, signal) {
9937
+ const encoder = this.options.imageEncoder;
9938
+ if (!encoder) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this engine has no image encoder (the *.renderEncoded kinds are cloud-server surface)");
9939
+ const { bytes, contentType } = await encoder.encode(raster, encode);
9940
+ if (signal.aborted) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Aborted, "aborted during image encode");
9941
+ return {
9942
+ contentType,
9943
+ width: raster.width,
9944
+ height: raster.height,
9945
+ bytes
9946
+ };
9947
+ }
9948
+ async handlePagesRenderEncoded(req, signal) {
9949
+ const inner = this.handlePagesRender({
9950
+ ...req,
9951
+ kind: "pages.render"
9952
+ }, signal);
9953
+ if (inner.payload.tag !== "pages.render") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
9954
+ const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
9955
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
9956
+ tag: "pages.renderEncoded",
9957
+ image
9958
+ }, [image.bytes.buffer]);
9959
+ }
9960
+ async handleDocumentRenderPageFileEncoded(req, signal) {
9961
+ const inner = this.handleDocumentRenderPageFile({
9962
+ ...req,
9963
+ kind: "document.renderPageFile"
9964
+ }, signal);
9965
+ if (inner.payload.tag !== "document.renderPageFile") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
9966
+ const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
9967
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
9968
+ tag: "document.renderPageFileEncoded",
9969
+ pageObjectNumber: inner.payload.pageObjectNumber,
9970
+ pageCount: inner.payload.pageCount,
9971
+ image
9972
+ }, [image.bytes.buffer]);
9973
+ }
9974
+ async handleAnnotationsRenderAppearancesEncoded(req, signal) {
9975
+ const inner = this.handleAnnotationsRenderAppearances({
9976
+ ...req,
9977
+ kind: "annotations.renderAppearances"
9978
+ }, signal);
9979
+ if (inner.payload.tag !== "annotations.renderAppearances") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
9980
+ const { pageState, appearances } = inner.payload.result;
9981
+ const encoded = [];
9982
+ for (const a of appearances) encoded.push({
9983
+ ref: a.ref,
9984
+ mode: a.mode,
9985
+ rect: a.rect,
9986
+ image: await this.encodeRaster(a.raster, req.encode, signal)
9987
+ });
9988
+ return (0, _embedpdf_engine_core_runtime.wirePack)({
9989
+ tag: "annotations.renderAppearancesEncoded",
9990
+ result: {
9991
+ pageState,
9992
+ appearances: encoded
9993
+ }
9994
+ }, encoded.map((e) => e.image.bytes.buffer));
9995
+ }
9519
9996
  handleDocumentSaveBuffer(req) {
9520
9997
  const session = this.requireSession(req);
9521
9998
  const saved = new DocumentSaver(this.runtime, session).saveStandaloneToBuffer(req.mode);
@@ -9860,6 +10337,7 @@ exports.DocumentActionsReader = DocumentActionsReader;
9860
10337
  exports.DocumentPagesMutator = PagesMutator;
9861
10338
  exports.DocumentSaver = DocumentSaver;
9862
10339
  exports.DocumentSession = DocumentSession;
10340
+ exports.EPDF_CHAR_GEOMETRY_LAYOUT = EPDF_CHAR_GEOMETRY_LAYOUT;
9863
10341
  exports.EventHub = EventHub;
9864
10342
  exports.FontRegistrar = FontRegistrar;
9865
10343
  exports.FormMutator = FormMutator;
@@ -9885,6 +10363,7 @@ exports.SearchReader = SearchReader;
9885
10363
  exports.SecurityReader = SecurityReader;
9886
10364
  exports.SessionEventPublisher = SessionEventPublisher;
9887
10365
  exports.WorkerHost = WorkerHost;
10366
+ exports.buildRunsFromRawGlyphs = buildRunsFromRawGlyphs;
9888
10367
  exports.computeMutationImpact = computeMutationImpact;
9889
10368
  exports.destroyLibrary = destroyLibrary;
9890
10369
  exports.disposeFormModel = disposeFormModel;