@inkbar/core 0.4.0 → 0.6.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.
@@ -1,3 +1,4 @@
1
+ import { Circle, Color, FreeText, FreeTextStyle, Fringe, GLOSS_FORMAT, GLOSS_FORMAT as GLOSS_FORMAT$1, GLOSS_MEDIA_TYPE, GLOSS_SCHEMA_ID, GLOSS_VERSION, GLOSS_VERSION as GLOSS_VERSION$1, Highlight, IMAGE_DATA_URL, INTENTS, Ink, LINE_ENDINGS, Line, LineEnding, NOTE_ICONS, Note, NoteIcon, PDF_NAME, PDF_NAME as PDF_NAME$1, Point, Polygon, Polyline, Rect, Redact, Square, Stamp, commonFields } from "gloss-format";
1
2
  import { z } from "zod";
2
3
  import { v7 } from "uuid";
3
4
  //#region src/model/errors.ts
@@ -95,6 +96,15 @@ function quadFromRect(r) {
95
96
  }
96
97
  };
97
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
+ }
98
108
  function unionRects(rects) {
99
109
  if (rects.length === 0) throw new RangeError("unionRects needs at least one rect");
100
110
  let x1 = Infinity;
@@ -250,6 +260,102 @@ function readonly(w) {
250
260
  };
251
261
  }
252
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
253
359
  //#region src/model/selection.ts
254
360
  const union = (a, b) => {
255
361
  const x = Math.min(a.x, b.x);
@@ -422,20 +528,10 @@ const COLORS = {
422
528
  mask: "#000000",
423
529
  redact: "#C0392B"
424
530
  };
425
- /**
426
- * Suggested-redaction colours, strengthening with confidence: amber for low, orange for
427
- * medium, the redaction red for high.
428
- */
429
- const SUGGESTION_COLORS = {
430
- low: "#D4A017",
431
- medium: "#E67E22",
432
- high: "#C0392B"
433
- };
434
531
  const staticDefaults = {
435
532
  color: (type) => COLORS[type],
436
533
  fill: () => "#000000",
437
- opacity: (type) => type === "highlight" ? .4 : 1,
438
- suggestion: (bucket) => SUGGESTION_COLORS[bucket]
534
+ opacity: (type) => type === "highlight" ? .4 : 1
439
535
  };
440
536
  const VARIABLES = {
441
537
  highlight: "--ink-highlight",
@@ -465,8 +561,7 @@ function cssVariableDefaults(el) {
465
561
  return {
466
562
  color: (type) => read(VARIABLES[type]) ?? staticDefaults.color(type),
467
563
  fill: () => read("--ink-redact-fill") ?? staticDefaults.fill(),
468
- opacity: (type) => staticDefaults.opacity(type),
469
- suggestion: (bucket) => read(`--ink-suggestion-${bucket}`) ?? staticDefaults.suggestion(bucket)
564
+ opacity: (type) => staticDefaults.opacity(type)
470
565
  };
471
566
  }
472
567
  /** Accepts `#rgb`, `#rrggbb`, `rgb(r, g, b)` and `rgb(r g b)`; returns `#RRGGBB` or undefined. */
@@ -507,51 +602,12 @@ function parsePdfDate(value, fallback) {
507
602
  //#endregion
508
603
  //#region src/model/annotations/pdf-names.ts
509
604
  /**
510
- * A PDF name: a non-empty run of regular characters, so no whitespace and none of the
511
- * delimiters `( ) < > [ ] { } / %`.
512
- */
513
- const PDF_NAME = /^[^\s()<>[\]{}/%]+$/;
514
- /**
515
605
  * A set of PDF names Inkbar knows that stays open: any other valid PDF name loads, is kept and
516
606
  * is written back unchanged. The output type keeps autocomplete for the known values.
517
607
  */
518
608
  function pdfNameSet(known) {
519
609
  return z.union([z.enum(known), z.string().regex(PDF_NAME, "expected a PDF name")]).describe(`One of ${known.join(", ")}. Any other PDF name is allowed and kept unchanged.`);
520
610
  }
521
- /** Line ending styles (PDF `/LE`). */
522
- const LINE_ENDINGS = [
523
- "None",
524
- "Square",
525
- "Circle",
526
- "Diamond",
527
- "OpenArrow",
528
- "ClosedArrow",
529
- "Butt",
530
- "ROpenArrow",
531
- "RClosedArrow",
532
- "Slash"
533
- ];
534
- /** Note icons (PDF `/Name` on a text annotation). */
535
- const NOTE_ICONS = [
536
- "Comment",
537
- "Note",
538
- "Help",
539
- "Insert",
540
- "Key",
541
- "NewParagraph",
542
- "Paragraph"
543
- ];
544
- /** Annotation intents (PDF `/IT`). */
545
- const INTENTS = [
546
- "FreeText",
547
- "FreeTextCallout",
548
- "FreeTextTypeWriter",
549
- "LineArrow",
550
- "LineDimension",
551
- "PolygonCloud",
552
- "PolygonDimension",
553
- "PolyLineDimension"
554
- ];
555
611
  /** True when `v` is one of the values in `list`. */
556
612
  function isKnown(list, v) {
557
613
  return list.includes(v);
@@ -562,12 +618,7 @@ function isKnown(list, v) {
562
618
  * The insets from a free text's `rect` to its text box, in points (PDF `/RD`, XFDF `fringe`,
563
619
  * both in this order).
564
620
  */
565
- const FringeSchema = z.object({
566
- left: z.number().nonnegative(),
567
- top: z.number().nonnegative(),
568
- right: z.number().nonnegative(),
569
- bottom: z.number().nonnegative()
570
- });
621
+ const FringeSchema = z.object(Fringe.shape);
571
622
  /** An ending's length, and its full width, as a multiple of the stroke width. */
572
623
  const ENDING_SCALE = 3;
573
624
  /**
@@ -640,73 +691,48 @@ function withCalloutBounds(box, callout, width, head) {
640
691
  }
641
692
  //#endregion
642
693
  //#region src/model/annotations/types.ts
643
- const ANNOTATION_FORMAT = "inkbar/annotations";
644
- const ANNOTATION_VERSION = 1;
645
- const Hex = z.string().regex(/^#[0-9a-fA-F]{6}$/, "expected #RRGGBB");
646
- const PointSchema = z.object({
647
- x: z.number(),
648
- y: z.number()
649
- });
650
- const RectSchema = z.object({
651
- x: z.number(),
652
- y: z.number(),
653
- width: z.number().nonnegative(),
654
- height: z.number().nonnegative()
655
- });
694
+ const PointSchema = z.object(Point.shape);
695
+ const RectSchema = z.object(Rect.shape);
656
696
  const QuadSchema = z.object({
657
697
  p1: PointSchema,
658
698
  p2: PointSchema,
659
699
  p3: PointSchema,
660
700
  p4: PointSchema
661
701
  });
662
- const Base = z.object({
663
- /** Client id, UUIDv7, assigned at creation and never reassigned. Embedded annotations use `pdf:<ref>`. */
664
- id: z.string().min(1),
665
- /** 0-based page index. */
666
- page: z.number().int().nonnegative(),
702
+ /** What Inkbar adds to every Gloss type: the fields it always fills in, and its viewer state. */
703
+ const Base = {
667
704
  /** Bounding box in PDF user space. */
668
705
  rect: RectSchema,
669
706
  /** Resolved #RRGGBB. Written into the PDF later, so it is always concrete. */
670
- color: Hex,
671
- opacity: z.number().min(0).max(1),
672
- author: z.string().optional(),
673
- contents: z.string().optional(),
674
- /** Id of the parent note; replies form threads. */
675
- inReplyTo: z.string().optional(),
676
- /** ISO 8601 date-time. */
677
- createdAt: z.iso.datetime({ offset: true }),
678
- /** ISO 8601 date-time. */
679
- modifiedAt: z.iso.datetime({ offset: true }),
707
+ color: Color,
708
+ opacity: commonFields.opacity.removeDefault(),
709
+ createdAt: commonFields.createdAt.unwrap(),
710
+ modifiedAt: commonFields.modifiedAt.unwrap(),
680
711
  /** Created in this session, or imported from the file. Viewer state, never exported. */
681
712
  source: z.enum(["session", "pdf"]),
682
713
  /** Embedded annotations are locked until the engine can rewrite the file. Viewer state, never exported. */
683
- locked: z.boolean().optional(),
684
- /** Short label for what the mark is (XFDF `subject`). */
685
- subject: z.string().optional(),
686
- /** What the mark is for, e.g. a callout or an arrow (XFDF `intent`, a PDF name). */
687
- intent: pdfNameSet(INTENTS).optional(),
688
- /** Host data. Preserved, never interpreted. */
689
- custom: z.record(z.string(), z.unknown()).optional()
690
- });
691
- const TextMarkupSchema = Base.extend({
714
+ locked: z.boolean().optional()
715
+ };
716
+ const Quads = z.array(QuadSchema).min(1);
717
+ const TextMarkupSchema = z.object({
718
+ ...Highlight.shape,
719
+ ...Base,
692
720
  type: z.enum([
693
721
  "highlight",
694
722
  "underline",
695
723
  "strikeout",
696
724
  "squiggly"
697
725
  ]),
698
- quads: z.array(QuadSchema).min(1)
726
+ quads: Quads
699
727
  });
700
728
  /** A note's icon (XFDF `icon`): a PDF name, so a name Inkbar does not know is kept. */
701
- const NoteIconSchema = pdfNameSet(NOTE_ICONS);
702
- const NoteSchema = Base.extend({
703
- type: z.literal("text"),
704
- icon: NoteIconSchema.default("Note")
729
+ const NoteIconSchema = NoteIcon;
730
+ const NoteSchema = z.object({
731
+ ...Note.shape,
732
+ ...Base
705
733
  });
706
734
  /** A line ending (XFDF `head`/`tail`): a PDF name, so a name Inkbar does not know is kept. */
707
- const LineEndingSchema = pdfNameSet(LINE_ENDINGS);
708
- /** Dash pattern in points, on then off, repeating. Absent means solid. */
709
- const DashesSchema = z.array(z.number().nonnegative()).min(1).optional();
735
+ const LineEndingSchema = LineEnding;
710
736
  /**
711
737
  * A key a type refuses outright rather than dropping like other unknown keys: one an older
712
738
  * shape used (so its value is not silently lost), or one that means nothing on the type (so a
@@ -718,20 +744,9 @@ const Refused = z.never().optional();
718
744
  * `defaultstyle` string. The older `align` is refused; other unknown keys are dropped.
719
745
  */
720
746
  const FreeTextStyleSchema = z.object({
721
- fontFamily: z.string().min(1).default("Helvetica"),
722
- /** Points. */
723
- fontSize: z.number().positive().default(12),
747
+ ...FreeTextStyle.shape,
724
748
  /** The text colour. */
725
- color: Hex,
726
- textAlign: z.enum([
727
- "left",
728
- "center",
729
- "right",
730
- "justify"
731
- ]).default("left"),
732
- fontWeight: z.literal("bold").optional(),
733
- fontStyle: z.literal("italic").optional(),
734
- textDecoration: z.literal("underline").optional(),
749
+ color: Color,
735
750
  align: Refused
736
751
  });
737
752
  /**
@@ -739,88 +754,56 @@ const FreeTextStyleSchema = z.object({
739
754
  * `defaultStyle`. A box in the older shape (top-level `fontFamily`, `fontSize`, `align`) is
740
755
  * refused rather than silently losing its style; other unknown keys are dropped.
741
756
  */
742
- const FreeTextSchema = Base.extend({
743
- type: z.literal("freetext"),
744
- /** The text shown on the page. */
745
- contents: z.string(),
757
+ const FreeTextSchema = z.object({
758
+ ...FreeText.shape,
759
+ ...Base,
746
760
  /** The box's fill (XFDF `color`). Absent means no fill. */
747
- color: Hex.optional(),
748
- /** The border's colour. An extension, not XFDF. */
749
- borderColor: Hex.optional(),
750
- /** The border's width in points; 0 draws no border. */
751
- width: z.number().nonnegative().default(0),
752
- dashes: DashesSchema,
753
- /** Points. An extension, not XFDF. */
754
- cornerRadius: z.number().nonnegative().optional(),
761
+ color: FreeText.shape.color,
762
+ /** The box's border colour. Absent means the text colour. */
763
+ borderColor: Color.optional(),
755
764
  defaultStyle: FreeTextStyleSchema,
756
- /**
757
- * The insets from `rect` to the text box, in points (XFDF `fringe`, PDF `/RD`). Absent means
758
- * the box is `rect`.
759
- */
760
765
  fringe: FringeSchema.optional(),
761
- /** A callout's leader line: the tip, an optional bend, and where it meets the box (PDF `/CL`). */
762
766
  callout: z.array(PointSchema).min(2).max(3).optional(),
763
- /** The line ending at a callout's tip (XFDF `head`, PDF `/LE`). Absent means `None`. */
764
- head: LineEndingSchema.optional(),
765
767
  fontFamily: Refused,
766
768
  fontSize: Refused,
767
769
  align: Refused
768
770
  });
769
- const ShapeFields = {
770
- width: z.number().nonnegative().default(1),
771
- dashes: DashesSchema,
772
- interiorColor: Hex.optional()
773
- };
774
- const SquareSchema = Base.extend({
775
- type: z.literal("square"),
776
- ...ShapeFields,
777
- /** The corner radius in points. An extension, not XFDF. */
778
- cornerRadius: z.number().nonnegative().optional()
771
+ const SquareSchema = z.object({
772
+ ...Square.shape,
773
+ ...Base
779
774
  });
780
- const CircleSchema = Base.extend({
781
- type: z.literal("circle"),
782
- ...ShapeFields,
775
+ const CircleSchema = z.object({
776
+ ...Circle.shape,
777
+ ...Base,
783
778
  cornerRadius: Refused
784
779
  });
785
780
  const ShapeSchema = z.discriminatedUnion("type", [SquareSchema, CircleSchema]);
786
- const LineSchema = Base.extend({
787
- type: z.literal("line"),
781
+ const LineSchema = z.object({
782
+ ...Line.shape,
783
+ ...Base,
788
784
  start: PointSchema,
789
- end: PointSchema,
790
- width: z.number().nonnegative().default(1),
791
- dashes: DashesSchema,
792
- head: LineEndingSchema.optional(),
793
- tail: LineEndingSchema.optional()
785
+ end: PointSchema
794
786
  });
795
- const PolyFields = {
796
- vertices: z.array(PointSchema).min(2),
797
- width: z.number().nonnegative().default(1),
798
- dashes: DashesSchema,
799
- interiorColor: Hex.optional()
800
- };
801
- const PolylineSchema = Base.extend({
802
- type: z.literal("polyline"),
803
- ...PolyFields,
804
- /** The ending at the first vertex. */
805
- head: LineEndingSchema.optional(),
806
- /** The ending at the last vertex. */
807
- tail: LineEndingSchema.optional()
787
+ const Vertices = z.array(PointSchema).min(2);
788
+ const PolylineSchema = z.object({
789
+ ...Polyline.shape,
790
+ ...Base,
791
+ vertices: Vertices
808
792
  });
809
- const PolygonSchema = Base.extend({
810
- type: z.literal("polygon"),
811
- ...PolyFields,
793
+ const PolygonSchema = z.object({
794
+ ...Polygon.shape,
795
+ ...Base,
796
+ vertices: Vertices,
812
797
  head: Refused,
813
798
  tail: Refused
814
799
  });
815
800
  const PolySchema = z.discriminatedUnion("type", [PolylineSchema, PolygonSchema]);
816
- const InkShape = Base.extend({
817
- type: z.literal("ink"),
801
+ const InkShape = z.object({
802
+ ...Ink.shape,
803
+ ...Base,
818
804
  inklist: z.array(z.array(PointSchema).min(1)).min(1),
819
- /** Pen pressure per sample, 0 to 1, shaped like `inklist`. Absent for mouse input. */
820
- pressure: z.array(z.array(z.number().min(0).max(1))).optional(),
821
- width: z.number().nonnegative().default(2),
822
- /** Kept so imported dashed ink round-trips. Inkbar draws ink solid. */
823
- dashes: DashesSchema
805
+ /** Pen pressure from 0 to 1, one value per point of each stroke. Absent means even pressure. */
806
+ pressure: z.array(z.array(z.number().min(0).max(1))).optional()
824
807
  });
825
808
  /** Pressure must have one value per point of each stroke. */
826
809
  function checkPressure(a, ctx) {
@@ -832,70 +815,48 @@ function checkPressure(a, ctx) {
832
815
  });
833
816
  }
834
817
  const InkSchema = InkShape.superRefine(checkPressure);
835
- /** A PNG or JPEG image as a base64 `data:` URL, as XFDF carries a stamp's `<imagedata>`. */
836
- const IMAGE_DATA_URL = /^data:image\/(png|jpeg);base64,/;
837
818
  /**
838
819
  * An image on the page (XFDF `stamp`), drawn to fill `rect`. Image and typed signatures are
839
820
  * stamps with `subject: 'Signature'`.
840
821
  */
841
- const StampSchema = Base.extend({
842
- type: z.literal("stamp"),
843
- /** A PNG or JPEG `data:` URL (XFDF `imagedata`). */
844
- imagedata: z.string().regex(IMAGE_DATA_URL, "expected a PNG or JPEG data URL"),
845
- /**
846
- * Quarter turns, in degrees anticlockwise, the image is turned within `rect` (XFDF
847
- * `rotation`). Absent means upright in user space.
848
- */
849
- rotation: z.union([
850
- z.literal(0),
851
- z.literal(90),
852
- z.literal(180),
853
- z.literal(270)
854
- ]).optional()
822
+ const StampSchema = z.object({
823
+ ...Stamp.shape,
824
+ ...Base
855
825
  });
856
826
  /**
857
- * A loupe (an extension, not XFDF): a circle inscribed in `rect` that shows the page content
858
- * under its own centre, magnified. `color` and `width` draw its rim.
827
+ * A loupe (Inkbar's own type, a circle with `custom.inkbar.type: 'loupe'` in a file): a circle
828
+ * inscribed in `rect` that shows the page content under its own centre, magnified. `color` and
829
+ * `width` draw its rim.
859
830
  */
860
- const LoupeSchema = Base.extend({
831
+ const LoupeSchema = z.object({
861
832
  type: z.literal("loupe"),
833
+ ...commonFields,
862
834
  /** How many times larger the content under the centre is drawn. Above 1. */
863
835
  magnification: z.number().gt(1).default(2),
864
836
  /** The rim's width in points. */
865
- width: z.number().nonnegative().default(2)
837
+ width: z.number().nonnegative().default(2),
838
+ ...Base
866
839
  });
867
840
  /**
868
- * A mask (an extension, not XFDF): dims everything on the page outside `shape` drawn in
869
- * `rect`, an ellipse inscribed in it or the rectangle itself. `color` is the dimming colour
870
- * (black by default) and `dim` how strongly it covers. One per page: adding a second replaces
871
- * the first.
841
+ * A mask (Inkbar's own type, a circle or square with `custom.inkbar.type: 'mask'` in a file):
842
+ * dims everything on the page outside `shape` drawn in `rect`, an ellipse inscribed in it or the
843
+ * rectangle itself. `color` is the dimming colour and `dim` how strongly it covers, from 0 to 1.
844
+ * One per page: adding a second replaces the first.
872
845
  */
873
- const MaskSchema = Base.extend({
846
+ const MaskSchema = z.object({
874
847
  type: z.literal("mask"),
848
+ ...commonFields,
875
849
  shape: z.enum(["rect", "ellipse"]),
876
- /** Opacity of the dimming, 0 (none) to 1 (opaque). */
877
- dim: z.number().min(0).max(1).default(.5)
850
+ dim: z.number().min(0).max(1).default(.5),
851
+ ...Base
878
852
  });
879
- const SuggestionSchema = z.object({
880
- jobId: z.string().optional(),
881
- confidence: z.number().min(0).max(1),
882
- reason: z.string().optional(),
883
- explanation: z.string().optional(),
884
- recommendation: z.enum([
885
- "redact",
886
- "review",
887
- "keep"
888
- ]).optional()
889
- });
890
- const RedactSchema = Base.extend({
891
- type: z.literal("redact"),
853
+ const RedactSchema = z.object({
854
+ ...Redact.shape,
855
+ ...Base,
892
856
  /** Present when the mark came from text; absent for an area mark. */
893
- quads: z.array(QuadSchema).min(1).optional(),
894
- origin: z.enum(["user", "suggested"]).default("user"),
895
- suggestion: SuggestionSchema.optional(),
896
- overlayText: z.string().optional(),
857
+ quads: Quads.optional(),
897
858
  /** Colour the redaction will be filled with when applied. */
898
- interiorColor: Hex
859
+ interiorColor: Color
899
860
  });
900
861
  const AnnotationSchema = z.discriminatedUnion("type", [
901
862
  TextMarkupSchema,
@@ -934,17 +895,12 @@ const ExportedAnnotationSchema = z.discriminatedUnion("type", [
934
895
  RedactSchema.omit(VIEWER_STATE)
935
896
  ]);
936
897
  const AnnotationDocumentSchema = z.object({
937
- format: z.literal(ANNOTATION_FORMAT),
938
- version: z.literal(1),
898
+ format: z.literal(GLOSS_FORMAT),
899
+ version: z.literal(GLOSS_VERSION),
939
900
  documentKey: z.string().optional(),
940
901
  /** Validated item by item on import so unknown types can be preserved. */
941
902
  annotations: z.array(z.unknown())
942
903
  });
943
- function confidenceBucket(confidence) {
944
- if (confidence < .5) return "low";
945
- if (confidence < .8) return "medium";
946
- return "high";
947
- }
948
904
  //#endregion
949
905
  //#region src/model/annotations/embedded.ts
950
906
  const SUPPORTED_SUBTYPES = /* @__PURE__ */ new Set([
@@ -982,7 +938,7 @@ function rectFromPdf(r) {
982
938
  }
983
939
  /** A PDF name exactly as PDF.js gives it (names are case-sensitive), or undefined if invalid. */
984
940
  function pdfName(raw) {
985
- return raw !== void 0 && PDF_NAME.test(raw) ? raw : void 0;
941
+ return raw !== void 0 && PDF_NAME$1.test(raw) ? raw : void 0;
986
942
  }
987
943
  /** `head` and `tail` from a Line's or PolyLine's `/LE`, left out when both are `None`. */
988
944
  function lineEndingsOf(raw) {
@@ -1000,18 +956,43 @@ function lineEndingsOf(raw) {
1000
956
  function importedId(page, pdfjsId) {
1001
957
  return `pdf:${page}:${pdfjsId}`;
1002
958
  }
959
+ /**
960
+ * Imports one page's PDF.js annotation data. `targets` is @internal: the annotations whose
961
+ * missing fields the document reads from the file after `ready`.
962
+ */
1003
963
  function importEmbeddedPage(page, raw, importedAt) {
1004
964
  const counts = {};
1005
965
  const annotations = [];
966
+ const targets = [];
1006
967
  for (const data of raw) {
1007
968
  counts[data.subtype] = (counts[data.subtype] ?? 0) + 1;
1008
969
  if (!SUPPORTED_SUBTYPES.has(data.subtype) || data.hidden || data.noView) continue;
1009
970
  const converted = convert(page, data, importedAt);
1010
- if (converted) annotations.push(converted);
971
+ if (!converted) continue;
972
+ annotations.push(converted);
973
+ const target = targetOf(converted.id, data);
974
+ if (target) targets.push(target);
1011
975
  }
1012
976
  return {
1013
977
  annotations,
1014
- counts
978
+ counts,
979
+ targets
980
+ };
981
+ }
982
+ function targetOf(id, d) {
983
+ const rect = Array.from(d.rect);
984
+ if (d.subtype === "Text") return d.name === "NoIcon" ? {
985
+ id,
986
+ subtype: "Text",
987
+ rect
988
+ } : void 0;
989
+ if (d.subtype !== "FreeText") return void 0;
990
+ const daFont = d.defaultAppearanceData?.fontName;
991
+ return {
992
+ id,
993
+ subtype: "FreeText",
994
+ rect,
995
+ ...daFont ? { daFont } : {}
1015
996
  };
1016
997
  }
1017
998
  function summarise(perPage) {
@@ -1152,7 +1133,6 @@ function convert(page, d, importedAt) {
1152
1133
  candidate = {
1153
1134
  ...base("redact"),
1154
1135
  type: "redact",
1155
- origin: "user",
1156
1136
  interiorColor: pdfColorToHex(d.interiorColor, staticDefaults.fill()),
1157
1137
  ...quads.length > 0 ? { quads } : {}
1158
1138
  };
@@ -1163,6 +1143,749 @@ function convert(page, d, importedAt) {
1163
1143
  return parsed.success ? parsed.data : void 0;
1164
1144
  }
1165
1145
  //#endregion
1146
+ //#region src/pdf/lexer.ts
1147
+ /** Thrown when the bytes are not valid PDF object syntax. */
1148
+ var PdfSyntaxError = class extends Error {
1149
+ constructor(message) {
1150
+ super(message);
1151
+ this.name = "PdfSyntaxError";
1152
+ }
1153
+ };
1154
+ const isWhite$1 = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
1155
+ const isDelimiter = (c) => c === 40 || c === 41 || c === 60 || c === 62 || c === 91 || c === 93 || c === 123 || c === 125 || c === 47 || c === 37;
1156
+ const isRegular = (c) => !isWhite$1(c) && !isDelimiter(c);
1157
+ const isDigit = (c) => c >= 48 && c <= 57;
1158
+ const latin1$1 = (bytes, from, to) => {
1159
+ let out = "";
1160
+ for (let i = from; i < to; i++) out += String.fromCharCode(bytes[i]);
1161
+ return out;
1162
+ };
1163
+ function skipSpace(bytes, offset) {
1164
+ let i = offset;
1165
+ while (i < bytes.length) {
1166
+ const c = bytes[i];
1167
+ if (isWhite$1(c)) i++;
1168
+ else if (c === 37) while (i < bytes.length && bytes[i] !== 10 && bytes[i] !== 13) i++;
1169
+ else break;
1170
+ }
1171
+ return i;
1172
+ }
1173
+ function tokenEnd(bytes, offset) {
1174
+ let i = offset;
1175
+ while (i < bytes.length && isRegular(bytes[i])) i++;
1176
+ return i;
1177
+ }
1178
+ const hexValue = (c) => {
1179
+ if (c === void 0) return -1;
1180
+ if (c >= 48 && c <= 57) return c - 48;
1181
+ if (c >= 65 && c <= 70) return c - 65 + 10;
1182
+ if (c >= 97 && c <= 102) return c - 97 + 10;
1183
+ return -1;
1184
+ };
1185
+ function parseName(bytes, offset) {
1186
+ const end = tokenEnd(bytes, offset + 1);
1187
+ let name = "";
1188
+ for (let i = offset + 1; i < end; i++) {
1189
+ const c = bytes[i];
1190
+ const hi = c === 35 ? hexValue(bytes[i + 1]) : -1;
1191
+ const lo = hi >= 0 ? hexValue(bytes[i + 2]) : -1;
1192
+ if (hi >= 0 && lo >= 0 && i + 2 < end) {
1193
+ name += String.fromCharCode(hi * 16 + lo);
1194
+ i += 2;
1195
+ } else name += String.fromCharCode(c);
1196
+ }
1197
+ return {
1198
+ value: {
1199
+ kind: "name",
1200
+ name
1201
+ },
1202
+ end
1203
+ };
1204
+ }
1205
+ function skipLiteralString(bytes, offset) {
1206
+ let depth = 0;
1207
+ for (let i = offset; i < bytes.length; i++) {
1208
+ const c = bytes[i];
1209
+ if (c === 92) i++;
1210
+ else if (c === 40) depth++;
1211
+ else if (c === 41 && --depth === 0) return i + 1;
1212
+ }
1213
+ throw new PdfSyntaxError("Unterminated string");
1214
+ }
1215
+ /** After a non-negative integer ending at `end`, looks for `gen R`. */
1216
+ function tryReference(bytes, num, end) {
1217
+ let i = end;
1218
+ if (i >= bytes.length || !isWhite$1(bytes[i])) return void 0;
1219
+ i = skipSpace(bytes, i);
1220
+ const genStart = i;
1221
+ while (i < bytes.length && isDigit(bytes[i])) i++;
1222
+ if (i === genStart || i >= bytes.length || !isWhite$1(bytes[i])) return void 0;
1223
+ const gen = Number(latin1$1(bytes, genStart, i));
1224
+ i = skipSpace(bytes, i);
1225
+ if (bytes[i] !== 82 || i + 1 < bytes.length && isRegular(bytes[i + 1])) return;
1226
+ return {
1227
+ value: {
1228
+ kind: "ref",
1229
+ num,
1230
+ gen
1231
+ },
1232
+ end: i + 1
1233
+ };
1234
+ }
1235
+ function parseNumberOrKeyword(bytes, offset) {
1236
+ const end = tokenEnd(bytes, offset);
1237
+ const token = latin1$1(bytes, offset, end);
1238
+ if (token === "true") return {
1239
+ value: true,
1240
+ end
1241
+ };
1242
+ if (token === "false") return {
1243
+ value: false,
1244
+ end
1245
+ };
1246
+ if (token === "null") return {
1247
+ value: null,
1248
+ end
1249
+ };
1250
+ if (/^[+-]?(\d+\.?\d*|\.\d+)$/.test(token)) {
1251
+ const n = Number(token);
1252
+ if (/^\d+$/.test(token)) {
1253
+ const ref = tryReference(bytes, n, end);
1254
+ if (ref) return ref;
1255
+ }
1256
+ return {
1257
+ value: n,
1258
+ end
1259
+ };
1260
+ }
1261
+ throw new PdfSyntaxError(`Unexpected token "${token}" at ${offset}`);
1262
+ }
1263
+ /** Parses one value at `offset`, skipping whitespace and comments first. */
1264
+ function parseValue(bytes, offset) {
1265
+ const i = skipSpace(bytes, offset);
1266
+ if (i >= bytes.length) throw new PdfSyntaxError("Unexpected end of data");
1267
+ const c = bytes[i];
1268
+ if (c === 47) return parseName(bytes, i);
1269
+ if (c === 40) return {
1270
+ value: { kind: "string" },
1271
+ end: skipLiteralString(bytes, i)
1272
+ };
1273
+ if (c === 60 && bytes[i + 1] === 60) return parseDict(bytes, i + 2);
1274
+ if (c === 60) {
1275
+ let j = i + 1;
1276
+ while (j < bytes.length && bytes[j] !== 62) j++;
1277
+ if (j >= bytes.length) throw new PdfSyntaxError("Unterminated hex string");
1278
+ return {
1279
+ value: { kind: "string" },
1280
+ end: j + 1
1281
+ };
1282
+ }
1283
+ if (c === 91) return parseArray(bytes, i + 1);
1284
+ if (isRegular(c)) return parseNumberOrKeyword(bytes, i);
1285
+ throw new PdfSyntaxError(`Unexpected "${String.fromCharCode(c)}" at ${i}`);
1286
+ }
1287
+ function parseArray(bytes, offset) {
1288
+ const items = [];
1289
+ let i = offset;
1290
+ for (;;) {
1291
+ i = skipSpace(bytes, i);
1292
+ if (i >= bytes.length) throw new PdfSyntaxError("Unterminated array");
1293
+ if (bytes[i] === 93) return {
1294
+ value: items,
1295
+ end: i + 1
1296
+ };
1297
+ const item = parseValue(bytes, i);
1298
+ items.push(item.value);
1299
+ i = item.end;
1300
+ }
1301
+ }
1302
+ function parseDict(bytes, offset) {
1303
+ const entries = /* @__PURE__ */ new Map();
1304
+ let i = offset;
1305
+ for (;;) {
1306
+ i = skipSpace(bytes, i);
1307
+ if (i >= bytes.length) throw new PdfSyntaxError("Unterminated dictionary");
1308
+ if (bytes[i] === 62 && bytes[i + 1] === 62) return {
1309
+ value: {
1310
+ kind: "dict",
1311
+ entries
1312
+ },
1313
+ end: i + 2
1314
+ };
1315
+ if (bytes[i] !== 47) throw new PdfSyntaxError(`Expected a name key at ${i}`);
1316
+ const key = parseName(bytes, i);
1317
+ const val = parseValue(bytes, key.end);
1318
+ entries.set(key.value.name, val.value);
1319
+ i = val.end;
1320
+ }
1321
+ }
1322
+ function readInteger(bytes, offset) {
1323
+ const i = skipSpace(bytes, offset);
1324
+ let end = i;
1325
+ while (end < bytes.length && isDigit(bytes[end])) end++;
1326
+ if (end === i) throw new PdfSyntaxError(`Expected an integer at ${i}`);
1327
+ return {
1328
+ n: Number(latin1$1(bytes, i, end)),
1329
+ end
1330
+ };
1331
+ }
1332
+ /** Parses `num gen obj <value> [stream]` at `offset`. `streamStart` is the first data byte after `stream` and its EOL. */
1333
+ function parseIndirect(bytes, offset) {
1334
+ const num = readInteger(bytes, offset);
1335
+ const gen = readInteger(bytes, num.end);
1336
+ const kw = skipSpace(bytes, gen.end);
1337
+ if (latin1$1(bytes, kw, tokenEnd(bytes, kw)) !== "obj") throw new PdfSyntaxError(`Expected "obj" at ${kw}`);
1338
+ const { value, end } = parseValue(bytes, kw + 3);
1339
+ const result = {
1340
+ num: num.n,
1341
+ gen: gen.n,
1342
+ value
1343
+ };
1344
+ const next = skipSpace(bytes, end);
1345
+ if (latin1$1(bytes, next, next + 6) === "stream" && !isRegular(bytes[next + 6] ?? 32)) {
1346
+ let start = next + 6;
1347
+ if (bytes[start] === 13 && bytes[start + 1] === 10) start += 2;
1348
+ else if (bytes[start] === 10 || bytes[start] === 13) start += 1;
1349
+ result.streamStart = start;
1350
+ }
1351
+ return result;
1352
+ }
1353
+ /** Reads a dictionary entry. */
1354
+ const dictGet = (d, key) => d.entries.get(key);
1355
+ /** True for a name object, optionally one with the given spelling. */
1356
+ const isName = (v, name) => typeof v === "object" && v !== null && v.kind === "name" && (name === void 0 || v.name === name);
1357
+ /** True for a dictionary object. */
1358
+ const isDict = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && v.kind === "dict";
1359
+ /** True for an indirect reference. */
1360
+ const isRef = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && v.kind === "ref";
1361
+ //#endregion
1362
+ //#region src/pdf/annotation-fields.ts
1363
+ /** `12R` → {num:12,gen:0}, `12R3` → {num:12,gen:3}; anything else undefined. */
1364
+ function refOfPdfjsId(id) {
1365
+ const m = /^(\d+)R(\d+)?$/.exec(id);
1366
+ if (!m) return void 0;
1367
+ return {
1368
+ kind: "ref",
1369
+ num: Number(m[1]),
1370
+ gen: m[2] === void 0 ? 0 : Number(m[2])
1371
+ };
1372
+ }
1373
+ const FAMILIES = [
1374
+ [/^(helvetica|helv)/i, "Helvetica"],
1375
+ [/^(times|tiro)/i, "Times"],
1376
+ [/^cour(ier|$)/i, "Courier"]
1377
+ ];
1378
+ /**
1379
+ * BaseFont (or bare resource name) → Inkbar font fields; undefined for Symbol and ZapfDingbats.
1380
+ * A family matches by prefix, so `HelveticaNeue` reads as Helvetica and `CourierNewPS` as
1381
+ * Courier. Any other name keeps its own family, minus a trailing `MT` or `PSMT`.
1382
+ * With `knownOnly`, a name outside Helvetica, Times and Courier gives undefined: a bare
1383
+ * resource id such as `F1` is not a family.
1384
+ */
1385
+ function fontFromBaseName(name, knownOnly = false) {
1386
+ const bare = name.replace(/^[A-Z]{6}\+/, "");
1387
+ if (/^(symbol|zapfdingbats|zadb)/i.test(bare)) return void 0;
1388
+ const cut = bare.search(/[-,]/);
1389
+ const base = cut < 0 ? bare : bare.slice(0, cut);
1390
+ const style = cut < 0 ? "" : bare.slice(cut + 1);
1391
+ const known = FAMILIES.find(([re]) => re.test(base));
1392
+ if (knownOnly && !known) return void 0;
1393
+ const fontFamily = known ? known[1] : base.replace(/(PS)?MT$/, "");
1394
+ if (!fontFamily) return void 0;
1395
+ const font = { fontFamily };
1396
+ if (/bold|black|heavy/i.test(style)) font.fontWeight = "bold";
1397
+ if (/italic|oblique/i.test(style)) font.fontStyle = "italic";
1398
+ return font;
1399
+ }
1400
+ const finite = (v) => typeof v === "number" && Number.isFinite(v);
1401
+ async function numbers$1(objs, v, counts) {
1402
+ const arr = await objs.resolve(v);
1403
+ if (!Array.isArray(arr) || !counts.includes(arr.length)) return void 0;
1404
+ const out = await Promise.all(arr.map((n) => objs.resolve(n)));
1405
+ return out.every(finite) ? out : void 0;
1406
+ }
1407
+ async function dictOf(objs, v) {
1408
+ const d = await objs.resolve(v);
1409
+ return isDict(d) ? d : void 0;
1410
+ }
1411
+ /** The font's /BaseFont, looked up in a /DR resource dictionary. */
1412
+ async function baseFontIn(objs, dr, resource) {
1413
+ const res = await dictOf(objs, dr);
1414
+ const fonts = res && await dictOf(objs, dictGet(res, "Font"));
1415
+ const font = fonts && await dictOf(objs, dictGet(fonts, resource));
1416
+ const base = font && await objs.resolve(dictGet(font, "BaseFont"));
1417
+ return isName(base) ? base.name : void 0;
1418
+ }
1419
+ async function readFont(objs, annot, daFont) {
1420
+ const root = await dictOf(objs, dictGet(objs.trailer, "Root"));
1421
+ const acroForm = root && await dictOf(objs, dictGet(root, "AcroForm"));
1422
+ const sources = [dictGet(annot, "DR"), acroForm && dictGet(acroForm, "DR")];
1423
+ for (const dr of sources) {
1424
+ const base = await baseFontIn(objs, dr, daFont);
1425
+ if (base !== void 0) return fontFromBaseName(base);
1426
+ }
1427
+ return fontFromBaseName(daFont, true);
1428
+ }
1429
+ async function readHead(objs, le) {
1430
+ let v = await objs.resolve(le);
1431
+ if (Array.isArray(v)) v = await objs.resolve(v[0]);
1432
+ return isName(v) && PDF_NAME$1.test(v.name) ? v.name : void 0;
1433
+ }
1434
+ /** `rect` is the annotation's [x1 y1 x2 y2], used to reject a fringe that leaves no box. */
1435
+ async function readFreeTextFields(objs, ref, rect, daFont) {
1436
+ const fields = {};
1437
+ try {
1438
+ const annot = await objs.get(ref);
1439
+ if (!isDict(annot)) return fields;
1440
+ const cl = await numbers$1(objs, dictGet(annot, "CL"), [4, 6]);
1441
+ if (cl) {
1442
+ fields.callout = [];
1443
+ for (let i = 0; i < cl.length; i += 2) fields.callout.push({
1444
+ x: cl[i],
1445
+ y: cl[i + 1]
1446
+ });
1447
+ }
1448
+ const rd = await numbers$1(objs, dictGet(annot, "RD"), [4]);
1449
+ if (rd && rect.length === 4 && rd.every((n) => n >= 0)) {
1450
+ const [left, top, right, bottom] = rd;
1451
+ const width = Math.abs(rect[2] - rect[0]);
1452
+ const height = Math.abs(rect[3] - rect[1]);
1453
+ if (left + right < width && top + bottom < height) fields.fringe = {
1454
+ left,
1455
+ top,
1456
+ right,
1457
+ bottom
1458
+ };
1459
+ }
1460
+ const head = await readHead(objs, dictGet(annot, "LE"));
1461
+ if (head !== void 0) fields.head = head;
1462
+ if (daFont !== void 0) {
1463
+ const font = await readFont(objs, annot, daFont);
1464
+ if (font) fields.font = font;
1465
+ }
1466
+ } catch {}
1467
+ return fields;
1468
+ }
1469
+ async function readTextIcon(objs, ref) {
1470
+ try {
1471
+ const annot = await objs.get(ref);
1472
+ if (!isDict(annot)) return void 0;
1473
+ const name = await objs.resolve(dictGet(annot, "Name"));
1474
+ return isName(name) && PDF_NAME$1.test(name.name) ? name.name : void 0;
1475
+ } catch {
1476
+ return;
1477
+ }
1478
+ }
1479
+ //#endregion
1480
+ //#region src/pdf/stream.ts
1481
+ const ENDSTREAM = Uint8Array.from("endstream", (c) => c.charCodeAt(0));
1482
+ function matchesAt(bytes, at, pattern) {
1483
+ if (at < 0 || at + pattern.length > bytes.length) return false;
1484
+ for (let i = 0; i < pattern.length; i++) if (bytes[at + i] !== pattern[i]) return false;
1485
+ return true;
1486
+ }
1487
+ function findEndstream(bytes, from) {
1488
+ for (let i = from; i + ENDSTREAM.length <= bytes.length; i++) if (matchesAt(bytes, i, ENDSTREAM)) return i;
1489
+ return bytes.length;
1490
+ }
1491
+ /** Raw stream bytes: trusts `length` when `endstream` follows it, otherwise scans. */
1492
+ function rawData(bytes, start, length) {
1493
+ if (length !== void 0 && Number.isInteger(length) && length >= 0) {
1494
+ let i = start + length;
1495
+ while (i < bytes.length && (bytes[i] === 10 || bytes[i] === 13 || bytes[i] === 32)) i++;
1496
+ if (matchesAt(bytes, i, ENDSTREAM)) return bytes.subarray(start, start + length);
1497
+ }
1498
+ let end = findEndstream(bytes, start);
1499
+ if (end > start && bytes[end - 1] === 10) end--;
1500
+ if (end > start && bytes[end - 1] === 13) end--;
1501
+ return bytes.subarray(start, end);
1502
+ }
1503
+ /** The single filter parameter dictionary, if any. Anything else that is defined is an error. */
1504
+ function decodeParms(dict) {
1505
+ const parms = dictGet(dict, "DecodeParms") ?? dictGet(dict, "DP");
1506
+ const first = Array.isArray(parms) ? parms[0] : parms;
1507
+ if (first === void 0 || first === null) return void 0;
1508
+ if (!isDict(first)) throw new PdfSyntaxError("Unsupported /DecodeParms");
1509
+ return first;
1510
+ }
1511
+ function numberParam(parms, key, fallback) {
1512
+ const v = parms ? dictGet(parms, key) : void 0;
1513
+ return typeof v === "number" ? v : fallback;
1514
+ }
1515
+ async function inflate(data) {
1516
+ const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate"));
1517
+ return new Uint8Array(await new Response(stream).arrayBuffer());
1518
+ }
1519
+ function paeth(a, b, c) {
1520
+ const p = a + b - c;
1521
+ const pa = Math.abs(p - a);
1522
+ const pb = Math.abs(p - b);
1523
+ const pc = Math.abs(p - c);
1524
+ if (pa <= pb && pa <= pc) return a;
1525
+ return pb <= pc ? b : c;
1526
+ }
1527
+ function undoPngPredictor(data, columns, colors, bits) {
1528
+ const bpp = Math.max(1, Math.ceil(colors * bits / 8));
1529
+ const rowBytes = Math.ceil(columns * colors * bits / 8);
1530
+ const rows = Math.floor(data.length / (rowBytes + 1));
1531
+ const out = new Uint8Array(rows * rowBytes);
1532
+ for (let r = 0; r < rows; r++) {
1533
+ const type = data[r * (rowBytes + 1)];
1534
+ const src = r * (rowBytes + 1) + 1;
1535
+ const dst = r * rowBytes;
1536
+ for (let x = 0; x < rowBytes; x++) {
1537
+ const raw = data[src + x];
1538
+ const left = x >= bpp ? out[dst + x - bpp] : 0;
1539
+ const up = r > 0 ? out[dst - rowBytes + x] : 0;
1540
+ const upLeft = r > 0 && x >= bpp ? out[dst - rowBytes + x - bpp] : 0;
1541
+ let value;
1542
+ switch (type) {
1543
+ case 0:
1544
+ value = raw;
1545
+ break;
1546
+ case 1:
1547
+ value = raw + left;
1548
+ break;
1549
+ case 2:
1550
+ value = raw + up;
1551
+ break;
1552
+ case 3:
1553
+ value = raw + (left + up >> 1);
1554
+ break;
1555
+ case 4:
1556
+ value = raw + paeth(left, up, upLeft);
1557
+ break;
1558
+ default: throw new PdfSyntaxError(`Unknown PNG filter type ${type}`);
1559
+ }
1560
+ out[dst + x] = value & 255;
1561
+ }
1562
+ }
1563
+ return out;
1564
+ }
1565
+ /** The decoded data of the stream whose dictionary is `dict` and whose data starts at `start`. `length` is `/Length`, already resolved. */
1566
+ async function decodeStream(bytes, dict, start, length) {
1567
+ const filter = dictGet(dict, "Filter") ?? dictGet(dict, "F");
1568
+ const filters = Array.isArray(filter) ? filter : filter === void 0 ? [] : [filter];
1569
+ if (filters.length === 0) return rawData(bytes, start, length);
1570
+ if (filters.length > 1 || !isName(filters[0], "FlateDecode")) throw new PdfSyntaxError("Unsupported stream filter");
1571
+ const inflated = await inflate(rawData(bytes, start, length));
1572
+ const parms = decodeParms(dict);
1573
+ const predictor = numberParam(parms, "Predictor", 1);
1574
+ if (predictor === 1) return inflated;
1575
+ if (predictor < 10) throw new PdfSyntaxError(`Unsupported predictor ${predictor}`);
1576
+ return undoPngPredictor(inflated, numberParam(parms, "Columns", 1), numberParam(parms, "Colors", 1), numberParam(parms, "BitsPerComponent", 8));
1577
+ }
1578
+ //#endregion
1579
+ //#region src/pdf/xref.ts
1580
+ const isWhite = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
1581
+ const latin1 = (bytes, from, to) => {
1582
+ let out = "";
1583
+ for (let i = from; i < to; i++) out += String.fromCharCode(bytes[i]);
1584
+ return out;
1585
+ };
1586
+ function readToken(bytes, offset) {
1587
+ let i = offset;
1588
+ while (i < bytes.length && isWhite(bytes[i])) i++;
1589
+ const start = i;
1590
+ while (i < bytes.length && !isWhite(bytes[i])) i++;
1591
+ return {
1592
+ token: latin1(bytes, start, i),
1593
+ end: i
1594
+ };
1595
+ }
1596
+ function readInt(bytes, offset) {
1597
+ const { token, end } = readToken(bytes, offset);
1598
+ if (!/^\d+$/.test(token)) throw new Error(`Expected an integer at ${offset}`);
1599
+ return {
1600
+ n: Number(token),
1601
+ end
1602
+ };
1603
+ }
1604
+ function findStartxref(bytes) {
1605
+ const tail = latin1(bytes, Math.max(0, bytes.length - 1024), bytes.length);
1606
+ const at = tail.lastIndexOf("startxref");
1607
+ if (at < 0) throw new Error("No startxref");
1608
+ return readInt(bytes, bytes.length - tail.length + at + 9).n;
1609
+ }
1610
+ function readTable(bytes, offset) {
1611
+ const entries = /* @__PURE__ */ new Map();
1612
+ let i = readToken(bytes, offset).end;
1613
+ for (;;) {
1614
+ const head = readToken(bytes, i);
1615
+ if (head.token.startsWith("trailer")) {
1616
+ const trailer = parseValue(bytes, head.end - (head.token.length - 7)).value;
1617
+ if (!isDict(trailer)) throw new Error("Trailer is not a dictionary");
1618
+ return {
1619
+ entries,
1620
+ trailer
1621
+ };
1622
+ }
1623
+ const first = readInt(bytes, i);
1624
+ const count = readInt(bytes, first.end);
1625
+ i = count.end;
1626
+ for (let k = 0; k < count.n; k++) {
1627
+ const off = readInt(bytes, i);
1628
+ const gen = readInt(bytes, off.end);
1629
+ const kind = readToken(bytes, gen.end);
1630
+ i = kind.end;
1631
+ if (kind.token === "n") entries.set(first.n + k, {
1632
+ type: "offset",
1633
+ offset: off.n,
1634
+ gen: gen.n
1635
+ });
1636
+ else if (kind.token === "f") entries.set(first.n + k, { type: "free" });
1637
+ else throw new Error(`Bad xref entry type "${kind.token}"`);
1638
+ }
1639
+ }
1640
+ }
1641
+ const numbers = (v) => Array.isArray(v) && v.every((x) => typeof x === "number" && x >= 0) ? v : void 0;
1642
+ async function readStream(bytes, offset) {
1643
+ const obj = parseIndirect(bytes, offset);
1644
+ const dict = obj.value;
1645
+ if (!isDict(dict) || obj.streamStart === void 0) throw new Error("Xref stream expected");
1646
+ const length = dict.entries.get("Length");
1647
+ const data = await decodeStream(bytes, dict, obj.streamStart, typeof length === "number" ? length : void 0);
1648
+ const w = numbers(dict.entries.get("W"));
1649
+ if (!w || w.length !== 3 || w.some((x) => x > 8)) throw new Error("Bad /W");
1650
+ const size = dict.entries.get("Size");
1651
+ const index = numbers(dict.entries.get("Index")) ?? [0, typeof size === "number" ? size : 0];
1652
+ const [w0, w1, w2] = w;
1653
+ const rowLength = w0 + w1 + w2;
1654
+ if (rowLength === 0) throw new Error("Empty xref rows");
1655
+ const field = (at, width) => {
1656
+ let n = 0;
1657
+ for (let k = 0; k < width; k++) n = n * 256 + data[at + k];
1658
+ return n;
1659
+ };
1660
+ const entries = /* @__PURE__ */ new Map();
1661
+ let row = 0;
1662
+ for (let s = 0; s + 1 < index.length; s += 2) for (let k = 0; k < index[s + 1]; k++, row++) {
1663
+ const at = row * rowLength;
1664
+ if (at + rowLength > data.length) return {
1665
+ entries,
1666
+ trailer: dict
1667
+ };
1668
+ const type = w0 === 0 ? 1 : field(at, w0);
1669
+ const a = field(at + w0, w1);
1670
+ const b = field(at + w0 + w1, w2);
1671
+ const num = index[s] + k;
1672
+ if (type === 0) entries.set(num, { type: "free" });
1673
+ else if (type === 1) entries.set(num, {
1674
+ type: "offset",
1675
+ offset: a,
1676
+ gen: b
1677
+ });
1678
+ else if (type === 2) entries.set(num, {
1679
+ type: "compressed",
1680
+ stream: a,
1681
+ index: b
1682
+ });
1683
+ }
1684
+ return {
1685
+ entries,
1686
+ trailer: dict
1687
+ };
1688
+ }
1689
+ async function readSection(bytes, offset) {
1690
+ if (!Number.isInteger(offset) || offset < 0 || offset >= bytes.length) throw new Error("Xref offset out of range");
1691
+ return readToken(bytes, offset).token === "xref" ? readTable(bytes, offset) : readStream(bytes, offset);
1692
+ }
1693
+ /** Newest entry wins along the /Prev chain (and a hybrid file's /XRefStm). Undefined when startxref or a section cannot be read. */
1694
+ async function readXref(bytes) {
1695
+ try {
1696
+ const entries = /* @__PURE__ */ new Map();
1697
+ const visited = /* @__PURE__ */ new Set();
1698
+ let trailer;
1699
+ let next = findStartxref(bytes);
1700
+ while (next !== void 0 && !visited.has(next)) {
1701
+ visited.add(next);
1702
+ const section = await readSection(bytes, next);
1703
+ trailer ??= section.trailer;
1704
+ const hybrid = section.trailer.entries.get("XRefStm");
1705
+ if (typeof hybrid === "number" && !visited.has(hybrid)) {
1706
+ visited.add(hybrid);
1707
+ const extra = await readSection(bytes, hybrid);
1708
+ for (const [num, e] of extra.entries) if (!entries.has(num)) entries.set(num, e);
1709
+ }
1710
+ for (const [num, e] of section.entries) if (!entries.has(num)) entries.set(num, e);
1711
+ const prev = section.trailer.entries.get("Prev");
1712
+ next = typeof prev === "number" ? prev : void 0;
1713
+ }
1714
+ return trailer ? {
1715
+ entries,
1716
+ trailer
1717
+ } : void 0;
1718
+ } catch {
1719
+ return;
1720
+ }
1721
+ }
1722
+ //#endregion
1723
+ //#region src/pdf/objects.ts
1724
+ var Reader = class {
1725
+ bytes;
1726
+ xref;
1727
+ trailer;
1728
+ objectStreams = /* @__PURE__ */ new Map();
1729
+ constructor(bytes, xref) {
1730
+ this.bytes = bytes;
1731
+ this.xref = xref;
1732
+ this.trailer = xref.trailer;
1733
+ }
1734
+ async get(ref) {
1735
+ try {
1736
+ return await this.load(ref, true);
1737
+ } catch {
1738
+ return;
1739
+ }
1740
+ }
1741
+ async resolve(v) {
1742
+ return isRef(v) ? this.get(v) : v;
1743
+ }
1744
+ /** `followLength` is false while resolving a /Length, so a self-referencing length cannot recurse. */
1745
+ async load(ref, followLength) {
1746
+ const entry = this.xref.entries.get(ref.num);
1747
+ if (!entry || entry.type === "free") return void 0;
1748
+ if (entry.type === "offset") {
1749
+ if (entry.gen !== ref.gen) return void 0;
1750
+ const obj = parseIndirect(this.bytes, entry.offset);
1751
+ return obj.num === ref.num && obj.gen === ref.gen ? obj.value : void 0;
1752
+ }
1753
+ if (ref.gen !== 0) return void 0;
1754
+ const container = await this.objectStream(entry.stream, followLength);
1755
+ if (!container) return void 0;
1756
+ let pair = container.pairs[entry.index];
1757
+ if (pair?.num !== ref.num) pair = container.pairs.find((p) => p.num === ref.num);
1758
+ if (!pair) return void 0;
1759
+ return parseValue(container.decoded, container.first + pair.offset).value;
1760
+ }
1761
+ objectStream(num, followLength) {
1762
+ let cached = this.objectStreams.get(num);
1763
+ if (!cached) {
1764
+ cached = this.readObjectStream(num, followLength).catch(() => void 0);
1765
+ this.objectStreams.set(num, cached);
1766
+ }
1767
+ return cached;
1768
+ }
1769
+ async readObjectStream(num, followLength) {
1770
+ const entry = this.xref.entries.get(num);
1771
+ if (entry?.type !== "offset") return void 0;
1772
+ const obj = parseIndirect(this.bytes, entry.offset);
1773
+ const dict = obj.value;
1774
+ if (obj.num !== num || !isDict(dict) || obj.streamStart === void 0) return void 0;
1775
+ const n = dict.entries.get("N");
1776
+ const first = dict.entries.get("First");
1777
+ if (typeof n !== "number" || typeof first !== "number") return void 0;
1778
+ let length = dict.entries.get("Length");
1779
+ if (isRef(length)) length = followLength ? await this.loadLength(length) : void 0;
1780
+ const decoded = await decodeStream(this.bytes, dict, obj.streamStart, typeof length === "number" ? length : void 0);
1781
+ const pairs = [];
1782
+ let at = 0;
1783
+ for (let i = 0; i < n; i++) {
1784
+ const a = parseValue(decoded, at);
1785
+ const b = parseValue(decoded, a.end);
1786
+ if (typeof a.value !== "number" || typeof b.value !== "number") return void 0;
1787
+ pairs.push({
1788
+ num: a.value,
1789
+ offset: b.value
1790
+ });
1791
+ at = b.end;
1792
+ }
1793
+ return {
1794
+ decoded,
1795
+ first,
1796
+ pairs
1797
+ };
1798
+ }
1799
+ async loadLength(ref) {
1800
+ try {
1801
+ return await this.load(ref, false);
1802
+ } catch {
1803
+ return;
1804
+ }
1805
+ }
1806
+ };
1807
+ /** Undefined when the file is encrypted (`/Encrypt` in the trailer) or its xref cannot be read. */
1808
+ async function openPdfObjects(bytes) {
1809
+ try {
1810
+ const xref = await readXref(bytes);
1811
+ if (!xref || xref.trailer.entries.has("Encrypt")) return void 0;
1812
+ return new Reader(bytes, xref);
1813
+ } catch {
1814
+ return;
1815
+ }
1816
+ }
1817
+ //#endregion
1818
+ //#region src/model/annotations/embedded-fields.ts
1819
+ /**
1820
+ * `a` with `fields` merged in: `callout`, `fringe`, `head` and the font keys of `defaultStyle`
1821
+ * on a FreeText, `icon` on a note. Undefined when `a` is not embedded, the fields do not fit its
1822
+ * type, or the result fails the schema.
1823
+ */
1824
+ function withEmbeddedFields(a, fields) {
1825
+ if (a.source !== "pdf") return void 0;
1826
+ const { callout, fringe, head, icon, font } = fields;
1827
+ let candidate;
1828
+ if (a.type === "text") {
1829
+ if (icon === void 0 || callout || fringe || head !== void 0 || font) return void 0;
1830
+ candidate = {
1831
+ ...a,
1832
+ icon
1833
+ };
1834
+ } else if (a.type === "freetext") {
1835
+ if (icon !== void 0) return void 0;
1836
+ candidate = {
1837
+ ...a,
1838
+ ...callout ? { callout } : {},
1839
+ ...fringe ? { fringe } : {},
1840
+ ...head !== void 0 ? { head } : {},
1841
+ defaultStyle: {
1842
+ ...a.defaultStyle,
1843
+ ...font
1844
+ }
1845
+ };
1846
+ } else return;
1847
+ const parsed = AnnotationSchema.safeParse(candidate);
1848
+ return parsed.success ? parsed.data : void 0;
1849
+ }
1850
+ /**
1851
+ * Reads every target once, calling `getData` only if a target names an annotation in
1852
+ * `annotations` by an object reference, and returns the fields read, by id. A target the file
1853
+ * adds nothing to, or whose fields would fail the schema, is left out. Never throws: any
1854
+ * failure leaves the annotations as they are.
1855
+ */
1856
+ async function readEmbeddedFields(annotations, targets, getData) {
1857
+ const read = /* @__PURE__ */ new Map();
1858
+ if (targets.length === 0) return read;
1859
+ try {
1860
+ const byId = new Map(annotations.map((a) => [a.id, a]));
1861
+ const work = targets.flatMap((target) => {
1862
+ const current = byId.get(target.id);
1863
+ const ref = refOfPdfjsId(target.id.split(":")[2] ?? "");
1864
+ return current && ref ? [{
1865
+ target,
1866
+ current,
1867
+ ref
1868
+ }] : [];
1869
+ });
1870
+ if (work.length === 0) return read;
1871
+ const objs = await openPdfObjects(await getData());
1872
+ if (!objs) return read;
1873
+ for (const { target, current, ref } of work) try {
1874
+ const fields = await readFields(objs, target, ref);
1875
+ if (fields && withEmbeddedFields(current, fields)) read.set(current.id, fields);
1876
+ } catch {}
1877
+ } catch {}
1878
+ return read;
1879
+ }
1880
+ async function readFields(objs, target, ref) {
1881
+ if (target.subtype === "Text") {
1882
+ const icon = await readTextIcon(objs, ref);
1883
+ return icon === void 0 ? void 0 : { icon };
1884
+ }
1885
+ const fields = await readFreeTextFields(objs, ref, target.rect, target.daFont);
1886
+ return Object.keys(fields).length > 0 ? fields : void 0;
1887
+ }
1888
+ //#endregion
1166
1889
  //#region src/model/env.ts
1167
1890
  /** True only when the build or runtime says production; browsers without `process` count as dev. */
1168
1891
  function isProduction() {
@@ -1174,6 +1897,7 @@ function isProduction() {
1174
1897
  }
1175
1898
  //#endregion
1176
1899
  //#region src/model/annotations/history.ts
1900
+ const noop = () => {};
1177
1901
  /**
1178
1902
  * Snapshot-based undo and redo over an uncontrolled store.
1179
1903
  *
@@ -1193,6 +1917,8 @@ function createHistory(store, opts = {}) {
1193
1917
  batch(fn) {
1194
1918
  fn();
1195
1919
  },
1920
+ track: () => noop,
1921
+ react: () => noop,
1196
1922
  destroy() {}
1197
1923
  };
1198
1924
  const limit = opts.limit ?? 200;
@@ -1200,14 +1926,84 @@ function createHistory(store, opts = {}) {
1200
1926
  const redoStack = [];
1201
1927
  let previous = store.list.get();
1202
1928
  let replaying = false;
1203
- /** Open batch depth; while above zero, changes update `previous` but push no entry. */
1929
+ /**
1930
+ * Open step depth (batches, and an edit while its reactions run); while above zero, changes
1931
+ * update the baselines but push no entry.
1932
+ */
1204
1933
  let depth = 0;
1205
- /** The list when the outermost batch opened (or was restarted by a replace). */
1206
- let batchStart = null;
1934
+ /** The lists when the outermost step opened (or were restarted by a replace). */
1935
+ let start = null;
1936
+ /** Each tracked store with its list as of the last change history saw. */
1937
+ const tracked = /* @__PURE__ */ new Map();
1938
+ const reactions = /* @__PURE__ */ new Set();
1207
1939
  const sync = () => {
1208
1940
  canUndo.set(undoStack.length > 0);
1209
1941
  canRedo.set(redoStack.length > 0);
1210
1942
  };
1943
+ const push = (entry) => {
1944
+ undoStack.push(entry);
1945
+ if (undoStack.length > limit) undoStack.shift();
1946
+ redoStack.length = 0;
1947
+ sync();
1948
+ };
1949
+ /**
1950
+ * Forgets `recordStore`'s records in every entry, so undo and redo leave it alone. An entry
1951
+ * left with nothing to replay (no annotation change, no other store) is dropped.
1952
+ */
1953
+ const dropEntries = (recordStore) => {
1954
+ for (const stack of [undoStack, redoStack]) {
1955
+ const kept = stack.filter((entry) => {
1956
+ if (!entry.records.delete(recordStore)) return true;
1957
+ const { before, after } = entry.annotations;
1958
+ return entry.records.size > 0 || !sameList(before, after);
1959
+ });
1960
+ stack.splice(0, stack.length, ...kept);
1961
+ }
1962
+ sync();
1963
+ };
1964
+ const resetStacks = () => {
1965
+ undoStack.length = 0;
1966
+ redoStack.length = 0;
1967
+ sync();
1968
+ };
1969
+ /** Opens the outermost step from `annotations` and every tracked store's baseline. */
1970
+ const open = (annotations) => {
1971
+ const records = /* @__PURE__ */ new Map();
1972
+ for (const [s, t] of tracked) records.set(s, t.previous);
1973
+ start = {
1974
+ annotations,
1975
+ records
1976
+ };
1977
+ };
1978
+ /**
1979
+ * Closes the outermost step: one entry for everything that changed since it opened. A step
1980
+ * whose lists end where they began (the same objects in the same order) records nothing.
1981
+ */
1982
+ const close = () => {
1983
+ const from = start;
1984
+ start = null;
1985
+ if (!from) return;
1986
+ const annotations = {
1987
+ before: from.annotations,
1988
+ after: store.list.get()
1989
+ };
1990
+ previous = annotations.after;
1991
+ const records = /* @__PURE__ */ new Map();
1992
+ for (const [s, t] of tracked) {
1993
+ const before = from.records.get(s) ?? t.previous;
1994
+ const after = s.list.get();
1995
+ t.previous = after;
1996
+ if (!sameList(before, after)) records.set(s, {
1997
+ before,
1998
+ after
1999
+ });
2000
+ }
2001
+ if (records.size === 0 && sameList(annotations.before, annotations.after)) return;
2002
+ push({
2003
+ annotations,
2004
+ records
2005
+ });
2006
+ };
1211
2007
  let destroyed = false;
1212
2008
  const unsubscribe = store.on("change", (change) => {
1213
2009
  const current = store.list.get();
@@ -1215,31 +2011,45 @@ function createHistory(store, opts = {}) {
1215
2011
  previous = current;
1216
2012
  return;
1217
2013
  }
2014
+ if (change.kind === "replace" && change.reason === "embedded") {
2015
+ const fill = (list) => list.map((a) => {
2016
+ const f = change.fields.get(a.id);
2017
+ return f && withEmbeddedFields(a, f) || a;
2018
+ });
2019
+ for (const { annotations } of [...undoStack, ...redoStack]) {
2020
+ annotations.before = fill(annotations.before);
2021
+ annotations.after = fill(annotations.after);
2022
+ }
2023
+ if (start) start.annotations = fill(start.annotations);
2024
+ previous = current;
2025
+ return;
2026
+ }
1218
2027
  if (change.kind === "replace") {
1219
- undoStack.length = 0;
1220
- redoStack.length = 0;
1221
2028
  previous = current;
1222
- if (depth > 0) batchStart = current;
1223
- sync();
2029
+ if (depth > 0) open(current);
2030
+ resetStacks();
1224
2031
  return;
1225
2032
  }
1226
2033
  if (depth > 0) {
1227
2034
  previous = current;
2035
+ for (const fn of [...reactions]) fn(change);
1228
2036
  return;
1229
2037
  }
1230
- undoStack.push({
1231
- before: previous,
1232
- after: current
1233
- });
1234
- if (undoStack.length > limit) undoStack.shift();
1235
- redoStack.length = 0;
1236
- previous = current;
1237
- sync();
2038
+ open(previous);
2039
+ depth++;
2040
+ try {
2041
+ for (const fn of [...reactions]) fn(change);
2042
+ } finally {
2043
+ if (--depth === 0 && !destroyed) close();
2044
+ }
1238
2045
  });
1239
- const apply = (list) => {
2046
+ /** Replays a step's lists under the `replaying` guard, skipping stores no longer tracked. */
2047
+ const apply = (entry, side) => {
1240
2048
  replaying = true;
1241
2049
  try {
1242
- store.replaceAll([...list], "history");
2050
+ const { annotations } = entry;
2051
+ if (!sameList(annotations.before, annotations.after)) store.replaceAll([...annotations[side]], "history");
2052
+ for (const [s, snapshot] of entry.records) if (tracked.has(s)) s.replaceAll([...snapshot[side]], "history");
1243
2053
  } finally {
1244
2054
  replaying = false;
1245
2055
  }
@@ -1251,14 +2061,14 @@ function createHistory(store, opts = {}) {
1251
2061
  const entry = undoStack.pop();
1252
2062
  if (destroyed || !entry) return;
1253
2063
  redoStack.push(entry);
1254
- apply(entry.before);
2064
+ apply(entry, "before");
1255
2065
  sync();
1256
2066
  },
1257
2067
  redo() {
1258
2068
  const entry = redoStack.pop();
1259
2069
  if (destroyed || !entry) return;
1260
2070
  undoStack.push(entry);
1261
- apply(entry.after);
2071
+ apply(entry, "after");
1262
2072
  sync();
1263
2073
  },
1264
2074
  batch(fn) {
@@ -1266,50 +2076,202 @@ function createHistory(store, opts = {}) {
1266
2076
  fn();
1267
2077
  return;
1268
2078
  }
1269
- if (depth++ === 0) batchStart = store.list.get();
2079
+ if (depth++ === 0) open(store.list.get());
1270
2080
  try {
1271
2081
  const result = fn();
1272
2082
  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.");
1273
2083
  } finally {
1274
- if (--depth === 0 && !destroyed) {
1275
- const before = batchStart;
1276
- batchStart = null;
1277
- const after = store.list.get();
1278
- if (before && !sameAnnotations(before, after)) {
1279
- undoStack.push({
1280
- before,
1281
- after
1282
- });
1283
- if (undoStack.length > limit) undoStack.shift();
1284
- redoStack.length = 0;
1285
- sync();
1286
- }
1287
- }
2084
+ if (--depth === 0 && !destroyed) close();
1288
2085
  }
1289
2086
  },
2087
+ track(recordStore) {
2088
+ if (destroyed || tracked.has(recordStore)) return noop;
2089
+ const unsubscribeStore = recordStore.on("change", (change) => {
2090
+ const t = tracked.get(recordStore);
2091
+ if (!t) return;
2092
+ const before = t.previous;
2093
+ const current = recordStore.list.get();
2094
+ t.previous = current;
2095
+ if (replaying || change.kind === "replace" && change.reason === "history") return;
2096
+ if (change.kind === "replace") {
2097
+ if (depth > 0 && start) start.records.set(recordStore, current);
2098
+ dropEntries(recordStore);
2099
+ return;
2100
+ }
2101
+ if (depth > 0) return;
2102
+ push({
2103
+ annotations: {
2104
+ before: previous,
2105
+ after: previous
2106
+ },
2107
+ records: /* @__PURE__ */ new Map([[recordStore, {
2108
+ before,
2109
+ after: current
2110
+ }]])
2111
+ });
2112
+ });
2113
+ tracked.set(recordStore, {
2114
+ previous: recordStore.list.get(),
2115
+ unsubscribe: unsubscribeStore
2116
+ });
2117
+ if (start) start.records.set(recordStore, recordStore.list.get());
2118
+ return () => {
2119
+ if (tracked.get(recordStore)?.unsubscribe !== unsubscribeStore) return;
2120
+ unsubscribeStore();
2121
+ tracked.delete(recordStore);
2122
+ start?.records.delete(recordStore);
2123
+ };
2124
+ },
2125
+ react(fn) {
2126
+ if (destroyed) return noop;
2127
+ const run = (edit) => fn(edit);
2128
+ reactions.add(run);
2129
+ return () => {
2130
+ reactions.delete(run);
2131
+ };
2132
+ },
1290
2133
  destroy() {
1291
2134
  destroyed = true;
1292
- batchStart = null;
2135
+ start = null;
1293
2136
  unsubscribe();
1294
- undoStack.length = 0;
1295
- redoStack.length = 0;
1296
- sync();
2137
+ for (const t of tracked.values()) t.unsubscribe();
2138
+ tracked.clear();
2139
+ reactions.clear();
2140
+ resetStacks();
1297
2141
  },
1298
2142
  clear() {
1299
- undoStack.length = 0;
1300
- redoStack.length = 0;
1301
2143
  previous = store.list.get();
1302
- sync();
2144
+ for (const [s, t] of tracked) t.previous = s.list.get();
2145
+ resetStacks();
1303
2146
  }
1304
2147
  };
1305
2148
  }
1306
- /** The same annotation objects in the same order. */
1307
- function sameAnnotations(a, b) {
2149
+ /** The same objects in the same order. */
2150
+ function sameList(a, b) {
1308
2151
  return a === b || a.length === b.length && a.every((x, i) => x === b[i]);
1309
2152
  }
1310
2153
  //#endregion
2154
+ //#region src/model/annotations/gloss-mapping.ts
2155
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2156
+ /** `rest` with `own` merged into a copy of its `custom.inkbar`; `own` wins. */
2157
+ function withCustom(rest, own) {
2158
+ const custom = isObject(rest.custom) ? rest.custom : {};
2159
+ const inkbar = isObject(custom.inkbar) ? custom.inkbar : {};
2160
+ return {
2161
+ ...rest,
2162
+ custom: {
2163
+ ...custom,
2164
+ inkbar: {
2165
+ ...inkbar,
2166
+ ...own
2167
+ }
2168
+ }
2169
+ };
2170
+ }
2171
+ /**
2172
+ * An annotation as a Gloss file writes it. Gloss has no loupe or mask and no pen pressure or
2173
+ * free-text border colour, so these go under `custom.inkbar`, with loupes and masks written as
2174
+ * the circle or square they draw.
2175
+ */
2176
+ function toFile(annotation) {
2177
+ switch (annotation.type) {
2178
+ case "ink": {
2179
+ const { pressure, ...rest } = annotation;
2180
+ return pressure ? withCustom(rest, { pressure }) : rest;
2181
+ }
2182
+ case "freetext": {
2183
+ const { borderColor, ...rest } = annotation;
2184
+ return borderColor ? withCustom(rest, { borderColor }) : rest;
2185
+ }
2186
+ case "loupe": {
2187
+ const { magnification, ...rest } = annotation;
2188
+ return withCustom({
2189
+ ...rest,
2190
+ type: "circle"
2191
+ }, {
2192
+ type: "loupe",
2193
+ magnification
2194
+ });
2195
+ }
2196
+ case "mask": {
2197
+ const { shape, dim, ...rest } = annotation;
2198
+ return withCustom({
2199
+ ...rest,
2200
+ type: shape === "ellipse" ? "circle" : "square"
2201
+ }, {
2202
+ type: "mask",
2203
+ dim
2204
+ });
2205
+ }
2206
+ default: return annotation;
2207
+ }
2208
+ }
2209
+ /**
2210
+ * Moves `keys` from `custom.inkbar` to the top level of `raw`, replacing any top-level value
2211
+ * (an absent key deletes it), then drops `custom.inkbar`, and `custom`, if that leaves them
2212
+ * empty. Everything else under `custom` stays as it was.
2213
+ */
2214
+ function lift(raw, inkbar, keys, extra = {}) {
2215
+ const item = {
2216
+ ...raw,
2217
+ ...extra
2218
+ };
2219
+ const left = { ...inkbar };
2220
+ let consumed = false;
2221
+ for (const key of keys) {
2222
+ delete item[key];
2223
+ if (key in left) {
2224
+ item[key] = left[key];
2225
+ delete left[key];
2226
+ consumed = true;
2227
+ }
2228
+ }
2229
+ if (!consumed || !isObject(raw.custom)) return item;
2230
+ const { inkbar: _inkbar, ...others } = raw.custom;
2231
+ const custom = Object.keys(left).length > 0 ? {
2232
+ ...others,
2233
+ inkbar: left
2234
+ } : others;
2235
+ if (Object.keys(custom).length > 0) item.custom = custom;
2236
+ else delete item.custom;
2237
+ return item;
2238
+ }
2239
+ /**
2240
+ * A raw file item as Inkbar parses it: Inkbar's own data moves out of `custom.inkbar`. An item
2241
+ * it doesn't recognise passes through unchanged.
2242
+ */
2243
+ function fromFile(raw) {
2244
+ const inkbar = isObject(raw.custom) && isObject(raw.custom.inkbar) ? raw.custom.inkbar : {};
2245
+ const from = (keys, extra) => ({
2246
+ item: lift(raw, inkbar, keys, extra),
2247
+ lifted: keys
2248
+ });
2249
+ const unchanged = {
2250
+ item: raw,
2251
+ lifted: []
2252
+ };
2253
+ switch (raw.type) {
2254
+ case "ink": return from(["pressure"]);
2255
+ case "freetext": return from(["borderColor"]);
2256
+ case "circle":
2257
+ if (inkbar.type === "loupe") return from(["type", "magnification"]);
2258
+ if (inkbar.type === "mask") return from(["type", "dim"], { shape: "ellipse" });
2259
+ return unchanged;
2260
+ case "square":
2261
+ if (inkbar.type === "mask") return from(["type", "dim"], { shape: "rect" });
2262
+ return unchanged;
2263
+ default: return unchanged;
2264
+ }
2265
+ }
2266
+ //#endregion
1311
2267
  //#region src/model/annotations/json.ts
1312
- const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...o.shape.type._zod.values]));
2268
+ /**
2269
+ * @internal The Gloss types this core parses, taken from its own schemas rather than Gloss's
2270
+ * list, so a type a later Gloss adds is still treated as foreign here. Loupes and masks are
2271
+ * Inkbar's own types: a file writes them as circles and squares, so a `loupe` or `mask` item is
2272
+ * foreign too.
2273
+ */
2274
+ const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...o.shape.type._zod.values]).filter((type) => type !== "loupe" && type !== "mask"));
1313
2275
  /**
1314
2276
  * Session annotations plus any preserved foreign items. Embedded (`source: 'pdf'`) ones stay in
1315
2277
  * the file. `source` and `locked` are viewer state and are left out.
@@ -1318,21 +2280,24 @@ const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...
1318
2280
  * export would carry: editing it never touches the store, and values JSON cannot hold, such as
1319
2281
  * functions or `undefined` inside `custom`, are left out exactly as `JSON.stringify` would.
1320
2282
  *
1321
- * Without foreign items the result validates against this version's `schema.json`. With them it
1322
- * validates only against the newer schema that produced them, since this version's schema
1323
- * describes only the types it knows.
2283
+ * Gloss has no loupe or mask, pen pressure or free-text border colour, so Inkbar writes these
2284
+ * under `custom.inkbar`, with loupes and masks as the circle or square they draw. Every other key
2285
+ * under `custom` goes out as it is.
2286
+ *
2287
+ * The result validates against the Gloss schema (`gloss-format/schema.json`). Foreign items
2288
+ * validate as unknown annotations while their type is one Gloss does not define.
1324
2289
  */
1325
2290
  function exportAnnotations(store, preserved = [], documentKey) {
1326
- const session = store.list.get().filter((a) => a.source === "session").map(({ source: _source, locked: _locked, ...rest }) => rest);
2291
+ const session = store.list.get().filter((a) => a.source === "session").map(({ source: _source, locked: _locked, ...rest }) => toFile(rest));
1327
2292
  return {
1328
- format: ANNOTATION_FORMAT,
1329
- version: 1,
2293
+ format: GLOSS_FORMAT$1,
2294
+ version: GLOSS_VERSION$1,
1330
2295
  ...documentKey === void 0 ? {} : { documentKey },
1331
2296
  annotations: JSON.parse(JSON.stringify([...session, ...preserved]))
1332
2297
  };
1333
2298
  }
1334
2299
  /**
1335
- * Validates the envelope (throws a TypeError when it is not an inkbar/annotations v1 document),
2300
+ * Validates the envelope (throws a TypeError when it is not a gloss v1 document),
1336
2301
  * then each item on its own. Embedded annotations are never touched; imported items become
1337
2302
  * session annotations.
1338
2303
  */
@@ -1353,7 +2318,7 @@ function importAnnotations(store, data, opts) {
1353
2318
  function planImport(store, data, opts) {
1354
2319
  const raw = typeof data === "string" ? JSON.parse(data) : data;
1355
2320
  const envelope = AnnotationDocumentSchema.safeParse(raw);
1356
- if (!envelope.success) throw new TypeError(`Expected an ${ANNOTATION_FORMAT} v1 document: ${envelope.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
2321
+ if (!envelope.success) throw new TypeError(`Expected a ${GLOSS_FORMAT$1} v${GLOSS_VERSION$1} document: ${envelope.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
1357
2322
  const embeddedIds = new Set(store.list.get().filter((a) => a.source === "pdf").map((a) => a.id));
1358
2323
  const result = {
1359
2324
  imported: 0,
@@ -1389,21 +2354,28 @@ function planImport(store, data, opts) {
1389
2354
  accepted.add(id);
1390
2355
  return true;
1391
2356
  };
1392
- envelope.data.annotations.forEach((item, index) => {
1393
- const type = item?.type;
2357
+ envelope.data.annotations.forEach((raw, index) => {
2358
+ const type = raw?.type;
1394
2359
  if (typeof type === "string" && !KNOWN_TYPES.has(type)) {
1395
- if (!isForeign(item)) result.errors.push({
2360
+ if (!isForeign(raw)) result.errors.push({
1396
2361
  index,
1397
2362
  issues: [`type: unknown type "${type}" needs a string id and a numeric page to be preserved`]
1398
2363
  });
1399
- else if (claim(item.id, index)) result.unknown.push(item);
2364
+ else if (claim(raw.id, index)) result.unknown.push(raw);
1400
2365
  return;
1401
2366
  }
2367
+ const { item, lifted } = isRecord(raw) && typeof type === "string" ? fromFile(raw) : {
2368
+ item: raw,
2369
+ lifted: []
2370
+ };
1402
2371
  const parsed = ExportedAnnotationSchema.safeParse(item);
1403
2372
  if (!parsed.success) {
1404
2373
  result.errors.push({
1405
2374
  index,
1406
- issues: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
2375
+ issues: parsed.error.issues.map((i) => {
2376
+ const path = i.path.join(".");
2377
+ return `${typeof i.path[0] === "string" && lifted.includes(i.path[0]) ? `custom.inkbar.${path}` : path}: ${i.message}`;
2378
+ })
1407
2379
  });
1408
2380
  return;
1409
2381
  }
@@ -1435,8 +2407,11 @@ function planImport(store, data, opts) {
1435
2407
  }
1436
2408
  };
1437
2409
  }
2410
+ function isRecord(item) {
2411
+ return typeof item === "object" && item !== null && !Array.isArray(item);
2412
+ }
1438
2413
  function isForeign(item) {
1439
- if (typeof item !== "object" || item === null || Array.isArray(item)) return false;
2414
+ if (!isRecord(item)) return false;
1440
2415
  const { type, id, page } = item;
1441
2416
  return typeof type === "string" && typeof id === "string" && typeof page === "number";
1442
2417
  }
@@ -1600,7 +2575,6 @@ function createAnnotationStore(opts) {
1600
2575
  }
1601
2576
  if (input.type === "redact") {
1602
2577
  if (input.interiorColor === void 0) candidate.interiorColor = opts.defaults.fill();
1603
- if (input.origin === "suggested" && input.suggestion && input.color === void 0) candidate.color = opts.defaults.suggestion(confidenceBucket(input.suggestion.confidence));
1604
2578
  }
1605
2579
  return AnnotationSchema.parse(candidate);
1606
2580
  }
@@ -1615,10 +2589,12 @@ function createAnnotationStore(opts) {
1615
2589
  if (!entry) return;
1616
2590
  pending.delete(key);
1617
2591
  if (pending.size === 0) proposed = void 0;
2592
+ pruneSelection(basis());
1618
2593
  if (isApplied(entry.change, list.get()) || isProduction()) return;
1619
2594
  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.`);
1620
2595
  }
1621
2596
  function commit(next, change) {
2597
+ if (change.kind !== "replace") emitter.emit("edit", change);
1622
2598
  if (controlled) {
1623
2599
  if (change.kind !== "replace") {
1624
2600
  const key = changeKey(change);
@@ -1717,6 +2693,7 @@ function createAnnotationStore(opts) {
1717
2693
  after
1718
2694
  };
1719
2695
  if (controlled && before.source === "pdf") {
2696
+ emitter.emit("edit", change);
1720
2697
  list.set(swap(list.get()));
1721
2698
  if (proposed) proposed = next;
1722
2699
  emitter.emit("change", change);
@@ -1785,6 +2762,26 @@ function createAnnotationStore(opts) {
1785
2762
  reason: "import"
1786
2763
  });
1787
2764
  },
2765
+ _patchEmbedded(fields) {
2766
+ if (destroyed) return;
2767
+ const applied = /* @__PURE__ */ new Map();
2768
+ const merge = (items) => items.map((a) => {
2769
+ const f = a.source === "pdf" ? fields.get(a.id) : void 0;
2770
+ const merged = f && withEmbeddedFields(a, f);
2771
+ if (!merged) return a;
2772
+ applied.set(a.id, f);
2773
+ return merged;
2774
+ });
2775
+ const next = merge(list.get());
2776
+ if (applied.size === 0) return;
2777
+ list.set(next);
2778
+ if (proposed) proposed = merge(proposed);
2779
+ emitter.emit("change", {
2780
+ kind: "replace",
2781
+ reason: "embedded",
2782
+ fields: applied
2783
+ });
2784
+ },
1788
2785
  on: (event, fn) => emitter.on(event, fn),
1789
2786
  destroy() {
1790
2787
  destroyed = true;
@@ -2019,6 +3016,8 @@ async function openDocument(source, options) {
2019
3016
  });
2020
3017
  const embedded = [];
2021
3018
  let preserved = [];
3019
+ /** Embedded annotations whose PDF.js data lacks fields the file holds; read after `ready`. */
3020
+ const targets = [];
2022
3021
  const destroyedError = () => new InkError("DocumentDestroyed", "The document has been destroyed");
2023
3022
  const ready = (async () => {
2024
3023
  const pdfjs = await loadPdfjs(options.assets);
@@ -2059,6 +3058,7 @@ async function openDocument(source, options) {
2059
3058
  });
2060
3059
  embedded.push(...summarise(perPage.map((r) => r.counts)));
2061
3060
  const fromFile = perPage.flatMap((r) => r.annotations);
3061
+ targets.push(...perPage.flatMap((r) => r.targets));
2062
3062
  if (fromFile.length > 0) store._setEmbedded(fromFile);
2063
3063
  })().then(() => {
2064
3064
  status.set({ state: "ready" });
@@ -2075,7 +3075,14 @@ async function openDocument(source, options) {
2075
3075
  const historyOption = options.history ?? true;
2076
3076
  const historyLimit = historyOption === false ? 0 : historyOption === true ? void 0 : historyOption.limit;
2077
3077
  const history = createHistory(store, historyLimit === void 0 ? {} : { limit: historyLimit });
2078
- return {
3078
+ ready.then(() => {
3079
+ if (destroyed || !handles) return;
3080
+ const { proxy } = handles;
3081
+ readEmbeddedFields(store.list.get(), targets, () => proxy.getData()).then((patched) => {
3082
+ if (!destroyed && patched.size > 0) store._patchEmbedded(patched);
3083
+ }).catch(() => void 0);
3084
+ }, () => void 0);
3085
+ const doc = {
2079
3086
  key: resolved.key,
2080
3087
  status: readonly(status),
2081
3088
  ready,
@@ -2086,6 +3093,9 @@ async function openDocument(source, options) {
2086
3093
  if (!handles) throw statusError(status.get());
2087
3094
  return textContentFromPdfjs(page, await (await handles.getPage(page)).getTextContent());
2088
3095
  },
3096
+ async pageText(page) {
3097
+ return (await doc.text(page)).items.map((item) => item.str).join(" ");
3098
+ },
2089
3099
  annotations: store,
2090
3100
  embedded,
2091
3101
  history,
@@ -2115,6 +3125,7 @@ async function openDocument(source, options) {
2115
3125
  return handles;
2116
3126
  }
2117
3127
  };
3128
+ return doc;
2118
3129
  }
2119
3130
  const EMBEDDED_CONCURRENCY = 8;
2120
3131
  /** Runs `task(0..count-1)` with at most `limit` in flight; results come back in index order. */
@@ -2835,23 +3846,6 @@ function createToolStyle(deps) {
2835
3846
  };
2836
3847
  }
2837
3848
  //#endregion
2838
- //#region src/model/annotations/suggestions.ts
2839
- /**
2840
- * Keeps a suggested redaction as the user's own: `origin` becomes `'user'` and the colour
2841
- * goes from the confidence colour to the store's default redaction colour, so an accepted
2842
- * mark looks like any other. The suggestion details stay on it. Returns the updated
2843
- * annotation, or undefined (changing nothing) when `id` is not a suggested redaction or is
2844
- * locked.
2845
- */
2846
- function acceptSuggestion(store, id) {
2847
- const a = store._getProposed(id);
2848
- if (a?.type !== "redact" || a.origin !== "suggested" || a.locked) return void 0;
2849
- return store.update(id, {
2850
- origin: "user",
2851
- color: store.defaults.color("redact")
2852
- });
2853
- }
2854
- //#endregion
2855
3849
  //#region src/model/annotations/threads.ts
2856
3850
  function isThreadRoot(a) {
2857
3851
  return a.type === "text" || isTextMarkup(a);
@@ -2968,24 +3962,41 @@ function rectOf(a, b) {
2968
3962
  height: Math.abs(b.y - a.y)
2969
3963
  };
2970
3964
  }
2971
- /** Vertices round the ellipse inscribed in `rect`, the first at the top centre, clockwise on the page. */
3965
+ /**
3966
+ * Vertices round a circle, the first at the top centre, clockwise on the page, stretched so their
3967
+ * bounds fill `rect`.
3968
+ */
2972
3969
  function ring(rect, count, radius) {
2973
- const cx = rect.x + rect.width / 2;
2974
- const cy = rect.y + rect.height / 2;
2975
- return Array.from({ length: count }, (_, i) => {
3970
+ const unit = Array.from({ length: count }, (_, i) => {
2976
3971
  const angle = i * 2 * Math.PI / count;
2977
3972
  const r = radius(i);
2978
3973
  return {
2979
- x: clean(cx + rect.width / 2 * r * Math.sin(angle)),
2980
- y: clean(cy + rect.height / 2 * r * Math.cos(angle))
3974
+ x: r * Math.sin(angle),
3975
+ y: r * Math.cos(angle)
2981
3976
  };
2982
3977
  });
3978
+ const xs = unit.map((p) => p.x);
3979
+ const ys = unit.map((p) => p.y);
3980
+ const fit = (v, min, max, start, size) => clean(max > min ? start + (v - min) / (max - min) * size : start + size / 2);
3981
+ const [minX, maxX, minY, maxY] = [
3982
+ Math.min(...xs),
3983
+ Math.max(...xs),
3984
+ Math.min(...ys),
3985
+ Math.max(...ys)
3986
+ ];
3987
+ return unit.map((p) => ({
3988
+ x: fit(p.x, minX, maxX, rect.x, rect.width),
3989
+ y: fit(p.y, minY, maxY, rect.y, rect.height)
3990
+ }));
2983
3991
  }
2984
- /** A star's `2 * points` vertices, outer and inner alternating, the first at the top centre. */
3992
+ /**
3993
+ * A star's `2 * points` vertices, outer and inner alternating, stretched to fill `rect`, the first
3994
+ * at the top centre.
3995
+ */
2985
3996
  function starVertices(rect, points, innerRatio = .5) {
2986
3997
  return ring(rect, Math.max(2, Math.round(points)) * 2, (i) => i % 2 === 0 ? 1 : innerRatio);
2987
3998
  }
2988
- /** A regular polygon's vertices on the inscribed ellipse, the first at the top centre. */
3999
+ /** A regular polygon's vertices, stretched to fill `rect`, the first at the top centre. */
2989
4000
  function polygonVertices(rect, sides) {
2990
4001
  return ring(rect, Math.max(3, Math.round(sides)), () => 1);
2991
4002
  }
@@ -3085,16 +4096,13 @@ function shapeInput(kind, page, start, end, opts) {
3085
4096
  type: "line",
3086
4097
  page,
3087
4098
  start,
3088
- end,
3089
- head: "None",
3090
- tail: "None"
4099
+ end
3091
4100
  };
3092
4101
  case "arrow": return {
3093
4102
  type: "line",
3094
4103
  page,
3095
4104
  start,
3096
4105
  end,
3097
- tail: "OpenArrow",
3098
4106
  intent: "LineArrow"
3099
4107
  };
3100
4108
  case "rectangle": return {
@@ -3917,6 +4925,6 @@ function createSignatureLibrary(store) {
3917
4925
  };
3918
4926
  }
3919
4927
  //#endregion
3920
- export { exportAnnotations as $, derived as $t, fromAnnotation as A, RedactSchema as At, isBubble as B, isStandardCallout as Bt, repliesTo as C, NoteSchema as Ct, acceptSuggestion as D, PolylineSchema as Dt, withoutReplies as E, PolygonSchema as Et, toFields as F, TextMarkupSchema as Ft, resizedBox as G, PDF_NAME as Gt, keepsAspect as H, INTENTS as Ht, typesInGroup as I, confidenceBucket as It, translated as J, parsePdfDate as Jt, resizedRect as K, isKnown as Kt, hasCallout as L, FringeSchema as Lt, styleGroupOfType as M, SquareSchema as Mt, styleGroupsOf as N, StampSchema as Nt, STYLE_GROUPS as O, QuadSchema as Ot, supportedProps as P, SuggestionSchema as Pt, deriveRect as Q, mergeLineRects as Qt, hasCornerHandles as R, calloutStroke as Rt, isThreadRoot as S, NoteIconSchema as St, threadRoot as T, PolySchema as Tt, movedEndpoint as U, LINE_ENDINGS as Ut, isTextMarkup as V, withCalloutBounds as Vt, movedTip as W, NOTE_ICONS as Wt, openDocument as X, normalizeColor as Xt, withBox as Y, cssVariableDefaults as Yt, createAnnotationStore as Z, staticDefaults as Zt, defaultEnds as _, InkError as _n, InkSchema as _t, createLocalSignatureStore as a, multiplyMatrix as an, importedId as at, isAbandonedNote as b, LoupeSchema as bt, normaliseInk as c, quadsFromFlat as cn, ANNOTATION_FORMAT as ct, recogniseStroke as d, rectFromQuad as dn, AnnotationSchema as dt, readonly as en, importAnnotations as et, DEFAULT_CORNER_RADIUS as f, rectsIntersect as fn, CircleSchema as ft, constrain as g, Emitter as gn, IMAGE_DATA_URL as gt, SHAPE_KINDS as h, unionRects as hn, FreeTextStyleSchema as ht, createInkTemplate as i, invertMatrix as in, importEmbeddedPage as it, styleGroupOf as j, ShapeSchema as jt, createToolStyle as k, RectSchema as kt, placeSignature as l, rectContainsPoint as ln, ANNOTATION_VERSION as lt, DEFAULT_SHAPE_SIZE as m, translateRect as mn, FreeTextSchema as mt, SIGNATURE_INK_WIDTH as n, applyMatrix as nn, isProduction as nt, createMemorySignatureStore as o, pointsFromFlat as on, pdfColorToHex as ot, DEFAULT_SHAPE_OPTIONS as p, translateQuad as pn, ExportedAnnotationSchema as pt, scaled as q, pdfNameSet as qt, createImageTemplate as r, approxEqual as rn, SUPPORTED_SUBTYPES as rt, createSignatureLibrary as s, quadFromRect as sn, summarise as st, DEFAULT_SIGNATURE_WIDTH as t, writable as tn, createHistory as tt, RECOGNISE_THRESHOLD as u, rectFromPoints as un, AnnotationDocumentSchema as ut, isPointerOnlyKind as v, isInkError as vn, LineEndingSchema as vt, threadOf as w, PointSchema as wt, isReply as x, MaskSchema as xt, shapeInput as y, LineSchema as yt, hasRectGeometry as z, freeTextBox as zt };
4928
+ export { importAnnotations as $, derived 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, createRecordStore 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 _, unionRects as _n, IMAGE_DATA_URL as _t, createLocalSignatureStore as a, multiplyMatrix as an, pdfColorToHex as at, isAbandonedNote as b, isInkError as bn, LineSchema as bt, normaliseInk as c, quadsFromFlat as cn, AnnotationSchema as ct, recogniseStroke as d, rectFromPoints as dn, FreeTextSchema as dt, readonly as en, createHistory as et, DEFAULT_CORNER_RADIUS as f, rectFromQuad as fn, FreeTextStyleSchema as ft, constrain as g, translateRect as gn, GLOSS_VERSION$1 as gt, SHAPE_KINDS as h, translateQuad as hn, GLOSS_SCHEMA_ID as ht, createInkTemplate as i, invertMatrix as in, importedId as it, styleGroupOfType as j, RedactSchema as jt, fromAnnotation as k, QuadSchema as kt, placeSignature as l, quadsFromRect as ln, CircleSchema as lt, DEFAULT_SHAPE_SIZE as m, rectsIntersect as mn, GLOSS_MEDIA_TYPE as mt, SIGNATURE_INK_WIDTH as n, applyMatrix as nn, SUPPORTED_SUBTYPES as nt, createMemorySignatureStore as o, pointsFromFlat as on, summarise as ot, DEFAULT_SHAPE_OPTIONS as p, rectFromQuads as pn, GLOSS_FORMAT$1 as pt, translated as q, parsePdfDate as qt, createImageTemplate as r, approxEqual as rn, importEmbeddedPage as rt, createSignatureLibrary as s, quadFromRect as sn, AnnotationDocumentSchema as st, DEFAULT_SIGNATURE_WIDTH as t, writable as tn, isProduction as tt, RECOGNISE_THRESHOLD as u, rectContainsPoint as un, ExportedAnnotationSchema as ut, isPointerOnlyKind as v, Emitter as vn, InkSchema as vt, threadOf as w, NoteSchema as wt, isReply as x, LoupeSchema as xt, shapeInput as y, InkError as yn, LineEndingSchema as yt, isBubble as z, isStandardCallout as zt };
3921
4929
 
3922
- //# sourceMappingURL=signatures-DF2VmNVa.mjs.map
4930
+ //# sourceMappingURL=signatures-D68fuLlt.mjs.map