@inkbar/core 0.4.0 → 0.5.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
@@ -422,20 +423,10 @@ const COLORS = {
422
423
  mask: "#000000",
423
424
  redact: "#C0392B"
424
425
  };
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
426
  const staticDefaults = {
435
427
  color: (type) => COLORS[type],
436
428
  fill: () => "#000000",
437
- opacity: (type) => type === "highlight" ? .4 : 1,
438
- suggestion: (bucket) => SUGGESTION_COLORS[bucket]
429
+ opacity: (type) => type === "highlight" ? .4 : 1
439
430
  };
440
431
  const VARIABLES = {
441
432
  highlight: "--ink-highlight",
@@ -465,8 +456,7 @@ function cssVariableDefaults(el) {
465
456
  return {
466
457
  color: (type) => read(VARIABLES[type]) ?? staticDefaults.color(type),
467
458
  fill: () => read("--ink-redact-fill") ?? staticDefaults.fill(),
468
- opacity: (type) => staticDefaults.opacity(type),
469
- suggestion: (bucket) => read(`--ink-suggestion-${bucket}`) ?? staticDefaults.suggestion(bucket)
459
+ opacity: (type) => staticDefaults.opacity(type)
470
460
  };
471
461
  }
472
462
  /** Accepts `#rgb`, `#rrggbb`, `rgb(r, g, b)` and `rgb(r g b)`; returns `#RRGGBB` or undefined. */
@@ -507,51 +497,12 @@ function parsePdfDate(value, fallback) {
507
497
  //#endregion
508
498
  //#region src/model/annotations/pdf-names.ts
509
499
  /**
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
500
  * A set of PDF names Inkbar knows that stays open: any other valid PDF name loads, is kept and
516
501
  * is written back unchanged. The output type keeps autocomplete for the known values.
517
502
  */
518
503
  function pdfNameSet(known) {
519
504
  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
505
  }
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
506
  /** True when `v` is one of the values in `list`. */
556
507
  function isKnown(list, v) {
557
508
  return list.includes(v);
@@ -562,12 +513,7 @@ function isKnown(list, v) {
562
513
  * The insets from a free text's `rect` to its text box, in points (PDF `/RD`, XFDF `fringe`,
563
514
  * both in this order).
564
515
  */
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
- });
516
+ const FringeSchema = z.object(Fringe.shape);
571
517
  /** An ending's length, and its full width, as a multiple of the stroke width. */
572
518
  const ENDING_SCALE = 3;
573
519
  /**
@@ -640,73 +586,48 @@ function withCalloutBounds(box, callout, width, head) {
640
586
  }
641
587
  //#endregion
642
588
  //#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
- });
589
+ const PointSchema = z.object(Point.shape);
590
+ const RectSchema = z.object(Rect.shape);
656
591
  const QuadSchema = z.object({
657
592
  p1: PointSchema,
658
593
  p2: PointSchema,
659
594
  p3: PointSchema,
660
595
  p4: PointSchema
661
596
  });
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(),
597
+ /** What Inkbar adds to every Gloss type: the fields it always fills in, and its viewer state. */
598
+ const Base = {
667
599
  /** Bounding box in PDF user space. */
668
600
  rect: RectSchema,
669
601
  /** 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 }),
602
+ color: Color,
603
+ opacity: commonFields.opacity.removeDefault(),
604
+ createdAt: commonFields.createdAt.unwrap(),
605
+ modifiedAt: commonFields.modifiedAt.unwrap(),
680
606
  /** Created in this session, or imported from the file. Viewer state, never exported. */
681
607
  source: z.enum(["session", "pdf"]),
682
608
  /** 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({
609
+ locked: z.boolean().optional()
610
+ };
611
+ const Quads = z.array(QuadSchema).min(1);
612
+ const TextMarkupSchema = z.object({
613
+ ...Highlight.shape,
614
+ ...Base,
692
615
  type: z.enum([
693
616
  "highlight",
694
617
  "underline",
695
618
  "strikeout",
696
619
  "squiggly"
697
620
  ]),
698
- quads: z.array(QuadSchema).min(1)
621
+ quads: Quads
699
622
  });
700
623
  /** 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")
624
+ const NoteIconSchema = NoteIcon;
625
+ const NoteSchema = z.object({
626
+ ...Note.shape,
627
+ ...Base
705
628
  });
706
629
  /** 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();
630
+ const LineEndingSchema = LineEnding;
710
631
  /**
711
632
  * A key a type refuses outright rather than dropping like other unknown keys: one an older
712
633
  * shape used (so its value is not silently lost), or one that means nothing on the type (so a
@@ -718,20 +639,9 @@ const Refused = z.never().optional();
718
639
  * `defaultstyle` string. The older `align` is refused; other unknown keys are dropped.
719
640
  */
720
641
  const FreeTextStyleSchema = z.object({
721
- fontFamily: z.string().min(1).default("Helvetica"),
722
- /** Points. */
723
- fontSize: z.number().positive().default(12),
642
+ ...FreeTextStyle.shape,
724
643
  /** 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(),
644
+ color: Color,
735
645
  align: Refused
736
646
  });
737
647
  /**
@@ -739,88 +649,56 @@ const FreeTextStyleSchema = z.object({
739
649
  * `defaultStyle`. A box in the older shape (top-level `fontFamily`, `fontSize`, `align`) is
740
650
  * refused rather than silently losing its style; other unknown keys are dropped.
741
651
  */
742
- const FreeTextSchema = Base.extend({
743
- type: z.literal("freetext"),
744
- /** The text shown on the page. */
745
- contents: z.string(),
652
+ const FreeTextSchema = z.object({
653
+ ...FreeText.shape,
654
+ ...Base,
746
655
  /** 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(),
656
+ color: FreeText.shape.color,
657
+ /** The box's border colour. Absent means the text colour. */
658
+ borderColor: Color.optional(),
755
659
  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
660
  fringe: FringeSchema.optional(),
761
- /** A callout's leader line: the tip, an optional bend, and where it meets the box (PDF `/CL`). */
762
661
  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
662
  fontFamily: Refused,
766
663
  fontSize: Refused,
767
664
  align: Refused
768
665
  });
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()
666
+ const SquareSchema = z.object({
667
+ ...Square.shape,
668
+ ...Base
779
669
  });
780
- const CircleSchema = Base.extend({
781
- type: z.literal("circle"),
782
- ...ShapeFields,
670
+ const CircleSchema = z.object({
671
+ ...Circle.shape,
672
+ ...Base,
783
673
  cornerRadius: Refused
784
674
  });
785
675
  const ShapeSchema = z.discriminatedUnion("type", [SquareSchema, CircleSchema]);
786
- const LineSchema = Base.extend({
787
- type: z.literal("line"),
676
+ const LineSchema = z.object({
677
+ ...Line.shape,
678
+ ...Base,
788
679
  start: PointSchema,
789
- end: PointSchema,
790
- width: z.number().nonnegative().default(1),
791
- dashes: DashesSchema,
792
- head: LineEndingSchema.optional(),
793
- tail: LineEndingSchema.optional()
680
+ end: PointSchema
794
681
  });
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()
682
+ const Vertices = z.array(PointSchema).min(2);
683
+ const PolylineSchema = z.object({
684
+ ...Polyline.shape,
685
+ ...Base,
686
+ vertices: Vertices
808
687
  });
809
- const PolygonSchema = Base.extend({
810
- type: z.literal("polygon"),
811
- ...PolyFields,
688
+ const PolygonSchema = z.object({
689
+ ...Polygon.shape,
690
+ ...Base,
691
+ vertices: Vertices,
812
692
  head: Refused,
813
693
  tail: Refused
814
694
  });
815
695
  const PolySchema = z.discriminatedUnion("type", [PolylineSchema, PolygonSchema]);
816
- const InkShape = Base.extend({
817
- type: z.literal("ink"),
696
+ const InkShape = z.object({
697
+ ...Ink.shape,
698
+ ...Base,
818
699
  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
700
+ /** Pen pressure from 0 to 1, one value per point of each stroke. Absent means even pressure. */
701
+ pressure: z.array(z.array(z.number().min(0).max(1))).optional()
824
702
  });
825
703
  /** Pressure must have one value per point of each stroke. */
826
704
  function checkPressure(a, ctx) {
@@ -832,70 +710,48 @@ function checkPressure(a, ctx) {
832
710
  });
833
711
  }
834
712
  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
713
  /**
838
714
  * An image on the page (XFDF `stamp`), drawn to fill `rect`. Image and typed signatures are
839
715
  * stamps with `subject: 'Signature'`.
840
716
  */
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()
717
+ const StampSchema = z.object({
718
+ ...Stamp.shape,
719
+ ...Base
855
720
  });
856
721
  /**
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.
722
+ * A loupe (Inkbar's own type, a circle with `custom.inkbar.type: 'loupe'` in a file): a circle
723
+ * inscribed in `rect` that shows the page content under its own centre, magnified. `color` and
724
+ * `width` draw its rim.
859
725
  */
860
- const LoupeSchema = Base.extend({
726
+ const LoupeSchema = z.object({
861
727
  type: z.literal("loupe"),
728
+ ...commonFields,
862
729
  /** How many times larger the content under the centre is drawn. Above 1. */
863
730
  magnification: z.number().gt(1).default(2),
864
731
  /** The rim's width in points. */
865
- width: z.number().nonnegative().default(2)
732
+ width: z.number().nonnegative().default(2),
733
+ ...Base
866
734
  });
867
735
  /**
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.
736
+ * A mask (Inkbar's own type, a circle or square with `custom.inkbar.type: 'mask'` in a file):
737
+ * dims everything on the page outside `shape` drawn in `rect`, an ellipse inscribed in it or the
738
+ * rectangle itself. `color` is the dimming colour and `dim` how strongly it covers, from 0 to 1.
739
+ * One per page: adding a second replaces the first.
872
740
  */
873
- const MaskSchema = Base.extend({
741
+ const MaskSchema = z.object({
874
742
  type: z.literal("mask"),
743
+ ...commonFields,
875
744
  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)
745
+ dim: z.number().min(0).max(1).default(.5),
746
+ ...Base
878
747
  });
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"),
748
+ const RedactSchema = z.object({
749
+ ...Redact.shape,
750
+ ...Base,
892
751
  /** 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(),
752
+ quads: Quads.optional(),
897
753
  /** Colour the redaction will be filled with when applied. */
898
- interiorColor: Hex
754
+ interiorColor: Color
899
755
  });
900
756
  const AnnotationSchema = z.discriminatedUnion("type", [
901
757
  TextMarkupSchema,
@@ -934,17 +790,12 @@ const ExportedAnnotationSchema = z.discriminatedUnion("type", [
934
790
  RedactSchema.omit(VIEWER_STATE)
935
791
  ]);
936
792
  const AnnotationDocumentSchema = z.object({
937
- format: z.literal(ANNOTATION_FORMAT),
938
- version: z.literal(1),
793
+ format: z.literal(GLOSS_FORMAT),
794
+ version: z.literal(GLOSS_VERSION),
939
795
  documentKey: z.string().optional(),
940
796
  /** Validated item by item on import so unknown types can be preserved. */
941
797
  annotations: z.array(z.unknown())
942
798
  });
943
- function confidenceBucket(confidence) {
944
- if (confidence < .5) return "low";
945
- if (confidence < .8) return "medium";
946
- return "high";
947
- }
948
799
  //#endregion
949
800
  //#region src/model/annotations/embedded.ts
950
801
  const SUPPORTED_SUBTYPES = /* @__PURE__ */ new Set([
@@ -982,7 +833,7 @@ function rectFromPdf(r) {
982
833
  }
983
834
  /** A PDF name exactly as PDF.js gives it (names are case-sensitive), or undefined if invalid. */
984
835
  function pdfName(raw) {
985
- return raw !== void 0 && PDF_NAME.test(raw) ? raw : void 0;
836
+ return raw !== void 0 && PDF_NAME$1.test(raw) ? raw : void 0;
986
837
  }
987
838
  /** `head` and `tail` from a Line's or PolyLine's `/LE`, left out when both are `None`. */
988
839
  function lineEndingsOf(raw) {
@@ -1000,18 +851,43 @@ function lineEndingsOf(raw) {
1000
851
  function importedId(page, pdfjsId) {
1001
852
  return `pdf:${page}:${pdfjsId}`;
1002
853
  }
854
+ /**
855
+ * Imports one page's PDF.js annotation data. `targets` is @internal: the annotations whose
856
+ * missing fields the document reads from the file after `ready`.
857
+ */
1003
858
  function importEmbeddedPage(page, raw, importedAt) {
1004
859
  const counts = {};
1005
860
  const annotations = [];
861
+ const targets = [];
1006
862
  for (const data of raw) {
1007
863
  counts[data.subtype] = (counts[data.subtype] ?? 0) + 1;
1008
864
  if (!SUPPORTED_SUBTYPES.has(data.subtype) || data.hidden || data.noView) continue;
1009
865
  const converted = convert(page, data, importedAt);
1010
- if (converted) annotations.push(converted);
866
+ if (!converted) continue;
867
+ annotations.push(converted);
868
+ const target = targetOf(converted.id, data);
869
+ if (target) targets.push(target);
1011
870
  }
1012
871
  return {
1013
872
  annotations,
1014
- counts
873
+ counts,
874
+ targets
875
+ };
876
+ }
877
+ function targetOf(id, d) {
878
+ const rect = Array.from(d.rect);
879
+ if (d.subtype === "Text") return d.name === "NoIcon" ? {
880
+ id,
881
+ subtype: "Text",
882
+ rect
883
+ } : void 0;
884
+ if (d.subtype !== "FreeText") return void 0;
885
+ const daFont = d.defaultAppearanceData?.fontName;
886
+ return {
887
+ id,
888
+ subtype: "FreeText",
889
+ rect,
890
+ ...daFont ? { daFont } : {}
1015
891
  };
1016
892
  }
1017
893
  function summarise(perPage) {
@@ -1152,7 +1028,6 @@ function convert(page, d, importedAt) {
1152
1028
  candidate = {
1153
1029
  ...base("redact"),
1154
1030
  type: "redact",
1155
- origin: "user",
1156
1031
  interiorColor: pdfColorToHex(d.interiorColor, staticDefaults.fill()),
1157
1032
  ...quads.length > 0 ? { quads } : {}
1158
1033
  };
@@ -1163,6 +1038,749 @@ function convert(page, d, importedAt) {
1163
1038
  return parsed.success ? parsed.data : void 0;
1164
1039
  }
1165
1040
  //#endregion
1041
+ //#region src/pdf/lexer.ts
1042
+ /** Thrown when the bytes are not valid PDF object syntax. */
1043
+ var PdfSyntaxError = class extends Error {
1044
+ constructor(message) {
1045
+ super(message);
1046
+ this.name = "PdfSyntaxError";
1047
+ }
1048
+ };
1049
+ const isWhite$1 = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
1050
+ const isDelimiter = (c) => c === 40 || c === 41 || c === 60 || c === 62 || c === 91 || c === 93 || c === 123 || c === 125 || c === 47 || c === 37;
1051
+ const isRegular = (c) => !isWhite$1(c) && !isDelimiter(c);
1052
+ const isDigit = (c) => c >= 48 && c <= 57;
1053
+ const latin1$1 = (bytes, from, to) => {
1054
+ let out = "";
1055
+ for (let i = from; i < to; i++) out += String.fromCharCode(bytes[i]);
1056
+ return out;
1057
+ };
1058
+ function skipSpace(bytes, offset) {
1059
+ let i = offset;
1060
+ while (i < bytes.length) {
1061
+ const c = bytes[i];
1062
+ if (isWhite$1(c)) i++;
1063
+ else if (c === 37) while (i < bytes.length && bytes[i] !== 10 && bytes[i] !== 13) i++;
1064
+ else break;
1065
+ }
1066
+ return i;
1067
+ }
1068
+ function tokenEnd(bytes, offset) {
1069
+ let i = offset;
1070
+ while (i < bytes.length && isRegular(bytes[i])) i++;
1071
+ return i;
1072
+ }
1073
+ const hexValue = (c) => {
1074
+ if (c === void 0) return -1;
1075
+ if (c >= 48 && c <= 57) return c - 48;
1076
+ if (c >= 65 && c <= 70) return c - 65 + 10;
1077
+ if (c >= 97 && c <= 102) return c - 97 + 10;
1078
+ return -1;
1079
+ };
1080
+ function parseName(bytes, offset) {
1081
+ const end = tokenEnd(bytes, offset + 1);
1082
+ let name = "";
1083
+ for (let i = offset + 1; i < end; i++) {
1084
+ const c = bytes[i];
1085
+ const hi = c === 35 ? hexValue(bytes[i + 1]) : -1;
1086
+ const lo = hi >= 0 ? hexValue(bytes[i + 2]) : -1;
1087
+ if (hi >= 0 && lo >= 0 && i + 2 < end) {
1088
+ name += String.fromCharCode(hi * 16 + lo);
1089
+ i += 2;
1090
+ } else name += String.fromCharCode(c);
1091
+ }
1092
+ return {
1093
+ value: {
1094
+ kind: "name",
1095
+ name
1096
+ },
1097
+ end
1098
+ };
1099
+ }
1100
+ function skipLiteralString(bytes, offset) {
1101
+ let depth = 0;
1102
+ for (let i = offset; i < bytes.length; i++) {
1103
+ const c = bytes[i];
1104
+ if (c === 92) i++;
1105
+ else if (c === 40) depth++;
1106
+ else if (c === 41 && --depth === 0) return i + 1;
1107
+ }
1108
+ throw new PdfSyntaxError("Unterminated string");
1109
+ }
1110
+ /** After a non-negative integer ending at `end`, looks for `gen R`. */
1111
+ function tryReference(bytes, num, end) {
1112
+ let i = end;
1113
+ if (i >= bytes.length || !isWhite$1(bytes[i])) return void 0;
1114
+ i = skipSpace(bytes, i);
1115
+ const genStart = i;
1116
+ while (i < bytes.length && isDigit(bytes[i])) i++;
1117
+ if (i === genStart || i >= bytes.length || !isWhite$1(bytes[i])) return void 0;
1118
+ const gen = Number(latin1$1(bytes, genStart, i));
1119
+ i = skipSpace(bytes, i);
1120
+ if (bytes[i] !== 82 || i + 1 < bytes.length && isRegular(bytes[i + 1])) return;
1121
+ return {
1122
+ value: {
1123
+ kind: "ref",
1124
+ num,
1125
+ gen
1126
+ },
1127
+ end: i + 1
1128
+ };
1129
+ }
1130
+ function parseNumberOrKeyword(bytes, offset) {
1131
+ const end = tokenEnd(bytes, offset);
1132
+ const token = latin1$1(bytes, offset, end);
1133
+ if (token === "true") return {
1134
+ value: true,
1135
+ end
1136
+ };
1137
+ if (token === "false") return {
1138
+ value: false,
1139
+ end
1140
+ };
1141
+ if (token === "null") return {
1142
+ value: null,
1143
+ end
1144
+ };
1145
+ if (/^[+-]?(\d+\.?\d*|\.\d+)$/.test(token)) {
1146
+ const n = Number(token);
1147
+ if (/^\d+$/.test(token)) {
1148
+ const ref = tryReference(bytes, n, end);
1149
+ if (ref) return ref;
1150
+ }
1151
+ return {
1152
+ value: n,
1153
+ end
1154
+ };
1155
+ }
1156
+ throw new PdfSyntaxError(`Unexpected token "${token}" at ${offset}`);
1157
+ }
1158
+ /** Parses one value at `offset`, skipping whitespace and comments first. */
1159
+ function parseValue(bytes, offset) {
1160
+ const i = skipSpace(bytes, offset);
1161
+ if (i >= bytes.length) throw new PdfSyntaxError("Unexpected end of data");
1162
+ const c = bytes[i];
1163
+ if (c === 47) return parseName(bytes, i);
1164
+ if (c === 40) return {
1165
+ value: { kind: "string" },
1166
+ end: skipLiteralString(bytes, i)
1167
+ };
1168
+ if (c === 60 && bytes[i + 1] === 60) return parseDict(bytes, i + 2);
1169
+ if (c === 60) {
1170
+ let j = i + 1;
1171
+ while (j < bytes.length && bytes[j] !== 62) j++;
1172
+ if (j >= bytes.length) throw new PdfSyntaxError("Unterminated hex string");
1173
+ return {
1174
+ value: { kind: "string" },
1175
+ end: j + 1
1176
+ };
1177
+ }
1178
+ if (c === 91) return parseArray(bytes, i + 1);
1179
+ if (isRegular(c)) return parseNumberOrKeyword(bytes, i);
1180
+ throw new PdfSyntaxError(`Unexpected "${String.fromCharCode(c)}" at ${i}`);
1181
+ }
1182
+ function parseArray(bytes, offset) {
1183
+ const items = [];
1184
+ let i = offset;
1185
+ for (;;) {
1186
+ i = skipSpace(bytes, i);
1187
+ if (i >= bytes.length) throw new PdfSyntaxError("Unterminated array");
1188
+ if (bytes[i] === 93) return {
1189
+ value: items,
1190
+ end: i + 1
1191
+ };
1192
+ const item = parseValue(bytes, i);
1193
+ items.push(item.value);
1194
+ i = item.end;
1195
+ }
1196
+ }
1197
+ function parseDict(bytes, offset) {
1198
+ const entries = /* @__PURE__ */ new Map();
1199
+ let i = offset;
1200
+ for (;;) {
1201
+ i = skipSpace(bytes, i);
1202
+ if (i >= bytes.length) throw new PdfSyntaxError("Unterminated dictionary");
1203
+ if (bytes[i] === 62 && bytes[i + 1] === 62) return {
1204
+ value: {
1205
+ kind: "dict",
1206
+ entries
1207
+ },
1208
+ end: i + 2
1209
+ };
1210
+ if (bytes[i] !== 47) throw new PdfSyntaxError(`Expected a name key at ${i}`);
1211
+ const key = parseName(bytes, i);
1212
+ const val = parseValue(bytes, key.end);
1213
+ entries.set(key.value.name, val.value);
1214
+ i = val.end;
1215
+ }
1216
+ }
1217
+ function readInteger(bytes, offset) {
1218
+ const i = skipSpace(bytes, offset);
1219
+ let end = i;
1220
+ while (end < bytes.length && isDigit(bytes[end])) end++;
1221
+ if (end === i) throw new PdfSyntaxError(`Expected an integer at ${i}`);
1222
+ return {
1223
+ n: Number(latin1$1(bytes, i, end)),
1224
+ end
1225
+ };
1226
+ }
1227
+ /** Parses `num gen obj <value> [stream]` at `offset`. `streamStart` is the first data byte after `stream` and its EOL. */
1228
+ function parseIndirect(bytes, offset) {
1229
+ const num = readInteger(bytes, offset);
1230
+ const gen = readInteger(bytes, num.end);
1231
+ const kw = skipSpace(bytes, gen.end);
1232
+ if (latin1$1(bytes, kw, tokenEnd(bytes, kw)) !== "obj") throw new PdfSyntaxError(`Expected "obj" at ${kw}`);
1233
+ const { value, end } = parseValue(bytes, kw + 3);
1234
+ const result = {
1235
+ num: num.n,
1236
+ gen: gen.n,
1237
+ value
1238
+ };
1239
+ const next = skipSpace(bytes, end);
1240
+ if (latin1$1(bytes, next, next + 6) === "stream" && !isRegular(bytes[next + 6] ?? 32)) {
1241
+ let start = next + 6;
1242
+ if (bytes[start] === 13 && bytes[start + 1] === 10) start += 2;
1243
+ else if (bytes[start] === 10 || bytes[start] === 13) start += 1;
1244
+ result.streamStart = start;
1245
+ }
1246
+ return result;
1247
+ }
1248
+ /** Reads a dictionary entry. */
1249
+ const dictGet = (d, key) => d.entries.get(key);
1250
+ /** True for a name object, optionally one with the given spelling. */
1251
+ const isName = (v, name) => typeof v === "object" && v !== null && v.kind === "name" && (name === void 0 || v.name === name);
1252
+ /** True for a dictionary object. */
1253
+ const isDict = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && v.kind === "dict";
1254
+ /** True for an indirect reference. */
1255
+ const isRef = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && v.kind === "ref";
1256
+ //#endregion
1257
+ //#region src/pdf/annotation-fields.ts
1258
+ /** `12R` → {num:12,gen:0}, `12R3` → {num:12,gen:3}; anything else undefined. */
1259
+ function refOfPdfjsId(id) {
1260
+ const m = /^(\d+)R(\d+)?$/.exec(id);
1261
+ if (!m) return void 0;
1262
+ return {
1263
+ kind: "ref",
1264
+ num: Number(m[1]),
1265
+ gen: m[2] === void 0 ? 0 : Number(m[2])
1266
+ };
1267
+ }
1268
+ const FAMILIES = [
1269
+ [/^(helvetica|helv)/i, "Helvetica"],
1270
+ [/^(times|tiro)/i, "Times"],
1271
+ [/^cour(ier|$)/i, "Courier"]
1272
+ ];
1273
+ /**
1274
+ * BaseFont (or bare resource name) → Inkbar font fields; undefined for Symbol and ZapfDingbats.
1275
+ * A family matches by prefix, so `HelveticaNeue` reads as Helvetica and `CourierNewPS` as
1276
+ * Courier. Any other name keeps its own family, minus a trailing `MT` or `PSMT`.
1277
+ * With `knownOnly`, a name outside Helvetica, Times and Courier gives undefined: a bare
1278
+ * resource id such as `F1` is not a family.
1279
+ */
1280
+ function fontFromBaseName(name, knownOnly = false) {
1281
+ const bare = name.replace(/^[A-Z]{6}\+/, "");
1282
+ if (/^(symbol|zapfdingbats|zadb)/i.test(bare)) return void 0;
1283
+ const cut = bare.search(/[-,]/);
1284
+ const base = cut < 0 ? bare : bare.slice(0, cut);
1285
+ const style = cut < 0 ? "" : bare.slice(cut + 1);
1286
+ const known = FAMILIES.find(([re]) => re.test(base));
1287
+ if (knownOnly && !known) return void 0;
1288
+ const fontFamily = known ? known[1] : base.replace(/(PS)?MT$/, "");
1289
+ if (!fontFamily) return void 0;
1290
+ const font = { fontFamily };
1291
+ if (/bold|black|heavy/i.test(style)) font.fontWeight = "bold";
1292
+ if (/italic|oblique/i.test(style)) font.fontStyle = "italic";
1293
+ return font;
1294
+ }
1295
+ const finite = (v) => typeof v === "number" && Number.isFinite(v);
1296
+ async function numbers$1(objs, v, counts) {
1297
+ const arr = await objs.resolve(v);
1298
+ if (!Array.isArray(arr) || !counts.includes(arr.length)) return void 0;
1299
+ const out = await Promise.all(arr.map((n) => objs.resolve(n)));
1300
+ return out.every(finite) ? out : void 0;
1301
+ }
1302
+ async function dictOf(objs, v) {
1303
+ const d = await objs.resolve(v);
1304
+ return isDict(d) ? d : void 0;
1305
+ }
1306
+ /** The font's /BaseFont, looked up in a /DR resource dictionary. */
1307
+ async function baseFontIn(objs, dr, resource) {
1308
+ const res = await dictOf(objs, dr);
1309
+ const fonts = res && await dictOf(objs, dictGet(res, "Font"));
1310
+ const font = fonts && await dictOf(objs, dictGet(fonts, resource));
1311
+ const base = font && await objs.resolve(dictGet(font, "BaseFont"));
1312
+ return isName(base) ? base.name : void 0;
1313
+ }
1314
+ async function readFont(objs, annot, daFont) {
1315
+ const root = await dictOf(objs, dictGet(objs.trailer, "Root"));
1316
+ const acroForm = root && await dictOf(objs, dictGet(root, "AcroForm"));
1317
+ const sources = [dictGet(annot, "DR"), acroForm && dictGet(acroForm, "DR")];
1318
+ for (const dr of sources) {
1319
+ const base = await baseFontIn(objs, dr, daFont);
1320
+ if (base !== void 0) return fontFromBaseName(base);
1321
+ }
1322
+ return fontFromBaseName(daFont, true);
1323
+ }
1324
+ async function readHead(objs, le) {
1325
+ let v = await objs.resolve(le);
1326
+ if (Array.isArray(v)) v = await objs.resolve(v[0]);
1327
+ return isName(v) && PDF_NAME$1.test(v.name) ? v.name : void 0;
1328
+ }
1329
+ /** `rect` is the annotation's [x1 y1 x2 y2], used to reject a fringe that leaves no box. */
1330
+ async function readFreeTextFields(objs, ref, rect, daFont) {
1331
+ const fields = {};
1332
+ try {
1333
+ const annot = await objs.get(ref);
1334
+ if (!isDict(annot)) return fields;
1335
+ const cl = await numbers$1(objs, dictGet(annot, "CL"), [4, 6]);
1336
+ if (cl) {
1337
+ fields.callout = [];
1338
+ for (let i = 0; i < cl.length; i += 2) fields.callout.push({
1339
+ x: cl[i],
1340
+ y: cl[i + 1]
1341
+ });
1342
+ }
1343
+ const rd = await numbers$1(objs, dictGet(annot, "RD"), [4]);
1344
+ if (rd && rect.length === 4 && rd.every((n) => n >= 0)) {
1345
+ const [left, top, right, bottom] = rd;
1346
+ const width = Math.abs(rect[2] - rect[0]);
1347
+ const height = Math.abs(rect[3] - rect[1]);
1348
+ if (left + right < width && top + bottom < height) fields.fringe = {
1349
+ left,
1350
+ top,
1351
+ right,
1352
+ bottom
1353
+ };
1354
+ }
1355
+ const head = await readHead(objs, dictGet(annot, "LE"));
1356
+ if (head !== void 0) fields.head = head;
1357
+ if (daFont !== void 0) {
1358
+ const font = await readFont(objs, annot, daFont);
1359
+ if (font) fields.font = font;
1360
+ }
1361
+ } catch {}
1362
+ return fields;
1363
+ }
1364
+ async function readTextIcon(objs, ref) {
1365
+ try {
1366
+ const annot = await objs.get(ref);
1367
+ if (!isDict(annot)) return void 0;
1368
+ const name = await objs.resolve(dictGet(annot, "Name"));
1369
+ return isName(name) && PDF_NAME$1.test(name.name) ? name.name : void 0;
1370
+ } catch {
1371
+ return;
1372
+ }
1373
+ }
1374
+ //#endregion
1375
+ //#region src/pdf/stream.ts
1376
+ const ENDSTREAM = Uint8Array.from("endstream", (c) => c.charCodeAt(0));
1377
+ function matchesAt(bytes, at, pattern) {
1378
+ if (at < 0 || at + pattern.length > bytes.length) return false;
1379
+ for (let i = 0; i < pattern.length; i++) if (bytes[at + i] !== pattern[i]) return false;
1380
+ return true;
1381
+ }
1382
+ function findEndstream(bytes, from) {
1383
+ for (let i = from; i + ENDSTREAM.length <= bytes.length; i++) if (matchesAt(bytes, i, ENDSTREAM)) return i;
1384
+ return bytes.length;
1385
+ }
1386
+ /** Raw stream bytes: trusts `length` when `endstream` follows it, otherwise scans. */
1387
+ function rawData(bytes, start, length) {
1388
+ if (length !== void 0 && Number.isInteger(length) && length >= 0) {
1389
+ let i = start + length;
1390
+ while (i < bytes.length && (bytes[i] === 10 || bytes[i] === 13 || bytes[i] === 32)) i++;
1391
+ if (matchesAt(bytes, i, ENDSTREAM)) return bytes.subarray(start, start + length);
1392
+ }
1393
+ let end = findEndstream(bytes, start);
1394
+ if (end > start && bytes[end - 1] === 10) end--;
1395
+ if (end > start && bytes[end - 1] === 13) end--;
1396
+ return bytes.subarray(start, end);
1397
+ }
1398
+ /** The single filter parameter dictionary, if any. Anything else that is defined is an error. */
1399
+ function decodeParms(dict) {
1400
+ const parms = dictGet(dict, "DecodeParms") ?? dictGet(dict, "DP");
1401
+ const first = Array.isArray(parms) ? parms[0] : parms;
1402
+ if (first === void 0 || first === null) return void 0;
1403
+ if (!isDict(first)) throw new PdfSyntaxError("Unsupported /DecodeParms");
1404
+ return first;
1405
+ }
1406
+ function numberParam(parms, key, fallback) {
1407
+ const v = parms ? dictGet(parms, key) : void 0;
1408
+ return typeof v === "number" ? v : fallback;
1409
+ }
1410
+ async function inflate(data) {
1411
+ const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate"));
1412
+ return new Uint8Array(await new Response(stream).arrayBuffer());
1413
+ }
1414
+ function paeth(a, b, c) {
1415
+ const p = a + b - c;
1416
+ const pa = Math.abs(p - a);
1417
+ const pb = Math.abs(p - b);
1418
+ const pc = Math.abs(p - c);
1419
+ if (pa <= pb && pa <= pc) return a;
1420
+ return pb <= pc ? b : c;
1421
+ }
1422
+ function undoPngPredictor(data, columns, colors, bits) {
1423
+ const bpp = Math.max(1, Math.ceil(colors * bits / 8));
1424
+ const rowBytes = Math.ceil(columns * colors * bits / 8);
1425
+ const rows = Math.floor(data.length / (rowBytes + 1));
1426
+ const out = new Uint8Array(rows * rowBytes);
1427
+ for (let r = 0; r < rows; r++) {
1428
+ const type = data[r * (rowBytes + 1)];
1429
+ const src = r * (rowBytes + 1) + 1;
1430
+ const dst = r * rowBytes;
1431
+ for (let x = 0; x < rowBytes; x++) {
1432
+ const raw = data[src + x];
1433
+ const left = x >= bpp ? out[dst + x - bpp] : 0;
1434
+ const up = r > 0 ? out[dst - rowBytes + x] : 0;
1435
+ const upLeft = r > 0 && x >= bpp ? out[dst - rowBytes + x - bpp] : 0;
1436
+ let value;
1437
+ switch (type) {
1438
+ case 0:
1439
+ value = raw;
1440
+ break;
1441
+ case 1:
1442
+ value = raw + left;
1443
+ break;
1444
+ case 2:
1445
+ value = raw + up;
1446
+ break;
1447
+ case 3:
1448
+ value = raw + (left + up >> 1);
1449
+ break;
1450
+ case 4:
1451
+ value = raw + paeth(left, up, upLeft);
1452
+ break;
1453
+ default: throw new PdfSyntaxError(`Unknown PNG filter type ${type}`);
1454
+ }
1455
+ out[dst + x] = value & 255;
1456
+ }
1457
+ }
1458
+ return out;
1459
+ }
1460
+ /** The decoded data of the stream whose dictionary is `dict` and whose data starts at `start`. `length` is `/Length`, already resolved. */
1461
+ async function decodeStream(bytes, dict, start, length) {
1462
+ const filter = dictGet(dict, "Filter") ?? dictGet(dict, "F");
1463
+ const filters = Array.isArray(filter) ? filter : filter === void 0 ? [] : [filter];
1464
+ if (filters.length === 0) return rawData(bytes, start, length);
1465
+ if (filters.length > 1 || !isName(filters[0], "FlateDecode")) throw new PdfSyntaxError("Unsupported stream filter");
1466
+ const inflated = await inflate(rawData(bytes, start, length));
1467
+ const parms = decodeParms(dict);
1468
+ const predictor = numberParam(parms, "Predictor", 1);
1469
+ if (predictor === 1) return inflated;
1470
+ if (predictor < 10) throw new PdfSyntaxError(`Unsupported predictor ${predictor}`);
1471
+ return undoPngPredictor(inflated, numberParam(parms, "Columns", 1), numberParam(parms, "Colors", 1), numberParam(parms, "BitsPerComponent", 8));
1472
+ }
1473
+ //#endregion
1474
+ //#region src/pdf/xref.ts
1475
+ const isWhite = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
1476
+ const latin1 = (bytes, from, to) => {
1477
+ let out = "";
1478
+ for (let i = from; i < to; i++) out += String.fromCharCode(bytes[i]);
1479
+ return out;
1480
+ };
1481
+ function readToken(bytes, offset) {
1482
+ let i = offset;
1483
+ while (i < bytes.length && isWhite(bytes[i])) i++;
1484
+ const start = i;
1485
+ while (i < bytes.length && !isWhite(bytes[i])) i++;
1486
+ return {
1487
+ token: latin1(bytes, start, i),
1488
+ end: i
1489
+ };
1490
+ }
1491
+ function readInt(bytes, offset) {
1492
+ const { token, end } = readToken(bytes, offset);
1493
+ if (!/^\d+$/.test(token)) throw new Error(`Expected an integer at ${offset}`);
1494
+ return {
1495
+ n: Number(token),
1496
+ end
1497
+ };
1498
+ }
1499
+ function findStartxref(bytes) {
1500
+ const tail = latin1(bytes, Math.max(0, bytes.length - 1024), bytes.length);
1501
+ const at = tail.lastIndexOf("startxref");
1502
+ if (at < 0) throw new Error("No startxref");
1503
+ return readInt(bytes, bytes.length - tail.length + at + 9).n;
1504
+ }
1505
+ function readTable(bytes, offset) {
1506
+ const entries = /* @__PURE__ */ new Map();
1507
+ let i = readToken(bytes, offset).end;
1508
+ for (;;) {
1509
+ const head = readToken(bytes, i);
1510
+ if (head.token.startsWith("trailer")) {
1511
+ const trailer = parseValue(bytes, head.end - (head.token.length - 7)).value;
1512
+ if (!isDict(trailer)) throw new Error("Trailer is not a dictionary");
1513
+ return {
1514
+ entries,
1515
+ trailer
1516
+ };
1517
+ }
1518
+ const first = readInt(bytes, i);
1519
+ const count = readInt(bytes, first.end);
1520
+ i = count.end;
1521
+ for (let k = 0; k < count.n; k++) {
1522
+ const off = readInt(bytes, i);
1523
+ const gen = readInt(bytes, off.end);
1524
+ const kind = readToken(bytes, gen.end);
1525
+ i = kind.end;
1526
+ if (kind.token === "n") entries.set(first.n + k, {
1527
+ type: "offset",
1528
+ offset: off.n,
1529
+ gen: gen.n
1530
+ });
1531
+ else if (kind.token === "f") entries.set(first.n + k, { type: "free" });
1532
+ else throw new Error(`Bad xref entry type "${kind.token}"`);
1533
+ }
1534
+ }
1535
+ }
1536
+ const numbers = (v) => Array.isArray(v) && v.every((x) => typeof x === "number" && x >= 0) ? v : void 0;
1537
+ async function readStream(bytes, offset) {
1538
+ const obj = parseIndirect(bytes, offset);
1539
+ const dict = obj.value;
1540
+ if (!isDict(dict) || obj.streamStart === void 0) throw new Error("Xref stream expected");
1541
+ const length = dict.entries.get("Length");
1542
+ const data = await decodeStream(bytes, dict, obj.streamStart, typeof length === "number" ? length : void 0);
1543
+ const w = numbers(dict.entries.get("W"));
1544
+ if (!w || w.length !== 3 || w.some((x) => x > 8)) throw new Error("Bad /W");
1545
+ const size = dict.entries.get("Size");
1546
+ const index = numbers(dict.entries.get("Index")) ?? [0, typeof size === "number" ? size : 0];
1547
+ const [w0, w1, w2] = w;
1548
+ const rowLength = w0 + w1 + w2;
1549
+ if (rowLength === 0) throw new Error("Empty xref rows");
1550
+ const field = (at, width) => {
1551
+ let n = 0;
1552
+ for (let k = 0; k < width; k++) n = n * 256 + data[at + k];
1553
+ return n;
1554
+ };
1555
+ const entries = /* @__PURE__ */ new Map();
1556
+ let row = 0;
1557
+ for (let s = 0; s + 1 < index.length; s += 2) for (let k = 0; k < index[s + 1]; k++, row++) {
1558
+ const at = row * rowLength;
1559
+ if (at + rowLength > data.length) return {
1560
+ entries,
1561
+ trailer: dict
1562
+ };
1563
+ const type = w0 === 0 ? 1 : field(at, w0);
1564
+ const a = field(at + w0, w1);
1565
+ const b = field(at + w0 + w1, w2);
1566
+ const num = index[s] + k;
1567
+ if (type === 0) entries.set(num, { type: "free" });
1568
+ else if (type === 1) entries.set(num, {
1569
+ type: "offset",
1570
+ offset: a,
1571
+ gen: b
1572
+ });
1573
+ else if (type === 2) entries.set(num, {
1574
+ type: "compressed",
1575
+ stream: a,
1576
+ index: b
1577
+ });
1578
+ }
1579
+ return {
1580
+ entries,
1581
+ trailer: dict
1582
+ };
1583
+ }
1584
+ async function readSection(bytes, offset) {
1585
+ if (!Number.isInteger(offset) || offset < 0 || offset >= bytes.length) throw new Error("Xref offset out of range");
1586
+ return readToken(bytes, offset).token === "xref" ? readTable(bytes, offset) : readStream(bytes, offset);
1587
+ }
1588
+ /** Newest entry wins along the /Prev chain (and a hybrid file's /XRefStm). Undefined when startxref or a section cannot be read. */
1589
+ async function readXref(bytes) {
1590
+ try {
1591
+ const entries = /* @__PURE__ */ new Map();
1592
+ const visited = /* @__PURE__ */ new Set();
1593
+ let trailer;
1594
+ let next = findStartxref(bytes);
1595
+ while (next !== void 0 && !visited.has(next)) {
1596
+ visited.add(next);
1597
+ const section = await readSection(bytes, next);
1598
+ trailer ??= section.trailer;
1599
+ const hybrid = section.trailer.entries.get("XRefStm");
1600
+ if (typeof hybrid === "number" && !visited.has(hybrid)) {
1601
+ visited.add(hybrid);
1602
+ const extra = await readSection(bytes, hybrid);
1603
+ for (const [num, e] of extra.entries) if (!entries.has(num)) entries.set(num, e);
1604
+ }
1605
+ for (const [num, e] of section.entries) if (!entries.has(num)) entries.set(num, e);
1606
+ const prev = section.trailer.entries.get("Prev");
1607
+ next = typeof prev === "number" ? prev : void 0;
1608
+ }
1609
+ return trailer ? {
1610
+ entries,
1611
+ trailer
1612
+ } : void 0;
1613
+ } catch {
1614
+ return;
1615
+ }
1616
+ }
1617
+ //#endregion
1618
+ //#region src/pdf/objects.ts
1619
+ var Reader = class {
1620
+ bytes;
1621
+ xref;
1622
+ trailer;
1623
+ objectStreams = /* @__PURE__ */ new Map();
1624
+ constructor(bytes, xref) {
1625
+ this.bytes = bytes;
1626
+ this.xref = xref;
1627
+ this.trailer = xref.trailer;
1628
+ }
1629
+ async get(ref) {
1630
+ try {
1631
+ return await this.load(ref, true);
1632
+ } catch {
1633
+ return;
1634
+ }
1635
+ }
1636
+ async resolve(v) {
1637
+ return isRef(v) ? this.get(v) : v;
1638
+ }
1639
+ /** `followLength` is false while resolving a /Length, so a self-referencing length cannot recurse. */
1640
+ async load(ref, followLength) {
1641
+ const entry = this.xref.entries.get(ref.num);
1642
+ if (!entry || entry.type === "free") return void 0;
1643
+ if (entry.type === "offset") {
1644
+ if (entry.gen !== ref.gen) return void 0;
1645
+ const obj = parseIndirect(this.bytes, entry.offset);
1646
+ return obj.num === ref.num && obj.gen === ref.gen ? obj.value : void 0;
1647
+ }
1648
+ if (ref.gen !== 0) return void 0;
1649
+ const container = await this.objectStream(entry.stream, followLength);
1650
+ if (!container) return void 0;
1651
+ let pair = container.pairs[entry.index];
1652
+ if (pair?.num !== ref.num) pair = container.pairs.find((p) => p.num === ref.num);
1653
+ if (!pair) return void 0;
1654
+ return parseValue(container.decoded, container.first + pair.offset).value;
1655
+ }
1656
+ objectStream(num, followLength) {
1657
+ let cached = this.objectStreams.get(num);
1658
+ if (!cached) {
1659
+ cached = this.readObjectStream(num, followLength).catch(() => void 0);
1660
+ this.objectStreams.set(num, cached);
1661
+ }
1662
+ return cached;
1663
+ }
1664
+ async readObjectStream(num, followLength) {
1665
+ const entry = this.xref.entries.get(num);
1666
+ if (entry?.type !== "offset") return void 0;
1667
+ const obj = parseIndirect(this.bytes, entry.offset);
1668
+ const dict = obj.value;
1669
+ if (obj.num !== num || !isDict(dict) || obj.streamStart === void 0) return void 0;
1670
+ const n = dict.entries.get("N");
1671
+ const first = dict.entries.get("First");
1672
+ if (typeof n !== "number" || typeof first !== "number") return void 0;
1673
+ let length = dict.entries.get("Length");
1674
+ if (isRef(length)) length = followLength ? await this.loadLength(length) : void 0;
1675
+ const decoded = await decodeStream(this.bytes, dict, obj.streamStart, typeof length === "number" ? length : void 0);
1676
+ const pairs = [];
1677
+ let at = 0;
1678
+ for (let i = 0; i < n; i++) {
1679
+ const a = parseValue(decoded, at);
1680
+ const b = parseValue(decoded, a.end);
1681
+ if (typeof a.value !== "number" || typeof b.value !== "number") return void 0;
1682
+ pairs.push({
1683
+ num: a.value,
1684
+ offset: b.value
1685
+ });
1686
+ at = b.end;
1687
+ }
1688
+ return {
1689
+ decoded,
1690
+ first,
1691
+ pairs
1692
+ };
1693
+ }
1694
+ async loadLength(ref) {
1695
+ try {
1696
+ return await this.load(ref, false);
1697
+ } catch {
1698
+ return;
1699
+ }
1700
+ }
1701
+ };
1702
+ /** Undefined when the file is encrypted (`/Encrypt` in the trailer) or its xref cannot be read. */
1703
+ async function openPdfObjects(bytes) {
1704
+ try {
1705
+ const xref = await readXref(bytes);
1706
+ if (!xref || xref.trailer.entries.has("Encrypt")) return void 0;
1707
+ return new Reader(bytes, xref);
1708
+ } catch {
1709
+ return;
1710
+ }
1711
+ }
1712
+ //#endregion
1713
+ //#region src/model/annotations/embedded-fields.ts
1714
+ /**
1715
+ * `a` with `fields` merged in: `callout`, `fringe`, `head` and the font keys of `defaultStyle`
1716
+ * on a FreeText, `icon` on a note. Undefined when `a` is not embedded, the fields do not fit its
1717
+ * type, or the result fails the schema.
1718
+ */
1719
+ function withEmbeddedFields(a, fields) {
1720
+ if (a.source !== "pdf") return void 0;
1721
+ const { callout, fringe, head, icon, font } = fields;
1722
+ let candidate;
1723
+ if (a.type === "text") {
1724
+ if (icon === void 0 || callout || fringe || head !== void 0 || font) return void 0;
1725
+ candidate = {
1726
+ ...a,
1727
+ icon
1728
+ };
1729
+ } else if (a.type === "freetext") {
1730
+ if (icon !== void 0) return void 0;
1731
+ candidate = {
1732
+ ...a,
1733
+ ...callout ? { callout } : {},
1734
+ ...fringe ? { fringe } : {},
1735
+ ...head !== void 0 ? { head } : {},
1736
+ defaultStyle: {
1737
+ ...a.defaultStyle,
1738
+ ...font
1739
+ }
1740
+ };
1741
+ } else return;
1742
+ const parsed = AnnotationSchema.safeParse(candidate);
1743
+ return parsed.success ? parsed.data : void 0;
1744
+ }
1745
+ /**
1746
+ * Reads every target once, calling `getData` only if a target names an annotation in
1747
+ * `annotations` by an object reference, and returns the fields read, by id. A target the file
1748
+ * adds nothing to, or whose fields would fail the schema, is left out. Never throws: any
1749
+ * failure leaves the annotations as they are.
1750
+ */
1751
+ async function readEmbeddedFields(annotations, targets, getData) {
1752
+ const read = /* @__PURE__ */ new Map();
1753
+ if (targets.length === 0) return read;
1754
+ try {
1755
+ const byId = new Map(annotations.map((a) => [a.id, a]));
1756
+ const work = targets.flatMap((target) => {
1757
+ const current = byId.get(target.id);
1758
+ const ref = refOfPdfjsId(target.id.split(":")[2] ?? "");
1759
+ return current && ref ? [{
1760
+ target,
1761
+ current,
1762
+ ref
1763
+ }] : [];
1764
+ });
1765
+ if (work.length === 0) return read;
1766
+ const objs = await openPdfObjects(await getData());
1767
+ if (!objs) return read;
1768
+ for (const { target, current, ref } of work) try {
1769
+ const fields = await readFields(objs, target, ref);
1770
+ if (fields && withEmbeddedFields(current, fields)) read.set(current.id, fields);
1771
+ } catch {}
1772
+ } catch {}
1773
+ return read;
1774
+ }
1775
+ async function readFields(objs, target, ref) {
1776
+ if (target.subtype === "Text") {
1777
+ const icon = await readTextIcon(objs, ref);
1778
+ return icon === void 0 ? void 0 : { icon };
1779
+ }
1780
+ const fields = await readFreeTextFields(objs, ref, target.rect, target.daFont);
1781
+ return Object.keys(fields).length > 0 ? fields : void 0;
1782
+ }
1783
+ //#endregion
1166
1784
  //#region src/model/env.ts
1167
1785
  /** True only when the build or runtime says production; browsers without `process` count as dev. */
1168
1786
  function isProduction() {
@@ -1215,6 +1833,19 @@ function createHistory(store, opts = {}) {
1215
1833
  previous = current;
1216
1834
  return;
1217
1835
  }
1836
+ if (change.kind === "replace" && change.reason === "embedded") {
1837
+ const fill = (list) => list.map((a) => {
1838
+ const f = change.fields.get(a.id);
1839
+ return f && withEmbeddedFields(a, f) || a;
1840
+ });
1841
+ for (const entry of [...undoStack, ...redoStack]) {
1842
+ entry.before = fill(entry.before);
1843
+ entry.after = fill(entry.after);
1844
+ }
1845
+ if (batchStart) batchStart = fill(batchStart);
1846
+ previous = current;
1847
+ return;
1848
+ }
1218
1849
  if (change.kind === "replace") {
1219
1850
  undoStack.length = 0;
1220
1851
  redoStack.length = 0;
@@ -1308,8 +1939,127 @@ function sameAnnotations(a, b) {
1308
1939
  return a === b || a.length === b.length && a.every((x, i) => x === b[i]);
1309
1940
  }
1310
1941
  //#endregion
1942
+ //#region src/model/annotations/gloss-mapping.ts
1943
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1944
+ /** `rest` with `own` merged into a copy of its `custom.inkbar`; `own` wins. */
1945
+ function withCustom(rest, own) {
1946
+ const custom = isObject(rest.custom) ? rest.custom : {};
1947
+ const inkbar = isObject(custom.inkbar) ? custom.inkbar : {};
1948
+ return {
1949
+ ...rest,
1950
+ custom: {
1951
+ ...custom,
1952
+ inkbar: {
1953
+ ...inkbar,
1954
+ ...own
1955
+ }
1956
+ }
1957
+ };
1958
+ }
1959
+ /**
1960
+ * An annotation as a Gloss file writes it. Gloss has no loupe or mask and no pen pressure or
1961
+ * free-text border colour, so these go under `custom.inkbar`, with loupes and masks written as
1962
+ * the circle or square they draw.
1963
+ */
1964
+ function toFile(annotation) {
1965
+ switch (annotation.type) {
1966
+ case "ink": {
1967
+ const { pressure, ...rest } = annotation;
1968
+ return pressure ? withCustom(rest, { pressure }) : rest;
1969
+ }
1970
+ case "freetext": {
1971
+ const { borderColor, ...rest } = annotation;
1972
+ return borderColor ? withCustom(rest, { borderColor }) : rest;
1973
+ }
1974
+ case "loupe": {
1975
+ const { magnification, ...rest } = annotation;
1976
+ return withCustom({
1977
+ ...rest,
1978
+ type: "circle"
1979
+ }, {
1980
+ type: "loupe",
1981
+ magnification
1982
+ });
1983
+ }
1984
+ case "mask": {
1985
+ const { shape, dim, ...rest } = annotation;
1986
+ return withCustom({
1987
+ ...rest,
1988
+ type: shape === "ellipse" ? "circle" : "square"
1989
+ }, {
1990
+ type: "mask",
1991
+ dim
1992
+ });
1993
+ }
1994
+ default: return annotation;
1995
+ }
1996
+ }
1997
+ /**
1998
+ * Moves `keys` from `custom.inkbar` to the top level of `raw`, replacing any top-level value
1999
+ * (an absent key deletes it), then drops `custom.inkbar`, and `custom`, if that leaves them
2000
+ * empty. Everything else under `custom` stays as it was.
2001
+ */
2002
+ function lift(raw, inkbar, keys, extra = {}) {
2003
+ const item = {
2004
+ ...raw,
2005
+ ...extra
2006
+ };
2007
+ const left = { ...inkbar };
2008
+ let consumed = false;
2009
+ for (const key of keys) {
2010
+ delete item[key];
2011
+ if (key in left) {
2012
+ item[key] = left[key];
2013
+ delete left[key];
2014
+ consumed = true;
2015
+ }
2016
+ }
2017
+ if (!consumed || !isObject(raw.custom)) return item;
2018
+ const { inkbar: _inkbar, ...others } = raw.custom;
2019
+ const custom = Object.keys(left).length > 0 ? {
2020
+ ...others,
2021
+ inkbar: left
2022
+ } : others;
2023
+ if (Object.keys(custom).length > 0) item.custom = custom;
2024
+ else delete item.custom;
2025
+ return item;
2026
+ }
2027
+ /**
2028
+ * A raw file item as Inkbar parses it: Inkbar's own data moves out of `custom.inkbar`. An item
2029
+ * it doesn't recognise passes through unchanged.
2030
+ */
2031
+ function fromFile(raw) {
2032
+ const inkbar = isObject(raw.custom) && isObject(raw.custom.inkbar) ? raw.custom.inkbar : {};
2033
+ const from = (keys, extra) => ({
2034
+ item: lift(raw, inkbar, keys, extra),
2035
+ lifted: keys
2036
+ });
2037
+ const unchanged = {
2038
+ item: raw,
2039
+ lifted: []
2040
+ };
2041
+ switch (raw.type) {
2042
+ case "ink": return from(["pressure"]);
2043
+ case "freetext": return from(["borderColor"]);
2044
+ case "circle":
2045
+ if (inkbar.type === "loupe") return from(["type", "magnification"]);
2046
+ if (inkbar.type === "mask") return from(["type", "dim"], { shape: "ellipse" });
2047
+ return unchanged;
2048
+ case "square":
2049
+ if (inkbar.type === "mask") return from(["type", "dim"], { shape: "rect" });
2050
+ return unchanged;
2051
+ default: return unchanged;
2052
+ }
2053
+ }
2054
+ //#endregion
1311
2055
  //#region src/model/annotations/json.ts
1312
- const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...o.shape.type._zod.values]));
2056
+ /**
2057
+ * @internal The Gloss types this core parses, taken from its own schemas rather than Gloss's
2058
+ * list, so a type a later Gloss adds is still treated as foreign here. Loupes and masks are
2059
+ * Inkbar's own types: a file writes them as circles and squares, so a `loupe` or `mask` item is
2060
+ * foreign too.
2061
+ */
2062
+ const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...o.shape.type._zod.values]).filter((type) => type !== "loupe" && type !== "mask"));
1313
2063
  /**
1314
2064
  * Session annotations plus any preserved foreign items. Embedded (`source: 'pdf'`) ones stay in
1315
2065
  * the file. `source` and `locked` are viewer state and are left out.
@@ -1318,21 +2068,24 @@ const KNOWN_TYPES = new Set(ExportedAnnotationSchema.options.flatMap((o) => [...
1318
2068
  * export would carry: editing it never touches the store, and values JSON cannot hold, such as
1319
2069
  * functions or `undefined` inside `custom`, are left out exactly as `JSON.stringify` would.
1320
2070
  *
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.
2071
+ * Gloss has no loupe or mask, pen pressure or free-text border colour, so Inkbar writes these
2072
+ * under `custom.inkbar`, with loupes and masks as the circle or square they draw. Every other key
2073
+ * under `custom` goes out as it is.
2074
+ *
2075
+ * The result validates against the Gloss schema (`gloss-format/schema.json`). Foreign items
2076
+ * validate as unknown annotations while their type is one Gloss does not define.
1324
2077
  */
1325
2078
  function exportAnnotations(store, preserved = [], documentKey) {
1326
- const session = store.list.get().filter((a) => a.source === "session").map(({ source: _source, locked: _locked, ...rest }) => rest);
2079
+ const session = store.list.get().filter((a) => a.source === "session").map(({ source: _source, locked: _locked, ...rest }) => toFile(rest));
1327
2080
  return {
1328
- format: ANNOTATION_FORMAT,
1329
- version: 1,
2081
+ format: GLOSS_FORMAT$1,
2082
+ version: GLOSS_VERSION$1,
1330
2083
  ...documentKey === void 0 ? {} : { documentKey },
1331
2084
  annotations: JSON.parse(JSON.stringify([...session, ...preserved]))
1332
2085
  };
1333
2086
  }
1334
2087
  /**
1335
- * Validates the envelope (throws a TypeError when it is not an inkbar/annotations v1 document),
2088
+ * Validates the envelope (throws a TypeError when it is not a gloss v1 document),
1336
2089
  * then each item on its own. Embedded annotations are never touched; imported items become
1337
2090
  * session annotations.
1338
2091
  */
@@ -1353,7 +2106,7 @@ function importAnnotations(store, data, opts) {
1353
2106
  function planImport(store, data, opts) {
1354
2107
  const raw = typeof data === "string" ? JSON.parse(data) : data;
1355
2108
  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("; ")}`);
2109
+ 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
2110
  const embeddedIds = new Set(store.list.get().filter((a) => a.source === "pdf").map((a) => a.id));
1358
2111
  const result = {
1359
2112
  imported: 0,
@@ -1389,21 +2142,28 @@ function planImport(store, data, opts) {
1389
2142
  accepted.add(id);
1390
2143
  return true;
1391
2144
  };
1392
- envelope.data.annotations.forEach((item, index) => {
1393
- const type = item?.type;
2145
+ envelope.data.annotations.forEach((raw, index) => {
2146
+ const type = raw?.type;
1394
2147
  if (typeof type === "string" && !KNOWN_TYPES.has(type)) {
1395
- if (!isForeign(item)) result.errors.push({
2148
+ if (!isForeign(raw)) result.errors.push({
1396
2149
  index,
1397
2150
  issues: [`type: unknown type "${type}" needs a string id and a numeric page to be preserved`]
1398
2151
  });
1399
- else if (claim(item.id, index)) result.unknown.push(item);
2152
+ else if (claim(raw.id, index)) result.unknown.push(raw);
1400
2153
  return;
1401
2154
  }
2155
+ const { item, lifted } = isRecord(raw) && typeof type === "string" ? fromFile(raw) : {
2156
+ item: raw,
2157
+ lifted: []
2158
+ };
1402
2159
  const parsed = ExportedAnnotationSchema.safeParse(item);
1403
2160
  if (!parsed.success) {
1404
2161
  result.errors.push({
1405
2162
  index,
1406
- issues: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
2163
+ issues: parsed.error.issues.map((i) => {
2164
+ const path = i.path.join(".");
2165
+ return `${typeof i.path[0] === "string" && lifted.includes(i.path[0]) ? `custom.inkbar.${path}` : path}: ${i.message}`;
2166
+ })
1407
2167
  });
1408
2168
  return;
1409
2169
  }
@@ -1435,8 +2195,11 @@ function planImport(store, data, opts) {
1435
2195
  }
1436
2196
  };
1437
2197
  }
2198
+ function isRecord(item) {
2199
+ return typeof item === "object" && item !== null && !Array.isArray(item);
2200
+ }
1438
2201
  function isForeign(item) {
1439
- if (typeof item !== "object" || item === null || Array.isArray(item)) return false;
2202
+ if (!isRecord(item)) return false;
1440
2203
  const { type, id, page } = item;
1441
2204
  return typeof type === "string" && typeof id === "string" && typeof page === "number";
1442
2205
  }
@@ -1600,7 +2363,6 @@ function createAnnotationStore(opts) {
1600
2363
  }
1601
2364
  if (input.type === "redact") {
1602
2365
  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
2366
  }
1605
2367
  return AnnotationSchema.parse(candidate);
1606
2368
  }
@@ -1615,6 +2377,7 @@ function createAnnotationStore(opts) {
1615
2377
  if (!entry) return;
1616
2378
  pending.delete(key);
1617
2379
  if (pending.size === 0) proposed = void 0;
2380
+ pruneSelection(basis());
1618
2381
  if (isApplied(entry.change, list.get()) || isProduction()) return;
1619
2382
  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
2383
  }
@@ -1785,6 +2548,26 @@ function createAnnotationStore(opts) {
1785
2548
  reason: "import"
1786
2549
  });
1787
2550
  },
2551
+ _patchEmbedded(fields) {
2552
+ if (destroyed) return;
2553
+ const applied = /* @__PURE__ */ new Map();
2554
+ const merge = (items) => items.map((a) => {
2555
+ const f = a.source === "pdf" ? fields.get(a.id) : void 0;
2556
+ const merged = f && withEmbeddedFields(a, f);
2557
+ if (!merged) return a;
2558
+ applied.set(a.id, f);
2559
+ return merged;
2560
+ });
2561
+ const next = merge(list.get());
2562
+ if (applied.size === 0) return;
2563
+ list.set(next);
2564
+ if (proposed) proposed = merge(proposed);
2565
+ emitter.emit("change", {
2566
+ kind: "replace",
2567
+ reason: "embedded",
2568
+ fields: applied
2569
+ });
2570
+ },
1788
2571
  on: (event, fn) => emitter.on(event, fn),
1789
2572
  destroy() {
1790
2573
  destroyed = true;
@@ -2019,6 +2802,8 @@ async function openDocument(source, options) {
2019
2802
  });
2020
2803
  const embedded = [];
2021
2804
  let preserved = [];
2805
+ /** Embedded annotations whose PDF.js data lacks fields the file holds; read after `ready`. */
2806
+ const targets = [];
2022
2807
  const destroyedError = () => new InkError("DocumentDestroyed", "The document has been destroyed");
2023
2808
  const ready = (async () => {
2024
2809
  const pdfjs = await loadPdfjs(options.assets);
@@ -2059,6 +2844,7 @@ async function openDocument(source, options) {
2059
2844
  });
2060
2845
  embedded.push(...summarise(perPage.map((r) => r.counts)));
2061
2846
  const fromFile = perPage.flatMap((r) => r.annotations);
2847
+ targets.push(...perPage.flatMap((r) => r.targets));
2062
2848
  if (fromFile.length > 0) store._setEmbedded(fromFile);
2063
2849
  })().then(() => {
2064
2850
  status.set({ state: "ready" });
@@ -2075,6 +2861,13 @@ async function openDocument(source, options) {
2075
2861
  const historyOption = options.history ?? true;
2076
2862
  const historyLimit = historyOption === false ? 0 : historyOption === true ? void 0 : historyOption.limit;
2077
2863
  const history = createHistory(store, historyLimit === void 0 ? {} : { limit: historyLimit });
2864
+ ready.then(() => {
2865
+ if (destroyed || !handles) return;
2866
+ const { proxy } = handles;
2867
+ readEmbeddedFields(store.list.get(), targets, () => proxy.getData()).then((patched) => {
2868
+ if (!destroyed && patched.size > 0) store._patchEmbedded(patched);
2869
+ }).catch(() => void 0);
2870
+ }, () => void 0);
2078
2871
  return {
2079
2872
  key: resolved.key,
2080
2873
  status: readonly(status),
@@ -2835,23 +3628,6 @@ function createToolStyle(deps) {
2835
3628
  };
2836
3629
  }
2837
3630
  //#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
3631
  //#region src/model/annotations/threads.ts
2856
3632
  function isThreadRoot(a) {
2857
3633
  return a.type === "text" || isTextMarkup(a);
@@ -2968,24 +3744,41 @@ function rectOf(a, b) {
2968
3744
  height: Math.abs(b.y - a.y)
2969
3745
  };
2970
3746
  }
2971
- /** Vertices round the ellipse inscribed in `rect`, the first at the top centre, clockwise on the page. */
3747
+ /**
3748
+ * Vertices round a circle, the first at the top centre, clockwise on the page, stretched so their
3749
+ * bounds fill `rect`.
3750
+ */
2972
3751
  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) => {
3752
+ const unit = Array.from({ length: count }, (_, i) => {
2976
3753
  const angle = i * 2 * Math.PI / count;
2977
3754
  const r = radius(i);
2978
3755
  return {
2979
- x: clean(cx + rect.width / 2 * r * Math.sin(angle)),
2980
- y: clean(cy + rect.height / 2 * r * Math.cos(angle))
3756
+ x: r * Math.sin(angle),
3757
+ y: r * Math.cos(angle)
2981
3758
  };
2982
3759
  });
3760
+ const xs = unit.map((p) => p.x);
3761
+ const ys = unit.map((p) => p.y);
3762
+ const fit = (v, min, max, start, size) => clean(max > min ? start + (v - min) / (max - min) * size : start + size / 2);
3763
+ const [minX, maxX, minY, maxY] = [
3764
+ Math.min(...xs),
3765
+ Math.max(...xs),
3766
+ Math.min(...ys),
3767
+ Math.max(...ys)
3768
+ ];
3769
+ return unit.map((p) => ({
3770
+ x: fit(p.x, minX, maxX, rect.x, rect.width),
3771
+ y: fit(p.y, minY, maxY, rect.y, rect.height)
3772
+ }));
2983
3773
  }
2984
- /** A star's `2 * points` vertices, outer and inner alternating, the first at the top centre. */
3774
+ /**
3775
+ * A star's `2 * points` vertices, outer and inner alternating, stretched to fill `rect`, the first
3776
+ * at the top centre.
3777
+ */
2985
3778
  function starVertices(rect, points, innerRatio = .5) {
2986
3779
  return ring(rect, Math.max(2, Math.round(points)) * 2, (i) => i % 2 === 0 ? 1 : innerRatio);
2987
3780
  }
2988
- /** A regular polygon's vertices on the inscribed ellipse, the first at the top centre. */
3781
+ /** A regular polygon's vertices, stretched to fill `rect`, the first at the top centre. */
2989
3782
  function polygonVertices(rect, sides) {
2990
3783
  return ring(rect, Math.max(3, Math.round(sides)), () => 1);
2991
3784
  }
@@ -3085,16 +3878,13 @@ function shapeInput(kind, page, start, end, opts) {
3085
3878
  type: "line",
3086
3879
  page,
3087
3880
  start,
3088
- end,
3089
- head: "None",
3090
- tail: "None"
3881
+ end
3091
3882
  };
3092
3883
  case "arrow": return {
3093
3884
  type: "line",
3094
3885
  page,
3095
3886
  start,
3096
3887
  end,
3097
- tail: "OpenArrow",
3098
3888
  intent: "LineArrow"
3099
3889
  };
3100
3890
  case "rectangle": return {
@@ -3917,6 +4707,6 @@ function createSignatureLibrary(store) {
3917
4707
  };
3918
4708
  }
3919
4709
  //#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 };
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 };
3921
4711
 
3922
- //# sourceMappingURL=signatures-DF2VmNVa.mjs.map
4712
+ //# sourceMappingURL=signatures-CMapTKrC.mjs.map