@embedpdf/plugin-annotation 2.12.1 → 2.14.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.
Files changed (41) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1912 -1456
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/handlers/callout-free-text.handler.d.ts +3 -0
  6. package/dist/lib/handlers/index.d.ts +1 -0
  7. package/dist/lib/handlers/types.d.ts +7 -1
  8. package/dist/lib/patching/index.d.ts +1 -0
  9. package/dist/lib/patching/patch-utils.d.ts +38 -1
  10. package/dist/lib/patching/patches/callout-freetext.patch.d.ts +3 -0
  11. package/dist/lib/patching/patches/index.d.ts +1 -0
  12. package/dist/lib/tools/default-tools.d.ts +35 -0
  13. package/dist/preact/index.cjs +1 -1
  14. package/dist/preact/index.cjs.map +1 -1
  15. package/dist/preact/index.js +395 -14
  16. package/dist/preact/index.js.map +1 -1
  17. package/dist/react/index.cjs +1 -1
  18. package/dist/react/index.cjs.map +1 -1
  19. package/dist/react/index.js +395 -14
  20. package/dist/react/index.js.map +1 -1
  21. package/dist/shared/components/annotations/callout-free-text-preview.d.ts +15 -0
  22. package/dist/shared/components/annotations/callout-free-text.d.ts +15 -0
  23. package/dist/shared-preact/components/annotations/callout-free-text-preview.d.ts +15 -0
  24. package/dist/shared-preact/components/annotations/callout-free-text.d.ts +15 -0
  25. package/dist/shared-react/components/annotations/callout-free-text-preview.d.ts +15 -0
  26. package/dist/shared-react/components/annotations/callout-free-text.d.ts +15 -0
  27. package/dist/svelte/components/annotations/CalloutFreeText.svelte.d.ts +15 -0
  28. package/dist/svelte/components/annotations/CalloutFreeTextPreview.svelte.d.ts +10 -0
  29. package/dist/svelte/components/renderers/CalloutFreeTextRenderer.svelte.d.ts +5 -0
  30. package/dist/svelte/index.cjs +1 -1
  31. package/dist/svelte/index.cjs.map +1 -1
  32. package/dist/svelte/index.js +497 -86
  33. package/dist/svelte/index.js.map +1 -1
  34. package/dist/vue/components/annotations/callout-free-text-preview.vue.d.ts +10 -0
  35. package/dist/vue/components/annotations/callout-free-text.vue.d.ts +25 -0
  36. package/dist/vue/components/renderers/callout-free-text-renderer.vue.d.ts +6 -0
  37. package/dist/vue/index.cjs +1 -1
  38. package/dist/vue/index.cjs.map +1 -1
  39. package/dist/vue/index.js +544 -170
  40. package/dist/vue/index.js.map +1 -1
  41. package/package.json +12 -12
package/dist/index.js CHANGED
@@ -737,6 +737,109 @@ function compensateRotatedVertexEdit(original, vertices, tightRect) {
737
737
  if (Math.abs(qx) < 1e-8 && Math.abs(qy) < 1e-8) return vertices;
738
738
  return vertices.map((v) => ({ x: v.x + qx, y: v.y + qy }));
739
739
  }
740
+ function computeTextBoxFromRD(rect, rd) {
741
+ if (!rd) return rect;
742
+ return {
743
+ origin: { x: rect.origin.x + rd.left, y: rect.origin.y + rd.top },
744
+ size: {
745
+ width: Math.max(0, rect.size.width - rd.left - rd.right),
746
+ height: Math.max(0, rect.size.height - rd.top - rd.bottom)
747
+ }
748
+ };
749
+ }
750
+ function computeRDFromTextBox(overallRect, textBox) {
751
+ return {
752
+ left: textBox.origin.x - overallRect.origin.x,
753
+ top: textBox.origin.y - overallRect.origin.y,
754
+ right: overallRect.origin.x + overallRect.size.width - (textBox.origin.x + textBox.size.width),
755
+ bottom: overallRect.origin.y + overallRect.size.height - (textBox.origin.y + textBox.size.height)
756
+ };
757
+ }
758
+ function computeCalloutConnectionPoint(knee, textBox) {
759
+ const cx = textBox.origin.x + textBox.size.width / 2;
760
+ const cy = textBox.origin.y + textBox.size.height / 2;
761
+ const dx = knee.x - cx;
762
+ const dy = knee.y - cy;
763
+ if (Math.abs(dx) >= Math.abs(dy)) {
764
+ return dx > 0 ? { x: textBox.origin.x + textBox.size.width, y: cy } : { x: textBox.origin.x, y: cy };
765
+ }
766
+ return dy > 0 ? { x: cx, y: textBox.origin.y + textBox.size.height } : { x: cx, y: textBox.origin.y };
767
+ }
768
+ function computeCalloutOverallRect(textBox, calloutLine, lineEnding, strokeWidth) {
769
+ const linePoints = [...calloutLine];
770
+ if (lineEnding && calloutLine.length >= 2) {
771
+ const handler = LINE_ENDING_HANDLERS[lineEnding];
772
+ if (handler) {
773
+ const angle = Math.atan2(
774
+ calloutLine[1].y - calloutLine[0].y,
775
+ calloutLine[1].x - calloutLine[0].x
776
+ );
777
+ const localPts = handler.getLocalPoints(strokeWidth);
778
+ const rotationAngle = handler.getRotation(angle + Math.PI);
779
+ const transformed = localPts.map(
780
+ (p) => rotateAndTranslatePoint(p, rotationAngle, calloutLine[0])
781
+ );
782
+ linePoints.push(...transformed);
783
+ }
784
+ }
785
+ const lineBbox = expandRect(rectFromPoints(linePoints), strokeWidth);
786
+ const tbRight = textBox.origin.x + textBox.size.width;
787
+ const tbBottom = textBox.origin.y + textBox.size.height;
788
+ const lnRight = lineBbox.origin.x + lineBbox.size.width;
789
+ const lnBottom = lineBbox.origin.y + lineBbox.size.height;
790
+ const minX = Math.min(textBox.origin.x, lineBbox.origin.x);
791
+ const minY = Math.min(textBox.origin.y, lineBbox.origin.y);
792
+ const maxX = Math.max(tbRight, lnRight);
793
+ const maxY = Math.max(tbBottom, lnBottom);
794
+ return {
795
+ origin: { x: minX, y: minY },
796
+ size: { width: maxX - minX, height: maxY - minY }
797
+ };
798
+ }
799
+ const calloutVertexConfig = {
800
+ extractVertices: (a) => {
801
+ const textBox = computeTextBoxFromRD(a.rect, a.rectangleDifferences);
802
+ const cl = a.calloutLine;
803
+ if (!cl || cl.length < 3) {
804
+ return [
805
+ { x: a.rect.origin.x, y: a.rect.origin.y },
806
+ { x: a.rect.origin.x, y: a.rect.origin.y },
807
+ { x: textBox.origin.x, y: textBox.origin.y },
808
+ { x: textBox.origin.x + textBox.size.width, y: textBox.origin.y + textBox.size.height }
809
+ ];
810
+ }
811
+ return [
812
+ cl[0],
813
+ cl[1],
814
+ { x: textBox.origin.x, y: textBox.origin.y },
815
+ { x: textBox.origin.x + textBox.size.width, y: textBox.origin.y + textBox.size.height }
816
+ ];
817
+ },
818
+ transformAnnotation: (a, vertices) => {
819
+ if (vertices.length < 4) return {};
820
+ const [arrowTip, knee, tbTL, tbBR] = vertices;
821
+ const textBox = {
822
+ origin: { x: Math.min(tbTL.x, tbBR.x), y: Math.min(tbTL.y, tbBR.y) },
823
+ size: {
824
+ width: Math.abs(tbBR.x - tbTL.x),
825
+ height: Math.abs(tbBR.y - tbTL.y)
826
+ }
827
+ };
828
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
829
+ const calloutLine = [arrowTip, knee, connectionPoint];
830
+ const overallRect = computeCalloutOverallRect(
831
+ textBox,
832
+ calloutLine,
833
+ a.lineEnding,
834
+ a.strokeWidth ?? 1
835
+ );
836
+ return {
837
+ calloutLine,
838
+ rect: overallRect,
839
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
840
+ };
841
+ }
842
+ };
740
843
  function createEnding(ending, strokeWidth, rad, px, py) {
741
844
  if (!ending) return null;
742
845
  const handler = LINE_ENDING_HANDLERS[ending];
@@ -893,495 +996,252 @@ function clampAnnotationToPage(annotation, pageSize) {
893
996
  } : {}
894
997
  };
895
998
  }
896
- const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
897
- __proto__: null,
898
- LINE_ENDING_HANDLERS,
899
- PatchRegistry,
900
- applyInsertUpright,
901
- calculateAABBFromVertices,
902
- calculateRotatedRectAABB,
903
- calculateRotatedRectAABBAroundPoint,
904
- clampAnnotationToPage,
905
- compensateRotatedVertexEdit,
906
- createEnding,
907
- getRectCenter,
908
- lineRectWithEndings,
909
- patchRegistry,
910
- resolveAnnotationRotationCenter,
911
- resolveRotateRects,
912
- resolveVertexEditRects,
913
- rotatePointAroundCenter: rotatePointAround,
914
- rotateVertices
915
- }, Symbol.toStringTag, { value: "Module" }));
916
- const COMMENT_SIZE = 24;
917
- const textHandlerFactory = {
918
- annotationType: PdfAnnotationSubtype.TEXT,
919
- create(context) {
920
- const { onCommit, getTool, pageSize } = context;
921
- return {
922
- onPointerDown: (pos) => {
923
- const tool = getTool();
924
- if (!tool) return;
925
- const rect = {
926
- origin: { x: pos.x - COMMENT_SIZE / 2, y: pos.y - COMMENT_SIZE / 2 },
927
- size: { width: COMMENT_SIZE, height: COMMENT_SIZE }
928
- };
929
- let anno = {
930
- ...tool.defaults,
931
- rect,
932
- type: PdfAnnotationSubtype.TEXT,
933
- name: tool.defaults.name ?? PdfAnnotationName.Comment,
934
- contents: tool.defaults.contents ?? "",
935
- flags: tool.defaults.flags ?? ["print", "noRotate", "noZoom"],
936
- pageIndex: context.pageIndex,
937
- id: uuidV4(),
938
- created: /* @__PURE__ */ new Date()
999
+ const patchInk = (original, ctx) => {
1000
+ switch (ctx.type) {
1001
+ case "vertex-edit":
1002
+ return ctx.changes;
1003
+ case "move": {
1004
+ if (!ctx.changes.rect) return ctx.changes;
1005
+ const { dx, dy, rects } = baseMoveChanges(original, ctx.changes.rect);
1006
+ return {
1007
+ ...rects,
1008
+ inkList: original.inkList.map((stroke) => ({
1009
+ points: stroke.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
1010
+ }))
1011
+ };
1012
+ }
1013
+ case "resize": {
1014
+ if (!ctx.changes.rect) return ctx.changes;
1015
+ const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
1016
+ original,
1017
+ ctx.changes.rect,
1018
+ ctx.metadata
1019
+ );
1020
+ const inset = (r2, pad) => ({
1021
+ origin: { x: r2.origin.x + pad, y: r2.origin.y + pad },
1022
+ size: {
1023
+ width: Math.max(1, r2.size.width - pad * 2),
1024
+ height: Math.max(1, r2.size.height - pad * 2)
1025
+ }
1026
+ });
1027
+ const resizeEpsilon = 1e-3;
1028
+ const widthChanged = Math.abs(scaleX - 1) > resizeEpsilon;
1029
+ const heightChanged = Math.abs(scaleY - 1) > resizeEpsilon;
1030
+ const strokeScale = widthChanged && !heightChanged ? scaleX : !widthChanged && heightChanged ? scaleY : Math.min(scaleX, scaleY);
1031
+ const rawStrokeWidth = Math.max(1, original.strokeWidth * strokeScale);
1032
+ const maxStrokeWidth = Math.max(
1033
+ 1,
1034
+ Math.min(resolvedRect.size.width, resolvedRect.size.height)
1035
+ );
1036
+ const clampedStrokeWidth = Math.min(rawStrokeWidth, maxStrokeWidth);
1037
+ const newStrokeWidth = Number(clampedStrokeWidth.toFixed(1));
1038
+ const innerOld = inset(oldRect, original.strokeWidth / 2);
1039
+ const innerNew = inset(resolvedRect, newStrokeWidth / 2);
1040
+ const sx = innerNew.size.width / Math.max(innerOld.size.width, 1e-6);
1041
+ const sy = innerNew.size.height / Math.max(innerOld.size.height, 1e-6);
1042
+ return {
1043
+ ...rects,
1044
+ inkList: original.inkList.map((stroke) => ({
1045
+ points: stroke.points.map((p) => ({
1046
+ x: innerNew.origin.x + (p.x - innerOld.origin.x) * sx,
1047
+ y: innerNew.origin.y + (p.y - innerOld.origin.y) * sy
1048
+ }))
1049
+ })),
1050
+ strokeWidth: newStrokeWidth
1051
+ };
1052
+ }
1053
+ case "rotate": {
1054
+ const result = baseRotateChanges(original, ctx);
1055
+ if (!result) return ctx.changes;
1056
+ const { dx, dy } = rotateOrbitDelta(original, result);
1057
+ return {
1058
+ ...result,
1059
+ inkList: original.inkList.map((stroke) => ({
1060
+ points: stroke.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
1061
+ }))
1062
+ };
1063
+ }
1064
+ case "property-update": {
1065
+ const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.rotation !== void 0;
1066
+ if (!needsRectUpdate) return ctx.changes;
1067
+ const merged = { ...original, ...ctx.changes };
1068
+ const pts = merged.inkList.flatMap((s) => s.points);
1069
+ const tightRect = expandRect(rectFromPoints(pts), merged.strokeWidth / 2);
1070
+ const effectiveRotation = ctx.changes.rotation ?? original.rotation ?? 0;
1071
+ if (original.unrotatedRect || ctx.changes.rotation !== void 0) {
1072
+ return {
1073
+ ...ctx.changes,
1074
+ unrotatedRect: tightRect,
1075
+ rect: calculateRotatedRectAABBAroundPoint(
1076
+ tightRect,
1077
+ effectiveRotation,
1078
+ resolveAnnotationRotationCenter(original)
1079
+ )
939
1080
  };
940
- anno = clampAnnotationToPage(anno, pageSize);
941
- onCommit(anno);
942
1081
  }
943
- };
1082
+ return { ...ctx.changes, rect: tightRect };
1083
+ }
1084
+ default:
1085
+ return ctx.changes;
944
1086
  }
945
1087
  };
946
- function useClickDetector({
947
- threshold = 5,
948
- getTool,
949
- onClickDetected
950
- }) {
951
- const [getStartPos, setStartPos] = useState(null);
952
- const [getHasMoved, setHasMoved] = useState(false);
953
- return {
954
- onStart: (pos) => {
955
- setStartPos(pos);
956
- setHasMoved(false);
957
- },
958
- onMove: (pos) => {
959
- const start = getStartPos();
960
- if (!start || getHasMoved()) return;
961
- const distance2 = Math.sqrt(Math.pow(pos.x - start.x, 2) + Math.pow(pos.y - start.y, 2));
962
- if (distance2 > threshold) {
963
- setHasMoved(true);
1088
+ const patchLine = (orig, ctx) => {
1089
+ switch (ctx.type) {
1090
+ case "vertex-edit":
1091
+ if (ctx.changes.linePoints) {
1092
+ const { start, end } = ctx.changes.linePoints;
1093
+ const rawPoints = [start, end];
1094
+ const rawRect = lineRectWithEndings(rawPoints, orig.strokeWidth, orig.lineEndings);
1095
+ const compensated = compensateRotatedVertexEdit(orig, rawPoints, rawRect);
1096
+ const rect = lineRectWithEndings(compensated, orig.strokeWidth, orig.lineEndings);
1097
+ return {
1098
+ ...resolveVertexEditRects(orig, rect),
1099
+ linePoints: { start: compensated[0], end: compensated[1] }
1100
+ };
964
1101
  }
965
- },
966
- onEnd: (pos) => {
967
- var _a;
968
- const start = getStartPos();
969
- if (start && !getHasMoved()) {
970
- const tool = getTool();
971
- if (tool && "clickBehavior" in tool && ((_a = tool.clickBehavior) == null ? void 0 : _a.enabled)) {
972
- onClickDetected(pos, tool);
1102
+ return ctx.changes;
1103
+ case "move": {
1104
+ if (!ctx.changes.rect) return ctx.changes;
1105
+ const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
1106
+ return {
1107
+ ...rects,
1108
+ linePoints: {
1109
+ start: { x: orig.linePoints.start.x + dx, y: orig.linePoints.start.y + dy },
1110
+ end: { x: orig.linePoints.end.x + dx, y: orig.linePoints.end.y + dy }
973
1111
  }
974
- }
975
- setStartPos(null);
976
- setHasMoved(false);
977
- },
978
- hasMoved: getHasMoved,
979
- reset: () => {
980
- setStartPos(null);
981
- setHasMoved(false);
1112
+ };
982
1113
  }
983
- };
984
- }
985
- const freeTextHandlerFactory = {
986
- annotationType: PdfAnnotationSubtype.FREETEXT,
987
- create(context) {
988
- const { onCommit, onPreview, getTool, pageSize, pageIndex, pageRotation } = context;
989
- const [getStart, setStart] = useState(null);
990
- const clampToPage = (pos) => ({
991
- x: clamp(pos.x, 0, pageSize.width),
992
- y: clamp(pos.y, 0, pageSize.height)
993
- });
994
- const getDefaults = () => {
995
- const tool = getTool();
996
- if (!tool) return null;
1114
+ case "resize": {
1115
+ if (!ctx.changes.rect) return ctx.changes;
1116
+ const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
1117
+ orig,
1118
+ ctx.changes.rect,
1119
+ ctx.metadata
1120
+ );
997
1121
  return {
998
- ...tool.defaults,
999
- fontColor: tool.defaults.fontColor ?? "#000000",
1000
- opacity: tool.defaults.opacity ?? 1,
1001
- fontSize: tool.defaults.fontSize ?? 12,
1002
- fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
1003
- color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
1004
- textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
1005
- verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
1006
- contents: tool.defaults.contents ?? "Insert text here",
1007
- flags: tool.defaults.flags ?? ["print"]
1008
- };
1009
- };
1010
- const clickDetector = useClickDetector({
1011
- threshold: 5,
1012
- getTool,
1013
- onClickDetected: (pos, tool) => {
1014
- var _a;
1015
- const defaults = getDefaults();
1016
- if (!defaults) return;
1017
- const clickConfig = tool.clickBehavior;
1018
- if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
1019
- const { width, height } = clickConfig.defaultSize;
1020
- const rect = {
1021
- origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
1022
- size: { width, height }
1023
- };
1024
- const contents = clickConfig.defaultContent ?? defaults.contents;
1025
- let anno = {
1026
- ...defaults,
1027
- contents,
1028
- type: PdfAnnotationSubtype.FREETEXT,
1029
- rect,
1030
- pageIndex,
1031
- id: uuidV4(),
1032
- created: /* @__PURE__ */ new Date()
1033
- };
1034
- if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
1035
- anno = applyInsertUpright(anno, pageRotation, false);
1036
- }
1037
- anno = clampAnnotationToPage(anno, pageSize);
1038
- onCommit(anno);
1039
- }
1040
- });
1041
- const getPreview = (current) => {
1042
- const start = getStart();
1043
- if (!start) return null;
1044
- const defaults = getDefaults();
1045
- if (!defaults) return null;
1046
- const minX = Math.min(start.x, current.x);
1047
- const minY = Math.min(start.y, current.y);
1048
- const width = Math.abs(start.x - current.x);
1049
- const height = Math.abs(start.y - current.y);
1050
- const rect = {
1051
- origin: { x: minX, y: minY },
1052
- size: { width, height }
1122
+ ...rects,
1123
+ linePoints: {
1124
+ start: {
1125
+ x: resolvedRect.origin.x + (orig.linePoints.start.x - oldRect.origin.x) * scaleX,
1126
+ y: resolvedRect.origin.y + (orig.linePoints.start.y - oldRect.origin.y) * scaleY
1127
+ },
1128
+ end: {
1129
+ x: resolvedRect.origin.x + (orig.linePoints.end.x - oldRect.origin.x) * scaleX,
1130
+ y: resolvedRect.origin.y + (orig.linePoints.end.y - oldRect.origin.y) * scaleY
1131
+ }
1132
+ }
1053
1133
  };
1134
+ }
1135
+ case "rotate": {
1136
+ const result = baseRotateChanges(orig, ctx);
1137
+ if (!result) return ctx.changes;
1138
+ const { dx, dy } = rotateOrbitDelta(orig, result);
1054
1139
  return {
1055
- type: PdfAnnotationSubtype.FREETEXT,
1056
- bounds: rect,
1057
- data: {
1058
- ...defaults,
1059
- rect
1140
+ ...result,
1141
+ linePoints: {
1142
+ start: { x: orig.linePoints.start.x + dx, y: orig.linePoints.start.y + dy },
1143
+ end: { x: orig.linePoints.end.x + dx, y: orig.linePoints.end.y + dy }
1060
1144
  }
1061
1145
  };
1062
- };
1063
- return {
1064
- onPointerDown: (pos, evt) => {
1065
- var _a;
1066
- const clampedPos = clampToPage(pos);
1067
- setStart(clampedPos);
1068
- clickDetector.onStart(clampedPos);
1069
- onPreview(getPreview(clampedPos));
1070
- (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
1071
- },
1072
- onPointerMove: (pos) => {
1073
- const clampedPos = clampToPage(pos);
1074
- clickDetector.onMove(clampedPos);
1075
- if (getStart() && clickDetector.hasMoved()) {
1076
- onPreview(getPreview(clampedPos));
1077
- }
1078
- },
1079
- onPointerUp: (pos, evt) => {
1080
- var _a, _b;
1081
- const start = getStart();
1082
- if (!start) return;
1083
- const defaults = getDefaults();
1084
- if (!defaults) return;
1085
- const clampedPos = clampToPage(pos);
1086
- if (!clickDetector.hasMoved()) {
1087
- clickDetector.onEnd(clampedPos);
1088
- } else {
1089
- const minX = Math.min(start.x, clampedPos.x);
1090
- const minY = Math.min(start.y, clampedPos.y);
1091
- const width = Math.abs(start.x - clampedPos.x);
1092
- const height = Math.abs(start.y - clampedPos.y);
1093
- const rect = {
1094
- origin: { x: minX, y: minY },
1095
- size: { width, height }
1096
- };
1097
- const tool = getTool();
1098
- let anno = {
1099
- ...defaults,
1100
- type: PdfAnnotationSubtype.FREETEXT,
1101
- rect,
1102
- pageIndex: context.pageIndex,
1103
- id: uuidV4(),
1104
- created: /* @__PURE__ */ new Date()
1105
- };
1106
- if ((_a = tool == null ? void 0 : tool.behavior) == null ? void 0 : _a.insertUpright) {
1107
- anno = applyInsertUpright(anno, pageRotation, true);
1108
- }
1109
- onCommit(anno);
1110
- }
1111
- setStart(null);
1112
- onPreview(null);
1113
- clickDetector.reset();
1114
- (_b = evt.releasePointerCapture) == null ? void 0 : _b.call(evt);
1115
- },
1116
- onPointerLeave: (_, evt) => {
1117
- var _a;
1118
- setStart(null);
1119
- onPreview(null);
1120
- clickDetector.reset();
1121
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
1122
- },
1123
- onPointerCancel: (_, evt) => {
1124
- var _a;
1125
- setStart(null);
1126
- onPreview(null);
1127
- clickDetector.reset();
1128
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
1146
+ }
1147
+ case "property-update": {
1148
+ const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.lineEndings !== void 0 || ctx.changes.rotation !== void 0;
1149
+ if (!needsRectUpdate) return ctx.changes;
1150
+ const merged = { ...orig, ...ctx.changes };
1151
+ const tightRect = lineRectWithEndings(
1152
+ [merged.linePoints.start, merged.linePoints.end],
1153
+ merged.strokeWidth,
1154
+ merged.lineEndings
1155
+ );
1156
+ const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
1157
+ if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
1158
+ return {
1159
+ ...ctx.changes,
1160
+ unrotatedRect: tightRect,
1161
+ rect: calculateRotatedRectAABBAroundPoint(
1162
+ tightRect,
1163
+ effectiveRotation,
1164
+ resolveAnnotationRotationCenter(orig)
1165
+ )
1166
+ };
1129
1167
  }
1130
- };
1168
+ return { ...ctx.changes, rect: tightRect };
1169
+ }
1170
+ default:
1171
+ return ctx.changes;
1131
1172
  }
1132
1173
  };
1133
- const lineHandlerFactory = {
1134
- annotationType: PdfAnnotationSubtype.LINE,
1135
- create(context) {
1136
- const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
1137
- const [getStart, setStart] = useState(null);
1138
- const clampToPage = (pos) => ({
1139
- x: clamp(pos.x, 0, pageSize.width),
1140
- y: clamp(pos.y, 0, pageSize.height)
1141
- });
1142
- const getDefaults = () => {
1143
- const tool = getTool();
1144
- if (!tool) return null;
1174
+ const patchPolyline = (orig, ctx) => {
1175
+ switch (ctx.type) {
1176
+ case "vertex-edit":
1177
+ if (ctx.changes.vertices && ctx.changes.vertices.length) {
1178
+ const rawVertices = ctx.changes.vertices;
1179
+ const rawRect = lineRectWithEndings(rawVertices, orig.strokeWidth, orig.lineEndings);
1180
+ const compensated = compensateRotatedVertexEdit(orig, rawVertices, rawRect);
1181
+ const rect = lineRectWithEndings(compensated, orig.strokeWidth, orig.lineEndings);
1182
+ return {
1183
+ ...resolveVertexEditRects(orig, rect),
1184
+ vertices: compensated
1185
+ };
1186
+ }
1187
+ return ctx.changes;
1188
+ case "move": {
1189
+ if (!ctx.changes.rect) return ctx.changes;
1190
+ const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
1145
1191
  return {
1146
- ...tool.defaults,
1147
- strokeWidth: tool.defaults.strokeWidth ?? 1,
1148
- lineEndings: tool.defaults.lineEndings ?? {
1149
- start: PdfAnnotationLineEnding.None,
1150
- end: PdfAnnotationLineEnding.None
1151
- },
1152
- color: tool.defaults.color ?? "#000000",
1153
- opacity: tool.defaults.opacity ?? 1,
1154
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
1155
- strokeDashArray: tool.defaults.strokeDashArray ?? [],
1156
- strokeColor: tool.defaults.strokeColor ?? "#000000",
1157
- flags: tool.defaults.flags ?? ["print"]
1192
+ ...rects,
1193
+ vertices: orig.vertices.map((p) => ({ x: p.x + dx, y: p.y + dy }))
1158
1194
  };
1159
- };
1160
- const clickDetector = useClickDetector({
1161
- threshold: 5,
1162
- getTool,
1163
- onClickDetected: (pos, tool) => {
1164
- const defaults = getDefaults();
1165
- if (!defaults) return;
1166
- const clickConfig = tool.clickBehavior;
1167
- if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
1168
- const angle = clickConfig.defaultAngle ?? 0;
1169
- const length = clickConfig.defaultLength;
1170
- const halfLength = length / 2;
1171
- const startX = pos.x - halfLength * Math.cos(angle);
1172
- const startY = pos.y - halfLength * Math.sin(angle);
1173
- const endX = pos.x + halfLength * Math.cos(angle);
1174
- const endY = pos.y + halfLength * Math.sin(angle);
1175
- const start = clampToPage({ x: startX, y: startY });
1176
- const end = clampToPage({ x: endX, y: endY });
1177
- const rect = lineRectWithEndings(
1178
- [start, end],
1179
- defaults.strokeWidth,
1180
- defaults.lineEndings
1181
- );
1182
- onCommit({
1183
- ...defaults,
1184
- rect,
1185
- linePoints: { start, end },
1186
- pageIndex,
1187
- id: uuidV4(),
1188
- created: /* @__PURE__ */ new Date(),
1189
- type: PdfAnnotationSubtype.LINE
1190
- });
1191
- }
1192
- });
1193
- const getPreview = (current) => {
1194
- const start = getStart();
1195
- if (!start) return null;
1196
- const defaults = getDefaults();
1197
- if (!defaults) return null;
1198
- const bounds = lineRectWithEndings(
1199
- [start, current],
1200
- defaults.strokeWidth,
1201
- defaults.lineEndings
1195
+ }
1196
+ case "resize": {
1197
+ if (!ctx.changes.rect) return ctx.changes;
1198
+ const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
1199
+ orig,
1200
+ ctx.changes.rect,
1201
+ ctx.metadata
1202
1202
  );
1203
1203
  return {
1204
- type: PdfAnnotationSubtype.LINE,
1205
- bounds,
1206
- data: {
1207
- ...defaults,
1208
- rect: bounds,
1209
- linePoints: { start, end: current }
1210
- }
1204
+ ...rects,
1205
+ vertices: orig.vertices.map((v) => ({
1206
+ x: resolvedRect.origin.x + (v.x - oldRect.origin.x) * scaleX,
1207
+ y: resolvedRect.origin.y + (v.y - oldRect.origin.y) * scaleY
1208
+ }))
1211
1209
  };
1212
- };
1213
- return {
1214
- onPointerDown: (pos, evt) => {
1215
- var _a;
1216
- const clampedPos = clampToPage(pos);
1217
- setStart(clampedPos);
1218
- clickDetector.onStart(clampedPos);
1219
- onPreview(getPreview(clampedPos));
1220
- (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
1221
- },
1222
- onPointerMove: (pos) => {
1223
- const clampedPos = clampToPage(pos);
1224
- clickDetector.onMove(clampedPos);
1225
- if (getStart() && clickDetector.hasMoved()) {
1226
- onPreview(getPreview(clampedPos));
1227
- }
1228
- },
1229
- onPointerUp: (pos, evt) => {
1230
- var _a;
1231
- const start = getStart();
1232
- if (!start) return;
1233
- const clampedPos = clampToPage(pos);
1234
- if (!clickDetector.hasMoved()) {
1235
- clickDetector.onEnd(clampedPos);
1236
- } else {
1237
- const defaults = getDefaults();
1238
- if (!defaults) return;
1239
- if (Math.abs(clampedPos.x - start.x) > 2 || Math.abs(clampedPos.y - start.y) > 2) {
1240
- const rect = lineRectWithEndings(
1241
- [start, clampedPos],
1242
- defaults.strokeWidth,
1243
- defaults.lineEndings
1244
- );
1245
- onCommit({
1246
- ...defaults,
1247
- rect,
1248
- linePoints: { start, end: clampedPos },
1249
- pageIndex,
1250
- id: uuidV4(),
1251
- flags: ["print"],
1252
- created: /* @__PURE__ */ new Date(),
1253
- type: PdfAnnotationSubtype.LINE
1254
- });
1255
- }
1256
- }
1257
- setStart(null);
1258
- onPreview(null);
1259
- clickDetector.reset();
1260
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
1261
- },
1262
- onPointerLeave: (_, evt) => {
1263
- var _a;
1264
- setStart(null);
1265
- onPreview(null);
1266
- clickDetector.reset();
1267
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
1268
- },
1269
- onPointerCancel: (_, evt) => {
1270
- var _a;
1271
- setStart(null);
1272
- onPreview(null);
1273
- clickDetector.reset();
1274
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
1275
- }
1276
- };
1277
- }
1278
- };
1279
- const polylineHandlerFactory = {
1280
- annotationType: PdfAnnotationSubtype.POLYLINE,
1281
- create(context) {
1282
- const { onCommit, onPreview, getTool, pageSize } = context;
1283
- const [getVertices, setVertices] = useState([]);
1284
- const [getCurrent, setCurrent] = useState(null);
1285
- const clampToPage = (pos) => ({
1286
- x: clamp(pos.x, 0, pageSize.width),
1287
- y: clamp(pos.y, 0, pageSize.height)
1288
- });
1289
- const getDefaults = () => {
1290
- const tool = getTool();
1291
- if (!tool) return null;
1210
+ }
1211
+ case "rotate": {
1212
+ const result = baseRotateChanges(orig, ctx);
1213
+ if (!result) return ctx.changes;
1214
+ const { dx, dy } = rotateOrbitDelta(orig, result);
1292
1215
  return {
1293
- ...tool.defaults,
1294
- strokeWidth: tool.defaults.strokeWidth ?? 1,
1295
- lineEndings: tool.defaults.lineEndings ?? {
1296
- start: PdfAnnotationLineEnding.None,
1297
- end: PdfAnnotationLineEnding.None
1298
- },
1299
- color: tool.defaults.color ?? "#000000",
1300
- opacity: tool.defaults.opacity ?? 1,
1301
- strokeColor: tool.defaults.strokeColor ?? "#000000",
1302
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
1303
- strokeDashArray: tool.defaults.strokeDashArray ?? [],
1304
- flags: tool.defaults.flags ?? ["print"]
1305
- };
1306
- };
1307
- const commitPolyline = () => {
1308
- const vertices = getVertices();
1309
- if (vertices.length < 2) return;
1310
- const defaults = getDefaults();
1311
- if (!defaults) return;
1312
- const rect = lineRectWithEndings(
1313
- vertices,
1314
- defaults.strokeWidth,
1315
- defaults.lineEndings
1316
- );
1317
- const anno = {
1318
- ...defaults,
1319
- vertices,
1320
- rect,
1321
- type: PdfAnnotationSubtype.POLYLINE,
1322
- pageIndex: context.pageIndex,
1323
- id: uuidV4(),
1324
- created: /* @__PURE__ */ new Date()
1216
+ ...result,
1217
+ vertices: orig.vertices.map((v) => ({ x: v.x + dx, y: v.y + dy }))
1325
1218
  };
1326
- onCommit(anno);
1327
- setVertices([]);
1328
- setCurrent(null);
1329
- onPreview(null);
1330
- };
1331
- const getPreview = () => {
1332
- const vertices = getVertices();
1333
- const currentPos = getCurrent();
1334
- if (vertices.length === 0 || !currentPos) return null;
1335
- const defaults = getDefaults();
1336
- if (!defaults) return null;
1337
- const allPoints = [...vertices, currentPos];
1338
- const bounds = lineRectWithEndings(
1339
- allPoints,
1340
- defaults.strokeWidth,
1341
- defaults.lineEndings
1219
+ }
1220
+ case "property-update": {
1221
+ const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.lineEndings !== void 0 || ctx.changes.rotation !== void 0;
1222
+ if (!needsRectUpdate) return ctx.changes;
1223
+ const merged = { ...orig, ...ctx.changes };
1224
+ const tightRect = lineRectWithEndings(
1225
+ merged.vertices,
1226
+ merged.strokeWidth,
1227
+ merged.lineEndings
1342
1228
  );
1343
- return {
1344
- type: PdfAnnotationSubtype.POLYLINE,
1345
- bounds,
1346
- data: {
1347
- ...defaults,
1348
- rect: bounds,
1349
- vertices: allPoints,
1350
- currentVertex: currentPos
1351
- }
1352
- };
1353
- };
1354
- return {
1355
- onClick: (pos, evt) => {
1356
- if (evt.metaKey || evt.ctrlKey) {
1357
- return;
1358
- }
1359
- const clampedPos = clampToPage(pos);
1360
- const vertices = getVertices();
1361
- const lastVertex = vertices[vertices.length - 1];
1362
- if (lastVertex && Math.abs(lastVertex.x - clampedPos.x) < 1 && Math.abs(lastVertex.y - clampedPos.y) < 1) {
1363
- return;
1364
- }
1365
- setVertices([...vertices, clampedPos]);
1366
- setCurrent(clampedPos);
1367
- onPreview(getPreview());
1368
- },
1369
- onDoubleClick: () => {
1370
- commitPolyline();
1371
- },
1372
- onPointerMove: (pos) => {
1373
- if (getVertices().length > 0) {
1374
- const clampedPos = clampToPage(pos);
1375
- setCurrent(clampedPos);
1376
- onPreview(getPreview());
1377
- }
1378
- },
1379
- onPointerCancel: () => {
1380
- setVertices([]);
1381
- setCurrent(null);
1382
- onPreview(null);
1229
+ const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
1230
+ if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
1231
+ return {
1232
+ ...ctx.changes,
1233
+ unrotatedRect: tightRect,
1234
+ rect: calculateRotatedRectAABBAroundPoint(
1235
+ tightRect,
1236
+ effectiveRotation,
1237
+ resolveAnnotationRotationCenter(orig)
1238
+ )
1239
+ };
1383
1240
  }
1384
- };
1241
+ return { ...ctx.changes, rect: tightRect };
1242
+ }
1243
+ default:
1244
+ return ctx.changes;
1385
1245
  }
1386
1246
  };
1387
1247
  function convertAABBRectToUnrotatedSpace(newAABBRect, originalAABBRect, originalUnrotatedRect, rotationDegrees) {
@@ -1932,204 +1792,462 @@ function generateCloudyPolygonPath(vertices, rectOrigin, intensity, lineWidth) {
1932
1792
  out.close();
1933
1793
  return out.build(lineWidth);
1934
1794
  }
1935
- const HANDLE_SIZE_PX = 14;
1936
- const polygonHandlerFactory = {
1937
- annotationType: PdfAnnotationSubtype.POLYGON,
1938
- create(context) {
1939
- const { onCommit, onPreview, getTool, scale, pageSize } = context;
1940
- const [getVertices, setVertices] = useState([]);
1941
- const [getCurrent, setCurrent] = useState(null);
1942
- const clampToPage = (pos) => ({
1943
- x: clamp(pos.x, 0, pageSize.width),
1944
- y: clamp(pos.y, 0, pageSize.height)
1945
- });
1946
- const isInsideStartHandle = (pos) => {
1947
- const vertices = getVertices();
1948
- if (vertices.length < 2) return false;
1949
- const sizePDF = HANDLE_SIZE_PX / scale;
1950
- const half = sizePDF / 2;
1951
- const v0 = vertices[0];
1952
- return pos.x >= v0.x - half && pos.x <= v0.x + half && pos.y >= v0.y - half && pos.y <= v0.y + half;
1953
- };
1954
- const getDefaults = () => {
1955
- const tool = getTool();
1956
- if (!tool) return null;
1795
+ function getPolygonPad(intensity, strokeWidth) {
1796
+ if ((intensity ?? 0) > 0) {
1797
+ return getCloudyBorderExtent(intensity, strokeWidth, false);
1798
+ }
1799
+ return strokeWidth / 2;
1800
+ }
1801
+ const patchPolygon = (orig, ctx) => {
1802
+ switch (ctx.type) {
1803
+ case "vertex-edit":
1804
+ if (ctx.changes.vertices && ctx.changes.vertices.length) {
1805
+ const pad = getPolygonPad(orig.cloudyBorderIntensity, orig.strokeWidth);
1806
+ const rawVertices = ctx.changes.vertices;
1807
+ const rawRect = expandRect(rectFromPoints(rawVertices), pad);
1808
+ const compensated = compensateRotatedVertexEdit(orig, rawVertices, rawRect);
1809
+ const rect = expandRect(rectFromPoints(compensated), pad);
1810
+ return {
1811
+ ...resolveVertexEditRects(orig, rect),
1812
+ vertices: compensated
1813
+ };
1814
+ }
1815
+ return ctx.changes;
1816
+ case "move": {
1817
+ if (!ctx.changes.rect) return ctx.changes;
1818
+ const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
1957
1819
  return {
1958
- ...tool.defaults,
1959
- color: tool.defaults.color ?? "#000000",
1960
- opacity: tool.defaults.opacity ?? 1,
1961
- strokeWidth: tool.defaults.strokeWidth ?? 1,
1962
- strokeColor: tool.defaults.strokeColor ?? "#000000",
1963
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
1964
- strokeDashArray: tool.defaults.strokeDashArray ?? [],
1965
- flags: tool.defaults.flags ?? ["print"]
1820
+ ...rects,
1821
+ vertices: orig.vertices.map((p) => ({ x: p.x + dx, y: p.y + dy }))
1966
1822
  };
1967
- };
1968
- const commitPolygon = () => {
1969
- const vertices = getVertices();
1970
- if (vertices.length < 3) return;
1971
- const defaults = getDefaults();
1972
- if (!defaults) return;
1973
- const intensity = defaults.cloudyBorderIntensity ?? 0;
1974
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
1975
- const rect = expandRect(rectFromPoints(vertices), pad);
1976
- const anno = {
1977
- ...defaults,
1978
- vertices,
1979
- rect,
1980
- type: PdfAnnotationSubtype.POLYGON,
1981
- pageIndex: context.pageIndex,
1982
- id: uuidV4(),
1983
- created: /* @__PURE__ */ new Date(),
1984
- ...intensity > 0 && {
1985
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
1986
- }
1823
+ }
1824
+ case "resize": {
1825
+ if (!ctx.changes.rect) return ctx.changes;
1826
+ const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
1827
+ orig,
1828
+ ctx.changes.rect,
1829
+ ctx.metadata
1830
+ );
1831
+ return {
1832
+ ...rects,
1833
+ vertices: orig.vertices.map((v) => ({
1834
+ x: resolvedRect.origin.x + (v.x - oldRect.origin.x) * scaleX,
1835
+ y: resolvedRect.origin.y + (v.y - oldRect.origin.y) * scaleY
1836
+ }))
1987
1837
  };
1988
- onCommit(anno);
1989
- setVertices([]);
1990
- setCurrent(null);
1991
- onPreview(null);
1992
- };
1993
- const getPreview = () => {
1994
- const vertices = getVertices();
1995
- const currentPos = getCurrent();
1996
- if (vertices.length === 0 || !currentPos) return null;
1997
- const defaults = getDefaults();
1998
- if (!defaults) return null;
1999
- const intensity = defaults.cloudyBorderIntensity ?? 0;
2000
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
2001
- const allPoints = [...vertices, currentPos];
2002
- const bounds = expandRect(rectFromPoints(allPoints), pad);
1838
+ }
1839
+ case "rotate": {
1840
+ const result = baseRotateChanges(orig, ctx);
1841
+ if (!result) return ctx.changes;
1842
+ const { dx, dy } = rotateOrbitDelta(orig, result);
2003
1843
  return {
2004
- type: PdfAnnotationSubtype.POLYGON,
2005
- bounds,
2006
- data: {
2007
- ...defaults,
2008
- rect: bounds,
2009
- vertices,
2010
- currentVertex: currentPos
2011
- }
1844
+ ...result,
1845
+ vertices: orig.vertices.map((v) => ({ x: v.x + dx, y: v.y + dy }))
2012
1846
  };
2013
- };
2014
- return {
2015
- onClick: (pos, evt) => {
2016
- if (evt.metaKey || evt.ctrlKey) {
2017
- return;
2018
- }
2019
- const clampedPos = clampToPage(pos);
2020
- if (isInsideStartHandle(clampedPos) && getVertices().length >= 3) {
2021
- commitPolygon();
2022
- return;
1847
+ }
1848
+ case "property-update": {
1849
+ const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
1850
+ const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.rotation !== void 0 || cloudyChanged;
1851
+ if (!needsRectUpdate) return ctx.changes;
1852
+ const merged = { ...orig, ...ctx.changes };
1853
+ const pad = getPolygonPad(merged.cloudyBorderIntensity, merged.strokeWidth);
1854
+ const tightRect = expandRect(rectFromPoints(merged.vertices), pad);
1855
+ let patch = ctx.changes;
1856
+ const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
1857
+ if (cloudyChanged || ctx.changes.strokeWidth !== void 0 && hasCloudy) {
1858
+ const intensity = merged.cloudyBorderIntensity ?? 0;
1859
+ if (intensity > 0) {
1860
+ const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
1861
+ patch = {
1862
+ ...patch,
1863
+ rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
1864
+ };
1865
+ } else {
1866
+ patch = { ...patch, rectangleDifferences: void 0 };
2023
1867
  }
2024
- const vertices = getVertices();
2025
- const lastVertex = vertices[vertices.length - 1];
2026
- if (lastVertex && Math.abs(lastVertex.x - clampedPos.x) < 1 && Math.abs(lastVertex.y - clampedPos.y) < 1) {
2027
- return;
1868
+ }
1869
+ const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
1870
+ if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
1871
+ return {
1872
+ ...patch,
1873
+ unrotatedRect: tightRect,
1874
+ rect: calculateRotatedRectAABBAroundPoint(
1875
+ tightRect,
1876
+ effectiveRotation,
1877
+ resolveAnnotationRotationCenter(orig)
1878
+ )
1879
+ };
1880
+ }
1881
+ return { ...patch, rect: tightRect };
1882
+ }
1883
+ default:
1884
+ return ctx.changes;
1885
+ }
1886
+ };
1887
+ const patchCircle = (orig, ctx) => {
1888
+ switch (ctx.type) {
1889
+ case "move":
1890
+ if (!ctx.changes.rect) return ctx.changes;
1891
+ return baseMoveChanges(orig, ctx.changes.rect).rects;
1892
+ case "resize":
1893
+ if (!ctx.changes.rect) return ctx.changes;
1894
+ return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
1895
+ case "rotate":
1896
+ return baseRotateChanges(orig, ctx) ?? ctx.changes;
1897
+ case "property-update": {
1898
+ let patch = ctx.changes;
1899
+ const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
1900
+ const strokeChanged = ctx.changes.strokeWidth !== void 0;
1901
+ const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
1902
+ if (cloudyChanged || strokeChanged && hasCloudy) {
1903
+ const merged = { ...orig, ...ctx.changes };
1904
+ const intensity = merged.cloudyBorderIntensity ?? 0;
1905
+ if (intensity > 0) {
1906
+ const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, true);
1907
+ patch = {
1908
+ ...patch,
1909
+ rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
1910
+ };
1911
+ } else {
1912
+ patch = { ...patch, rectangleDifferences: void 0 };
2028
1913
  }
2029
- setVertices([...vertices, clampedPos]);
2030
- setCurrent(clampedPos);
2031
- onPreview(getPreview());
2032
- },
2033
- onDoubleClick: (_) => {
2034
- commitPolygon();
2035
- },
2036
- onPointerMove: (pos) => {
2037
- if (getVertices().length > 0) {
2038
- const clampedPos = clampToPage(pos);
2039
- setCurrent(clampedPos);
2040
- onPreview(getPreview());
1914
+ }
1915
+ if (ctx.changes.rotation !== void 0) {
1916
+ patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
1917
+ }
1918
+ return patch;
1919
+ }
1920
+ default:
1921
+ return ctx.changes;
1922
+ }
1923
+ };
1924
+ const patchSquare = (orig, ctx) => {
1925
+ switch (ctx.type) {
1926
+ case "move":
1927
+ if (!ctx.changes.rect) return ctx.changes;
1928
+ return baseMoveChanges(orig, ctx.changes.rect).rects;
1929
+ case "resize":
1930
+ if (!ctx.changes.rect) return ctx.changes;
1931
+ return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
1932
+ case "rotate":
1933
+ return baseRotateChanges(orig, ctx) ?? ctx.changes;
1934
+ case "property-update": {
1935
+ let patch = ctx.changes;
1936
+ const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
1937
+ const strokeChanged = ctx.changes.strokeWidth !== void 0;
1938
+ const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
1939
+ if (cloudyChanged || strokeChanged && hasCloudy) {
1940
+ const merged = { ...orig, ...ctx.changes };
1941
+ const intensity = merged.cloudyBorderIntensity ?? 0;
1942
+ if (intensity > 0) {
1943
+ const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
1944
+ patch = {
1945
+ ...patch,
1946
+ rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
1947
+ };
1948
+ } else {
1949
+ patch = { ...patch, rectangleDifferences: void 0 };
2041
1950
  }
2042
- },
2043
- onPointerCancel: (_) => {
2044
- setVertices([]);
2045
- setCurrent(null);
2046
- onPreview(null);
2047
1951
  }
2048
- };
1952
+ if (ctx.changes.rotation !== void 0) {
1953
+ patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
1954
+ }
1955
+ return patch;
1956
+ }
1957
+ default:
1958
+ return ctx.changes;
2049
1959
  }
2050
1960
  };
2051
- const squareHandlerFactory = {
2052
- annotationType: PdfAnnotationSubtype.SQUARE,
2053
- create(context) {
2054
- const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
2055
- const [getStart, setStart] = useState(null);
2056
- const clampToPage = (pos) => ({
2057
- x: clamp(pos.x, 0, pageSize.width),
2058
- y: clamp(pos.y, 0, pageSize.height)
2059
- });
2060
- const getDefaults = () => {
2061
- const tool = getTool();
2062
- if (!tool) return null;
1961
+ const patchFreeText = (orig, ctx) => {
1962
+ switch (ctx.type) {
1963
+ case "move":
1964
+ if (!ctx.changes.rect) return ctx.changes;
1965
+ return baseMoveChanges(orig, ctx.changes.rect).rects;
1966
+ case "resize":
1967
+ if (!ctx.changes.rect) return ctx.changes;
1968
+ return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
1969
+ case "rotate":
1970
+ return baseRotateChanges(orig, ctx) ?? ctx.changes;
1971
+ case "property-update":
1972
+ if (ctx.changes.rotation !== void 0) {
1973
+ return { ...ctx.changes, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
1974
+ }
1975
+ return ctx.changes;
1976
+ default:
1977
+ return ctx.changes;
1978
+ }
1979
+ };
1980
+ function rebuildFromVertices(orig, arrowTip, knee, tbTL, tbBR) {
1981
+ const textBox = {
1982
+ origin: { x: Math.min(tbTL.x, tbBR.x), y: Math.min(tbTL.y, tbBR.y) },
1983
+ size: {
1984
+ width: Math.abs(tbBR.x - tbTL.x),
1985
+ height: Math.abs(tbBR.y - tbTL.y)
1986
+ }
1987
+ };
1988
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
1989
+ const calloutLine = [arrowTip, knee, connectionPoint];
1990
+ const overallRect = computeCalloutOverallRect(
1991
+ textBox,
1992
+ calloutLine,
1993
+ orig.lineEnding,
1994
+ orig.strokeWidth ?? 1
1995
+ );
1996
+ return {
1997
+ calloutLine,
1998
+ rect: overallRect,
1999
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
2000
+ };
2001
+ }
2002
+ const patchCalloutFreeText = (orig, ctx) => {
2003
+ var _a, _b;
2004
+ switch (ctx.type) {
2005
+ case "vertex-edit": {
2006
+ if (!ctx.changes.calloutLine) return ctx.changes;
2007
+ const verts = ctx.changes.calloutLine;
2008
+ if (verts.length < 4) return ctx.changes;
2009
+ return rebuildFromVertices(orig, verts[0], verts[1], verts[2], verts[3]);
2010
+ }
2011
+ case "move": {
2012
+ if (!ctx.changes.rect) return ctx.changes;
2013
+ const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
2014
+ const movedLine = (_a = orig.calloutLine) == null ? void 0 : _a.map((p) => ({ x: p.x + dx, y: p.y + dy }));
2063
2015
  return {
2064
- ...tool.defaults,
2065
- flags: tool.defaults.flags ?? ["print"],
2066
- strokeWidth: tool.defaults.strokeWidth ?? 2,
2067
- strokeColor: tool.defaults.strokeColor ?? "#000000",
2068
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2069
- strokeDashArray: tool.defaults.strokeDashArray ?? [],
2070
- color: tool.defaults.color ?? "#000000",
2071
- opacity: tool.defaults.opacity ?? 1
2016
+ ...rects,
2017
+ ...movedLine && { calloutLine: movedLine }
2072
2018
  };
2073
- };
2019
+ }
2020
+ case "rotate": {
2021
+ const result = baseRotateChanges(orig, ctx);
2022
+ if (!result) return ctx.changes;
2023
+ const { dx, dy } = rotateOrbitDelta(orig, result);
2024
+ const movedLine = (_b = orig.calloutLine) == null ? void 0 : _b.map((p) => ({ x: p.x + dx, y: p.y + dy }));
2025
+ return {
2026
+ ...result,
2027
+ ...movedLine && { calloutLine: movedLine }
2028
+ };
2029
+ }
2030
+ case "property-update": {
2031
+ if (ctx.changes.lineEnding !== void 0 || ctx.changes.strokeWidth !== void 0) {
2032
+ const merged = { ...orig, ...ctx.changes };
2033
+ if (merged.calloutLine && merged.calloutLine.length >= 3) {
2034
+ const textBox = computeTextBoxFromRD(orig.rect, orig.rectangleDifferences);
2035
+ const overallRect = computeCalloutOverallRect(
2036
+ textBox,
2037
+ merged.calloutLine,
2038
+ merged.lineEnding,
2039
+ merged.strokeWidth ?? 1
2040
+ );
2041
+ return {
2042
+ ...ctx.changes,
2043
+ rect: overallRect,
2044
+ rectangleDifferences: computeRDFromTextBox(overallRect, textBox)
2045
+ };
2046
+ }
2047
+ }
2048
+ return ctx.changes;
2049
+ }
2050
+ default:
2051
+ return ctx.changes;
2052
+ }
2053
+ };
2054
+ const patchStamp = (orig, ctx) => {
2055
+ switch (ctx.type) {
2056
+ case "move":
2057
+ if (!ctx.changes.rect) return ctx.changes;
2058
+ return baseMoveChanges(orig, ctx.changes.rect).rects;
2059
+ case "resize":
2060
+ if (!ctx.changes.rect) return ctx.changes;
2061
+ return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
2062
+ case "rotate":
2063
+ return baseRotateChanges(orig, ctx) ?? ctx.changes;
2064
+ case "property-update":
2065
+ if (ctx.changes.rotation !== void 0) {
2066
+ return { ...ctx.changes, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
2067
+ }
2068
+ return ctx.changes;
2069
+ default:
2070
+ return ctx.changes;
2071
+ }
2072
+ };
2073
+ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2074
+ __proto__: null,
2075
+ LINE_ENDING_HANDLERS,
2076
+ PatchRegistry,
2077
+ applyInsertUpright,
2078
+ calculateAABBFromVertices,
2079
+ calculateRotatedRectAABB,
2080
+ calculateRotatedRectAABBAroundPoint,
2081
+ calloutVertexConfig,
2082
+ clampAnnotationToPage,
2083
+ compensateRotatedVertexEdit,
2084
+ computeCalloutConnectionPoint,
2085
+ computeCalloutOverallRect,
2086
+ computeRDFromTextBox,
2087
+ computeTextBoxFromRD,
2088
+ createEnding,
2089
+ getRectCenter,
2090
+ lineRectWithEndings,
2091
+ patchCalloutFreeText,
2092
+ patchCircle,
2093
+ patchFreeText,
2094
+ patchInk,
2095
+ patchLine,
2096
+ patchPolygon,
2097
+ patchPolyline,
2098
+ patchRegistry,
2099
+ patchSquare,
2100
+ patchStamp,
2101
+ resolveAnnotationRotationCenter,
2102
+ resolveRotateRects,
2103
+ resolveVertexEditRects,
2104
+ rotatePointAroundCenter: rotatePointAround,
2105
+ rotateVertices
2106
+ }, Symbol.toStringTag, { value: "Module" }));
2107
+ const COMMENT_SIZE = 24;
2108
+ const textHandlerFactory = {
2109
+ annotationType: PdfAnnotationSubtype.TEXT,
2110
+ create(context) {
2111
+ const { onCommit, getTool, pageSize } = context;
2112
+ return {
2113
+ onPointerDown: (pos) => {
2114
+ const tool = getTool();
2115
+ if (!tool) return;
2116
+ const rect = {
2117
+ origin: { x: pos.x - COMMENT_SIZE / 2, y: pos.y - COMMENT_SIZE / 2 },
2118
+ size: { width: COMMENT_SIZE, height: COMMENT_SIZE }
2119
+ };
2120
+ let anno = {
2121
+ ...tool.defaults,
2122
+ rect,
2123
+ type: PdfAnnotationSubtype.TEXT,
2124
+ name: tool.defaults.name ?? PdfAnnotationName.Comment,
2125
+ contents: tool.defaults.contents ?? "",
2126
+ flags: tool.defaults.flags ?? ["print", "noRotate", "noZoom"],
2127
+ pageIndex: context.pageIndex,
2128
+ id: uuidV4(),
2129
+ created: /* @__PURE__ */ new Date()
2130
+ };
2131
+ anno = clampAnnotationToPage(anno, pageSize);
2132
+ onCommit(anno);
2133
+ }
2134
+ };
2135
+ }
2136
+ };
2137
+ function useClickDetector({
2138
+ threshold = 5,
2139
+ getTool,
2140
+ onClickDetected
2141
+ }) {
2142
+ const [getStartPos, setStartPos] = useState(null);
2143
+ const [getHasMoved, setHasMoved] = useState(false);
2144
+ return {
2145
+ onStart: (pos) => {
2146
+ setStartPos(pos);
2147
+ setHasMoved(false);
2148
+ },
2149
+ onMove: (pos) => {
2150
+ const start = getStartPos();
2151
+ if (!start || getHasMoved()) return;
2152
+ const distance2 = Math.sqrt(Math.pow(pos.x - start.x, 2) + Math.pow(pos.y - start.y, 2));
2153
+ if (distance2 > threshold) {
2154
+ setHasMoved(true);
2155
+ }
2156
+ },
2157
+ onEnd: (pos) => {
2158
+ var _a;
2159
+ const start = getStartPos();
2160
+ if (start && !getHasMoved()) {
2161
+ const tool = getTool();
2162
+ if (tool && "clickBehavior" in tool && ((_a = tool.clickBehavior) == null ? void 0 : _a.enabled)) {
2163
+ onClickDetected(pos, tool);
2164
+ }
2165
+ }
2166
+ setStartPos(null);
2167
+ setHasMoved(false);
2168
+ },
2169
+ hasMoved: getHasMoved,
2170
+ reset: () => {
2171
+ setStartPos(null);
2172
+ setHasMoved(false);
2173
+ }
2174
+ };
2175
+ }
2176
+ const freeTextHandlerFactory = {
2177
+ annotationType: PdfAnnotationSubtype.FREETEXT,
2178
+ create(context) {
2179
+ const { onCommit, onPreview, getTool, pageSize, pageIndex, pageRotation } = context;
2180
+ const [getStart, setStart] = useState(null);
2181
+ const clampToPage = (pos) => ({
2182
+ x: clamp(pos.x, 0, pageSize.width),
2183
+ y: clamp(pos.y, 0, pageSize.height)
2184
+ });
2185
+ const getDefaults = () => {
2186
+ const tool = getTool();
2187
+ if (!tool) return null;
2188
+ return {
2189
+ ...tool.defaults,
2190
+ fontColor: tool.defaults.fontColor ?? "#000000",
2191
+ opacity: tool.defaults.opacity ?? 1,
2192
+ fontSize: tool.defaults.fontSize ?? 12,
2193
+ fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
2194
+ color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
2195
+ textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
2196
+ verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
2197
+ contents: tool.defaults.contents ?? "Insert text here",
2198
+ flags: tool.defaults.flags ?? ["print"]
2199
+ };
2200
+ };
2074
2201
  const clickDetector = useClickDetector({
2075
2202
  threshold: 5,
2076
2203
  getTool,
2077
2204
  onClickDetected: (pos, tool) => {
2205
+ var _a;
2078
2206
  const defaults = getDefaults();
2079
2207
  if (!defaults) return;
2080
2208
  const clickConfig = tool.clickBehavior;
2081
2209
  if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
2082
2210
  const { width, height } = clickConfig.defaultSize;
2083
- const halfWidth = width / 2;
2084
- const halfHeight = height / 2;
2085
- const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
2086
- const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
2087
- const strokeWidth = defaults.strokeWidth;
2088
- const intensity = defaults.cloudyBorderIntensity ?? 0;
2089
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
2090
2211
  const rect = {
2091
- origin: { x: x - pad, y: y - pad },
2092
- size: { width: width + 2 * pad, height: height + 2 * pad }
2212
+ origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
2213
+ size: { width, height }
2093
2214
  };
2094
- const anno = {
2215
+ const contents = clickConfig.defaultContent ?? defaults.contents;
2216
+ let anno = {
2095
2217
  ...defaults,
2096
- type: PdfAnnotationSubtype.SQUARE,
2097
- created: /* @__PURE__ */ new Date(),
2098
- id: uuidV4(),
2099
- pageIndex,
2218
+ contents,
2219
+ type: PdfAnnotationSubtype.FREETEXT,
2100
2220
  rect,
2101
- ...intensity > 0 && {
2102
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2103
- }
2221
+ pageIndex,
2222
+ id: uuidV4(),
2223
+ created: /* @__PURE__ */ new Date()
2104
2224
  };
2225
+ if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
2226
+ anno = applyInsertUpright(anno, pageRotation, false);
2227
+ }
2228
+ anno = clampAnnotationToPage(anno, pageSize);
2105
2229
  onCommit(anno);
2106
2230
  }
2107
2231
  });
2108
2232
  const getPreview = (current) => {
2109
- const p1 = getStart();
2110
- if (!p1) return null;
2111
- const minX = Math.min(p1.x, current.x);
2112
- const minY = Math.min(p1.y, current.y);
2113
- const width = Math.abs(p1.x - current.x);
2114
- const height = Math.abs(p1.y - current.y);
2233
+ const start = getStart();
2234
+ if (!start) return null;
2115
2235
  const defaults = getDefaults();
2116
2236
  if (!defaults) return null;
2117
- const strokeWidth = defaults.strokeWidth;
2118
- const intensity = defaults.cloudyBorderIntensity ?? 0;
2119
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
2237
+ const minX = Math.min(start.x, current.x);
2238
+ const minY = Math.min(start.y, current.y);
2239
+ const width = Math.abs(start.x - current.x);
2240
+ const height = Math.abs(start.y - current.y);
2120
2241
  const rect = {
2121
- origin: { x: minX - pad, y: minY - pad },
2122
- size: { width: width + 2 * pad, height: height + 2 * pad }
2242
+ origin: { x: minX, y: minY },
2243
+ size: { width, height }
2123
2244
  };
2124
2245
  return {
2125
- type: PdfAnnotationSubtype.SQUARE,
2246
+ type: PdfAnnotationSubtype.FREETEXT,
2126
2247
  bounds: rect,
2127
2248
  data: {
2128
- rect,
2129
2249
  ...defaults,
2130
- ...intensity > 0 && {
2131
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2132
- }
2250
+ rect
2133
2251
  }
2134
2252
  };
2135
2253
  };
@@ -2150,39 +2268,41 @@ const squareHandlerFactory = {
2150
2268
  }
2151
2269
  },
2152
2270
  onPointerUp: (pos, evt) => {
2153
- var _a;
2154
- const p1 = getStart();
2155
- if (!p1) return;
2271
+ var _a, _b;
2272
+ const start = getStart();
2273
+ if (!start) return;
2156
2274
  const defaults = getDefaults();
2157
2275
  if (!defaults) return;
2158
2276
  const clampedPos = clampToPage(pos);
2159
2277
  if (!clickDetector.hasMoved()) {
2160
2278
  clickDetector.onEnd(clampedPos);
2161
2279
  } else {
2162
- const defaults2 = getDefaults();
2163
- if (!defaults2) return;
2164
- const preview = getPreview(clampedPos);
2165
- if (preview) {
2166
- const intensity = defaults2.cloudyBorderIntensity ?? 0;
2167
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, false) : void 0;
2168
- const anno = {
2169
- ...defaults2,
2170
- type: PdfAnnotationSubtype.SQUARE,
2171
- created: /* @__PURE__ */ new Date(),
2172
- id: uuidV4(),
2173
- pageIndex,
2174
- rect: preview.data.rect,
2175
- ...pad !== void 0 && {
2176
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2177
- }
2178
- };
2179
- onCommit(anno);
2280
+ const minX = Math.min(start.x, clampedPos.x);
2281
+ const minY = Math.min(start.y, clampedPos.y);
2282
+ const width = Math.abs(start.x - clampedPos.x);
2283
+ const height = Math.abs(start.y - clampedPos.y);
2284
+ const rect = {
2285
+ origin: { x: minX, y: minY },
2286
+ size: { width, height }
2287
+ };
2288
+ const tool = getTool();
2289
+ let anno = {
2290
+ ...defaults,
2291
+ type: PdfAnnotationSubtype.FREETEXT,
2292
+ rect,
2293
+ pageIndex: context.pageIndex,
2294
+ id: uuidV4(),
2295
+ created: /* @__PURE__ */ new Date()
2296
+ };
2297
+ if ((_a = tool == null ? void 0 : tool.behavior) == null ? void 0 : _a.insertUpright) {
2298
+ anno = applyInsertUpright(anno, pageRotation, true);
2180
2299
  }
2300
+ onCommit(anno);
2181
2301
  }
2182
2302
  setStart(null);
2183
2303
  onPreview(null);
2184
2304
  clickDetector.reset();
2185
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2305
+ (_b = evt.releasePointerCapture) == null ? void 0 : _b.call(evt);
2186
2306
  },
2187
2307
  onPointerLeave: (_, evt) => {
2188
2308
  var _a;
@@ -2201,131 +2321,234 @@ const squareHandlerFactory = {
2201
2321
  };
2202
2322
  }
2203
2323
  };
2204
- const imageFetchCache = /* @__PURE__ */ new Map();
2205
- const stampHandlerFactory = {
2206
- annotationType: PdfAnnotationSubtype.STAMP,
2324
+ const CLICK_THRESHOLD = 5;
2325
+ const DEFAULT_TB_WIDTH = 150;
2326
+ const DEFAULT_TB_HEIGHT = 40;
2327
+ const calloutFreeTextHandlerFactory = {
2328
+ annotationType: PdfAnnotationSubtype.FREETEXT,
2207
2329
  create(context) {
2208
- const { services, onCommit, onPreview, getTool, pageSize, pageRotation } = context;
2209
- let cachedBuffer = null;
2210
- let cachedSize = null;
2211
- const commitStamp = (pos, width, height, ctx) => {
2212
- var _a;
2330
+ const { onCommit, onPreview, getTool, pageSize, pageIndex } = context;
2331
+ const [getPhase, setPhase] = useState("arrow");
2332
+ const [getArrowTip, setArrowTip] = useState(null);
2333
+ const [getKnee, setKnee] = useState(null);
2334
+ const [getDownPos, setDownPos] = useState(null);
2335
+ const [getTextBoxStart, setTextBoxStart] = useState(null);
2336
+ const [getDragging, setDragging] = useState(false);
2337
+ const clampToPage = (pos) => ({
2338
+ x: clamp(pos.x, 0, pageSize.width),
2339
+ y: clamp(pos.y, 0, pageSize.height)
2340
+ });
2341
+ const clampTextBox = (tb) => ({
2342
+ origin: {
2343
+ x: clamp(tb.origin.x, 0, pageSize.width - tb.size.width),
2344
+ y: clamp(tb.origin.y, 0, pageSize.height - tb.size.height)
2345
+ },
2346
+ size: tb.size
2347
+ });
2348
+ const getDefaults = () => {
2213
2349
  const tool = getTool();
2214
- if (!tool) return;
2215
- const rect = {
2216
- origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
2217
- size: { width, height }
2218
- };
2219
- let anno = {
2350
+ if (!tool) return null;
2351
+ return {
2220
2352
  ...tool.defaults,
2221
- rect,
2222
- type: PdfAnnotationSubtype.STAMP,
2223
- name: tool.defaults.name ?? PdfAnnotationName.Image,
2224
- subject: tool.defaults.subject ?? "Stamp",
2353
+ fontColor: tool.defaults.fontColor ?? "#000000",
2354
+ opacity: tool.defaults.opacity ?? 1,
2355
+ fontSize: tool.defaults.fontSize ?? 12,
2356
+ fontFamily: tool.defaults.fontFamily ?? PdfStandardFont.Helvetica,
2357
+ color: tool.defaults.color ?? tool.defaults.backgroundColor ?? "transparent",
2358
+ textAlign: tool.defaults.textAlign ?? PdfTextAlignment.Left,
2359
+ verticalAlign: tool.defaults.verticalAlign ?? PdfVerticalAlignment.Top,
2360
+ contents: tool.defaults.contents ?? "Insert text",
2225
2361
  flags: tool.defaults.flags ?? ["print"],
2226
- pageIndex: context.pageIndex,
2362
+ lineEnding: tool.defaults.lineEnding ?? PdfAnnotationLineEnding.OpenArrow,
2363
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2364
+ strokeWidth: tool.defaults.strokeWidth ?? 1
2365
+ };
2366
+ };
2367
+ const isClick = (a, b) => Math.abs(a.x - b.x) < CLICK_THRESHOLD && Math.abs(a.y - b.y) < CLICK_THRESHOLD;
2368
+ const buildPreview = (cursor) => {
2369
+ const defaults = getDefaults();
2370
+ if (!defaults) return null;
2371
+ const arrowTip = getArrowTip();
2372
+ const knee = getKnee();
2373
+ const phase = getPhase();
2374
+ if (phase === "knee" && arrowTip) {
2375
+ const calloutLine = [arrowTip, cursor];
2376
+ const minX = Math.min(arrowTip.x, cursor.x);
2377
+ const minY = Math.min(arrowTip.y, cursor.y);
2378
+ const w = Math.abs(arrowTip.x - cursor.x);
2379
+ const h = Math.abs(arrowTip.y - cursor.y);
2380
+ const bounds = {
2381
+ origin: { x: minX, y: minY },
2382
+ size: { width: Math.max(w, 1), height: Math.max(h, 1) }
2383
+ };
2384
+ return {
2385
+ type: PdfAnnotationSubtype.FREETEXT,
2386
+ bounds,
2387
+ data: {
2388
+ ...defaults,
2389
+ rect: bounds,
2390
+ calloutLine
2391
+ }
2392
+ };
2393
+ }
2394
+ if (phase === "textbox" && arrowTip && knee) {
2395
+ const tbStart = getTextBoxStart();
2396
+ let textBox;
2397
+ if (getDragging() && tbStart) {
2398
+ textBox = {
2399
+ origin: { x: Math.min(tbStart.x, cursor.x), y: Math.min(tbStart.y, cursor.y) },
2400
+ size: {
2401
+ width: Math.max(Math.abs(cursor.x - tbStart.x), 20),
2402
+ height: Math.max(Math.abs(cursor.y - tbStart.y), 14)
2403
+ }
2404
+ };
2405
+ } else {
2406
+ textBox = {
2407
+ origin: { x: cursor.x - DEFAULT_TB_WIDTH / 2, y: cursor.y - DEFAULT_TB_HEIGHT / 2 },
2408
+ size: { width: DEFAULT_TB_WIDTH, height: DEFAULT_TB_HEIGHT }
2409
+ };
2410
+ }
2411
+ textBox = clampTextBox(textBox);
2412
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
2413
+ const calloutLine = [arrowTip, knee, connectionPoint];
2414
+ const overallRect = computeCalloutOverallRect(
2415
+ textBox,
2416
+ calloutLine,
2417
+ defaults.lineEnding,
2418
+ defaults.strokeWidth
2419
+ );
2420
+ return {
2421
+ type: PdfAnnotationSubtype.FREETEXT,
2422
+ bounds: overallRect,
2423
+ data: {
2424
+ ...defaults,
2425
+ rect: overallRect,
2426
+ calloutLine,
2427
+ textBox
2428
+ }
2429
+ };
2430
+ }
2431
+ return null;
2432
+ };
2433
+ const commitCallout = (tb) => {
2434
+ const defaults = getDefaults();
2435
+ const arrowTip = getArrowTip();
2436
+ const knee = getKnee();
2437
+ if (!defaults || !arrowTip || !knee) return;
2438
+ const textBox = clampTextBox(tb);
2439
+ const connectionPoint = computeCalloutConnectionPoint(knee, textBox);
2440
+ const calloutLine = [arrowTip, knee, connectionPoint];
2441
+ const overallRect = computeCalloutOverallRect(
2442
+ textBox,
2443
+ calloutLine,
2444
+ defaults.lineEnding,
2445
+ defaults.strokeWidth
2446
+ );
2447
+ const rd = computeRDFromTextBox(overallRect, textBox);
2448
+ const anno = {
2449
+ ...defaults,
2450
+ type: PdfAnnotationSubtype.FREETEXT,
2451
+ intent: "FreeTextCallout",
2452
+ rect: overallRect,
2453
+ rectangleDifferences: rd,
2454
+ calloutLine,
2455
+ pageIndex,
2227
2456
  id: uuidV4(),
2228
2457
  created: /* @__PURE__ */ new Date()
2229
2458
  };
2230
- if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
2231
- anno = applyInsertUpright(anno, pageRotation, false);
2232
- }
2233
- anno = clampAnnotationToPage(anno, pageSize);
2234
- onCommit(anno, ctx);
2459
+ onCommit(anno);
2460
+ resetState();
2235
2461
  };
2236
- const commitFromBuffer = (pos, buffer, imageSize) => {
2237
- const meta = getImageMetadata(buffer);
2238
- if (!meta || meta.mimeType === "application/pdf") return false;
2239
- const fitted = fitSizeWithin(meta, pageSize);
2240
- const width = (imageSize == null ? void 0 : imageSize.width) ?? fitted.width;
2241
- const height = (imageSize == null ? void 0 : imageSize.height) ?? fitted.height;
2242
- commitStamp(pos, width, height, { data: buffer });
2243
- return true;
2462
+ const resetState = () => {
2463
+ setPhase("arrow");
2464
+ setArrowTip(null);
2465
+ setKnee(null);
2466
+ setDownPos(null);
2467
+ setTextBoxStart(null);
2468
+ setDragging(false);
2469
+ onPreview(null);
2244
2470
  };
2245
2471
  return {
2246
- onHandlerActiveStart: () => {
2247
- const tool = getTool();
2248
- const imageSrc = tool == null ? void 0 : tool.defaults.imageSrc;
2249
- if (!imageSrc) return;
2250
- let entry = imageFetchCache.get(imageSrc);
2251
- if (!entry) {
2252
- const promise = fetch(imageSrc).then((res) => res.arrayBuffer()).then((buffer) => {
2253
- const meta = getImageMetadata(buffer);
2254
- if (!meta || meta.mimeType === "application/pdf") return null;
2255
- const fitted = fitSizeWithin(meta, pageSize);
2256
- const imageSize = tool.defaults.imageSize;
2257
- return {
2258
- buffer,
2259
- width: (imageSize == null ? void 0 : imageSize.width) ?? fitted.width,
2260
- height: (imageSize == null ? void 0 : imageSize.height) ?? fitted.height
2261
- };
2262
- }).catch(() => null);
2263
- entry = { promise, refs: 1 };
2264
- imageFetchCache.set(imageSrc, entry);
2265
- } else {
2266
- entry.refs++;
2267
- }
2268
- entry.promise.then((result) => {
2269
- if (!result) return;
2270
- cachedBuffer = result.buffer;
2271
- cachedSize = { width: result.width, height: result.height };
2272
- });
2273
- },
2274
- onHandlerActiveEnd: () => {
2275
- const tool = getTool();
2276
- const imageSrc = tool == null ? void 0 : tool.defaults.imageSrc;
2277
- if (imageSrc) {
2278
- const entry = imageFetchCache.get(imageSrc);
2279
- if (entry && --entry.refs <= 0) {
2280
- imageFetchCache.delete(imageSrc);
2281
- }
2472
+ onPointerDown: (pos, evt) => {
2473
+ var _a, _b;
2474
+ const clampedPos = clampToPage(pos);
2475
+ const phase = getPhase();
2476
+ if (phase === "arrow" || phase === "knee") {
2477
+ setDownPos(clampedPos);
2478
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
2479
+ } else if (phase === "textbox") {
2480
+ setTextBoxStart(clampedPos);
2481
+ setDragging(true);
2482
+ (_b = evt.setPointerCapture) == null ? void 0 : _b.call(evt);
2282
2483
  }
2283
- cachedBuffer = null;
2284
- cachedSize = null;
2285
- onPreview(null);
2286
2484
  },
2287
2485
  onPointerMove: (pos) => {
2288
- var _a;
2289
- const tool = getTool();
2290
- if (!((_a = tool == null ? void 0 : tool.behavior) == null ? void 0 : _a.showGhost) || !cachedSize || !tool.defaults.imageSrc) return;
2291
- const rect = {
2292
- origin: { x: pos.x - cachedSize.width / 2, y: pos.y - cachedSize.height / 2 },
2293
- size: cachedSize
2294
- };
2295
- onPreview({
2296
- type: PdfAnnotationSubtype.STAMP,
2297
- bounds: rect,
2298
- data: { rect, ghostUrl: tool.defaults.imageSrc, pageRotation }
2299
- });
2486
+ const clampedPos = clampToPage(pos);
2487
+ const phase = getPhase();
2488
+ if (phase === "textbox" && getDragging()) {
2489
+ onPreview(buildPreview(clampedPos));
2490
+ } else if (phase === "knee" || phase === "textbox") {
2491
+ onPreview(buildPreview(clampedPos));
2492
+ }
2300
2493
  },
2301
- onPointerDown: (pos) => {
2302
- const tool = getTool();
2303
- if (!tool) return;
2304
- const { imageSrc, imageSize } = tool.defaults;
2305
- if (imageSrc) {
2306
- onPreview(null);
2307
- if (cachedBuffer) {
2308
- commitFromBuffer(pos, cachedBuffer, imageSize);
2309
- } else {
2310
- fetch(imageSrc).then((res) => res.arrayBuffer()).then((buffer) => commitFromBuffer(pos, buffer, imageSize));
2311
- }
2312
- } else {
2313
- services.requestFile({
2314
- accept: "image/png,image/jpeg",
2315
- onFile: (file) => {
2316
- file.arrayBuffer().then((buffer) => commitFromBuffer(pos, buffer));
2494
+ onPointerUp: (pos, evt) => {
2495
+ var _a, _b, _c, _d;
2496
+ const clampedPos = clampToPage(pos);
2497
+ const phase = getPhase();
2498
+ const downPos = getDownPos();
2499
+ if (phase === "arrow" && downPos && isClick(downPos, clampedPos)) {
2500
+ setArrowTip(clampedPos);
2501
+ setPhase("knee");
2502
+ setDownPos(null);
2503
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2504
+ return;
2505
+ }
2506
+ if (phase === "knee" && downPos && isClick(downPos, clampedPos)) {
2507
+ setKnee(clampedPos);
2508
+ setPhase("textbox");
2509
+ setDownPos(null);
2510
+ (_b = evt.releasePointerCapture) == null ? void 0 : _b.call(evt);
2511
+ return;
2512
+ }
2513
+ if (phase === "textbox" && getDragging()) {
2514
+ const tbStart = getTextBoxStart();
2515
+ if (tbStart) {
2516
+ const minX = Math.min(tbStart.x, clampedPos.x);
2517
+ const minY = Math.min(tbStart.y, clampedPos.y);
2518
+ const w = Math.abs(clampedPos.x - tbStart.x);
2519
+ const h = Math.abs(clampedPos.y - tbStart.y);
2520
+ if (w > 5 || h > 5) {
2521
+ const textBox = {
2522
+ origin: { x: minX, y: minY },
2523
+ size: { width: Math.max(w, 20), height: Math.max(h, 14) }
2524
+ };
2525
+ commitCallout(textBox);
2526
+ } else {
2527
+ commitCallout({
2528
+ origin: {
2529
+ x: tbStart.x - DEFAULT_TB_WIDTH / 2,
2530
+ y: tbStart.y - DEFAULT_TB_HEIGHT / 2
2531
+ },
2532
+ size: { width: DEFAULT_TB_WIDTH, height: DEFAULT_TB_HEIGHT }
2533
+ });
2317
2534
  }
2318
- });
2535
+ }
2536
+ (_c = evt.releasePointerCapture) == null ? void 0 : _c.call(evt);
2537
+ return;
2319
2538
  }
2539
+ setDownPos(null);
2540
+ (_d = evt.releasePointerCapture) == null ? void 0 : _d.call(evt);
2320
2541
  },
2321
- onPointerLeave: () => {
2322
- onPreview(null);
2542
+ onPointerCancel: (_, evt) => {
2543
+ var _a;
2544
+ resetState();
2545
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2323
2546
  }
2324
2547
  };
2325
2548
  }
2326
2549
  };
2327
- const circleHandlerFactory = {
2328
- annotationType: PdfAnnotationSubtype.CIRCLE,
2550
+ const lineHandlerFactory = {
2551
+ annotationType: PdfAnnotationSubtype.LINE,
2329
2552
  create(context) {
2330
2553
  const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
2331
2554
  const [getStart, setStart] = useState(null);
@@ -2338,12 +2561,16 @@ const circleHandlerFactory = {
2338
2561
  if (!tool) return null;
2339
2562
  return {
2340
2563
  ...tool.defaults,
2341
- strokeWidth: tool.defaults.strokeWidth ?? 2,
2342
- strokeColor: tool.defaults.strokeColor ?? "#000000",
2343
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2344
- strokeDashArray: tool.defaults.strokeDashArray ?? [],
2564
+ strokeWidth: tool.defaults.strokeWidth ?? 1,
2565
+ lineEndings: tool.defaults.lineEndings ?? {
2566
+ start: PdfAnnotationLineEnding.None,
2567
+ end: PdfAnnotationLineEnding.None
2568
+ },
2345
2569
  color: tool.defaults.color ?? "#000000",
2346
2570
  opacity: tool.defaults.opacity ?? 1,
2571
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2572
+ strokeDashArray: tool.defaults.strokeDashArray ?? [],
2573
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2347
2574
  flags: tool.defaults.flags ?? ["print"]
2348
2575
  };
2349
2576
  };
@@ -2355,57 +2582,48 @@ const circleHandlerFactory = {
2355
2582
  if (!defaults) return;
2356
2583
  const clickConfig = tool.clickBehavior;
2357
2584
  if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
2358
- const { width, height } = clickConfig.defaultSize;
2359
- const halfWidth = width / 2;
2360
- const halfHeight = height / 2;
2361
- const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
2362
- const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
2363
- const strokeWidth = defaults.strokeWidth;
2364
- const intensity = defaults.cloudyBorderIntensity ?? 0;
2365
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
2366
- const rect = {
2367
- origin: { x: x - pad, y: y - pad },
2368
- size: { width: width + 2 * pad, height: height + 2 * pad }
2369
- };
2370
- const anno = {
2585
+ const angle = clickConfig.defaultAngle ?? 0;
2586
+ const length = clickConfig.defaultLength;
2587
+ const halfLength = length / 2;
2588
+ const startX = pos.x - halfLength * Math.cos(angle);
2589
+ const startY = pos.y - halfLength * Math.sin(angle);
2590
+ const endX = pos.x + halfLength * Math.cos(angle);
2591
+ const endY = pos.y + halfLength * Math.sin(angle);
2592
+ const start = clampToPage({ x: startX, y: startY });
2593
+ const end = clampToPage({ x: endX, y: endY });
2594
+ const rect = lineRectWithEndings(
2595
+ [start, end],
2596
+ defaults.strokeWidth,
2597
+ defaults.lineEndings
2598
+ );
2599
+ onCommit({
2371
2600
  ...defaults,
2372
- type: PdfAnnotationSubtype.CIRCLE,
2373
- created: /* @__PURE__ */ new Date(),
2374
- id: uuidV4(),
2375
- pageIndex,
2376
2601
  rect,
2377
- ...intensity > 0 && {
2378
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2379
- }
2380
- };
2381
- onCommit(anno);
2602
+ linePoints: { start, end },
2603
+ pageIndex,
2604
+ id: uuidV4(),
2605
+ created: /* @__PURE__ */ new Date(),
2606
+ type: PdfAnnotationSubtype.LINE
2607
+ });
2382
2608
  }
2383
2609
  });
2384
2610
  const getPreview = (current) => {
2385
- const p1 = getStart();
2386
- if (!p1) return null;
2387
- const minX = Math.min(p1.x, current.x);
2388
- const minY = Math.min(p1.y, current.y);
2389
- const width = Math.abs(p1.x - current.x);
2390
- const height = Math.abs(p1.y - current.y);
2611
+ const start = getStart();
2612
+ if (!start) return null;
2391
2613
  const defaults = getDefaults();
2392
2614
  if (!defaults) return null;
2393
- const strokeWidth = defaults.strokeWidth;
2394
- const intensity = defaults.cloudyBorderIntensity ?? 0;
2395
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
2396
- const rect = {
2397
- origin: { x: minX - pad, y: minY - pad },
2398
- size: { width: width + 2 * pad, height: height + 2 * pad }
2399
- };
2615
+ const bounds = lineRectWithEndings(
2616
+ [start, current],
2617
+ defaults.strokeWidth,
2618
+ defaults.lineEndings
2619
+ );
2400
2620
  return {
2401
- type: PdfAnnotationSubtype.CIRCLE,
2402
- bounds: rect,
2621
+ type: PdfAnnotationSubtype.LINE,
2622
+ bounds,
2403
2623
  data: {
2404
- rect,
2405
2624
  ...defaults,
2406
- ...intensity > 0 && {
2407
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2408
- }
2625
+ rect: bounds,
2626
+ linePoints: { start, end: current }
2409
2627
  }
2410
2628
  };
2411
2629
  };
@@ -2427,33 +2645,30 @@ const circleHandlerFactory = {
2427
2645
  },
2428
2646
  onPointerUp: (pos, evt) => {
2429
2647
  var _a;
2430
- const p1 = getStart();
2431
- if (!p1) return;
2432
- const defaults = getDefaults();
2433
- if (!defaults) return;
2648
+ const start = getStart();
2649
+ if (!start) return;
2434
2650
  const clampedPos = clampToPage(pos);
2435
2651
  if (!clickDetector.hasMoved()) {
2436
2652
  clickDetector.onEnd(clampedPos);
2437
2653
  } else {
2438
- const defaults2 = getDefaults();
2439
- if (!defaults2) return;
2440
- const preview = getPreview(clampedPos);
2441
- if (preview) {
2442
- const intensity = defaults2.cloudyBorderIntensity ?? 0;
2443
- const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, true) : void 0;
2444
- const anno = {
2445
- ...defaults2,
2446
- type: PdfAnnotationSubtype.CIRCLE,
2654
+ const defaults = getDefaults();
2655
+ if (!defaults) return;
2656
+ if (Math.abs(clampedPos.x - start.x) > 2 || Math.abs(clampedPos.y - start.y) > 2) {
2657
+ const rect = lineRectWithEndings(
2658
+ [start, clampedPos],
2659
+ defaults.strokeWidth,
2660
+ defaults.lineEndings
2661
+ );
2662
+ onCommit({
2663
+ ...defaults,
2664
+ rect,
2665
+ linePoints: { start, end: clampedPos },
2666
+ pageIndex,
2667
+ id: uuidV4(),
2447
2668
  flags: ["print"],
2448
2669
  created: /* @__PURE__ */ new Date(),
2449
- id: uuidV4(),
2450
- pageIndex,
2451
- rect: preview.data.rect,
2452
- ...pad !== void 0 && {
2453
- rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2454
- }
2455
- };
2456
- onCommit(anno);
2670
+ type: PdfAnnotationSubtype.LINE
2671
+ });
2457
2672
  }
2458
2673
  }
2459
2674
  setStart(null);
@@ -2478,11 +2693,12 @@ const circleHandlerFactory = {
2478
2693
  };
2479
2694
  }
2480
2695
  };
2481
- const linkHandlerFactory = {
2482
- annotationType: PdfAnnotationSubtype.LINK,
2696
+ const polylineHandlerFactory = {
2697
+ annotationType: PdfAnnotationSubtype.POLYLINE,
2483
2698
  create(context) {
2484
- const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
2485
- const [getStart, setStart] = useState(null);
2699
+ const { onCommit, onPreview, getTool, pageSize } = context;
2700
+ const [getVertices, setVertices] = useState([]);
2701
+ const [getCurrent, setCurrent] = useState(null);
2486
2702
  const clampToPage = (pos) => ({
2487
2703
  x: clamp(pos.x, 0, pageSize.width),
2488
2704
  y: clamp(pos.y, 0, pageSize.height)
@@ -2492,708 +2708,909 @@ const linkHandlerFactory = {
2492
2708
  if (!tool) return null;
2493
2709
  return {
2494
2710
  ...tool.defaults,
2495
- flags: tool.defaults.flags ?? ["print"],
2496
- strokeWidth: tool.defaults.strokeWidth ?? 2,
2497
- strokeColor: tool.defaults.strokeColor ?? "#0000FF",
2498
- strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.UNDERLINE,
2499
- strokeDashArray: tool.defaults.strokeDashArray ?? []
2711
+ strokeWidth: tool.defaults.strokeWidth ?? 1,
2712
+ lineEndings: tool.defaults.lineEndings ?? {
2713
+ start: PdfAnnotationLineEnding.None,
2714
+ end: PdfAnnotationLineEnding.None
2715
+ },
2716
+ color: tool.defaults.color ?? "#000000",
2717
+ opacity: tool.defaults.opacity ?? 1,
2718
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2719
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2720
+ strokeDashArray: tool.defaults.strokeDashArray ?? [],
2721
+ flags: tool.defaults.flags ?? ["print"]
2500
2722
  };
2501
2723
  };
2502
- const clickDetector = useClickDetector({
2503
- threshold: 5,
2504
- getTool,
2505
- onClickDetected: (pos, tool) => {
2506
- const defaults = getDefaults();
2507
- if (!defaults) return;
2508
- const clickConfig = tool.clickBehavior;
2509
- if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
2510
- const { width, height } = clickConfig.defaultSize;
2511
- const halfWidth = width / 2;
2512
- const halfHeight = height / 2;
2513
- const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
2514
- const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
2515
- const rect = {
2516
- origin: { x, y },
2517
- size: { width, height }
2518
- };
2519
- const anno = {
2520
- ...defaults,
2521
- type: PdfAnnotationSubtype.LINK,
2522
- target: void 0,
2523
- created: /* @__PURE__ */ new Date(),
2524
- id: uuidV4(),
2525
- pageIndex,
2526
- rect
2527
- };
2528
- onCommit(anno);
2529
- }
2530
- });
2531
- const getPreview = (current) => {
2532
- const p1 = getStart();
2533
- if (!p1) return null;
2534
- const minX = Math.min(p1.x, current.x);
2535
- const minY = Math.min(p1.y, current.y);
2536
- const width = Math.abs(p1.x - current.x);
2537
- const height = Math.abs(p1.y - current.y);
2724
+ const commitPolyline = () => {
2725
+ const vertices = getVertices();
2726
+ if (vertices.length < 2) return;
2538
2727
  const defaults = getDefaults();
2539
- if (!defaults) return null;
2540
- const rect = {
2541
- origin: { x: minX, y: minY },
2542
- size: { width, height }
2728
+ if (!defaults) return;
2729
+ const rect = lineRectWithEndings(
2730
+ vertices,
2731
+ defaults.strokeWidth,
2732
+ defaults.lineEndings
2733
+ );
2734
+ const anno = {
2735
+ ...defaults,
2736
+ vertices,
2737
+ rect,
2738
+ type: PdfAnnotationSubtype.POLYLINE,
2739
+ pageIndex: context.pageIndex,
2740
+ id: uuidV4(),
2741
+ created: /* @__PURE__ */ new Date()
2543
2742
  };
2743
+ onCommit(anno);
2744
+ setVertices([]);
2745
+ setCurrent(null);
2746
+ onPreview(null);
2747
+ };
2748
+ const getPreview = () => {
2749
+ const vertices = getVertices();
2750
+ const currentPos = getCurrent();
2751
+ if (vertices.length === 0 || !currentPos) return null;
2752
+ const defaults = getDefaults();
2753
+ if (!defaults) return null;
2754
+ const allPoints = [...vertices, currentPos];
2755
+ const bounds = lineRectWithEndings(
2756
+ allPoints,
2757
+ defaults.strokeWidth,
2758
+ defaults.lineEndings
2759
+ );
2544
2760
  return {
2545
- type: PdfAnnotationSubtype.LINK,
2546
- bounds: rect,
2761
+ type: PdfAnnotationSubtype.POLYLINE,
2762
+ bounds,
2547
2763
  data: {
2548
- rect,
2549
- strokeColor: defaults.strokeColor,
2550
- strokeWidth: defaults.strokeWidth,
2551
- strokeStyle: defaults.strokeStyle,
2552
- strokeDashArray: defaults.strokeDashArray
2764
+ ...defaults,
2765
+ rect: bounds,
2766
+ vertices: allPoints,
2767
+ currentVertex: currentPos
2553
2768
  }
2554
2769
  };
2555
2770
  };
2556
2771
  return {
2557
- onPointerDown: (pos, evt) => {
2558
- var _a;
2559
- const clampedPos = clampToPage(pos);
2560
- setStart(clampedPos);
2561
- clickDetector.onStart(clampedPos);
2562
- onPreview(getPreview(clampedPos));
2563
- (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
2564
- },
2565
- onPointerMove: (pos) => {
2566
- const clampedPos = clampToPage(pos);
2567
- clickDetector.onMove(clampedPos);
2568
- if (getStart() && clickDetector.hasMoved()) {
2569
- onPreview(getPreview(clampedPos));
2772
+ onClick: (pos, evt) => {
2773
+ if (evt.metaKey || evt.ctrlKey) {
2774
+ return;
2570
2775
  }
2571
- },
2572
- onPointerUp: (pos, evt) => {
2573
- var _a;
2574
- const p1 = getStart();
2575
- if (!p1) return;
2576
- const defaults = getDefaults();
2577
- if (!defaults) return;
2578
2776
  const clampedPos = clampToPage(pos);
2579
- if (!clickDetector.hasMoved()) {
2580
- clickDetector.onEnd(clampedPos);
2581
- } else {
2582
- const preview = getPreview(clampedPos);
2583
- if (preview) {
2584
- const anno = {
2585
- ...defaults,
2586
- type: PdfAnnotationSubtype.LINK,
2587
- target: void 0,
2588
- created: /* @__PURE__ */ new Date(),
2589
- id: uuidV4(),
2590
- pageIndex,
2591
- rect: preview.data.rect
2592
- };
2593
- onCommit(anno);
2594
- }
2777
+ const vertices = getVertices();
2778
+ const lastVertex = vertices[vertices.length - 1];
2779
+ if (lastVertex && Math.abs(lastVertex.x - clampedPos.x) < 1 && Math.abs(lastVertex.y - clampedPos.y) < 1) {
2780
+ return;
2595
2781
  }
2596
- setStart(null);
2597
- onPreview(null);
2598
- clickDetector.reset();
2599
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2782
+ setVertices([...vertices, clampedPos]);
2783
+ setCurrent(clampedPos);
2784
+ onPreview(getPreview());
2600
2785
  },
2601
- onPointerLeave: (_, evt) => {
2602
- var _a;
2603
- setStart(null);
2604
- onPreview(null);
2605
- clickDetector.reset();
2606
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2786
+ onDoubleClick: () => {
2787
+ commitPolyline();
2607
2788
  },
2608
- onPointerCancel: (_, evt) => {
2609
- var _a;
2610
- setStart(null);
2789
+ onPointerMove: (pos) => {
2790
+ if (getVertices().length > 0) {
2791
+ const clampedPos = clampToPage(pos);
2792
+ setCurrent(clampedPos);
2793
+ onPreview(getPreview());
2794
+ }
2795
+ },
2796
+ onPointerCancel: () => {
2797
+ setVertices([]);
2798
+ setCurrent(null);
2611
2799
  onPreview(null);
2612
- clickDetector.reset();
2613
- (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2614
2800
  }
2615
2801
  };
2616
2802
  }
2617
2803
  };
2618
- const textMarkupSelectionHandler = {
2619
- toolId: "__textMarkup__",
2620
- handle(context, selections, getText) {
2621
- const tool = context.getTool();
2622
- if (!tool) return;
2623
- for (const selection of selections) {
2624
- const id = uuidV4();
2625
- getText().then((text) => {
2626
- var _a;
2627
- context.createAnnotation(selection.pageIndex, {
2628
- ...tool.defaults,
2629
- rect: selection.rect,
2630
- segmentRects: selection.segmentRects,
2631
- pageIndex: selection.pageIndex,
2632
- created: /* @__PURE__ */ new Date(),
2633
- id,
2634
- ...text != null && { custom: { text } }
2635
- });
2636
- if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
2637
- context.selectAnnotation(selection.pageIndex, id);
2638
- }
2639
- });
2640
- }
2641
- }
2642
- };
2643
- function computeCaretRect(lastSegRect) {
2644
- const lineHeight = lastSegRect.size.height;
2645
- const height = lineHeight / 2;
2646
- const width = height;
2647
- const lineEndX = lastSegRect.origin.x + lastSegRect.size.width;
2648
- return {
2649
- origin: {
2650
- x: lineEndX - width / 2,
2651
- y: lastSegRect.origin.y + lineHeight / 2
2652
- },
2653
- size: { width, height }
2654
- };
2655
- }
2656
- const insertTextSelectionHandler = {
2657
- toolId: "insertText",
2658
- handle(context, selections, getText) {
2659
- const tool = context.getTool();
2660
- if (!tool) return;
2661
- const getDefaults = () => ({
2662
- strokeColor: tool.defaults.strokeColor ?? "#E44234",
2663
- opacity: tool.defaults.opacity ?? 1,
2664
- flags: tool.defaults.flags ?? ["print"]
2665
- });
2666
- for (const selection of selections) {
2667
- const lastSegRect = selection.segmentRects[selection.segmentRects.length - 1];
2668
- if (!lastSegRect) continue;
2669
- const caretRect = computeCaretRect(lastSegRect);
2670
- const caretId = uuidV4();
2671
- const defaults = getDefaults();
2672
- getText().then((text) => {
2673
- var _a;
2674
- context.createAnnotation(selection.pageIndex, {
2675
- type: PdfAnnotationSubtype.CARET,
2676
- id: caretId,
2677
- pageIndex: selection.pageIndex,
2678
- rect: caretRect,
2679
- strokeColor: defaults.strokeColor,
2680
- opacity: defaults.opacity,
2681
- intent: "Insert",
2682
- rectangleDifferences: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
2683
- created: /* @__PURE__ */ new Date(),
2684
- flags: defaults.flags,
2685
- ...text != null && { custom: { text } }
2686
- });
2687
- if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
2688
- context.selectAnnotation(selection.pageIndex, caretId);
2689
- }
2690
- });
2691
- }
2692
- }
2693
- };
2694
- const replaceTextSelectionHandler = {
2695
- toolId: "replaceText",
2696
- handle(context, selections, getText) {
2697
- const tool = context.getTool();
2698
- if (!tool) return;
2699
- const getDefaults = () => ({
2700
- strokeColor: tool.defaults.strokeColor ?? "#E44234",
2701
- opacity: tool.defaults.opacity ?? 1,
2702
- flags: tool.defaults.flags ?? ["print"]
2804
+ const HANDLE_SIZE_PX = 14;
2805
+ const polygonHandlerFactory = {
2806
+ annotationType: PdfAnnotationSubtype.POLYGON,
2807
+ create(context) {
2808
+ const { onCommit, onPreview, getTool, scale, pageSize } = context;
2809
+ const [getVertices, setVertices] = useState([]);
2810
+ const [getCurrent, setCurrent] = useState(null);
2811
+ const clampToPage = (pos) => ({
2812
+ x: clamp(pos.x, 0, pageSize.width),
2813
+ y: clamp(pos.y, 0, pageSize.height)
2703
2814
  });
2704
- for (const selection of selections) {
2705
- const lastSegRect = selection.segmentRects[selection.segmentRects.length - 1];
2706
- if (!lastSegRect) continue;
2707
- const caretRect = computeCaretRect(lastSegRect);
2708
- const caretId = uuidV4();
2709
- const strikeoutId = uuidV4();
2710
- const defaults = getDefaults();
2711
- getText().then((text) => {
2712
- var _a;
2713
- context.createAnnotation(selection.pageIndex, {
2714
- type: PdfAnnotationSubtype.CARET,
2715
- id: caretId,
2716
- pageIndex: selection.pageIndex,
2717
- rect: caretRect,
2718
- strokeColor: defaults.strokeColor,
2719
- opacity: defaults.opacity,
2720
- intent: "Replace",
2721
- rectangleDifferences: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
2722
- created: /* @__PURE__ */ new Date(),
2723
- flags: defaults.flags
2724
- });
2725
- context.createAnnotation(selection.pageIndex, {
2726
- type: PdfAnnotationSubtype.STRIKEOUT,
2727
- id: strikeoutId,
2728
- pageIndex: selection.pageIndex,
2729
- rect: selection.rect,
2730
- segmentRects: selection.segmentRects,
2731
- strokeColor: defaults.strokeColor,
2732
- opacity: defaults.opacity,
2733
- intent: "StrikeOutTextEdit",
2734
- inReplyToId: caretId,
2735
- replyType: PdfAnnotationReplyType.Group,
2736
- created: /* @__PURE__ */ new Date(),
2737
- flags: defaults.flags,
2738
- ...text != null && { custom: { text } }
2739
- });
2740
- if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
2741
- context.selectAnnotation(selection.pageIndex, caretId);
2742
- }
2743
- });
2744
- }
2745
- }
2746
- };
2747
- const patchInk = (original, ctx) => {
2748
- switch (ctx.type) {
2749
- case "vertex-edit":
2750
- return ctx.changes;
2751
- case "move": {
2752
- if (!ctx.changes.rect) return ctx.changes;
2753
- const { dx, dy, rects } = baseMoveChanges(original, ctx.changes.rect);
2815
+ const isInsideStartHandle = (pos) => {
2816
+ const vertices = getVertices();
2817
+ if (vertices.length < 2) return false;
2818
+ const sizePDF = HANDLE_SIZE_PX / scale;
2819
+ const half = sizePDF / 2;
2820
+ const v0 = vertices[0];
2821
+ return pos.x >= v0.x - half && pos.x <= v0.x + half && pos.y >= v0.y - half && pos.y <= v0.y + half;
2822
+ };
2823
+ const getDefaults = () => {
2824
+ const tool = getTool();
2825
+ if (!tool) return null;
2754
2826
  return {
2755
- ...rects,
2756
- inkList: original.inkList.map((stroke) => ({
2757
- points: stroke.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
2758
- }))
2827
+ ...tool.defaults,
2828
+ color: tool.defaults.color ?? "#000000",
2829
+ opacity: tool.defaults.opacity ?? 1,
2830
+ strokeWidth: tool.defaults.strokeWidth ?? 1,
2831
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2832
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2833
+ strokeDashArray: tool.defaults.strokeDashArray ?? [],
2834
+ flags: tool.defaults.flags ?? ["print"]
2759
2835
  };
2760
- }
2761
- case "resize": {
2762
- if (!ctx.changes.rect) return ctx.changes;
2763
- const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
2764
- original,
2765
- ctx.changes.rect,
2766
- ctx.metadata
2767
- );
2768
- const inset = (r2, pad) => ({
2769
- origin: { x: r2.origin.x + pad, y: r2.origin.y + pad },
2770
- size: {
2771
- width: Math.max(1, r2.size.width - pad * 2),
2772
- height: Math.max(1, r2.size.height - pad * 2)
2836
+ };
2837
+ const commitPolygon = () => {
2838
+ const vertices = getVertices();
2839
+ if (vertices.length < 3) return;
2840
+ const defaults = getDefaults();
2841
+ if (!defaults) return;
2842
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
2843
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
2844
+ const rect = expandRect(rectFromPoints(vertices), pad);
2845
+ const anno = {
2846
+ ...defaults,
2847
+ vertices,
2848
+ rect,
2849
+ type: PdfAnnotationSubtype.POLYGON,
2850
+ pageIndex: context.pageIndex,
2851
+ id: uuidV4(),
2852
+ created: /* @__PURE__ */ new Date(),
2853
+ ...intensity > 0 && {
2854
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2773
2855
  }
2774
- });
2775
- const resizeEpsilon = 1e-3;
2776
- const widthChanged = Math.abs(scaleX - 1) > resizeEpsilon;
2777
- const heightChanged = Math.abs(scaleY - 1) > resizeEpsilon;
2778
- const strokeScale = widthChanged && !heightChanged ? scaleX : !widthChanged && heightChanged ? scaleY : Math.min(scaleX, scaleY);
2779
- const rawStrokeWidth = Math.max(1, original.strokeWidth * strokeScale);
2780
- const maxStrokeWidth = Math.max(
2781
- 1,
2782
- Math.min(resolvedRect.size.width, resolvedRect.size.height)
2783
- );
2784
- const clampedStrokeWidth = Math.min(rawStrokeWidth, maxStrokeWidth);
2785
- const newStrokeWidth = Number(clampedStrokeWidth.toFixed(1));
2786
- const innerOld = inset(oldRect, original.strokeWidth / 2);
2787
- const innerNew = inset(resolvedRect, newStrokeWidth / 2);
2788
- const sx = innerNew.size.width / Math.max(innerOld.size.width, 1e-6);
2789
- const sy = innerNew.size.height / Math.max(innerOld.size.height, 1e-6);
2790
- return {
2791
- ...rects,
2792
- inkList: original.inkList.map((stroke) => ({
2793
- points: stroke.points.map((p) => ({
2794
- x: innerNew.origin.x + (p.x - innerOld.origin.x) * sx,
2795
- y: innerNew.origin.y + (p.y - innerOld.origin.y) * sy
2796
- }))
2797
- })),
2798
- strokeWidth: newStrokeWidth
2799
2856
  };
2800
- }
2801
- case "rotate": {
2802
- const result = baseRotateChanges(original, ctx);
2803
- if (!result) return ctx.changes;
2804
- const { dx, dy } = rotateOrbitDelta(original, result);
2857
+ onCommit(anno);
2858
+ setVertices([]);
2859
+ setCurrent(null);
2860
+ onPreview(null);
2861
+ };
2862
+ const getPreview = () => {
2863
+ const vertices = getVertices();
2864
+ const currentPos = getCurrent();
2865
+ if (vertices.length === 0 || !currentPos) return null;
2866
+ const defaults = getDefaults();
2867
+ if (!defaults) return null;
2868
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
2869
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults.strokeWidth, false) : defaults.strokeWidth / 2;
2870
+ const allPoints = [...vertices, currentPos];
2871
+ const bounds = expandRect(rectFromPoints(allPoints), pad);
2805
2872
  return {
2806
- ...result,
2807
- inkList: original.inkList.map((stroke) => ({
2808
- points: stroke.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
2809
- }))
2873
+ type: PdfAnnotationSubtype.POLYGON,
2874
+ bounds,
2875
+ data: {
2876
+ ...defaults,
2877
+ rect: bounds,
2878
+ vertices,
2879
+ currentVertex: currentPos
2880
+ }
2810
2881
  };
2811
- }
2812
- case "property-update": {
2813
- const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.rotation !== void 0;
2814
- if (!needsRectUpdate) return ctx.changes;
2815
- const merged = { ...original, ...ctx.changes };
2816
- const pts = merged.inkList.flatMap((s) => s.points);
2817
- const tightRect = expandRect(rectFromPoints(pts), merged.strokeWidth / 2);
2818
- const effectiveRotation = ctx.changes.rotation ?? original.rotation ?? 0;
2819
- if (original.unrotatedRect || ctx.changes.rotation !== void 0) {
2820
- return {
2821
- ...ctx.changes,
2822
- unrotatedRect: tightRect,
2823
- rect: calculateRotatedRectAABBAroundPoint(
2824
- tightRect,
2825
- effectiveRotation,
2826
- resolveAnnotationRotationCenter(original)
2827
- )
2828
- };
2882
+ };
2883
+ return {
2884
+ onClick: (pos, evt) => {
2885
+ if (evt.metaKey || evt.ctrlKey) {
2886
+ return;
2887
+ }
2888
+ const clampedPos = clampToPage(pos);
2889
+ if (isInsideStartHandle(clampedPos) && getVertices().length >= 3) {
2890
+ commitPolygon();
2891
+ return;
2892
+ }
2893
+ const vertices = getVertices();
2894
+ const lastVertex = vertices[vertices.length - 1];
2895
+ if (lastVertex && Math.abs(lastVertex.x - clampedPos.x) < 1 && Math.abs(lastVertex.y - clampedPos.y) < 1) {
2896
+ return;
2897
+ }
2898
+ setVertices([...vertices, clampedPos]);
2899
+ setCurrent(clampedPos);
2900
+ onPreview(getPreview());
2901
+ },
2902
+ onDoubleClick: (_) => {
2903
+ commitPolygon();
2904
+ },
2905
+ onPointerMove: (pos) => {
2906
+ if (getVertices().length > 0) {
2907
+ const clampedPos = clampToPage(pos);
2908
+ setCurrent(clampedPos);
2909
+ onPreview(getPreview());
2910
+ }
2911
+ },
2912
+ onPointerCancel: (_) => {
2913
+ setVertices([]);
2914
+ setCurrent(null);
2915
+ onPreview(null);
2829
2916
  }
2830
- return { ...ctx.changes, rect: tightRect };
2831
- }
2832
- default:
2833
- return ctx.changes;
2917
+ };
2834
2918
  }
2835
2919
  };
2836
- const patchLine = (orig, ctx) => {
2837
- switch (ctx.type) {
2838
- case "vertex-edit":
2839
- if (ctx.changes.linePoints) {
2840
- const { start, end } = ctx.changes.linePoints;
2841
- const rawPoints = [start, end];
2842
- const rawRect = lineRectWithEndings(rawPoints, orig.strokeWidth, orig.lineEndings);
2843
- const compensated = compensateRotatedVertexEdit(orig, rawPoints, rawRect);
2844
- const rect = lineRectWithEndings(compensated, orig.strokeWidth, orig.lineEndings);
2845
- return {
2846
- ...resolveVertexEditRects(orig, rect),
2847
- linePoints: { start: compensated[0], end: compensated[1] }
2920
+ const squareHandlerFactory = {
2921
+ annotationType: PdfAnnotationSubtype.SQUARE,
2922
+ create(context) {
2923
+ const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
2924
+ const [getStart, setStart] = useState(null);
2925
+ const clampToPage = (pos) => ({
2926
+ x: clamp(pos.x, 0, pageSize.width),
2927
+ y: clamp(pos.y, 0, pageSize.height)
2928
+ });
2929
+ const getDefaults = () => {
2930
+ const tool = getTool();
2931
+ if (!tool) return null;
2932
+ return {
2933
+ ...tool.defaults,
2934
+ flags: tool.defaults.flags ?? ["print"],
2935
+ strokeWidth: tool.defaults.strokeWidth ?? 2,
2936
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
2937
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
2938
+ strokeDashArray: tool.defaults.strokeDashArray ?? [],
2939
+ color: tool.defaults.color ?? "#000000",
2940
+ opacity: tool.defaults.opacity ?? 1
2941
+ };
2942
+ };
2943
+ const clickDetector = useClickDetector({
2944
+ threshold: 5,
2945
+ getTool,
2946
+ onClickDetected: (pos, tool) => {
2947
+ const defaults = getDefaults();
2948
+ if (!defaults) return;
2949
+ const clickConfig = tool.clickBehavior;
2950
+ if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
2951
+ const { width, height } = clickConfig.defaultSize;
2952
+ const halfWidth = width / 2;
2953
+ const halfHeight = height / 2;
2954
+ const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
2955
+ const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
2956
+ const strokeWidth = defaults.strokeWidth;
2957
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
2958
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
2959
+ const rect = {
2960
+ origin: { x: x - pad, y: y - pad },
2961
+ size: { width: width + 2 * pad, height: height + 2 * pad }
2962
+ };
2963
+ const anno = {
2964
+ ...defaults,
2965
+ type: PdfAnnotationSubtype.SQUARE,
2966
+ created: /* @__PURE__ */ new Date(),
2967
+ id: uuidV4(),
2968
+ pageIndex,
2969
+ rect,
2970
+ ...intensity > 0 && {
2971
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
2972
+ }
2848
2973
  };
2974
+ onCommit(anno);
2849
2975
  }
2850
- return ctx.changes;
2851
- case "move": {
2852
- if (!ctx.changes.rect) return ctx.changes;
2853
- const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
2976
+ });
2977
+ const getPreview = (current) => {
2978
+ const p1 = getStart();
2979
+ if (!p1) return null;
2980
+ const minX = Math.min(p1.x, current.x);
2981
+ const minY = Math.min(p1.y, current.y);
2982
+ const width = Math.abs(p1.x - current.x);
2983
+ const height = Math.abs(p1.y - current.y);
2984
+ const defaults = getDefaults();
2985
+ if (!defaults) return null;
2986
+ const strokeWidth = defaults.strokeWidth;
2987
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
2988
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, false) : strokeWidth / 2;
2989
+ const rect = {
2990
+ origin: { x: minX - pad, y: minY - pad },
2991
+ size: { width: width + 2 * pad, height: height + 2 * pad }
2992
+ };
2854
2993
  return {
2855
- ...rects,
2856
- linePoints: {
2857
- start: { x: orig.linePoints.start.x + dx, y: orig.linePoints.start.y + dy },
2858
- end: { x: orig.linePoints.end.x + dx, y: orig.linePoints.end.y + dy }
2994
+ type: PdfAnnotationSubtype.SQUARE,
2995
+ bounds: rect,
2996
+ data: {
2997
+ rect,
2998
+ ...defaults,
2999
+ ...intensity > 0 && {
3000
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
3001
+ }
2859
3002
  }
2860
3003
  };
2861
- }
2862
- case "resize": {
2863
- if (!ctx.changes.rect) return ctx.changes;
2864
- const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
2865
- orig,
2866
- ctx.changes.rect,
2867
- ctx.metadata
2868
- );
2869
- return {
2870
- ...rects,
2871
- linePoints: {
2872
- start: {
2873
- x: resolvedRect.origin.x + (orig.linePoints.start.x - oldRect.origin.x) * scaleX,
2874
- y: resolvedRect.origin.y + (orig.linePoints.start.y - oldRect.origin.y) * scaleY
2875
- },
2876
- end: {
2877
- x: resolvedRect.origin.x + (orig.linePoints.end.x - oldRect.origin.x) * scaleX,
2878
- y: resolvedRect.origin.y + (orig.linePoints.end.y - oldRect.origin.y) * scaleY
3004
+ };
3005
+ return {
3006
+ onPointerDown: (pos, evt) => {
3007
+ var _a;
3008
+ const clampedPos = clampToPage(pos);
3009
+ setStart(clampedPos);
3010
+ clickDetector.onStart(clampedPos);
3011
+ onPreview(getPreview(clampedPos));
3012
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
3013
+ },
3014
+ onPointerMove: (pos) => {
3015
+ const clampedPos = clampToPage(pos);
3016
+ clickDetector.onMove(clampedPos);
3017
+ if (getStart() && clickDetector.hasMoved()) {
3018
+ onPreview(getPreview(clampedPos));
3019
+ }
3020
+ },
3021
+ onPointerUp: (pos, evt) => {
3022
+ var _a;
3023
+ const p1 = getStart();
3024
+ if (!p1) return;
3025
+ const defaults = getDefaults();
3026
+ if (!defaults) return;
3027
+ const clampedPos = clampToPage(pos);
3028
+ if (!clickDetector.hasMoved()) {
3029
+ clickDetector.onEnd(clampedPos);
3030
+ } else {
3031
+ const defaults2 = getDefaults();
3032
+ if (!defaults2) return;
3033
+ const preview = getPreview(clampedPos);
3034
+ if (preview) {
3035
+ const intensity = defaults2.cloudyBorderIntensity ?? 0;
3036
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, false) : void 0;
3037
+ const anno = {
3038
+ ...defaults2,
3039
+ type: PdfAnnotationSubtype.SQUARE,
3040
+ created: /* @__PURE__ */ new Date(),
3041
+ id: uuidV4(),
3042
+ pageIndex,
3043
+ rect: preview.data.rect,
3044
+ ...pad !== void 0 && {
3045
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
3046
+ }
3047
+ };
3048
+ onCommit(anno);
2879
3049
  }
2880
3050
  }
2881
- };
2882
- }
2883
- case "rotate": {
2884
- const result = baseRotateChanges(orig, ctx);
2885
- if (!result) return ctx.changes;
2886
- const { dx, dy } = rotateOrbitDelta(orig, result);
2887
- return {
2888
- ...result,
2889
- linePoints: {
2890
- start: { x: orig.linePoints.start.x + dx, y: orig.linePoints.start.y + dy },
2891
- end: { x: orig.linePoints.end.x + dx, y: orig.linePoints.end.y + dy }
3051
+ setStart(null);
3052
+ onPreview(null);
3053
+ clickDetector.reset();
3054
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3055
+ },
3056
+ onPointerLeave: (_, evt) => {
3057
+ var _a;
3058
+ setStart(null);
3059
+ onPreview(null);
3060
+ clickDetector.reset();
3061
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3062
+ },
3063
+ onPointerCancel: (_, evt) => {
3064
+ var _a;
3065
+ setStart(null);
3066
+ onPreview(null);
3067
+ clickDetector.reset();
3068
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3069
+ }
3070
+ };
3071
+ }
3072
+ };
3073
+ const imageFetchCache = /* @__PURE__ */ new Map();
3074
+ const stampHandlerFactory = {
3075
+ annotationType: PdfAnnotationSubtype.STAMP,
3076
+ create(context) {
3077
+ const { services, onCommit, onPreview, getTool, pageSize, pageRotation } = context;
3078
+ let cachedBuffer = null;
3079
+ let cachedSize = null;
3080
+ const commitStamp = (pos, width, height, ctx) => {
3081
+ var _a;
3082
+ const tool = getTool();
3083
+ if (!tool) return;
3084
+ const rect = {
3085
+ origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
3086
+ size: { width, height }
3087
+ };
3088
+ let anno = {
3089
+ ...tool.defaults,
3090
+ rect,
3091
+ type: PdfAnnotationSubtype.STAMP,
3092
+ name: tool.defaults.name ?? PdfAnnotationName.Image,
3093
+ subject: tool.defaults.subject ?? "Stamp",
3094
+ flags: tool.defaults.flags ?? ["print"],
3095
+ pageIndex: context.pageIndex,
3096
+ id: uuidV4(),
3097
+ created: /* @__PURE__ */ new Date()
3098
+ };
3099
+ if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
3100
+ anno = applyInsertUpright(anno, pageRotation, false);
3101
+ }
3102
+ anno = clampAnnotationToPage(anno, pageSize);
3103
+ onCommit(anno, ctx);
3104
+ };
3105
+ const commitFromBuffer = (pos, buffer, imageSize) => {
3106
+ const meta = getImageMetadata(buffer);
3107
+ if (!meta || meta.mimeType === "application/pdf") return false;
3108
+ const fitted = fitSizeWithin(meta, pageSize);
3109
+ const width = (imageSize == null ? void 0 : imageSize.width) ?? fitted.width;
3110
+ const height = (imageSize == null ? void 0 : imageSize.height) ?? fitted.height;
3111
+ commitStamp(pos, width, height, { data: buffer });
3112
+ return true;
3113
+ };
3114
+ return {
3115
+ onHandlerActiveStart: () => {
3116
+ const tool = getTool();
3117
+ const imageSrc = tool == null ? void 0 : tool.defaults.imageSrc;
3118
+ if (!imageSrc) return;
3119
+ let entry = imageFetchCache.get(imageSrc);
3120
+ if (!entry) {
3121
+ const promise = fetch(imageSrc).then((res) => res.arrayBuffer()).then((buffer) => {
3122
+ const meta = getImageMetadata(buffer);
3123
+ if (!meta || meta.mimeType === "application/pdf") return null;
3124
+ const fitted = fitSizeWithin(meta, pageSize);
3125
+ const imageSize = tool.defaults.imageSize;
3126
+ return {
3127
+ buffer,
3128
+ width: (imageSize == null ? void 0 : imageSize.width) ?? fitted.width,
3129
+ height: (imageSize == null ? void 0 : imageSize.height) ?? fitted.height
3130
+ };
3131
+ }).catch(() => null);
3132
+ entry = { promise, refs: 1 };
3133
+ imageFetchCache.set(imageSrc, entry);
3134
+ } else {
3135
+ entry.refs++;
3136
+ }
3137
+ entry.promise.then((result) => {
3138
+ if (!result) return;
3139
+ cachedBuffer = result.buffer;
3140
+ cachedSize = { width: result.width, height: result.height };
3141
+ });
3142
+ },
3143
+ onHandlerActiveEnd: () => {
3144
+ const tool = getTool();
3145
+ const imageSrc = tool == null ? void 0 : tool.defaults.imageSrc;
3146
+ if (imageSrc) {
3147
+ const entry = imageFetchCache.get(imageSrc);
3148
+ if (entry && --entry.refs <= 0) {
3149
+ imageFetchCache.delete(imageSrc);
3150
+ }
2892
3151
  }
2893
- };
2894
- }
2895
- case "property-update": {
2896
- const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.lineEndings !== void 0 || ctx.changes.rotation !== void 0;
2897
- if (!needsRectUpdate) return ctx.changes;
2898
- const merged = { ...orig, ...ctx.changes };
2899
- const tightRect = lineRectWithEndings(
2900
- [merged.linePoints.start, merged.linePoints.end],
2901
- merged.strokeWidth,
2902
- merged.lineEndings
2903
- );
2904
- const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
2905
- if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
2906
- return {
2907
- ...ctx.changes,
2908
- unrotatedRect: tightRect,
2909
- rect: calculateRotatedRectAABBAroundPoint(
2910
- tightRect,
2911
- effectiveRotation,
2912
- resolveAnnotationRotationCenter(orig)
2913
- )
3152
+ cachedBuffer = null;
3153
+ cachedSize = null;
3154
+ onPreview(null);
3155
+ },
3156
+ onPointerMove: (pos) => {
3157
+ var _a;
3158
+ const tool = getTool();
3159
+ if (!((_a = tool == null ? void 0 : tool.behavior) == null ? void 0 : _a.showGhost) || !cachedSize || !tool.defaults.imageSrc) return;
3160
+ const rect = {
3161
+ origin: { x: pos.x - cachedSize.width / 2, y: pos.y - cachedSize.height / 2 },
3162
+ size: cachedSize
2914
3163
  };
3164
+ onPreview({
3165
+ type: PdfAnnotationSubtype.STAMP,
3166
+ bounds: rect,
3167
+ data: { rect, ghostUrl: tool.defaults.imageSrc, pageRotation }
3168
+ });
3169
+ },
3170
+ onPointerDown: (pos) => {
3171
+ const tool = getTool();
3172
+ if (!tool) return;
3173
+ const { imageSrc, imageSize } = tool.defaults;
3174
+ if (imageSrc) {
3175
+ onPreview(null);
3176
+ if (cachedBuffer) {
3177
+ commitFromBuffer(pos, cachedBuffer, imageSize);
3178
+ } else {
3179
+ fetch(imageSrc).then((res) => res.arrayBuffer()).then((buffer) => commitFromBuffer(pos, buffer, imageSize));
3180
+ }
3181
+ } else {
3182
+ services.requestFile({
3183
+ accept: "image/png,image/jpeg",
3184
+ onFile: (file) => {
3185
+ file.arrayBuffer().then((buffer) => commitFromBuffer(pos, buffer));
3186
+ }
3187
+ });
3188
+ }
3189
+ },
3190
+ onPointerLeave: () => {
3191
+ onPreview(null);
2915
3192
  }
2916
- return { ...ctx.changes, rect: tightRect };
2917
- }
2918
- default:
2919
- return ctx.changes;
3193
+ };
2920
3194
  }
2921
3195
  };
2922
- const patchPolyline = (orig, ctx) => {
2923
- switch (ctx.type) {
2924
- case "vertex-edit":
2925
- if (ctx.changes.vertices && ctx.changes.vertices.length) {
2926
- const rawVertices = ctx.changes.vertices;
2927
- const rawRect = lineRectWithEndings(rawVertices, orig.strokeWidth, orig.lineEndings);
2928
- const compensated = compensateRotatedVertexEdit(orig, rawVertices, rawRect);
2929
- const rect = lineRectWithEndings(compensated, orig.strokeWidth, orig.lineEndings);
2930
- return {
2931
- ...resolveVertexEditRects(orig, rect),
2932
- vertices: compensated
2933
- };
2934
- }
2935
- return ctx.changes;
2936
- case "move": {
2937
- if (!ctx.changes.rect) return ctx.changes;
2938
- const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
3196
+ const circleHandlerFactory = {
3197
+ annotationType: PdfAnnotationSubtype.CIRCLE,
3198
+ create(context) {
3199
+ const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
3200
+ const [getStart, setStart] = useState(null);
3201
+ const clampToPage = (pos) => ({
3202
+ x: clamp(pos.x, 0, pageSize.width),
3203
+ y: clamp(pos.y, 0, pageSize.height)
3204
+ });
3205
+ const getDefaults = () => {
3206
+ const tool = getTool();
3207
+ if (!tool) return null;
2939
3208
  return {
2940
- ...rects,
2941
- vertices: orig.vertices.map((p) => ({ x: p.x + dx, y: p.y + dy }))
3209
+ ...tool.defaults,
3210
+ strokeWidth: tool.defaults.strokeWidth ?? 2,
3211
+ strokeColor: tool.defaults.strokeColor ?? "#000000",
3212
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.SOLID,
3213
+ strokeDashArray: tool.defaults.strokeDashArray ?? [],
3214
+ color: tool.defaults.color ?? "#000000",
3215
+ opacity: tool.defaults.opacity ?? 1,
3216
+ flags: tool.defaults.flags ?? ["print"]
2942
3217
  };
2943
- }
2944
- case "resize": {
2945
- if (!ctx.changes.rect) return ctx.changes;
2946
- const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
2947
- orig,
2948
- ctx.changes.rect,
2949
- ctx.metadata
2950
- );
2951
- return {
2952
- ...rects,
2953
- vertices: orig.vertices.map((v) => ({
2954
- x: resolvedRect.origin.x + (v.x - oldRect.origin.x) * scaleX,
2955
- y: resolvedRect.origin.y + (v.y - oldRect.origin.y) * scaleY
2956
- }))
3218
+ };
3219
+ const clickDetector = useClickDetector({
3220
+ threshold: 5,
3221
+ getTool,
3222
+ onClickDetected: (pos, tool) => {
3223
+ const defaults = getDefaults();
3224
+ if (!defaults) return;
3225
+ const clickConfig = tool.clickBehavior;
3226
+ if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
3227
+ const { width, height } = clickConfig.defaultSize;
3228
+ const halfWidth = width / 2;
3229
+ const halfHeight = height / 2;
3230
+ const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
3231
+ const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
3232
+ const strokeWidth = defaults.strokeWidth;
3233
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
3234
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
3235
+ const rect = {
3236
+ origin: { x: x - pad, y: y - pad },
3237
+ size: { width: width + 2 * pad, height: height + 2 * pad }
3238
+ };
3239
+ const anno = {
3240
+ ...defaults,
3241
+ type: PdfAnnotationSubtype.CIRCLE,
3242
+ created: /* @__PURE__ */ new Date(),
3243
+ id: uuidV4(),
3244
+ pageIndex,
3245
+ rect,
3246
+ ...intensity > 0 && {
3247
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
3248
+ }
3249
+ };
3250
+ onCommit(anno);
3251
+ }
3252
+ });
3253
+ const getPreview = (current) => {
3254
+ const p1 = getStart();
3255
+ if (!p1) return null;
3256
+ const minX = Math.min(p1.x, current.x);
3257
+ const minY = Math.min(p1.y, current.y);
3258
+ const width = Math.abs(p1.x - current.x);
3259
+ const height = Math.abs(p1.y - current.y);
3260
+ const defaults = getDefaults();
3261
+ if (!defaults) return null;
3262
+ const strokeWidth = defaults.strokeWidth;
3263
+ const intensity = defaults.cloudyBorderIntensity ?? 0;
3264
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, strokeWidth, true) : strokeWidth / 2;
3265
+ const rect = {
3266
+ origin: { x: minX - pad, y: minY - pad },
3267
+ size: { width: width + 2 * pad, height: height + 2 * pad }
2957
3268
  };
2958
- }
2959
- case "rotate": {
2960
- const result = baseRotateChanges(orig, ctx);
2961
- if (!result) return ctx.changes;
2962
- const { dx, dy } = rotateOrbitDelta(orig, result);
2963
3269
  return {
2964
- ...result,
2965
- vertices: orig.vertices.map((v) => ({ x: v.x + dx, y: v.y + dy }))
3270
+ type: PdfAnnotationSubtype.CIRCLE,
3271
+ bounds: rect,
3272
+ data: {
3273
+ rect,
3274
+ ...defaults,
3275
+ ...intensity > 0 && {
3276
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
3277
+ }
3278
+ }
2966
3279
  };
2967
- }
2968
- case "property-update": {
2969
- const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.lineEndings !== void 0 || ctx.changes.rotation !== void 0;
2970
- if (!needsRectUpdate) return ctx.changes;
2971
- const merged = { ...orig, ...ctx.changes };
2972
- const tightRect = lineRectWithEndings(
2973
- merged.vertices,
2974
- merged.strokeWidth,
2975
- merged.lineEndings
2976
- );
2977
- const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
2978
- if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
2979
- return {
2980
- ...ctx.changes,
2981
- unrotatedRect: tightRect,
2982
- rect: calculateRotatedRectAABBAroundPoint(
2983
- tightRect,
2984
- effectiveRotation,
2985
- resolveAnnotationRotationCenter(orig)
2986
- )
2987
- };
3280
+ };
3281
+ return {
3282
+ onPointerDown: (pos, evt) => {
3283
+ var _a;
3284
+ const clampedPos = clampToPage(pos);
3285
+ setStart(clampedPos);
3286
+ clickDetector.onStart(clampedPos);
3287
+ onPreview(getPreview(clampedPos));
3288
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
3289
+ },
3290
+ onPointerMove: (pos) => {
3291
+ const clampedPos = clampToPage(pos);
3292
+ clickDetector.onMove(clampedPos);
3293
+ if (getStart() && clickDetector.hasMoved()) {
3294
+ onPreview(getPreview(clampedPos));
3295
+ }
3296
+ },
3297
+ onPointerUp: (pos, evt) => {
3298
+ var _a;
3299
+ const p1 = getStart();
3300
+ if (!p1) return;
3301
+ const defaults = getDefaults();
3302
+ if (!defaults) return;
3303
+ const clampedPos = clampToPage(pos);
3304
+ if (!clickDetector.hasMoved()) {
3305
+ clickDetector.onEnd(clampedPos);
3306
+ } else {
3307
+ const defaults2 = getDefaults();
3308
+ if (!defaults2) return;
3309
+ const preview = getPreview(clampedPos);
3310
+ if (preview) {
3311
+ const intensity = defaults2.cloudyBorderIntensity ?? 0;
3312
+ const pad = intensity > 0 ? getCloudyBorderExtent(intensity, defaults2.strokeWidth, true) : void 0;
3313
+ const anno = {
3314
+ ...defaults2,
3315
+ type: PdfAnnotationSubtype.CIRCLE,
3316
+ flags: ["print"],
3317
+ created: /* @__PURE__ */ new Date(),
3318
+ id: uuidV4(),
3319
+ pageIndex,
3320
+ rect: preview.data.rect,
3321
+ ...pad !== void 0 && {
3322
+ rectangleDifferences: { left: pad, top: pad, right: pad, bottom: pad }
3323
+ }
3324
+ };
3325
+ onCommit(anno);
3326
+ }
3327
+ }
3328
+ setStart(null);
3329
+ onPreview(null);
3330
+ clickDetector.reset();
3331
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3332
+ },
3333
+ onPointerLeave: (_, evt) => {
3334
+ var _a;
3335
+ setStart(null);
3336
+ onPreview(null);
3337
+ clickDetector.reset();
3338
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3339
+ },
3340
+ onPointerCancel: (_, evt) => {
3341
+ var _a;
3342
+ setStart(null);
3343
+ onPreview(null);
3344
+ clickDetector.reset();
3345
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
2988
3346
  }
2989
- return { ...ctx.changes, rect: tightRect };
2990
- }
2991
- default:
2992
- return ctx.changes;
3347
+ };
2993
3348
  }
2994
3349
  };
2995
- function getPolygonPad(intensity, strokeWidth) {
2996
- if ((intensity ?? 0) > 0) {
2997
- return getCloudyBorderExtent(intensity, strokeWidth, false);
2998
- }
2999
- return strokeWidth / 2;
3000
- }
3001
- const patchPolygon = (orig, ctx) => {
3002
- switch (ctx.type) {
3003
- case "vertex-edit":
3004
- if (ctx.changes.vertices && ctx.changes.vertices.length) {
3005
- const pad = getPolygonPad(orig.cloudyBorderIntensity, orig.strokeWidth);
3006
- const rawVertices = ctx.changes.vertices;
3007
- const rawRect = expandRect(rectFromPoints(rawVertices), pad);
3008
- const compensated = compensateRotatedVertexEdit(orig, rawVertices, rawRect);
3009
- const rect = expandRect(rectFromPoints(compensated), pad);
3010
- return {
3011
- ...resolveVertexEditRects(orig, rect),
3012
- vertices: compensated
3013
- };
3014
- }
3015
- return ctx.changes;
3016
- case "move": {
3017
- if (!ctx.changes.rect) return ctx.changes;
3018
- const { dx, dy, rects } = baseMoveChanges(orig, ctx.changes.rect);
3350
+ const linkHandlerFactory = {
3351
+ annotationType: PdfAnnotationSubtype.LINK,
3352
+ create(context) {
3353
+ const { pageIndex, onCommit, onPreview, getTool, pageSize } = context;
3354
+ const [getStart, setStart] = useState(null);
3355
+ const clampToPage = (pos) => ({
3356
+ x: clamp(pos.x, 0, pageSize.width),
3357
+ y: clamp(pos.y, 0, pageSize.height)
3358
+ });
3359
+ const getDefaults = () => {
3360
+ const tool = getTool();
3361
+ if (!tool) return null;
3019
3362
  return {
3020
- ...rects,
3021
- vertices: orig.vertices.map((p) => ({ x: p.x + dx, y: p.y + dy }))
3363
+ ...tool.defaults,
3364
+ flags: tool.defaults.flags ?? ["print"],
3365
+ strokeWidth: tool.defaults.strokeWidth ?? 2,
3366
+ strokeColor: tool.defaults.strokeColor ?? "#0000FF",
3367
+ strokeStyle: tool.defaults.strokeStyle ?? PdfAnnotationBorderStyle.UNDERLINE,
3368
+ strokeDashArray: tool.defaults.strokeDashArray ?? []
3022
3369
  };
3023
- }
3024
- case "resize": {
3025
- if (!ctx.changes.rect) return ctx.changes;
3026
- const { scaleX, scaleY, oldRect, resolvedRect, rects } = baseResizeScaling(
3027
- orig,
3028
- ctx.changes.rect,
3029
- ctx.metadata
3030
- );
3031
- return {
3032
- ...rects,
3033
- vertices: orig.vertices.map((v) => ({
3034
- x: resolvedRect.origin.x + (v.x - oldRect.origin.x) * scaleX,
3035
- y: resolvedRect.origin.y + (v.y - oldRect.origin.y) * scaleY
3036
- }))
3370
+ };
3371
+ const clickDetector = useClickDetector({
3372
+ threshold: 5,
3373
+ getTool,
3374
+ onClickDetected: (pos, tool) => {
3375
+ const defaults = getDefaults();
3376
+ if (!defaults) return;
3377
+ const clickConfig = tool.clickBehavior;
3378
+ if (!(clickConfig == null ? void 0 : clickConfig.enabled)) return;
3379
+ const { width, height } = clickConfig.defaultSize;
3380
+ const halfWidth = width / 2;
3381
+ const halfHeight = height / 2;
3382
+ const x = clamp(pos.x - halfWidth, 0, pageSize.width - width);
3383
+ const y = clamp(pos.y - halfHeight, 0, pageSize.height - height);
3384
+ const rect = {
3385
+ origin: { x, y },
3386
+ size: { width, height }
3387
+ };
3388
+ const anno = {
3389
+ ...defaults,
3390
+ type: PdfAnnotationSubtype.LINK,
3391
+ target: void 0,
3392
+ created: /* @__PURE__ */ new Date(),
3393
+ id: uuidV4(),
3394
+ pageIndex,
3395
+ rect
3396
+ };
3397
+ onCommit(anno);
3398
+ }
3399
+ });
3400
+ const getPreview = (current) => {
3401
+ const p1 = getStart();
3402
+ if (!p1) return null;
3403
+ const minX = Math.min(p1.x, current.x);
3404
+ const minY = Math.min(p1.y, current.y);
3405
+ const width = Math.abs(p1.x - current.x);
3406
+ const height = Math.abs(p1.y - current.y);
3407
+ const defaults = getDefaults();
3408
+ if (!defaults) return null;
3409
+ const rect = {
3410
+ origin: { x: minX, y: minY },
3411
+ size: { width, height }
3037
3412
  };
3038
- }
3039
- case "rotate": {
3040
- const result = baseRotateChanges(orig, ctx);
3041
- if (!result) return ctx.changes;
3042
- const { dx, dy } = rotateOrbitDelta(orig, result);
3043
3413
  return {
3044
- ...result,
3045
- vertices: orig.vertices.map((v) => ({ x: v.x + dx, y: v.y + dy }))
3414
+ type: PdfAnnotationSubtype.LINK,
3415
+ bounds: rect,
3416
+ data: {
3417
+ rect,
3418
+ strokeColor: defaults.strokeColor,
3419
+ strokeWidth: defaults.strokeWidth,
3420
+ strokeStyle: defaults.strokeStyle,
3421
+ strokeDashArray: defaults.strokeDashArray
3422
+ }
3046
3423
  };
3047
- }
3048
- case "property-update": {
3049
- const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
3050
- const needsRectUpdate = ctx.changes.strokeWidth !== void 0 || ctx.changes.rotation !== void 0 || cloudyChanged;
3051
- if (!needsRectUpdate) return ctx.changes;
3052
- const merged = { ...orig, ...ctx.changes };
3053
- const pad = getPolygonPad(merged.cloudyBorderIntensity, merged.strokeWidth);
3054
- const tightRect = expandRect(rectFromPoints(merged.vertices), pad);
3055
- let patch = ctx.changes;
3056
- const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
3057
- if (cloudyChanged || ctx.changes.strokeWidth !== void 0 && hasCloudy) {
3058
- const intensity = merged.cloudyBorderIntensity ?? 0;
3059
- if (intensity > 0) {
3060
- const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
3061
- patch = {
3062
- ...patch,
3063
- rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
3064
- };
3065
- } else {
3066
- patch = { ...patch, rectangleDifferences: void 0 };
3424
+ };
3425
+ return {
3426
+ onPointerDown: (pos, evt) => {
3427
+ var _a;
3428
+ const clampedPos = clampToPage(pos);
3429
+ setStart(clampedPos);
3430
+ clickDetector.onStart(clampedPos);
3431
+ onPreview(getPreview(clampedPos));
3432
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
3433
+ },
3434
+ onPointerMove: (pos) => {
3435
+ const clampedPos = clampToPage(pos);
3436
+ clickDetector.onMove(clampedPos);
3437
+ if (getStart() && clickDetector.hasMoved()) {
3438
+ onPreview(getPreview(clampedPos));
3067
3439
  }
3068
- }
3069
- const effectiveRotation = ctx.changes.rotation ?? orig.rotation ?? 0;
3070
- if (orig.unrotatedRect || ctx.changes.rotation !== void 0) {
3071
- return {
3072
- ...patch,
3073
- unrotatedRect: tightRect,
3074
- rect: calculateRotatedRectAABBAroundPoint(
3075
- tightRect,
3076
- effectiveRotation,
3077
- resolveAnnotationRotationCenter(orig)
3078
- )
3079
- };
3080
- }
3081
- return { ...patch, rect: tightRect };
3082
- }
3083
- default:
3084
- return ctx.changes;
3085
- }
3086
- };
3087
- const patchCircle = (orig, ctx) => {
3088
- switch (ctx.type) {
3089
- case "move":
3090
- if (!ctx.changes.rect) return ctx.changes;
3091
- return baseMoveChanges(orig, ctx.changes.rect).rects;
3092
- case "resize":
3093
- if (!ctx.changes.rect) return ctx.changes;
3094
- return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
3095
- case "rotate":
3096
- return baseRotateChanges(orig, ctx) ?? ctx.changes;
3097
- case "property-update": {
3098
- let patch = ctx.changes;
3099
- const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
3100
- const strokeChanged = ctx.changes.strokeWidth !== void 0;
3101
- const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
3102
- if (cloudyChanged || strokeChanged && hasCloudy) {
3103
- const merged = { ...orig, ...ctx.changes };
3104
- const intensity = merged.cloudyBorderIntensity ?? 0;
3105
- if (intensity > 0) {
3106
- const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, true);
3107
- patch = {
3108
- ...patch,
3109
- rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
3110
- };
3440
+ },
3441
+ onPointerUp: (pos, evt) => {
3442
+ var _a;
3443
+ const p1 = getStart();
3444
+ if (!p1) return;
3445
+ const defaults = getDefaults();
3446
+ if (!defaults) return;
3447
+ const clampedPos = clampToPage(pos);
3448
+ if (!clickDetector.hasMoved()) {
3449
+ clickDetector.onEnd(clampedPos);
3111
3450
  } else {
3112
- patch = { ...patch, rectangleDifferences: void 0 };
3451
+ const preview = getPreview(clampedPos);
3452
+ if (preview) {
3453
+ const anno = {
3454
+ ...defaults,
3455
+ type: PdfAnnotationSubtype.LINK,
3456
+ target: void 0,
3457
+ created: /* @__PURE__ */ new Date(),
3458
+ id: uuidV4(),
3459
+ pageIndex,
3460
+ rect: preview.data.rect
3461
+ };
3462
+ onCommit(anno);
3463
+ }
3113
3464
  }
3465
+ setStart(null);
3466
+ onPreview(null);
3467
+ clickDetector.reset();
3468
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3469
+ },
3470
+ onPointerLeave: (_, evt) => {
3471
+ var _a;
3472
+ setStart(null);
3473
+ onPreview(null);
3474
+ clickDetector.reset();
3475
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3476
+ },
3477
+ onPointerCancel: (_, evt) => {
3478
+ var _a;
3479
+ setStart(null);
3480
+ onPreview(null);
3481
+ clickDetector.reset();
3482
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
3114
3483
  }
3115
- if (ctx.changes.rotation !== void 0) {
3116
- patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
3117
- }
3118
- return patch;
3119
- }
3120
- default:
3121
- return ctx.changes;
3484
+ };
3122
3485
  }
3123
3486
  };
3124
- const patchSquare = (orig, ctx) => {
3125
- switch (ctx.type) {
3126
- case "move":
3127
- if (!ctx.changes.rect) return ctx.changes;
3128
- return baseMoveChanges(orig, ctx.changes.rect).rects;
3129
- case "resize":
3130
- if (!ctx.changes.rect) return ctx.changes;
3131
- return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
3132
- case "rotate":
3133
- return baseRotateChanges(orig, ctx) ?? ctx.changes;
3134
- case "property-update": {
3135
- let patch = ctx.changes;
3136
- const cloudyChanged = ctx.changes.cloudyBorderIntensity !== void 0;
3137
- const strokeChanged = ctx.changes.strokeWidth !== void 0;
3138
- const hasCloudy = (orig.cloudyBorderIntensity ?? 0) > 0;
3139
- if (cloudyChanged || strokeChanged && hasCloudy) {
3140
- const merged = { ...orig, ...ctx.changes };
3141
- const intensity = merged.cloudyBorderIntensity ?? 0;
3142
- if (intensity > 0) {
3143
- const extent = getCloudyBorderExtent(intensity, merged.strokeWidth, false);
3144
- patch = {
3145
- ...patch,
3146
- rectangleDifferences: { left: extent, top: extent, right: extent, bottom: extent }
3147
- };
3148
- } else {
3149
- patch = { ...patch, rectangleDifferences: void 0 };
3487
+ const textMarkupSelectionHandler = {
3488
+ toolId: "__textMarkup__",
3489
+ handle(context, selections, getText) {
3490
+ const tool = context.getTool();
3491
+ if (!tool) return;
3492
+ for (const selection of selections) {
3493
+ const id = uuidV4();
3494
+ getText().then((text) => {
3495
+ var _a;
3496
+ context.createAnnotation(selection.pageIndex, {
3497
+ ...tool.defaults,
3498
+ rect: selection.rect,
3499
+ segmentRects: selection.segmentRects,
3500
+ pageIndex: selection.pageIndex,
3501
+ created: /* @__PURE__ */ new Date(),
3502
+ id,
3503
+ ...text != null && { custom: { text } }
3504
+ });
3505
+ if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
3506
+ context.selectAnnotation(selection.pageIndex, id);
3150
3507
  }
3151
- }
3152
- if (ctx.changes.rotation !== void 0) {
3153
- patch = { ...patch, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
3154
- }
3155
- return patch;
3508
+ });
3156
3509
  }
3157
- default:
3158
- return ctx.changes;
3159
3510
  }
3160
3511
  };
3161
- const patchFreeText = (orig, ctx) => {
3162
- switch (ctx.type) {
3163
- case "move":
3164
- if (!ctx.changes.rect) return ctx.changes;
3165
- return baseMoveChanges(orig, ctx.changes.rect).rects;
3166
- case "resize":
3167
- if (!ctx.changes.rect) return ctx.changes;
3168
- return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
3169
- case "rotate":
3170
- return baseRotateChanges(orig, ctx) ?? ctx.changes;
3171
- case "property-update":
3172
- if (ctx.changes.rotation !== void 0) {
3173
- return { ...ctx.changes, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
3174
- }
3175
- return ctx.changes;
3176
- default:
3177
- return ctx.changes;
3512
+ function computeCaretRect(lastSegRect) {
3513
+ const lineHeight = lastSegRect.size.height;
3514
+ const height = lineHeight / 2;
3515
+ const width = height;
3516
+ const lineEndX = lastSegRect.origin.x + lastSegRect.size.width;
3517
+ return {
3518
+ origin: {
3519
+ x: lineEndX - width / 2,
3520
+ y: lastSegRect.origin.y + lineHeight / 2
3521
+ },
3522
+ size: { width, height }
3523
+ };
3524
+ }
3525
+ const insertTextSelectionHandler = {
3526
+ toolId: "insertText",
3527
+ handle(context, selections, getText) {
3528
+ const tool = context.getTool();
3529
+ if (!tool) return;
3530
+ const getDefaults = () => ({
3531
+ strokeColor: tool.defaults.strokeColor ?? "#E44234",
3532
+ opacity: tool.defaults.opacity ?? 1,
3533
+ flags: tool.defaults.flags ?? ["print"]
3534
+ });
3535
+ for (const selection of selections) {
3536
+ const lastSegRect = selection.segmentRects[selection.segmentRects.length - 1];
3537
+ if (!lastSegRect) continue;
3538
+ const caretRect = computeCaretRect(lastSegRect);
3539
+ const caretId = uuidV4();
3540
+ const defaults = getDefaults();
3541
+ getText().then((text) => {
3542
+ var _a;
3543
+ context.createAnnotation(selection.pageIndex, {
3544
+ type: PdfAnnotationSubtype.CARET,
3545
+ id: caretId,
3546
+ pageIndex: selection.pageIndex,
3547
+ rect: caretRect,
3548
+ strokeColor: defaults.strokeColor,
3549
+ opacity: defaults.opacity,
3550
+ intent: "Insert",
3551
+ rectangleDifferences: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
3552
+ created: /* @__PURE__ */ new Date(),
3553
+ flags: defaults.flags,
3554
+ ...text != null && { custom: { text } }
3555
+ });
3556
+ if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
3557
+ context.selectAnnotation(selection.pageIndex, caretId);
3558
+ }
3559
+ });
3560
+ }
3178
3561
  }
3179
3562
  };
3180
- const patchStamp = (orig, ctx) => {
3181
- switch (ctx.type) {
3182
- case "move":
3183
- if (!ctx.changes.rect) return ctx.changes;
3184
- return baseMoveChanges(orig, ctx.changes.rect).rects;
3185
- case "resize":
3186
- if (!ctx.changes.rect) return ctx.changes;
3187
- return baseResizeScaling(orig, ctx.changes.rect, ctx.metadata).rects;
3188
- case "rotate":
3189
- return baseRotateChanges(orig, ctx) ?? ctx.changes;
3190
- case "property-update":
3191
- if (ctx.changes.rotation !== void 0) {
3192
- return { ...ctx.changes, ...basePropertyRotationChanges(orig, ctx.changes.rotation) };
3193
- }
3194
- return ctx.changes;
3195
- default:
3196
- return ctx.changes;
3563
+ const replaceTextSelectionHandler = {
3564
+ toolId: "replaceText",
3565
+ handle(context, selections, getText) {
3566
+ const tool = context.getTool();
3567
+ if (!tool) return;
3568
+ const getDefaults = () => ({
3569
+ strokeColor: tool.defaults.strokeColor ?? "#E44234",
3570
+ opacity: tool.defaults.opacity ?? 1,
3571
+ flags: tool.defaults.flags ?? ["print"]
3572
+ });
3573
+ for (const selection of selections) {
3574
+ const lastSegRect = selection.segmentRects[selection.segmentRects.length - 1];
3575
+ if (!lastSegRect) continue;
3576
+ const caretRect = computeCaretRect(lastSegRect);
3577
+ const caretId = uuidV4();
3578
+ const strikeoutId = uuidV4();
3579
+ const defaults = getDefaults();
3580
+ getText().then((text) => {
3581
+ var _a;
3582
+ context.createAnnotation(selection.pageIndex, {
3583
+ type: PdfAnnotationSubtype.CARET,
3584
+ id: caretId,
3585
+ pageIndex: selection.pageIndex,
3586
+ rect: caretRect,
3587
+ strokeColor: defaults.strokeColor,
3588
+ opacity: defaults.opacity,
3589
+ intent: "Replace",
3590
+ rectangleDifferences: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
3591
+ created: /* @__PURE__ */ new Date(),
3592
+ flags: defaults.flags
3593
+ });
3594
+ context.createAnnotation(selection.pageIndex, {
3595
+ type: PdfAnnotationSubtype.STRIKEOUT,
3596
+ id: strikeoutId,
3597
+ pageIndex: selection.pageIndex,
3598
+ rect: selection.rect,
3599
+ segmentRects: selection.segmentRects,
3600
+ strokeColor: defaults.strokeColor,
3601
+ opacity: defaults.opacity,
3602
+ intent: "StrikeOutTextEdit",
3603
+ inReplyToId: caretId,
3604
+ replyType: PdfAnnotationReplyType.Group,
3605
+ created: /* @__PURE__ */ new Date(),
3606
+ flags: defaults.flags,
3607
+ ...text != null && { custom: { text } }
3608
+ });
3609
+ if ((_a = tool.behavior) == null ? void 0 : _a.selectAfterCreate) {
3610
+ context.selectAnnotation(selection.pageIndex, caretId);
3611
+ }
3612
+ });
3613
+ }
3197
3614
  }
3198
3615
  };
3199
3616
  const textMarkupTools = [
@@ -3657,7 +4074,7 @@ const freeTextTools = [
3657
4074
  name: "Free Text",
3658
4075
  labelKey: "annotation.freeText",
3659
4076
  categories: ["annotation", "markup"],
3660
- matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT ? 1 : 0,
4077
+ matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT && a.intent !== "FreeTextCallout" ? 1 : 0,
3661
4078
  interaction: {
3662
4079
  exclusive: false,
3663
4080
  cursor: "crosshair",
@@ -3697,6 +4114,44 @@ const freeTextTools = [
3697
4114
  pointerHandler: freeTextHandlerFactory
3698
4115
  }
3699
4116
  ];
4117
+ const calloutFreeTextTools = [
4118
+ {
4119
+ id: "freeTextCallout",
4120
+ name: "Callout",
4121
+ labelKey: "annotation.callout",
4122
+ categories: ["annotation", "markup"],
4123
+ matchScore: (a) => a.type === PdfAnnotationSubtype.FREETEXT && a.intent === "FreeTextCallout" ? 10 : 0,
4124
+ interaction: {
4125
+ exclusive: false,
4126
+ cursor: "crosshair",
4127
+ isDraggable: true,
4128
+ isResizable: false,
4129
+ isRotatable: false
4130
+ },
4131
+ defaults: {
4132
+ type: PdfAnnotationSubtype.FREETEXT,
4133
+ intent: "FreeTextCallout",
4134
+ contents: "Insert text",
4135
+ fontSize: 14,
4136
+ fontColor: "#E44234",
4137
+ fontFamily: PdfStandardFont.Helvetica,
4138
+ textAlign: PdfTextAlignment.Left,
4139
+ verticalAlign: PdfVerticalAlignment.Top,
4140
+ color: "transparent",
4141
+ opacity: 1,
4142
+ lineEnding: PdfAnnotationLineEnding.OpenArrow,
4143
+ strokeColor: "#E44234",
4144
+ strokeWidth: 1
4145
+ },
4146
+ behavior: {
4147
+ insertUpright: true,
4148
+ editAfterCreate: true,
4149
+ selectAfterCreate: true
4150
+ },
4151
+ transform: patchCalloutFreeText,
4152
+ pointerHandler: calloutFreeTextHandlerFactory
4153
+ }
4154
+ ];
3700
4155
  const stampTools = [
3701
4156
  {
3702
4157
  id: "stamp",
@@ -3763,6 +4218,7 @@ const defaultTools = [
3763
4218
  ...polygonTools,
3764
4219
  ...textCommentTools,
3765
4220
  ...freeTextTools,
4221
+ ...calloutFreeTextTools,
3766
4222
  ...stampTools,
3767
4223
  ...linkTools
3768
4224
  ];