@embedpdf/plugin-annotation 2.13.0 → 2.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +522 -7
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/annotation-plugin.d.ts +25 -0
  6. package/dist/lib/handlers/callout-free-text.handler.d.ts +3 -0
  7. package/dist/lib/handlers/index.d.ts +1 -0
  8. package/dist/lib/handlers/types.d.ts +7 -1
  9. package/dist/lib/helpers.d.ts +8 -0
  10. package/dist/lib/patching/patch-utils.d.ts +38 -1
  11. package/dist/lib/patching/patches/callout-freetext.patch.d.ts +3 -0
  12. package/dist/lib/patching/patches/index.d.ts +1 -0
  13. package/dist/lib/tools/default-tools.d.ts +35 -0
  14. package/dist/lib/types.d.ts +21 -2
  15. package/dist/preact/index.cjs +1 -1
  16. package/dist/preact/index.cjs.map +1 -1
  17. package/dist/preact/index.js +422 -32
  18. package/dist/preact/index.js.map +1 -1
  19. package/dist/react/index.cjs +1 -1
  20. package/dist/react/index.cjs.map +1 -1
  21. package/dist/react/index.js +422 -32
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/shared/components/annotation-container.d.ts +13 -1
  24. package/dist/shared/components/annotations/callout-free-text-preview.d.ts +15 -0
  25. package/dist/shared/components/annotations/callout-free-text.d.ts +15 -0
  26. package/dist/shared/components/types.d.ts +12 -0
  27. package/dist/shared-preact/components/annotation-container.d.ts +13 -1
  28. package/dist/shared-preact/components/annotations/callout-free-text-preview.d.ts +15 -0
  29. package/dist/shared-preact/components/annotations/callout-free-text.d.ts +15 -0
  30. package/dist/shared-preact/components/types.d.ts +12 -0
  31. package/dist/shared-react/components/annotation-container.d.ts +13 -1
  32. package/dist/shared-react/components/annotations/callout-free-text-preview.d.ts +15 -0
  33. package/dist/shared-react/components/annotations/callout-free-text.d.ts +15 -0
  34. package/dist/shared-react/components/types.d.ts +12 -0
  35. package/dist/svelte/components/annotations/CalloutFreeText.svelte.d.ts +15 -0
  36. package/dist/svelte/components/annotations/CalloutFreeTextPreview.svelte.d.ts +10 -0
  37. package/dist/svelte/components/renderers/CalloutFreeTextRenderer.svelte.d.ts +5 -0
  38. package/dist/svelte/components/types.d.ts +12 -0
  39. package/dist/svelte/index.cjs +1 -1
  40. package/dist/svelte/index.cjs.map +1 -1
  41. package/dist/svelte/index.js +528 -106
  42. package/dist/svelte/index.js.map +1 -1
  43. package/dist/svelte/types.d.ts +12 -0
  44. package/dist/vue/components/annotation-container.vue.d.ts +12 -0
  45. package/dist/vue/components/annotations/callout-free-text-preview.vue.d.ts +10 -0
  46. package/dist/vue/components/annotations/callout-free-text.vue.d.ts +25 -0
  47. package/dist/vue/components/renderers/callout-free-text-renderer.vue.d.ts +6 -0
  48. package/dist/vue/index.cjs +1 -1
  49. package/dist/vue/index.cjs.map +1 -1
  50. package/dist/vue/index.js +601 -211
  51. package/dist/vue/index.js.map +1 -1
  52. package/dist/vue/types.d.ts +12 -0
  53. package/package.json +12 -12
package/dist/index.js CHANGED
@@ -207,6 +207,22 @@ function hasLockedFlag(annotation) {
207
207
  var _a;
208
208
  return ((_a = annotation.flags) == null ? void 0 : _a.includes("locked")) ?? false;
209
209
  }
210
+ function hasNoViewFlag(annotation) {
211
+ var _a;
212
+ return ((_a = annotation.flags) == null ? void 0 : _a.includes("noView")) ?? false;
213
+ }
214
+ function hasHiddenFlag(annotation) {
215
+ var _a;
216
+ return ((_a = annotation.flags) == null ? void 0 : _a.includes("hidden")) ?? false;
217
+ }
218
+ function hasReadOnlyFlag(annotation) {
219
+ var _a;
220
+ return ((_a = annotation.flags) == null ? void 0 : _a.includes("readOnly")) ?? false;
221
+ }
222
+ function hasLockedContentsFlag(annotation) {
223
+ var _a;
224
+ return ((_a = annotation.flags) == null ? void 0 : _a.includes("lockedContents")) ?? false;
225
+ }
210
226
  const getAnnotationsByPageIndex = (s, page) => (s.pages[page] ?? []).map((uid) => s.byUid[uid]);
211
227
  const getAnnotations = (s) => {
212
228
  const out = {};
@@ -737,6 +753,109 @@ function compensateRotatedVertexEdit(original, vertices, tightRect) {
737
753
  if (Math.abs(qx) < 1e-8 && Math.abs(qy) < 1e-8) return vertices;
738
754
  return vertices.map((v) => ({ x: v.x + qx, y: v.y + qy }));
739
755
  }
756
+ function computeTextBoxFromRD(rect, rd) {
757
+ if (!rd) return rect;
758
+ return {
759
+ origin: { x: rect.origin.x + rd.left, y: rect.origin.y + rd.top },
760
+ size: {
761
+ width: Math.max(0, rect.size.width - rd.left - rd.right),
762
+ height: Math.max(0, rect.size.height - rd.top - rd.bottom)
763
+ }
764
+ };
765
+ }
766
+ function computeRDFromTextBox(overallRect, textBox) {
767
+ return {
768
+ left: textBox.origin.x - overallRect.origin.x,
769
+ top: textBox.origin.y - overallRect.origin.y,
770
+ right: overallRect.origin.x + overallRect.size.width - (textBox.origin.x + textBox.size.width),
771
+ bottom: overallRect.origin.y + overallRect.size.height - (textBox.origin.y + textBox.size.height)
772
+ };
773
+ }
774
+ function computeCalloutConnectionPoint(knee, textBox) {
775
+ const cx = textBox.origin.x + textBox.size.width / 2;
776
+ const cy = textBox.origin.y + textBox.size.height / 2;
777
+ const dx = knee.x - cx;
778
+ const dy = knee.y - cy;
779
+ if (Math.abs(dx) >= Math.abs(dy)) {
780
+ return dx > 0 ? { x: textBox.origin.x + textBox.size.width, y: cy } : { x: textBox.origin.x, y: cy };
781
+ }
782
+ return dy > 0 ? { x: cx, y: textBox.origin.y + textBox.size.height } : { x: cx, y: textBox.origin.y };
783
+ }
784
+ function computeCalloutOverallRect(textBox, calloutLine, lineEnding, strokeWidth) {
785
+ const linePoints = [...calloutLine];
786
+ if (lineEnding && calloutLine.length >= 2) {
787
+ const handler = LINE_ENDING_HANDLERS[lineEnding];
788
+ if (handler) {
789
+ const angle = Math.atan2(
790
+ calloutLine[1].y - calloutLine[0].y,
791
+ calloutLine[1].x - calloutLine[0].x
792
+ );
793
+ const localPts = handler.getLocalPoints(strokeWidth);
794
+ const rotationAngle = handler.getRotation(angle + Math.PI);
795
+ const transformed = localPts.map(
796
+ (p) => rotateAndTranslatePoint(p, rotationAngle, calloutLine[0])
797
+ );
798
+ linePoints.push(...transformed);
799
+ }
800
+ }
801
+ const lineBbox = expandRect(rectFromPoints(linePoints), strokeWidth);
802
+ const tbRight = textBox.origin.x + textBox.size.width;
803
+ const tbBottom = textBox.origin.y + textBox.size.height;
804
+ const lnRight = lineBbox.origin.x + lineBbox.size.width;
805
+ const lnBottom = lineBbox.origin.y + lineBbox.size.height;
806
+ const minX = Math.min(textBox.origin.x, lineBbox.origin.x);
807
+ const minY = Math.min(textBox.origin.y, lineBbox.origin.y);
808
+ const maxX = Math.max(tbRight, lnRight);
809
+ const maxY = Math.max(tbBottom, lnBottom);
810
+ return {
811
+ origin: { x: minX, y: minY },
812
+ size: { width: maxX - minX, height: maxY - minY }
813
+ };
814
+ }
815
+ const calloutVertexConfig = {
816
+ extractVertices: (a) => {
817
+ const textBox = computeTextBoxFromRD(a.rect, a.rectangleDifferences);
818
+ const cl = a.calloutLine;
819
+ if (!cl || cl.length < 3) {
820
+ return [
821
+ { x: a.rect.origin.x, y: a.rect.origin.y },
822
+ { x: a.rect.origin.x, y: a.rect.origin.y },
823
+ { x: textBox.origin.x, y: textBox.origin.y },
824
+ { x: textBox.origin.x + textBox.size.width, y: textBox.origin.y + textBox.size.height }
825
+ ];
826
+ }
827
+ return [
828
+ cl[0],
829
+ cl[1],
830
+ { x: textBox.origin.x, y: textBox.origin.y },
831
+ { x: textBox.origin.x + textBox.size.width, y: textBox.origin.y + textBox.size.height }
832
+ ];
833
+ },
834
+ transformAnnotation: (a, vertices) => {
835
+ if (vertices.length < 4) return {};
836
+ const [arrowTip, knee, tbTL, tbBR] = vertices;
837
+ const textBox = {
838
+ origin: { x: Math.min(tbTL.x, tbBR.x), y: Math.min(tbTL.y, tbBR.y) },
839
+ size: {
840
+ width: Math.abs(tbBR.x - tbTL.x),
841
+ height: Math.abs(tbBR.y - tbTL.y)
842
+ }
843
+ };
844
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
845
+ const calloutLine = [arrowTip, knee, connectionPoint];
846
+ const overallRect = computeCalloutOverallRect(
847
+ textBox,
848
+ calloutLine,
849
+ a.lineEnding,
850
+ a.strokeWidth ?? 1
851
+ );
852
+ return {
853
+ calloutLine,
854
+ rect: overallRect,
855
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
856
+ };
857
+ }
858
+ };
740
859
  function createEnding(ending, strokeWidth, rad, px, py) {
741
860
  if (!ending) return null;
742
861
  const handler = LINE_ENDING_HANDLERS[ending];
@@ -1874,6 +1993,80 @@ const patchFreeText = (orig, ctx) => {
1874
1993
  return ctx.changes;
1875
1994
  }
1876
1995
  };
1996
+ function rebuildFromVertices(orig, arrowTip, knee, tbTL, tbBR) {
1997
+ const textBox = {
1998
+ origin: { x: Math.min(tbTL.x, tbBR.x), y: Math.min(tbTL.y, tbBR.y) },
1999
+ size: {
2000
+ width: Math.abs(tbBR.x - tbTL.x),
2001
+ height: Math.abs(tbBR.y - tbTL.y)
2002
+ }
2003
+ };
2004
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
2005
+ const calloutLine = [arrowTip, knee, connectionPoint];
2006
+ const overallRect = computeCalloutOverallRect(
2007
+ textBox,
2008
+ calloutLine,
2009
+ orig.lineEnding,
2010
+ orig.strokeWidth ?? 1
2011
+ );
2012
+ return {
2013
+ calloutLine,
2014
+ rect: overallRect,
2015
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
2016
+ };
2017
+ }
2018
+ const patchCalloutFreeText = (orig, ctx) => {
2019
+ var _a, _b;
2020
+ switch (ctx.type) {
2021
+ case "vertex-edit": {
2022
+ if (!ctx.changes.calloutLine) return ctx.changes;
2023
+ const verts = ctx.changes.calloutLine;
2024
+ if (verts.length < 4) return ctx.changes;
2025
+ return rebuildFromVertices(orig, verts[0], verts[1], verts[2], verts[3]);
2026
+ }
2027
+ case "move": {
2028
+ if (!ctx.changes.rect) return ctx.changes;
2029
+ const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
2030
+ const movedLine = (_a = orig.calloutLine) == null ? void 0 : _a.map((p) => ({ x: p.x + dx, y: p.y + dy }));
2031
+ return {
2032
+ ...rects,
2033
+ ...movedLine && { calloutLine: movedLine }
2034
+ };
2035
+ }
2036
+ case "rotate": {
2037
+ const result = baseRotateChanges(orig, ctx);
2038
+ if (!result) return ctx.changes;
2039
+ const { dx, dy } = rotateOrbitDelta(orig, result);
2040
+ const movedLine = (_b = orig.calloutLine) == null ? void 0 : _b.map((p) => ({ x: p.x + dx, y: p.y + dy }));
2041
+ return {
2042
+ ...result,
2043
+ ...movedLine && { calloutLine: movedLine }
2044
+ };
2045
+ }
2046
+ case "property-update": {
2047
+ if (ctx.changes.lineEnding !== void 0 || ctx.changes.strokeWidth !== void 0) {
2048
+ const merged = { ...orig, ...ctx.changes };
2049
+ if (merged.calloutLine && merged.calloutLine.length >= 3) {
2050
+ const textBox = computeTextBoxFromRD(orig.rect, orig.rectangleDifferences);
2051
+ const overallRect = computeCalloutOverallRect(
2052
+ textBox,
2053
+ merged.calloutLine,
2054
+ merged.lineEnding,
2055
+ merged.strokeWidth ?? 1
2056
+ );
2057
+ return {
2058
+ ...ctx.changes,
2059
+ rect: overallRect,
2060
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
2061
+ };
2062
+ }
2063
+ }
2064
+ return ctx.changes;
2065
+ }
2066
+ default:
2067
+ return ctx.changes;
2068
+ }
2069
+ };
1877
2070
  const patchStamp = (orig, ctx) => {
1878
2071
  switch (ctx.type) {
1879
2072
  case "move":
@@ -1901,11 +2094,17 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
1901
2094
  calculateAABBFromVertices,
1902
2095
  calculateRotatedRectAABB,
1903
2096
  calculateRotatedRectAABBAroundPoint,
2097
+ calloutVertexConfig,
1904
2098
  clampAnnotationToPage,
1905
2099
  compensateRotatedVertexEdit,
2100
+ computeCalloutConnectionPoint,
2101
+ computeCalloutOverallRect,
2102
+ computeRDFromTextBox,
2103
+ computeTextBoxFromRD,
1906
2104
  createEnding,
1907
2105
  getRectCenter,
1908
2106
  lineRectWithEndings,
2107
+ patchCalloutFreeText,
1909
2108
  patchCircle,
1910
2109
  patchFreeText,
1911
2110
  patchInk,
@@ -2138,6 +2337,232 @@ const freeTextHandlerFactory = {
2138
2337
  };
2139
2338
  }
2140
2339
  };
2340
+ const CLICK_THRESHOLD = 5;
2341
+ const DEFAULT_TB_WIDTH = 150;
2342
+ const DEFAULT_TB_HEIGHT = 40;
2343
+ const calloutFreeTextHandlerFactory = {
2344
+ annotationType: PdfAnnotationSubtype.FREETEXT,
2345
+ create(context) {
2346
+ const { onCommit, onPreview, getTool, pageSize, pageIndex } = context;
2347
+ const [getPhase, setPhase] = useState("arrow");
2348
+ const [getArrowTip, setArrowTip] = useState(null);
2349
+ const [getKnee, setKnee] = useState(null);
2350
+ const [getDownPos, setDownPos] = useState(null);
2351
+ const [getTextBoxStart, setTextBoxStart] = useState(null);
2352
+ const [getDragging, setDragging] = useState(false);
2353
+ const clampToPage = (pos) => ({
2354
+ x: clamp(pos.x, 0, pageSize.width),
2355
+ y: clamp(pos.y, 0, pageSize.height)
2356
+ });
2357
+ const clampTextBox = (tb) => ({
2358
+ origin: {
2359
+ x: clamp(tb.origin.x, 0, pageSize.width - tb.size.width),
2360
+ y: clamp(tb.origin.y, 0, pageSize.height - tb.size.height)
2361
+ },
2362
+ size: tb.size
2363
+ });
2364
+ const getDefaults = () => {
2365
+ const tool = getTool();
2366
+ if (!tool) return null;
2367
+ return {
2368
+ ...tool.defaults,
2369
+ fontColor: tool.defaults.fontColor ?? "#000000",
2370
+ opacity: tool.defaults.opacity ?? 1,
2371
+ fontSize: tool.defaults.fontSize ?? 12,
2372
+ fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
2373
+ color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
2374
+ textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
2375
+ verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
2376
+ contents: tool.defaults.contents ?? "Insert text",
2377
+ flags: tool.defaults.flags ?? ["print"],
2378
+ lineEnding: tool.defaults.lineEnding ?? PdfAnnotationLineEnding.OpenArrow,
2379
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2380
+ strokeWidth: tool.defaults.strokeWidth ?? 1
2381
+ };
2382
+ };
2383
+ const isClick = (a, b) => Math.abs(a.x - b.x) < CLICK_THRESHOLD && Math.abs(a.y - b.y) < CLICK_THRESHOLD;
2384
+ const buildPreview = (cursor) => {
2385
+ const defaults = getDefaults();
2386
+ if (!defaults) return null;
2387
+ const arrowTip = getArrowTip();
2388
+ const knee = getKnee();
2389
+ const phase = getPhase();
2390
+ if (phase === "knee" && arrowTip) {
2391
+ const calloutLine = [arrowTip, cursor];
2392
+ const minX = Math.min(arrowTip.x, cursor.x);
2393
+ const minY = Math.min(arrowTip.y, cursor.y);
2394
+ const w = Math.abs(arrowTip.x - cursor.x);
2395
+ const h = Math.abs(arrowTip.y - cursor.y);
2396
+ const bounds = {
2397
+ origin: { x: minX, y: minY },
2398
+ size: { width: Math.max(w, 1), height: Math.max(h, 1) }
2399
+ };
2400
+ return {
2401
+ type: PdfAnnotationSubtype.FREETEXT,
2402
+ bounds,
2403
+ data: {
2404
+ ...defaults,
2405
+ rect: bounds,
2406
+ calloutLine
2407
+ }
2408
+ };
2409
+ }
2410
+ if (phase === "textbox" && arrowTip && knee) {
2411
+ const tbStart = getTextBoxStart();
2412
+ let textBox;
2413
+ if (getDragging() && tbStart) {
2414
+ textBox = {
2415
+ origin: { x: Math.min(tbStart.x, cursor.x), y: Math.min(tbStart.y, cursor.y) },
2416
+ size: {
2417
+ width: Math.max(Math.abs(cursor.x - tbStart.x), 20),
2418
+ height: Math.max(Math.abs(cursor.y - tbStart.y), 14)
2419
+ }
2420
+ };
2421
+ } else {
2422
+ textBox = {
2423
+ origin: { x: cursor.x - DEFAULT_TB_WIDTH / 2, y: cursor.y - DEFAULT_TB_HEIGHT / 2 },
2424
+ size: { width: DEFAULT_TB_WIDTH, height: DEFAULT_TB_HEIGHT }
2425
+ };
2426
+ }
2427
+ textBox = clampTextBox(textBox);
2428
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
2429
+ const calloutLine = [arrowTip, knee, connectionPoint];
2430
+ const overallRect = computeCalloutOverallRect(
2431
+ textBox,
2432
+ calloutLine,
2433
+ defaults.lineEnding,
2434
+ defaults.strokeWidth
2435
+ );
2436
+ return {
2437
+ type: PdfAnnotationSubtype.FREETEXT,
2438
+ bounds: overallRect,
2439
+ data: {
2440
+ ...defaults,
2441
+ rect: overallRect,
2442
+ calloutLine,
2443
+ textBox
2444
+ }
2445
+ };
2446
+ }
2447
+ return null;
2448
+ };
2449
+ const commitCallout = (tb) => {
2450
+ const defaults = getDefaults();
2451
+ const arrowTip = getArrowTip();
2452
+ const knee = getKnee();
2453
+ if (!defaults || !arrowTip || !knee) return;
2454
+ const textBox = clampTextBox(tb);
2455
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
2456
+ const calloutLine = [arrowTip, knee, connectionPoint];
2457
+ const overallRect = computeCalloutOverallRect(
2458
+ textBox,
2459
+ calloutLine,
2460
+ defaults.lineEnding,
2461
+ defaults.strokeWidth
2462
+ );
2463
+ const rd = computeRDFromTextBox(overallRect, textBox);
2464
+ const anno = {
2465
+ ...defaults,
2466
+ type: PdfAnnotationSubtype.FREETEXT,
2467
+ intent: "FreeTextCallout",
2468
+ rect: overallRect,
2469
+ rectangleDifferences: rd,
2470
+ calloutLine,
2471
+ pageIndex,
2472
+ id: uuidV4(),
2473
+ created: /* @__PURE__ */ new Date()
2474
+ };
2475
+ onCommit(anno);
2476
+ resetState();
2477
+ };
2478
+ const resetState = () => {
2479
+ setPhase("arrow");
2480
+ setArrowTip(null);
2481
+ setKnee(null);
2482
+ setDownPos(null);
2483
+ setTextBoxStart(null);
2484
+ setDragging(false);
2485
+ onPreview(null);
2486
+ };
2487
+ return {
2488
+ onPointerDown: (pos, evt) => {
2489
+ var _a, _b;
2490
+ const clampedPos = clampToPage(pos);
2491
+ const phase = getPhase();
2492
+ if (phase === "arrow" || phase === "knee") {
2493
+ setDownPos(clampedPos);
2494
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
2495
+ } else if (phase === "textbox") {
2496
+ setTextBoxStart(clampedPos);
2497
+ setDragging(true);
2498
+ (_b = evt.setPointerCapture) == null ? void 0 : _b.call(evt);
2499
+ }
2500
+ },
2501
+ onPointerMove: (pos) => {
2502
+ const clampedPos = clampToPage(pos);
2503
+ const phase = getPhase();
2504
+ if (phase === "textbox" && getDragging()) {
2505
+ onPreview(buildPreview(clampedPos));
2506
+ } else if (phase === "knee" || phase === "textbox") {
2507
+ onPreview(buildPreview(clampedPos));
2508
+ }
2509
+ },
2510
+ onPointerUp: (pos, evt) => {
2511
+ var _a, _b, _c, _d;
2512
+ const clampedPos = clampToPage(pos);
2513
+ const phase = getPhase();
2514
+ const downPos = getDownPos();
2515
+ if (phase === "arrow" && downPos && isClick(downPos, clampedPos)) {
2516
+ setArrowTip(clampedPos);
2517
+ setPhase("knee");
2518
+ setDownPos(null);
2519
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2520
+ return;
2521
+ }
2522
+ if (phase === "knee" && downPos && isClick(downPos, clampedPos)) {
2523
+ setKnee(clampedPos);
2524
+ setPhase("textbox");
2525
+ setDownPos(null);
2526
+ (_b = evt.releasePointerCapture) == null ? void 0 : _b.call(evt);
2527
+ return;
2528
+ }
2529
+ if (phase === "textbox" && getDragging()) {
2530
+ const tbStart = getTextBoxStart();
2531
+ if (tbStart) {
2532
+ const minX = Math.min(tbStart.x, clampedPos.x);
2533
+ const minY = Math.min(tbStart.y, clampedPos.y);
2534
+ const w = Math.abs(clampedPos.x - tbStart.x);
2535
+ const h = Math.abs(clampedPos.y - tbStart.y);
2536
+ if (w > 5 || h > 5) {
2537
+ const textBox = {
2538
+ origin: { x: minX, y: minY },
2539
+ size: { width: Math.max(w, 20), height: Math.max(h, 14) }
2540
+ };
2541
+ commitCallout(textBox);
2542
+ } else {
2543
+ commitCallout({
2544
+ origin: {
2545
+ x: tbStart.x - DEFAULT_TB_WIDTH / 2,
2546
+ y: tbStart.y - DEFAULT_TB_HEIGHT / 2
2547
+ },
2548
+ size: { width: DEFAULT_TB_WIDTH, height: DEFAULT_TB_HEIGHT }
2549
+ });
2550
+ }
2551
+ }
2552
+ (_c = evt.releasePointerCapture) == null ? void 0 : _c.call(evt);
2553
+ return;
2554
+ }
2555
+ setDownPos(null);
2556
+ (_d = evt.releasePointerCapture) == null ? void 0 : _d.call(evt);
2557
+ },
2558
+ onPointerCancel: (_, evt) => {
2559
+ var _a;
2560
+ resetState();
2561
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2562
+ }
2563
+ };
2564
+ }
2565
+ };
2141
2566
  const lineHandlerFactory = {
2142
2567
  annotationType: PdfAnnotationSubtype.LINE,
2143
2568
  create(context) {
@@ -3665,7 +4090,7 @@ const freeTextTools = [
3665
4090
  name: "Free Text",
3666
4091
  labelKey: "annotation.freeText",
3667
4092
  categories: ["annotation", "markup"],
3668
- matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT ? 1 : 0,
4093
+ matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT && a.intent !== "FreeTextCallout" ? 1 : 0,
3669
4094
  interaction: {
3670
4095
  exclusive: false,
3671
4096
  cursor: "crosshair",
@@ -3705,6 +4130,44 @@ const freeTextTools = [
3705
4130
  pointerHandler: freeTextHandlerFactory
3706
4131
  }
3707
4132
  ];
4133
+ const calloutFreeTextTools = [
4134
+ {
4135
+ id: "freeTextCallout",
4136
+ name: "Callout",
4137
+ labelKey: "annotation.callout",
4138
+ categories: ["annotation", "markup"],
4139
+ matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT && a.intent === "FreeTextCallout" ? 10 : 0,
4140
+ interaction: {
4141
+ exclusive: false,
4142
+ cursor: "crosshair",
4143
+ isDraggable: true,
4144
+ isResizable: false,
4145
+ isRotatable: false
4146
+ },
4147
+ defaults: {
4148
+ type: PdfAnnotationSubtype.FREETEXT,
4149
+ intent: "FreeTextCallout",
4150
+ contents: "Insert text",
4151
+ fontSize: 14,
4152
+ fontColor: "#E44234",
4153
+ fontFamily: PdfStandardFont.Helvetica,
4154
+ textAlign: PdfTextAlignment.Left,
4155
+ verticalAlign: PdfVerticalAlignment.Top,
4156
+ color: "transparent",
4157
+ opacity: 1,
4158
+ lineEnding: PdfAnnotationLineEnding.OpenArrow,
4159
+ strokeColor: "#E44234",
4160
+ strokeWidth: 1
4161
+ },
4162
+ behavior: {
4163
+ insertUpright: true,
4164
+ editAfterCreate: true,
4165
+ selectAfterCreate: true
4166
+ },
4167
+ transform: patchCalloutFreeText,
4168
+ pointerHandler: calloutFreeTextHandlerFactory
4169
+ }
4170
+ ];
3708
4171
  const stampTools = [
3709
4172
  {
3710
4173
  id: "stamp",
@@ -3771,6 +4234,7 @@ const defaultTools = [
3771
4234
  ...polygonTools,
3772
4235
  ...textCommentTools,
3773
4236
  ...freeTextTools,
4237
+ ...calloutFreeTextTools,
3774
4238
  ...stampTools,
3775
4239
  ...linkTools
3776
4240
  ];
@@ -4301,7 +4765,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
4301
4765
  const docState = this.state.documents[documentId];
4302
4766
  if (!docState) return;
4303
4767
  const pageAnnotations = (docState.pages[pageIndex] ?? []).map((uid) => docState.byUid[uid]).filter((ta) => ta !== void 0).filter((ta) => isSelectableAnnotation(ta));
4304
- const selectedIds = pageAnnotations.filter((ta) => rectsIntersect(rect, ta.object.rect)).filter((ta) => !this.isAnnotationLocked(ta.object, documentId)).map((ta) => ta.object.id);
4768
+ const selectedIds = pageAnnotations.filter((ta) => rectsIntersect(rect, ta.object.rect)).filter((ta) => this.isAnnotationSelectable(ta.object, documentId)).map((ta) => ta.object.id);
4305
4769
  if (selectedIds.length > 0) {
4306
4770
  const expandedIds = /* @__PURE__ */ new Set();
4307
4771
  for (const id of selectedIds) {
@@ -4400,6 +4864,9 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
4400
4864
  setLocked: (mode, documentId) => this.setLocked(mode, documentId),
4401
4865
  getLocked: (documentId) => this.getLocked(documentId),
4402
4866
  isAnnotationLocked: (annotation, documentId) => this.isAnnotationLocked(annotation, documentId),
4867
+ isAnnotationInteractive: (annotation, documentId) => this.isAnnotationInteractive(annotation, documentId),
4868
+ isAnnotationStructurallyLocked: (annotation, documentId) => this.isAnnotationStructurallyLocked(annotation, documentId),
4869
+ isAnnotationContentLocked: (annotation, documentId) => this.isAnnotationContentLocked(annotation, documentId),
4403
4870
  isCategoryLocked: (category, documentId) => this.isCategoryLockedMethod(category, documentId),
4404
4871
  isToolLocked: (toolId, documentId) => this.isToolLockedMethod(toolId, documentId),
4405
4872
  // Sync (lightweight state update without commit)
@@ -4474,6 +4941,9 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
4474
4941
  setLocked: (mode) => this.setLocked(mode, documentId),
4475
4942
  getLocked: () => this.getLocked(documentId),
4476
4943
  isAnnotationLocked: (annotation) => this.isAnnotationLocked(annotation, documentId),
4944
+ isAnnotationInteractive: (annotation) => this.isAnnotationInteractive(annotation, documentId),
4945
+ isAnnotationStructurallyLocked: (annotation) => this.isAnnotationStructurallyLocked(annotation, documentId),
4946
+ isAnnotationContentLocked: (annotation) => this.isAnnotationContentLocked(annotation, documentId),
4477
4947
  isCategoryLocked: (category) => this.isCategoryLockedMethod(category, documentId),
4478
4948
  isToolLocked: (toolId) => this.isToolLockedMethod(toolId, documentId),
4479
4949
  syncAnnotationObject: (id, patch) => this.syncAnnotationObject(id, patch, documentId),
@@ -5016,7 +5486,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
5016
5486
  selectAnnotation(pageIndex, id, documentId) {
5017
5487
  const docId = documentId ?? this.getActiveDocumentId();
5018
5488
  const ta = this.getAnnotationById(id, docId);
5019
- if (ta && this.isAnnotationLocked(ta.object, docId)) return;
5489
+ if (ta && !this.isAnnotationSelectable(ta.object, docId)) return;
5020
5490
  if (this.isInGroupMethod(id, docId)) {
5021
5491
  const members = this.getGroupMembersMethod(id, docId);
5022
5492
  const memberIds = members.map((m) => m.object.id);
@@ -5062,7 +5532,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
5062
5532
  toggleSelectionMethod(pageIndex, id, documentId) {
5063
5533
  const docId = documentId ?? this.getActiveDocumentId();
5064
5534
  const ta = this.getAnnotationById(id, docId);
5065
- if (ta && this.isAnnotationLocked(ta.object, docId)) return;
5535
+ if (ta && !this.isAnnotationSelectable(ta.object, docId)) return;
5066
5536
  const docState = this.getDocumentState(docId);
5067
5537
  if (docState.selectedUids.includes(id)) {
5068
5538
  this.dispatch(removeFromSelection(docId, id));
@@ -5082,7 +5552,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
5082
5552
  addToSelectionMethod(pageIndex, id, documentId) {
5083
5553
  const docId = documentId ?? this.getActiveDocumentId();
5084
5554
  const ta = this.getAnnotationById(id, docId);
5085
- if (ta && this.isAnnotationLocked(ta.object, docId)) return;
5555
+ if (ta && !this.isAnnotationInteractive(ta.object, docId)) return;
5086
5556
  this.dispatch(addToSelection(docId, pageIndex, id));
5087
5557
  }
5088
5558
  removeFromSelectionMethod(id, documentId) {
@@ -5094,7 +5564,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
5094
5564
  const docState = this.getDocumentState(docId);
5095
5565
  const filtered = ids.filter((id) => {
5096
5566
  const ta = docState.byUid[id];
5097
- return !ta || !this.isAnnotationLocked(ta.object, docId);
5567
+ return !ta || this.isAnnotationInteractive(ta.object, docId);
5098
5568
  });
5099
5569
  this.dispatch(setSelection(docId, filtered));
5100
5570
  }
@@ -6082,7 +6552,7 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
6082
6552
  const lockedSelected = updatedDocState.selectedUids.filter((uid) => {
6083
6553
  const ta = updatedDocState.byUid[uid];
6084
6554
  if (!ta) return false;
6085
- return this.isAnnotationLocked(ta.object, docId);
6555
+ return !this.isAnnotationInteractive(ta.object, docId);
6086
6556
  });
6087
6557
  if (lockedSelected.length > 0) {
6088
6558
  const remaining = updatedDocState.selectedUids.filter((uid) => !lockedSelected.includes(uid));
@@ -6098,6 +6568,47 @@ const _AnnotationPlugin = class _AnnotationPlugin extends BasePlugin {
6098
6568
  const categories = getAnnotationCategories(tool);
6099
6569
  return isCategoryLocked(categories, this.getLocked(documentId));
6100
6570
  }
6571
+ /**
6572
+ * Whether the annotation can be interacted with at all (selected, edited, menu shown).
6573
+ * Returns false for `noView`, `hidden`, `readOnly`, or category-locked annotations.
6574
+ * This is the spec-compliant "can the user do anything with this?" predicate.
6575
+ */
6576
+ isAnnotationInteractive(annotation, documentId) {
6577
+ if (hasNoViewFlag(annotation)) return false;
6578
+ if (hasHiddenFlag(annotation)) return false;
6579
+ if (hasReadOnlyFlag(annotation)) return false;
6580
+ const tool = this.findToolForAnnotation(annotation);
6581
+ const categories = getAnnotationCategories(tool);
6582
+ if (isCategoryLocked(categories, this.getLocked(documentId))) return false;
6583
+ return true;
6584
+ }
6585
+ /**
6586
+ * Whether the annotation's structure (position, size, rotation, vertices) is locked.
6587
+ * Non-interactive annotations are treated as structurally locked. PDF `locked` flag
6588
+ * makes an otherwise-interactive annotation structurally locked while still selectable.
6589
+ */
6590
+ isAnnotationStructurallyLocked(annotation, documentId) {
6591
+ if (!this.isAnnotationInteractive(annotation, documentId)) return true;
6592
+ return hasLockedFlag(annotation);
6593
+ }
6594
+ /**
6595
+ * Whether the annotation's content (e.g. FreeText text) is locked.
6596
+ * Non-interactive annotations are treated as content-locked. PDF `lockedContents`
6597
+ * flag blocks content edits while still allowing move/resize.
6598
+ */
6599
+ isAnnotationContentLocked(annotation, documentId) {
6600
+ if (!this.isAnnotationInteractive(annotation, documentId)) return true;
6601
+ return hasLockedContentsFlag(annotation);
6602
+ }
6603
+ /**
6604
+ * Whether a specific annotation is eligible to be added to the selection store.
6605
+ * Differs from the subtype-based `isSelectableAnnotation` helper: that answers
6606
+ * "is this subtype ever selectable?", while this answers "is this specific
6607
+ * annotation selectable right now given its flags and document lock state?".
6608
+ */
6609
+ isAnnotationSelectable(annotation, documentId) {
6610
+ return this.isAnnotationInteractive(annotation, documentId);
6611
+ }
6101
6612
  isCategoryLockedMethod(category, documentId) {
6102
6613
  return isCategoryLocked([category], this.getLocked(documentId));
6103
6614
  }
@@ -6430,9 +6941,13 @@ export {
6430
6941
  getSidebarAnnotationsWithRepliesGroupedByPage,
6431
6942
  getToolDefaultsById,
6432
6943
  hasAttachedLinks,
6944
+ hasHiddenFlag,
6433
6945
  hasIRTChildren,
6946
+ hasLockedContentsFlag,
6434
6947
  hasLockedFlag,
6435
6948
  hasMultipleSelected,
6949
+ hasNoViewFlag,
6950
+ hasReadOnlyFlag,
6436
6951
  inferRotationCenterFromRects2 as inferRotationCenterFromRects,
6437
6952
  initialDocumentState,
6438
6953
  initialState,