@embedpdf/engine-core 3.0.0-next.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.
Files changed (55) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +24 -0
  3. package/dist/AbortError-ByBflvEl.cjs +38 -0
  4. package/dist/AbortError-ByBflvEl.cjs.map +1 -0
  5. package/dist/AbortError-CjSasIlL.js +27 -0
  6. package/dist/AbortError-CjSasIlL.js.map +1 -0
  7. package/dist/AnnotationListSnapshot-W0RCsyDE.d.cts +5043 -0
  8. package/dist/AnnotationListSnapshot-W0RCsyDE.d.ts +5043 -0
  9. package/dist/DocumentSecurityService-B-tULKNY.d.ts +332 -0
  10. package/dist/DocumentSecurityService-Df79fgpe.d.cts +332 -0
  11. package/dist/Engine-B7Oe7f5i.d.ts +1013 -0
  12. package/dist/Engine-D9loFZc1.d.cts +1013 -0
  13. package/dist/builders-BvikWRuO.cjs +1684 -0
  14. package/dist/builders-BvikWRuO.cjs.map +1 -0
  15. package/dist/builders-C548U47M.js +1367 -0
  16. package/dist/builders-C548U47M.js.map +1 -0
  17. package/dist/conformance.cjs +5414 -0
  18. package/dist/conformance.cjs.map +1 -0
  19. package/dist/conformance.d.cts +530 -0
  20. package/dist/conformance.d.ts +530 -0
  21. package/dist/conformance.js +5394 -0
  22. package/dist/conformance.js.map +1 -0
  23. package/dist/fold-CB4fAOOy.js +175 -0
  24. package/dist/fold-CB4fAOOy.js.map +1 -0
  25. package/dist/fold-IaMph3CO.cjs +216 -0
  26. package/dist/fold-IaMph3CO.cjs.map +1 -0
  27. package/dist/index.cjs +82 -0
  28. package/dist/index.d.cts +2 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +5 -0
  31. package/dist/resolver-DAZWVoJL.cjs +434 -0
  32. package/dist/resolver-DAZWVoJL.cjs.map +1 -0
  33. package/dist/resolver-DiPMTfcy.js +363 -0
  34. package/dist/resolver-DiPMTfcy.js.map +1 -0
  35. package/dist/runtime.cjs +453 -0
  36. package/dist/runtime.cjs.map +1 -0
  37. package/dist/runtime.d.cts +1002 -0
  38. package/dist/runtime.d.ts +1002 -0
  39. package/dist/runtime.js +361 -0
  40. package/dist/runtime.js.map +1 -0
  41. package/dist/schemas-9OpSSeM6.js +2493 -0
  42. package/dist/schemas-9OpSSeM6.js.map +1 -0
  43. package/dist/schemas-B_1VyFsm.cjs +3608 -0
  44. package/dist/schemas-B_1VyFsm.cjs.map +1 -0
  45. package/dist/subtype-BvAzLCGC.js +196 -0
  46. package/dist/subtype-BvAzLCGC.js.map +1 -0
  47. package/dist/subtype-qejjVr6h.cjs +231 -0
  48. package/dist/subtype-qejjVr6h.cjs.map +1 -0
  49. package/dist/wire.cjs +1516 -0
  50. package/dist/wire.cjs.map +1 -0
  51. package/dist/wire.d.cts +1546 -0
  52. package/dist/wire.d.ts +1546 -0
  53. package/dist/wire.js +1280 -0
  54. package/dist/wire.js.map +1 -0
  55. package/package.json +74 -0
@@ -0,0 +1,1684 @@
1
+ const require_subtype = require("./subtype-qejjVr6h.cjs");
2
+ const require_fold = require("./fold-IaMph3CO.cjs");
3
+ //#region src/geometry/convert.ts
4
+ /**
5
+ * Normalize a `PdfRect` to the y-up invariant (`left <= right`, `bottom <= top`),
6
+ * regardless of how the PDF ordered the corners (a `/Rect` array or `FS_RECTF`
7
+ * may carry them in any order). THE enforcement point for the "PdfRect is always
8
+ * normalized" rule: every producer that reads a raw box from PDFium passes it
9
+ * through here, so the invariant holds once, centrally — downstream `width =
10
+ * right - left` is never negative.
11
+ */
12
+ function normalizePdfRect(r) {
13
+ return {
14
+ left: Math.min(r.left, r.right),
15
+ right: Math.max(r.left, r.right),
16
+ bottom: Math.min(r.top, r.bottom),
17
+ top: Math.max(r.top, r.bottom)
18
+ };
19
+ }
20
+ function pdfRectWidth(r) {
21
+ return r.right - r.left;
22
+ }
23
+ function pdfRectHeight(r) {
24
+ return r.top - r.bottom;
25
+ }
26
+ function pdfRectSize(r) {
27
+ return {
28
+ width: r.right - r.left,
29
+ height: r.top - r.bottom
30
+ };
31
+ }
32
+ /** Convert edges -> origin+size, staying y-up (origin at bottom-left). */
33
+ function pdfRectToOriginSize(r) {
34
+ return {
35
+ x: r.left,
36
+ y: r.bottom,
37
+ width: r.right - r.left,
38
+ height: r.top - r.bottom
39
+ };
40
+ }
41
+ /** Convert y-up origin+size back to edges. */
42
+ function pdfRectFromOriginSize(o) {
43
+ return {
44
+ left: o.x,
45
+ bottom: o.y,
46
+ right: o.x + o.width,
47
+ top: o.y + o.height
48
+ };
49
+ }
50
+ /**
51
+ * Enclosing axis-aligned box of a quad, in PDF user space. Orientation
52
+ * agnostic (correct for rotated/skewed quads) — this is what highlight
53
+ * rendering and hit-testing want.
54
+ */
55
+ function pdfQuadBounds(q) {
56
+ const xs = [
57
+ q.p1.x,
58
+ q.p2.x,
59
+ q.p3.x,
60
+ q.p4.x
61
+ ];
62
+ const ys = [
63
+ q.p1.y,
64
+ q.p2.y,
65
+ q.p3.y,
66
+ q.p4.y
67
+ ];
68
+ return {
69
+ left: Math.min(...xs),
70
+ right: Math.max(...xs),
71
+ bottom: Math.min(...ys),
72
+ top: Math.max(...ys)
73
+ };
74
+ }
75
+ /**
76
+ * Axis-aligned named-corner interpretation of a quad.
77
+ *
78
+ * VALID ONLY for upright (non-rotated, non-skewed) quads: it is derived from
79
+ * the enclosing bounds, so for rotated/skewed quads it returns the bounding
80
+ * box corners, not the true geometric corners. For arbitrary quads use the
81
+ * positional `p1..p4` or `pdfQuadBounds` directly.
82
+ */
83
+ function pdfQuadCorners(q) {
84
+ const b = pdfQuadBounds(q);
85
+ return {
86
+ topLeft: {
87
+ x: b.left,
88
+ y: b.top
89
+ },
90
+ topRight: {
91
+ x: b.right,
92
+ y: b.top
93
+ },
94
+ bottomLeft: {
95
+ x: b.left,
96
+ y: b.bottom
97
+ },
98
+ bottomRight: {
99
+ x: b.right,
100
+ y: b.bottom
101
+ }
102
+ };
103
+ }
104
+ /**
105
+ * Build a quad from named corners, in PDFium `FS_QUADPOINTSF` slot order
106
+ * (`p1 = topLeft`, `p2 = topRight`, `p3 = bottomLeft`, `p4 = bottomRight`).
107
+ * The inverse of `pdfQuadCorners` for upright quads.
108
+ */
109
+ function pdfQuadFromCorners(c) {
110
+ return {
111
+ p1: c.topLeft,
112
+ p2: c.topRight,
113
+ p3: c.bottomLeft,
114
+ p4: c.bottomRight
115
+ };
116
+ }
117
+ //#endregion
118
+ //#region src/dto/PageRender.ts
119
+ function createPageImageHandle(result, blobSource) {
120
+ return {
121
+ ...result,
122
+ async objectUrl(signal) {
123
+ if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new require_fold.EngineError(require_subtype.EngineErrorCode.RuntimeUnavailable, "Object URLs are not available in this environment");
124
+ const blob = await blobSource.blob(signal);
125
+ const url = URL.createObjectURL(blob);
126
+ return {
127
+ url,
128
+ revoke: () => URL.revokeObjectURL(url)
129
+ };
130
+ }
131
+ };
132
+ }
133
+ //#endregion
134
+ //#region src/dto/PdfSaveMode.ts
135
+ const DEFAULT_PDF_SAVE_MODE = "incremental";
136
+ //#endregion
137
+ //#region src/identity/PageObjectNumber.ts
138
+ /**
139
+ * Type guard for the rare malformed-PDF case where a page is a direct
140
+ * object. The engine treats `0` as malformed and throws `EngineError(NotFound)`
141
+ * before any page-level operation.
142
+ */
143
+ function isValidPageObjectNumber(value) {
144
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
145
+ }
146
+ //#endregion
147
+ //#region src/identity/AnnotationStableId.ts
148
+ /**
149
+ * URL-safe encoding of a stable id, used by the cloud HTTP surface as the
150
+ * `:annotKey` route parameter. Decoded by the server back into an
151
+ * `AnnotationStableId` via `decodeStableIdKey`.
152
+ *
153
+ * Format:
154
+ * `{ kind: 'objectNumber', value: 42 }` -> `'obj:42'`
155
+ * `{ kind: 'nm', value: 'foo bar' }` -> `'nm:foo bar'`
156
+ *
157
+ * The caller is responsible for `encodeURIComponent`-ing the result before
158
+ * splicing it into a URL path; `wirePaths.layerAnnotationByKey` already does
159
+ * that. /NM values are opaque strings and may contain anything; the
160
+ * `nm:` prefix lets the decoder distinguish them from numeric ids
161
+ * unambiguously.
162
+ */
163
+ function encodeStableIdKey(id) {
164
+ if (id.kind === "objectNumber") {
165
+ if (!Number.isInteger(id.value) || id.value <= 0) throw new RangeError(`encodeStableIdKey: objectNumber must be a positive integer, got ${id.value}`);
166
+ return `obj:${id.value}`;
167
+ }
168
+ return `nm:${id.value}`;
169
+ }
170
+ /**
171
+ * Inverse of `encodeStableIdKey`. Returns `null` for malformed input so
172
+ * the server can answer 400 InvalidArg with a useful message instead of
173
+ * throwing.
174
+ *
175
+ * The input is the already-`decodeURIComponent`-ed segment from the route
176
+ * path. Empty `/NM` values are rejected: `nm:` with no suffix is not a
177
+ * valid identity.
178
+ */
179
+ function decodeStableIdKey(key) {
180
+ if (key.startsWith("obj:")) {
181
+ const rest = key.slice(4);
182
+ const n = Number.parseInt(rest, 10);
183
+ if (!Number.isInteger(n) || n <= 0 || String(n) !== rest) return null;
184
+ return {
185
+ kind: "objectNumber",
186
+ value: n
187
+ };
188
+ }
189
+ if (key.startsWith("nm:")) {
190
+ const value = key.slice(3);
191
+ if (value.length === 0) return null;
192
+ return {
193
+ kind: "nm",
194
+ value
195
+ };
196
+ }
197
+ return null;
198
+ }
199
+ //#endregion
200
+ //#region src/revision/RevisionToken.ts
201
+ function revisionTokensEqual(a, b) {
202
+ return a.docSessionId === b.docSessionId && a.pageObjectNumber === b.pageObjectNumber && a.generation === b.generation;
203
+ }
204
+ //#endregion
205
+ //#region src/revision/WeakAnnotationState.ts
206
+ const UNKNOWN_WEAK_ANNOTATION_STATE = { kind: "unknown" };
207
+ function knownWeakAnnotationState(hasAnyWeakAnnotations) {
208
+ return {
209
+ kind: "known",
210
+ hasAnyWeakAnnotations
211
+ };
212
+ }
213
+ //#endregion
214
+ //#region src/annotation/primitives.ts
215
+ const NO_ANNOTATION_FLAGS = {
216
+ invisible: false,
217
+ hidden: false,
218
+ print: false,
219
+ noZoom: false,
220
+ noRotate: false,
221
+ noView: false,
222
+ readOnly: false,
223
+ locked: false,
224
+ toggleNoView: false,
225
+ lockedContents: false
226
+ };
227
+ //#endregion
228
+ //#region src/resource/BinarySource.ts
229
+ function isBlob(value) {
230
+ return typeof Blob !== "undefined" && value instanceof Blob;
231
+ }
232
+ /**
233
+ * Detach a Uint8Array view into a standalone ArrayBuffer without copying
234
+ * when the view already spans its whole buffer.
235
+ */
236
+ function toStandaloneArrayBuffer(view) {
237
+ if (view.byteOffset === 0 && view.buffer instanceof ArrayBuffer && view.byteLength === view.buffer.byteLength) return view.buffer;
238
+ return view.slice().buffer;
239
+ }
240
+ /**
241
+ * Resolve any accepted `BinarySource` form into a `WireResource`.
242
+ * Async because Blob bytes can only be read asynchronously.
243
+ */
244
+ async function resolveBinarySource(source) {
245
+ if (source instanceof Uint8Array) return { bytes: toStandaloneArrayBuffer(source) };
246
+ if (isBlob(source)) {
247
+ const blobName = blobFileName(source);
248
+ return {
249
+ bytes: await source.arrayBuffer(),
250
+ mimeType: source.type || void 0,
251
+ ...blobName !== void 0 ? { name: blobName } : {}
252
+ };
253
+ }
254
+ const { data, mimeType, name } = source;
255
+ const inner = data instanceof Uint8Array ? {
256
+ bytes: toStandaloneArrayBuffer(data),
257
+ mimeType: void 0,
258
+ name: void 0
259
+ } : {
260
+ bytes: await data.arrayBuffer(),
261
+ mimeType: data.type || void 0,
262
+ name: blobFileName(data)
263
+ };
264
+ const resolvedName = name ?? inner.name;
265
+ return {
266
+ bytes: inner.bytes,
267
+ ...mimeType ?? inner.mimeType ? { mimeType: mimeType ?? inner.mimeType } : {},
268
+ ...resolvedName !== void 0 ? { name: resolvedName } : {}
269
+ };
270
+ }
271
+ /** A browser `File` is a Blob with a `name` — pick it up so attachment
272
+ * drafts can pass a `File` directly without repeating the file name. */
273
+ function blobFileName(blob) {
274
+ const name = blob.name;
275
+ return typeof name === "string" && name.length > 0 ? name : void 0;
276
+ }
277
+ //#endregion
278
+ //#region src/resource/binaryMetadata.ts
279
+ /**
280
+ * Detect format (and intrinsic dimensions for rasters) of a binary buffer.
281
+ *
282
+ * @returns metadata, or `null` for unsupported/corrupt data
283
+ */
284
+ function sniffBinaryMetadata(buffer) {
285
+ if (buffer.byteLength < 4) return null;
286
+ const bytes = new Uint8Array(buffer);
287
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return parsePng(buffer);
288
+ if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return parseJpeg(buffer);
289
+ if (bytes[0] === 37 && bytes[1] === 80 && bytes[2] === 68 && bytes[3] === 70) return { mimeType: "application/pdf" };
290
+ return null;
291
+ }
292
+ function parsePng(buffer) {
293
+ if (buffer.byteLength < 24) return null;
294
+ const view = new DataView(buffer);
295
+ const width = view.getUint32(16);
296
+ const height = view.getUint32(20);
297
+ if (width === 0 || height === 0) return null;
298
+ return {
299
+ mimeType: "image/png",
300
+ width,
301
+ height
302
+ };
303
+ }
304
+ function parseJpeg(buffer) {
305
+ const bytes = new Uint8Array(buffer);
306
+ let offset = 2;
307
+ while (offset + 4 < bytes.byteLength) {
308
+ if (bytes[offset] !== 255) return null;
309
+ const marker = bytes[offset + 1];
310
+ if (marker >= 192 && marker <= 195) {
311
+ if (offset + 9 > bytes.byteLength) return null;
312
+ const view = new DataView(buffer, offset + 5);
313
+ const height = view.getUint16(0);
314
+ const width = view.getUint16(2);
315
+ if (width === 0 || height === 0) return null;
316
+ return {
317
+ mimeType: "image/jpeg",
318
+ width,
319
+ height
320
+ };
321
+ }
322
+ if (marker === 216 || marker === 217) offset += 2;
323
+ else if (marker === 255) offset += 1;
324
+ else {
325
+ const segLen = bytes[offset + 2] << 8 | bytes[offset + 3];
326
+ offset += 2 + segLen;
327
+ }
328
+ }
329
+ return null;
330
+ }
331
+ //#endregion
332
+ //#region src/annotation/kinds/file-attachment/normalize.ts
333
+ /**
334
+ * Resolve an attachment `file` into its wire halves: metadata into the
335
+ * JSON body, bytes into the resource map. Unlike stamps there is NO
336
+ * format allowlist — attaching arbitrary files is the point — so the
337
+ * declared mime type wins (attachment formats cannot be reliably sniffed;
338
+ * the writer falls back to `application/octet-stream` when absent).
339
+ *
340
+ * Shared by the file-attachment annotation draft (below) and the
341
+ * document-level `attachments.create` mutation — one vocabulary, one
342
+ * normalizer for both homes a file can enter a PDF through.
343
+ */
344
+ async function normalizeAttachmentFileSource(file, key) {
345
+ const resolved = await resolveBinarySource(file.data);
346
+ const name = file.name ?? resolved.name;
347
+ if (!name) throw new require_fold.EngineError(require_subtype.EngineErrorCode.InvalidArg, "attachment file requires a name — pass { data, name } or a File whose name is set");
348
+ const mimeType = file.mimeType ?? resolved.mimeType;
349
+ return {
350
+ wireFile: {
351
+ resource: key,
352
+ name,
353
+ ...mimeType !== void 0 ? { mimeType } : {},
354
+ ...file.description !== void 0 ? { description: file.description } : {}
355
+ },
356
+ resource: {
357
+ bytes: resolved.bytes,
358
+ ...mimeType !== void 0 ? { mimeType } : {},
359
+ name
360
+ }
361
+ };
362
+ }
363
+ async function normalizeFileAttachmentDraft(draft, allocateKey) {
364
+ const { file, ...rest } = draft;
365
+ const key = allocateKey();
366
+ const { wireFile, resource } = await normalizeAttachmentFileSource(file, key);
367
+ return {
368
+ wire: {
369
+ ...rest,
370
+ file: wireFile
371
+ },
372
+ resources: { [key]: resource }
373
+ };
374
+ }
375
+ //#endregion
376
+ //#region src/annotation/kinds/stamp/normalize.ts
377
+ /**
378
+ * Resolve + validate a stamp `source`. Sniffing happens here — before any
379
+ * transport — so callers get a fast `InvalidArg` instead of a worker/HTTP
380
+ * round-trip. The sniffed mime type overwrites whatever was declared.
381
+ */
382
+ async function resolveStampSource(source) {
383
+ const resolved = await resolveBinarySource(source);
384
+ const meta = sniffBinaryMetadata(resolved.bytes);
385
+ if (!meta) throw new require_fold.EngineError(require_subtype.EngineErrorCode.InvalidArg, "stamp source must be PNG, JPEG, or single-page PDF bytes (format is detected from the data, not the declared mime type)");
386
+ return {
387
+ ...resolved,
388
+ mimeType: meta.mimeType
389
+ };
390
+ }
391
+ async function normalizeStampDraft(draft, allocateKey) {
392
+ const { source, ...rest } = draft;
393
+ const resolved = await resolveStampSource(source);
394
+ const key = allocateKey();
395
+ return {
396
+ wire: {
397
+ ...rest,
398
+ source: { resource: key }
399
+ },
400
+ resources: { [key]: resolved }
401
+ };
402
+ }
403
+ async function normalizeStampPatch(patch, allocateKey) {
404
+ if (patch.source === void 0) {
405
+ const { source: _source, ...rest } = patch;
406
+ return {
407
+ wire: rest,
408
+ resources: {}
409
+ };
410
+ }
411
+ const { source, ...rest } = patch;
412
+ const resolved = await resolveStampSource(source);
413
+ const key = allocateKey();
414
+ return {
415
+ wire: {
416
+ ...rest,
417
+ source: { resource: key }
418
+ },
419
+ resources: { [key]: resolved }
420
+ };
421
+ }
422
+ //#endregion
423
+ //#region src/annotation/normalize.ts
424
+ function createResourceKeyAllocator() {
425
+ let next = 0;
426
+ return () => `r${next++}`;
427
+ }
428
+ async function normalizeAnnotationDraft(draft) {
429
+ switch (draft.subtype) {
430
+ case "stamp": return normalizeStampDraft(draft, createResourceKeyAllocator());
431
+ case "file-attachment": return normalizeFileAttachmentDraft(draft, createResourceKeyAllocator());
432
+ default: return {
433
+ wire: draft,
434
+ resources: {}
435
+ };
436
+ }
437
+ }
438
+ async function normalizeAnnotationPatch(patch) {
439
+ switch (patch.subtype) {
440
+ case "stamp": return normalizeStampPatch(patch, createResourceKeyAllocator());
441
+ default: return {
442
+ wire: patch,
443
+ resources: {}
444
+ };
445
+ }
446
+ }
447
+ //#endregion
448
+ //#region src/annotation/relationships.ts
449
+ /**
450
+ * Classify one annotation by its relationship edge alone. Mirrors the
451
+ * spec's "practical parser rule": no `/IRT` is top-level; `/RT /Group` is
452
+ * a grouped subordinate; anything else with an `/IRT` is a reply (the
453
+ * engine has already normalized a missing `/RT` to `'reply'`).
454
+ */
455
+ function classifyRelation(a) {
456
+ if (!a.inReplyTo) return "top-level";
457
+ return a.replyType === "group" ? "grouped-subordinate" : "reply";
458
+ }
459
+ /**
460
+ * Stable string key for an {@link AnnotationRef}, used to match a child's
461
+ * `inReplyTo` against a candidate parent's `ref`. Both sides are produced
462
+ * by the engine with the same identity precedence (objectNumber, then nm,
463
+ * then index), so equal keys mean "same annotation".
464
+ */
465
+ function refKey(ref) {
466
+ switch (ref.kind) {
467
+ case "objectNumber": return `obj:${ref.pageObjectNumber}:${ref.annotObjectNumber}`;
468
+ case "nm": return `nm:${ref.pageObjectNumber}:${ref.nm}`;
469
+ case "index": return `idx:${ref.pageObjectNumber}:${ref.index}`;
470
+ }
471
+ }
472
+ /**
473
+ * Compose a flat annotation list (one page, or a whole document via
474
+ * `listRawAll()` flattened) into {@link AnnotationThread}s in primary
475
+ * order.
476
+ *
477
+ * Rules (ISO 32000 §12.5.6.2 + the spec's UI rule):
478
+ * - Annotations with no `/IRT` become thread primaries, in input order.
479
+ * - A `/RT /Group` child is attached to its primary's `groupedParts`.
480
+ * - A `/RT /R` (or default) child is attached to its primary's `replies`.
481
+ * - A child whose parent is not in the input set is treated as an
482
+ * orphan primary (defensive — e.g. a partial/filtered page slice).
483
+ *
484
+ * Limitations (v1, matches the legacy stack): threading is one level deep
485
+ * — a reply-to-a-reply is keyed to its direct parent, so it only appears
486
+ * nested if that direct parent is itself a primary. Callers that want a
487
+ * sidebar of "most annotations, not widgets/popups" should pre-filter the
488
+ * input; this helper is intentionally unopinionated about eligibility.
489
+ */
490
+ function buildThreads(annotations) {
491
+ const byKey = /* @__PURE__ */ new Map();
492
+ for (const a of annotations) {
493
+ byKey.set(refKey(a.ref), a);
494
+ if (a.nm && a.nm.length > 0) byKey.set(`nm:${a.ref.pageObjectNumber}:${a.nm}`, a);
495
+ }
496
+ const threads = [];
497
+ const threadByPrimaryKey = /* @__PURE__ */ new Map();
498
+ const primaryThread = (primary) => {
499
+ const key = refKey(primary.ref);
500
+ let thread = threadByPrimaryKey.get(key);
501
+ if (!thread) {
502
+ thread = {
503
+ primary,
504
+ groupedParts: [],
505
+ replies: []
506
+ };
507
+ threadByPrimaryKey.set(key, thread);
508
+ threads.push(thread);
509
+ }
510
+ return thread;
511
+ };
512
+ for (const a of annotations) if (!a.inReplyTo) primaryThread(a);
513
+ for (const a of annotations) {
514
+ if (!a.inReplyTo) continue;
515
+ const parent = byKey.get(refKey(a.inReplyTo));
516
+ if (!parent || parent.inReplyTo) {
517
+ primaryThread(a);
518
+ continue;
519
+ }
520
+ const thread = primaryThread(parent);
521
+ if (a.replyType === "group") thread.groupedParts.push(a);
522
+ else thread.replies.push(a);
523
+ }
524
+ return threads;
525
+ }
526
+ //#endregion
527
+ //#region src/mutation/AnnotationMutationImpactPolicy.ts
528
+ function changesAnnotationList(_kind) {
529
+ return true;
530
+ }
531
+ function shiftsExistingAnnotationIndices(kind) {
532
+ return kind === "delete" || kind === "move";
533
+ }
534
+ function invalidatesWeakIndexRefs(kind, weakStateBefore) {
535
+ if (!shiftsExistingAnnotationIndices(kind)) return false;
536
+ if (weakStateBefore.kind !== "known") throw new Error(`invalidatesWeakIndexRefs requires a known WeakAnnotationState for ${kind}; got 'unknown'. Caller must scan the page before computing mutation impact.`);
537
+ return weakStateBefore.hasAnyWeakAnnotations;
538
+ }
539
+ //#endregion
540
+ //#region src/annotation/appearance.ts
541
+ /**
542
+ * Coordinate tolerance in PDF user-space points. PDF numbers round-trip
543
+ * through f32 (~7 significant digits) and client-side space conversions, so
544
+ * exact float equality would misclassify no-op writes; 1e-3 pt is far below
545
+ * anything visible while far above accumulated f32 drift at page magnitudes.
546
+ */
547
+ const EPSILON = .001;
548
+ /**
549
+ * Keys that never affect `/AP` on any kind: the discriminator, behavioral
550
+ * flags, reply relationships, and grouping.
551
+ */
552
+ const INERT_KEYS = /* @__PURE__ */ new Set([
553
+ "subtype",
554
+ "flags",
555
+ "inReplyTo",
556
+ "replyType",
557
+ "groupId"
558
+ ]);
559
+ /**
560
+ * Kinds whose `/AP` paints `/Contents`. Everywhere else `contents` is popup
561
+ * note text — editing a comment must never re-bake (or destroy) the shape's
562
+ * appearance.
563
+ */
564
+ const CONTENTS_PAINTED = /* @__PURE__ */ new Set(["free-text", "redact"]);
565
+ /**
566
+ * The vertex family's `/EMBD_Metadata/Rotation` is an advisory scalar (the
567
+ * vertices are already rotated); it never drives the AP generator, so patches
568
+ * touching it are appearance-inert for these kinds.
569
+ */
570
+ const ADVISORY_ROTATION = /* @__PURE__ */ new Set([
571
+ "line",
572
+ "polyline",
573
+ "polygon",
574
+ "ink"
575
+ ]);
576
+ /**
577
+ * The box kinds carry the `/EMBD_Metadata` transform pair (`rotation` +
578
+ * `unrotatedRect`), tri-state on writes: omitted fields are PRESERVED, `null`
579
+ * (or `0` for rotation) clears. Translation verification therefore compares
580
+ * the after-state — `patch.rotation ?? current.rotation` — not the patch keys.
581
+ */
582
+ const BOX_TRANSFORM = /* @__PURE__ */ new Set([
583
+ "square",
584
+ "circle",
585
+ "free-text",
586
+ "stamp"
587
+ ]);
588
+ /**
589
+ * Per-kind absolute-geometry fields (PDF user space) that a rigid translation
590
+ * shifts together. A kind absent from this table never takes the translation
591
+ * route. `rect` is required in every entry: a translation always moves `/Rect`.
592
+ */
593
+ const TRANSLATABLE_GEOMETRY = {
594
+ square: ["rect", "unrotatedRect"],
595
+ circle: ["rect", "unrotatedRect"],
596
+ "free-text": [
597
+ "rect",
598
+ "unrotatedRect",
599
+ "calloutLine"
600
+ ],
601
+ line: ["rect", "linePoints"],
602
+ polygon: ["rect", "vertices"],
603
+ polyline: ["rect", "vertices"],
604
+ ink: ["rect", "inkList"],
605
+ highlight: ["rect", "quadPoints"],
606
+ underline: ["rect", "quadPoints"],
607
+ squiggly: ["rect", "quadPoints"],
608
+ strikeout: ["rect", "quadPoints"],
609
+ redact: ["rect", "quadPoints"],
610
+ caret: ["rect"],
611
+ text: ["rect"],
612
+ stamp: ["rect", "unrotatedRect"],
613
+ "file-attachment": ["rect"],
614
+ link: ["rect"]
615
+ };
616
+ const numEq = (a, b) => Math.abs(a - b) <= EPSILON;
617
+ /** Degrees normalized to [0,360); absent reads as 0 (no rotation). */
618
+ const normDeg = (v) => {
619
+ return typeof v === "number" ? (v % 360 + 360) % 360 : 0;
620
+ };
621
+ /**
622
+ * Semantic equality between a patch value and the current DTO value.
623
+ * Numbers compare within {@link EPSILON}; `null` and `undefined` both mean
624
+ * "entry absent" (the tri-state clear of an absent entry is a no-op); arrays
625
+ * and objects compare structurally.
626
+ */
627
+ function semanticEqual(a, b) {
628
+ if (a == null || b == null) return a == null && b == null;
629
+ if (typeof a === "number" && typeof b === "number") return numEq(a, b);
630
+ if (Array.isArray(a) || Array.isArray(b)) {
631
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
632
+ return a.every((v, i) => semanticEqual(v, b[i]));
633
+ }
634
+ if (typeof a === "object" && typeof b === "object") {
635
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
636
+ for (const k of keys) if (!semanticEqual(a[k], b[k])) return false;
637
+ return true;
638
+ }
639
+ return a === b;
640
+ }
641
+ /**
642
+ * Is `next` exactly `cur` shifted by `(dx, dy)`? Walks the value structurally:
643
+ * numeric leaves under x-axis keys (`left`/`right`/`x`) must shift by `dx`,
644
+ * y-axis keys (`bottom`/`top`/`y`) by `dy`, and any other numeric leaf must be
645
+ * unchanged. Arrays walk elementwise (same length required).
646
+ */
647
+ function shiftedBy(cur, next, dx, dy) {
648
+ if (cur == null || next == null) return cur == null && next == null;
649
+ if (Array.isArray(cur) || Array.isArray(next)) {
650
+ if (!Array.isArray(cur) || !Array.isArray(next) || cur.length !== next.length) return false;
651
+ return cur.every((v, i) => shiftedBy(v, next[i], dx, dy));
652
+ }
653
+ if (typeof cur === "object" && typeof next === "object") {
654
+ const keys = /* @__PURE__ */ new Set([...Object.keys(cur), ...Object.keys(next)]);
655
+ for (const k of keys) {
656
+ const c = cur[k];
657
+ const n = next[k];
658
+ if (typeof c === "number" && typeof n === "number") {
659
+ if (!numEq(c + (k === "left" || k === "right" || k === "x" ? dx : k === "bottom" || k === "top" || k === "y" ? dy : 0), n)) return false;
660
+ } else if (!shiftedBy(c, n, dx, dy)) return false;
661
+ }
662
+ return true;
663
+ }
664
+ return cur === next;
665
+ }
666
+ /**
667
+ * Verify the touched geometry is one rigid translation of the current state.
668
+ * Requires `patch.rect` (a translation always moves `/Rect`) with width and
669
+ * height preserved; every other geometry field present on the annotation must
670
+ * ride along shifted by the same delta — a rect move that leaves `vertices`
671
+ * behind is NOT a translation (the dictionary would desync from the pixels).
672
+ * For box kinds the `/EMBD_Metadata` transform group is checked as an
673
+ * after-state: writes are tri-state (omitted preserves, null/0 clears), so an
674
+ * omitted rotation keeps the current one — but a preserved-yet-unshifted
675
+ * `unrotatedRect` still fails the congruence check via the field loop.
676
+ */
677
+ function isRigidTranslation(cur, pat, subtype, geometryKeys) {
678
+ const curRect = cur.rect;
679
+ const patRect = pat.rect;
680
+ if (!curRect || !patRect) return false;
681
+ const dx = patRect.left - curRect.left;
682
+ const dy = patRect.bottom - curRect.bottom;
683
+ if (!numEq(patRect.right - patRect.left, curRect.right - curRect.left)) return false;
684
+ if (!numEq(patRect.top - patRect.bottom, curRect.top - curRect.bottom)) return false;
685
+ if (BOX_TRANSFORM.has(subtype)) {
686
+ const rotAfter = pat.rotation === void 0 ? cur.rotation : pat.rotation;
687
+ if (!numEq(normDeg(cur.rotation), normDeg(rotAfter))) return false;
688
+ }
689
+ for (const key of geometryKeys) {
690
+ if (key === "rect") continue;
691
+ const c = cur[key];
692
+ const p = pat[key];
693
+ if (c == null && p == null) continue;
694
+ if (c == null || p == null) return false;
695
+ if (!shiftedBy(c, p, dx, dy)) return false;
696
+ }
697
+ return true;
698
+ }
699
+ /**
700
+ * Classify an update patch against the annotation's current DTO.
701
+ *
702
+ * 1. Value-diff: drop keys whose value semantically equals the current one,
703
+ * plus the always-inert metadata keys (and per-kind inert keys: `contents`
704
+ * where it isn't painted, advisory `rotation` on the vertex family).
705
+ * Nothing left → `'inert'`.
706
+ * 2. If every remaining key is translatable geometry for this kind AND the
707
+ * values are one rigid translation → `'translation'`.
708
+ * 3. Anything else — style, text, unknown keys, unknown kinds → `'regenerate'`.
709
+ */
710
+ function appearanceImpactOf(current, patch) {
711
+ if (patch.subtype !== current.subtype) return "regenerate";
712
+ const cur = current;
713
+ const pat = patch;
714
+ const subtype = patch.subtype;
715
+ const touched = [];
716
+ for (const [key, value] of Object.entries(pat)) {
717
+ if (value === void 0 || INERT_KEYS.has(key)) continue;
718
+ if (key === "contents" && !CONTENTS_PAINTED.has(subtype)) continue;
719
+ if (key === "rotation" && ADVISORY_ROTATION.has(subtype)) continue;
720
+ if (!semanticEqual(value, cur[key])) touched.push(key);
721
+ }
722
+ if (touched.length === 0) return "inert";
723
+ const geometryKeys = TRANSLATABLE_GEOMETRY[subtype];
724
+ if (!geometryKeys) return "regenerate";
725
+ if (!touched.every((k) => geometryKeys.includes(k))) return "regenerate";
726
+ return isRigidTranslation(cur, pat, subtype, geometryKeys) ? "translation" : "regenerate";
727
+ }
728
+ //#endregion
729
+ //#region src/identity/FormFieldRef.ts
730
+ /**
731
+ * URL-safe encoding of a `FormFieldRef`, used by the cloud HTTP surface as
732
+ * the `:fieldKey` route parameter. Decoded by the server back into a
733
+ * `FormFieldRef` via `decodeFieldRefKey`. Mirrors the annotation plane's
734
+ * `encodeStableIdKey` (`obj:42` / `nm:…`) so the two member-key syntaxes
735
+ * read the same on the wire:
736
+ *
737
+ * `{ kind: 'objectNumber', fieldObjectNumber: 12 }` -> `'obj:12'`
738
+ * `{ kind: 'fqn', name: 'billing.name' }` -> `'fqn:billing.name'`
739
+ *
740
+ * The caller is responsible for `encodeURIComponent`-ing the result before
741
+ * splicing it into a URL path; the `wirePaths.layerFormField*` builders
742
+ * already do that. FQNs are opaque strings and may contain anything; the
743
+ * `fqn:` prefix keeps them unambiguous from numeric ids.
744
+ */
745
+ function encodeFieldRefKey(ref) {
746
+ if (ref.kind === "objectNumber") {
747
+ if (!Number.isInteger(ref.fieldObjectNumber) || ref.fieldObjectNumber <= 0) throw new RangeError(`encodeFieldRefKey: fieldObjectNumber must be a positive integer, got ${ref.fieldObjectNumber}`);
748
+ return `obj:${ref.fieldObjectNumber}`;
749
+ }
750
+ return `fqn:${ref.name}`;
751
+ }
752
+ /**
753
+ * Inverse of `encodeFieldRefKey`. Returns `null` for malformed input so the
754
+ * server can answer 400 InvalidArg with a useful message instead of
755
+ * throwing. The input is the already-`decodeURIComponent`-ed segment from
756
+ * the route path. Empty FQNs are rejected: `fqn:` with no suffix is not a
757
+ * valid identity.
758
+ */
759
+ function decodeFieldRefKey(key) {
760
+ if (key.startsWith("obj:")) {
761
+ const rest = key.slice(4);
762
+ const n = Number.parseInt(rest, 10);
763
+ if (!Number.isInteger(n) || n <= 0 || String(n) !== rest) return null;
764
+ return {
765
+ kind: "objectNumber",
766
+ fieldObjectNumber: n
767
+ };
768
+ }
769
+ if (key.startsWith("fqn:")) {
770
+ const name = key.slice(4);
771
+ if (name.length === 0) return null;
772
+ return {
773
+ kind: "fqn",
774
+ name
775
+ };
776
+ }
777
+ return null;
778
+ }
779
+ //#endregion
780
+ //#region src/search/literal.ts
781
+ /**
782
+ * The fold flavor a literal query needs. Haystack and needle MUST be
783
+ * folded with the same options; corpus caches key their pre-folded text
784
+ * on this (the default `{}` fold is the persistable one — non-default
785
+ * queries fold the original page text at query time).
786
+ */
787
+ function foldOptionsFor(query) {
788
+ return {
789
+ keepCase: !!query.matchCase,
790
+ keepMarks: !!query.matchDiacritics
791
+ };
792
+ }
793
+ const WORD_UNIT = /[\p{L}\p{N}_]/u;
794
+ const MARK = /\p{M}/u;
795
+ /** Whether the code point ENDING at `index` (exclusive) is a word char. */
796
+ function wordBefore(text, index) {
797
+ if (index <= 0) return false;
798
+ const unit = text.charCodeAt(index - 1);
799
+ const start = unit >= 56320 && unit <= 57343 && index >= 2 ? index - 2 : index - 1;
800
+ return WORD_UNIT.test(String.fromCodePoint(text.codePointAt(start)));
801
+ }
802
+ /** Whether the code point starting at `index` is a word char. */
803
+ function wordAt(text, index) {
804
+ if (index >= text.length) return false;
805
+ return WORD_UNIT.test(String.fromCodePoint(text.codePointAt(index)));
806
+ }
807
+ /**
808
+ * All non-overlapping literal matches, in original code-unit space.
809
+ * `haystack` must have been folded with `foldOptionsFor(query)`.
810
+ */
811
+ function matchLiteral(haystack, query) {
812
+ const needle = require_fold.foldText(query.text, foldOptionsFor(query)).folded;
813
+ if (needle.trim().length === 0) return [];
814
+ const out = [];
815
+ let from = 0;
816
+ while (from <= haystack.folded.length - needle.length) {
817
+ const at = haystack.folded.indexOf(needle, from);
818
+ if (at < 0) break;
819
+ if (query.wholeWord && (wordBefore(haystack.folded, at) || wordAt(haystack.folded, at + needle.length))) {
820
+ from = at + 1;
821
+ continue;
822
+ }
823
+ if (query.matchDiacritics && at + needle.length < haystack.folded.length) {
824
+ const after = haystack.folded.codePointAt(at + needle.length);
825
+ if (MARK.test(String.fromCodePoint(after))) {
826
+ from = at + 1;
827
+ continue;
828
+ }
829
+ }
830
+ out.push(require_fold.toOriginalRange(haystack, at, needle.length));
831
+ from = at + needle.length;
832
+ }
833
+ return out;
834
+ }
835
+ //#endregion
836
+ //#region src/search/regex.ts
837
+ /**
838
+ * The portable search-regex dialect: JavaScript `u`-mode syntax MINUS the
839
+ * features RE2 refuses (backreferences, lookaround). The local engine
840
+ * executes patterns with JS `RegExp`; the server executes the SAME
841
+ * pattern with RE2, whose linear-time guarantee is the ReDoS defense.
842
+ * Restricting both sides to the common subset keeps one query string
843
+ * valid — and equally powerful — everywhere.
844
+ */
845
+ const SEARCH_REGEX_MAX_LENGTH = 512;
846
+ /**
847
+ * Validate a pattern against the dialect. Cheap and dependency-free —
848
+ * viewers call it on keystroke for early feedback; engines re-validate
849
+ * and reject bad patterns with `InvalidArg`.
850
+ */
851
+ function validateSearchRegex(pattern) {
852
+ if (pattern.length === 0) return {
853
+ ok: false,
854
+ issue: "empty",
855
+ message: "Pattern is empty."
856
+ };
857
+ if (pattern.length > 512) return {
858
+ ok: false,
859
+ issue: "too-long",
860
+ message: `Pattern exceeds 512 characters.`
861
+ };
862
+ let inClass = false;
863
+ for (let i = 0; i < pattern.length; i++) {
864
+ const ch = pattern[i];
865
+ if (ch === "\\") {
866
+ const next = pattern[i + 1] ?? "";
867
+ if (!inClass && next >= "1" && next <= "9") return {
868
+ ok: false,
869
+ issue: "backreference",
870
+ message: `Backreference \\${next} is not supported.`
871
+ };
872
+ if (!inClass && next === "k" && pattern[i + 2] === "<") return {
873
+ ok: false,
874
+ issue: "backreference",
875
+ message: "Named backreferences (\\k<…>) are not supported."
876
+ };
877
+ i++;
878
+ continue;
879
+ }
880
+ if (inClass) {
881
+ if (ch === "]") inClass = false;
882
+ continue;
883
+ }
884
+ if (ch === "[") {
885
+ inClass = true;
886
+ continue;
887
+ }
888
+ if (ch === "(" && pattern[i + 1] === "?") {
889
+ const tag = pattern[i + 2] ?? "";
890
+ if (tag === "=" || tag === "!") return {
891
+ ok: false,
892
+ issue: "lookaround",
893
+ message: "Lookahead ((?=…), (?!…)) is not supported."
894
+ };
895
+ if (tag === "<" && (pattern[i + 3] === "=" || pattern[i + 3] === "!")) return {
896
+ ok: false,
897
+ issue: "lookaround",
898
+ message: "Lookbehind ((?<=…), (?<!…)) is not supported."
899
+ };
900
+ }
901
+ }
902
+ try {
903
+ new RegExp(pattern, "mu");
904
+ } catch (error) {
905
+ return {
906
+ ok: false,
907
+ issue: "syntax",
908
+ message: error.message
909
+ };
910
+ }
911
+ return { ok: true };
912
+ }
913
+ function validateSearchQuery(query) {
914
+ if (!query.regex) return { ok: true };
915
+ if (query.matchDiacritics) return {
916
+ ok: false,
917
+ issue: "diacritics-with-regex",
918
+ message: "Diacritic-sensitive matching is not available for regex patterns (regex runs on the raw text plane)."
919
+ };
920
+ return validateSearchRegex(query.text);
921
+ }
922
+ /**
923
+ * All matches of a dialect-valid pattern over the RAW page text (regex
924
+ * does not run against folded text — case handling is the `i` flag, and
925
+ * diacritic folding never applies). `wholeWord` is a boundary POST-FILTER
926
+ * using the same Unicode word test as the literal path — never `\b`,
927
+ * whose ASCII word set would give the toggle different semantics in regex
928
+ * mode ("caf" whole-word matching inside "café"). Filtering after the
929
+ * match also survives the move to RE2 server-side. Zero-length matches
930
+ * are skipped: search UI cannot highlight nothing, and skipping them is
931
+ * also the infinite-loop guard for patterns like `a*`.
932
+ */
933
+ function matchRegex(text, query) {
934
+ const valid = validateSearchQuery({
935
+ ...query,
936
+ regex: true
937
+ });
938
+ if (!valid.ok) throw new Error(`Invalid search query (${valid.issue}): ${valid.message}`);
939
+ const re = new RegExp(query.text, query.matchCase ? "gmu" : "gimu");
940
+ const out = [];
941
+ let m;
942
+ while ((m = re.exec(text)) !== null) {
943
+ if (m[0].length === 0) {
944
+ const unit = text.charCodeAt(re.lastIndex);
945
+ re.lastIndex += unit >= 55296 && unit <= 56319 ? 2 : 1;
946
+ continue;
947
+ }
948
+ if (query.wholeWord && (wordBefore(text, m.index) || wordAt(text, m.index + m[0].length))) continue;
949
+ out.push({
950
+ start: m.index,
951
+ length: m[0].length
952
+ });
953
+ }
954
+ return out;
955
+ }
956
+ //#endregion
957
+ //#region src/search/matcher.ts
958
+ /**
959
+ * The whole match stage in one pure call: page text in, original-space
960
+ * match ranges out. Engines with a pre-folded corpus for the default fold
961
+ * skip this and call `matchLiteral` on the cached `FoldedText` directly;
962
+ * everything else (non-default fold options, regex, tests) comes through
963
+ * here.
964
+ */
965
+ function matchPageText(text, query) {
966
+ if (query.regex) return matchRegex(text, query);
967
+ return matchLiteral(require_fold.foldText(text, foldOptionsFor(query)), query);
968
+ }
969
+ //#endregion
970
+ //#region src/search/snippet.ts
971
+ /** Default context on each side of a match, in code units. */
972
+ const SEARCH_SNIPPET_CONTEXT = 48;
973
+ const WHITESPACE = /\s/;
974
+ const WHITESPACE_ALL = /\s/g;
975
+ const isLowSurrogate = (text, index) => {
976
+ const unit = text.charCodeAt(index);
977
+ return unit >= 56320 && unit <= 57343;
978
+ };
979
+ /**
980
+ * The `'full'`-mode excerpt around one match: up to `context` code units
981
+ * of page text on each side, trimmed to a whitespace boundary when one
982
+ * exists inside the window (so snippets start and end on whole words),
983
+ * never splitting a surrogate pair, whitespace flattened 1:1.
984
+ */
985
+ function buildSnippet(text, range, context = 48) {
986
+ const matchEnd = range.start + range.length;
987
+ let start = Math.max(0, range.start - context);
988
+ if (start > 0) {
989
+ for (let i = start; i < range.start; i++) if (WHITESPACE.test(text[i])) {
990
+ start = i + 1;
991
+ break;
992
+ }
993
+ if (isLowSurrogate(text, start)) start++;
994
+ }
995
+ let end = Math.min(text.length, matchEnd + context);
996
+ if (end < text.length) {
997
+ for (let i = end - 1; i >= matchEnd; i--) if (WHITESPACE.test(text[i])) {
998
+ end = i;
999
+ break;
1000
+ }
1001
+ if (isLowSurrogate(text, end)) end--;
1002
+ }
1003
+ return {
1004
+ text: text.slice(start, end).replace(WHITESPACE_ALL, " "),
1005
+ matchStart: range.start - start,
1006
+ matchLength: range.length
1007
+ };
1008
+ }
1009
+ //#endregion
1010
+ //#region src/search/rects.ts
1011
+ /**
1012
+ * Match-range → merged line rects, in PDF user space (y-up).
1013
+ *
1014
+ * This is the same line-merge text selection uses (adapted from Chromium's
1015
+ * pdf/pdfium/pdfium_range.cc `MergeAdjacentRects`, BSD-licensed, Copyright
1016
+ * 2010 The Chromium Authors), operating directly on the engine's
1017
+ * `PageGeometrySnapshot`: glyph loose boxes → sub-runs (split on big
1018
+ * intra-run gaps) → one rect per visual line. Search highlights therefore
1019
+ * look exactly like selections — never a rect per glyph (the v2 bug).
1020
+ *
1021
+ * It lives in engine-core because BOTH sides need it: the server computes
1022
+ * rects here for `'rects'`-mode responses (geometry never leaves the trust
1023
+ * boundary), and the local engine computes them in the worker.
1024
+ */
1025
+ const FLAG_EMPTY = 2;
1026
+ const CHAR_DISTANCE_FACTOR = 2.5;
1027
+ const FONT_SIZE_RATIO_THRESHOLD = 1.5;
1028
+ const VERTICAL_OVERLAP_THRESHOLD = .8;
1029
+ /**
1030
+ * Merged line rects for the text-page char range
1031
+ * `[charStart, charStart + charCount)`. Ranges index the same space as
1032
+ * `PageGeometryRun.charStart`; chars outside the snapshot are ignored.
1033
+ */
1034
+ function searchRectsForRange(snapshot, charStart, charCount) {
1035
+ if (charCount <= 0) return [];
1036
+ const lo = charStart;
1037
+ const hi = charStart + charCount - 1;
1038
+ const subRuns = [];
1039
+ for (const run of snapshot.runs) {
1040
+ const runEnd = run.charStart + run.glyphs.length - 1;
1041
+ if (runEnd < lo || run.charStart > hi) continue;
1042
+ const s = Math.max(lo, run.charStart);
1043
+ const e = Math.min(hi, runEnd);
1044
+ let left = Infinity;
1045
+ let right = -Infinity;
1046
+ let bottom = Infinity;
1047
+ let top = -Infinity;
1048
+ let count = 0;
1049
+ let widthSum = 0;
1050
+ let prevRight = -Infinity;
1051
+ const flush = () => {
1052
+ if (count > 0 && right > left && top > bottom) subRuns.push({
1053
+ rect: {
1054
+ left,
1055
+ bottom,
1056
+ right,
1057
+ top
1058
+ },
1059
+ charCount: count,
1060
+ fontSize: run.fontSize
1061
+ });
1062
+ left = Infinity;
1063
+ right = -Infinity;
1064
+ bottom = Infinity;
1065
+ top = -Infinity;
1066
+ count = 0;
1067
+ widthSum = 0;
1068
+ prevRight = -Infinity;
1069
+ };
1070
+ for (let ci = s; ci <= e; ci++) {
1071
+ const g = run.glyphs[ci - run.charStart];
1072
+ if ((g.flags & FLAG_EMPTY) !== 0) continue;
1073
+ const b = g.looseBox;
1074
+ if (count > 0 && prevRight > -Infinity) {
1075
+ const avg = widthSum / count;
1076
+ if (avg > 0 && Math.abs(b.left - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
1077
+ }
1078
+ left = Math.min(left, b.left);
1079
+ right = Math.max(right, b.right);
1080
+ bottom = Math.min(bottom, b.bottom);
1081
+ top = Math.max(top, b.top);
1082
+ count++;
1083
+ widthSum += b.right - b.left;
1084
+ prevRight = b.right;
1085
+ }
1086
+ flush();
1087
+ }
1088
+ return mergeAdjacentRects(subRuns);
1089
+ }
1090
+ function mergeAdjacentRects(runs) {
1091
+ const out = [];
1092
+ let prev = null;
1093
+ let cur = null;
1094
+ for (const run of runs) {
1095
+ if (prev && cur && shouldMerge(prev, run)) cur = union(cur, run.rect);
1096
+ else {
1097
+ if (cur) out.push(cur);
1098
+ cur = run.rect;
1099
+ }
1100
+ prev = run;
1101
+ }
1102
+ if (cur) out.push(cur);
1103
+ return out;
1104
+ }
1105
+ function shouldMerge(a, b) {
1106
+ if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
1107
+ if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
1108
+ }
1109
+ if (verticalOverlap(a.rect, b.rect) < VERTICAL_OVERLAP_THRESHOLD) return false;
1110
+ const aw = (a.rect.right - a.rect.left) / a.charCount;
1111
+ const bw = (b.rect.right - b.rect.left) / b.charCount;
1112
+ const aL = a.rect.left - aw;
1113
+ const aR = a.rect.right + aw;
1114
+ const bL = b.rect.left - bw;
1115
+ return aL < b.rect.right + bw && aR > bL;
1116
+ }
1117
+ function union(a, b) {
1118
+ return {
1119
+ left: Math.min(a.left, b.left),
1120
+ bottom: Math.min(a.bottom, b.bottom),
1121
+ right: Math.max(a.right, b.right),
1122
+ top: Math.max(a.top, b.top)
1123
+ };
1124
+ }
1125
+ function verticalOverlap(a, b) {
1126
+ const ah = a.top - a.bottom;
1127
+ const bh = b.top - b.bottom;
1128
+ if (ah <= 0 || bh <= 0) return 0;
1129
+ const u = Math.max(a.top, b.top) - Math.min(a.bottom, b.bottom);
1130
+ if (u === ah || u === bh) return 1;
1131
+ return Math.max(0, Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)) / u;
1132
+ }
1133
+ //#endregion
1134
+ //#region src/search/epoch.ts
1135
+ /**
1136
+ * The search CONTENT EPOCH: a deterministic fingerprint of everything a
1137
+ * search result depends on — page order/structure (`layoutVersion`) and
1138
+ * each page's text content (`contentVersion`), nothing else. Annotation
1139
+ * and metadata churn deliberately do NOT move it, so cached search
1140
+ * responses in a collaborative document survive comment storms.
1141
+ *
1142
+ * Both sides compute it from the manifest they already hold: the client
1143
+ * to mint versioned search URLs, the server to validate them (mismatch →
1144
+ * NotFound, the standard stale-versioned-read signal). FNV-1a 64-bit —
1145
+ * dependency-free and stable; not cryptographic, and doesn't need to be:
1146
+ * an adversary can only vary his OWN authorized query results.
1147
+ */
1148
+ function searchContentEpoch(manifest) {
1149
+ let input = `${manifest.layoutVersion}`;
1150
+ for (const page of manifest.pages) input += `|${page.state.pageObjectNumber}:${page.cache.contentVersion}`;
1151
+ return fnv1a64(input);
1152
+ }
1153
+ const FNV_OFFSET = 14695981039346656037n;
1154
+ const FNV_PRIME = 1099511628211n;
1155
+ const MASK_64 = 18446744073709551615n;
1156
+ function fnv1a64(input) {
1157
+ let hash = FNV_OFFSET;
1158
+ for (let i = 0; i < input.length; i++) {
1159
+ hash ^= BigInt(input.charCodeAt(i));
1160
+ hash = hash * FNV_PRIME & MASK_64;
1161
+ }
1162
+ return hash.toString(16).padStart(16, "0");
1163
+ }
1164
+ /**
1165
+ * Canonicalize a query for CACHE KEYING: default-fold literal queries are
1166
+ * pre-folded ("Café", "café" and "CAFE" are the same search, so they
1167
+ * should be the same cache entry). Semantics are unchanged — the fold is
1168
+ * idempotent and exactly what the matcher applies to the needle anyway.
1169
+ * Case- or diacritic-sensitive literals and regex patterns pass through
1170
+ * untouched (their raw form IS the query).
1171
+ */
1172
+ function canonicalSearchQuery(query) {
1173
+ if (query.regex || query.matchCase || query.matchDiacritics) return query;
1174
+ const canonical = { text: require_fold.foldText(query.text).folded };
1175
+ if (query.wholeWord) canonical.wholeWord = true;
1176
+ return canonical;
1177
+ }
1178
+ //#endregion
1179
+ //#region src/auth/scope/pdf-bits.ts
1180
+ /**
1181
+ * ISO 32000 user-access permission bit positions, expressed as integer
1182
+ * masks. Bit numbers in the spec are 1-indexed, so bit 3 = `1 << 2`.
1183
+ *
1184
+ * Bits 1, 2, 7, 8, and 13+ are reserved and not surfaced here.
1185
+ */
1186
+ const PDF_BITS = {
1187
+ /** Bit 3 — print the document. */
1188
+ PRINT: 4,
1189
+ /** Bit 4 — modify content streams. */
1190
+ MODIFY: 8,
1191
+ /** Bit 5 — copy/extract text and graphics. */
1192
+ COPY: 16,
1193
+ /** Bit 6 — annotate + fill forms (+ modify form fields with bit 4). */
1194
+ ANNOTATE_FILL: 32,
1195
+ /** Bit 9 — fill existing forms (even if bit 6 is clear). */
1196
+ FILL_FORMS: 256,
1197
+ /** Bit 10 — accessibility extraction (deprecated in PDF 2.0). */
1198
+ ACCESSIBILITY: 512,
1199
+ /** Bit 11 — assemble document (insert/rotate/delete pages, outline). */
1200
+ ASSEMBLE: 1024,
1201
+ /** Bit 12 — high-quality print (refines bit 3). */
1202
+ PRINT_HIGH: 2048
1203
+ };
1204
+ /**
1205
+ * Decode a raw PDFium permission integer into a typed boolean view.
1206
+ *
1207
+ * `null` input (the documents row column is nullable until probed)
1208
+ * yields all-false bits — i.e., a maximally restrictive interpretation.
1209
+ * That keeps `pdf.permissions` expansion safe on unprobed documents.
1210
+ */
1211
+ function decodePdfBits(raw) {
1212
+ if (raw === null) return {
1213
+ bit3: false,
1214
+ bit4: false,
1215
+ bit5: false,
1216
+ bit6: false,
1217
+ bit9: false,
1218
+ bit10: false,
1219
+ bit11: false,
1220
+ bit12: false
1221
+ };
1222
+ return {
1223
+ bit3: (raw & PDF_BITS.PRINT) !== 0,
1224
+ bit4: (raw & PDF_BITS.MODIFY) !== 0,
1225
+ bit5: (raw & PDF_BITS.COPY) !== 0,
1226
+ bit6: (raw & PDF_BITS.ANNOTATE_FILL) !== 0,
1227
+ bit9: (raw & PDF_BITS.FILL_FORMS) !== 0,
1228
+ bit10: (raw & PDF_BITS.ACCESSIBILITY) !== 0,
1229
+ bit11: (raw & PDF_BITS.ASSEMBLE) !== 0,
1230
+ bit12: (raw & PDF_BITS.PRINT_HIGH) !== 0
1231
+ };
1232
+ }
1233
+ //#endregion
1234
+ //#region src/auth/scope/builders.ts
1235
+ /**
1236
+ * Typed capability constants for customer-side JWT minting.
1237
+ *
1238
+ * The shape mirrors the dotted capability name so it reads naturally:
1239
+ * caps.doc.text.copy() // "doc.text.copy"
1240
+ * caps.doc.download() // "doc.download"
1241
+ * caps.doc.download.flattened() // "doc.download.flattened"
1242
+ * caps.doc.print.high() // "doc.print.high"
1243
+ *
1244
+ * Each leaf is a function returning the literal string (as `const`) so
1245
+ * TypeScript catches typos at the call site. Wrappers like `download`
1246
+ * and `print` are `Object.assign`'d to be callable AND carry child
1247
+ * properties for the refinement capabilities.
1248
+ */
1249
+ const caps = { doc: {
1250
+ open: () => "doc.open",
1251
+ render: () => "doc.render",
1252
+ text: {
1253
+ select: () => "doc.text.select",
1254
+ copy: () => "doc.text.copy",
1255
+ search: () => "doc.text.search"
1256
+ },
1257
+ content: { copy: () => "doc.content.copy" },
1258
+ download: Object.assign(() => "doc.download", { flattened: () => "doc.download.flattened" }),
1259
+ print: Object.assign(() => "doc.print", { high: () => "doc.print.high" }),
1260
+ pages: {
1261
+ modify: () => "doc.pages.modify",
1262
+ assemble: () => "doc.pages.assemble"
1263
+ },
1264
+ forms: {
1265
+ read: () => "doc.forms.read",
1266
+ fill: () => "doc.forms.fill",
1267
+ modify: () => "doc.forms.modify"
1268
+ },
1269
+ annotate: {
1270
+ read: () => "doc.annotate.read",
1271
+ modify: () => "doc.annotate.modify"
1272
+ },
1273
+ metadata: { modify: () => "doc.metadata.modify" },
1274
+ attachments: { modify: () => "doc.attachments.modify" },
1275
+ redact: () => "doc.redact"
1276
+ } };
1277
+ /**
1278
+ * Builder factory for collab scope filters. Each entity:action pair
1279
+ * gets a sub-object with the four filter constructors.
1280
+ *
1281
+ * collab.annotations.create.self() → "annotations:create:self"
1282
+ * collab.annotations.create.group("legal") → "annotations:create:group=legal"
1283
+ * collab.annotations.update.group("4") → "annotations:update:group=4"
1284
+ * collab.annotations.delete.createdBy("u-7") → "annotations:delete:createdBy=u-7"
1285
+ * collab.annotations.setGroup.group("legal") → "annotations:set-group:group=legal"
1286
+ * collab.annotations.all.all() → "annotations:*:all" (action wildcard)
1287
+ *
1288
+ * On create, the filter is evaluated against the caller's own identity
1289
+ * (no impersonation), so `:self` and `:all` trivially pass; `:group=X`
1290
+ * is the meaningful filter — it constrains creation to callers whose
1291
+ * default group is X.
1292
+ */
1293
+ const collab = { annotations: {
1294
+ create: makeFilterBuilder("annotations", "create"),
1295
+ update: makeFilterBuilder("annotations", "update"),
1296
+ delete: makeFilterBuilder("annotations", "delete"),
1297
+ setGroup: makeSetGroupBuilder(),
1298
+ /** Action wildcard — matches create, update, delete, AND set-group with the given filter. */
1299
+ all: makeFilterBuilder("annotations", "*")
1300
+ } };
1301
+ function makeFilterBuilder(entity, action) {
1302
+ return {
1303
+ all: () => `${entity}:${action}:all`,
1304
+ self: () => `${entity}:${action}:self`,
1305
+ createdBy: (userId) => `${entity}:${action}:createdBy=${userId}`,
1306
+ group: (groupId) => `${entity}:${action}:group=${groupId}`
1307
+ };
1308
+ }
1309
+ function makeSetGroupBuilder() {
1310
+ return {
1311
+ all: () => "annotations:set-group:all",
1312
+ group: (groupId) => `annotations:set-group:group=${groupId}`
1313
+ };
1314
+ }
1315
+ /**
1316
+ * Builder for the `pdf.permissions` virtual scope. Returns the literal
1317
+ * string; provided as a helper for parity with caps/collab in JWT
1318
+ * construction code.
1319
+ */
1320
+ const pdfPermissions = () => "pdf.permissions";
1321
+ /**
1322
+ * Materialise `pdf.permissions` into the concrete capability list it
1323
+ * would expand to under the given PDF bit configuration.
1324
+ *
1325
+ * Useful when the customer wants to start from PDF defaults and then
1326
+ * subtract specific capabilities — easier to build the JWT scope array
1327
+ * by filtering an explicit list than by composing virtual + denials.
1328
+ *
1329
+ * const defaults = materializePdfPermissions(pdfBits);
1330
+ * const scope = [
1331
+ * ...defaults.filter(s => s !== 'doc.text.copy'),
1332
+ * 'doc.download',
1333
+ * ];
1334
+ *
1335
+ * IMPORTANT: this MUST stay in sync with `addPdfPermissions` inside
1336
+ * resolver.ts. A test pins them together.
1337
+ */
1338
+ function materializePdfPermissions(b) {
1339
+ const out = /* @__PURE__ */ new Set();
1340
+ out.add("doc.open");
1341
+ out.add("doc.render");
1342
+ out.add("doc.annotate.read");
1343
+ out.add("doc.forms.read");
1344
+ if (b.bit5) {
1345
+ out.add("doc.text.select");
1346
+ out.add("doc.text.copy");
1347
+ out.add("doc.text.search");
1348
+ out.add("doc.content.copy");
1349
+ }
1350
+ if (b.bit3) out.add("doc.print");
1351
+ if (b.bit12 && b.bit3) out.add("doc.print.high");
1352
+ if (b.bit4) {
1353
+ out.add("doc.pages.modify");
1354
+ out.add("doc.redact");
1355
+ out.add("doc.metadata.modify");
1356
+ out.add("doc.attachments.modify");
1357
+ }
1358
+ if (b.bit11) out.add("doc.pages.assemble");
1359
+ if (b.bit6) out.add("doc.annotate.modify");
1360
+ if (b.bit6 || b.bit9) out.add("doc.forms.fill");
1361
+ if (b.bit6 && b.bit4) out.add("doc.forms.modify");
1362
+ return [...out];
1363
+ }
1364
+ //#endregion
1365
+ Object.defineProperty(exports, "DEFAULT_PDF_SAVE_MODE", {
1366
+ enumerable: true,
1367
+ get: function() {
1368
+ return DEFAULT_PDF_SAVE_MODE;
1369
+ }
1370
+ });
1371
+ Object.defineProperty(exports, "NO_ANNOTATION_FLAGS", {
1372
+ enumerable: true,
1373
+ get: function() {
1374
+ return NO_ANNOTATION_FLAGS;
1375
+ }
1376
+ });
1377
+ Object.defineProperty(exports, "PDF_BITS", {
1378
+ enumerable: true,
1379
+ get: function() {
1380
+ return PDF_BITS;
1381
+ }
1382
+ });
1383
+ Object.defineProperty(exports, "SEARCH_REGEX_MAX_LENGTH", {
1384
+ enumerable: true,
1385
+ get: function() {
1386
+ return SEARCH_REGEX_MAX_LENGTH;
1387
+ }
1388
+ });
1389
+ Object.defineProperty(exports, "SEARCH_SNIPPET_CONTEXT", {
1390
+ enumerable: true,
1391
+ get: function() {
1392
+ return SEARCH_SNIPPET_CONTEXT;
1393
+ }
1394
+ });
1395
+ Object.defineProperty(exports, "UNKNOWN_WEAK_ANNOTATION_STATE", {
1396
+ enumerable: true,
1397
+ get: function() {
1398
+ return UNKNOWN_WEAK_ANNOTATION_STATE;
1399
+ }
1400
+ });
1401
+ Object.defineProperty(exports, "appearanceImpactOf", {
1402
+ enumerable: true,
1403
+ get: function() {
1404
+ return appearanceImpactOf;
1405
+ }
1406
+ });
1407
+ Object.defineProperty(exports, "buildSnippet", {
1408
+ enumerable: true,
1409
+ get: function() {
1410
+ return buildSnippet;
1411
+ }
1412
+ });
1413
+ Object.defineProperty(exports, "buildThreads", {
1414
+ enumerable: true,
1415
+ get: function() {
1416
+ return buildThreads;
1417
+ }
1418
+ });
1419
+ Object.defineProperty(exports, "canonicalSearchQuery", {
1420
+ enumerable: true,
1421
+ get: function() {
1422
+ return canonicalSearchQuery;
1423
+ }
1424
+ });
1425
+ Object.defineProperty(exports, "caps", {
1426
+ enumerable: true,
1427
+ get: function() {
1428
+ return caps;
1429
+ }
1430
+ });
1431
+ Object.defineProperty(exports, "changesAnnotationList", {
1432
+ enumerable: true,
1433
+ get: function() {
1434
+ return changesAnnotationList;
1435
+ }
1436
+ });
1437
+ Object.defineProperty(exports, "classifyRelation", {
1438
+ enumerable: true,
1439
+ get: function() {
1440
+ return classifyRelation;
1441
+ }
1442
+ });
1443
+ Object.defineProperty(exports, "collab", {
1444
+ enumerable: true,
1445
+ get: function() {
1446
+ return collab;
1447
+ }
1448
+ });
1449
+ Object.defineProperty(exports, "createPageImageHandle", {
1450
+ enumerable: true,
1451
+ get: function() {
1452
+ return createPageImageHandle;
1453
+ }
1454
+ });
1455
+ Object.defineProperty(exports, "decodeFieldRefKey", {
1456
+ enumerable: true,
1457
+ get: function() {
1458
+ return decodeFieldRefKey;
1459
+ }
1460
+ });
1461
+ Object.defineProperty(exports, "decodePdfBits", {
1462
+ enumerable: true,
1463
+ get: function() {
1464
+ return decodePdfBits;
1465
+ }
1466
+ });
1467
+ Object.defineProperty(exports, "decodeStableIdKey", {
1468
+ enumerable: true,
1469
+ get: function() {
1470
+ return decodeStableIdKey;
1471
+ }
1472
+ });
1473
+ Object.defineProperty(exports, "encodeFieldRefKey", {
1474
+ enumerable: true,
1475
+ get: function() {
1476
+ return encodeFieldRefKey;
1477
+ }
1478
+ });
1479
+ Object.defineProperty(exports, "encodeStableIdKey", {
1480
+ enumerable: true,
1481
+ get: function() {
1482
+ return encodeStableIdKey;
1483
+ }
1484
+ });
1485
+ Object.defineProperty(exports, "foldOptionsFor", {
1486
+ enumerable: true,
1487
+ get: function() {
1488
+ return foldOptionsFor;
1489
+ }
1490
+ });
1491
+ Object.defineProperty(exports, "invalidatesWeakIndexRefs", {
1492
+ enumerable: true,
1493
+ get: function() {
1494
+ return invalidatesWeakIndexRefs;
1495
+ }
1496
+ });
1497
+ Object.defineProperty(exports, "isValidPageObjectNumber", {
1498
+ enumerable: true,
1499
+ get: function() {
1500
+ return isValidPageObjectNumber;
1501
+ }
1502
+ });
1503
+ Object.defineProperty(exports, "knownWeakAnnotationState", {
1504
+ enumerable: true,
1505
+ get: function() {
1506
+ return knownWeakAnnotationState;
1507
+ }
1508
+ });
1509
+ Object.defineProperty(exports, "matchLiteral", {
1510
+ enumerable: true,
1511
+ get: function() {
1512
+ return matchLiteral;
1513
+ }
1514
+ });
1515
+ Object.defineProperty(exports, "matchPageText", {
1516
+ enumerable: true,
1517
+ get: function() {
1518
+ return matchPageText;
1519
+ }
1520
+ });
1521
+ Object.defineProperty(exports, "matchRegex", {
1522
+ enumerable: true,
1523
+ get: function() {
1524
+ return matchRegex;
1525
+ }
1526
+ });
1527
+ Object.defineProperty(exports, "materializePdfPermissions", {
1528
+ enumerable: true,
1529
+ get: function() {
1530
+ return materializePdfPermissions;
1531
+ }
1532
+ });
1533
+ Object.defineProperty(exports, "normalizeAnnotationDraft", {
1534
+ enumerable: true,
1535
+ get: function() {
1536
+ return normalizeAnnotationDraft;
1537
+ }
1538
+ });
1539
+ Object.defineProperty(exports, "normalizeAnnotationPatch", {
1540
+ enumerable: true,
1541
+ get: function() {
1542
+ return normalizeAnnotationPatch;
1543
+ }
1544
+ });
1545
+ Object.defineProperty(exports, "normalizeAttachmentFileSource", {
1546
+ enumerable: true,
1547
+ get: function() {
1548
+ return normalizeAttachmentFileSource;
1549
+ }
1550
+ });
1551
+ Object.defineProperty(exports, "normalizePdfRect", {
1552
+ enumerable: true,
1553
+ get: function() {
1554
+ return normalizePdfRect;
1555
+ }
1556
+ });
1557
+ Object.defineProperty(exports, "pdfPermissions", {
1558
+ enumerable: true,
1559
+ get: function() {
1560
+ return pdfPermissions;
1561
+ }
1562
+ });
1563
+ Object.defineProperty(exports, "pdfQuadBounds", {
1564
+ enumerable: true,
1565
+ get: function() {
1566
+ return pdfQuadBounds;
1567
+ }
1568
+ });
1569
+ Object.defineProperty(exports, "pdfQuadCorners", {
1570
+ enumerable: true,
1571
+ get: function() {
1572
+ return pdfQuadCorners;
1573
+ }
1574
+ });
1575
+ Object.defineProperty(exports, "pdfQuadFromCorners", {
1576
+ enumerable: true,
1577
+ get: function() {
1578
+ return pdfQuadFromCorners;
1579
+ }
1580
+ });
1581
+ Object.defineProperty(exports, "pdfRectFromOriginSize", {
1582
+ enumerable: true,
1583
+ get: function() {
1584
+ return pdfRectFromOriginSize;
1585
+ }
1586
+ });
1587
+ Object.defineProperty(exports, "pdfRectHeight", {
1588
+ enumerable: true,
1589
+ get: function() {
1590
+ return pdfRectHeight;
1591
+ }
1592
+ });
1593
+ Object.defineProperty(exports, "pdfRectSize", {
1594
+ enumerable: true,
1595
+ get: function() {
1596
+ return pdfRectSize;
1597
+ }
1598
+ });
1599
+ Object.defineProperty(exports, "pdfRectToOriginSize", {
1600
+ enumerable: true,
1601
+ get: function() {
1602
+ return pdfRectToOriginSize;
1603
+ }
1604
+ });
1605
+ Object.defineProperty(exports, "pdfRectWidth", {
1606
+ enumerable: true,
1607
+ get: function() {
1608
+ return pdfRectWidth;
1609
+ }
1610
+ });
1611
+ Object.defineProperty(exports, "refKey", {
1612
+ enumerable: true,
1613
+ get: function() {
1614
+ return refKey;
1615
+ }
1616
+ });
1617
+ Object.defineProperty(exports, "resolveBinarySource", {
1618
+ enumerable: true,
1619
+ get: function() {
1620
+ return resolveBinarySource;
1621
+ }
1622
+ });
1623
+ Object.defineProperty(exports, "revisionTokensEqual", {
1624
+ enumerable: true,
1625
+ get: function() {
1626
+ return revisionTokensEqual;
1627
+ }
1628
+ });
1629
+ Object.defineProperty(exports, "searchContentEpoch", {
1630
+ enumerable: true,
1631
+ get: function() {
1632
+ return searchContentEpoch;
1633
+ }
1634
+ });
1635
+ Object.defineProperty(exports, "searchRectsForRange", {
1636
+ enumerable: true,
1637
+ get: function() {
1638
+ return searchRectsForRange;
1639
+ }
1640
+ });
1641
+ Object.defineProperty(exports, "semanticEqual", {
1642
+ enumerable: true,
1643
+ get: function() {
1644
+ return semanticEqual;
1645
+ }
1646
+ });
1647
+ Object.defineProperty(exports, "shiftsExistingAnnotationIndices", {
1648
+ enumerable: true,
1649
+ get: function() {
1650
+ return shiftsExistingAnnotationIndices;
1651
+ }
1652
+ });
1653
+ Object.defineProperty(exports, "sniffBinaryMetadata", {
1654
+ enumerable: true,
1655
+ get: function() {
1656
+ return sniffBinaryMetadata;
1657
+ }
1658
+ });
1659
+ Object.defineProperty(exports, "validateSearchQuery", {
1660
+ enumerable: true,
1661
+ get: function() {
1662
+ return validateSearchQuery;
1663
+ }
1664
+ });
1665
+ Object.defineProperty(exports, "validateSearchRegex", {
1666
+ enumerable: true,
1667
+ get: function() {
1668
+ return validateSearchRegex;
1669
+ }
1670
+ });
1671
+ Object.defineProperty(exports, "wordAt", {
1672
+ enumerable: true,
1673
+ get: function() {
1674
+ return wordAt;
1675
+ }
1676
+ });
1677
+ Object.defineProperty(exports, "wordBefore", {
1678
+ enumerable: true,
1679
+ get: function() {
1680
+ return wordBefore;
1681
+ }
1682
+ });
1683
+
1684
+ //# sourceMappingURL=builders-BvikWRuO.cjs.map