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