@embedpdf/engine-core 3.0.0-next.13 → 3.0.0-next.15

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 (135) hide show
  1. package/dist/AnnotationList-CkijRHet.d.cts +27163 -0
  2. package/dist/AnnotationList-CkijRHet.d.ts +27163 -0
  3. package/dist/Change-CKoxfTNY.js +889 -0
  4. package/dist/Change-CKoxfTNY.js.map +1 -0
  5. package/dist/Change-CjImsrN0.cjs +1020 -0
  6. package/dist/Change-CjImsrN0.cjs.map +1 -0
  7. package/dist/DocumentRenderService-BfFdFSwI.cjs +120 -0
  8. package/dist/DocumentRenderService-BfFdFSwI.cjs.map +1 -0
  9. package/dist/DocumentRenderService-DV3J9lMz.js +85 -0
  10. package/dist/DocumentRenderService-DV3J9lMz.js.map +1 -0
  11. package/dist/{DocumentSecurityService-_JRjqvoI.d.ts → DocumentSecurityService-CU1M6Q7m.d.ts} +100 -112
  12. package/dist/{DocumentSecurityService-ClTZc0zO.d.cts → DocumentSecurityService-e6vp2de8.d.cts} +100 -112
  13. package/dist/LocalDocumentHandle-D02FMm-Z.cjs +53 -0
  14. package/dist/LocalDocumentHandle-D02FMm-Z.cjs.map +1 -0
  15. package/dist/LocalDocumentHandle-DAPdaOt_.js +30 -0
  16. package/dist/LocalDocumentHandle-DAPdaOt_.js.map +1 -0
  17. package/dist/LocalEngine-DFoBm4B3.d.cts +1565 -0
  18. package/dist/LocalEngine-DPriFujS.d.ts +1565 -0
  19. package/dist/PageInsertBlankInput-CXHAM7l6.cjs +17 -0
  20. package/dist/PageInsertBlankInput-CXHAM7l6.cjs.map +1 -0
  21. package/dist/PageInsertBlankInput-DhVYVJ_S.js +12 -0
  22. package/dist/PageInsertBlankInput-DhVYVJ_S.js.map +1 -0
  23. package/dist/authority-B3BklsB4.js +2239 -0
  24. package/dist/authority-B3BklsB4.js.map +1 -0
  25. package/dist/authority-DLMNR7ff.cjs +2676 -0
  26. package/dist/authority-DLMNR7ff.cjs.map +1 -0
  27. package/dist/builders-CNj0POeY.js +161 -0
  28. package/dist/builders-CNj0POeY.js.map +1 -0
  29. package/dist/builders-Cgdp_YCn.cjs +184 -0
  30. package/dist/builders-Cgdp_YCn.cjs.map +1 -0
  31. package/dist/calibrate-7T3fnP8-.js +2404 -0
  32. package/dist/calibrate-7T3fnP8-.js.map +1 -0
  33. package/dist/calibrate-DJBXnhLE.cjs +2919 -0
  34. package/dist/calibrate-DJBXnhLE.cjs.map +1 -0
  35. package/dist/charmap-Cprti1ta.js +239 -0
  36. package/dist/charmap-Cprti1ta.js.map +1 -0
  37. package/dist/charmap-aYlefQP9.cjs +304 -0
  38. package/dist/charmap-aYlefQP9.cjs.map +1 -0
  39. package/dist/conformance.cjs +16753 -5247
  40. package/dist/conformance.cjs.map +1 -1
  41. package/dist/conformance.d.cts +928 -165
  42. package/dist/conformance.d.ts +928 -165
  43. package/dist/conformance.js +16731 -5268
  44. package/dist/conformance.js.map +1 -1
  45. package/dist/format-Bp3QFSH1.cjs +574 -0
  46. package/dist/format-Bp3QFSH1.cjs.map +1 -0
  47. package/dist/format-Dgg428Kb.js +461 -0
  48. package/dist/format-Dgg428Kb.js.map +1 -0
  49. package/dist/index.cjs +330 -103
  50. package/dist/index.d.cts +2 -2
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.js +14 -5
  53. package/dist/pageRefs-B3ur7Ckm.cjs +51 -0
  54. package/dist/pageRefs-B3ur7Ckm.cjs.map +1 -0
  55. package/dist/pageRefs-DkbuxSCV.js +40 -0
  56. package/dist/pageRefs-DkbuxSCV.js.map +1 -0
  57. package/dist/public.cjs +57 -0
  58. package/dist/public.d.cts +5 -0
  59. package/dist/public.d.ts +5 -0
  60. package/dist/public.js +9 -0
  61. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs +3646 -0
  62. package/dist/resolveAnnotationDraft-7Ke4nV7F.cjs.map +1 -0
  63. package/dist/resolveAnnotationDraft-CUpf0SMh.js +3167 -0
  64. package/dist/resolveAnnotationDraft-CUpf0SMh.js.map +1 -0
  65. package/dist/{resolver-inAVzsWt.js → resolver-CzxmD9Ck.js} +294 -144
  66. package/dist/resolver-CzxmD9Ck.js.map +1 -0
  67. package/dist/{resolver-CYVNV4hn.cjs → resolver-D2OCtE8F.cjs} +347 -143
  68. package/dist/resolver-D2OCtE8F.cjs.map +1 -0
  69. package/dist/runtime-Cl0v56mm.d.ts +2032 -0
  70. package/dist/runtime-p1WDzDo4.d.cts +2032 -0
  71. package/dist/runtime.cjs +733 -284
  72. package/dist/runtime.cjs.map +1 -1
  73. package/dist/runtime.d.cts +5 -1348
  74. package/dist/runtime.d.ts +5 -1348
  75. package/dist/runtime.js +396 -179
  76. package/dist/runtime.js.map +1 -1
  77. package/dist/schema-C-gU4vj7.cjs +3034 -0
  78. package/dist/schema-C-gU4vj7.cjs.map +1 -0
  79. package/dist/schema-DzlUNk38.js +2081 -0
  80. package/dist/schema-DzlUNk38.js.map +1 -0
  81. package/dist/schemas-Berf1swF.cjs +2481 -0
  82. package/dist/schemas-Berf1swF.cjs.map +1 -0
  83. package/dist/schemas-D6xOGjqg.js +1654 -0
  84. package/dist/schemas-D6xOGjqg.js.map +1 -0
  85. package/dist/subtype-CG4WpIRj.js +129 -0
  86. package/dist/subtype-CG4WpIRj.js.map +1 -0
  87. package/dist/subtype-aDt5TFmK.cjs +158 -0
  88. package/dist/subtype-aDt5TFmK.cjs.map +1 -0
  89. package/dist/validation.cjs +578 -0
  90. package/dist/validation.cjs.map +1 -0
  91. package/dist/validation.d.cts +174 -0
  92. package/dist/validation.d.ts +174 -0
  93. package/dist/validation.js +571 -0
  94. package/dist/validation.js.map +1 -0
  95. package/dist/value-BFS1R8ek.cjs +435 -0
  96. package/dist/value-BFS1R8ek.cjs.map +1 -0
  97. package/dist/value-Dhc8eYcx.js +340 -0
  98. package/dist/value-Dhc8eYcx.js.map +1 -0
  99. package/dist/viewport-BJW13EGZ.js +540 -0
  100. package/dist/viewport-BJW13EGZ.js.map +1 -0
  101. package/dist/viewport-DlL5Bxbg.cjs +671 -0
  102. package/dist/viewport-DlL5Bxbg.cjs.map +1 -0
  103. package/dist/wire.cjs +1056 -379
  104. package/dist/wire.cjs.map +1 -1
  105. package/dist/wire.d.cts +15069 -725
  106. package/dist/wire.d.ts +15069 -725
  107. package/dist/wire.js +825 -239
  108. package/dist/wire.js.map +1 -1
  109. package/package.json +10 -1
  110. package/dist/AbortError-Cq2igpZ6.cjs +0 -38
  111. package/dist/AbortError-Cq2igpZ6.cjs.map +0 -1
  112. package/dist/AbortError-DHqPBnYc.js +0 -27
  113. package/dist/AbortError-DHqPBnYc.js.map +0 -1
  114. package/dist/AnnotationListSnapshot-D3mFut1e.d.cts +0 -6871
  115. package/dist/AnnotationListSnapshot-D3mFut1e.d.ts +0 -6871
  116. package/dist/Engine-Bx0NOfmd.d.cts +0 -1264
  117. package/dist/Engine-DxtpCYcP.d.ts +0 -1264
  118. package/dist/PageInsertBlankInput-CqXBmFhN.cjs +0 -449
  119. package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +0 -1
  120. package/dist/PageInsertBlankInput-cHq9-i-z.js +0 -378
  121. package/dist/PageInsertBlankInput-cHq9-i-z.js.map +0 -1
  122. package/dist/builders-Bqm8yTpi.cjs +0 -3564
  123. package/dist/builders-Bqm8yTpi.cjs.map +0 -1
  124. package/dist/builders-Dqo2X-dt.js +0 -3121
  125. package/dist/builders-Dqo2X-dt.js.map +0 -1
  126. package/dist/fold-2V29cxCV.cjs +0 -447
  127. package/dist/fold-2V29cxCV.cjs.map +0 -1
  128. package/dist/fold-CdEJTBTn.js +0 -334
  129. package/dist/fold-CdEJTBTn.js.map +0 -1
  130. package/dist/resolver-CYVNV4hn.cjs.map +0 -1
  131. package/dist/resolver-inAVzsWt.js.map +0 -1
  132. package/dist/schemas-BLARNCgx.js +0 -3113
  133. package/dist/schemas-BLARNCgx.js.map +0 -1
  134. package/dist/schemas-DIqFMmrC.cjs +0 -4468
  135. package/dist/schemas-DIqFMmrC.cjs.map +0 -1
@@ -0,0 +1,2404 @@
1
+ import { $ as ANNOTATION_FIELD_SPACES_BY_NAME, A as shapeFieldsOf, G as assertRowResources, H as assertKnownFields, J as isBundlePageRef, O as DRAWN_RECT_KINDS, P as semanticEqual, Q as ANNOTATION_FIELD_SPACES, U as assertResourceIds, V as assertBundlePages, W as assertResourcesNamed, Y as isRecord, _t as pdfQuadBounds, dt as pdfPointsBounds, ft as pdfTurnOfDrawn, j as UNBAKED_KINDS, k as pdfShapeForRect, l as pdfResolveRectCommand, lt as pdfPointTurned, mt as isSamePdfRect, n as pdfPredictAnnotationDraft, nt as kindFactsOf, o as pdfPredictAnnotationPatch, ot as DEFAULT_RICH_TEXT_BODY, pt as pdfTurnOfUpright, q as invalidBundle, tt as ANNOTATION_RESOURCE_ROLES, ut as pdfPointUnturned, v as assertWritableMeasure, wt as pdfRectTurnedBounds } from "./resolveAnnotationDraft-CUpf0SMh.js";
2
+ import { h as EngineError, u as encodePageKey, v as _defineProperty, y as EngineErrorCode } from "./format-Dgg428Kb.js";
3
+ import { c as toBase64, o as decodedLengthOf, s as fromBase64 } from "./charmap-Cprti1ta.js";
4
+ //#region src/geometry/renderSize.ts
5
+ /**
6
+ * The pixel size of a render: an area of `width` × `height` points, turned
7
+ * by `rotation` (width and height swap on a quarter turn) and sized by
8
+ * `viewport`. The local worker allocates exactly this; the cloud client
9
+ * reports it without rendering. `InvalidArg` for a viewport that isn't
10
+ * positive.
11
+ */
12
+ function renderSize(area, rotation, viewport) {
13
+ const swap = rotation === 90 || rotation === 270;
14
+ const baseWidth = swap ? area.height : area.width;
15
+ const baseHeight = swap ? area.width : area.height;
16
+ if (viewport.kind === "width") {
17
+ if (!Number.isFinite(viewport.width) || viewport.width <= 0) throw new EngineError(EngineErrorCode.InvalidArg, "render viewport width must be positive");
18
+ const width = Math.max(1, Math.round(viewport.width));
19
+ return {
20
+ width,
21
+ height: Math.max(1, Math.round(width * baseHeight / baseWidth))
22
+ };
23
+ }
24
+ const scale = viewport.scale ?? 1;
25
+ if (!Number.isFinite(scale) || scale <= 0) throw new EngineError(EngineErrorCode.InvalidArg, "render viewport scale must be positive");
26
+ return {
27
+ width: Math.max(1, Math.round(baseWidth * scale)),
28
+ height: Math.max(1, Math.round(baseHeight * scale))
29
+ };
30
+ }
31
+ //#endregion
32
+ //#region src/geometry/pageTransform.ts
33
+ /**
34
+ * The transform of the image a render with `options` makes of `page`, without
35
+ * rendering: its size, rounding included, and the conversion between page
36
+ * space and its pixels. Takes the page's layout (`doc.pages.list()`).
37
+ * `InvalidArg` for a viewport that isn't positive or a target without area.
38
+ */
39
+ function pageTransform(page, options = {}) {
40
+ const target = options.target;
41
+ return renderAreaTransform(target?.kind === "rect" ? renderTargetArea(target.rect) : {
42
+ x: 0,
43
+ y: 0,
44
+ width: page.size.width,
45
+ height: page.size.height
46
+ }, options);
47
+ }
48
+ /** The transform of a render of `area` with `options`' turn and size. */
49
+ function renderAreaTransform(area, options) {
50
+ const rotation = options.rotation ?? 0;
51
+ const { width, height } = renderSize(area, rotation, options.viewport ?? { kind: "scale" });
52
+ return renderTransform(area, rotation, width, height);
53
+ }
54
+ /**
55
+ * A render target's area with its sides in order, as the renderer takes it.
56
+ * `InvalidArg` for a rect without area, or one that isn't numbers.
57
+ */
58
+ function renderTargetArea(rect) {
59
+ const { x, y, width, height } = rect;
60
+ if (![
61
+ x,
62
+ y,
63
+ width,
64
+ height
65
+ ].every(Number.isFinite) || width === 0 || height === 0) throw new EngineError(EngineErrorCode.InvalidArg, "render rect must have positive area");
66
+ return {
67
+ x: Math.min(x, x + width),
68
+ y: Math.min(y, y + height),
69
+ width: Math.abs(width),
70
+ height: Math.abs(height)
71
+ };
72
+ }
73
+ /**
74
+ * The matrix that draws `area` of the page, turned clockwise by `rotation`,
75
+ * onto `width` × `height` pixels. The renderer draws with it.
76
+ */
77
+ function renderMatrix(area, rotation, width, height) {
78
+ const { x, y } = area;
79
+ const w = area.width;
80
+ const h = area.height;
81
+ switch (rotation) {
82
+ case 0: return [
83
+ width / w,
84
+ 0,
85
+ 0,
86
+ height / h,
87
+ -width / w * x,
88
+ -height / h * y
89
+ ];
90
+ case 90: return [
91
+ 0,
92
+ height / w,
93
+ -width / h,
94
+ 0,
95
+ width / h * (y + h),
96
+ -height / w * x
97
+ ];
98
+ case 180: return [
99
+ -width / w,
100
+ 0,
101
+ 0,
102
+ -height / h,
103
+ width / w * (x + w),
104
+ height / h * (y + h)
105
+ ];
106
+ case 270: return [
107
+ 0,
108
+ -height / w,
109
+ width / h,
110
+ 0,
111
+ -width / h * y,
112
+ height / w * (x + w)
113
+ ];
114
+ }
115
+ }
116
+ /** The transform of a render of `area`, turned by `rotation`, drawn onto `width` × `height` pixels. */
117
+ function renderTransform(area, rotation, width, height) {
118
+ const matrix = renderMatrix(area, rotation, width, height);
119
+ const inverse = inverseOf(matrix);
120
+ return {
121
+ width,
122
+ height,
123
+ matrix,
124
+ pageToPixels: ((value) => mapped(matrix, value)),
125
+ pixelsToPage: ((value) => mapped(inverse, value))
126
+ };
127
+ }
128
+ function inverseOf([a, b, c, d, e, f]) {
129
+ const det = a * d - b * c;
130
+ return [
131
+ d / det,
132
+ -b / det,
133
+ -c / det,
134
+ a / det,
135
+ (c * f - d * e) / det,
136
+ (b * e - a * f) / det
137
+ ];
138
+ }
139
+ function mappedPoint(m, { x, y }) {
140
+ return {
141
+ x: m[0] * x + m[2] * y + m[4],
142
+ y: m[1] * x + m[3] * y + m[5]
143
+ };
144
+ }
145
+ /** A point, a box or a quad through `m`. A box stays a box: every turn is a quarter turn. */
146
+ function mapped(m, value) {
147
+ if ("upperLeft" in value) return {
148
+ upperLeft: mappedPoint(m, value.upperLeft),
149
+ upperRight: mappedPoint(m, value.upperRight),
150
+ lowerLeft: mappedPoint(m, value.lowerLeft),
151
+ lowerRight: mappedPoint(m, value.lowerRight)
152
+ };
153
+ if ("width" in value) {
154
+ const from = mappedPoint(m, value);
155
+ const to = mappedPoint(m, {
156
+ x: value.x + value.width,
157
+ y: value.y + value.height
158
+ });
159
+ return {
160
+ x: Math.min(from.x, to.x),
161
+ y: Math.min(from.y, to.y),
162
+ width: Math.abs(to.x - from.x),
163
+ height: Math.abs(to.y - from.y)
164
+ };
165
+ }
166
+ return mappedPoint(m, value);
167
+ }
168
+ //#endregion
169
+ //#region src/geometry/pageSpace.ts
170
+ /** A point in the file's coordinates, in page space. `visible` is the page's visible box. */
171
+ function pagePointOf(point, visible) {
172
+ return {
173
+ x: point.x - visible.left,
174
+ y: visible.top - point.y
175
+ };
176
+ }
177
+ /** A page-space point in the file's coordinates. */
178
+ function pdfPointOf(point, visible) {
179
+ return {
180
+ x: point.x + visible.left,
181
+ y: visible.top - point.y
182
+ };
183
+ }
184
+ /** A normalized rect in the file's coordinates, in page space. */
185
+ function pageBoxOf(rect, visible) {
186
+ return {
187
+ x: rect.left - visible.left,
188
+ y: visible.top - rect.top,
189
+ width: rect.right - rect.left,
190
+ height: rect.top - rect.bottom
191
+ };
192
+ }
193
+ /** A page-space box in the file's coordinates, keys in `normalizePdfRect`'s order. */
194
+ function pdfRectOf(box, visible) {
195
+ const left = box.x + visible.left;
196
+ const top = visible.top - box.y;
197
+ return {
198
+ left,
199
+ right: left + box.width,
200
+ bottom: top - box.height,
201
+ top
202
+ };
203
+ }
204
+ function pageQuadOf(quad, visible) {
205
+ return {
206
+ upperLeft: pagePointOf(quad.upperLeft, visible),
207
+ upperRight: pagePointOf(quad.upperRight, visible),
208
+ lowerLeft: pagePointOf(quad.lowerLeft, visible),
209
+ lowerRight: pagePointOf(quad.lowerRight, visible)
210
+ };
211
+ }
212
+ function pdfQuadOf(quad, visible) {
213
+ return {
214
+ upperLeft: pdfPointOf(quad.upperLeft, visible),
215
+ upperRight: pdfPointOf(quad.upperRight, visible),
216
+ lowerLeft: pdfPointOf(quad.lowerLeft, visible),
217
+ lowerRight: pdfPointOf(quad.lowerRight, visible)
218
+ };
219
+ }
220
+ /** A page-space point flipped into y-up coordinates. */
221
+ const mirroredPoint = (point) => ({
222
+ x: point.x,
223
+ y: -point.y
224
+ });
225
+ /** A y-up point flipped back into page space. */
226
+ const unmirroredPoint = (point) => ({
227
+ x: point.x,
228
+ y: -point.y
229
+ });
230
+ /** A page-space box flipped into a y-up rect. */
231
+ const mirroredRect = (box) => ({
232
+ left: box.x,
233
+ right: box.x + box.width,
234
+ bottom: -(box.y + box.height),
235
+ top: -box.y
236
+ });
237
+ /** A y-up rect flipped back into a page-space box. */
238
+ const unmirroredBox = (rect) => ({
239
+ x: rect.left,
240
+ y: -rect.top,
241
+ width: rect.right - rect.left,
242
+ height: rect.top - rect.bottom
243
+ });
244
+ const mirroredQuad = (quad) => ({
245
+ upperLeft: mirroredPoint(quad.upperLeft),
246
+ upperRight: mirroredPoint(quad.upperRight),
247
+ lowerLeft: mirroredPoint(quad.lowerLeft),
248
+ lowerRight: mirroredPoint(quad.lowerRight)
249
+ });
250
+ const unmirroredQuad = (quad) => ({
251
+ upperLeft: unmirroredPoint(quad.upperLeft),
252
+ upperRight: unmirroredPoint(quad.upperRight),
253
+ lowerLeft: unmirroredPoint(quad.lowerLeft),
254
+ lowerRight: unmirroredPoint(quad.lowerRight)
255
+ });
256
+ //#endregion
257
+ //#region src/dto/PageGeometrySnapshot.ts
258
+ /** Narrowing guard: is this run the rotated (non-upright) variant? */
259
+ function isRotatedGeometryRun(run) {
260
+ return "rotation" in run;
261
+ }
262
+ /**
263
+ * Uniform oriented-cell view over either run variant, in the file's
264
+ * coordinates: the glyph's loose cell as a quad (synthesized from the box
265
+ * corners when the run is upright).
266
+ */
267
+ function glyphLooseQuad(run, index) {
268
+ if (isRotatedGeometryRun(run)) return run.glyphs[index].loose;
269
+ const b = run.glyphs[index].loose;
270
+ return {
271
+ upperLeft: {
272
+ x: b.left,
273
+ y: b.top
274
+ },
275
+ upperRight: {
276
+ x: b.right,
277
+ y: b.top
278
+ },
279
+ lowerLeft: {
280
+ x: b.left,
281
+ y: b.bottom
282
+ },
283
+ lowerRight: {
284
+ x: b.right,
285
+ y: b.bottom
286
+ }
287
+ };
288
+ }
289
+ /**
290
+ * Uniform AABB view over either run variant, in the file's coordinates: the
291
+ * glyph's loose box, or the
292
+ * enclosing bounds of its oriented cell when the run is rotated. The
293
+ * conservative envelope for consumers that genuinely want a box (culling,
294
+ * scroll targets) — never a substitute for handling orientation in geometry
295
+ * that gets drawn.
296
+ */
297
+ function glyphLooseBounds(run, index) {
298
+ if (isRotatedGeometryRun(run)) return pdfQuadBounds(run.glyphs[index].loose);
299
+ return run.glyphs[index].loose;
300
+ }
301
+ //#endregion
302
+ //#region src/promise/AbortError.ts
303
+ /**
304
+ * What a cancelled call rejects with: an `EngineError` with code `Aborted`,
305
+ * named `AbortError` like the DOM's, so both `EngineError.is(err,
306
+ * EngineErrorCode.Aborted)` and `err.name === 'AbortError'` recognise it.
307
+ */
308
+ var AbortError = class extends EngineError {
309
+ constructor(reason) {
310
+ super(EngineErrorCode.Aborted, reasonMessage(reason));
311
+ _defineProperty(this, "name", "AbortError");
312
+ _defineProperty(this, "reason", void 0);
313
+ this.reason = reason;
314
+ }
315
+ };
316
+ function isAbortError(value) {
317
+ return value instanceof AbortError || value?.name === "AbortError";
318
+ }
319
+ function reasonMessage(reason) {
320
+ if (reason == null) return "aborted";
321
+ if (typeof reason === "string") return reason;
322
+ if (reason instanceof Error) return reason.message || "aborted";
323
+ try {
324
+ return JSON.stringify(reason);
325
+ } catch {
326
+ return "aborted";
327
+ }
328
+ }
329
+ //#endregion
330
+ //#region src/resource/BinarySource.ts
331
+ function isBlob$1(value) {
332
+ return typeof Blob !== "undefined" && value instanceof Blob;
333
+ }
334
+ /**
335
+ * Copy a Uint8Array view into a fresh, exactly-sized ArrayBuffer the call
336
+ * owns. Always copies — even when the view spans its whole buffer — because
337
+ * the result may be transferred to a worker (detaching it), and the view is
338
+ * the caller's. Returning `view.buffer` here would let one transfer zero
339
+ * the caller's bytes (a stamp library's Uint8Array became single-use).
340
+ * Callers wanting a zero-copy hand-off need an explicit "give" form, as
341
+ * `pages.insert` has for bare ArrayBuffers; `BinarySource` has none.
342
+ */
343
+ function toOwnedArrayBuffer(view) {
344
+ const copy = new ArrayBuffer(view.byteLength);
345
+ new Uint8Array(copy).set(view);
346
+ return copy;
347
+ }
348
+ /**
349
+ * Resolve any accepted `BinarySource` form into a `WireResource`.
350
+ * Async because Blob bytes can only be read asynchronously.
351
+ */
352
+ async function resolveBinarySource(source) {
353
+ if (source instanceof Uint8Array) return { bytes: toOwnedArrayBuffer(source) };
354
+ if (isBlob$1(source)) {
355
+ const blobName = blobFileName(source);
356
+ return {
357
+ bytes: await source.arrayBuffer(),
358
+ mimeType: source.type || void 0,
359
+ ...blobName !== void 0 ? { name: blobName } : {}
360
+ };
361
+ }
362
+ const { data, mimeType, name } = source;
363
+ const inner = data instanceof Uint8Array ? {
364
+ bytes: toOwnedArrayBuffer(data),
365
+ mimeType: void 0,
366
+ name: void 0
367
+ } : {
368
+ bytes: await data.arrayBuffer(),
369
+ mimeType: data.type || void 0,
370
+ name: blobFileName(data)
371
+ };
372
+ const resolvedName = name ?? inner.name;
373
+ return {
374
+ bytes: inner.bytes,
375
+ ...mimeType ?? inner.mimeType ? { mimeType: mimeType ?? inner.mimeType } : {},
376
+ ...resolvedName !== void 0 ? { name: resolvedName } : {}
377
+ };
378
+ }
379
+ /** A browser `File` is a Blob with a `name` — pick it up so attachment
380
+ * drafts can pass a `File` directly without repeating the file name. */
381
+ function blobFileName(blob) {
382
+ const name = blob.name;
383
+ return typeof name === "string" && name.length > 0 ? name : void 0;
384
+ }
385
+ //#endregion
386
+ //#region src/resource/binaryMetadata.ts
387
+ /**
388
+ * Detect format (and intrinsic dimensions for rasters) of a binary buffer.
389
+ *
390
+ * @returns metadata, or `null` for unsupported/corrupt data
391
+ */
392
+ function sniffBinaryMetadata(buffer) {
393
+ if (buffer.byteLength < 4) return null;
394
+ const bytes = new Uint8Array(buffer);
395
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return parsePng(buffer);
396
+ if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return parseJpeg(buffer);
397
+ if (bytes[0] === 37 && bytes[1] === 80 && bytes[2] === 68 && bytes[3] === 70) return { mimeType: "application/pdf" };
398
+ return null;
399
+ }
400
+ function parsePng(buffer) {
401
+ if (buffer.byteLength < 24) return null;
402
+ const view = new DataView(buffer);
403
+ const width = view.getUint32(16);
404
+ const height = view.getUint32(20);
405
+ if (width === 0 || height === 0) return null;
406
+ return {
407
+ mimeType: "image/png",
408
+ width,
409
+ height
410
+ };
411
+ }
412
+ function parseJpeg(buffer) {
413
+ const bytes = new Uint8Array(buffer);
414
+ let offset = 2;
415
+ while (offset + 4 < bytes.byteLength) {
416
+ if (bytes[offset] !== 255) return null;
417
+ const marker = bytes[offset + 1];
418
+ if (marker >= 192 && marker <= 195) {
419
+ if (offset + 9 > bytes.byteLength) return null;
420
+ const view = new DataView(buffer, offset + 5);
421
+ const height = view.getUint16(0);
422
+ const width = view.getUint16(2);
423
+ if (width === 0 || height === 0) return null;
424
+ return {
425
+ mimeType: "image/jpeg",
426
+ width,
427
+ height
428
+ };
429
+ }
430
+ if (marker === 216 || marker === 217) offset += 2;
431
+ else if (marker === 255) offset += 1;
432
+ else {
433
+ const segLen = bytes[offset + 2] << 8 | bytes[offset + 3];
434
+ offset += 2 + segLen;
435
+ }
436
+ }
437
+ return null;
438
+ }
439
+ //#endregion
440
+ //#region src/annotation/resources.ts
441
+ /**
442
+ * Bytes travel beside an annotation's data, never inside it:
443
+ * `create(data, resources)` and `update(ref, patch, resources)`. A read
444
+ * returns the data only. Each role names what the bytes are for; a kind
445
+ * declares the roles it takes (`kinds/<kind>/declaration.ts`).
446
+ */
447
+ /**
448
+ * What bytes beside an annotation's data can be for:
449
+ *
450
+ * - `appearance`: what the box shows before the rotation the data describes,
451
+ * PNG, JPEG or a one-page PDF, scaled into the box as the data's `fit` says.
452
+ * A stamp requires it; on update it replaces the drawing.
453
+ * - `file`: an attached file's exact bytes; its name, MIME type and
454
+ * description are the data's `file`. A file attachment requires it; on
455
+ * update it replaces the bytes.
456
+ */
457
+ const ANNOTATION_RESOURCE_ROLE_NAMES = ["appearance", "file"];
458
+ /**
459
+ * Copy each resource into bytes the write owns: the local engine transfers
460
+ * them to its worker, which detaches the buffer, and the caller's bytes are
461
+ * only borrowed. An appearance that isn't PNG, JPEG or PDF fails here,
462
+ * before anything is sent; the format comes from the bytes.
463
+ */
464
+ async function resolveAnnotationResources(resources) {
465
+ if (!resources) return {};
466
+ for (const role of Object.keys(resources)) if (!ANNOTATION_RESOURCE_ROLE_NAMES.includes(role)) throw new EngineError(EngineErrorCode.InvalidArg, `unknown annotation resource '${role}'; expected ${ANNOTATION_RESOURCE_ROLE_NAMES.join(" or ")}`);
467
+ const wire = {};
468
+ for (const role of ANNOTATION_RESOURCE_ROLE_NAMES) {
469
+ const data = resources[role];
470
+ if (data === void 0) continue;
471
+ const bytes = await ownedBytes(data);
472
+ if (role === "appearance" && !sniffBinaryMetadata(bytes)) throw new EngineError(EngineErrorCode.InvalidArg, "the appearance resource must be PNG, JPEG or one-page PDF bytes");
473
+ wire[role] = bytes;
474
+ }
475
+ return wire;
476
+ }
477
+ /**
478
+ * A file-attachment create's `file`, completed from its `file` resource when
479
+ * that is a `File` or a `Blob`: the name a `File` brings and the type a
480
+ * `Blob` does, where the data leaves them out. A `Blob` with no type gives
481
+ * none. Other drafts, and bare bytes, pass as they are.
482
+ */
483
+ function withFileFromResource(draft, resources) {
484
+ const data = resources?.file;
485
+ if (draft.subtype !== "file-attachment" || !isBlob(data)) return draft;
486
+ const name = draft.file?.name ?? blobFileName(data);
487
+ if (name === void 0) return draft;
488
+ return {
489
+ ...draft,
490
+ file: {
491
+ ...draft.file,
492
+ name,
493
+ mimeType: draft.file?.mimeType !== void 0 ? draft.file.mimeType : data.type || null
494
+ }
495
+ };
496
+ }
497
+ function hasAnnotationResources(resources) {
498
+ return ANNOTATION_RESOURCE_ROLE_NAMES.some((role) => resources[role] !== void 0);
499
+ }
500
+ /** The buffers of a write's resources: what a worker transfer list hands over. */
501
+ function annotationResourceBuffers(resources) {
502
+ return Object.values(resources).filter((bytes) => bytes !== void 0);
503
+ }
504
+ /**
505
+ * Check a write's resources against its kind: a role the kind doesn't take
506
+ * is refused, and so is a create without a role the kind requires.
507
+ */
508
+ function assertAnnotationResources(subtype, resources, write) {
509
+ const roles = ANNOTATION_RESOURCE_ROLES[subtype] ?? {};
510
+ for (const role of ANNOTATION_RESOURCE_ROLE_NAMES) {
511
+ const given = resources?.[role] !== void 0;
512
+ if (given && roles[role] === void 0) throw new EngineError(EngineErrorCode.InvalidArg, `a ${subtype} annotation takes no '${role}' resource`);
513
+ if (!given && write === "create" && roles[role] === "required") throw new EngineError(EngineErrorCode.InvalidArg, `creating a ${subtype} annotation needs its '${role}' resource`);
514
+ }
515
+ }
516
+ function isBlob(value) {
517
+ return typeof Blob !== "undefined" && value instanceof Blob;
518
+ }
519
+ async function ownedBytes(data) {
520
+ if (isBlob(data)) return data.arrayBuffer();
521
+ const copy = new ArrayBuffer(data.byteLength);
522
+ new Uint8Array(copy).set(data);
523
+ return copy;
524
+ }
525
+ //#endregion
526
+ //#region src/transfer/bundleLimits.ts
527
+ const DEFAULT_BUNDLE_LIMITS = Object.freeze({
528
+ bundleBytes: 50 * 1024 * 1024,
529
+ manifestBytes: 16 * 1024 * 1024,
530
+ items: 1e4,
531
+ pages: 2e3,
532
+ resources: 2e3,
533
+ resourceBytes: 50 * 1024 * 1024,
534
+ imagePixels: 4e7
535
+ });
536
+ /** The bytes of a bundle's manifest: its pages and rows (`{ pages, items }`) as UTF-8 JSON. */
537
+ function manifestBytesOf(rows) {
538
+ return new TextEncoder().encode(JSON.stringify(rows)).length;
539
+ }
540
+ /** Refuse `value` when it is past the limit named `limit`. */
541
+ function assertWithinLimit(kind, limits, limit, value) {
542
+ const max = limits[limit];
543
+ if (value <= max) return;
544
+ throw new EngineError(EngineErrorCode.PayloadTooLarge, `the ${kind} bundle is past its ${limit} limit: ${value} > ${max}`, { details: {
545
+ limit,
546
+ max,
547
+ value
548
+ } });
549
+ }
550
+ //#endregion
551
+ //#region src/transfer/codec.ts
552
+ /** A resource's entry in the file besides its base64: its id, quotes and separators. */
553
+ const RESOURCE_ENTRY_CHARACTERS = 81;
554
+ /** The file's own keys and punctuation. */
555
+ const ENVELOPE_CHARACTERS = 256;
556
+ const A_BUNDLE = {
557
+ annotation: "an annotation bundle",
558
+ form: "a form bundle"
559
+ };
560
+ /**
561
+ * The JSON file of one family's bundles: a bundle whole, to store or
562
+ * download. HTTP carries bundles as multipart instead, without the third
563
+ * base64 adds.
564
+ */
565
+ function bundleCodec(spec) {
566
+ const notABundle = (cause) => new EngineError(EngineErrorCode.InvalidArg, `the file is not ${A_BUNDLE[spec.kind]}`, cause === void 0 ? void 0 : { cause });
567
+ return {
568
+ stringify(bundle, options) {
569
+ const limits = options?.limits ?? DEFAULT_BUNDLE_LIMITS;
570
+ const sizes = /* @__PURE__ */ new Map();
571
+ for (const [id, bytes] of Object.entries(bundle.resources)) sizes.set(id, bytes.length);
572
+ spec.assertManifest(bundle, sizes, limits);
573
+ const file = {};
574
+ for (const key of spec.keys) file[key] = bundle[key];
575
+ const resources = {};
576
+ for (const [id, bytes] of Object.entries(bundle.resources)) resources[id] = toBase64(bytes);
577
+ file.resources = resources;
578
+ return JSON.stringify(file);
579
+ },
580
+ async parse(text, options) {
581
+ const limits = options?.limits ?? DEFAULT_BUNDLE_LIMITS;
582
+ const maxLength = Math.ceil(limits.bundleBytes * 4 / 3) + limits.resources * RESOURCE_ENTRY_CHARACTERS + ENVELOPE_CHARACTERS;
583
+ if (text.length > maxLength) throw new EngineError(EngineErrorCode.PayloadTooLarge, `the file is ${text.length} characters, more than a bundle within ${limits.bundleBytes} bytes can be`, { details: {
584
+ limit: "bundleBytes",
585
+ max: limits.bundleBytes,
586
+ fileLength: text.length
587
+ } });
588
+ let file;
589
+ try {
590
+ file = JSON.parse(text);
591
+ } catch (error) {
592
+ throw notABundle(error);
593
+ }
594
+ const encoded = typeof file === "object" && file !== null ? file.resources : null;
595
+ if (typeof encoded !== "object" || encoded === null || Array.isArray(encoded)) throw notABundle();
596
+ const sizes = /* @__PURE__ */ new Map();
597
+ for (const [id, value] of Object.entries(encoded)) {
598
+ const size = typeof value === "string" ? decodedLengthOf(value) : null;
599
+ if (size === null) throw new EngineError(EngineErrorCode.InvalidArg, `resource ${id} is not base64`);
600
+ sizes.set(id, size);
601
+ }
602
+ spec.assertManifest(file, sizes, limits);
603
+ const resources = {};
604
+ for (const [id, value] of Object.entries(encoded)) try {
605
+ resources[id] = fromBase64(value);
606
+ } catch (error) {
607
+ throw new EngineError(EngineErrorCode.InvalidArg, `resource ${id} is not base64`, { cause: error });
608
+ }
609
+ await assertResourceIds(spec.kind, resources);
610
+ const bundle = {};
611
+ for (const key of spec.keys) bundle[key] = file[key];
612
+ bundle.resources = resources;
613
+ return bundle;
614
+ }
615
+ };
616
+ }
617
+ /** The manifest's keys, in the order a file writes them. */
618
+ const ANNOTATION_BUNDLE_KEYS = [
619
+ "format",
620
+ "version",
621
+ "pages",
622
+ "items"
623
+ ];
624
+ const BUNDLE_FIELDS = [...ANNOTATION_BUNDLE_KEYS, "resources"];
625
+ const ITEM_FIELDS = ["data", "resources"];
626
+ /**
627
+ * Check a bundle before anything uses it, the checks cheapest first: its
628
+ * format, its counts and sizes against `limits` (`PayloadTooLarge`), its
629
+ * shape and references (`InvalidArg`), then every resource against its id.
630
+ */
631
+ async function assertAnnotationBundle(bundle, limits = DEFAULT_BUNDLE_LIMITS) {
632
+ const resources = isRecord(bundle?.resources) ? bundle.resources : invalidBundle("annotation", "no resources");
633
+ const sizes = /* @__PURE__ */ new Map();
634
+ for (const [id, bytes] of Object.entries(resources)) {
635
+ if (!(bytes instanceof Uint8Array)) invalidBundle("annotation", `resource ${id} is not bytes`);
636
+ sizes.set(id, bytes.length);
637
+ }
638
+ assertAnnotationBundleManifest(bundle, sizes, limits);
639
+ await assertResourceIds("annotation", resources);
640
+ }
641
+ /**
642
+ * The checks that need no resource bytes, only their sizes by id: format
643
+ * and version, counts, sizes, the shape of pages and items, and that items
644
+ * and resources name each other exactly.
645
+ */
646
+ function assertAnnotationBundleManifest(manifest, resourceSizes, limits) {
647
+ if (!isRecord(manifest)) invalidBundle("annotation", "not an annotation bundle");
648
+ assertKnownFields("annotation", "the bundle", manifest, BUNDLE_FIELDS);
649
+ if (manifest.format !== "embedpdf/annotations") invalidBundle("annotation", `unknown format ${JSON.stringify(manifest.format)}`);
650
+ if (manifest.version !== 1) invalidBundle("annotation", `unknown version ${JSON.stringify(manifest.version)}`);
651
+ const { pages, items } = manifest;
652
+ if (!Array.isArray(pages)) invalidBundle("annotation", "pages is not a list");
653
+ if (!Array.isArray(items)) invalidBundle("annotation", "items is not a list");
654
+ assertWithinLimit("annotation", limits, "items", items.length);
655
+ assertWithinLimit("annotation", limits, "pages", pages.length);
656
+ assertWithinLimit("annotation", limits, "resources", resourceSizes.size);
657
+ let bundleBytes = manifestBytesOf({
658
+ pages,
659
+ items
660
+ });
661
+ assertWithinLimit("annotation", limits, "manifestBytes", bundleBytes);
662
+ for (const size of resourceSizes.values()) {
663
+ assertWithinLimit("annotation", limits, "resourceBytes", size);
664
+ bundleBytes += size;
665
+ }
666
+ assertWithinLimit("annotation", limits, "bundleBytes", bundleBytes);
667
+ const pageKeys = assertBundlePages("annotation", pages);
668
+ const named = /* @__PURE__ */ new Set();
669
+ items.forEach((item, index) => {
670
+ if (!isRecord(item) || !isRecord(item.data) || typeof item.data.subtype !== "string") invalidBundle("annotation", `item ${index} has no annotation data`);
671
+ assertKnownFields("annotation", `item ${index}`, item, ITEM_FIELDS);
672
+ const ref = item.data.ref;
673
+ const page = isRecord(ref) ? ref.page : void 0;
674
+ if (!isBundlePageRef(page) || !pageKeys.has(encodePageKey(page))) invalidBundle("annotation", `item ${index} is on a page the bundle doesn't list`);
675
+ assertRowResources("annotation", `item ${index}`, item.resources, ANNOTATION_RESOURCE_ROLE_NAMES, resourceSizes, named);
676
+ });
677
+ assertResourcesNamed("annotation", "item", resourceSizes, named);
678
+ }
679
+ //#endregion
680
+ //#region src/transfer/AnnotationTransfer.ts
681
+ /**
682
+ * An annotation bundle as one JSON file, each resource in base64: a bundle
683
+ * whole, to store or download. HTTP carries bundles as multipart instead,
684
+ * without the third base64 adds.
685
+ */
686
+ const AnnotationTransfer = bundleCodec({
687
+ kind: "annotation",
688
+ keys: ANNOTATION_BUNDLE_KEYS,
689
+ assertManifest: assertAnnotationBundleManifest
690
+ });
691
+ //#endregion
692
+ //#region src/dto/Attachment.ts
693
+ /** The ref of an attachment whose name-tree key you already hold. */
694
+ function toAttachmentRef(key) {
695
+ return {
696
+ kind: "key",
697
+ key
698
+ };
699
+ }
700
+ //#endregion
701
+ //#region src/annotation/appearanceTurn.ts
702
+ /** The kinds drawn about a box that can turn. */
703
+ const BOX_KINDS = /* @__PURE__ */ new Set([
704
+ "square",
705
+ "circle",
706
+ "free-text",
707
+ "stamp",
708
+ "caret"
709
+ ]);
710
+ /**
711
+ * The turn an annotation's appearance raster is drawn without, degrees
712
+ * clockwise, or `null` when the raster is drawn as the page shows it. A box
713
+ * kind drawn turned whose drawing stays inside the turned box (its `rect` is
714
+ * the upright box around it) renders upright over its `box`, for the
715
+ * consumer to turn about the middle of `box`: a new turn needs no new
716
+ * raster. A callout (its line isn't turned) and a drawing that reaches past
717
+ * its box (a cloudy border's bumps) render as the page shows them, placed by
718
+ * `rect`.
719
+ */
720
+ function pdfAppearanceTurnOf(annotation) {
721
+ const { subtype, rect, box, rotation, intent } = annotation;
722
+ if (!BOX_KINDS.has(subtype) || !box || !rotation) return null;
723
+ if (subtype === "free-text" && intent === "free-text-callout") return null;
724
+ return isSamePdfRect(pdfRectTurnedBounds(box, rotation), rect) ? rotation : null;
725
+ }
726
+ //#endregion
727
+ //#region src/annotation/resolve/applyAnnotationPatch.ts
728
+ /**
729
+ * A resolved patch's fields as a read spells them. Most fields read as they
730
+ * are written; these take a different shape on the way in.
731
+ */
732
+ function readValueOf(name, value, current) {
733
+ if (value === null) return null;
734
+ switch (name) {
735
+ case "reply": {
736
+ const reply = value;
737
+ return {
738
+ to: reply.to,
739
+ type: reply.type ?? "reply"
740
+ };
741
+ }
742
+ case "richText": {
743
+ const input = value;
744
+ const read = current;
745
+ const body = input.body ? {
746
+ ...DEFAULT_RICH_TEXT_BODY,
747
+ ...input.body
748
+ } : read?.body ?? DEFAULT_RICH_TEXT_BODY;
749
+ return {
750
+ body: {
751
+ ...body,
752
+ color: body.color.toLowerCase()
753
+ },
754
+ paragraphs: input.paragraphs.map((paragraph) => ({
755
+ ...paragraph,
756
+ runs: paragraph.runs.filter((run) => run.text !== "").map((run) => run.style?.color ? {
757
+ ...run,
758
+ style: {
759
+ ...run.style,
760
+ color: run.style.color.toLowerCase()
761
+ }
762
+ } : run)
763
+ }))
764
+ };
765
+ }
766
+ case "file": {
767
+ const written = value;
768
+ return {
769
+ ...current ?? {
770
+ size: null,
771
+ checksum: null,
772
+ createdAt: null,
773
+ modifiedAt: null
774
+ },
775
+ name: written.name,
776
+ mimeType: written.mimeType ?? null,
777
+ description: written.description ?? null
778
+ };
779
+ }
780
+ default: return value;
781
+ }
782
+ }
783
+ /**
784
+ * `current` with a resolved patch's data fields in place, each as a read
785
+ * spells it. Fields the engine works out or stamps itself (a drawn kind's
786
+ * `rect`, the modified date and author) keep
787
+ * the value `current` has: the engine's answer brings the new one. Works in
788
+ * either space: it only moves values by field name.
789
+ */
790
+ function applyResolvedPatch(current, resolved) {
791
+ const owners = kindFactsOf(current.subtype)?.owners ?? {};
792
+ const read = current;
793
+ const next = { ...read };
794
+ for (const [name, value] of Object.entries(resolved)) {
795
+ if (name === "subtype" || value === void 0) continue;
796
+ if (owners[name] !== "data") continue;
797
+ next[name] = readValueOf(name, value, read[name]);
798
+ }
799
+ return {
800
+ ...next,
801
+ ...followingReads(current, resolved)
802
+ };
803
+ }
804
+ /**
805
+ * Fields the engine keeps as found, but rewrites when a data field they
806
+ * mirror changes: a link's `target` is the root of its `/A`, so a new target
807
+ * is a new `actions.activate` (and no target, none).
808
+ */
809
+ function followingReads(current, resolved) {
810
+ if (current.subtype !== "link" || resolved.subtype !== "link") return {};
811
+ const target = resolved.target;
812
+ if (target === void 0 || target !== null && target.kind !== "uri" && target.kind !== "goto") return {};
813
+ const { activate: _replaced, ...others } = current.actions ?? {};
814
+ const actions = target === null ? others : {
815
+ ...others,
816
+ activate: actionTreeOf(target)
817
+ };
818
+ return { actions: Object.keys(actions).length > 0 ? actions : null };
819
+ }
820
+ /** The action tree the engine writes for a link target: one URI or GoTo action. */
821
+ function actionTreeOf(target) {
822
+ return {
823
+ root: target.kind === "uri" ? {
824
+ type: "uri",
825
+ subtype: "URI",
826
+ uri: target.uri,
827
+ isMap: false,
828
+ next: []
829
+ } : {
830
+ type: "goto",
831
+ subtype: "GoTo",
832
+ destination: target.destination,
833
+ next: []
834
+ },
835
+ incomplete: false,
836
+ warningFlags: 0,
837
+ warnings: []
838
+ };
839
+ }
840
+ //#endregion
841
+ //#region src/annotation/defaults.ts
842
+ const RED = "#ff0000";
843
+ const BLACK = "#000000";
844
+ const YELLOW = "#ffff00";
845
+ /** Every kind: printed (as Acrobat creates them), no other flag set, normal blending. A popup doesn't print. */
846
+ const BASE = {
847
+ invisible: false,
848
+ hidden: false,
849
+ print: true,
850
+ noZoom: false,
851
+ noRotate: false,
852
+ noView: false,
853
+ readOnly: false,
854
+ locked: false,
855
+ toggleNoView: false,
856
+ lockedContents: false,
857
+ blendMode: "normal"
858
+ };
859
+ /** Shapes and lines: a solid red line, one point wide. */
860
+ const STROKE = {
861
+ ...BASE,
862
+ color: RED,
863
+ opacity: 1,
864
+ strokeWidth: 1,
865
+ borderStyle: "solid"
866
+ };
867
+ const NO_ENDINGS = {
868
+ start: "none",
869
+ end: "none"
870
+ };
871
+ /** Text markup other than a highlight: black. */
872
+ const MARKUP = {
873
+ ...BASE,
874
+ color: BLACK,
875
+ opacity: 1
876
+ };
877
+ const ANNOTATION_DEFAULTS = {
878
+ square: STROKE,
879
+ circle: STROKE,
880
+ line: {
881
+ ...STROKE,
882
+ lineEndings: NO_ENDINGS,
883
+ captionPosition: "inline"
884
+ },
885
+ polygon: STROKE,
886
+ polyline: {
887
+ ...STROKE,
888
+ lineEndings: NO_ENDINGS
889
+ },
890
+ ink: STROKE,
891
+ highlight: {
892
+ ...BASE,
893
+ blendMode: "multiply",
894
+ color: YELLOW,
895
+ opacity: 1
896
+ },
897
+ underline: MARKUP,
898
+ squiggly: MARKUP,
899
+ strikeout: MARKUP,
900
+ caret: {
901
+ ...BASE,
902
+ color: RED,
903
+ opacity: 1
904
+ },
905
+ "free-text": {
906
+ ...BASE,
907
+ contents: "",
908
+ intent: "free-text",
909
+ fontFamily: "helvetica",
910
+ fontSize: 12,
911
+ textAlign: "left",
912
+ verticalAlign: "top",
913
+ color: BLACK,
914
+ fontColor: BLACK,
915
+ opacity: 1,
916
+ strokeWidth: 1,
917
+ borderStyle: "solid"
918
+ },
919
+ text: {
920
+ ...BASE,
921
+ icon: "note",
922
+ open: false,
923
+ color: YELLOW,
924
+ opacity: 1
925
+ },
926
+ "file-attachment": {
927
+ ...BASE,
928
+ icon: "paperclip",
929
+ color: YELLOW,
930
+ opacity: 1
931
+ },
932
+ link: BASE,
933
+ redact: {
934
+ ...BASE,
935
+ quadPoints: [],
936
+ color: RED,
937
+ opacity: 1,
938
+ repeat: false,
939
+ fontFamily: "helvetica",
940
+ fontSize: 12,
941
+ fontColor: BLACK,
942
+ textAlign: "left"
943
+ },
944
+ stamp: {
945
+ ...BASE,
946
+ fit: "contain",
947
+ opacity: 1
948
+ },
949
+ widget: {
950
+ ...BASE,
951
+ strokeWidth: 1,
952
+ borderStyle: "solid",
953
+ textAlign: "left"
954
+ },
955
+ popup: {
956
+ ...BASE,
957
+ print: false,
958
+ open: false
959
+ }
960
+ };
961
+ /** What a create of `subtype` reads back for each data field it leaves out. */
962
+ function annotationDefaultsOf(subtype) {
963
+ return subtype === "unsupported" ? {} : ANNOTATION_DEFAULTS[subtype];
964
+ }
965
+ //#endregion
966
+ //#region src/pageSpace/destinations.ts
967
+ const boxFor = (page, box) => typeof box === "function" ? box(page) : box;
968
+ /** An axis value that may keep the current one (`null`) or be left out. */
969
+ const across = (value, convert) => value == null ? value : convert(value);
970
+ /**
971
+ * A destination in the file's coordinates, in page space: for one read by
972
+ * another PDF tool, before handing it to the viewer. `box` is the `pdfCropBox`
973
+ * of the page it goes to.
974
+ */
975
+ function pageDestinationOf(destination, box) {
976
+ const { left: boxLeft, top: boxTop } = boxFor(destination.page, box);
977
+ const x = (left) => across(left, (value) => value - boxLeft);
978
+ const y = (top) => across(top, (value) => boxTop - value);
979
+ switch (destination.kind) {
980
+ case "xyz": {
981
+ const { left, top, ...rest } = destination;
982
+ return {
983
+ ...rest,
984
+ ..."left" in destination ? { x: x(left) } : {},
985
+ ..."top" in destination ? { y: y(top) } : {}
986
+ };
987
+ }
988
+ case "fitH":
989
+ case "fitBH": {
990
+ const { top, ...rest } = destination;
991
+ return {
992
+ ...rest,
993
+ ..."top" in destination ? { y: y(top) } : {}
994
+ };
995
+ }
996
+ case "fitV":
997
+ case "fitBV": {
998
+ const { left, ...rest } = destination;
999
+ return {
1000
+ ...rest,
1001
+ ..."left" in destination ? { x: x(left) } : {}
1002
+ };
1003
+ }
1004
+ case "fitR": {
1005
+ const { left, bottom, right, top, ...rest } = destination;
1006
+ const lower = Math.min(bottom, top);
1007
+ const upper = Math.max(bottom, top);
1008
+ return {
1009
+ ...rest,
1010
+ x: Math.min(left, right) - boxLeft,
1011
+ y: boxTop - upper,
1012
+ width: Math.abs(right - left),
1013
+ height: upper - lower
1014
+ };
1015
+ }
1016
+ case "fit":
1017
+ case "fitB": return destination;
1018
+ }
1019
+ }
1020
+ /**
1021
+ * A page-space destination in the file's coordinates: for handing one to
1022
+ * another PDF tool. `box` is the `pdfCropBox` of the page it goes to.
1023
+ */
1024
+ function pdfDestinationOf(destination, box) {
1025
+ const { left: boxLeft, top: boxTop } = boxFor(destination.page, box);
1026
+ const left = (x) => across(x, (value) => value + boxLeft);
1027
+ const top = (y) => across(y, (value) => boxTop - value);
1028
+ switch (destination.kind) {
1029
+ case "xyz": {
1030
+ const { x, y, ...rest } = destination;
1031
+ return {
1032
+ ...rest,
1033
+ ..."x" in destination ? { left: left(x) } : {},
1034
+ ..."y" in destination ? { top: top(y) } : {}
1035
+ };
1036
+ }
1037
+ case "fitH":
1038
+ case "fitBH": {
1039
+ const { y, ...rest } = destination;
1040
+ return {
1041
+ ...rest,
1042
+ ..."y" in destination ? { top: top(y) } : {}
1043
+ };
1044
+ }
1045
+ case "fitV":
1046
+ case "fitBV": {
1047
+ const { x, ...rest } = destination;
1048
+ return {
1049
+ ...rest,
1050
+ ..."x" in destination ? { left: left(x) } : {}
1051
+ };
1052
+ }
1053
+ case "fitR": {
1054
+ const { x, y, width, height, ...rest } = destination;
1055
+ const upper = boxTop - y;
1056
+ return {
1057
+ ...rest,
1058
+ left: x + boxLeft,
1059
+ bottom: upper - height,
1060
+ right: x + boxLeft + width,
1061
+ top: upper
1062
+ };
1063
+ }
1064
+ case "fit":
1065
+ case "fitB": return destination;
1066
+ }
1067
+ }
1068
+ function mapLinkTarget(target, convert) {
1069
+ return target.kind === "goto" ? {
1070
+ ...target,
1071
+ destination: convert(target.destination)
1072
+ } : target;
1073
+ }
1074
+ function mapActionNode(node, convert) {
1075
+ const next = node.next.map((child) => mapActionNode(child, convert));
1076
+ return node.type === "goto" ? {
1077
+ ...node,
1078
+ next,
1079
+ destination: convert(node.destination)
1080
+ } : {
1081
+ ...node,
1082
+ next
1083
+ };
1084
+ }
1085
+ function mapActionTree(tree, convert) {
1086
+ return {
1087
+ ...tree,
1088
+ root: tree.root ? mapActionNode(tree.root, convert) : null
1089
+ };
1090
+ }
1091
+ /** An action to write, its destinations converted. */
1092
+ function mapActionWrite(action, convert) {
1093
+ const next = action.next?.map((child) => mapActionWrite(child, convert));
1094
+ const converted = action.type === "goto" ? {
1095
+ ...action,
1096
+ destination: convert(action.destination)
1097
+ } : action;
1098
+ return next ? {
1099
+ ...converted,
1100
+ next
1101
+ } : converted;
1102
+ }
1103
+ /**
1104
+ * A record of triggers as a read has them (trees) or a write takes them
1105
+ * (actions to write, `null` to remove one), its destinations converted.
1106
+ */
1107
+ function mapTriggerActions(triggers, convert) {
1108
+ return Object.fromEntries(Object.entries(triggers).map(([trigger, value]) => {
1109
+ if (value == null) return [trigger, value];
1110
+ return [trigger, "root" in value ? mapActionTree(value, convert) : mapActionWrite(value, convert)];
1111
+ }));
1112
+ }
1113
+ /** Every action tree in a record of triggers (an annotation's, a page's or a field's). */
1114
+ function mapTriggers(triggers, convert) {
1115
+ return Object.fromEntries(Object.entries(triggers).map(([trigger, tree]) => [trigger, tree ? mapActionTree(tree, convert) : tree]));
1116
+ }
1117
+ function mapAnnotationActions(actions, convert) {
1118
+ return mapTriggers(actions, convert);
1119
+ }
1120
+ function mapPageActions(actions, convert) {
1121
+ return mapTriggers(actions, convert);
1122
+ }
1123
+ function mapFieldActions(actions, convert) {
1124
+ return mapTriggers(actions, convert);
1125
+ }
1126
+ function mapDocumentActions(snapshot, convert) {
1127
+ const { nameTreeScripts, openAction, openDestination, ...triggers } = snapshot;
1128
+ return {
1129
+ ...mapTriggers(triggers, convert),
1130
+ nameTreeScripts: nameTreeScripts.map((script) => ({
1131
+ ...script,
1132
+ action: mapActionTree(script.action, convert)
1133
+ })),
1134
+ openAction: openAction ? mapActionTree(openAction, convert) : null,
1135
+ ..."openDestination" in snapshot ? { openDestination: openDestination ? convert(openDestination) : openDestination } : {}
1136
+ };
1137
+ }
1138
+ //#endregion
1139
+ //#region src/pageSpace/measure.ts
1140
+ const withOrigin = (measure, convert) => measure.subtype === "rectilinear" && measure.origin ? {
1141
+ ...measure,
1142
+ origin: convert(measure.origin)
1143
+ } : measure;
1144
+ /** A scale in page space: its origin (`/O`) is a place on the page. */
1145
+ function pageMeasureOf(measure, visible) {
1146
+ return withOrigin(measure, (origin) => pagePointOf(origin, visible));
1147
+ }
1148
+ /** A page-space scale in the file's coordinates. */
1149
+ function pdfMeasureOf(measure, visible) {
1150
+ return withOrigin(measure, (origin) => pdfPointOf(origin, visible));
1151
+ }
1152
+ /** A page's measurement viewports in page space. */
1153
+ function pageViewportsOf(viewports, visible) {
1154
+ return viewports.map((viewport) => ({
1155
+ ...viewport,
1156
+ bbox: pageBoxOf(viewport.bbox, visible),
1157
+ measure: viewport.measure ? pageMeasureOf(viewport.measure, visible) : null
1158
+ }));
1159
+ }
1160
+ //#endregion
1161
+ //#region src/pageSpace/annotations.ts
1162
+ const TO_PAGE = {
1163
+ length: (value) => value,
1164
+ point: (point, { visible }) => pagePointOf(point, visible),
1165
+ points: (points, { visible }) => points.map((point) => pagePointOf(point, visible)),
1166
+ strokes: (strokes, { visible }) => strokes.map((stroke) => stroke.map((point) => pagePointOf(point, visible))),
1167
+ box: (rect, { visible }) => pageBoxOf(rect, visible),
1168
+ quads: (quads, { visible }) => quads.map((quad) => pageQuadOf(quad, visible)),
1169
+ linePoints: (line, { visible }) => ({
1170
+ start: pagePointOf(line.start, visible),
1171
+ end: pagePointOf(line.end, visible)
1172
+ }),
1173
+ calloutLine: (points, { visible }) => points.map((point) => pagePointOf(point, visible)),
1174
+ measure: (measure, { visible }) => pageMeasureOf(measure, visible),
1175
+ linkTarget: (target, { boxOf }) => mapLinkTarget(target, (destination) => pageDestinationOf(destination, boxOf)),
1176
+ actions: (actions, { boxOf }) => mapTriggerActions(actions, (destination) => pageDestinationOf(destination, boxOf))
1177
+ };
1178
+ const TO_PDF = {
1179
+ length: (value) => value,
1180
+ point: (point, { visible }) => pdfPointOf(point, visible),
1181
+ points: (points, { visible }) => points.map((point) => pdfPointOf(point, visible)),
1182
+ strokes: (strokes, { visible }) => strokes.map((stroke) => stroke.map((point) => pdfPointOf(point, visible))),
1183
+ box: (box, { visible }) => pdfRectOf(box, visible),
1184
+ quads: (quads, { visible }) => quads.map((quad) => pdfQuadOf(quad, visible)),
1185
+ linePoints: (line, { visible }) => ({
1186
+ start: pdfPointOf(line.start, visible),
1187
+ end: pdfPointOf(line.end, visible)
1188
+ }),
1189
+ calloutLine: (points, { visible }) => points.map((point) => pdfPointOf(point, visible)),
1190
+ measure: (measure, { visible }) => pdfMeasureOf(measure, visible),
1191
+ linkTarget: (target, { boxOf }) => mapLinkTarget(target, (destination) => pdfDestinationOf(destination, boxOf)),
1192
+ actions: (actions, { boxOf }) => mapTriggerActions(actions, (destination) => pdfDestinationOf(destination, boxOf))
1193
+ };
1194
+ /**
1195
+ * The measured fields of a kind; a kind the engine doesn't know has the base
1196
+ * fields. An update may leave out its kind: a field's name says what it
1197
+ * holds, the same in every kind.
1198
+ */
1199
+ const spacesOf = (subtype) => subtype === void 0 ? ANNOTATION_FIELD_SPACES_BY_NAME : ANNOTATION_FIELD_SPACES[subtype] ?? ANNOTATION_FIELD_SPACES.unsupported;
1200
+ function convertFields(subtype, value, frame, converters) {
1201
+ const spaces = spacesOf(subtype);
1202
+ return Object.fromEntries(Object.entries(value).map(([name, field]) => {
1203
+ const space = spaces[name];
1204
+ if (!space || field == null) return [name, field];
1205
+ const converted = converters[space](field, frame);
1206
+ if (converters === TO_PDF && hasNaN(converted)) throw notInPageSpace(name, space);
1207
+ return [name, converted];
1208
+ }));
1209
+ }
1210
+ /** Whether a converted value has a number that isn't one: a place not given in page space. */
1211
+ function hasNaN(value) {
1212
+ if (typeof value === "number") return Number.isNaN(value);
1213
+ if (typeof value !== "object" || value === null) return false;
1214
+ return Object.values(value).some(hasNaN);
1215
+ }
1216
+ function notInPageSpace(name, space) {
1217
+ const shape = space === "box" ? "{ x, y, width, height }" : "points as { x, y }";
1218
+ return new EngineError(EngineErrorCode.InvalidArg, `field '${name}': expected page space, ${shape} from the page's top-left`, { details: { field: name } });
1219
+ }
1220
+ /** A read in page space. `visible` is its page's visible box, in the file's coordinates. */
1221
+ function pageAnnotationOf(annotation, visible, boxOf) {
1222
+ return convertFields(annotation.subtype, annotation, {
1223
+ visible,
1224
+ boxOf
1225
+ }, TO_PAGE);
1226
+ }
1227
+ /** A page-space read in the file's coordinates. */
1228
+ function pdfAnnotationOf(annotation, visible, boxOf) {
1229
+ return convertFields(annotation.subtype, annotation, {
1230
+ visible,
1231
+ boxOf
1232
+ }, TO_PDF);
1233
+ }
1234
+ /** A page-space create in the file's coordinates. */
1235
+ function pdfAnnotationDraftOf(draft, visible, boxOf) {
1236
+ return convertFields(draft.subtype, draft, {
1237
+ visible,
1238
+ boxOf
1239
+ }, TO_PDF);
1240
+ }
1241
+ /** A create in the file's coordinates, in page space. */
1242
+ function pageAnnotationDraftOf(draft, visible, boxOf) {
1243
+ return convertFields(draft.subtype, draft, {
1244
+ visible,
1245
+ boxOf
1246
+ }, TO_PAGE);
1247
+ }
1248
+ /**
1249
+ * A page-space update in the file's coordinates. An update may leave out its
1250
+ * `subtype`; its fields' names say what they hold.
1251
+ */
1252
+ function pdfAnnotationPatchOf(patch, visible, boxOf) {
1253
+ return convertFields(patch.subtype, patch, {
1254
+ visible,
1255
+ boxOf
1256
+ }, TO_PDF);
1257
+ }
1258
+ /** An update in the file's coordinates, in page space. */
1259
+ function pageAnnotationPatchOf(patch, visible, boxOf) {
1260
+ return convertFields(patch.subtype, patch, {
1261
+ visible,
1262
+ boxOf
1263
+ }, TO_PAGE);
1264
+ }
1265
+ //#endregion
1266
+ //#region src/text/layout.ts
1267
+ /**
1268
+ * The canonical text-interaction layout — the one place glyph geometry
1269
+ * becomes hit targets, word/line ranges, and visual-line segments.
1270
+ *
1271
+ * Selection owns gestures and state; search owns matching and cursors;
1272
+ * neither owns segmentation. Both consume this module, so a text range has
1273
+ * exactly one canonical segmentation regardless of how it was produced.
1274
+ *
1275
+ * Everything here works in the file's coordinates (y up), as the engine reads
1276
+ * them. The public layout (`createTextLayout`, `pageSpace/text.ts`) runs this
1277
+ * one on page-space geometry flipped top to bottom and flips its answers
1278
+ * back. The line-merge is adapted from Chromium's
1279
+ * pdf/pdfium/pdfium_range.cc `MergeAdjacentRects` (BSD-licensed, Copyright
1280
+ * 2010 The Chromium Authors); hit-testing mirrors PDFium `GetIndexAtPos`
1281
+ * (exact tight box first, then a tolerance pass).
1282
+ *
1283
+ * Orientation model — frames. Every run belongs to a frame: frame 0 is page
1284
+ * space itself (all upright runs — the dominant case takes a byte-identical
1285
+ * fast path), and each rotated orientation cluster gets an orthonormal frame
1286
+ * in which its text reads upright (baseline +x, ascent +y). Frames are
1287
+ * derived from the semantic edges of the first classifiable glyph's quad —
1288
+ * never from advisory wire fields — and keyed by (baseline direction,
1289
+ * ascent handedness):
1290
+ *
1291
+ * - Rotated and mirrored text become upright inside their frame.
1292
+ * - Shear (fake italic) is deliberately not part of the frame key or the
1293
+ * basis: a sheared run shares the frame of its unsheared neighbours, so
1294
+ * a mixed roman/italic line merges into one segment (Acrobat parity),
1295
+ * and the shear is absorbed as a small in-frame AABB residue. This is
1296
+ * the deterministic, order-independent contract — an affine
1297
+ * (shear-preserving) basis would either fragment mixed lines or make
1298
+ * the roman body's geometry depend on whether an italic run came first.
1299
+ * - Runs of different frames never merge into one line.
1300
+ *
1301
+ * All algorithms run on frame-local boxes; every participating run of a
1302
+ * cluster is transformed through the same frame, so merged coordinates are
1303
+ * always commensurable. Only the output maps back to page space, as
1304
+ * oriented quads.
1305
+ */
1306
+ /** A space or a character with no box ends a word. */
1307
+ const isBoundary = (g) => !!(g.space || g.empty);
1308
+ const isEmpty = (g) => !!g.empty;
1309
+ const CHAR_DISTANCE_FACTOR = 2.5;
1310
+ const FONT_SIZE_RATIO_THRESHOLD = 1.5;
1311
+ const VERTICAL_OVERLAP_THRESHOLD = .8;
1312
+ const LINE_OVERLAP_THRESHOLD = .5;
1313
+ /** Baseline directions within ~0.5° share a frame. */
1314
+ const FRAME_DOT_TOLERANCE = Math.cos(.0087);
1315
+ const EDGE_EPSILON = 1e-6;
1316
+ /**
1317
+ * The layout of a page's geometry in the file's coordinates, for the engine's
1318
+ * own readers. Everything else uses `createTextLayout`, in page space.
1319
+ */
1320
+ function createPdfTextLayout(snapshot) {
1321
+ const layout = buildPageTextLayout(snapshot);
1322
+ const charCount = layout.glyphs.length;
1323
+ const indexOf = (at) => {
1324
+ if (typeof at !== "number") return textGlyphAt(layout, at);
1325
+ return Number.isInteger(at) && at >= 0 && at < charCount ? at : null;
1326
+ };
1327
+ const rangeOf = ([from, to]) => ({
1328
+ start: from,
1329
+ count: to - from + 1
1330
+ });
1331
+ return {
1332
+ charCount,
1333
+ runs: snapshot.runs,
1334
+ charAt: (point) => textGlyphAt(layout, point),
1335
+ wordAt: (at) => {
1336
+ const index = indexOf(at);
1337
+ return index === null ? null : rangeOf(expandTextRangeToWord(layout, index));
1338
+ },
1339
+ lineAt: (at) => {
1340
+ const index = indexOf(at);
1341
+ return index === null ? null : rangeOf(expandTextRangeToLine(layout, index));
1342
+ },
1343
+ segments: (range) => textSegmentsForRange(layout, range.start, range.count),
1344
+ charQuad: (index) => textGlyphQuad(layout, index)
1345
+ };
1346
+ }
1347
+ const IDENTITY_FRAME = {
1348
+ baseline: {
1349
+ x: 1,
1350
+ y: 0
1351
+ },
1352
+ ascent: {
1353
+ x: 0,
1354
+ y: 1
1355
+ }
1356
+ };
1357
+ const toFrame = (f, p) => ({
1358
+ x: p.x * f.baseline.x + p.y * f.baseline.y,
1359
+ y: p.x * f.ascent.x + p.y * f.ascent.y
1360
+ });
1361
+ const fromFrame = (f, p) => ({
1362
+ x: f.baseline.x * p.x + f.ascent.x * p.y,
1363
+ y: f.baseline.y * p.x + f.ascent.y * p.y
1364
+ });
1365
+ const ZERO_RECT = {
1366
+ left: 0,
1367
+ bottom: 0,
1368
+ right: 0,
1369
+ top: 0
1370
+ };
1371
+ /** Frame-local AABB of a page-space cell (exact under the frame's rotation;
1372
+ * shear residue is the documented in-frame envelope). */
1373
+ function frameBoxOfQuad(f, q) {
1374
+ const pts = [
1375
+ q.upperLeft,
1376
+ q.upperRight,
1377
+ q.lowerLeft,
1378
+ q.lowerRight
1379
+ ].map((p) => toFrame(f, p));
1380
+ const xs = pts.map((p) => p.x);
1381
+ const ys = pts.map((p) => p.y);
1382
+ return {
1383
+ left: Math.min(...xs),
1384
+ bottom: Math.min(...ys),
1385
+ right: Math.max(...xs),
1386
+ top: Math.max(...ys)
1387
+ };
1388
+ }
1389
+ /** Find-or-create the frame for a rotated run, keyed by the baseline
1390
+ * direction + ascent handedness of its first classifiable glyph's edges. */
1391
+ function frameForRun(frames, run) {
1392
+ const seed = run.glyphs.find((g) => !isEmpty(g));
1393
+ if (!seed) return 0;
1394
+ const q = seed.loose;
1395
+ const bx = q.upperRight.x - q.upperLeft.x;
1396
+ const by = q.upperRight.y - q.upperLeft.y;
1397
+ const len = Math.hypot(bx, by);
1398
+ if (len <= EDGE_EPSILON) return 0;
1399
+ const baseline = {
1400
+ x: bx / len,
1401
+ y: by / len
1402
+ };
1403
+ const ax = q.upperLeft.x - q.lowerLeft.x;
1404
+ const ay = q.upperLeft.y - q.lowerLeft.y;
1405
+ const sign = baseline.x * ay - baseline.y * ax >= 0 ? 1 : -1;
1406
+ const ascent = {
1407
+ x: -baseline.y * sign,
1408
+ y: baseline.x * sign
1409
+ };
1410
+ for (let i = 0; i < frames.length; i++) {
1411
+ const f = frames[i];
1412
+ const dot = f.baseline.x * baseline.x + f.baseline.y * baseline.y;
1413
+ const sameHandedness = f.ascent.x * ascent.x + f.ascent.y * ascent.y > 0;
1414
+ if (dot >= FRAME_DOT_TOLERANCE && sameHandedness) return i;
1415
+ }
1416
+ frames.push({
1417
+ baseline,
1418
+ ascent
1419
+ });
1420
+ return frames.length - 1;
1421
+ }
1422
+ /**
1423
+ * Flatten a page's geometry snapshot into the canonical layout. Upright runs
1424
+ * copy their wire boxes verbatim (frame 0, wire run rects included); rotated
1425
+ * runs project their glyph cells into their cluster's frame.
1426
+ */
1427
+ function buildPageTextLayout(snapshot) {
1428
+ const glyphs = [];
1429
+ const runs = [];
1430
+ const frames = [IDENTITY_FRAME];
1431
+ for (const run of snapshot.runs) {
1432
+ if (!isRotatedGeometryRun(run)) {
1433
+ for (const g of run.glyphs) glyphs.push({
1434
+ loose: g.loose,
1435
+ tight: g.tight,
1436
+ space: g.space,
1437
+ empty: g.empty
1438
+ });
1439
+ runs.push({
1440
+ start: run.start,
1441
+ count: run.glyphs.length,
1442
+ rect: run.rect,
1443
+ fontSize: run.fontSize,
1444
+ frame: 0
1445
+ });
1446
+ continue;
1447
+ }
1448
+ const frame = frameForRun(frames, run);
1449
+ const f = frames[frame];
1450
+ let runBox = null;
1451
+ for (const g of run.glyphs) {
1452
+ if (isEmpty(g)) {
1453
+ glyphs.push({
1454
+ loose: ZERO_RECT,
1455
+ space: g.space,
1456
+ empty: true
1457
+ });
1458
+ continue;
1459
+ }
1460
+ const loose = frameBoxOfQuad(f, g.loose);
1461
+ const tight = g.tight ? frameBoxOfQuad(f, g.tight) : void 0;
1462
+ glyphs.push({
1463
+ loose,
1464
+ tight,
1465
+ space: g.space
1466
+ });
1467
+ runBox = runBox ? {
1468
+ left: Math.min(runBox.left, loose.left),
1469
+ bottom: Math.min(runBox.bottom, loose.bottom),
1470
+ right: Math.max(runBox.right, loose.right),
1471
+ top: Math.max(runBox.top, loose.top)
1472
+ } : loose;
1473
+ }
1474
+ runs.push({
1475
+ start: run.start,
1476
+ count: run.glyphs.length,
1477
+ rect: runBox ?? ZERO_RECT,
1478
+ fontSize: run.fontSize,
1479
+ frame
1480
+ });
1481
+ }
1482
+ return {
1483
+ glyphs,
1484
+ runs,
1485
+ frames
1486
+ };
1487
+ }
1488
+ const inRect = (r, p) => p.x >= r.left && p.x <= r.right && p.y >= r.bottom && p.y <= r.top;
1489
+ function avgGlyphHeight(layout) {
1490
+ let total = 0;
1491
+ let count = 0;
1492
+ for (const g of layout.glyphs) {
1493
+ if (isEmpty(g)) continue;
1494
+ total += g.loose.top - g.loose.bottom;
1495
+ count++;
1496
+ }
1497
+ return count === 0 ? 0 : total / count;
1498
+ }
1499
+ /** The point expressed in every frame (index-aligned with `layout.frames`).
1500
+ * Frame 0 returns the point itself — no float round-trip on the fast path. */
1501
+ function pointPerFrame(layout, p) {
1502
+ return layout.frames.map((f, i) => i === 0 ? p : toFrame(f, p));
1503
+ }
1504
+ /**
1505
+ * The glyph (char) index at a page-space point, or null when nothing is near
1506
+ * (so callers can show the pointer cursor off-text). PDFium `GetIndexAtPos`:
1507
+ * exact tight-box containment first, then a tolerance pass (closest by
1508
+ * Manhattan distance within `toleranceFactor × average glyph height`). Each
1509
+ * run tests the point in its own frame, so rotated text hit-tests exactly.
1510
+ */
1511
+ function textGlyphAt(layout, point, toleranceFactor = 1.5) {
1512
+ const pts = pointPerFrame(layout, point);
1513
+ for (const run of layout.runs) {
1514
+ const q = pts[run.frame];
1515
+ if (!inRect(run.rect, q)) continue;
1516
+ for (let i = 0; i < run.count; i++) {
1517
+ const g = layout.glyphs[run.start + i];
1518
+ if (inRect(g.tight ?? g.loose, q)) return run.start + i;
1519
+ }
1520
+ }
1521
+ if (toleranceFactor <= 0) return null;
1522
+ const half = avgGlyphHeight(layout) * toleranceFactor / 2;
1523
+ let best = -1;
1524
+ let bestDist = Infinity;
1525
+ for (const run of layout.runs) {
1526
+ const q = pts[run.frame];
1527
+ const r = run.rect;
1528
+ if (q.x < r.left - half || q.x > r.right + half || q.y < r.bottom - half || q.y > r.top + half) continue;
1529
+ for (let i = 0; i < run.count; i++) {
1530
+ const g = layout.glyphs[run.start + i];
1531
+ if (isEmpty(g)) continue;
1532
+ const b = g.tight ?? g.loose;
1533
+ if (q.x < b.left - half || q.x > b.right + half || q.y < b.bottom - half || q.y > b.top + half) continue;
1534
+ const dx = Math.min(Math.abs(q.x - b.left), Math.abs(q.x - b.right));
1535
+ const dy = Math.min(Math.abs(q.y - b.bottom), Math.abs(q.y - b.top));
1536
+ if (dx + dy < bestDist - EDGE_EPSILON) {
1537
+ bestDist = dx + dy;
1538
+ best = run.start + i;
1539
+ }
1540
+ }
1541
+ }
1542
+ return best >= 0 ? best : null;
1543
+ }
1544
+ /** Double-click: the word around `glyph` (walk to space/empty glyphs both ways). */
1545
+ function expandTextRangeToWord(layout, glyph) {
1546
+ const n = layout.glyphs.length;
1547
+ if (glyph < 0 || glyph >= n) return [glyph, glyph];
1548
+ let from = glyph;
1549
+ while (from > 0 && !isBoundary(layout.glyphs[from - 1])) from--;
1550
+ let to = glyph;
1551
+ while (to < n - 1 && !isBoundary(layout.glyphs[to + 1])) to++;
1552
+ return [from, to];
1553
+ }
1554
+ /** Triple-click: the full visual line — same-frame runs whose vertical extent
1555
+ * overlaps the anchor run's. A differently-oriented run is a line boundary. */
1556
+ function expandTextRangeToLine(layout, glyph) {
1557
+ const ri = layout.runs.findIndex((r) => glyph >= r.start && glyph < r.start + r.count);
1558
+ if (ri < 0) return [glyph, glyph];
1559
+ const anchor = layout.runs[ri];
1560
+ const bottom = anchor.rect.bottom;
1561
+ const top = anchor.rect.top;
1562
+ let from = anchor.start;
1563
+ let to = anchor.start + anchor.count - 1;
1564
+ for (let r = ri - 1; r >= 0; r--) {
1565
+ const run = layout.runs[r];
1566
+ if (isZero(run.rect)) continue;
1567
+ if (run.frame !== anchor.frame) break;
1568
+ if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
1569
+ from = run.start;
1570
+ }
1571
+ for (let r = ri + 1; r < layout.runs.length; r++) {
1572
+ const run = layout.runs[r];
1573
+ if (isZero(run.rect)) continue;
1574
+ if (run.frame !== anchor.frame) break;
1575
+ if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
1576
+ to = run.start + run.count - 1;
1577
+ }
1578
+ return [from, to];
1579
+ }
1580
+ /**
1581
+ * A single glyph's oriented cell in page space, or null for degenerate
1582
+ * glyphs — the anchor for caret placement at a selection boundary.
1583
+ */
1584
+ function textGlyphQuad(layout, glyph) {
1585
+ const g = layout.glyphs[glyph];
1586
+ if (!g || isEmpty(g)) return null;
1587
+ const b = g.loose;
1588
+ if (b.right <= b.left || b.top <= b.bottom) return null;
1589
+ const run = layout.runs.find((r) => glyph >= r.start && glyph < r.start + r.count);
1590
+ const f = layout.frames[run?.frame ?? 0];
1591
+ return quadOfFrameRect(f, run?.frame ?? 0, b);
1592
+ }
1593
+ /**
1594
+ * Merged visual-line segments for the half-open char range
1595
+ * `[start, start + count)` — the canonical segmentation. Chars
1596
+ * outside the layout are ignored.
1597
+ */
1598
+ function textSegmentsForRange(layout, start, count) {
1599
+ if (count <= 0 || layout.glyphs.length === 0) return [];
1600
+ const lo = Math.max(0, start);
1601
+ const hi = Math.min(layout.glyphs.length - 1, start + count - 1);
1602
+ if (hi < lo) return [];
1603
+ const subRuns = [];
1604
+ for (const run of layout.runs) {
1605
+ const runEnd = run.start + run.count - 1;
1606
+ if (runEnd < lo || run.start > hi) continue;
1607
+ const s = Math.max(lo, run.start);
1608
+ const e = Math.min(hi, runEnd);
1609
+ let left = Infinity;
1610
+ let right = -Infinity;
1611
+ let bottom = Infinity;
1612
+ let top = -Infinity;
1613
+ let charCountAcc = 0;
1614
+ let widthSum = 0;
1615
+ let prevRight = -Infinity;
1616
+ let firstX = 0;
1617
+ let lastX = 0;
1618
+ const flush = () => {
1619
+ if (charCountAcc > 0 && left !== Infinity) subRuns.push({
1620
+ rect: {
1621
+ left,
1622
+ bottom,
1623
+ right,
1624
+ top
1625
+ },
1626
+ charCount: charCountAcc,
1627
+ fontSize: run.fontSize,
1628
+ frame: run.frame,
1629
+ firstX,
1630
+ lastX
1631
+ });
1632
+ left = Infinity;
1633
+ right = -Infinity;
1634
+ bottom = Infinity;
1635
+ top = -Infinity;
1636
+ charCountAcc = 0;
1637
+ widthSum = 0;
1638
+ prevRight = -Infinity;
1639
+ };
1640
+ for (let ci = s; ci <= e; ci++) {
1641
+ const g = layout.glyphs[ci];
1642
+ if (isEmpty(g)) continue;
1643
+ const b = g.loose;
1644
+ if (charCountAcc > 0 && prevRight > -Infinity) {
1645
+ const avg = widthSum / charCountAcc;
1646
+ if (avg > 0 && Math.abs(b.left - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
1647
+ }
1648
+ const centerX = (b.left + b.right) / 2;
1649
+ if (charCountAcc === 0) firstX = centerX;
1650
+ lastX = centerX;
1651
+ left = Math.min(left, b.left);
1652
+ right = Math.max(right, b.right);
1653
+ bottom = Math.min(bottom, b.bottom);
1654
+ top = Math.max(top, b.top);
1655
+ charCountAcc++;
1656
+ widthSum += b.right - b.left;
1657
+ prevRight = b.right;
1658
+ }
1659
+ flush();
1660
+ }
1661
+ return mergeAdjacentSubRuns(subRuns).map((m) => materializeSegment(layout, m));
1662
+ }
1663
+ function mergeAdjacentSubRuns(runs) {
1664
+ const out = [];
1665
+ let prev = null;
1666
+ let cur = null;
1667
+ for (const run of runs) {
1668
+ if (prev && cur && prev.frame === run.frame && shouldMerge(prev, run)) cur = {
1669
+ rect: {
1670
+ left: Math.min(cur.rect.left, run.rect.left),
1671
+ bottom: Math.min(cur.rect.bottom, run.rect.bottom),
1672
+ right: Math.max(cur.rect.right, run.rect.right),
1673
+ top: Math.max(cur.rect.top, run.rect.top)
1674
+ },
1675
+ frame: cur.frame,
1676
+ firstX: cur.firstX,
1677
+ lastX: run.lastX
1678
+ };
1679
+ else {
1680
+ if (cur) out.push(cur);
1681
+ cur = {
1682
+ rect: run.rect,
1683
+ frame: run.frame,
1684
+ firstX: run.firstX,
1685
+ lastX: run.lastX
1686
+ };
1687
+ }
1688
+ prev = run;
1689
+ }
1690
+ if (cur && cur.rect.right > cur.rect.left && cur.rect.top > cur.rect.bottom) out.push(cur);
1691
+ return out;
1692
+ }
1693
+ /** Segment quad for a frame-local rect (frame 0: the rect's own corners,
1694
+ * no float round-trip). The frame is y-up, so the upper corners sit at the
1695
+ * frame top. */
1696
+ function quadOfFrameRect(f, frameIndex, r) {
1697
+ if (frameIndex === 0) return {
1698
+ upperLeft: {
1699
+ x: r.left,
1700
+ y: r.top
1701
+ },
1702
+ upperRight: {
1703
+ x: r.right,
1704
+ y: r.top
1705
+ },
1706
+ lowerLeft: {
1707
+ x: r.left,
1708
+ y: r.bottom
1709
+ },
1710
+ lowerRight: {
1711
+ x: r.right,
1712
+ y: r.bottom
1713
+ }
1714
+ };
1715
+ return {
1716
+ upperLeft: fromFrame(f, {
1717
+ x: r.left,
1718
+ y: r.top
1719
+ }),
1720
+ upperRight: fromFrame(f, {
1721
+ x: r.right,
1722
+ y: r.top
1723
+ }),
1724
+ lowerLeft: fromFrame(f, {
1725
+ x: r.left,
1726
+ y: r.bottom
1727
+ }),
1728
+ lowerRight: fromFrame(f, {
1729
+ x: r.right,
1730
+ y: r.bottom
1731
+ })
1732
+ };
1733
+ }
1734
+ function materializeSegment(layout, m) {
1735
+ const frame = layout.frames[m.frame];
1736
+ const quad = quadOfFrameRect(frame, m.frame, m.rect);
1737
+ return {
1738
+ quad,
1739
+ rect: m.frame === 0 ? m.rect : {
1740
+ left: Math.min(quad.upperLeft.x, quad.upperRight.x, quad.lowerLeft.x, quad.lowerRight.x),
1741
+ bottom: Math.min(quad.upperLeft.y, quad.upperRight.y, quad.lowerLeft.y, quad.lowerRight.y),
1742
+ right: Math.max(quad.upperLeft.x, quad.upperRight.x, quad.lowerLeft.x, quad.lowerRight.x),
1743
+ top: Math.max(quad.upperLeft.y, quad.upperRight.y, quad.lowerLeft.y, quad.lowerRight.y)
1744
+ },
1745
+ advance: m.lastX >= m.firstX ? 1 : -1
1746
+ };
1747
+ }
1748
+ function shouldMerge(a, b) {
1749
+ if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
1750
+ if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
1751
+ }
1752
+ if (verticalOverlap(a.rect, b.rect) < VERTICAL_OVERLAP_THRESHOLD) return false;
1753
+ const aw = (a.rect.right - a.rect.left) / a.charCount;
1754
+ const bw = (b.rect.right - b.rect.left) / b.charCount;
1755
+ const aL = a.rect.left - aw;
1756
+ const aR = a.rect.right + aw;
1757
+ const bL = b.rect.left - bw;
1758
+ return aL < b.rect.right + bw && aR > bL;
1759
+ }
1760
+ function verticalOverlap(a, b) {
1761
+ const ah = a.top - a.bottom;
1762
+ const bh = b.top - b.bottom;
1763
+ if (ah <= 0 || bh <= 0) return 0;
1764
+ const u = Math.max(a.top, b.top) - Math.min(a.bottom, b.bottom);
1765
+ if (u === ah || u === bh) return 1;
1766
+ return Math.max(0, Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)) / u;
1767
+ }
1768
+ const isZero = (r) => r.right === r.left && r.top === r.bottom;
1769
+ function overlapV(bottom1, top1, bottom2, top2) {
1770
+ const u = Math.max(top1, top2) - Math.min(bottom1, bottom2);
1771
+ if (u === 0) return false;
1772
+ return Math.max(0, Math.min(top1, top2) - Math.max(bottom1, bottom2)) / u >= LINE_OVERLAP_THRESHOLD;
1773
+ }
1774
+ //#endregion
1775
+ //#region src/pageSpace/text.ts
1776
+ /**
1777
+ * A character with no box of its own (`empty`) carries a zeroed box or quad
1778
+ * that says so, in either space, and so does a run of nothing but such
1779
+ * characters; it is not a place, so it never converts.
1780
+ */
1781
+ const NO_BOX$1 = {
1782
+ x: 0,
1783
+ y: 0,
1784
+ width: 0,
1785
+ height: 0
1786
+ };
1787
+ const NO_RECT = {
1788
+ left: 0,
1789
+ bottom: 0,
1790
+ right: 0,
1791
+ top: 0
1792
+ };
1793
+ const NO_QUAD = {
1794
+ upperLeft: {
1795
+ x: 0,
1796
+ y: 0
1797
+ },
1798
+ upperRight: {
1799
+ x: 0,
1800
+ y: 0
1801
+ },
1802
+ lowerLeft: {
1803
+ x: 0,
1804
+ y: 0
1805
+ },
1806
+ lowerRight: {
1807
+ x: 0,
1808
+ y: 0
1809
+ }
1810
+ };
1811
+ /** Whether a run has a character with a box of its own. */
1812
+ const hasBox = (run) => run.glyphs.some((glyph) => !glyph.empty);
1813
+ /** A page's text geometry in page space. `visible` is the page's visible box. */
1814
+ function pageGeometryOf(snapshot, visible) {
1815
+ return {
1816
+ ...snapshot,
1817
+ runs: snapshot.runs.map((run) => {
1818
+ const rect = hasBox(run) ? pageBoxOf(run.rect, visible) : NO_BOX$1;
1819
+ if (isRotatedGeometryRun(run)) return {
1820
+ ...run,
1821
+ rect,
1822
+ glyphs: run.glyphs.map(({ loose, tight, ...glyph }) => ({
1823
+ ...glyph,
1824
+ loose: glyph.empty ? NO_QUAD : pageQuadOf(loose, visible),
1825
+ ...tight ? { tight: pageQuadOf(tight, visible) } : {}
1826
+ }))
1827
+ };
1828
+ return {
1829
+ ...run,
1830
+ rect,
1831
+ glyphs: run.glyphs.map(({ loose, tight, ...glyph }) => ({
1832
+ ...glyph,
1833
+ loose: glyph.empty ? NO_BOX$1 : pageBoxOf(loose, visible),
1834
+ ...tight ? { tight: pageBoxOf(tight, visible) } : {}
1835
+ }))
1836
+ };
1837
+ })
1838
+ };
1839
+ }
1840
+ /** A page-space run flipped top to bottom, for the y-up math of the text layout and its helpers. */
1841
+ function mirroredRun(run) {
1842
+ const rect = hasBox(run) ? mirroredRect(run.rect) : NO_RECT;
1843
+ if (isRotatedGeometryRun(run)) return {
1844
+ ...run,
1845
+ rect,
1846
+ glyphs: run.glyphs.map(({ loose, tight, ...glyph }) => ({
1847
+ ...glyph,
1848
+ loose: glyph.empty ? NO_QUAD : mirroredQuad(loose),
1849
+ ...tight ? { tight: mirroredQuad(tight) } : {}
1850
+ }))
1851
+ };
1852
+ return {
1853
+ ...run,
1854
+ rect,
1855
+ glyphs: run.glyphs.map(({ loose, tight, ...glyph }) => ({
1856
+ ...glyph,
1857
+ loose: glyph.empty ? NO_RECT : mirroredRect(loose),
1858
+ ...tight ? { tight: mirroredRect(tight) } : {}
1859
+ }))
1860
+ };
1861
+ }
1862
+ function pageTextSegmentOf(segment, visible) {
1863
+ return {
1864
+ ...segment,
1865
+ quad: pageQuadOf(segment.quad, visible),
1866
+ rect: pageBoxOf(segment.rect, visible)
1867
+ };
1868
+ }
1869
+ /** A search batch in page space: each match measured on its own page. */
1870
+ function pageSearchSliceOf(slice, boxOf) {
1871
+ return {
1872
+ ...slice,
1873
+ matches: slice.matches.map((match) => {
1874
+ const visible = boxOf(match.page);
1875
+ return {
1876
+ ...match,
1877
+ segments: match.segments.map((segment) => pageTextSegmentOf(segment, visible))
1878
+ };
1879
+ })
1880
+ };
1881
+ }
1882
+ /**
1883
+ * The layout of a page's text geometry in page space. It runs the same
1884
+ * layout as the file's coordinates do, on the geometry flipped top to bottom,
1885
+ * and flips each answer back: the flip is exact and the layout doesn't depend
1886
+ * on where the origin is.
1887
+ */
1888
+ function createTextLayout(snapshot) {
1889
+ const layout = createPdfTextLayout({
1890
+ ...snapshot,
1891
+ runs: snapshot.runs.map(mirroredRun)
1892
+ });
1893
+ const at = (value) => typeof value === "number" ? value : mirroredPoint(value);
1894
+ return {
1895
+ charCount: layout.charCount,
1896
+ runs: snapshot.runs,
1897
+ charAt: (point) => layout.charAt(mirroredPoint(point)),
1898
+ wordAt: (value) => layout.wordAt(at(value)),
1899
+ lineAt: (value) => layout.lineAt(at(value)),
1900
+ segments: (range) => layout.segments(range).map((segment) => ({
1901
+ ...segment,
1902
+ quad: unmirroredQuad(segment.quad),
1903
+ rect: unmirroredBox(segment.rect)
1904
+ })),
1905
+ charQuad: (index) => {
1906
+ const quad = layout.charQuad(index);
1907
+ return quad ? unmirroredQuad(quad) : null;
1908
+ }
1909
+ };
1910
+ }
1911
+ //#endregion
1912
+ //#region src/annotation/drawnPoints.ts
1913
+ /**
1914
+ * The points of a line, polyline, polygon or ink as the page shows them: the
1915
+ * upright points a read gives, turned by `rotation` about the middle of their
1916
+ * box. One list for a line (its two ends) or a polygon, one per ink stroke;
1917
+ * `null` for any other kind.
1918
+ */
1919
+ function pdfDrawnPointsOf(annotation) {
1920
+ let sets;
1921
+ switch (annotation.subtype) {
1922
+ case "line":
1923
+ sets = [[annotation.linePoints.start, annotation.linePoints.end]];
1924
+ break;
1925
+ case "polyline":
1926
+ case "polygon":
1927
+ sets = [annotation.vertices];
1928
+ break;
1929
+ case "ink":
1930
+ sets = annotation.inkList.map((stroke) => [...stroke]);
1931
+ break;
1932
+ default: return null;
1933
+ }
1934
+ if (!annotation.rotation) return sets.map((set) => set.map((point) => ({ ...point })));
1935
+ const turn = pdfTurnOfUpright(sets.flat(), annotation.rotation);
1936
+ return sets.map((set) => set.map((point) => pdfPointTurned(point, turn)));
1937
+ }
1938
+ //#endregion
1939
+ //#region src/annotation/resolve/annotationOfDraft.ts
1940
+ const NO_BOX = {
1941
+ x: 0,
1942
+ y: 0,
1943
+ width: 0,
1944
+ height: 0
1945
+ };
1946
+ /**
1947
+ * A free text's rich text as a read spells it, and the text style it reads
1948
+ * back with: the resolved body over the engine's defaults, and its size,
1949
+ * text color and alignment beside it. The font keeps the name the draft
1950
+ * gave, or the default.
1951
+ */
1952
+ function freeTextReadOf(given, read) {
1953
+ const richText = given.richText;
1954
+ const body = {
1955
+ ...DEFAULT_RICH_TEXT_BODY,
1956
+ ...richText?.body
1957
+ };
1958
+ body.color = body.color.toLowerCase();
1959
+ read.richText = {
1960
+ ...read.richText,
1961
+ body
1962
+ };
1963
+ read.fontSize = body.size;
1964
+ read.fontColor = body.color;
1965
+ read.textAlign = body.align;
1966
+ }
1967
+ /**
1968
+ * The annotation a resolved draft creates, as a read spells it: the draft's
1969
+ * data fields, the kind's defaults for those it leaves out, and what
1970
+ * `context` says for the fields the engine owns or stamps. Works in either
1971
+ * space: it only moves values by field name.
1972
+ */
1973
+ function annotationOfResolvedDraft(resolved, context) {
1974
+ const subtype = resolved.subtype;
1975
+ const owners = kindFactsOf(subtype)?.owners ?? {};
1976
+ const defaults = annotationDefaultsOf(subtype);
1977
+ const given = resolved;
1978
+ const read = { subtype };
1979
+ for (const [name, owner] of Object.entries(owners)) if (owner === "data" && given[name] !== void 0) read[name] = readValueOf(name, given[name], void 0);
1980
+ else if (owner === "data") read[name] = defaults[name] ?? null;
1981
+ else if (owner === "attribution") read[name] = context.attribution?.[name] ?? null;
1982
+ else read[name] = null;
1983
+ if (read.nm === null && context.nm !== void 0) read.nm = context.nm;
1984
+ read.ref = context.ref;
1985
+ read.page = context.ref.page;
1986
+ read.hasAppearance = !UNBAKED_KINDS.has(subtype);
1987
+ if (read.rect === null) read.rect = context.rect ?? given.rect ?? given.box ?? NO_BOX;
1988
+ if (subtype === "free-text") freeTextReadOf(given, read);
1989
+ if (subtype === "link") {
1990
+ const target = given.target;
1991
+ read.actions = target && (target.kind === "uri" || target.kind === "goto") ? { activate: actionTreeOf(target) } : null;
1992
+ }
1993
+ if (subtype === "widget") read.fieldFamily = "unknown";
1994
+ return read;
1995
+ }
1996
+ //#endregion
1997
+ //#region src/pageSpace/helpers.ts
1998
+ /**
1999
+ * The engine's geometry helpers, for page-space values. Each flips its
2000
+ * values top to bottom, runs the helper written for the file's coordinates
2001
+ * (y up), and flips the answer back: exact, and needing no page box, since
2002
+ * none of that math depends on where the origin is.
2003
+ */
2004
+ const mirroredTurn = (turn) => ({
2005
+ degrees: turn.degrees,
2006
+ center: mirroredPoint(turn.center)
2007
+ });
2008
+ const unmirroredTurn = (turn) => ({
2009
+ degrees: turn.degrees,
2010
+ center: unmirroredPoint(turn.center)
2011
+ });
2012
+ /** The box around `points`; a zero box at the origin when there are none. */
2013
+ const pagePointsBounds = (points) => unmirroredBox(pdfPointsBounds(points.map(mirroredPoint)));
2014
+ /** `point` turned by `turn`; a negative angle turns it back. */
2015
+ const pagePointTurned = (point, turn) => unmirroredPoint(pdfPointTurned(mirroredPoint(point), mirroredTurn(turn)));
2016
+ /** `point` turned back by `turn`. */
2017
+ const pagePointUnturned = (point, turn) => unmirroredPoint(pdfPointUnturned(mirroredPoint(point), mirroredTurn(turn)));
2018
+ /** The turn that draws upright `points`: `degrees` about the middle of their box. */
2019
+ const pageTurnOfUpright = (points, degrees) => unmirroredTurn(pdfTurnOfUpright(points.map(mirroredPoint), degrees));
2020
+ /** The turn drawn `points` were made with: `degrees` about the middle of their upright box. */
2021
+ const pageTurnOfDrawn = (points, degrees) => unmirroredTurn(pdfTurnOfDrawn(points.map(mirroredPoint), degrees));
2022
+ /** The upright box around a quad, whatever its turn or skew. */
2023
+ const pageQuadBounds = (quad) => unmirroredBox(pdfQuadBounds(mirroredQuad(quad)));
2024
+ /** A glyph's loose cell as a quad. */
2025
+ const pageGlyphLooseQuad = (run, index) => unmirroredQuad(glyphLooseQuad(mirroredRun(run), index));
2026
+ /** A glyph's loose cell as an upright box. */
2027
+ const pageGlyphLooseBounds = (run, index) => unmirroredBox(glyphLooseBounds(mirroredRun(run), index));
2028
+ /**
2029
+ * The points of a line, polyline, polygon or ink as the page shows them: the
2030
+ * upright points a read gives, turned by `rotation` about the middle of their
2031
+ * box. One list for a line (its two ends) or a polygon, one per ink stroke;
2032
+ * `null` for any other kind.
2033
+ */
2034
+ function drawnPointsOf(annotation) {
2035
+ const flip = (points) => points.map(mirroredPoint);
2036
+ let mirrored;
2037
+ switch (annotation.subtype) {
2038
+ case "line":
2039
+ mirrored = {
2040
+ subtype: "line",
2041
+ rotation: annotation.rotation,
2042
+ linePoints: {
2043
+ start: mirroredPoint(annotation.linePoints.start),
2044
+ end: mirroredPoint(annotation.linePoints.end)
2045
+ }
2046
+ };
2047
+ break;
2048
+ case "polyline":
2049
+ case "polygon":
2050
+ mirrored = {
2051
+ subtype: annotation.subtype,
2052
+ rotation: annotation.rotation,
2053
+ vertices: flip(annotation.vertices)
2054
+ };
2055
+ break;
2056
+ case "ink":
2057
+ mirrored = {
2058
+ subtype: "ink",
2059
+ rotation: annotation.rotation,
2060
+ inkList: annotation.inkList.map(flip)
2061
+ };
2062
+ break;
2063
+ default: return null;
2064
+ }
2065
+ const sets = pdfDrawnPointsOf(mirrored);
2066
+ return sets && sets.map((set) => set.map(unmirroredPoint));
2067
+ }
2068
+ /**
2069
+ * The turn an annotation's appearance raster is drawn without, degrees
2070
+ * clockwise, or `null` when the raster is drawn as the page shows it. A box
2071
+ * kind drawn turned whose drawing stays inside the turned box (its `rect` is
2072
+ * the upright box around it) renders upright over its `box`, for the
2073
+ * consumer to turn about the middle of `box`: a new turn needs no new
2074
+ * raster. A callout (its line isn't turned) and a drawing that reaches past
2075
+ * its box (a cloudy border's bumps) render as the page shows them, placed by
2076
+ * `rect`.
2077
+ */
2078
+ function appearanceTurnOf(annotation) {
2079
+ return pdfAppearanceTurnOf({
2080
+ ...annotation,
2081
+ rect: mirroredRect(annotation.rect),
2082
+ box: annotation.box ? mirroredRect(annotation.box) : annotation.box
2083
+ });
2084
+ }
2085
+ /** Page space with its origin as the file's: turning one into the other is the mirror. */
2086
+ const MIRROR = {
2087
+ left: 0,
2088
+ bottom: 0,
2089
+ right: 0,
2090
+ top: 0
2091
+ };
2092
+ /** Shape fields hold no destinations, so nothing asks for another page's box. */
2093
+ const noOtherPage = () => {
2094
+ throw new Error("a shape field holds no destination");
2095
+ };
2096
+ /**
2097
+ * The shape fields that put `annotation` at `rect`, the rule an update's
2098
+ * `rect` follows (`create({ ...copy, ...shapeForRect(copy, target) })` puts a
2099
+ * copy there).
2100
+ *
2101
+ * - A kind whose shape is its rect (note, file attachment, link, popup,
2102
+ * redaction) takes `rect` as it is.
2103
+ * - A drawn kind has every place it holds mapped from its current `rect` onto
2104
+ * `rect`, each axis on its own, as Acrobat does when a script sets
2105
+ * `annot.rect`: a rect of the same size moves the shape, a bigger one
2106
+ * stretches it. The engine then works out the rect from the shape, so it
2107
+ * can differ a little from `rect` (a stroke keeps its width). A box at a
2108
+ * quarter turn stretches along its own sides, as it stands on the page.
2109
+ *
2110
+ * A drawn kind refuses a rect that would squash it to no width or height, a
2111
+ * rect of another size when it's turned (it can only move) unless it is a
2112
+ * box at a quarter turn, and one when its own rect has no width or height to
2113
+ * stretch. Each refusal is `InvalidArg` on `rect`.
2114
+ */
2115
+ function shapeForRect(annotation, rect) {
2116
+ if (!DRAWN_RECT_KINDS.has(annotation.subtype)) return { rect };
2117
+ const read = annotation;
2118
+ const placed = pdfShapeForRect(pdfAnnotationOf(Object.fromEntries([
2119
+ "subtype",
2120
+ "rect",
2121
+ ...shapeFieldsOf(annotation.subtype)
2122
+ ].filter((name) => read[name] !== void 0).map((name) => [name, read[name]])), MIRROR, noOtherPage), mirroredRect(rect));
2123
+ const { subtype: _kind, ...fields } = pageAnnotationPatchOf({
2124
+ subtype: annotation.subtype,
2125
+ ...placed
2126
+ }, MIRROR, noOtherPage);
2127
+ return fields;
2128
+ }
2129
+ /**
2130
+ * `patch` with its `rect` resolved as the engine resolves it on update (the
2131
+ * same step, `pdfResolveRectCommand`):
2132
+ *
2133
+ * - the rect the annotation reads, sent back, is dropped: nothing moves;
2134
+ * - on a drawn kind, another rect becomes the shape fields that put the
2135
+ * drawing there (`shapeForRect`);
2136
+ * - a new rect with a changed shape field is refused (`InvalidArg` on
2137
+ * `rect`): which of the two to follow would be a guess.
2138
+ *
2139
+ * A kind whose shape is its rect keeps it as a plain field. A viewer runs
2140
+ * this before it merges fields it writes itself, so a `rect` behaves the same
2141
+ * from every door. Values `patch` gives come back as given.
2142
+ */
2143
+ function resolveRectCommand(annotation, patch) {
2144
+ const given = patch;
2145
+ if (given.rect === void 0 || !DRAWN_RECT_KINDS.has(annotation.subtype)) return patch;
2146
+ const read = annotation;
2147
+ const shapeNames = shapeFieldsOf(annotation.subtype);
2148
+ const pick = (from, names) => Object.fromEntries(names.filter((name) => from[name] !== void 0).map((name) => [name, from[name]]));
2149
+ const { subtype: _kind, ...placed } = pageAnnotationPatchOf(pdfResolveRectCommand(pdfAnnotationOf({
2150
+ subtype: annotation.subtype,
2151
+ ...pick(read, ["rect", ...shapeNames])
2152
+ }, MIRROR, noOtherPage), pdfAnnotationPatchOf({
2153
+ subtype: annotation.subtype,
2154
+ ...pick(given, ["rect", ...shapeNames])
2155
+ }, MIRROR, noOtherPage)), MIRROR, noOtherPage);
2156
+ const { rect: _rect, ...rest } = given;
2157
+ const out = { ...rest };
2158
+ for (const [name, value] of Object.entries(placed)) out[name] = given[name] !== void 0 && semanticEqual(value, given[name]) ? given[name] : value;
2159
+ return out;
2160
+ }
2161
+ /**
2162
+ * Every page's box is the mirror, so a link's destination makes the round
2163
+ * trip into the file's coordinates and back unchanged, with no real page.
2164
+ */
2165
+ const mirrorEveryPage = () => MIRROR;
2166
+ /**
2167
+ * The patch the engine writes for `patch` on `current`, as a viewer predicts
2168
+ * it: its subtype filled in, and every field that follows from it stated. A
2169
+ * drawn kind's new `rect` becomes the shape fields that put it there, values
2170
+ * sent back as read are dropped, a note's standard review state brings its
2171
+ * state model, a free text's contents and rich text follow each other, a
2172
+ * callout's line stays attached to its moved box, and a measurement's label
2173
+ * follows its points and scale. Values `patch` gives come back as given.
2174
+ *
2175
+ * Throws `InvalidArg` for what the engine refuses without its schemas: a
2176
+ * field the kind doesn't declare, a changed subtype or name, a rect with a
2177
+ * new shape or that isn't finite, and where nothing can follow. The engine
2178
+ * checks the values against the kind's schema when the update reaches it,
2179
+ * and refuses there what this lets through.
2180
+ */
2181
+ function resolveAnnotationPatch(current, patch, options = {}) {
2182
+ const back = pageAnnotationPatchOf(pdfPredictAnnotationPatch(pdfAnnotationOf(current, MIRROR, mirrorEveryPage), pdfAnnotationPatchOf({
2183
+ ...patch,
2184
+ subtype: patch.subtype ?? current.subtype
2185
+ }, MIRROR, mirrorEveryPage), options), MIRROR, mirrorEveryPage);
2186
+ const given = patch;
2187
+ for (const name of Object.keys(back)) if (given[name] !== void 0 && semanticEqual(back[name], given[name])) back[name] = given[name];
2188
+ return back;
2189
+ }
2190
+ /**
2191
+ * `current` as the engine will read it after `patch`: what a viewer shows
2192
+ * while the write is on its way. Fields the engine works out or stamps itself
2193
+ * (a drawn kind's `rect`, the modified date and author) keep their current
2194
+ * value until the engine's answer brings the new one. A field `patch` leaves
2195
+ * alone keeps its very value.
2196
+ */
2197
+ function applyAnnotationPatch(current, patch, options = {}) {
2198
+ return applyResolvedPatch(current, resolveAnnotationPatch(current, patch, options));
2199
+ }
2200
+ /**
2201
+ * The draft the engine writes for `draft`, as a viewer predicts it: every
2202
+ * field that follows from it stated. A box kind placed by its `rect` states
2203
+ * its `box`, a note's standard review state brings its state model, a free
2204
+ * text's rich text and contents follow each other, and a measurement's label
2205
+ * follows its points and scale. Fields left out stay out: the kind's
2206
+ * defaults (`ANNOTATION_DEFAULTS`) say what they read back. Values `draft`
2207
+ * gives come back as given.
2208
+ *
2209
+ * Throws `InvalidArg` for what the engine refuses without its schemas: a
2210
+ * field the kind doesn't declare, a box kind it can't place, and where
2211
+ * nothing can follow. The engine checks the values against the kind's schema
2212
+ * when the create reaches it, and refuses there what this lets through.
2213
+ */
2214
+ function resolveAnnotationDraft(draft, options = {}) {
2215
+ const back = pageAnnotationDraftOf(pdfPredictAnnotationDraft(pdfAnnotationDraftOf(draft, MIRROR, mirrorEveryPage), options), MIRROR, mirrorEveryPage);
2216
+ const given = draft;
2217
+ for (const name of Object.keys(back)) if (given[name] !== void 0 && semanticEqual(back[name], given[name])) back[name] = given[name];
2218
+ return back;
2219
+ }
2220
+ /**
2221
+ * The annotation the engine will read back after creating `draft`: what a
2222
+ * viewer shows while the create is on its way. `context` says what the draft
2223
+ * doesn't: the ref and place it will have, who creates it, and, for a kind
2224
+ * whose `rect` the engine works out from its drawing, that box. The draft is
2225
+ * resolved as `resolveAnnotationDraft` does it, without the engine's schema
2226
+ * check: a create the engine refuses leaves the view when it answers.
2227
+ */
2228
+ function annotationOfDraft(draft, context) {
2229
+ return annotationOfResolvedDraft(resolveAnnotationDraft(draft, { describeFont: context.describeFont }), context);
2230
+ }
2231
+ //#endregion
2232
+ //#region src/search/fold.ts
2233
+ /**
2234
+ * Text folding for literal search: the deterministic normalization applied
2235
+ * to both the page text and the needle so that "Café" finds "cafe" and a
2236
+ * line-wrapped "hello\n world" finds "hello world".
2237
+ *
2238
+ * Fold version 1:
2239
+ * - whitespace runs collapse to a single space (any `\s`, including the
2240
+ * spaces some compatibility decompositions emit) — or are dropped
2241
+ * entirely with `dropWhitespace` (ignoreWhitespace),
2242
+ * - each code point is NFKD-decomposed (ligatures split: "fi" → "fi",
2243
+ * "²" → "2"),
2244
+ * - combining marks are stripped unless `keepMarks`,
2245
+ * - case is folded via upper→lower round-trip unless `keepCase` (this
2246
+ * poor-man's full fold catches "ß"→"ss" and "ς"→"σ", which a plain
2247
+ * `toLowerCase` misses).
2248
+ *
2249
+ * Every folded code unit remembers which original code point produced it
2250
+ * (`map`), so match ranges found in folded space translate back to exact
2251
+ * original-text ranges — the property the whole anchor stage rests on.
2252
+ *
2253
+ * Pre-folded corpus artifacts store this fold's output; bump
2254
+ * `SEARCH_FOLD_VERSION` on any semantic change so stored corpora are
2255
+ * rebuilt instead of silently mismatching fresh needles.
2256
+ */
2257
+ /** Version stamp for persisted pre-folded corpus artifacts. */
2258
+ const SEARCH_FOLD_VERSION = 1;
2259
+ const WHITESPACE = /\s/;
2260
+ const MARKS = /\p{M}/gu;
2261
+ function foldText(original, options = {}) {
2262
+ const units = [];
2263
+ const map = [];
2264
+ let lastWasSpace = false;
2265
+ let index = 0;
2266
+ for (const cp of original) {
2267
+ let piece;
2268
+ if (WHITESPACE.test(cp)) piece = " ";
2269
+ else {
2270
+ piece = cp.normalize("NFKD");
2271
+ if (!options.keepMarks) piece = piece.replace(MARKS, "");
2272
+ if (!options.keepCase) piece = piece.toUpperCase().toLowerCase();
2273
+ }
2274
+ for (let u = 0; u < piece.length; u++) {
2275
+ const unit = piece[u];
2276
+ if (WHITESPACE.test(unit)) {
2277
+ if (options.dropWhitespace || lastWasSpace) continue;
2278
+ units.push(" ");
2279
+ map.push(index);
2280
+ lastWasSpace = true;
2281
+ } else {
2282
+ units.push(unit);
2283
+ map.push(index);
2284
+ lastWasSpace = false;
2285
+ }
2286
+ }
2287
+ index += cp.length;
2288
+ }
2289
+ return {
2290
+ folded: units.join(""),
2291
+ map: Uint32Array.from(map),
2292
+ original
2293
+ };
2294
+ }
2295
+ /** Code-unit length of the code point starting at `index` (1 or 2). */
2296
+ function codePointLengthAt(text, index) {
2297
+ const unit = text.charCodeAt(index);
2298
+ if (unit >= 55296 && unit <= 56319 && index + 1 < text.length) {
2299
+ const next = text.charCodeAt(index + 1);
2300
+ if (next >= 56320 && next <= 57343) return 2;
2301
+ }
2302
+ return 1;
2303
+ }
2304
+ /**
2305
+ * Translate a match found in folded space back to a range of the original
2306
+ * text (code units).
2307
+ * The range covers whole original code points: a hit on either folded
2308
+ * half of "fi" spans the full ligature, and a hit ending on a collapsed
2309
+ * space extends only through the first whitespace char of the run (the
2310
+ * remainder is presentation, not match).
2311
+ */
2312
+ function toOriginalRange(folded, foldedStart, foldedLength) {
2313
+ const start = folded.map[foldedStart];
2314
+ const lastOriginal = folded.map[foldedStart + foldedLength - 1];
2315
+ return {
2316
+ start,
2317
+ count: lastOriginal + codePointLengthAt(folded.original, lastOriginal) - start
2318
+ };
2319
+ }
2320
+ //#endregion
2321
+ //#region src/measure/units.ts
2322
+ const METRES = {
2323
+ pt: .0254 / 72,
2324
+ mm: .001,
2325
+ cm: .01,
2326
+ m: 1,
2327
+ km: 1e3,
2328
+ in: .0254,
2329
+ ft: .3048,
2330
+ yd: .9144,
2331
+ mi: 1609.344
2332
+ };
2333
+ const squareOf = (unit) => `${unit}2`;
2334
+ function squareMetres(unit) {
2335
+ return unit === "ha" ? 1e4 : unit === "acre" ? 4046.8564224 : METRES[unit.slice(0, -1)] ** 2;
2336
+ }
2337
+ const areaUnitLabel = (unit) => unit.endsWith("2") ? unit.slice(0, -1) + "²" : unit;
2338
+ //#endregion
2339
+ //#region src/measure/calibrate.ts
2340
+ function invalid(message) {
2341
+ return new EngineError(EngineErrorCode.InvalidArg, message);
2342
+ }
2343
+ function positive(value) {
2344
+ if (!Number.isFinite(value) || value <= 0) throw invalid("Calibration requires finite positive lengths and scales");
2345
+ }
2346
+ function build(perPoint, unit, ratio, format) {
2347
+ positive(Math.fround(perPoint));
2348
+ const precision = format.precision ?? 100;
2349
+ if (!Number.isInteger(Math.log10(precision)) || precision < 1 || precision > 1e9) throw invalid("Invalid decimal precision");
2350
+ const areaUnit = format.areaUnit ?? squareOf(unit);
2351
+ const common = {
2352
+ precision,
2353
+ ...format.fixed === void 0 ? {} : { fixed: format.fixed }
2354
+ };
2355
+ const distance = format.imperialFeetInches ? [{
2356
+ unit: "ft",
2357
+ conversion: Math.fround(METRES[unit] / METRES.ft)
2358
+ }, {
2359
+ unit: "in",
2360
+ conversion: 12,
2361
+ fraction: "fraction",
2362
+ precision: format.fractionalPrecision ?? 16,
2363
+ ..."fixed" in common ? { fixed: common.fixed } : {}
2364
+ }] : [{
2365
+ unit,
2366
+ conversion: 1,
2367
+ ...common
2368
+ }];
2369
+ if (format.imperialFeetInches && (!Number.isInteger(distance[1].precision) || distance[1].precision < 1 || distance[1].precision > 2147483647)) throw invalid("Invalid fraction denominator");
2370
+ const measure = {
2371
+ subtype: "rectilinear",
2372
+ ratio,
2373
+ x: [{
2374
+ unit,
2375
+ conversion: Math.fround(perPoint),
2376
+ ...common
2377
+ }],
2378
+ distance,
2379
+ area: [{
2380
+ unit: areaUnitLabel(areaUnit),
2381
+ conversion: Math.fround(METRES[unit] ** 2 / squareMetres(areaUnit)),
2382
+ ...common
2383
+ }]
2384
+ };
2385
+ assertWritableMeasure(measure);
2386
+ return measure;
2387
+ }
2388
+ function measureFromKnownLength(userSpaceLength, real, format = {}) {
2389
+ positive(userSpaceLength);
2390
+ positive(real.value);
2391
+ const perPoint = real.value / userSpaceLength;
2392
+ return build(perPoint, real.unit, `1 in = ${Number((perPoint * 72).toPrecision(8))} ${real.unit}`, format);
2393
+ }
2394
+ function measureFromRatio(paper, real, unit, options = {}) {
2395
+ const { userUnit = 1, ...format } = options;
2396
+ positive(paper);
2397
+ positive(real);
2398
+ positive(userUnit);
2399
+ return build(userUnit / 72 * .0254 * (real / paper) / METRES[unit], unit, `${paper}:${real}`, format);
2400
+ }
2401
+ //#endregion
2402
+ export { pdfAppearanceTurnOf as $, createPdfTextLayout as A, renderTargetArea as At, mapActionNode as B, resolveAnnotationPatch as C, pageQuadOf as Ct, pageGeometryOf as D, pageTransform as Dt, createTextLayout as E, pdfRectOf as Et, pdfAnnotationOf as F, mapLinkTarget as G, mapAnnotationActions as H, pdfAnnotationPatchOf as I, pageDestinationOf as J, mapPageActions as K, pageMeasureOf as L, pageAnnotationOf as M, renderSize as Mt, pageAnnotationPatchOf as N, pageSearchSliceOf as O, renderAreaTransform as Ot, pdfAnnotationDraftOf as P, applyResolvedPatch as Q, pageViewportsOf as R, resolveAnnotationDraft as S, pagePointOf as St, shapeForRect as T, pdfQuadOf as Tt, mapDocumentActions as U, mapActionTree as V, mapFieldActions as W, ANNOTATION_DEFAULTS as X, pdfDestinationOf as Y, annotationDefaultsOf as Z, pagePointUnturned as _, isAbortError as _t, squareMetres as a, DEFAULT_BUNDLE_LIMITS as at, pageTurnOfDrawn as b, isRotatedGeometryRun as bt, foldText as c, ANNOTATION_RESOURCE_ROLE_NAMES as ct, appearanceTurnOf as d, hasAnnotationResources as dt, toAttachmentRef as et, applyAnnotationPatch as f, resolveAnnotationResources as ft, pagePointTurned as g, AbortError as gt, pageGlyphLooseQuad as h, resolveBinarySource as ht, areaUnitLabel as i, bundleCodec as it, pageAnnotationDraftOf as j, renderTransform as jt, pageTextSegmentOf as k, renderMatrix as kt, toOriginalRange as l, annotationResourceBuffers as lt, pageGlyphLooseBounds as m, sniffBinaryMetadata as mt, measureFromRatio as n, assertAnnotationBundle as nt, squareOf as o, assertWithinLimit as ot, drawnPointsOf as p, withFileFromResource as pt, mapTriggerActions as q, METRES as r, assertAnnotationBundleManifest as rt, SEARCH_FOLD_VERSION as s, manifestBytesOf as st, measureFromKnownLength as t, AnnotationTransfer as tt, annotationOfDraft as u, assertAnnotationResources as ut, pagePointsBounds as v, glyphLooseBounds as vt, resolveRectCommand as w, pdfPointOf as wt, pageTurnOfUpright as x, pageBoxOf as xt, pageQuadBounds as y, glyphLooseQuad as yt, pdfMeasureOf as z };
2403
+
2404
+ //# sourceMappingURL=calibrate-7T3fnP8-.js.map