@inkbar/core 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -96,6 +96,15 @@ function quadFromRect(r) {
96
96
  }
97
97
  };
98
98
  }
99
+ /** One quad covering `r`, in the corner order of `quadFromRect`. */
100
+ function quadsFromRect(r) {
101
+ return [quadFromRect(r)];
102
+ }
103
+ /** The bounding box of every corner of `quads`. */
104
+ function rectFromQuads(quads) {
105
+ if (quads.length === 0) throw new RangeError("rectFromQuads needs at least one quad");
106
+ return unionRects(quads.map(rectFromQuad));
107
+ }
99
108
  function unionRects(rects) {
100
109
  if (rects.length === 0) throw new RangeError("unionRects needs at least one rect");
101
110
  let x1 = Infinity;
@@ -251,6 +260,102 @@ function readonly(w) {
251
260
  };
252
261
  }
253
262
  //#endregion
263
+ //#region src/model/records.ts
264
+ /**
265
+ * A keyed list of plain records. Records are stored as frozen copies, so a change's `before` and `after` can be
266
+ * kept as history entries without copying.
267
+ */
268
+ function createRecordStore(key, opts = {}) {
269
+ const list = writable((opts.initial ?? []).map(frozen));
270
+ const emitter = new Emitter();
271
+ let destroyed = false;
272
+ function commit(next, change) {
273
+ if (destroyed) return;
274
+ list.set(next);
275
+ emitter.emit("change", change);
276
+ opts.onChange?.(next, change);
277
+ }
278
+ return {
279
+ key,
280
+ list: readonly(list),
281
+ get: (id) => list.get().find((r) => r.id === id),
282
+ add(record) {
283
+ if (list.get().some((r) => r.id === record.id)) throw new InkError("DuplicateId", `A record with id ${record.id} already exists`, {
284
+ key,
285
+ id: record.id
286
+ });
287
+ const stored = frozen(record);
288
+ commit([...list.get(), stored], {
289
+ kind: "add",
290
+ record: stored
291
+ });
292
+ return stored;
293
+ },
294
+ update(id, patch) {
295
+ const before = list.get().find((r) => r.id === id);
296
+ if (destroyed || !before) return void 0;
297
+ const after = Object.freeze({
298
+ ...before,
299
+ ...patch,
300
+ id: before.id
301
+ });
302
+ commit(list.get().map((r) => r.id === id ? after : r), {
303
+ kind: "update",
304
+ id,
305
+ before,
306
+ after
307
+ });
308
+ return after;
309
+ },
310
+ remove(id) {
311
+ const record = list.get().find((r) => r.id === id);
312
+ if (destroyed || !record) return void 0;
313
+ commit(list.get().filter((r) => r.id !== id), {
314
+ kind: "remove",
315
+ record
316
+ });
317
+ return record;
318
+ },
319
+ replaceAll(incoming, reason) {
320
+ const prev = new Map(list.get().map((r) => [r.id, r]));
321
+ const added = [];
322
+ const updated = [];
323
+ const next = incoming.map((r) => {
324
+ const old = prev.get(r.id);
325
+ if (!old) {
326
+ added.push(r.id);
327
+ return frozen(r);
328
+ }
329
+ if (shallowEqual(old, r)) return old;
330
+ updated.push(r.id);
331
+ return frozen(r);
332
+ });
333
+ const kept = new Set(next.map((r) => r.id));
334
+ commit(next, {
335
+ kind: "replace",
336
+ reason,
337
+ added,
338
+ updated,
339
+ removed: [...prev.keys()].filter((id) => !kept.has(id))
340
+ });
341
+ },
342
+ on: (event, fn) => emitter.on(event, fn),
343
+ destroy() {
344
+ destroyed = true;
345
+ emitter.clear();
346
+ }
347
+ };
348
+ }
349
+ /** Freezes a copy, so the caller's object stays theirs; an already frozen record is kept. */
350
+ function frozen(record) {
351
+ return Object.isFrozen(record) ? record : Object.freeze({ ...record });
352
+ }
353
+ function shallowEqual(a, b) {
354
+ const ka = Object.keys(a);
355
+ const kb = Object.keys(b);
356
+ return ka.length === kb.length && ka.every((k) => Object.hasOwn(b, k) && a[k] === b[k]);
357
+ }
358
+ //#endregion
254
359
  //#region src/model/selection.ts
255
360
  const union = (a, b) => {
256
361
  const x = Math.min(a.x, b.x);
@@ -305,6 +410,167 @@ function mergeLineRects(rects, tolerance = 2, gap) {
305
410
  return runs.map(quadFromRect);
306
411
  }
307
412
  //#endregion
413
+ //#region src/pdfjs/glyphs.ts
414
+ const IDENTITY = [
415
+ 1,
416
+ 0,
417
+ 0,
418
+ 1,
419
+ 0,
420
+ 0
421
+ ];
422
+ const FONT_IDENTITY = [
423
+ .001,
424
+ 0,
425
+ 0,
426
+ .001,
427
+ 0,
428
+ 0
429
+ ];
430
+ /**
431
+ * The glyphs a page draws, in content order, with where each starts and where its own width ends
432
+ * in user space. The pen moves the way PDF.js's canvas renderer moves it (`showText` and
433
+ * `showType3Text`): text matrix and moves, the current transformation matrix, font size and font
434
+ * matrix, character and word spacing, horizontal scaling, and `TJ` kerning. Glyphs in vertical
435
+ * fonts are left out. Annotation appearances are not read.
436
+ */
437
+ async function pageGlyphs(page, pdfjs) {
438
+ const ops = pdfjs.OPS;
439
+ const list = await page.getOperatorList({ annotationMode: pdfjs.AnnotationMode.DISABLE });
440
+ const out = [];
441
+ let s = {
442
+ ctm: IDENTITY,
443
+ textMatrix: IDENTITY,
444
+ x: 0,
445
+ y: 0,
446
+ lineX: 0,
447
+ lineY: 0,
448
+ font: {},
449
+ fontSize: 0,
450
+ fontDirection: 1,
451
+ charSpacing: 0,
452
+ wordSpacing: 0,
453
+ hScale: 1,
454
+ leading: 0,
455
+ rise: 0
456
+ };
457
+ const saved = [];
458
+ const save = () => {
459
+ saved.push(s);
460
+ s = { ...s };
461
+ };
462
+ const restore = () => {
463
+ s = saved.pop() ?? s;
464
+ };
465
+ const setFont = (name, size) => {
466
+ let font = {};
467
+ try {
468
+ font = page.commonObjs.get(String(name)) ?? {};
469
+ } catch {}
470
+ const n = Number(size);
471
+ s.font = font;
472
+ s.fontDirection = n < 0 ? -1 : 1;
473
+ s.fontSize = Math.abs(n);
474
+ };
475
+ const moveText = (x, y) => {
476
+ s.x = s.lineX += x;
477
+ s.y = s.lineY += y;
478
+ };
479
+ const show = (glyphs) => {
480
+ const { font, fontSize, fontDirection, charSpacing, wordSpacing } = s;
481
+ if (fontSize === 0) return;
482
+ const fontMatrix = font.fontMatrix ?? FONT_IDENTITY;
483
+ const toUser = multiplyMatrix(s.textMatrix, s.ctm);
484
+ const at = (x) => applyMatrix(toUser, {
485
+ x: s.x + x,
486
+ y: s.y + s.rise
487
+ });
488
+ const hScale = s.hScale * fontDirection;
489
+ let x = 0;
490
+ for (const g of glyphs) {
491
+ if (typeof g === "number") {
492
+ x += -g * fontSize / 1e3;
493
+ continue;
494
+ }
495
+ if (!g) continue;
496
+ const spacing = charSpacing + (g.isSpace ? wordSpacing : 0);
497
+ const width = font.isType3Font ? (g.width * fontMatrix[0] + fontMatrix[4]) * fontSize : g.width * fontSize * fontMatrix[0];
498
+ if (!font.vertical) out.push({
499
+ text: g.unicode,
500
+ start: at(x * hScale),
501
+ end: at((x + width) * hScale)
502
+ });
503
+ x += width + spacing * (font.isType3Font ? 1 : fontDirection);
504
+ }
505
+ s.x += x * hScale;
506
+ };
507
+ for (let i = 0; i < list.fnArray.length; i++) {
508
+ const args = list.argsArray[i];
509
+ switch (list.fnArray[i]) {
510
+ case ops.save:
511
+ save();
512
+ break;
513
+ case ops.restore:
514
+ restore();
515
+ break;
516
+ case ops.paintFormXObjectBegin:
517
+ save();
518
+ if (args?.[0]) s.ctm = multiplyMatrix(args[0], s.ctm);
519
+ break;
520
+ case ops.paintFormXObjectEnd:
521
+ restore();
522
+ break;
523
+ case ops.transform:
524
+ s.ctm = multiplyMatrix(args, s.ctm);
525
+ break;
526
+ case ops.beginText:
527
+ s.textMatrix = IDENTITY;
528
+ s.x = s.lineX = s.y = s.lineY = 0;
529
+ break;
530
+ case ops.setTextMatrix:
531
+ s.textMatrix = args[0];
532
+ s.x = s.lineX = s.y = s.lineY = 0;
533
+ break;
534
+ case ops.moveText:
535
+ moveText(Number(args[0]), Number(args[1]));
536
+ break;
537
+ case ops.setLeadingMoveText:
538
+ s.leading = Number(args[1]);
539
+ moveText(Number(args[0]), Number(args[1]));
540
+ break;
541
+ case ops.setLeading:
542
+ s.leading = -Number(args[0]);
543
+ break;
544
+ case ops.nextLine:
545
+ moveText(0, s.leading);
546
+ break;
547
+ case ops.setFont:
548
+ setFont(args[0], args[1]);
549
+ break;
550
+ case ops.setGState:
551
+ for (const [key, value] of args[0]) if (key === "Font") {
552
+ const [name, size] = value;
553
+ setFont(name, size);
554
+ }
555
+ break;
556
+ case ops.setCharSpacing:
557
+ s.charSpacing = Number(args[0]);
558
+ break;
559
+ case ops.setWordSpacing:
560
+ s.wordSpacing = Number(args[0]);
561
+ break;
562
+ case ops.setHScale:
563
+ s.hScale = Number(args[0]) / 100;
564
+ break;
565
+ case ops.setTextRise:
566
+ s.rise = Number(args[0]);
567
+ break;
568
+ case ops.showText: show(args[0]);
569
+ }
570
+ }
571
+ return out;
572
+ }
573
+ //#endregion
308
574
  //#region src/pdfjs/load.ts
309
575
  const REQUIRED = [
310
576
  "worker",
@@ -1792,6 +2058,7 @@ function isProduction() {
1792
2058
  }
1793
2059
  //#endregion
1794
2060
  //#region src/model/annotations/history.ts
2061
+ const noop = () => {};
1795
2062
  /**
1796
2063
  * Snapshot-based undo and redo over an uncontrolled store.
1797
2064
  *
@@ -1811,6 +2078,8 @@ function createHistory(store, opts = {}) {
1811
2078
  batch(fn) {
1812
2079
  fn();
1813
2080
  },
2081
+ track: () => noop,
2082
+ react: () => noop,
1814
2083
  destroy() {}
1815
2084
  };
1816
2085
  const limit = opts.limit ?? 200;
@@ -1818,14 +2087,84 @@ function createHistory(store, opts = {}) {
1818
2087
  const redoStack = [];
1819
2088
  let previous = store.list.get();
1820
2089
  let replaying = false;
1821
- /** Open batch depth; while above zero, changes update `previous` but push no entry. */
2090
+ /**
2091
+ * Open step depth (batches, and an edit while its reactions run); while above zero, changes
2092
+ * update the baselines but push no entry.
2093
+ */
1822
2094
  let depth = 0;
1823
- /** The list when the outermost batch opened (or was restarted by a replace). */
1824
- let batchStart = null;
2095
+ /** The lists when the outermost step opened (or were restarted by a replace). */
2096
+ let start = null;
2097
+ /** Each tracked store with its list as of the last change history saw. */
2098
+ const tracked = /* @__PURE__ */ new Map();
2099
+ const reactions = /* @__PURE__ */ new Set();
1825
2100
  const sync = () => {
1826
2101
  canUndo.set(undoStack.length > 0);
1827
2102
  canRedo.set(redoStack.length > 0);
1828
2103
  };
2104
+ const push = (entry) => {
2105
+ undoStack.push(entry);
2106
+ if (undoStack.length > limit) undoStack.shift();
2107
+ redoStack.length = 0;
2108
+ sync();
2109
+ };
2110
+ /**
2111
+ * Forgets `recordStore`'s records in every entry, so undo and redo leave it alone. An entry
2112
+ * left with nothing to replay (no annotation change, no other store) is dropped.
2113
+ */
2114
+ const dropEntries = (recordStore) => {
2115
+ for (const stack of [undoStack, redoStack]) {
2116
+ const kept = stack.filter((entry) => {
2117
+ if (!entry.records.delete(recordStore)) return true;
2118
+ const { before, after } = entry.annotations;
2119
+ return entry.records.size > 0 || !sameList(before, after);
2120
+ });
2121
+ stack.splice(0, stack.length, ...kept);
2122
+ }
2123
+ sync();
2124
+ };
2125
+ const resetStacks = () => {
2126
+ undoStack.length = 0;
2127
+ redoStack.length = 0;
2128
+ sync();
2129
+ };
2130
+ /** Opens the outermost step from `annotations` and every tracked store's baseline. */
2131
+ const open = (annotations) => {
2132
+ const records = /* @__PURE__ */ new Map();
2133
+ for (const [s, t] of tracked) records.set(s, t.previous);
2134
+ start = {
2135
+ annotations,
2136
+ records
2137
+ };
2138
+ };
2139
+ /**
2140
+ * Closes the outermost step: one entry for everything that changed since it opened. A step
2141
+ * whose lists end where they began (the same objects in the same order) records nothing.
2142
+ */
2143
+ const close = () => {
2144
+ const from = start;
2145
+ start = null;
2146
+ if (!from) return;
2147
+ const annotations = {
2148
+ before: from.annotations,
2149
+ after: store.list.get()
2150
+ };
2151
+ previous = annotations.after;
2152
+ const records = /* @__PURE__ */ new Map();
2153
+ for (const [s, t] of tracked) {
2154
+ const before = from.records.get(s) ?? t.previous;
2155
+ const after = s.list.get();
2156
+ t.previous = after;
2157
+ if (!sameList(before, after)) records.set(s, {
2158
+ before,
2159
+ after
2160
+ });
2161
+ }
2162
+ if (records.size === 0 && sameList(annotations.before, annotations.after)) return;
2163
+ push({
2164
+ annotations,
2165
+ records
2166
+ });
2167
+ };
1829
2168
  let destroyed = false;
1830
2169
  const unsubscribe = store.on("change", (change) => {
1831
2170
  const current = store.list.get();
@@ -1838,39 +2177,40 @@ function createHistory(store, opts = {}) {
1838
2177
  const f = change.fields.get(a.id);
1839
2178
  return f && withEmbeddedFields(a, f) || a;
1840
2179
  });
1841
- for (const entry of [...undoStack, ...redoStack]) {
1842
- entry.before = fill(entry.before);
1843
- entry.after = fill(entry.after);
2180
+ for (const { annotations } of [...undoStack, ...redoStack]) {
2181
+ annotations.before = fill(annotations.before);
2182
+ annotations.after = fill(annotations.after);
1844
2183
  }
1845
- if (batchStart) batchStart = fill(batchStart);
2184
+ if (start) start.annotations = fill(start.annotations);
1846
2185
  previous = current;
1847
2186
  return;
1848
2187
  }
1849
2188
  if (change.kind === "replace") {
1850
- undoStack.length = 0;
1851
- redoStack.length = 0;
1852
2189
  previous = current;
1853
- if (depth > 0) batchStart = current;
1854
- sync();
2190
+ if (depth > 0) open(current);
2191
+ resetStacks();
1855
2192
  return;
1856
2193
  }
1857
2194
  if (depth > 0) {
1858
2195
  previous = current;
2196
+ for (const fn of [...reactions]) fn(change);
1859
2197
  return;
1860
2198
  }
1861
- undoStack.push({
1862
- before: previous,
1863
- after: current
1864
- });
1865
- if (undoStack.length > limit) undoStack.shift();
1866
- redoStack.length = 0;
1867
- previous = current;
1868
- sync();
2199
+ open(previous);
2200
+ depth++;
2201
+ try {
2202
+ for (const fn of [...reactions]) fn(change);
2203
+ } finally {
2204
+ if (--depth === 0 && !destroyed) close();
2205
+ }
1869
2206
  });
1870
- const apply = (list) => {
2207
+ /** Replays a step's lists under the `replaying` guard, skipping stores no longer tracked. */
2208
+ const apply = (entry, side) => {
1871
2209
  replaying = true;
1872
2210
  try {
1873
- store.replaceAll([...list], "history");
2211
+ const { annotations } = entry;
2212
+ if (!sameList(annotations.before, annotations.after)) store.replaceAll([...annotations[side]], "history");
2213
+ for (const [s, snapshot] of entry.records) if (tracked.has(s)) s.replaceAll([...snapshot[side]], "history");
1874
2214
  } finally {
1875
2215
  replaying = false;
1876
2216
  }
@@ -1882,14 +2222,14 @@ function createHistory(store, opts = {}) {
1882
2222
  const entry = undoStack.pop();
1883
2223
  if (destroyed || !entry) return;
1884
2224
  redoStack.push(entry);
1885
- apply(entry.before);
2225
+ apply(entry, "before");
1886
2226
  sync();
1887
2227
  },
1888
2228
  redo() {
1889
2229
  const entry = redoStack.pop();
1890
2230
  if (destroyed || !entry) return;
1891
2231
  undoStack.push(entry);
1892
- apply(entry.after);
2232
+ apply(entry, "after");
1893
2233
  sync();
1894
2234
  },
1895
2235
  batch(fn) {
@@ -1897,45 +2237,78 @@ function createHistory(store, opts = {}) {
1897
2237
  fn();
1898
2238
  return;
1899
2239
  }
1900
- if (depth++ === 0) batchStart = store.list.get();
2240
+ if (depth++ === 0) open(store.list.get());
1901
2241
  try {
1902
2242
  const result = fn();
1903
2243
  if (result !== null && typeof result === "object" && typeof result.then === "function" && !isProduction()) console.warn("[inkbar] history.batch(fn): fn returned a promise. Changes after its first await are not part of the batch.");
1904
2244
  } finally {
1905
- if (--depth === 0 && !destroyed) {
1906
- const before = batchStart;
1907
- batchStart = null;
1908
- const after = store.list.get();
1909
- if (before && !sameAnnotations(before, after)) {
1910
- undoStack.push({
1911
- before,
1912
- after
1913
- });
1914
- if (undoStack.length > limit) undoStack.shift();
1915
- redoStack.length = 0;
1916
- sync();
1917
- }
1918
- }
2245
+ if (--depth === 0 && !destroyed) close();
1919
2246
  }
1920
2247
  },
2248
+ track(recordStore) {
2249
+ if (destroyed || tracked.has(recordStore)) return noop;
2250
+ const unsubscribeStore = recordStore.on("change", (change) => {
2251
+ const t = tracked.get(recordStore);
2252
+ if (!t) return;
2253
+ const before = t.previous;
2254
+ const current = recordStore.list.get();
2255
+ t.previous = current;
2256
+ if (replaying || change.kind === "replace" && change.reason === "history") return;
2257
+ if (change.kind === "replace") {
2258
+ if (depth > 0 && start) start.records.set(recordStore, current);
2259
+ dropEntries(recordStore);
2260
+ return;
2261
+ }
2262
+ if (depth > 0) return;
2263
+ push({
2264
+ annotations: {
2265
+ before: previous,
2266
+ after: previous
2267
+ },
2268
+ records: /* @__PURE__ */ new Map([[recordStore, {
2269
+ before,
2270
+ after: current
2271
+ }]])
2272
+ });
2273
+ });
2274
+ tracked.set(recordStore, {
2275
+ previous: recordStore.list.get(),
2276
+ unsubscribe: unsubscribeStore
2277
+ });
2278
+ if (start) start.records.set(recordStore, recordStore.list.get());
2279
+ return () => {
2280
+ if (tracked.get(recordStore)?.unsubscribe !== unsubscribeStore) return;
2281
+ unsubscribeStore();
2282
+ tracked.delete(recordStore);
2283
+ start?.records.delete(recordStore);
2284
+ };
2285
+ },
2286
+ react(fn) {
2287
+ if (destroyed) return noop;
2288
+ const run = (edit) => fn(edit);
2289
+ reactions.add(run);
2290
+ return () => {
2291
+ reactions.delete(run);
2292
+ };
2293
+ },
1921
2294
  destroy() {
1922
2295
  destroyed = true;
1923
- batchStart = null;
2296
+ start = null;
1924
2297
  unsubscribe();
1925
- undoStack.length = 0;
1926
- redoStack.length = 0;
1927
- sync();
2298
+ for (const t of tracked.values()) t.unsubscribe();
2299
+ tracked.clear();
2300
+ reactions.clear();
2301
+ resetStacks();
1928
2302
  },
1929
2303
  clear() {
1930
- undoStack.length = 0;
1931
- redoStack.length = 0;
1932
2304
  previous = store.list.get();
1933
- sync();
2305
+ for (const [s, t] of tracked) t.previous = s.list.get();
2306
+ resetStacks();
1934
2307
  }
1935
2308
  };
1936
2309
  }
1937
- /** The same annotation objects in the same order. */
1938
- function sameAnnotations(a, b) {
2310
+ /** The same objects in the same order. */
2311
+ function sameList(a, b) {
1939
2312
  return a === b || a.length === b.length && a.every((x, i) => x === b[i]);
1940
2313
  }
1941
2314
  //#endregion
@@ -2382,6 +2755,7 @@ function createAnnotationStore(opts) {
2382
2755
  console.warn(`[inkbar] ControlledMutationIgnored: the host did not apply a ${entry.change.kind} for annotation ${entry.id}. In controlled mode, pass the list from onChange back into the store.`);
2383
2756
  }
2384
2757
  function commit(next, change) {
2758
+ if (change.kind !== "replace") emitter.emit("edit", change);
2385
2759
  if (controlled) {
2386
2760
  if (change.kind !== "replace") {
2387
2761
  const key = changeKey(change);
@@ -2480,6 +2854,7 @@ function createAnnotationStore(opts) {
2480
2854
  after
2481
2855
  };
2482
2856
  if (controlled && before.source === "pdf") {
2857
+ emitter.emit("edit", change);
2483
2858
  list.set(swap(list.get()));
2484
2859
  if (proposed) proposed = next;
2485
2860
  emitter.emit("change", change);
@@ -2578,6 +2953,30 @@ function createAnnotationStore(opts) {
2578
2953
  };
2579
2954
  }
2580
2955
  //#endregion
2956
+ //#region src/model/measure-char.ts
2957
+ /**
2958
+ * Measures characters on an offscreen canvas in the font family PDF.js reports for a run, the
2959
+ * same family its text layer measures with. Undefined where there is no canvas (Node), so text
2960
+ * items carry no advances there. Widths are cached per family and character.
2961
+ */
2962
+ function canvasMeasureChar() {
2963
+ if (typeof OffscreenCanvas === "undefined") return void 0;
2964
+ const ctx = new OffscreenCanvas(1, 1).getContext("2d");
2965
+ if (!ctx) return void 0;
2966
+ const cache = /* @__PURE__ */ new Map();
2967
+ return (char, family) => {
2968
+ let widths = cache.get(family);
2969
+ if (!widths) cache.set(family, widths = /* @__PURE__ */ new Map());
2970
+ let w = widths.get(char);
2971
+ if (w === void 0) {
2972
+ ctx.font = `100px ${family}`;
2973
+ w = ctx.measureText(char).width;
2974
+ widths.set(char, w);
2975
+ }
2976
+ return w;
2977
+ };
2978
+ }
2979
+ //#endregion
2581
2980
  //#region src/model/page-cache.ts
2582
2981
  function createPageCache(fetch) {
2583
2982
  const cache = /* @__PURE__ */ new Map();
@@ -2618,6 +3017,235 @@ function pageInfoFromProxy(page, index) {
2618
3017
  function normaliseRotation(rotate) {
2619
3018
  return (Math.round(rotate / 90) * 90 % 360 + 360) % 360;
2620
3019
  }
3020
+ /** How far off the baseline, as a fraction of the font size, an item can sit and still share the line. */
3021
+ const SAME_LINE = .5;
3022
+ const FOLD = {};
3023
+ for (const c of "‘’‚‛") FOLD[c] = "'";
3024
+ for (const c of "“”„‟") FOLD[c] = "\"";
3025
+ for (const c of "‐‑‒–—―") FOLD[c] = "-";
3026
+ /** A line ending in one of these joins to the next line's word: hyphen, U+2010, U+2011, soft hyphen. */
3027
+ const JOINING_HYPHENS = /* @__PURE__ */ new Set([
3028
+ "-",
3029
+ "‐",
3030
+ "‑",
3031
+ "­"
3032
+ ]);
3033
+ const MARKS = /\p{M}/gu;
3034
+ const WHITESPACE = /^\s$/u;
3035
+ /** Sticky: set `lastIndex` to test the code point at that index without slicing. */
3036
+ const LETTER_AT = /\p{L}/uy;
3037
+ /** One code point to its normalised form: NFKD, marks stripped, quotes and dashes folded. */
3038
+ function fold(ch) {
3039
+ const out = ch.normalize("NFKD").replace(MARKS, "");
3040
+ let s = "";
3041
+ for (const c of out) s += FOLD[c] ?? c;
3042
+ return s;
3043
+ }
3044
+ function letterAt(s, i) {
3045
+ LETTER_AT.lastIndex = i;
3046
+ return LETTER_AT.test(s);
3047
+ }
3048
+ const isSpace = (ch) => ch !== void 0 && WHITESPACE.test(ch);
3049
+ function lineGap(prev, next) {
3050
+ const dx = prev.quad.p4.x - prev.quad.p3.x;
3051
+ const dy = prev.quad.p4.y - prev.quad.p3.y;
3052
+ const len = Math.hypot(dx, dy);
3053
+ const ux = len === 0 ? 1 : dx / len;
3054
+ const uy = len === 0 ? 0 : dy / len;
3055
+ const rtl = prev.dir === "rtl" && next.dir === "rtl";
3056
+ const from = rtl ? next.quad.p4 : prev.quad.p4;
3057
+ const to = rtl ? prev.quad.p3 : next.quad.p3;
3058
+ const sx = to.x - from.x;
3059
+ const sy = to.y - from.y;
3060
+ const size = Math.max(prev.fontSize, next.fontSize);
3061
+ return {
3062
+ gap: sx * ux + sy * uy,
3063
+ sameLine: Math.abs(sx * -uy + sy * ux) <= SAME_LINE * size
3064
+ };
3065
+ }
3066
+ /**
3067
+ * Joins a page's items the way the page reads: in content order with nothing between them,
3068
+ * a space after an item that ends its line, and a space where two items on a line sit further
3069
+ * apart than `GAP_SPACE` of the font size. Neither adds a space next to whitespace already there.
3070
+ */
3071
+ function searchableText(content) {
3072
+ const chars = [];
3073
+ const item = [];
3074
+ const offset = [];
3075
+ const eolSpace = /* @__PURE__ */ new Set();
3076
+ let prev;
3077
+ const space = (eol) => {
3078
+ if (eol) eolSpace.add(chars.length);
3079
+ chars.push(" ");
3080
+ item.push(-1);
3081
+ offset.push(-1);
3082
+ };
3083
+ content.items.forEach((it, i) => {
3084
+ if (it.str === "") {
3085
+ if (it.hasEOL && chars.length > 0 && !isSpace(chars[chars.length - 1])) space(true);
3086
+ return;
3087
+ }
3088
+ if (prev && chars.length > 0 && !isSpace(chars[chars.length - 1])) {
3089
+ const { gap, sameLine } = lineGap(prev, it);
3090
+ const size = Math.max(prev.fontSize, it.fontSize);
3091
+ if (!isSpace(it.str[0]) && (!sameLine || gap > .15 * size)) space(false);
3092
+ }
3093
+ for (let k = 0; k < it.str.length; k++) {
3094
+ chars.push(it.str[k]);
3095
+ item.push(i);
3096
+ offset.push(k);
3097
+ }
3098
+ if (it.hasEOL && !isSpace(it.str[it.str.length - 1])) space(true);
3099
+ prev = it;
3100
+ });
3101
+ const raw = chars.join("");
3102
+ let norm = "";
3103
+ const map = [];
3104
+ for (let r = 0; r < raw.length;) {
3105
+ const ch = String.fromCodePoint(raw.codePointAt(r));
3106
+ if (JOINING_HYPHENS.has(ch) && eolSpace.has(r + 1) && letterAt(raw, r + 2)) {
3107
+ r += 2;
3108
+ continue;
3109
+ }
3110
+ for (const c of fold(ch)) {
3111
+ const out = isSpace(c) ? " " : c;
3112
+ if (out === " " && norm.endsWith(" ")) continue;
3113
+ norm += out;
3114
+ for (let u = 0; u < out.length; u++) map.push(r);
3115
+ }
3116
+ r += ch.length;
3117
+ }
3118
+ map.push(raw.length);
3119
+ const normToRaw = Int32Array.from(map);
3120
+ return {
3121
+ page: content.page,
3122
+ raw,
3123
+ norm,
3124
+ normToRaw,
3125
+ rawToItem: Int32Array.from(item),
3126
+ rawToOffset: Int32Array.from(offset),
3127
+ content
3128
+ };
3129
+ }
3130
+ /** Folds a query the way `norm` is folded; case is lowered here only when the match ignores it. */
3131
+ function normalizeQuery(query, matchCase) {
3132
+ let out = "";
3133
+ for (const ch of query) out += fold(ch);
3134
+ out = out.replace(/\s+/gu, " ");
3135
+ return matchCase ? out : out.toLowerCase();
3136
+ }
3137
+ /** Whether `query` folds to nothing but whitespace, which a search treats as no query at all. */
3138
+ function isBlankQuery(query) {
3139
+ return normalizeQuery(query, true).trim() === "";
3140
+ }
3141
+ const WORD_BEFORE = /[\p{L}\p{N}_]$/u;
3142
+ const WORD_AFTER = /^[\p{L}\p{N}_]/u;
3143
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3144
+ /** Match ranges `[start, end)` into `norm`, in order and without overlap. */
3145
+ function normRanges(norm, query, matchCase) {
3146
+ const out = [];
3147
+ if (typeof query === "string") {
3148
+ if (isBlankQuery(query)) return out;
3149
+ const needle = normalizeQuery(query, true);
3150
+ const haystack = matchCase ? norm : norm.toLowerCase();
3151
+ const target = matchCase ? needle : needle.toLowerCase();
3152
+ if (haystack.length === norm.length && target.length === needle.length) {
3153
+ for (let at = haystack.indexOf(target); at !== -1; at = haystack.indexOf(target, at + target.length)) out.push([at, at + target.length]);
3154
+ return out;
3155
+ }
3156
+ return regexRanges(norm, new RegExp(escapeRegExp(needle), "giu"));
3157
+ }
3158
+ const flags = query.flags.replace(/[gy]/g, "") + "g";
3159
+ return regexRanges(norm, new RegExp(query.source, flags));
3160
+ }
3161
+ function regexRanges(norm, re) {
3162
+ const out = [];
3163
+ const unicode = re.unicode || re.flags.includes("v");
3164
+ for (let m = re.exec(norm); m; m = re.exec(norm)) {
3165
+ if (m[0] !== "") {
3166
+ out.push([m.index, m.index + m[0].length]);
3167
+ continue;
3168
+ }
3169
+ re.lastIndex = m.index + (unicode && norm.codePointAt(m.index) > 65535 ? 2 : 1);
3170
+ }
3171
+ return out;
3172
+ }
3173
+ const lerp = (a, b, t) => ({
3174
+ x: a.x + (b.x - a.x) * t,
3175
+ y: a.y + (b.y - a.y) * t
3176
+ });
3177
+ /**
3178
+ * The part of an item's quad from character `a` to `b`, cut along its baseline at the item's
3179
+ * `advances`, or at an equal share per character when it has none. With advances it spans every
3180
+ * boundary from `a` to `b` and the end of every glyph in between, so glyphs the pen drew out of
3181
+ * order, or that kerning overlaps with the next one, stay inside it.
3182
+ */
3183
+ function cutQuad(it, a, b) {
3184
+ const q = it.quad;
3185
+ const len = it.str.length;
3186
+ const span = it.advances?.slice(a, b + 1);
3187
+ const ends = it.glyphEnds?.slice(a, b) ?? [];
3188
+ const t0 = span ? Math.min(...span) : a / len;
3189
+ const t1 = span ? Math.max(...span, ...ends) : b / len;
3190
+ return {
3191
+ p1: lerp(q.p1, q.p2, t0),
3192
+ p2: lerp(q.p1, q.p2, t1),
3193
+ p3: lerp(q.p3, q.p4, t0),
3194
+ p4: lerp(q.p3, q.p4, t1)
3195
+ };
3196
+ }
3197
+ function quadsFor(st, start, end) {
3198
+ const spans = /* @__PURE__ */ new Map();
3199
+ for (let r = start; r < end; r++) {
3200
+ const i = st.rawToItem[r];
3201
+ if (i < 0) continue;
3202
+ const k = st.rawToOffset[r];
3203
+ const span = spans.get(i);
3204
+ if (span) {
3205
+ span[0] = Math.min(span[0], k);
3206
+ span[1] = Math.max(span[1], k + 1);
3207
+ } else spans.set(i, [k, k + 1]);
3208
+ }
3209
+ const quads = [];
3210
+ let exact = true;
3211
+ for (const [i, [a, b]] of spans) {
3212
+ const it = st.content.items[i];
3213
+ quads.push(cutQuad(it, a, b));
3214
+ if (!it.glyphEnds) exact = false;
3215
+ }
3216
+ return {
3217
+ quads,
3218
+ exact
3219
+ };
3220
+ }
3221
+ const collapse = (s) => s.replace(/\s+/gu, " ");
3222
+ /** Every hit for `query` on one page, in page order. */
3223
+ function matchPage(st, query, opts) {
3224
+ const hits = [];
3225
+ for (const [ns, ne] of normRanges(st.norm, query, opts.matchCase)) {
3226
+ if (opts.wholeWord) {
3227
+ if (WORD_BEFORE.test(st.norm.slice(Math.max(0, ns - 2), ns))) continue;
3228
+ if (WORD_AFTER.test(st.norm.slice(ne, ne + 2))) continue;
3229
+ }
3230
+ const start = st.normToRaw[ns];
3231
+ const last = st.normToRaw[ne - 1];
3232
+ const end = last + (st.raw.codePointAt(last) > 65535 ? 2 : 1);
3233
+ const { quads, exact } = quadsFor(st, start, end);
3234
+ if (quads.length === 0) continue;
3235
+ hits.push({
3236
+ page: st.page,
3237
+ start,
3238
+ end,
3239
+ text: st.raw.slice(start, end),
3240
+ quads,
3241
+ rect: rectFromQuads(quads),
3242
+ exact,
3243
+ before: collapse(st.raw.slice(Math.max(0, start - 40), start)),
3244
+ after: collapse(st.raw.slice(end, end + 40))
3245
+ });
3246
+ }
3247
+ return hits;
3248
+ }
2621
3249
  //#endregion
2622
3250
  //#region src/model/source.ts
2623
3251
  function resolveSource(src, fetchImpl = globalThis.fetch) {
@@ -2679,6 +3307,82 @@ async function fetchBytes(url, fetchImpl) {
2679
3307
  }
2680
3308
  }
2681
3309
  //#endregion
3310
+ //#region src/model/glyph-advances.ts
3311
+ /** How many glyphs past the last match to look for the start of the next item first. */
3312
+ const LOOKAHEAD = 256;
3313
+ /** How far, as a fraction of the font size, a glyph may start from the item origin and still match. */
3314
+ const START_SLACK = .05;
3315
+ /** How far off the baseline, as a fraction of the font size, a glyph may sit (text rise). */
3316
+ const RISE_SLACK = 1;
3317
+ /**
3318
+ * Each item's character boundaries as fractions of its width (see `TextItem.advances`), from the
3319
+ * glyphs that drew it. PDF.js builds text items from the same glyphs in the same order. An item
3320
+ * matches a glyph sequence that spells its text and whose first glyph starts at the item's origin.
3321
+ * Boundaries are where the pen put each glyph, projected onto the item's baseline, so they hold
3322
+ * through moves, scaling and spacing inside an item, and may run past 1 when PDF.js reports a
3323
+ * width shorter than the glyphs it drew. An item that cannot be matched, or that runs right to
3324
+ * left or top to bottom, gets undefined.
3325
+ */
3326
+ function glyphAdvances(runs, glyphs) {
3327
+ let next = 0;
3328
+ return runs.map((run) => {
3329
+ if (run.dir !== "ltr" || run.str.trim() === "" || !(run.width > 0)) return void 0;
3330
+ const tryFrom = (from, to) => {
3331
+ for (let start = from; start < to; start++) {
3332
+ const m = matchRun(run, glyphs, start);
3333
+ if (m) return m;
3334
+ }
3335
+ };
3336
+ const m = tryFrom(next, Math.min(glyphs.length, next + LOOKAHEAD)) ?? tryFrom(0, glyphs.length);
3337
+ if (!m) return void 0;
3338
+ next = m.next;
3339
+ return m.bounds;
3340
+ });
3341
+ }
3342
+ /** The text a glyph's text stands for in PDF.js text content, which expands ligatures. */
3343
+ function variants(text) {
3344
+ const n = text.normalize("NFKC");
3345
+ return n === text ? [text] : [text, n];
3346
+ }
3347
+ /** A glyph the item may leave out: a space, or a character that draws nothing. */
3348
+ const skippable = (text) => /^[\s\p{Cf}]*$/u.test(text);
3349
+ function matchRun(run, glyphs, start) {
3350
+ const { str, origin, along, width, fontSize } = run;
3351
+ const t = (p) => ((p.x - origin.x) * along.x + (p.y - origin.y) * along.y) / width;
3352
+ const off = (p) => Math.abs((p.x - origin.x) * along.y - (p.y - origin.y) * along.x);
3353
+ const first = glyphs[start];
3354
+ if (!first || Math.abs(t(first.start) * width) > START_SLACK * fontSize || off(first.start) > RISE_SLACK * fontSize) return void 0;
3355
+ const bounds = [];
3356
+ const ends = [];
3357
+ let end = 0;
3358
+ let g = start;
3359
+ while (bounds.length < str.length) {
3360
+ const glyph = glyphs[g];
3361
+ const at = bounds.length;
3362
+ const text = glyph && variants(glyph.text).find((v) => v !== "" && str.startsWith(v, at));
3363
+ if (glyph && text) {
3364
+ const t0 = t(glyph.start);
3365
+ end = t(glyph.end);
3366
+ for (let k = 0; k < text.length; k++) {
3367
+ bounds.push(t0 + (end - t0) * k / text.length);
3368
+ ends.push(t0 + (end - t0) * (k + 1) / text.length);
3369
+ }
3370
+ g++;
3371
+ } else if (at > 0 && /\s/u.test(str[at])) {
3372
+ bounds.push(end);
3373
+ ends.push(end);
3374
+ } else if (at > 0 && glyph && skippable(glyph.text)) g++;
3375
+ else return void 0;
3376
+ }
3377
+ return {
3378
+ bounds: {
3379
+ advances: [...bounds, end],
3380
+ ends
3381
+ },
3382
+ next: g
3383
+ };
3384
+ }
3385
+ //#endregion
2682
3386
  //#region src/model/text.ts
2683
3387
  /** Fractions of the font size used when PDF.js has no usable metrics for the font. */
2684
3388
  const FALLBACK_ASCENT = .75;
@@ -2696,8 +3400,10 @@ const FALLBACK_DESCENT = .25;
2696
3400
  * So p3 = (e, f) - v * descent * fontSize, p4 = p3 + u * width, p1 = p3 + v * (ascent + descent) * fontSize,
2697
3401
  * p2 = p4 + (p1 - p3). `rect` is the axis-aligned bounding box of the quad.
2698
3402
  */
2699
- function textContentFromPdfjs(page, tc) {
3403
+ function textContentFromPdfjs(page, tc, measure, glyphs) {
2700
3404
  const items = [];
3405
+ const runs = [];
3406
+ const families = [];
2701
3407
  for (const raw of tc.items) {
2702
3408
  if (!("str" in raw)) continue;
2703
3409
  const item = raw;
@@ -2742,21 +3448,61 @@ function textContentFromPdfjs(page, tc) {
2742
3448
  p3,
2743
3449
  p4
2744
3450
  };
3451
+ const dir = isDir(item.dir) ? item.dir : "ltr";
2745
3452
  items.push({
2746
3453
  str: item.str,
2747
- dir: isDir(item.dir) ? item.dir : "ltr",
3454
+ dir,
2748
3455
  fontName: item.fontName,
2749
3456
  fontSize,
2750
3457
  hasEOL: item.hasEOL,
2751
3458
  quad,
2752
3459
  rect: rectFromQuad(quad)
2753
3460
  });
3461
+ runs.push({
3462
+ str: item.str,
3463
+ dir,
3464
+ fontSize,
3465
+ origin: {
3466
+ x: e,
3467
+ y: f
3468
+ },
3469
+ along: u,
3470
+ width: w
3471
+ });
3472
+ families.push(tc.styles[item.fontName]?.fontFamily ?? "sans-serif");
2754
3473
  }
3474
+ const exact = glyphs ? glyphAdvances(runs, glyphs) : [];
3475
+ items.forEach((it, i) => {
3476
+ const bounds = exact[i];
3477
+ if (bounds) {
3478
+ it.advances = bounds.advances;
3479
+ it.glyphEnds = bounds.ends;
3480
+ } else if (measure && it.dir === "ltr") {
3481
+ const advances = advancesOf(it.str, families[i], measure);
3482
+ if (advances) it.advances = advances;
3483
+ }
3484
+ });
2755
3485
  return {
2756
3486
  page,
2757
3487
  items
2758
3488
  };
2759
3489
  }
3490
+ /** Each character boundary of `str` as a fraction of its measured width, or undefined if it measures 0. */
3491
+ function advancesOf(str, family, measure) {
3492
+ const ends = [0];
3493
+ let sum = 0;
3494
+ for (let i = 0; i < str.length; i++) {
3495
+ const cp = str.codePointAt(i);
3496
+ if (cp > 65535) {
3497
+ sum += measure(String.fromCodePoint(cp), family);
3498
+ ends.push(sum);
3499
+ i++;
3500
+ } else sum += measure(str[i], family);
3501
+ ends.push(sum);
3502
+ }
3503
+ if (!(sum > 0)) return void 0;
3504
+ return ends.map((e) => e / sum);
3505
+ }
2760
3506
  function metrics(style) {
2761
3507
  if (!style || style.vertical) return {
2762
3508
  ascent: FALLBACK_ASCENT,
@@ -2795,6 +3541,7 @@ async function openDocument(source, options) {
2795
3541
  let pageCache;
2796
3542
  let destroyed = false;
2797
3543
  let handles;
3544
+ const measure = canvasMeasureChar();
2798
3545
  let destroyTask;
2799
3546
  const store = createAnnotationStore({
2800
3547
  mode: options.annotations ?? { mode: "uncontrolled" },
@@ -2868,16 +3615,93 @@ async function openDocument(source, options) {
2868
3615
  if (!destroyed && patched.size > 0) store._patchEmbedded(patched);
2869
3616
  }).catch(() => void 0);
2870
3617
  }, () => void 0);
2871
- return {
3618
+ /**
3619
+ * A page's text with item boundaries measured or, with `glyphs`, taken from where the page
3620
+ * draws each glyph. Reading the glyphs means reading the page's drawing commands, which also
3621
+ * decodes its images, so search does it only for pages with hits.
3622
+ */
3623
+ async function pageContent(page, glyphs) {
3624
+ if (destroyed) throw destroyedError();
3625
+ if (!handles) throw statusError(status.get());
3626
+ const p = await handles.getPage(page);
3627
+ const tc = await p.getTextContent();
3628
+ const placed = glyphs && tc.items.length > 0 ? await pageGlyphs(p, handles.module).catch(() => void 0) : void 0;
3629
+ return textContentFromPdfjs(page, tc, measure, placed);
3630
+ }
3631
+ /** Each page's searchable text, and whether its boundaries come from the page's glyphs. */
3632
+ const searchable = /* @__PURE__ */ new Map();
3633
+ const abortError = (signal) => signal?.reason ?? new DOMException("Aborted", "AbortError");
3634
+ const searchableFor = (page, placed) => {
3635
+ let cached = searchable.get(page);
3636
+ if (!cached) cached = pageContent(page, placed).then((c) => ({
3637
+ st: searchableText(c),
3638
+ placed
3639
+ }));
3640
+ else if (placed) cached = cached.then((c) => c.placed ? c : pageContent(page, true).then((pc) => ({
3641
+ st: searchableText(pc),
3642
+ placed: true
3643
+ })));
3644
+ else return cached.then((c) => c.st);
3645
+ const entry = cached;
3646
+ searchable.set(page, entry);
3647
+ entry.catch(() => {
3648
+ if (searchable.get(page) === entry) searchable.delete(page);
3649
+ });
3650
+ return entry.then((c) => c.st);
3651
+ };
3652
+ async function* scan(query, opts) {
3653
+ const { signal } = opts;
3654
+ const matchOpts = {
3655
+ matchCase: opts.matchCase ?? false,
3656
+ wholeWord: opts.wholeWord ?? false
3657
+ };
3658
+ const order = opts.pages ?? Array.from({ length: pages.length }, (_, i) => i);
3659
+ const check = () => {
3660
+ if (signal?.aborted || destroyed) throw abortError(signal);
3661
+ };
3662
+ check();
3663
+ for (let n = 0; n < order.length; n++) {
3664
+ if (n > 0) await new Promise((r) => setTimeout(r));
3665
+ check();
3666
+ let hits;
3667
+ try {
3668
+ hits = matchPage(await searchableFor(order[n], false), query, matchOpts);
3669
+ if (hits.length > 0) {
3670
+ check();
3671
+ hits = matchPage(await searchableFor(order[n], true), query, matchOpts);
3672
+ }
3673
+ } catch (error) {
3674
+ check();
3675
+ throw error;
3676
+ }
3677
+ check();
3678
+ for (const hit of hits) {
3679
+ yield hit;
3680
+ check();
3681
+ }
3682
+ }
3683
+ }
3684
+ const doc = {
2872
3685
  key: resolved.key,
2873
3686
  status: readonly(status),
2874
3687
  ready,
2875
3688
  pages,
2876
3689
  info,
2877
- async text(page) {
2878
- if (destroyed) throw destroyedError();
2879
- if (!handles) throw statusError(status.get());
2880
- return textContentFromPdfjs(page, await (await handles.getPage(page)).getTextContent());
3690
+ text(page) {
3691
+ return pageContent(page, true);
3692
+ },
3693
+ async pageText(page) {
3694
+ return searchableText(await doc.text(page)).raw;
3695
+ },
3696
+ search(query, opts = {}) {
3697
+ return {
3698
+ [Symbol.asyncIterator]: () => scan(query, opts),
3699
+ async all() {
3700
+ const hits = [];
3701
+ for await (const hit of scan(query, opts)) hits.push(hit);
3702
+ return hits;
3703
+ }
3704
+ };
2881
3705
  },
2882
3706
  annotations: store,
2883
3707
  embedded,
@@ -2901,6 +3725,7 @@ async function openDocument(source, options) {
2901
3725
  history.destroy();
2902
3726
  store.destroy();
2903
3727
  pageCache?.clear();
3728
+ searchable.clear();
2904
3729
  if (destroyTask) teardown(destroyTask);
2905
3730
  },
2906
3731
  get _pdfjs() {
@@ -2908,6 +3733,7 @@ async function openDocument(source, options) {
2908
3733
  return handles;
2909
3734
  }
2910
3735
  };
3736
+ return doc;
2911
3737
  }
2912
3738
  const EMBEDDED_CONCURRENCY = 8;
2913
3739
  /** Runs `task(0..count-1)` with at most `limit` in flight; results come back in index order. */
@@ -4707,6 +5533,6 @@ function createSignatureLibrary(store) {
4707
5533
  };
4708
5534
  }
4709
5535
  //#endregion
4710
- export { importAnnotations as $, readonly as $t, styleGroupOf as A, RectSchema as At, isTextMarkup as B, withCalloutBounds as Bt, repliesTo as C, NoteIconSchema as Ct, STYLE_GROUPS as D, PolygonSchema as Dt, withoutReplies as E, PolySchema as Et, typesInGroup as F, TextMarkupSchema as Ft, resizedRect as G, isKnown as Gt, movedEndpoint as H, LINE_ENDINGS as Ht, hasCallout as I, FringeSchema as It, withBox as J, cssVariableDefaults as Jt, scaled as K, pdfNameSet as Kt, hasCornerHandles as L, calloutStroke as Lt, styleGroupsOf as M, ShapeSchema as Mt, supportedProps as N, SquareSchema as Nt, createToolStyle as O, PolylineSchema as Ot, toFields as P, StampSchema as Pt, exportAnnotations as Q, derived as Qt, hasRectGeometry as R, freeTextBox as Rt, isThreadRoot as S, MaskSchema as St, threadRoot as T, PointSchema as Tt, movedTip as U, NOTE_ICONS as Ut, keepsAspect as V, INTENTS as Vt, resizedBox as W, PDF_NAME$1 as Wt, createAnnotationStore as X, staticDefaults as Xt, openDocument as Y, normalizeColor as Yt, deriveRect as Z, mergeLineRects as Zt, defaultEnds as _, isInkError as _n, IMAGE_DATA_URL as _t, createLocalSignatureStore as a, pointsFromFlat as an, pdfColorToHex as at, isAbandonedNote as b, LineSchema as bt, normaliseInk as c, rectContainsPoint as cn, AnnotationSchema as ct, recogniseStroke as d, rectsIntersect as dn, FreeTextSchema as dt, writable as en, createHistory as et, DEFAULT_CORNER_RADIUS as f, translateQuad as fn, FreeTextStyleSchema as ft, constrain as g, InkError as gn, GLOSS_VERSION$1 as gt, SHAPE_KINDS as h, Emitter as hn, GLOSS_SCHEMA_ID as ht, createInkTemplate as i, multiplyMatrix as in, importedId as it, styleGroupOfType as j, RedactSchema as jt, fromAnnotation as k, QuadSchema as kt, placeSignature as l, rectFromPoints as ln, CircleSchema as lt, DEFAULT_SHAPE_SIZE as m, unionRects as mn, GLOSS_MEDIA_TYPE as mt, SIGNATURE_INK_WIDTH as n, approxEqual as nn, SUPPORTED_SUBTYPES as nt, createMemorySignatureStore as o, quadFromRect as on, summarise as ot, DEFAULT_SHAPE_OPTIONS as p, translateRect as pn, GLOSS_FORMAT$1 as pt, translated as q, parsePdfDate as qt, createImageTemplate as r, invertMatrix as rn, importEmbeddedPage as rt, createSignatureLibrary as s, quadsFromFlat as sn, AnnotationDocumentSchema as st, DEFAULT_SIGNATURE_WIDTH as t, applyMatrix as tn, isProduction as tt, RECOGNISE_THRESHOLD as u, rectFromQuad as un, ExportedAnnotationSchema as ut, isPointerOnlyKind as v, InkSchema as vt, threadOf as w, NoteSchema as wt, isReply as x, LoupeSchema as xt, shapeInput as y, LineEndingSchema as yt, isBubble as z, isStandardCallout as zt };
5536
+ export { exportAnnotations as $, createRecordStore as $t, styleGroupOf as A, QuadSchema as At, isTextMarkup as B, isStandardCallout as Bt, repliesTo as C, MaskSchema as Ct, STYLE_GROUPS as D, PolySchema as Dt, withoutReplies as E, PointSchema as Et, typesInGroup as F, StampSchema as Ft, resizedRect as G, PDF_NAME$1 as Gt, movedEndpoint as H, INTENTS as Ht, hasCallout as I, TextMarkupSchema as It, withBox as J, parsePdfDate as Jt, scaled as K, isKnown as Kt, hasCornerHandles as L, FringeSchema as Lt, styleGroupsOf as M, RedactSchema as Mt, supportedProps as N, ShapeSchema as Nt, createToolStyle as O, PolygonSchema as Ot, toFields as P, SquareSchema as Pt, deriveRect as Q, mergeLineRects as Qt, hasRectGeometry as R, calloutStroke as Rt, isThreadRoot as S, LoupeSchema as St, threadRoot as T, NoteSchema as Tt, movedTip as U, LINE_ENDINGS as Ut, keepsAspect as V, withCalloutBounds as Vt, resizedBox as W, NOTE_ICONS as Wt, isBlankQuery as X, normalizeColor as Xt, openDocument as Y, cssVariableDefaults as Yt, createAnnotationStore as Z, staticDefaults as Zt, defaultEnds as _, translateRect as _n, GLOSS_VERSION$1 as _t, createLocalSignatureStore as a, invertMatrix as an, importedId as at, isAbandonedNote as b, InkError as bn, LineEndingSchema as bt, normaliseInk as c, quadFromRect as cn, AnnotationDocumentSchema as ct, recogniseStroke as d, rectContainsPoint as dn, ExportedAnnotationSchema as dt, derived as en, importAnnotations as et, DEFAULT_CORNER_RADIUS as f, rectFromPoints as fn, FreeTextSchema as ft, constrain as g, translateQuad as gn, GLOSS_SCHEMA_ID as gt, SHAPE_KINDS as h, rectsIntersect as hn, GLOSS_MEDIA_TYPE as ht, createInkTemplate as i, approxEqual as in, importEmbeddedPage as it, styleGroupOfType as j, RectSchema as jt, fromAnnotation as k, PolylineSchema as kt, placeSignature as l, quadsFromFlat as ln, AnnotationSchema as lt, DEFAULT_SHAPE_SIZE as m, rectFromQuads as mn, GLOSS_FORMAT$1 as mt, SIGNATURE_INK_WIDTH as n, writable as nn, isProduction as nt, createMemorySignatureStore as o, multiplyMatrix as on, pdfColorToHex as ot, DEFAULT_SHAPE_OPTIONS as p, rectFromQuad as pn, FreeTextStyleSchema as pt, translated as q, pdfNameSet as qt, createImageTemplate as r, applyMatrix as rn, SUPPORTED_SUBTYPES as rt, createSignatureLibrary as s, pointsFromFlat as sn, summarise as st, DEFAULT_SIGNATURE_WIDTH as t, readonly as tn, createHistory as tt, RECOGNISE_THRESHOLD as u, quadsFromRect as un, CircleSchema as ut, isPointerOnlyKind as v, unionRects as vn, IMAGE_DATA_URL as vt, threadOf as w, NoteIconSchema as wt, isReply as x, isInkError as xn, LineSchema as xt, shapeInput as y, Emitter as yn, InkSchema as yt, isBubble as z, freeTextBox as zt };
4711
5537
 
4712
- //# sourceMappingURL=signatures-CMapTKrC.mjs.map
5538
+ //# sourceMappingURL=signatures-Do_l2IlM.mjs.map