@embedpdf/plugin-annotation 1.0.7 → 1.0.9

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.
@@ -5,77 +5,246 @@ var useAnnotationPlugin = () => usePlugin(AnnotationPlugin.id);
5
5
  var useAnnotationCapability = () => useCapability(AnnotationPlugin.id);
6
6
 
7
7
  // src/preact/components/annotations.tsx
8
- import { PdfAnnotationSubtype } from "@embedpdf/models";
8
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype2 } from "@embedpdf/models";
9
9
  import { usePointerHandlers } from "@embedpdf/plugin-interaction-manager/preact";
10
10
  import {
11
11
  getAnnotationsByPageIndex,
12
12
  getSelectedAnnotationByPageIndex
13
13
  } from "@embedpdf/plugin-annotation";
14
- import { useMemo, useState, useEffect } from "preact/hooks";
14
+ import { useMemo as useMemo2, useState as useState2, useEffect as useEffect2, useCallback } from "preact/hooks";
15
15
 
16
- // src/preact/components/selectable-container.tsx
17
- import { useCallback } from "preact/hooks";
18
- import { useSelectionCapability } from "@embedpdf/plugin-selection/preact";
16
+ // src/preact/components/annotation-container.tsx
17
+ import { useEffect, useRef, useState } from "preact/hooks";
18
+ import { restoreOffset } from "@embedpdf/models";
19
19
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
20
- function SelectableAnnotationContainer({
21
- trackedAnnotation,
20
+ function AnnotationContainer({
22
21
  scale,
23
- isSelected = false,
24
22
  pageIndex,
25
- children
23
+ rotation,
24
+ trackedAnnotation,
25
+ children,
26
+ style,
27
+ outlineOffset = 1,
28
+ isSelected = false,
29
+ isDraggable = true,
30
+ isResizable = true,
31
+ computeResizePatch,
32
+ ...props
26
33
  }) {
27
34
  const { provides: annotationProvides } = useAnnotationCapability();
28
- const { provides: selectionProvides } = useSelectionCapability();
29
- const handleClick = useCallback(
30
- (e) => {
31
- e.stopPropagation();
32
- if (annotationProvides && selectionProvides) {
33
- annotationProvides.selectAnnotation(pageIndex, trackedAnnotation.localId);
34
- selectionProvides.clear();
35
+ const ref = useRef(null);
36
+ const [dragState, setDragState] = useState("idle");
37
+ const [resizeDirection, setResizeDirection] = useState(null);
38
+ const [startPos, setStartPos] = useState(null);
39
+ const [startRect, setStartRect] = useState(null);
40
+ const [currentRect, setCurrentRect] = useState(trackedAnnotation.object.rect);
41
+ const [previewObject, setPreviewObject] = useState(null);
42
+ useEffect(() => {
43
+ setCurrentRect(trackedAnnotation.object.rect);
44
+ setPreviewObject(null);
45
+ }, [trackedAnnotation]);
46
+ const handlePointerDown = (e) => {
47
+ if (!isSelected) return;
48
+ e.stopPropagation();
49
+ e.preventDefault();
50
+ const target = e.target;
51
+ if (isResizable && target.classList.contains("resize-handle")) {
52
+ setDragState("resizing");
53
+ setResizeDirection(target.dataset.direction);
54
+ } else if (isDraggable) {
55
+ setDragState("dragging");
56
+ } else {
57
+ return;
58
+ }
59
+ setStartPos({ x: e.clientX, y: e.clientY });
60
+ setStartRect(currentRect);
61
+ ref.current?.setPointerCapture(e.pointerId);
62
+ };
63
+ const handlePointerMove = (e) => {
64
+ if (dragState === "idle" || !startPos || !startRect) return;
65
+ const dispDelta = { x: e.clientX - startPos.x, y: e.clientY - startPos.y };
66
+ const { x: dx, y: dy } = restoreOffset(dispDelta, rotation, scale);
67
+ let newOriginX = startRect.origin.x;
68
+ let newOriginY = startRect.origin.y;
69
+ let newWidth = startRect.size.width;
70
+ let newHeight = startRect.size.height;
71
+ if (dragState === "dragging") {
72
+ newOriginX += dx;
73
+ newOriginY += dy;
74
+ } else if (dragState === "resizing" && resizeDirection) {
75
+ if (resizeDirection.includes("right")) {
76
+ newWidth += dx;
77
+ } else if (resizeDirection.includes("left")) {
78
+ newOriginX += dx;
79
+ newWidth -= dx;
35
80
  }
36
- },
37
- [annotationProvides, selectionProvides, isSelected, pageIndex, trackedAnnotation.localId]
38
- );
39
- return /* @__PURE__ */ jsxs(Fragment, { children: [
40
- children,
41
- /* @__PURE__ */ jsx(
42
- "div",
43
- {
44
- className: "markup-annotation",
45
- style: {
46
- position: "absolute",
47
- mixBlendMode: "multiply",
48
- cursor: "pointer",
49
- outline: isSelected ? "2px solid #007ACC" : "none",
50
- outlineOffset: isSelected ? "1px" : "0px",
51
- left: `${trackedAnnotation.object.rect.origin.x * scale}px`,
52
- top: `${trackedAnnotation.object.rect.origin.y * scale}px`,
53
- width: `${trackedAnnotation.object.rect.size.width * scale}px`,
54
- height: `${trackedAnnotation.object.rect.size.height * scale}px`,
55
- zIndex: 1
56
- },
57
- onMouseDown: handleClick
81
+ if (resizeDirection.includes("bottom")) {
82
+ newHeight += dy;
83
+ } else if (resizeDirection.includes("top")) {
84
+ newOriginY += dy;
85
+ newHeight -= dy;
86
+ }
87
+ if (newWidth < 1 || newHeight < 1) return;
88
+ }
89
+ const tentativeRect = {
90
+ origin: { x: newOriginX, y: newOriginY },
91
+ size: { width: newWidth, height: newHeight }
92
+ };
93
+ let previewPatch = { rect: tentativeRect };
94
+ if (computeResizePatch) {
95
+ const dir = dragState === "resizing" ? resizeDirection : "bottom-right";
96
+ if (dir) {
97
+ previewPatch = computeResizePatch(trackedAnnotation.object, tentativeRect, dir);
98
+ }
99
+ }
100
+ setCurrentRect(previewPatch.rect || tentativeRect);
101
+ setPreviewObject(previewPatch);
102
+ };
103
+ const handlePointerUp = (e) => {
104
+ if (dragState === "idle") return;
105
+ const usedDirection = resizeDirection || "bottom-right";
106
+ setDragState("idle");
107
+ setResizeDirection(null);
108
+ ref.current?.releasePointerCapture(e.pointerId);
109
+ if (annotationProvides && trackedAnnotation) {
110
+ let patch = { rect: currentRect };
111
+ if (computeResizePatch && usedDirection) {
112
+ patch = computeResizePatch(trackedAnnotation.object, currentRect, usedDirection);
58
113
  }
59
- )
60
- ] });
114
+ annotationProvides.updateAnnotation(pageIndex, trackedAnnotation.localId, patch);
115
+ }
116
+ setStartPos(null);
117
+ setStartRect(null);
118
+ setPreviewObject(null);
119
+ };
120
+ const currentObject = previewObject ? { ...trackedAnnotation.object, ...previewObject } : trackedAnnotation.object;
121
+ return /* @__PURE__ */ jsxs(
122
+ "div",
123
+ {
124
+ ref,
125
+ onPointerDown: handlePointerDown,
126
+ onPointerMove: handlePointerMove,
127
+ onPointerUp: handlePointerUp,
128
+ style: {
129
+ position: "absolute",
130
+ outline: isSelected ? "1px solid #007ACC" : "none",
131
+ outlineOffset: isSelected ? `${outlineOffset}px` : "0px",
132
+ left: `${currentRect.origin.x * scale}px`,
133
+ top: `${currentRect.origin.y * scale}px`,
134
+ width: `${currentRect.size.width * scale}px`,
135
+ height: `${currentRect.size.height * scale}px`,
136
+ pointerEvents: isSelected ? "auto" : "none",
137
+ cursor: isSelected && isDraggable ? "move" : "default",
138
+ ...style
139
+ },
140
+ ...props,
141
+ children: [
142
+ typeof children === "function" ? children(currentObject) : children,
143
+ isSelected && isResizable && /* @__PURE__ */ jsxs(Fragment, { children: [
144
+ /* @__PURE__ */ jsx(
145
+ "div",
146
+ {
147
+ className: "resize-handle",
148
+ "data-direction": "top-left",
149
+ style: {
150
+ position: "absolute",
151
+ top: -7 - outlineOffset,
152
+ left: -7 - outlineOffset,
153
+ width: 13,
154
+ height: 13,
155
+ background: "blue",
156
+ borderRadius: "50%",
157
+ cursor: rotation % 2 ? "nesw-resize" : "nwse-resize"
158
+ }
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx(
162
+ "div",
163
+ {
164
+ className: "resize-handle",
165
+ "data-direction": "top-right",
166
+ style: {
167
+ position: "absolute",
168
+ top: -7 - outlineOffset,
169
+ right: -7 - outlineOffset,
170
+ width: 13,
171
+ height: 13,
172
+ background: "blue",
173
+ borderRadius: "50%",
174
+ cursor: rotation % 2 ? "nwse-resize" : "nesw-resize"
175
+ }
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx(
179
+ "div",
180
+ {
181
+ className: "resize-handle",
182
+ "data-direction": "bottom-left",
183
+ style: {
184
+ position: "absolute",
185
+ bottom: -7 - outlineOffset,
186
+ left: -7 - outlineOffset,
187
+ width: 13,
188
+ height: 13,
189
+ background: "blue",
190
+ borderRadius: "50%",
191
+ cursor: rotation % 2 ? "nwse-resize" : "nesw-resize"
192
+ }
193
+ }
194
+ ),
195
+ /* @__PURE__ */ jsx(
196
+ "div",
197
+ {
198
+ className: "resize-handle",
199
+ "data-direction": "bottom-right",
200
+ style: {
201
+ position: "absolute",
202
+ bottom: -7 - outlineOffset,
203
+ right: -7 - outlineOffset,
204
+ width: 13,
205
+ height: 13,
206
+ background: "blue",
207
+ borderRadius: "50%",
208
+ cursor: rotation % 2 ? "nesw-resize" : "nwse-resize"
209
+ }
210
+ }
211
+ )
212
+ ] })
213
+ ]
214
+ }
215
+ );
61
216
  }
62
217
 
63
218
  // src/preact/components/text-markup/highlight.tsx
64
219
  import { Fragment as Fragment2, jsx as jsx2 } from "preact/jsx-runtime";
65
- function Highlight({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
220
+ function Highlight({
221
+ color = "#FFFF00",
222
+ opacity = 0.5,
223
+ rects,
224
+ rect,
225
+ scale,
226
+ onClick,
227
+ style,
228
+ ...props
229
+ }) {
66
230
  return /* @__PURE__ */ jsx2(Fragment2, { children: rects.map((b, i) => /* @__PURE__ */ jsx2(
67
231
  "div",
68
232
  {
233
+ onMouseDown: onClick,
69
234
  style: {
70
235
  position: "absolute",
71
- left: b.origin.x * scale,
72
- top: b.origin.y * scale,
236
+ left: (rect ? b.origin.x - rect.origin.x : b.origin.x) * scale,
237
+ top: (rect ? b.origin.y - rect.origin.y : b.origin.y) * scale,
73
238
  width: b.size.width * scale,
74
239
  height: b.size.height * scale,
75
240
  background: color,
76
241
  opacity,
77
- pointerEvents: "none"
78
- }
242
+ pointerEvents: onClick ? "auto" : "none",
243
+ cursor: onClick ? "pointer" : "default",
244
+ zIndex: onClick ? 1 : null,
245
+ ...style
246
+ },
247
+ ...props
79
248
  },
80
249
  i
81
250
  )) });
@@ -83,21 +252,49 @@ function Highlight({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
83
252
 
84
253
  // src/preact/components/text-markup/underline.tsx
85
254
  import { Fragment as Fragment3, jsx as jsx3 } from "preact/jsx-runtime";
86
- function Underline({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
255
+ function Underline({
256
+ color = "#FFFF00",
257
+ opacity = 0.5,
258
+ rects,
259
+ rect,
260
+ scale,
261
+ onClick,
262
+ style,
263
+ ...props
264
+ }) {
87
265
  const thickness = 2 * scale;
88
266
  return /* @__PURE__ */ jsx3(Fragment3, { children: rects.map((r, i) => /* @__PURE__ */ jsx3(
89
267
  "div",
90
268
  {
269
+ onMouseDown: onClick,
91
270
  style: {
92
271
  position: "absolute",
93
- left: r.origin.x * scale,
94
- top: (r.origin.y + r.size.height) * scale - thickness,
272
+ left: (rect ? r.origin.x - rect.origin.x : r.origin.x) * scale,
273
+ top: (rect ? r.origin.y - rect.origin.y : r.origin.y) * scale,
95
274
  width: r.size.width * scale,
96
- height: thickness,
97
- background: color,
98
- opacity,
99
- pointerEvents: "none"
100
- }
275
+ height: r.size.height * scale,
276
+ background: "transparent",
277
+ pointerEvents: onClick ? "auto" : "none",
278
+ cursor: onClick ? "pointer" : "default",
279
+ zIndex: onClick ? 1 : 0,
280
+ ...style
281
+ },
282
+ ...props,
283
+ children: /* @__PURE__ */ jsx3(
284
+ "div",
285
+ {
286
+ style: {
287
+ position: "absolute",
288
+ left: 0,
289
+ bottom: 0,
290
+ width: "100%",
291
+ height: thickness,
292
+ background: color,
293
+ opacity,
294
+ pointerEvents: "none"
295
+ }
296
+ }
297
+ )
101
298
  },
102
299
  i
103
300
  )) });
@@ -105,21 +302,50 @@ function Underline({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
105
302
 
106
303
  // src/preact/components/text-markup/strikeout.tsx
107
304
  import { Fragment as Fragment4, jsx as jsx4 } from "preact/jsx-runtime";
108
- function Strikeout({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
305
+ function Strikeout({
306
+ color = "#FFFF00",
307
+ opacity = 0.5,
308
+ rects,
309
+ rect,
310
+ scale,
311
+ onClick,
312
+ style,
313
+ ...props
314
+ }) {
109
315
  const thickness = 2 * scale;
110
316
  return /* @__PURE__ */ jsx4(Fragment4, { children: rects.map((r, i) => /* @__PURE__ */ jsx4(
111
317
  "div",
112
318
  {
319
+ onMouseDown: onClick,
113
320
  style: {
114
321
  position: "absolute",
115
- left: r.origin.x * scale,
116
- top: (r.origin.y + r.size.height / 2) * scale - thickness / 2,
322
+ left: (rect ? r.origin.x - rect.origin.x : r.origin.x) * scale,
323
+ top: (rect ? r.origin.y - rect.origin.y : r.origin.y) * scale,
117
324
  width: r.size.width * scale,
118
- height: thickness,
119
- background: color,
120
- opacity,
121
- pointerEvents: "none"
122
- }
325
+ height: r.size.height * scale,
326
+ background: "transparent",
327
+ pointerEvents: onClick ? "auto" : "none",
328
+ cursor: onClick ? "pointer" : "default",
329
+ zIndex: onClick ? 1 : 0,
330
+ ...style
331
+ },
332
+ ...props,
333
+ children: /* @__PURE__ */ jsx4(
334
+ "div",
335
+ {
336
+ style: {
337
+ position: "absolute",
338
+ left: 0,
339
+ top: "50%",
340
+ width: "100%",
341
+ height: thickness,
342
+ background: color,
343
+ opacity,
344
+ transform: "translateY(-50%)",
345
+ pointerEvents: "none"
346
+ }
347
+ }
348
+ )
123
349
  },
124
350
  i
125
351
  )) });
@@ -127,7 +353,16 @@ function Strikeout({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
127
353
 
128
354
  // src/preact/components/text-markup/squiggly.tsx
129
355
  import { Fragment as Fragment5, jsx as jsx5 } from "preact/jsx-runtime";
130
- function Squiggly({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
356
+ function Squiggly({
357
+ color = "#FFFF00",
358
+ opacity = 0.5,
359
+ rects,
360
+ rect,
361
+ scale,
362
+ onClick,
363
+ style,
364
+ ...props
365
+ }) {
131
366
  const amplitude = 2 * scale;
132
367
  const period = 6 * scale;
133
368
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${period}" height="${amplitude * 2}" viewBox="0 0 ${period} ${amplitude * 2}">
@@ -138,31 +373,162 @@ function Squiggly({ color = "#FFFF00", opacity = 0.5, rects, scale }) {
138
373
  return /* @__PURE__ */ jsx5(Fragment5, { children: rects.map((r, i) => /* @__PURE__ */ jsx5(
139
374
  "div",
140
375
  {
376
+ onMouseDown: onClick,
141
377
  style: {
142
378
  position: "absolute",
143
- left: r.origin.x * scale,
144
- top: (r.origin.y + r.size.height) * scale - amplitude,
379
+ left: (rect ? r.origin.x - rect.origin.x : r.origin.x) * scale,
380
+ top: (rect ? r.origin.y - rect.origin.y : r.origin.y) * scale,
145
381
  width: r.size.width * scale,
146
- height: amplitude * 2,
147
- backgroundImage: svgDataUri,
148
- backgroundRepeat: "repeat-x",
149
- backgroundSize: `${period}px ${amplitude * 2}px`,
150
- opacity,
151
- pointerEvents: "none"
152
- }
382
+ height: r.size.height * scale,
383
+ background: "transparent",
384
+ pointerEvents: onClick ? "auto" : "none",
385
+ cursor: onClick ? "pointer" : "default",
386
+ zIndex: onClick ? 1 : 0,
387
+ ...style
388
+ },
389
+ ...props,
390
+ children: /* @__PURE__ */ jsx5(
391
+ "div",
392
+ {
393
+ style: {
394
+ position: "absolute",
395
+ left: 0,
396
+ bottom: 0,
397
+ width: "100%",
398
+ height: amplitude * 2,
399
+ backgroundImage: svgDataUri,
400
+ backgroundRepeat: "repeat-x",
401
+ backgroundSize: `${period}px ${amplitude * 2}px`,
402
+ opacity,
403
+ pointerEvents: "none"
404
+ }
405
+ }
406
+ )
153
407
  },
154
408
  i
155
409
  )) });
156
410
  }
157
411
 
412
+ // src/preact/components/annotations/ink.tsx
413
+ import { useMemo } from "preact/hooks";
414
+ import { jsx as jsx6 } from "preact/jsx-runtime";
415
+ function Ink({
416
+ color = "#000000",
417
+ opacity = 1,
418
+ strokeWidth,
419
+ inkList,
420
+ rect,
421
+ scale,
422
+ onClick
423
+ }) {
424
+ const paths = useMemo(() => {
425
+ return inkList.map(({ points }) => {
426
+ let d = "";
427
+ points.forEach(({ x, y }, i) => {
428
+ const lx = x - rect.origin.x;
429
+ const ly = y - rect.origin.y;
430
+ d += (i === 0 ? "M" : "L") + lx + " " + ly + " ";
431
+ });
432
+ return d.trim();
433
+ });
434
+ }, [inkList, rect]);
435
+ const width = rect.size.width * scale;
436
+ const height = rect.size.height * scale;
437
+ return /* @__PURE__ */ jsx6(
438
+ "svg",
439
+ {
440
+ style: {
441
+ position: "absolute",
442
+ width,
443
+ height,
444
+ pointerEvents: "none",
445
+ zIndex: 2
446
+ },
447
+ width,
448
+ height,
449
+ viewBox: `0 0 ${rect.size.width} ${rect.size.height}`,
450
+ children: paths.map((d, i) => /* @__PURE__ */ jsx6(
451
+ "path",
452
+ {
453
+ d,
454
+ fill: "none",
455
+ stroke: color,
456
+ strokeWidth,
457
+ strokeLinecap: "round",
458
+ strokeLinejoin: "round",
459
+ opacity,
460
+ pointerEvents: "visibleStroke",
461
+ onMouseDown: onClick,
462
+ style: {
463
+ cursor: "pointer"
464
+ }
465
+ },
466
+ i
467
+ ))
468
+ }
469
+ );
470
+ }
471
+
158
472
  // src/preact/components/annotations.tsx
159
- import { Fragment as Fragment6, jsx as jsx6 } from "preact/jsx-runtime";
160
- function Annotations({ pageIndex, scale }) {
473
+ import { useSelectionCapability } from "@embedpdf/plugin-selection/preact";
474
+
475
+ // src/shared/resize-ink.ts
476
+ import {
477
+ PdfAnnotationSubtype
478
+ } from "@embedpdf/models";
479
+ function resizeInkAnnotation(original, newRect, direction, uniform = false) {
480
+ if (original.type !== PdfAnnotationSubtype.INK) {
481
+ throw new Error("resizeInkAnnotation: original is not an ink annotation");
482
+ }
483
+ const oldRect = original.rect;
484
+ let scaleX = newRect.size.width / oldRect.size.width;
485
+ let scaleY = newRect.size.height / oldRect.size.height;
486
+ const minSize = 10;
487
+ if (newRect.size.width < minSize || newRect.size.height < minSize) {
488
+ scaleX = Math.max(scaleX, minSize / oldRect.size.width);
489
+ scaleY = Math.max(scaleY, minSize / oldRect.size.height);
490
+ newRect = {
491
+ origin: newRect.origin,
492
+ size: {
493
+ width: oldRect.size.width * scaleX,
494
+ height: oldRect.size.height * scaleY
495
+ }
496
+ };
497
+ }
498
+ if (uniform) {
499
+ const minScale = Math.min(scaleX, scaleY);
500
+ scaleX = minScale;
501
+ scaleY = minScale;
502
+ newRect.size = {
503
+ width: oldRect.size.width * minScale,
504
+ height: oldRect.size.height * minScale
505
+ };
506
+ }
507
+ const newInkList = original.inkList.map((stroke) => ({
508
+ points: stroke.points.map((p) => ({
509
+ x: newRect.origin.x + (p.x - oldRect.origin.x) * scaleX,
510
+ y: newRect.origin.y + (p.y - oldRect.origin.y) * scaleY
511
+ }))
512
+ }));
513
+ const avgScale = (scaleX + scaleY) / 2;
514
+ const newStrokeWidth = original.strokeWidth * avgScale;
515
+ return {
516
+ rect: newRect,
517
+ inkList: newInkList,
518
+ strokeWidth: newStrokeWidth
519
+ };
520
+ }
521
+
522
+ // src/preact/components/annotations.tsx
523
+ import { Fragment as Fragment6, jsx as jsx7 } from "preact/jsx-runtime";
524
+ function Annotations(annotationsProps) {
525
+ const { pageIndex, scale } = annotationsProps;
161
526
  const { provides: annotationProvides } = useAnnotationCapability();
162
- const [annotations, setAnnotations] = useState([]);
527
+ const { provides: selectionProvides } = useSelectionCapability();
528
+ const [annotations, setAnnotations] = useState2([]);
163
529
  const { register } = usePointerHandlers({ pageIndex });
164
- const [selectionState, setSelectionState] = useState(null);
165
- useEffect(() => {
530
+ const [selectionState, setSelectionState] = useState2(null);
531
+ useEffect2(() => {
166
532
  if (annotationProvides) {
167
533
  annotationProvides.onStateChange((state) => {
168
534
  setAnnotations(getAnnotationsByPageIndex(state, pageIndex));
@@ -170,7 +536,7 @@ function Annotations({ pageIndex, scale }) {
170
536
  });
171
537
  }
172
538
  }, [annotationProvides]);
173
- const handlers = useMemo(
539
+ const handlers = useMemo2(
174
540
  () => ({
175
541
  onPointerDown: (_, pe) => {
176
542
  if (pe.target === pe.currentTarget && annotationProvides) {
@@ -180,87 +546,137 @@ function Annotations({ pageIndex, scale }) {
180
546
  }),
181
547
  [annotationProvides]
182
548
  );
183
- useEffect(() => {
549
+ const handleClick = useCallback(
550
+ (e, annotation) => {
551
+ e.stopPropagation();
552
+ if (annotationProvides && selectionProvides) {
553
+ annotationProvides.selectAnnotation(pageIndex, annotation.localId);
554
+ selectionProvides.clear();
555
+ }
556
+ },
557
+ [annotationProvides, selectionProvides, pageIndex]
558
+ );
559
+ useEffect2(() => {
184
560
  return register(handlers);
185
561
  }, [register, handlers]);
186
- return /* @__PURE__ */ jsx6(Fragment6, { children: annotations.map((annotation) => {
562
+ return /* @__PURE__ */ jsx7(Fragment6, { children: annotations.map((annotation) => {
187
563
  const isSelected = selectionState?.localId === annotation.localId;
188
564
  switch (annotation.object.type) {
189
- case PdfAnnotationSubtype.UNDERLINE:
190
- return /* @__PURE__ */ jsx6(
191
- SelectableAnnotationContainer,
565
+ case PdfAnnotationSubtype2.UNDERLINE:
566
+ return /* @__PURE__ */ jsx7(
567
+ AnnotationContainer,
192
568
  {
193
569
  trackedAnnotation: annotation,
194
- scale,
195
570
  isSelected,
196
- pageIndex,
197
- children: /* @__PURE__ */ jsx6(
571
+ isDraggable: false,
572
+ isResizable: false,
573
+ style: { mixBlendMode: "multiply" },
574
+ ...annotationsProps,
575
+ children: /* @__PURE__ */ jsx7(
198
576
  Underline,
199
577
  {
578
+ rect: annotation.object.rect,
200
579
  color: annotation.object.color,
201
580
  opacity: annotation.object.opacity,
202
581
  rects: annotation.object.segmentRects,
203
- scale
582
+ scale,
583
+ onClick: (e) => handleClick(e, annotation)
204
584
  }
205
585
  )
206
586
  },
207
587
  annotation.localId
208
588
  );
209
- case PdfAnnotationSubtype.STRIKEOUT:
210
- return /* @__PURE__ */ jsx6(
211
- SelectableAnnotationContainer,
589
+ case PdfAnnotationSubtype2.STRIKEOUT:
590
+ return /* @__PURE__ */ jsx7(
591
+ AnnotationContainer,
212
592
  {
213
593
  trackedAnnotation: annotation,
214
- scale,
215
594
  isSelected,
216
- pageIndex,
217
- children: /* @__PURE__ */ jsx6(
595
+ isDraggable: false,
596
+ isResizable: false,
597
+ style: { mixBlendMode: "multiply" },
598
+ ...annotationsProps,
599
+ children: /* @__PURE__ */ jsx7(
218
600
  Strikeout,
219
601
  {
602
+ rect: annotation.object.rect,
220
603
  color: annotation.object.color,
221
604
  opacity: annotation.object.opacity,
222
605
  rects: annotation.object.segmentRects,
223
- scale
606
+ scale,
607
+ onClick: (e) => handleClick(e, annotation)
224
608
  }
225
609
  )
226
610
  },
227
611
  annotation.localId
228
612
  );
229
- case PdfAnnotationSubtype.SQUIGGLY:
230
- return /* @__PURE__ */ jsx6(
231
- SelectableAnnotationContainer,
613
+ case PdfAnnotationSubtype2.SQUIGGLY:
614
+ return /* @__PURE__ */ jsx7(
615
+ AnnotationContainer,
232
616
  {
233
617
  trackedAnnotation: annotation,
234
- scale,
235
618
  isSelected,
236
- pageIndex,
237
- children: /* @__PURE__ */ jsx6(
619
+ isDraggable: false,
620
+ isResizable: false,
621
+ style: { mixBlendMode: "multiply" },
622
+ ...annotationsProps,
623
+ children: /* @__PURE__ */ jsx7(
238
624
  Squiggly,
239
625
  {
240
626
  color: annotation.object.color,
241
627
  opacity: annotation.object.opacity,
242
628
  rects: annotation.object.segmentRects,
243
- scale
629
+ rect: annotation.object.rect,
630
+ scale,
631
+ onClick: (e) => handleClick(e, annotation)
244
632
  }
245
633
  )
246
634
  },
247
635
  annotation.localId
248
636
  );
249
- case PdfAnnotationSubtype.HIGHLIGHT:
250
- return /* @__PURE__ */ jsx6(
251
- SelectableAnnotationContainer,
637
+ case PdfAnnotationSubtype2.HIGHLIGHT:
638
+ return /* @__PURE__ */ jsx7(
639
+ AnnotationContainer,
252
640
  {
253
641
  trackedAnnotation: annotation,
254
- scale,
255
642
  isSelected,
256
- pageIndex,
257
- children: /* @__PURE__ */ jsx6(
643
+ isDraggable: false,
644
+ isResizable: false,
645
+ style: { mixBlendMode: "multiply" },
646
+ ...annotationsProps,
647
+ children: /* @__PURE__ */ jsx7(
258
648
  Highlight,
259
649
  {
260
650
  color: annotation.object.color,
261
651
  opacity: annotation.object.opacity,
262
652
  rects: annotation.object.segmentRects,
263
- scale
653
+ scale,
654
+ rect: annotation.object.rect,
655
+ onClick: (e) => handleClick(e, annotation)
656
+ }
657
+ )
658
+ },
659
+ annotation.localId
660
+ );
661
+ case PdfAnnotationSubtype2.INK:
662
+ return /* @__PURE__ */ jsx7(
663
+ AnnotationContainer,
664
+ {
665
+ isSelected,
666
+ trackedAnnotation: annotation,
667
+ outlineOffset: 6,
668
+ computeResizePatch: resizeInkAnnotation,
669
+ ...annotationsProps,
670
+ children: (obj) => /* @__PURE__ */ jsx7(
671
+ Ink,
672
+ {
673
+ color: obj.color,
674
+ opacity: obj.opacity,
675
+ strokeWidth: obj.strokeWidth,
676
+ inkList: obj.inkList,
677
+ rect: obj.rect,
678
+ scale,
679
+ onClick: (e) => handleClick(e, annotation)
264
680
  }
265
681
  )
266
682
  },
@@ -273,66 +689,113 @@ function Annotations({ pageIndex, scale }) {
273
689
  }
274
690
 
275
691
  // src/preact/components/text-markup.tsx
276
- import { PdfAnnotationSubtype as PdfAnnotationSubtype2 } from "@embedpdf/models";
692
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype3 } from "@embedpdf/models";
277
693
  import { useSelectionCapability as useSelectionCapability2 } from "@embedpdf/plugin-selection/preact";
278
- import { useEffect as useEffect2, useState as useState2 } from "preact/hooks";
279
- import { jsx as jsx7 } from "preact/jsx-runtime";
694
+ import { useEffect as useEffect3, useState as useState3 } from "preact/hooks";
695
+ import { jsx as jsx8 } from "preact/jsx-runtime";
280
696
  function TextMarkup({ pageIndex, scale }) {
281
697
  const { provides: selectionProvides } = useSelectionCapability2();
282
698
  const { provides: annotationProvides } = useAnnotationCapability();
283
- const [rects, setRects] = useState2([]);
284
- const [activeTool, setActiveTool] = useState2({ mode: null, defaults: null });
285
- useEffect2(() => {
699
+ const [rects, setRects] = useState3([]);
700
+ const [boundingRect, setBoundingRect] = useState3(null);
701
+ const [activeTool, setActiveTool] = useState3({ mode: null, defaults: null });
702
+ useEffect3(() => {
286
703
  if (!selectionProvides) return;
287
704
  const off = selectionProvides.onSelectionChange(() => {
288
705
  setRects(selectionProvides.getHighlightRectsForPage(pageIndex));
706
+ setBoundingRect(selectionProvides.getBoundingRectForPage(pageIndex));
289
707
  });
290
708
  return off;
291
709
  }, [selectionProvides, pageIndex]);
292
- useEffect2(() => {
710
+ useEffect3(() => {
293
711
  if (!annotationProvides) return;
294
712
  const off = annotationProvides.onActiveToolChange(setActiveTool);
295
713
  return off;
296
714
  }, [annotationProvides]);
715
+ if (!boundingRect) return null;
297
716
  switch (activeTool.mode) {
298
- case PdfAnnotationSubtype2.UNDERLINE:
299
- return /* @__PURE__ */ jsx7(
300
- Underline,
717
+ case PdfAnnotationSubtype3.UNDERLINE:
718
+ return /* @__PURE__ */ jsx8(
719
+ "div",
301
720
  {
302
- color: activeTool.defaults?.color,
303
- opacity: activeTool.defaults?.opacity,
304
- rects,
305
- scale
721
+ style: {
722
+ mixBlendMode: "multiply",
723
+ pointerEvents: "none",
724
+ position: "absolute",
725
+ inset: 0
726
+ },
727
+ children: /* @__PURE__ */ jsx8(
728
+ Underline,
729
+ {
730
+ color: activeTool.defaults?.color,
731
+ opacity: activeTool.defaults?.opacity,
732
+ rects,
733
+ scale
734
+ }
735
+ )
306
736
  }
307
737
  );
308
- case PdfAnnotationSubtype2.HIGHLIGHT:
309
- return /* @__PURE__ */ jsx7(
310
- Highlight,
738
+ case PdfAnnotationSubtype3.HIGHLIGHT:
739
+ return /* @__PURE__ */ jsx8(
740
+ "div",
311
741
  {
312
- color: activeTool.defaults?.color,
313
- opacity: activeTool.defaults?.opacity,
314
- rects,
315
- scale
742
+ style: {
743
+ mixBlendMode: "multiply",
744
+ pointerEvents: "none",
745
+ position: "absolute",
746
+ inset: 0
747
+ },
748
+ children: /* @__PURE__ */ jsx8(
749
+ Highlight,
750
+ {
751
+ color: activeTool.defaults?.color,
752
+ opacity: activeTool.defaults?.opacity,
753
+ rects,
754
+ scale
755
+ }
756
+ )
316
757
  }
317
758
  );
318
- case PdfAnnotationSubtype2.STRIKEOUT:
319
- return /* @__PURE__ */ jsx7(
320
- Strikeout,
759
+ case PdfAnnotationSubtype3.STRIKEOUT:
760
+ return /* @__PURE__ */ jsx8(
761
+ "div",
321
762
  {
322
- color: activeTool.defaults?.color,
323
- opacity: activeTool.defaults?.opacity,
324
- rects,
325
- scale
763
+ style: {
764
+ mixBlendMode: "multiply",
765
+ pointerEvents: "none",
766
+ position: "absolute",
767
+ inset: 0
768
+ },
769
+ children: /* @__PURE__ */ jsx8(
770
+ Strikeout,
771
+ {
772
+ color: activeTool.defaults?.color,
773
+ opacity: activeTool.defaults?.opacity,
774
+ rects,
775
+ scale
776
+ }
777
+ )
326
778
  }
327
779
  );
328
- case PdfAnnotationSubtype2.SQUIGGLY:
329
- return /* @__PURE__ */ jsx7(
330
- Squiggly,
780
+ case PdfAnnotationSubtype3.SQUIGGLY:
781
+ return /* @__PURE__ */ jsx8(
782
+ "div",
331
783
  {
332
- color: activeTool.defaults?.color,
333
- opacity: activeTool.defaults?.opacity,
334
- rects,
335
- scale
784
+ style: {
785
+ mixBlendMode: "multiply",
786
+ pointerEvents: "none",
787
+ position: "absolute",
788
+ inset: 0
789
+ },
790
+ children: /* @__PURE__ */ jsx8(
791
+ Squiggly,
792
+ {
793
+ color: activeTool.defaults?.color,
794
+ opacity: activeTool.defaults?.opacity,
795
+ rects,
796
+ scale
797
+ }
798
+ )
336
799
  }
337
800
  );
338
801
  default:
@@ -340,9 +803,194 @@ function TextMarkup({ pageIndex, scale }) {
340
803
  }
341
804
  }
342
805
 
806
+ // src/preact/components/annotations/ink-paint.tsx
807
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState4 } from "preact/hooks";
808
+ import { usePointerHandlers as usePointerHandlers2 } from "@embedpdf/plugin-interaction-manager/preact";
809
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype4 } from "@embedpdf/models";
810
+ import { jsx as jsx9 } from "preact/jsx-runtime";
811
+ var MAX_STROKE_WIDTH = 30;
812
+ var InkPaint = ({ pageIndex, scale, pageWidth, pageHeight }) => {
813
+ const { provides: annotationProvides } = useAnnotationCapability();
814
+ const [activeTool, setActiveTool] = useState4({ mode: null, defaults: null });
815
+ useEffect4(() => {
816
+ if (!annotationProvides) return;
817
+ const off = annotationProvides.onActiveToolChange(setActiveTool);
818
+ return off;
819
+ }, [annotationProvides]);
820
+ if (activeTool.mode !== PdfAnnotationSubtype4.INK) return null;
821
+ const toolColor = activeTool.defaults?.color ?? "#000000";
822
+ const toolOpacity = activeTool.defaults?.opacity ?? 1;
823
+ const toolStrokeWidth = activeTool.defaults?.strokeWidth ?? 2;
824
+ const { register } = usePointerHandlers2({ modeId: "ink", pageIndex });
825
+ const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
826
+ const [currentStrokes, setCurrentStrokes] = useState4([]);
827
+ const [isDrawing, setIsDrawing] = useState4(false);
828
+ const timerRef = useRef2(null);
829
+ const pageWidthPDF = pageWidth / scale;
830
+ const pageHeightPDF = pageHeight / scale;
831
+ const handlers = useMemo3(
832
+ () => ({
833
+ onPointerDown: (pos, evt) => {
834
+ const curX = clamp(pos.x, 0, pageWidthPDF);
835
+ const curY = clamp(pos.y, 0, pageHeightPDF);
836
+ setIsDrawing(true);
837
+ if (timerRef.current) {
838
+ clearTimeout(timerRef.current);
839
+ timerRef.current = null;
840
+ setCurrentStrokes((prev) => [...prev, { points: [{ x: curX, y: curY }] }]);
841
+ } else {
842
+ setCurrentStrokes([{ points: [{ x: curX, y: curY }] }]);
843
+ }
844
+ evt.target?.setPointerCapture?.(evt.pointerId);
845
+ },
846
+ onPointerMove: (pos) => {
847
+ if (!isDrawing) return;
848
+ const curX = clamp(pos.x, 0, pageWidthPDF);
849
+ const curY = clamp(pos.y, 0, pageHeightPDF);
850
+ setCurrentStrokes((prev) => {
851
+ if (!prev.length) return prev;
852
+ const last = prev[prev.length - 1];
853
+ const newLast = { points: [...last.points, { x: curX, y: curY }] };
854
+ return [...prev.slice(0, -1), newLast];
855
+ });
856
+ },
857
+ onPointerUp: (_, evt) => {
858
+ setIsDrawing(false);
859
+ evt.target?.releasePointerCapture?.(evt.pointerId);
860
+ if (timerRef.current) clearTimeout(timerRef.current);
861
+ timerRef.current = setTimeout(() => {
862
+ if (currentStrokes.length && annotationProvides) {
863
+ const allPoints2 = currentStrokes.flatMap((s) => s.points);
864
+ if (!allPoints2.length) return;
865
+ const minX2 = Math.min(...allPoints2.map((p) => p.x));
866
+ const minY2 = Math.min(...allPoints2.map((p) => p.y));
867
+ const maxX2 = Math.max(...allPoints2.map((p) => p.x));
868
+ const maxY2 = Math.max(...allPoints2.map((p) => p.y));
869
+ const halfStroke2 = MAX_STROKE_WIDTH / 2;
870
+ const rectMinX = minX2 - halfStroke2;
871
+ const rectMinY = minY2 - halfStroke2;
872
+ const rectMaxX = maxX2 + halfStroke2;
873
+ const rectMaxY = maxY2 + halfStroke2;
874
+ if (rectMaxX - rectMinX < 1 || rectMaxY - rectMinY < 1) return;
875
+ const rect = {
876
+ origin: { x: rectMinX, y: rectMinY },
877
+ size: { width: rectMaxX - rectMinX, height: rectMaxY - rectMinY }
878
+ };
879
+ const anno = {
880
+ type: PdfAnnotationSubtype4.INK,
881
+ rect,
882
+ inkList: currentStrokes,
883
+ color: toolColor,
884
+ opacity: toolOpacity,
885
+ strokeWidth: toolStrokeWidth,
886
+ pageIndex,
887
+ id: Date.now() + Math.random()
888
+ };
889
+ annotationProvides.createAnnotation(pageIndex, anno);
890
+ annotationProvides.setAnnotationMode(null);
891
+ annotationProvides.selectAnnotation(pageIndex, anno.id);
892
+ }
893
+ setCurrentStrokes([]);
894
+ timerRef.current = null;
895
+ }, 3e3);
896
+ },
897
+ onPointerCancel: (_, evt) => {
898
+ setIsDrawing(false);
899
+ evt.target?.releasePointerCapture?.(evt.pointerId);
900
+ setCurrentStrokes([]);
901
+ if (timerRef.current) {
902
+ clearTimeout(timerRef.current);
903
+ timerRef.current = null;
904
+ }
905
+ }
906
+ }),
907
+ [
908
+ pageWidthPDF,
909
+ pageHeightPDF,
910
+ currentStrokes,
911
+ annotationProvides,
912
+ pageIndex,
913
+ toolColor,
914
+ toolOpacity,
915
+ toolStrokeWidth,
916
+ isDrawing
917
+ ]
918
+ );
919
+ useEffect4(() => {
920
+ if (!register) return;
921
+ return register(handlers);
922
+ }, [register, handlers]);
923
+ useEffect4(() => {
924
+ return () => {
925
+ if (timerRef.current) clearTimeout(timerRef.current);
926
+ };
927
+ }, []);
928
+ if (!currentStrokes.length) return null;
929
+ const allPoints = currentStrokes.flatMap((s) => s.points);
930
+ if (!allPoints.length) return null;
931
+ const minX = Math.min(...allPoints.map((p) => p.x));
932
+ const minY = Math.min(...allPoints.map((p) => p.y));
933
+ const maxX = Math.max(...allPoints.map((p) => p.x));
934
+ const maxY = Math.max(...allPoints.map((p) => p.y));
935
+ const halfStroke = MAX_STROKE_WIDTH / 2;
936
+ const svgMinX = minX - halfStroke;
937
+ const svgMinY = minY - halfStroke;
938
+ const svgMaxX = maxX + halfStroke;
939
+ const svgMaxY = maxY + halfStroke;
940
+ const dw = svgMaxX - svgMinX;
941
+ const dh = svgMaxY - svgMinY;
942
+ const paths = currentStrokes.map(({ points }) => {
943
+ let d = "";
944
+ points.forEach(({ x, y }, i) => {
945
+ const lx = x - svgMinX;
946
+ const ly = y - svgMinY;
947
+ d += (i === 0 ? "M" : "L") + lx + " " + ly + " ";
948
+ });
949
+ return d.trim();
950
+ });
951
+ return /* @__PURE__ */ jsx9(
952
+ "svg",
953
+ {
954
+ style: {
955
+ position: "absolute",
956
+ left: svgMinX * scale,
957
+ top: svgMinY * scale,
958
+ width: dw * scale,
959
+ height: dh * scale,
960
+ pointerEvents: "none",
961
+ zIndex: 2
962
+ },
963
+ width: dw * scale,
964
+ height: dh * scale,
965
+ viewBox: `0 0 ${dw} ${dh}`,
966
+ children: paths.map((d, i) => /* @__PURE__ */ jsx9(
967
+ "path",
968
+ {
969
+ d,
970
+ fill: "none",
971
+ stroke: toolColor,
972
+ strokeWidth: toolStrokeWidth,
973
+ strokeLinecap: "round",
974
+ strokeLinejoin: "round",
975
+ opacity: toolOpacity
976
+ },
977
+ i
978
+ ))
979
+ }
980
+ );
981
+ };
982
+
343
983
  // src/preact/components/annotation-layer.tsx
344
- import { jsx as jsx8, jsxs as jsxs2 } from "preact/jsx-runtime";
345
- function AnnotationLayer({ pageIndex, scale, style, ...props }) {
984
+ import { jsx as jsx10, jsxs as jsxs2 } from "preact/jsx-runtime";
985
+ function AnnotationLayer({
986
+ pageIndex,
987
+ scale,
988
+ pageWidth,
989
+ pageHeight,
990
+ rotation,
991
+ style,
992
+ ...props
993
+ }) {
346
994
  return /* @__PURE__ */ jsxs2(
347
995
  "div",
348
996
  {
@@ -351,8 +999,9 @@ function AnnotationLayer({ pageIndex, scale, style, ...props }) {
351
999
  },
352
1000
  ...props,
353
1001
  children: [
354
- /* @__PURE__ */ jsx8(Annotations, { pageIndex, scale }),
355
- /* @__PURE__ */ jsx8(TextMarkup, { pageIndex, scale })
1002
+ /* @__PURE__ */ jsx10(Annotations, { pageIndex, scale, rotation }),
1003
+ /* @__PURE__ */ jsx10(TextMarkup, { pageIndex, scale }),
1004
+ /* @__PURE__ */ jsx10(InkPaint, { pageIndex, scale, pageWidth, pageHeight })
356
1005
  ]
357
1006
  }
358
1007
  );