@embedpdf/plugin-annotation 1.0.6 → 1.0.8

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.
@@ -4,137 +4,539 @@ import { AnnotationPlugin } from "@embedpdf/plugin-annotation";
4
4
  var useAnnotationPlugin = () => usePlugin(AnnotationPlugin.id);
5
5
  var useAnnotationCapability = () => useCapability(AnnotationPlugin.id);
6
6
 
7
- // src/preact/components/annotation-layer.tsx
8
- import { useEffect, useMemo, useState } from "preact/hooks";
9
-
10
- // ../models/dist/index.js
11
- var PdfSoftHyphenMarker = "\xAD";
12
- var PdfZeroWidthSpace = "\u200B";
13
- var PdfWordJoiner = "\u2060";
14
- var PdfBomOrZwnbsp = "\uFEFF";
15
- var PdfNonCharacterFFFE = "\uFFFE";
16
- var PdfNonCharacterFFFF = "\uFFFF";
17
- var PdfUnwantedTextMarkers = Object.freeze([
18
- PdfSoftHyphenMarker,
19
- PdfZeroWidthSpace,
20
- PdfWordJoiner,
21
- PdfBomOrZwnbsp,
22
- PdfNonCharacterFFFE,
23
- PdfNonCharacterFFFF
24
- ]);
25
- var PdfUnwantedTextRegex = new RegExp(`[${PdfUnwantedTextMarkers.join("")}]`, "g");
26
- var PdfAnnotationSubtype = /* @__PURE__ */ ((PdfAnnotationSubtype2) => {
27
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNKNOWN"] = 0] = "UNKNOWN";
28
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TEXT"] = 1] = "TEXT";
29
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINK"] = 2] = "LINK";
30
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FREETEXT"] = 3] = "FREETEXT";
31
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINE"] = 4] = "LINE";
32
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUARE"] = 5] = "SQUARE";
33
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CIRCLE"] = 6] = "CIRCLE";
34
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYGON"] = 7] = "POLYGON";
35
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYLINE"] = 8] = "POLYLINE";
36
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["HIGHLIGHT"] = 9] = "HIGHLIGHT";
37
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNDERLINE"] = 10] = "UNDERLINE";
38
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUIGGLY"] = 11] = "SQUIGGLY";
39
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STRIKEOUT"] = 12] = "STRIKEOUT";
40
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STAMP"] = 13] = "STAMP";
41
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CARET"] = 14] = "CARET";
42
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["INK"] = 15] = "INK";
43
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POPUP"] = 16] = "POPUP";
44
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FILEATTACHMENT"] = 17] = "FILEATTACHMENT";
45
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SOUND"] = 18] = "SOUND";
46
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["MOVIE"] = 19] = "MOVIE";
47
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WIDGET"] = 20] = "WIDGET";
48
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SCREEN"] = 21] = "SCREEN";
49
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["PRINTERMARK"] = 22] = "PRINTERMARK";
50
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TRAPNET"] = 23] = "TRAPNET";
51
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WATERMARK"] = 24] = "WATERMARK";
52
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["THREED"] = 25] = "THREED";
53
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["RICHMEDIA"] = 26] = "RICHMEDIA";
54
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["XFAWIDGET"] = 27] = "XFAWIDGET";
55
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["REDACT"] = 28] = "REDACT";
56
- return PdfAnnotationSubtype2;
57
- })(PdfAnnotationSubtype || {});
58
-
59
- // src/lib/selectors.ts
60
- var getAnnotationsByPageIndex = (state, pageIndex) => {
61
- return state.annotations[pageIndex] || [];
62
- };
7
+ // src/preact/components/annotations.tsx
8
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype2 } from "@embedpdf/models";
9
+ import { usePointerHandlers } from "@embedpdf/plugin-interaction-manager/preact";
10
+ import {
11
+ getAnnotationsByPageIndex,
12
+ getSelectedAnnotationByPageIndex
13
+ } from "@embedpdf/plugin-annotation";
14
+ import { useMemo as useMemo2, useState as useState2, useEffect as useEffect2, useCallback } from "preact/hooks";
63
15
 
64
- // src/preact/components/annotations/highlight.tsx
65
- import { useCallback } from "preact/hooks";
66
- import { Fragment, jsx } from "preact/jsx-runtime";
67
- function HighlightAnnotation({
68
- annotation,
16
+ // src/preact/components/annotation-container.tsx
17
+ import { useEffect, useRef, useState } from "preact/hooks";
18
+ import { restoreOffset } from "@embedpdf/models";
19
+ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
20
+ function AnnotationContainer({
69
21
  scale,
22
+ pageIndex,
23
+ rotation,
24
+ trackedAnnotation,
25
+ children,
26
+ style,
27
+ outlineOffset = 1,
70
28
  isSelected = false,
71
- pageIndex
29
+ isDraggable = true,
30
+ isResizable = true,
31
+ computeResizePatch,
32
+ ...props
72
33
  }) {
73
34
  const { provides: annotationProvides } = useAnnotationCapability();
74
- const highlightColor = annotation.color || { red: 255, green: 255, blue: 0, alpha: 76 };
75
- const rgbaColor = `rgba(${highlightColor.red}, ${highlightColor.green}, ${highlightColor.blue}, ${highlightColor.alpha / 255})`;
76
- const handleClick = useCallback(
77
- (e) => {
78
- e.stopPropagation();
79
- if (annotationProvides) {
80
- annotationProvides.selectAnnotation(pageIndex, annotation.id);
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;
81
80
  }
82
- },
83
- [annotationProvides, isSelected, pageIndex, annotation.id]
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);
113
+ }
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
+ }
84
215
  );
85
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
216
+ }
217
+
218
+ // src/preact/components/text-markup/highlight.tsx
219
+ import { Fragment as Fragment2, jsx as jsx2 } from "preact/jsx-runtime";
220
+ function Highlight({
221
+ color = "#FFFF00",
222
+ opacity = 0.5,
223
+ rects,
224
+ rect,
225
+ scale,
226
+ onClick,
227
+ style,
228
+ ...props
229
+ }) {
230
+ return /* @__PURE__ */ jsx2(Fragment2, { children: rects.map((b, i) => /* @__PURE__ */ jsx2(
231
+ "div",
232
+ {
233
+ onMouseDown: onClick,
234
+ style: {
235
+ position: "absolute",
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,
238
+ width: b.size.width * scale,
239
+ height: b.size.height * scale,
240
+ background: color,
241
+ opacity,
242
+ pointerEvents: onClick ? "auto" : "none",
243
+ cursor: onClick ? "pointer" : "default",
244
+ zIndex: onClick ? 1 : null,
245
+ ...style
246
+ },
247
+ ...props
248
+ },
249
+ i
250
+ )) });
251
+ }
252
+
253
+ // src/preact/components/text-markup/underline.tsx
254
+ import { Fragment as Fragment3, jsx as jsx3 } from "preact/jsx-runtime";
255
+ function Underline({
256
+ color = "#FFFF00",
257
+ opacity = 0.5,
258
+ rects,
259
+ rect,
260
+ scale,
261
+ onClick,
262
+ style,
263
+ ...props
264
+ }) {
265
+ const thickness = 2 * scale;
266
+ return /* @__PURE__ */ jsx3(Fragment3, { children: rects.map((r, i) => /* @__PURE__ */ jsx3(
267
+ "div",
268
+ {
269
+ onMouseDown: onClick,
270
+ style: {
271
+ position: "absolute",
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,
274
+ width: r.size.width * scale,
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
+ )
298
+ },
299
+ i
300
+ )) });
301
+ }
302
+
303
+ // src/preact/components/text-markup/strikeout.tsx
304
+ import { Fragment as Fragment4, jsx as jsx4 } from "preact/jsx-runtime";
305
+ function Strikeout({
306
+ color = "#FFFF00",
307
+ opacity = 0.5,
308
+ rects,
309
+ rect,
310
+ scale,
311
+ onClick,
312
+ style,
313
+ ...props
314
+ }) {
315
+ const thickness = 2 * scale;
316
+ return /* @__PURE__ */ jsx4(Fragment4, { children: rects.map((r, i) => /* @__PURE__ */ jsx4(
317
+ "div",
318
+ {
319
+ onMouseDown: onClick,
320
+ style: {
321
+ position: "absolute",
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,
324
+ width: r.size.width * scale,
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
+ )
349
+ },
350
+ i
351
+ )) });
352
+ }
353
+
354
+ // src/preact/components/text-markup/squiggly.tsx
355
+ import { Fragment as Fragment5, jsx as jsx5 } from "preact/jsx-runtime";
356
+ function Squiggly({
357
+ color = "#FFFF00",
358
+ opacity = 0.5,
359
+ rects,
360
+ rect,
361
+ scale,
362
+ onClick,
363
+ style,
364
+ ...props
365
+ }) {
366
+ const amplitude = 2 * scale;
367
+ const period = 6 * scale;
368
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${period}" height="${amplitude * 2}" viewBox="0 0 ${period} ${amplitude * 2}">
369
+ <path d="M0 ${amplitude} Q ${period / 4} 0 ${period / 2} ${amplitude} T ${period} ${amplitude}"
370
+ fill="none" stroke="${color}" stroke-width="${amplitude}" stroke-linecap="round"/>
371
+ </svg>`;
372
+ const svgDataUri = `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`;
373
+ return /* @__PURE__ */ jsx5(Fragment5, { children: rects.map((r, i) => /* @__PURE__ */ jsx5(
86
374
  "div",
87
375
  {
88
- className: "highlight-annotation",
376
+ onMouseDown: onClick,
89
377
  style: {
90
378
  position: "absolute",
91
- mixBlendMode: "multiply",
92
- cursor: "pointer",
93
- outline: isSelected ? "2px solid #007ACC" : "none",
94
- left: `${annotation.rect.origin.x * scale}px`,
95
- top: `${annotation.rect.origin.y * scale}px`,
96
- width: `${annotation.rect.size.width * scale}px`,
97
- height: `${annotation.rect.size.height * scale}px`
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,
381
+ width: r.size.width * scale,
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
98
388
  },
99
- onMouseDown: handleClick,
100
- children: annotation.segmentRects.map((rect, index) => /* @__PURE__ */ jsx(
389
+ ...props,
390
+ children: /* @__PURE__ */ jsx5(
101
391
  "div",
102
392
  {
103
393
  style: {
104
394
  position: "absolute",
105
- left: `${(rect.origin.x - annotation.rect.origin.x) * scale}px`,
106
- top: `${(rect.origin.y - annotation.rect.origin.y) * scale}px`,
107
- width: `${rect.size.width * scale}px`,
108
- height: `${rect.size.height * scale}px`,
109
- backgroundColor: rgbaColor
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
+ )
407
+ },
408
+ i
409
+ )) });
410
+ }
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"
110
464
  }
111
465
  },
112
- index
466
+ i
113
467
  ))
114
468
  }
115
- ) });
469
+ );
116
470
  }
117
471
 
118
- // src/preact/components/annotation-layer.tsx
119
- import { usePointerHandlers } from "@embedpdf/plugin-interaction-manager/preact";
120
- import { jsx as jsx2 } from "preact/jsx-runtime";
121
- function AnnotationLayer({ pageIndex, scale, style, ...props }) {
472
+ // src/preact/components/annotations.tsx
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;
122
526
  const { provides: annotationProvides } = useAnnotationCapability();
123
- const [annotations, setAnnotations] = useState([]);
527
+ const { provides: selectionProvides } = useSelectionCapability();
528
+ const [annotations, setAnnotations] = useState2([]);
124
529
  const { register } = usePointerHandlers({ pageIndex });
125
- const [selectionState, setSelectionState] = useState({ selectedPageIndex: void 0, selectedAnnotationId: void 0 });
126
- useEffect(() => {
530
+ const [selectionState, setSelectionState] = useState2(null);
531
+ useEffect2(() => {
127
532
  if (annotationProvides) {
128
533
  annotationProvides.onStateChange((state) => {
129
534
  setAnnotations(getAnnotationsByPageIndex(state, pageIndex));
130
- setSelectionState({
131
- selectedPageIndex: state.selectedAnnotation?.pageIndex,
132
- selectedAnnotationId: state.selectedAnnotation?.annotationId
133
- });
535
+ setSelectionState(getSelectedAnnotationByPageIndex(state, pageIndex));
134
536
  });
135
537
  }
136
538
  }, [annotationProvides]);
137
- const handlers = useMemo(
539
+ const handlers = useMemo2(
138
540
  () => ({
139
541
  onPointerDown: (_, pe) => {
140
542
  if (pe.target === pe.currentTarget && annotationProvides) {
@@ -144,34 +546,463 @@ function AnnotationLayer({ pageIndex, scale, style, ...props }) {
144
546
  }),
145
547
  [annotationProvides]
146
548
  );
147
- 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(() => {
148
560
  return register(handlers);
149
561
  }, [register, handlers]);
150
- return /* @__PURE__ */ jsx2(
562
+ return /* @__PURE__ */ jsx7(Fragment6, { children: annotations.map((annotation) => {
563
+ const isSelected = selectionState?.localId === annotation.localId;
564
+ switch (annotation.object.type) {
565
+ case PdfAnnotationSubtype2.UNDERLINE:
566
+ return /* @__PURE__ */ jsx7(
567
+ AnnotationContainer,
568
+ {
569
+ trackedAnnotation: annotation,
570
+ isSelected,
571
+ isDraggable: false,
572
+ isResizable: false,
573
+ style: { mixBlendMode: "multiply" },
574
+ ...annotationsProps,
575
+ children: /* @__PURE__ */ jsx7(
576
+ Underline,
577
+ {
578
+ rect: annotation.object.rect,
579
+ color: annotation.object.color,
580
+ opacity: annotation.object.opacity,
581
+ rects: annotation.object.segmentRects,
582
+ scale,
583
+ onClick: (e) => handleClick(e, annotation)
584
+ }
585
+ )
586
+ },
587
+ annotation.localId
588
+ );
589
+ case PdfAnnotationSubtype2.STRIKEOUT:
590
+ return /* @__PURE__ */ jsx7(
591
+ AnnotationContainer,
592
+ {
593
+ trackedAnnotation: annotation,
594
+ isSelected,
595
+ isDraggable: false,
596
+ isResizable: false,
597
+ style: { mixBlendMode: "multiply" },
598
+ ...annotationsProps,
599
+ children: /* @__PURE__ */ jsx7(
600
+ Strikeout,
601
+ {
602
+ rect: annotation.object.rect,
603
+ color: annotation.object.color,
604
+ opacity: annotation.object.opacity,
605
+ rects: annotation.object.segmentRects,
606
+ scale,
607
+ onClick: (e) => handleClick(e, annotation)
608
+ }
609
+ )
610
+ },
611
+ annotation.localId
612
+ );
613
+ case PdfAnnotationSubtype2.SQUIGGLY:
614
+ return /* @__PURE__ */ jsx7(
615
+ AnnotationContainer,
616
+ {
617
+ trackedAnnotation: annotation,
618
+ isSelected,
619
+ isDraggable: false,
620
+ isResizable: false,
621
+ style: { mixBlendMode: "multiply" },
622
+ ...annotationsProps,
623
+ children: /* @__PURE__ */ jsx7(
624
+ Squiggly,
625
+ {
626
+ color: annotation.object.color,
627
+ opacity: annotation.object.opacity,
628
+ rects: annotation.object.segmentRects,
629
+ rect: annotation.object.rect,
630
+ scale,
631
+ onClick: (e) => handleClick(e, annotation)
632
+ }
633
+ )
634
+ },
635
+ annotation.localId
636
+ );
637
+ case PdfAnnotationSubtype2.HIGHLIGHT:
638
+ return /* @__PURE__ */ jsx7(
639
+ AnnotationContainer,
640
+ {
641
+ trackedAnnotation: annotation,
642
+ isSelected,
643
+ isDraggable: false,
644
+ isResizable: false,
645
+ style: { mixBlendMode: "multiply" },
646
+ ...annotationsProps,
647
+ children: /* @__PURE__ */ jsx7(
648
+ Highlight,
649
+ {
650
+ color: annotation.object.color,
651
+ opacity: annotation.object.opacity,
652
+ rects: annotation.object.segmentRects,
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)
680
+ }
681
+ )
682
+ },
683
+ annotation.localId
684
+ );
685
+ default:
686
+ return null;
687
+ }
688
+ }) });
689
+ }
690
+
691
+ // src/preact/components/text-markup.tsx
692
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype3 } from "@embedpdf/models";
693
+ import { useSelectionCapability as useSelectionCapability2 } from "@embedpdf/plugin-selection/preact";
694
+ import { useEffect as useEffect3, useState as useState3 } from "preact/hooks";
695
+ import { jsx as jsx8 } from "preact/jsx-runtime";
696
+ function TextMarkup({ pageIndex, scale }) {
697
+ const { provides: selectionProvides } = useSelectionCapability2();
698
+ const { provides: annotationProvides } = useAnnotationCapability();
699
+ const [rects, setRects] = useState3([]);
700
+ const [boundingRect, setBoundingRect] = useState3(null);
701
+ const [activeTool, setActiveTool] = useState3({ mode: null, defaults: null });
702
+ useEffect3(() => {
703
+ if (!selectionProvides) return;
704
+ const off = selectionProvides.onSelectionChange(() => {
705
+ setRects(selectionProvides.getHighlightRectsForPage(pageIndex));
706
+ setBoundingRect(selectionProvides.getBoundingRectForPage(pageIndex));
707
+ });
708
+ return off;
709
+ }, [selectionProvides, pageIndex]);
710
+ useEffect3(() => {
711
+ if (!annotationProvides) return;
712
+ const off = annotationProvides.onActiveToolChange(setActiveTool);
713
+ return off;
714
+ }, [annotationProvides]);
715
+ if (!boundingRect) return null;
716
+ switch (activeTool.mode) {
717
+ case PdfAnnotationSubtype3.UNDERLINE:
718
+ return /* @__PURE__ */ jsx8(
719
+ "div",
720
+ {
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
+ )
736
+ }
737
+ );
738
+ case PdfAnnotationSubtype3.HIGHLIGHT:
739
+ return /* @__PURE__ */ jsx8(
740
+ "div",
741
+ {
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
+ )
757
+ }
758
+ );
759
+ case PdfAnnotationSubtype3.STRIKEOUT:
760
+ return /* @__PURE__ */ jsx8(
761
+ "div",
762
+ {
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
+ )
778
+ }
779
+ );
780
+ case PdfAnnotationSubtype3.SQUIGGLY:
781
+ return /* @__PURE__ */ jsx8(
782
+ "div",
783
+ {
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
+ )
799
+ }
800
+ );
801
+ default:
802
+ return null;
803
+ }
804
+ }
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
+
983
+ // src/preact/components/annotation-layer.tsx
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
+ }) {
994
+ return /* @__PURE__ */ jsxs2(
151
995
  "div",
152
996
  {
153
997
  style: {
154
998
  ...style
155
999
  },
156
1000
  ...props,
157
- children: annotations.map((annotation) => {
158
- const isSelected = selectionState.selectedPageIndex === pageIndex && selectionState.selectedAnnotationId === annotation.id;
159
- switch (annotation.type) {
160
- case PdfAnnotationSubtype.HIGHLIGHT:
161
- return /* @__PURE__ */ jsx2(
162
- HighlightAnnotation,
163
- {
164
- annotation,
165
- scale,
166
- isSelected,
167
- pageIndex
168
- },
169
- annotation.id
170
- );
171
- default:
172
- return null;
173
- }
174
- })
1001
+ children: [
1002
+ /* @__PURE__ */ jsx10(Annotations, { pageIndex, scale, rotation }),
1003
+ /* @__PURE__ */ jsx10(TextMarkup, { pageIndex, scale }),
1004
+ /* @__PURE__ */ jsx10(InkPaint, { pageIndex, scale, pageWidth, pageHeight })
1005
+ ]
175
1006
  }
176
1007
  );
177
1008
  }