react-pdf-highlighter-plus 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/esm/index.css +1221 -0
  2. package/dist/esm/index.css.map +1 -0
  3. package/dist/esm/index.d.ts +1348 -19
  4. package/dist/esm/index.js +3558 -18
  5. package/dist/esm/index.js.map +1 -1
  6. package/package.json +3 -2
  7. package/dist/esm/components/AreaHighlight.d.ts +0 -82
  8. package/dist/esm/components/AreaHighlight.js +0 -109
  9. package/dist/esm/components/AreaHighlight.js.map +0 -1
  10. package/dist/esm/components/DrawingCanvas.d.ts +0 -48
  11. package/dist/esm/components/DrawingCanvas.js +0 -277
  12. package/dist/esm/components/DrawingCanvas.js.map +0 -1
  13. package/dist/esm/components/DrawingHighlight.d.ts +0 -70
  14. package/dist/esm/components/DrawingHighlight.js +0 -164
  15. package/dist/esm/components/DrawingHighlight.js.map +0 -1
  16. package/dist/esm/components/FreetextHighlight.d.ts +0 -112
  17. package/dist/esm/components/FreetextHighlight.js +0 -193
  18. package/dist/esm/components/FreetextHighlight.js.map +0 -1
  19. package/dist/esm/components/HighlightLayer.d.ts +0 -49
  20. package/dist/esm/components/HighlightLayer.js +0 -37
  21. package/dist/esm/components/HighlightLayer.js.map +0 -1
  22. package/dist/esm/components/ImageHighlight.d.ts +0 -63
  23. package/dist/esm/components/ImageHighlight.js +0 -65
  24. package/dist/esm/components/ImageHighlight.js.map +0 -1
  25. package/dist/esm/components/MonitoredHighlightContainer.d.ts +0 -37
  26. package/dist/esm/components/MonitoredHighlightContainer.js +0 -42
  27. package/dist/esm/components/MonitoredHighlightContainer.js.map +0 -1
  28. package/dist/esm/components/MouseMonitor.d.ts +0 -34
  29. package/dist/esm/components/MouseMonitor.js +0 -30
  30. package/dist/esm/components/MouseMonitor.js.map +0 -1
  31. package/dist/esm/components/MouseSelection.d.ts +0 -66
  32. package/dist/esm/components/MouseSelection.js +0 -122
  33. package/dist/esm/components/MouseSelection.js.map +0 -1
  34. package/dist/esm/components/PdfHighlighter.d.ts +0 -184
  35. package/dist/esm/components/PdfHighlighter.js +0 -410
  36. package/dist/esm/components/PdfHighlighter.js.map +0 -1
  37. package/dist/esm/components/PdfLoader.d.ts +0 -55
  38. package/dist/esm/components/PdfLoader.js +0 -57
  39. package/dist/esm/components/PdfLoader.js.map +0 -1
  40. package/dist/esm/components/ShapeCanvas.d.ts +0 -51
  41. package/dist/esm/components/ShapeCanvas.js +0 -205
  42. package/dist/esm/components/ShapeCanvas.js.map +0 -1
  43. package/dist/esm/components/ShapeHighlight.d.ts +0 -107
  44. package/dist/esm/components/ShapeHighlight.js +0 -140
  45. package/dist/esm/components/ShapeHighlight.js.map +0 -1
  46. package/dist/esm/components/SignaturePad.d.ts +0 -40
  47. package/dist/esm/components/SignaturePad.js +0 -138
  48. package/dist/esm/components/SignaturePad.js.map +0 -1
  49. package/dist/esm/components/TextHighlight.d.ts +0 -93
  50. package/dist/esm/components/TextHighlight.js +0 -115
  51. package/dist/esm/components/TextHighlight.js.map +0 -1
  52. package/dist/esm/components/TipContainer.d.ts +0 -27
  53. package/dist/esm/components/TipContainer.js +0 -58
  54. package/dist/esm/components/TipContainer.js.map +0 -1
  55. package/dist/esm/contexts/HighlightContext.d.ts +0 -44
  56. package/dist/esm/contexts/HighlightContext.js +0 -16
  57. package/dist/esm/contexts/HighlightContext.js.map +0 -1
  58. package/dist/esm/contexts/PdfHighlighterContext.d.ts +0 -89
  59. package/dist/esm/contexts/PdfHighlighterContext.js +0 -16
  60. package/dist/esm/contexts/PdfHighlighterContext.js.map +0 -1
  61. package/dist/esm/lib/coordinates.d.ts +0 -16
  62. package/dist/esm/lib/coordinates.js +0 -69
  63. package/dist/esm/lib/coordinates.js.map +0 -1
  64. package/dist/esm/lib/export-pdf.d.ts +0 -81
  65. package/dist/esm/lib/export-pdf.js +0 -511
  66. package/dist/esm/lib/export-pdf.js.map +0 -1
  67. package/dist/esm/lib/get-bounding-rect.d.ts +0 -3
  68. package/dist/esm/lib/get-bounding-rect.js +0 -35
  69. package/dist/esm/lib/get-bounding-rect.js.map +0 -1
  70. package/dist/esm/lib/get-client-rects.d.ts +0 -3
  71. package/dist/esm/lib/get-client-rects.js +0 -43
  72. package/dist/esm/lib/get-client-rects.js.map +0 -1
  73. package/dist/esm/lib/group-highlights-by-page.d.ts +0 -6
  74. package/dist/esm/lib/group-highlights-by-page.js +0 -23
  75. package/dist/esm/lib/group-highlights-by-page.js.map +0 -1
  76. package/dist/esm/lib/optimize-client-rects.d.ts +0 -3
  77. package/dist/esm/lib/optimize-client-rects.js +0 -65
  78. package/dist/esm/lib/optimize-client-rects.js.map +0 -1
  79. package/dist/esm/lib/pdfjs-dom.d.ts +0 -9
  80. package/dist/esm/lib/pdfjs-dom.js +0 -55
  81. package/dist/esm/lib/pdfjs-dom.js.map +0 -1
  82. package/dist/esm/lib/screenshot.d.ts +0 -4
  83. package/dist/esm/lib/screenshot.js +0 -24
  84. package/dist/esm/lib/screenshot.js.map +0 -1
  85. package/dist/esm/types.d.ts +0 -213
  86. package/dist/esm/types.js +0 -2
  87. package/dist/esm/types.js.map +0 -1
package/dist/esm/index.js CHANGED
@@ -1,19 +1,3559 @@
1
- import { PdfHighlighter, } from "./components/PdfHighlighter";
2
- import { TextHighlight, } from "./components/TextHighlight";
3
- import { MonitoredHighlightContainer, } from "./components/MonitoredHighlightContainer";
4
- import { AreaHighlight, } from "./components/AreaHighlight";
5
- import { FreetextHighlight, } from "./components/FreetextHighlight";
6
- import { ImageHighlight, } from "./components/ImageHighlight";
7
- import { SignaturePad, } from "./components/SignaturePad";
8
- import { DrawingCanvas, } from "./components/DrawingCanvas";
9
- import { DrawingHighlight, } from "./components/DrawingHighlight";
10
- import { ShapeCanvas, } from "./components/ShapeCanvas";
11
- import { ShapeHighlight, } from "./components/ShapeHighlight";
12
- import { PdfLoader } from "./components/PdfLoader";
13
- import { useHighlightContainerContext, } from "./contexts/HighlightContext";
14
- import { viewportPositionToScaled, scaledPositionToViewport, } from "./lib/coordinates";
15
- import { exportPdf, } from "./lib/export-pdf";
16
- import { usePdfHighlighterContext, } from "./contexts/PdfHighlighterContext";
17
- export { PdfHighlighter, PdfLoader, TextHighlight, MonitoredHighlightContainer, AreaHighlight, FreetextHighlight, ImageHighlight, SignaturePad, DrawingCanvas, DrawingHighlight, ShapeCanvas, ShapeHighlight, useHighlightContainerContext, viewportPositionToScaled, scaledPositionToViewport, usePdfHighlighterContext, exportPdf, };
18
- export * from "./types";
1
+ // src/components/PdfHighlighter.tsx
2
+ import "pdfjs-dist/web/pdf_viewer.css";
3
+ import debounce from "lodash.debounce";
4
+ import React6, {
5
+ useLayoutEffect as useLayoutEffect2,
6
+ useRef as useRef5,
7
+ useState as useState5
8
+ } from "react";
9
+ import { createRoot } from "react-dom/client";
10
+
11
+ // src/contexts/PdfHighlighterContext.ts
12
+ import { createContext, useContext } from "react";
13
+ var PdfHighlighterContext = createContext(void 0);
14
+ var usePdfHighlighterContext = () => {
15
+ const pdfHighlighterUtils = useContext(PdfHighlighterContext);
16
+ if (pdfHighlighterUtils === void 0) {
17
+ throw new Error(
18
+ "usePdfHighlighterContext must be used within PdfHighlighter!"
19
+ );
20
+ }
21
+ return pdfHighlighterUtils;
22
+ };
23
+
24
+ // src/lib/coordinates.ts
25
+ var viewportToScaled = (rect, { width, height }) => {
26
+ return {
27
+ x1: rect.left,
28
+ y1: rect.top,
29
+ x2: rect.left + rect.width,
30
+ y2: rect.top + rect.height,
31
+ width,
32
+ height,
33
+ pageNumber: rect.pageNumber
34
+ };
35
+ };
36
+ var viewportPositionToScaled = ({ boundingRect, rects }, viewer) => {
37
+ const pageNumber = boundingRect.pageNumber;
38
+ const viewport = viewer.getPageView(pageNumber - 1).viewport;
39
+ const scale = (obj) => viewportToScaled(obj, viewport);
40
+ return {
41
+ boundingRect: scale(boundingRect),
42
+ rects: (rects || []).map(scale)
43
+ };
44
+ };
45
+ var pdfToViewport = (pdf, viewport) => {
46
+ const [x1, y1, x2, y2] = viewport.convertToViewportRectangle([
47
+ pdf.x1,
48
+ pdf.y1,
49
+ pdf.x2,
50
+ pdf.y2
51
+ ]);
52
+ return {
53
+ left: Math.min(x1, x2),
54
+ top: Math.min(y1, y2),
55
+ width: Math.abs(x2 - x1),
56
+ height: Math.abs(y1 - y2),
57
+ pageNumber: pdf.pageNumber
58
+ };
59
+ };
60
+ var scaledToViewport = (scaled, viewport, usePdfCoordinates = false) => {
61
+ const { width, height } = viewport;
62
+ if (usePdfCoordinates) {
63
+ return pdfToViewport(scaled, viewport);
64
+ }
65
+ if (scaled.x1 === void 0) {
66
+ throw new Error("You are using old position format, please update");
67
+ }
68
+ const x1 = width * scaled.x1 / scaled.width;
69
+ const y1 = height * scaled.y1 / scaled.height;
70
+ const x2 = width * scaled.x2 / scaled.width;
71
+ const y2 = height * scaled.y2 / scaled.height;
72
+ return {
73
+ left: x1,
74
+ top: y1,
75
+ width: x2 - x1,
76
+ height: y2 - y1,
77
+ pageNumber: scaled.pageNumber
78
+ };
79
+ };
80
+ var scaledPositionToViewport = ({ boundingRect, rects, usePdfCoordinates }, viewer) => {
81
+ const pageNumber = boundingRect.pageNumber;
82
+ const viewport = viewer.getPageView(pageNumber - 1).viewport;
83
+ const scale = (obj) => scaledToViewport(obj, viewport, usePdfCoordinates);
84
+ return {
85
+ boundingRect: scale(boundingRect),
86
+ rects: (rects || []).map(scale)
87
+ };
88
+ };
89
+
90
+ // src/lib/get-bounding-rect.ts
91
+ var getBoundingRect = (clientRects) => {
92
+ const rects = Array.from(clientRects).map((rect) => {
93
+ const { left, top, width, height, pageNumber: pageNumber2 } = rect;
94
+ const X02 = left;
95
+ const X12 = left + width;
96
+ const Y02 = top;
97
+ const Y12 = top + height;
98
+ return { X0: X02, X1: X12, Y0: Y02, Y1: Y12, pageNumber: pageNumber2 };
99
+ });
100
+ let firstPageNumber = Number.MAX_SAFE_INTEGER;
101
+ rects.forEach((rect) => {
102
+ firstPageNumber = Math.min(
103
+ firstPageNumber,
104
+ rect.pageNumber ?? firstPageNumber
105
+ );
106
+ });
107
+ const rectsWithSizeOnFirstPage = rects.filter(
108
+ (rect) => (rect.X0 > 0 || rect.X1 > 0 || rect.Y0 > 0 || rect.Y1 > 0) && rect.pageNumber === firstPageNumber
109
+ );
110
+ const optimal = rectsWithSizeOnFirstPage.reduce((res, rect) => {
111
+ return {
112
+ X0: Math.min(res.X0, rect.X0),
113
+ X1: Math.max(res.X1, rect.X1),
114
+ Y0: Math.min(res.Y0, rect.Y0),
115
+ Y1: Math.max(res.Y1, rect.Y1),
116
+ pageNumber: firstPageNumber
117
+ };
118
+ }, rectsWithSizeOnFirstPage[0]);
119
+ const { X0, X1, Y0, Y1, pageNumber } = optimal;
120
+ return {
121
+ left: X0,
122
+ top: Y0,
123
+ width: X1 - X0,
124
+ height: Y1 - Y0,
125
+ pageNumber
126
+ };
127
+ };
128
+ var get_bounding_rect_default = getBoundingRect;
129
+
130
+ // src/lib/optimize-client-rects.ts
131
+ var sort = (rects) => rects.sort((A, B) => {
132
+ const top = (A.pageNumber || 0) * A.top - (B.pageNumber || 0) * B.top;
133
+ if (top === 0) {
134
+ return A.left - B.left;
135
+ }
136
+ return top;
137
+ });
138
+ var overlaps = (A, B) => A.pageNumber === B.pageNumber && A.left <= B.left && B.left <= A.left + A.width;
139
+ var sameLine = (A, B, yMargin = 5) => A.pageNumber === B.pageNumber && Math.abs(A.top - B.top) < yMargin && Math.abs(A.height - B.height) < yMargin;
140
+ var inside = (A, B) => A.pageNumber === B.pageNumber && A.top > B.top && A.left > B.left && A.top + A.height < B.top + B.height && A.left + A.width < B.left + B.width;
141
+ var nextTo = (A, B, xMargin = 10) => {
142
+ const Aright = A.left + A.width;
143
+ const Bright = B.left + B.width;
144
+ return A.pageNumber === B.pageNumber && A.left <= B.left && Aright <= Bright && B.left - Aright <= xMargin;
145
+ };
146
+ var extendWidth = (A, B) => {
147
+ A.width = Math.max(B.width - A.left + B.left, A.width);
148
+ };
149
+ var optimizeClientRects = (clientRects) => {
150
+ const rects = sort(clientRects);
151
+ const toRemove = /* @__PURE__ */ new Set();
152
+ const firstPass = rects.filter((rect) => {
153
+ return rects.every((otherRect) => {
154
+ return !inside(rect, otherRect);
155
+ });
156
+ });
157
+ let passCount = 0;
158
+ while (passCount <= 2) {
159
+ firstPass.forEach((A) => {
160
+ firstPass.forEach((B) => {
161
+ if (A === B || toRemove.has(A) || toRemove.has(B)) {
162
+ return;
163
+ }
164
+ if (!sameLine(A, B)) {
165
+ return;
166
+ }
167
+ if (overlaps(A, B)) {
168
+ extendWidth(A, B);
169
+ A.height = Math.max(A.height, B.height);
170
+ toRemove.add(B);
171
+ }
172
+ if (nextTo(A, B)) {
173
+ extendWidth(A, B);
174
+ toRemove.add(B);
175
+ }
176
+ });
177
+ });
178
+ passCount += 1;
179
+ }
180
+ return firstPass.filter((rect) => !toRemove.has(rect));
181
+ };
182
+ var optimize_client_rects_default = optimizeClientRects;
183
+
184
+ // src/lib/get-client-rects.ts
185
+ var isClientRectInsidePageRect = (clientRect, pageRect) => {
186
+ if (clientRect.top < pageRect.top) {
187
+ return false;
188
+ }
189
+ if (clientRect.bottom > pageRect.bottom) {
190
+ return false;
191
+ }
192
+ if (clientRect.right > pageRect.right) {
193
+ return false;
194
+ }
195
+ if (clientRect.left < pageRect.left) {
196
+ return false;
197
+ }
198
+ return true;
199
+ };
200
+ var getClientRects = (range, pages, shouldOptimize = true) => {
201
+ const clientRects = Array.from(range.getClientRects());
202
+ const rects = [];
203
+ for (const clientRect of clientRects) {
204
+ for (const page of pages) {
205
+ const pageRect = page.node.getBoundingClientRect();
206
+ if (isClientRectInsidePageRect(clientRect, pageRect) && clientRect.width > 0 && clientRect.height > 0 && clientRect.width < pageRect.width && clientRect.left > pageRect.left && clientRect.height < pageRect.height) {
207
+ const highlightedRect = {
208
+ top: clientRect.top + page.node.scrollTop - pageRect.top,
209
+ left: clientRect.left + page.node.scrollLeft - pageRect.left,
210
+ width: clientRect.width,
211
+ height: clientRect.height,
212
+ pageNumber: page.number
213
+ };
214
+ rects.push(highlightedRect);
215
+ }
216
+ }
217
+ }
218
+ return shouldOptimize ? optimize_client_rects_default(rects) : rects;
219
+ };
220
+ var get_client_rects_default = getClientRects;
221
+
222
+ // src/lib/group-highlights-by-page.ts
223
+ var groupHighlightsByPage = (highlights) => highlights.reduce((acc, highlight) => {
224
+ if (!highlight) {
225
+ return acc;
226
+ }
227
+ const pageNumbers = [
228
+ highlight.position.boundingRect.pageNumber,
229
+ ...highlight.position.rects.map((rect) => rect.pageNumber || 0)
230
+ ];
231
+ pageNumbers.forEach((pageNumber) => {
232
+ acc[pageNumber] ||= [];
233
+ const pageSpecificHighlight = {
234
+ ...highlight,
235
+ position: {
236
+ ...highlight.position,
237
+ rects: highlight.position.rects.filter(
238
+ (rect) => pageNumber === rect.pageNumber
239
+ )
240
+ }
241
+ };
242
+ acc[pageNumber].push(pageSpecificHighlight);
243
+ });
244
+ return acc;
245
+ }, {});
246
+ var group_highlights_by_page_default = groupHighlightsByPage;
247
+
248
+ // src/lib/pdfjs-dom.ts
249
+ var getDocument = (elm) => (elm || {}).ownerDocument || document;
250
+ var getWindow = (elm) => (getDocument(elm) || {}).defaultView || window;
251
+ var isHTMLElement = (elm) => elm instanceof HTMLElement || elm instanceof getWindow(elm).HTMLElement;
252
+ var isHTMLCanvasElement = (elm) => elm instanceof HTMLCanvasElement || elm instanceof getWindow(elm).HTMLCanvasElement;
253
+ var asElement = (x) => x;
254
+ var getPageFromElement = (target) => {
255
+ const node = asElement(target.closest(".page"));
256
+ if (!node || !isHTMLElement(node)) {
257
+ return null;
258
+ }
259
+ const number = Number(asElement(node).dataset.pageNumber);
260
+ return { node, number };
261
+ };
262
+ var getPagesFromRange = (range) => {
263
+ const startParentElement = range.startContainer.parentElement;
264
+ const endParentElement = range.endContainer.parentElement;
265
+ if (!isHTMLElement(startParentElement) || !isHTMLElement(endParentElement)) {
266
+ return [];
267
+ }
268
+ const startPage = getPageFromElement(asElement(startParentElement));
269
+ const endPage = getPageFromElement(asElement(endParentElement));
270
+ if (!startPage?.number || !endPage?.number) {
271
+ return [];
272
+ }
273
+ if (startPage.number === endPage.number) {
274
+ return [startPage];
275
+ }
276
+ if (startPage.number === endPage.number - 1) {
277
+ return [startPage, endPage];
278
+ }
279
+ const pages = [];
280
+ let currentPageNumber = startPage.number;
281
+ const document2 = startPage.node.ownerDocument;
282
+ while (currentPageNumber <= endPage.number) {
283
+ const currentPage = getPageFromElement(
284
+ document2.querySelector(
285
+ `[data-page-number='${currentPageNumber}'`
286
+ )
287
+ );
288
+ if (currentPage) {
289
+ pages.push(currentPage);
290
+ }
291
+ currentPageNumber++;
292
+ }
293
+ return pages;
294
+ };
295
+ var findOrCreateContainerLayer = (container, className) => {
296
+ const doc = getDocument(container);
297
+ let layer = container.querySelector(`.${className}`);
298
+ if (!layer && container.children.length) {
299
+ layer = doc.createElement("div");
300
+ layer.className = className;
301
+ container.appendChild(layer);
302
+ }
303
+ return layer;
304
+ };
305
+
306
+ // src/components/DrawingCanvas.tsx
307
+ import React, {
308
+ useRef,
309
+ useEffect,
310
+ useCallback,
311
+ useState
312
+ } from "react";
313
+ var DrawingCanvas = ({
314
+ isActive,
315
+ strokeColor = "#000000",
316
+ strokeWidth = 3,
317
+ viewer,
318
+ onComplete,
319
+ onCancel
320
+ }) => {
321
+ const canvasRef = useRef(null);
322
+ const [strokes, setStrokes] = useState([]);
323
+ const [currentStroke, setCurrentStroke] = useState(null);
324
+ const isDrawingRef = useRef(false);
325
+ const [pageNumber, setPageNumber] = useState(null);
326
+ const [pageElement, setPageElement] = useState(null);
327
+ const findPageFromPoint = useCallback(
328
+ (clientX, clientY) => {
329
+ if (!viewer) return null;
330
+ for (let i = 0; i < viewer.pagesCount; i++) {
331
+ const pageView = viewer.getPageView(i);
332
+ if (!pageView?.div) continue;
333
+ const rect = pageView.div.getBoundingClientRect();
334
+ if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
335
+ return {
336
+ pageNumber: i + 1,
337
+ element: pageView.div,
338
+ rect
339
+ };
340
+ }
341
+ }
342
+ return null;
343
+ },
344
+ [viewer]
345
+ );
346
+ const redrawCanvas = useCallback(() => {
347
+ const canvas = canvasRef.current;
348
+ const ctx = canvas?.getContext("2d");
349
+ if (!ctx || !canvas) return;
350
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
351
+ strokes.forEach((stroke) => {
352
+ if (stroke.points.length < 2) return;
353
+ ctx.strokeStyle = stroke.color;
354
+ ctx.lineWidth = stroke.width;
355
+ ctx.lineCap = "round";
356
+ ctx.lineJoin = "round";
357
+ ctx.beginPath();
358
+ ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
359
+ stroke.points.slice(1).forEach((point) => {
360
+ ctx.lineTo(point.x, point.y);
361
+ });
362
+ ctx.stroke();
363
+ });
364
+ if (currentStroke && currentStroke.points.length >= 2) {
365
+ ctx.strokeStyle = currentStroke.color;
366
+ ctx.lineWidth = currentStroke.width;
367
+ ctx.lineCap = "round";
368
+ ctx.lineJoin = "round";
369
+ ctx.beginPath();
370
+ ctx.moveTo(currentStroke.points[0].x, currentStroke.points[0].y);
371
+ currentStroke.points.slice(1).forEach((point) => {
372
+ ctx.lineTo(point.x, point.y);
373
+ });
374
+ ctx.stroke();
375
+ }
376
+ }, [strokes, currentStroke]);
377
+ useEffect(() => {
378
+ redrawCanvas();
379
+ }, [redrawCanvas]);
380
+ const handleStart = useCallback(
381
+ (clientX, clientY) => {
382
+ const pageInfo = findPageFromPoint(clientX, clientY);
383
+ if (!pageInfo) return;
384
+ console.log("DrawingCanvas: Started drawing on page", pageInfo.pageNumber);
385
+ if (pageNumber === null) {
386
+ setPageNumber(pageInfo.pageNumber);
387
+ setPageElement(pageInfo.element);
388
+ const canvas = canvasRef.current;
389
+ if (canvas) {
390
+ canvas.width = pageInfo.rect.width;
391
+ canvas.height = pageInfo.rect.height;
392
+ canvas.style.left = `${pageInfo.rect.left}px`;
393
+ canvas.style.top = `${pageInfo.rect.top}px`;
394
+ }
395
+ } else if (pageInfo.pageNumber !== pageNumber) {
396
+ console.log("DrawingCanvas: Ignoring - different page");
397
+ return;
398
+ }
399
+ isDrawingRef.current = true;
400
+ const pos = {
401
+ x: clientX - pageInfo.rect.left,
402
+ y: clientY - pageInfo.rect.top
403
+ };
404
+ setCurrentStroke({ points: [pos], color: strokeColor, width: strokeWidth });
405
+ },
406
+ [pageNumber, findPageFromPoint, strokeColor, strokeWidth]
407
+ );
408
+ const handleMove = useCallback(
409
+ (clientX, clientY) => {
410
+ if (!isDrawingRef.current || !pageElement) return;
411
+ const rect = pageElement.getBoundingClientRect();
412
+ const pos = {
413
+ x: clientX - rect.left,
414
+ y: clientY - rect.top
415
+ };
416
+ setCurrentStroke((prev) => {
417
+ if (!prev) return null;
418
+ return { ...prev, points: [...prev.points, pos] };
419
+ });
420
+ },
421
+ [pageElement]
422
+ );
423
+ const handleEnd = useCallback(() => {
424
+ if (!isDrawingRef.current) return;
425
+ isDrawingRef.current = false;
426
+ if (currentStroke && currentStroke.points.length >= 2) {
427
+ setStrokes((prev) => [...prev, currentStroke]);
428
+ }
429
+ setCurrentStroke(null);
430
+ }, [currentStroke]);
431
+ const handleMouseDown = useCallback(
432
+ (e) => {
433
+ e.preventDefault();
434
+ handleStart(e.clientX, e.clientY);
435
+ },
436
+ [handleStart]
437
+ );
438
+ const handleMouseMove = useCallback(
439
+ (e) => {
440
+ handleMove(e.clientX, e.clientY);
441
+ },
442
+ [handleMove]
443
+ );
444
+ const handleMouseUp = useCallback(() => {
445
+ handleEnd();
446
+ }, [handleEnd]);
447
+ const handleTouchStart = useCallback(
448
+ (e) => {
449
+ e.preventDefault();
450
+ if (e.touches.length > 0) {
451
+ handleStart(e.touches[0].clientX, e.touches[0].clientY);
452
+ }
453
+ },
454
+ [handleStart]
455
+ );
456
+ const handleTouchMove = useCallback(
457
+ (e) => {
458
+ e.preventDefault();
459
+ if (e.touches.length > 0) {
460
+ handleMove(e.touches[0].clientX, e.touches[0].clientY);
461
+ }
462
+ },
463
+ [handleMove]
464
+ );
465
+ const handleTouchEnd = useCallback(() => {
466
+ handleEnd();
467
+ }, [handleEnd]);
468
+ useEffect(() => {
469
+ if (!isActive) return;
470
+ const handleKeyDown = (e) => {
471
+ if (e.code === "Escape") {
472
+ console.log("DrawingCanvas: Cancelled via Escape");
473
+ onCancel();
474
+ }
475
+ };
476
+ document.addEventListener("keydown", handleKeyDown);
477
+ return () => document.removeEventListener("keydown", handleKeyDown);
478
+ }, [isActive, onCancel]);
479
+ const handleClear = () => {
480
+ console.log("DrawingCanvas: Cleared strokes");
481
+ setStrokes([]);
482
+ setCurrentStroke(null);
483
+ setPageNumber(null);
484
+ setPageElement(null);
485
+ };
486
+ const handleDone = () => {
487
+ if (strokes.length === 0 || pageNumber === null || !pageElement || !viewer) {
488
+ console.log("DrawingCanvas: No strokes to save");
489
+ onCancel();
490
+ return;
491
+ }
492
+ console.log("DrawingCanvas: Completing drawing with", strokes.length, "strokes");
493
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
494
+ strokes.forEach((stroke) => {
495
+ stroke.points.forEach((point) => {
496
+ minX = Math.min(minX, point.x);
497
+ minY = Math.min(minY, point.y);
498
+ maxX = Math.max(maxX, point.x);
499
+ maxY = Math.max(maxY, point.y);
500
+ });
501
+ });
502
+ const maxStrokeWidth = Math.max(...strokes.map((s) => s.width));
503
+ const padding = maxStrokeWidth * 2;
504
+ minX = Math.max(0, minX - padding);
505
+ minY = Math.max(0, minY - padding);
506
+ maxX = maxX + padding;
507
+ maxY = maxY + padding;
508
+ const width = maxX - minX;
509
+ const height = maxY - minY;
510
+ const outputCanvas = document.createElement("canvas");
511
+ outputCanvas.width = width;
512
+ outputCanvas.height = height;
513
+ const outputCtx = outputCanvas.getContext("2d");
514
+ if (!outputCtx) {
515
+ console.error("DrawingCanvas: Could not get output canvas context");
516
+ onCancel();
517
+ return;
518
+ }
519
+ strokes.forEach((stroke) => {
520
+ if (stroke.points.length < 2) return;
521
+ outputCtx.strokeStyle = stroke.color;
522
+ outputCtx.lineWidth = stroke.width;
523
+ outputCtx.lineCap = "round";
524
+ outputCtx.lineJoin = "round";
525
+ outputCtx.beginPath();
526
+ outputCtx.moveTo(stroke.points[0].x - minX, stroke.points[0].y - minY);
527
+ stroke.points.slice(1).forEach((point) => {
528
+ outputCtx.lineTo(point.x - minX, point.y - minY);
529
+ });
530
+ outputCtx.stroke();
531
+ });
532
+ const dataUrl = outputCanvas.toDataURL("image/png");
533
+ const viewportPosition = {
534
+ boundingRect: {
535
+ left: minX,
536
+ top: minY,
537
+ width,
538
+ height,
539
+ pageNumber
540
+ },
541
+ rects: []
542
+ };
543
+ const scaledPosition = viewportPositionToScaled(viewportPosition, viewer);
544
+ const normalizedStrokes = strokes.map((stroke) => ({
545
+ ...stroke,
546
+ points: stroke.points.map((point) => ({
547
+ x: point.x - minX,
548
+ y: point.y - minY
549
+ }))
550
+ }));
551
+ console.log("DrawingCanvas: Created drawing at position", scaledPosition);
552
+ onComplete(dataUrl, scaledPosition, normalizedStrokes);
553
+ setStrokes([]);
554
+ setCurrentStroke(null);
555
+ setPageNumber(null);
556
+ setPageElement(null);
557
+ };
558
+ if (!isActive) return null;
559
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
560
+ "canvas",
561
+ {
562
+ ref: canvasRef,
563
+ className: "DrawingCanvas",
564
+ style: {
565
+ width: pageElement ? pageElement.getBoundingClientRect().width : "100%",
566
+ height: pageElement ? pageElement.getBoundingClientRect().height : "100%",
567
+ position: "fixed"
568
+ },
569
+ onMouseDown: handleMouseDown,
570
+ onMouseMove: handleMouseMove,
571
+ onMouseUp: handleMouseUp,
572
+ onMouseLeave: handleMouseUp,
573
+ onTouchStart: handleTouchStart,
574
+ onTouchMove: handleTouchMove,
575
+ onTouchEnd: handleTouchEnd
576
+ }
577
+ ), /* @__PURE__ */ React.createElement("div", { className: "DrawingCanvas__controls" }, /* @__PURE__ */ React.createElement(
578
+ "button",
579
+ {
580
+ type: "button",
581
+ className: "DrawingCanvas__clearButton",
582
+ onClick: handleClear
583
+ },
584
+ "Clear"
585
+ ), /* @__PURE__ */ React.createElement(
586
+ "button",
587
+ {
588
+ type: "button",
589
+ className: "DrawingCanvas__cancelButton",
590
+ onClick: onCancel
591
+ },
592
+ "Cancel"
593
+ ), /* @__PURE__ */ React.createElement(
594
+ "button",
595
+ {
596
+ type: "button",
597
+ className: "DrawingCanvas__doneButton",
598
+ onClick: handleDone
599
+ },
600
+ "Done"
601
+ )));
602
+ };
603
+
604
+ // src/components/HighlightLayer.tsx
605
+ import React2 from "react";
606
+
607
+ // src/contexts/HighlightContext.ts
608
+ import { createContext as createContext2, useContext as useContext2 } from "react";
609
+ var HighlightContext = createContext2(void 0);
610
+ var useHighlightContainerContext = () => {
611
+ const highlightContainerUtils = useContext2(HighlightContext);
612
+ if (highlightContainerUtils === void 0) {
613
+ throw new Error(
614
+ "useHighlightContainerContext must be used within a child of PdfHighlighter!"
615
+ );
616
+ }
617
+ return highlightContainerUtils;
618
+ };
619
+
620
+ // src/lib/screenshot.ts
621
+ var getAreaAsPng = (canvas, position) => {
622
+ const { left, top, width, height } = position;
623
+ const doc = canvas ? canvas.ownerDocument : null;
624
+ const newCanvas = doc && doc.createElement("canvas");
625
+ if (!newCanvas || !isHTMLCanvasElement(newCanvas)) {
626
+ return "";
627
+ }
628
+ newCanvas.width = width;
629
+ newCanvas.height = height;
630
+ const newCanvasContext = newCanvas.getContext("2d");
631
+ if (!newCanvasContext || !canvas) {
632
+ return "";
633
+ }
634
+ const dpr = window.devicePixelRatio;
635
+ newCanvasContext.drawImage(
636
+ canvas,
637
+ left * dpr,
638
+ top * dpr,
639
+ width * dpr,
640
+ height * dpr,
641
+ 0,
642
+ 0,
643
+ width,
644
+ height
645
+ );
646
+ return newCanvas.toDataURL("image/png");
647
+ };
648
+ var screenshot = (position, pageNumber, viewer) => {
649
+ return getAreaAsPng(viewer.getPageView(pageNumber - 1).canvas, position);
650
+ };
651
+ var screenshot_default = screenshot;
652
+
653
+ // src/components/HighlightLayer.tsx
654
+ var EMPTY_ID = "empty-id";
655
+ var HighlightLayer = ({
656
+ highlightsByPage,
657
+ pageNumber,
658
+ scrolledToHighlightId,
659
+ viewer,
660
+ highlightBindings,
661
+ children
662
+ }) => {
663
+ const currentHighlights = highlightsByPage[pageNumber] || [];
664
+ return /* @__PURE__ */ React2.createElement("div", null, currentHighlights.map((highlight, index) => {
665
+ const viewportHighlight = {
666
+ ...highlight,
667
+ id: "id" in highlight ? highlight.id : EMPTY_ID,
668
+ // Give Empty ID to GhostHighlight
669
+ position: scaledPositionToViewport(highlight.position, viewer)
670
+ };
671
+ const isScrolledTo = Boolean(
672
+ scrolledToHighlightId === viewportHighlight.id
673
+ );
674
+ const highlightUtils = {
675
+ highlight: viewportHighlight,
676
+ viewportToScaled: (rect) => {
677
+ const viewport = viewer.getPageView(
678
+ (rect.pageNumber || pageNumber) - 1
679
+ // Convert to 0 index
680
+ ).viewport;
681
+ return viewportToScaled(rect, viewport);
682
+ },
683
+ screenshot: (boundingRect) => screenshot_default(boundingRect, pageNumber, viewer),
684
+ isScrolledTo,
685
+ highlightBindings
686
+ };
687
+ return /* @__PURE__ */ React2.createElement(HighlightContext.Provider, { value: highlightUtils, key: index }, children);
688
+ }));
689
+ };
690
+
691
+ // src/components/MouseSelection.tsx
692
+ import React3, { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
693
+ var getBoundingRect2 = (start, end) => {
694
+ return {
695
+ left: Math.min(end.x, start.x),
696
+ top: Math.min(end.y, start.y),
697
+ width: Math.abs(end.x - start.x),
698
+ height: Math.abs(end.y - start.y)
699
+ };
700
+ };
701
+ var getContainerCoords = (container, pageX, pageY) => {
702
+ const containerBoundingRect = container.getBoundingClientRect();
703
+ return {
704
+ x: pageX - containerBoundingRect.left + container.scrollLeft,
705
+ y: pageY - containerBoundingRect.top + container.scrollTop - window.scrollY
706
+ };
707
+ };
708
+ var MouseSelection = ({
709
+ viewer,
710
+ onSelection,
711
+ onReset,
712
+ onDragStart,
713
+ enableAreaSelection,
714
+ onChange,
715
+ style
716
+ }) => {
717
+ const [start, setStart] = useState2(null);
718
+ const [end, setEnd] = useState2(null);
719
+ const [locked, setLocked] = useState2(false);
720
+ const rootRef = useRef2(null);
721
+ const startTargetRef = useRef2(null);
722
+ const reset = () => {
723
+ onReset && onReset();
724
+ setStart(null);
725
+ setEnd(null);
726
+ setLocked(false);
727
+ };
728
+ useEffect2(() => {
729
+ onChange && onChange(Boolean(start && end));
730
+ if (!rootRef.current) return;
731
+ const container = asElement(rootRef.current.parentElement);
732
+ const handleMouseUp = (event) => {
733
+ if (!start || !end || !startTargetRef.current) return;
734
+ const boundingRect = getBoundingRect2(start, end);
735
+ const shouldEnd = boundingRect.width >= 1 && boundingRect.height >= 1;
736
+ if (!container.contains(asElement(event.target)) || !shouldEnd) {
737
+ reset();
738
+ return;
739
+ }
740
+ setLocked(true);
741
+ const page = getPageFromElement(startTargetRef.current);
742
+ if (!page) return;
743
+ const pageBoundingRect = {
744
+ ...boundingRect,
745
+ top: boundingRect.top - page.node.offsetTop,
746
+ left: boundingRect.left - page.node.offsetLeft,
747
+ pageNumber: page.number
748
+ };
749
+ const viewportPosition = {
750
+ boundingRect: pageBoundingRect,
751
+ rects: []
752
+ };
753
+ const scaledPosition = viewportPositionToScaled(viewportPosition, viewer);
754
+ const image = screenshot_default(
755
+ pageBoundingRect,
756
+ pageBoundingRect.pageNumber,
757
+ viewer
758
+ );
759
+ onSelection && onSelection(viewportPosition, scaledPosition, image, reset, event);
760
+ };
761
+ const handleMouseMove = (event) => {
762
+ if (!rootRef.current || !start || locked) return;
763
+ setEnd(getContainerCoords(container, event.pageX, event.pageY));
764
+ };
765
+ const handleMouseDown = (event) => {
766
+ const shouldStart = (event2) => enableAreaSelection(event2) && isHTMLElement(event2.target) && Boolean(asElement(event2.target).closest(".page"));
767
+ const shouldReset = (event2) => start && !asElement(event2.target).closest(".PdfHighlighter__tip-container");
768
+ if (!shouldStart(event)) {
769
+ if (shouldReset(event)) reset();
770
+ return;
771
+ }
772
+ startTargetRef.current = asElement(event.target);
773
+ onDragStart && onDragStart(event);
774
+ setStart(getContainerCoords(container, event.pageX, event.pageY));
775
+ setEnd(null);
776
+ setLocked(false);
777
+ };
778
+ container.addEventListener("mousemove", handleMouseMove);
779
+ container.addEventListener("mousedown", handleMouseDown);
780
+ document.addEventListener("mouseup", handleMouseUp);
781
+ return () => {
782
+ container.removeEventListener("mousemove", handleMouseMove);
783
+ container.removeEventListener("mousedown", handleMouseDown);
784
+ document.removeEventListener("mouseup", handleMouseUp);
785
+ };
786
+ }, [start, end, enableAreaSelection]);
787
+ return /* @__PURE__ */ React3.createElement("div", { className: "MouseSelection-container", ref: rootRef }, start && end && /* @__PURE__ */ React3.createElement(
788
+ "div",
789
+ {
790
+ className: "MouseSelection",
791
+ style: { ...getBoundingRect2(start, end), ...style }
792
+ }
793
+ ));
794
+ };
795
+
796
+ // src/components/ShapeCanvas.tsx
797
+ import React4, {
798
+ useRef as useRef3,
799
+ useEffect as useEffect3,
800
+ useCallback as useCallback2,
801
+ useState as useState3
802
+ } from "react";
803
+ var ShapeCanvas = ({
804
+ isActive,
805
+ shapeType,
806
+ strokeColor = "#000000",
807
+ strokeWidth = 2,
808
+ viewer,
809
+ onComplete,
810
+ onCancel
811
+ }) => {
812
+ const containerRef = useRef3(null);
813
+ const [startPoint, setStartPoint] = useState3(null);
814
+ const [currentPoint, setCurrentPoint] = useState3(null);
815
+ const [pageNumber, setPageNumber] = useState3(null);
816
+ const [pageRect, setPageRect] = useState3(null);
817
+ const isDrawingRef = useRef3(false);
818
+ const findPageFromPoint = useCallback2(
819
+ (clientX, clientY) => {
820
+ if (!viewer) return null;
821
+ for (let i = 0; i < viewer.pagesCount; i++) {
822
+ const pageView = viewer.getPageView(i);
823
+ if (!pageView?.div) continue;
824
+ const rect = pageView.div.getBoundingClientRect();
825
+ if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
826
+ return {
827
+ pageNumber: i + 1,
828
+ element: pageView.div,
829
+ rect
830
+ };
831
+ }
832
+ }
833
+ return null;
834
+ },
835
+ [viewer]
836
+ );
837
+ const handleStart = useCallback2(
838
+ (clientX, clientY) => {
839
+ const pageInfo = findPageFromPoint(clientX, clientY);
840
+ if (!pageInfo) return;
841
+ console.log("ShapeCanvas: Started drawing on page", pageInfo.pageNumber);
842
+ setPageNumber(pageInfo.pageNumber);
843
+ setPageRect(pageInfo.rect);
844
+ isDrawingRef.current = true;
845
+ const pos = {
846
+ x: clientX - pageInfo.rect.left,
847
+ y: clientY - pageInfo.rect.top
848
+ };
849
+ setStartPoint(pos);
850
+ setCurrentPoint(pos);
851
+ },
852
+ [findPageFromPoint]
853
+ );
854
+ const handleMove = useCallback2(
855
+ (clientX, clientY) => {
856
+ if (!isDrawingRef.current || !pageRect) return;
857
+ const pos = {
858
+ x: clientX - pageRect.left,
859
+ y: clientY - pageRect.top
860
+ };
861
+ setCurrentPoint(pos);
862
+ },
863
+ [pageRect]
864
+ );
865
+ const handleEnd = useCallback2(() => {
866
+ if (!isDrawingRef.current || !startPoint || !currentPoint || pageNumber === null || !viewer) {
867
+ isDrawingRef.current = false;
868
+ setStartPoint(null);
869
+ setCurrentPoint(null);
870
+ return;
871
+ }
872
+ isDrawingRef.current = false;
873
+ const minX = Math.min(startPoint.x, currentPoint.x);
874
+ const minY = Math.min(startPoint.y, currentPoint.y);
875
+ const maxX = Math.max(startPoint.x, currentPoint.x);
876
+ const maxY = Math.max(startPoint.y, currentPoint.y);
877
+ const width = maxX - minX;
878
+ const height = maxY - minY;
879
+ if (width < 10 || height < 10) {
880
+ console.log("ShapeCanvas: Shape too small, ignoring");
881
+ setStartPoint(null);
882
+ setCurrentPoint(null);
883
+ return;
884
+ }
885
+ console.log("ShapeCanvas: Creating shape", shapeType, "at", { minX, minY, width, height });
886
+ const viewportPosition = {
887
+ boundingRect: {
888
+ left: minX,
889
+ top: minY,
890
+ width,
891
+ height,
892
+ pageNumber
893
+ },
894
+ rects: []
895
+ };
896
+ const scaledPosition = viewportPositionToScaled(viewportPosition, viewer);
897
+ let shapeData = {
898
+ shapeType,
899
+ strokeColor,
900
+ strokeWidth
901
+ };
902
+ if (shapeType === "arrow") {
903
+ shapeData.startPoint = {
904
+ x: (startPoint.x - minX) / width,
905
+ y: (startPoint.y - minY) / height
906
+ };
907
+ shapeData.endPoint = {
908
+ x: (currentPoint.x - minX) / width,
909
+ y: (currentPoint.y - minY) / height
910
+ };
911
+ console.log("ShapeCanvas: Arrow points", shapeData.startPoint, "->", shapeData.endPoint);
912
+ }
913
+ console.log("ShapeCanvas: Created shape at position", scaledPosition);
914
+ onComplete(scaledPosition, shapeData);
915
+ setStartPoint(null);
916
+ setCurrentPoint(null);
917
+ setPageNumber(null);
918
+ setPageRect(null);
919
+ }, [startPoint, currentPoint, pageNumber, viewer, shapeType, strokeColor, strokeWidth, onComplete]);
920
+ const handleMouseDown = useCallback2(
921
+ (e) => {
922
+ e.preventDefault();
923
+ handleStart(e.clientX, e.clientY);
924
+ },
925
+ [handleStart]
926
+ );
927
+ const handleMouseMove = useCallback2(
928
+ (e) => {
929
+ handleMove(e.clientX, e.clientY);
930
+ },
931
+ [handleMove]
932
+ );
933
+ const handleMouseUp = useCallback2(() => {
934
+ handleEnd();
935
+ }, [handleEnd]);
936
+ const handleTouchStart = useCallback2(
937
+ (e) => {
938
+ e.preventDefault();
939
+ if (e.touches.length > 0) {
940
+ handleStart(e.touches[0].clientX, e.touches[0].clientY);
941
+ }
942
+ },
943
+ [handleStart]
944
+ );
945
+ const handleTouchMove = useCallback2(
946
+ (e) => {
947
+ e.preventDefault();
948
+ if (e.touches.length > 0) {
949
+ handleMove(e.touches[0].clientX, e.touches[0].clientY);
950
+ }
951
+ },
952
+ [handleMove]
953
+ );
954
+ const handleTouchEnd = useCallback2(() => {
955
+ handleEnd();
956
+ }, [handleEnd]);
957
+ useEffect3(() => {
958
+ if (!isActive) return;
959
+ const handleKeyDown = (e) => {
960
+ if (e.code === "Escape") {
961
+ console.log("ShapeCanvas: Cancelled via Escape");
962
+ onCancel();
963
+ }
964
+ };
965
+ document.addEventListener("keydown", handleKeyDown);
966
+ return () => document.removeEventListener("keydown", handleKeyDown);
967
+ }, [isActive, onCancel]);
968
+ const renderShapePreview = () => {
969
+ if (!startPoint || !currentPoint || !pageRect) return null;
970
+ const minX = Math.min(startPoint.x, currentPoint.x);
971
+ const minY = Math.min(startPoint.y, currentPoint.y);
972
+ const width = Math.abs(currentPoint.x - startPoint.x);
973
+ const height = Math.abs(currentPoint.y - startPoint.y);
974
+ const svgStyle = {
975
+ position: "fixed",
976
+ left: pageRect.left,
977
+ top: pageRect.top,
978
+ width: pageRect.width,
979
+ height: pageRect.height,
980
+ pointerEvents: "none",
981
+ zIndex: 1001
982
+ };
983
+ return /* @__PURE__ */ React4.createElement("svg", { style: svgStyle }, shapeType === "rectangle" && /* @__PURE__ */ React4.createElement(
984
+ "rect",
985
+ {
986
+ x: minX,
987
+ y: minY,
988
+ width,
989
+ height,
990
+ stroke: strokeColor,
991
+ strokeWidth,
992
+ fill: "none"
993
+ }
994
+ ), shapeType === "circle" && /* @__PURE__ */ React4.createElement(
995
+ "ellipse",
996
+ {
997
+ cx: minX + width / 2,
998
+ cy: minY + height / 2,
999
+ rx: width / 2,
1000
+ ry: height / 2,
1001
+ stroke: strokeColor,
1002
+ strokeWidth,
1003
+ fill: "none"
1004
+ }
1005
+ ), shapeType === "arrow" && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("defs", null, /* @__PURE__ */ React4.createElement(
1006
+ "marker",
1007
+ {
1008
+ id: "shape-canvas-arrowhead",
1009
+ markerWidth: "10",
1010
+ markerHeight: "7",
1011
+ refX: "9",
1012
+ refY: "3.5",
1013
+ orient: "auto"
1014
+ },
1015
+ /* @__PURE__ */ React4.createElement("polygon", { points: "0 0, 10 3.5, 0 7", fill: strokeColor })
1016
+ )), /* @__PURE__ */ React4.createElement(
1017
+ "line",
1018
+ {
1019
+ x1: startPoint.x,
1020
+ y1: startPoint.y,
1021
+ x2: currentPoint.x,
1022
+ y2: currentPoint.y,
1023
+ stroke: strokeColor,
1024
+ strokeWidth,
1025
+ markerEnd: "url(#shape-canvas-arrowhead)"
1026
+ }
1027
+ )));
1028
+ };
1029
+ if (!isActive) return null;
1030
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(
1031
+ "div",
1032
+ {
1033
+ ref: containerRef,
1034
+ className: "ShapeCanvas",
1035
+ onMouseDown: handleMouseDown,
1036
+ onMouseMove: handleMouseMove,
1037
+ onMouseUp: handleMouseUp,
1038
+ onMouseLeave: handleMouseUp,
1039
+ onTouchStart: handleTouchStart,
1040
+ onTouchMove: handleTouchMove,
1041
+ onTouchEnd: handleTouchEnd
1042
+ }
1043
+ ), renderShapePreview(), /* @__PURE__ */ React4.createElement("div", { className: "ShapeCanvas__controls" }, /* @__PURE__ */ React4.createElement("div", { className: "ShapeCanvas__hint" }, "Click and drag to draw a ", shapeType, ". Press Escape to cancel."), /* @__PURE__ */ React4.createElement(
1044
+ "button",
1045
+ {
1046
+ type: "button",
1047
+ className: "ShapeCanvas__cancelButton",
1048
+ onClick: onCancel
1049
+ },
1050
+ "Cancel"
1051
+ )));
1052
+ };
1053
+
1054
+ // src/components/TipContainer.tsx
1055
+ import React5, {
1056
+ useLayoutEffect,
1057
+ useRef as useRef4,
1058
+ useState as useState4
1059
+ } from "react";
1060
+ var clamp = (value, left, right) => Math.min(Math.max(value, left), right);
1061
+ var VERTICAL_PADDING = 5;
1062
+ var TipContainer = ({
1063
+ viewer,
1064
+ updateTipPositionRef
1065
+ }) => {
1066
+ const [height, setHeight] = useState4(0);
1067
+ const [width, setWidth] = useState4(0);
1068
+ const containerRef = useRef4(null);
1069
+ const updatePosition = () => {
1070
+ if (!containerRef.current) return;
1071
+ const { offsetHeight, offsetWidth } = containerRef.current;
1072
+ setHeight(offsetHeight);
1073
+ setWidth(offsetWidth);
1074
+ };
1075
+ updateTipPositionRef.current = updatePosition;
1076
+ useLayoutEffect(() => {
1077
+ updatePosition();
1078
+ }, [updatePosition]);
1079
+ const { getTip } = usePdfHighlighterContext();
1080
+ const currentTip = getTip();
1081
+ if (!currentTip) return null;
1082
+ const { position, content } = currentTip;
1083
+ const { boundingRect } = position;
1084
+ const pageNumber = boundingRect.pageNumber;
1085
+ const pageNode = viewer.getPageView(pageNumber - 1).div;
1086
+ const pageBoundingClientRect = pageNode.getBoundingClientRect();
1087
+ const { left: pageLeft, width: pageWidth } = pageBoundingClientRect;
1088
+ const scrollTop = viewer.container.scrollTop;
1089
+ const left = pageNode.offsetLeft + boundingRect.left + boundingRect.width / 2;
1090
+ const highlightTop = boundingRect.top + pageNode.offsetTop;
1091
+ const highlightBottom = highlightTop + boundingRect.height;
1092
+ const shouldMove = highlightTop - height - VERTICAL_PADDING < scrollTop;
1093
+ const top = shouldMove ? highlightBottom + VERTICAL_PADDING : highlightTop - height - VERTICAL_PADDING;
1094
+ const clampedLeft = clamp(left - width / 2, 0, pageLeft + pageWidth - width);
1095
+ return /* @__PURE__ */ React5.createElement(
1096
+ "div",
1097
+ {
1098
+ className: "PdfHighlighter__tip-container",
1099
+ style: {
1100
+ top,
1101
+ left: clampedLeft,
1102
+ height: "max-content",
1103
+ width: "max-content"
1104
+ },
1105
+ ref: containerRef
1106
+ },
1107
+ content
1108
+ );
1109
+ };
1110
+
1111
+ // src/components/PdfHighlighter.tsx
1112
+ var EventBus;
1113
+ var PDFLinkService;
1114
+ var PDFViewer;
1115
+ (async () => {
1116
+ const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");
1117
+ EventBus = pdfjs.EventBus;
1118
+ PDFLinkService = pdfjs.PDFLinkService;
1119
+ PDFViewer = pdfjs.PDFViewer;
1120
+ })();
1121
+ var SCROLL_MARGIN = 10;
1122
+ var DEFAULT_SCALE_VALUE = "auto";
1123
+ var DEFAULT_TEXT_SELECTION_COLOR = "rgba(153,193,218,255)";
1124
+ var findOrCreateHighlightLayer = (textLayer) => {
1125
+ return findOrCreateContainerLayer(
1126
+ textLayer,
1127
+ "PdfHighlighter__highlight-layer"
1128
+ );
1129
+ };
1130
+ var disableTextSelection = (viewer, flag) => {
1131
+ viewer.viewer?.classList.toggle("PdfHighlighter--disable-selection", flag);
1132
+ };
1133
+ var PdfHighlighter = ({
1134
+ highlights,
1135
+ onScrollAway,
1136
+ pdfScaleValue = DEFAULT_SCALE_VALUE,
1137
+ onSelection: onSelectionFinished,
1138
+ onCreateGhostHighlight,
1139
+ onRemoveGhostHighlight,
1140
+ selectionTip,
1141
+ enableAreaSelection,
1142
+ areaSelectionMode,
1143
+ mouseSelectionStyle,
1144
+ pdfDocument,
1145
+ children,
1146
+ textSelectionColor = DEFAULT_TEXT_SELECTION_COLOR,
1147
+ utilsRef,
1148
+ style,
1149
+ enableFreetextCreation,
1150
+ onFreetextClick,
1151
+ enableImageCreation,
1152
+ onImageClick,
1153
+ enableDrawingMode,
1154
+ onDrawingComplete,
1155
+ onDrawingCancel,
1156
+ drawingStrokeColor = "#000000",
1157
+ drawingStrokeWidth = 3,
1158
+ enableShapeMode,
1159
+ onShapeComplete,
1160
+ onShapeCancel,
1161
+ shapeStrokeColor = "#000000",
1162
+ shapeStrokeWidth = 2
1163
+ }) => {
1164
+ const [tip, setTip] = useState5(null);
1165
+ const [isViewerReady, setIsViewerReady] = useState5(false);
1166
+ const containerNodeRef = useRef5(null);
1167
+ const highlightBindingsRef = useRef5(
1168
+ {}
1169
+ );
1170
+ const ghostHighlightRef = useRef5(null);
1171
+ const selectionRef = useRef5(null);
1172
+ const scrolledToHighlightIdRef = useRef5(null);
1173
+ const isAreaSelectionInProgressRef = useRef5(false);
1174
+ const isEditInProgressRef = useRef5(false);
1175
+ const updateTipPositionRef = useRef5(() => {
1176
+ });
1177
+ const eventBusRef = useRef5(new EventBus());
1178
+ const linkServiceRef = useRef5(
1179
+ new PDFLinkService({
1180
+ eventBus: eventBusRef.current,
1181
+ externalLinkTarget: 2
1182
+ })
1183
+ );
1184
+ const resizeObserverRef = useRef5(null);
1185
+ const viewerRef = useRef5(null);
1186
+ useLayoutEffect2(() => {
1187
+ if (!containerNodeRef.current) return;
1188
+ const debouncedDocumentInit = debounce(() => {
1189
+ viewerRef.current = viewerRef.current || new PDFViewer({
1190
+ container: containerNodeRef.current,
1191
+ eventBus: eventBusRef.current,
1192
+ textLayerMode: 2,
1193
+ removePageBorders: true,
1194
+ linkService: linkServiceRef.current
1195
+ });
1196
+ viewerRef.current.setDocument(pdfDocument);
1197
+ linkServiceRef.current.setDocument(pdfDocument);
1198
+ linkServiceRef.current.setViewer(viewerRef.current);
1199
+ setIsViewerReady(true);
1200
+ }, 100);
1201
+ debouncedDocumentInit();
1202
+ return () => {
1203
+ debouncedDocumentInit.cancel();
1204
+ };
1205
+ }, [document]);
1206
+ useLayoutEffect2(() => {
1207
+ if (!containerNodeRef.current) return;
1208
+ resizeObserverRef.current = new ResizeObserver(handleScaleValue);
1209
+ resizeObserverRef.current.observe(containerNodeRef.current);
1210
+ const doc = containerNodeRef.current.ownerDocument;
1211
+ eventBusRef.current.on("textlayerrendered", renderHighlightLayers);
1212
+ eventBusRef.current.on("pagesinit", handleScaleValue);
1213
+ doc.addEventListener("keydown", handleKeyDown);
1214
+ renderHighlightLayers();
1215
+ return () => {
1216
+ eventBusRef.current.off("pagesinit", handleScaleValue);
1217
+ eventBusRef.current.off("textlayerrendered", renderHighlightLayers);
1218
+ doc.removeEventListener("keydown", handleKeyDown);
1219
+ resizeObserverRef.current?.disconnect();
1220
+ };
1221
+ }, [selectionTip, highlights, onSelectionFinished]);
1222
+ const handleScroll = () => {
1223
+ onScrollAway && onScrollAway();
1224
+ scrolledToHighlightIdRef.current = null;
1225
+ renderHighlightLayers();
1226
+ };
1227
+ const handleMouseUp = () => {
1228
+ const container = containerNodeRef.current;
1229
+ const selection = getWindow(container).getSelection();
1230
+ if (!container || !selection || selection.isCollapsed || !viewerRef.current)
1231
+ return;
1232
+ const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
1233
+ if (!range || !container.contains(range.commonAncestorContainer)) return;
1234
+ const pages = getPagesFromRange(range);
1235
+ if (!pages || pages.length === 0) return;
1236
+ const rects = get_client_rects_default(range, pages);
1237
+ if (rects.length === 0) return;
1238
+ const viewportPosition = {
1239
+ boundingRect: get_bounding_rect_default(rects),
1240
+ rects
1241
+ };
1242
+ const scaledPosition = viewportPositionToScaled(
1243
+ viewportPosition,
1244
+ viewerRef.current
1245
+ );
1246
+ const content = {
1247
+ text: selection.toString().split("\n").join(" ")
1248
+ // Make all line breaks spaces
1249
+ };
1250
+ selectionRef.current = {
1251
+ content,
1252
+ type: "text",
1253
+ position: scaledPosition,
1254
+ makeGhostHighlight: () => {
1255
+ ghostHighlightRef.current = {
1256
+ content,
1257
+ type: "text",
1258
+ position: scaledPosition
1259
+ };
1260
+ onCreateGhostHighlight && onCreateGhostHighlight(ghostHighlightRef.current);
1261
+ clearTextSelection();
1262
+ renderHighlightLayers();
1263
+ return ghostHighlightRef.current;
1264
+ }
1265
+ };
1266
+ onSelectionFinished && onSelectionFinished(selectionRef.current);
1267
+ selectionTip && setTip({ position: viewportPosition, content: selectionTip });
1268
+ };
1269
+ const handleMouseDown = (event) => {
1270
+ if (!isHTMLElement(event.target) || asElement(event.target).closest(".PdfHighlighter__tip-container")) {
1271
+ return;
1272
+ }
1273
+ if (enableFreetextCreation?.(event.nativeEvent) && onFreetextClick && !isEditInProgressRef.current) {
1274
+ const target = asElement(event.target);
1275
+ const page = getPageFromElement(target);
1276
+ if (page && viewerRef.current) {
1277
+ const pageRect = page.node.getBoundingClientRect();
1278
+ const clickX = event.clientX - pageRect.left;
1279
+ const clickY = event.clientY - pageRect.top;
1280
+ const defaultWidth = 150;
1281
+ const defaultHeight = 80;
1282
+ const viewportPosition = {
1283
+ boundingRect: {
1284
+ left: clickX,
1285
+ top: clickY,
1286
+ width: defaultWidth,
1287
+ height: defaultHeight,
1288
+ pageNumber: page.number
1289
+ },
1290
+ rects: []
1291
+ };
1292
+ const scaledPosition = viewportPositionToScaled(
1293
+ viewportPosition,
1294
+ viewerRef.current
1295
+ );
1296
+ onFreetextClick(scaledPosition);
1297
+ return;
1298
+ }
1299
+ }
1300
+ if (enableImageCreation?.(event.nativeEvent) && onImageClick && !isEditInProgressRef.current) {
1301
+ const target = asElement(event.target);
1302
+ const page = getPageFromElement(target);
1303
+ if (page && viewerRef.current) {
1304
+ const pageRect = page.node.getBoundingClientRect();
1305
+ const clickX = event.clientX - pageRect.left;
1306
+ const clickY = event.clientY - pageRect.top;
1307
+ const defaultWidth = 150;
1308
+ const defaultHeight = 100;
1309
+ const viewportPosition = {
1310
+ boundingRect: {
1311
+ left: clickX,
1312
+ top: clickY,
1313
+ width: defaultWidth,
1314
+ height: defaultHeight,
1315
+ pageNumber: page.number
1316
+ },
1317
+ rects: []
1318
+ };
1319
+ const scaledPosition = viewportPositionToScaled(
1320
+ viewportPosition,
1321
+ viewerRef.current
1322
+ );
1323
+ onImageClick(scaledPosition);
1324
+ return;
1325
+ }
1326
+ }
1327
+ setTip(null);
1328
+ clearTextSelection();
1329
+ removeGhostHighlight();
1330
+ toggleEditInProgress(false);
1331
+ };
1332
+ const handleKeyDown = (event) => {
1333
+ if (event.code === "Escape") {
1334
+ clearTextSelection();
1335
+ removeGhostHighlight();
1336
+ setTip(null);
1337
+ }
1338
+ };
1339
+ const handleScaleValue = () => {
1340
+ if (viewerRef.current) {
1341
+ viewerRef.current.currentScaleValue = pdfScaleValue.toString();
1342
+ }
1343
+ };
1344
+ const renderHighlightLayer = (highlightBindings, pageNumber) => {
1345
+ if (!viewerRef.current) return;
1346
+ highlightBindings.reactRoot.render(
1347
+ /* @__PURE__ */ React6.createElement(PdfHighlighterContext.Provider, { value: pdfHighlighterUtils }, /* @__PURE__ */ React6.createElement(
1348
+ HighlightLayer,
1349
+ {
1350
+ highlightsByPage: group_highlights_by_page_default([
1351
+ ...highlights,
1352
+ ghostHighlightRef.current
1353
+ ]),
1354
+ pageNumber,
1355
+ scrolledToHighlightId: scrolledToHighlightIdRef.current,
1356
+ viewer: viewerRef.current,
1357
+ highlightBindings,
1358
+ children
1359
+ }
1360
+ ))
1361
+ );
1362
+ };
1363
+ const renderHighlightLayers = () => {
1364
+ if (!viewerRef.current) return;
1365
+ for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber++) {
1366
+ const highlightBindings = highlightBindingsRef.current[pageNumber];
1367
+ if (highlightBindings?.container?.isConnected) {
1368
+ renderHighlightLayer(highlightBindings, pageNumber);
1369
+ } else {
1370
+ const { textLayer } = viewerRef.current.getPageView(pageNumber - 1) || {};
1371
+ if (!textLayer) continue;
1372
+ const highlightLayer = findOrCreateHighlightLayer(
1373
+ textLayer.div
1374
+ );
1375
+ if (highlightLayer) {
1376
+ const reactRoot = createRoot(highlightLayer);
1377
+ highlightBindingsRef.current[pageNumber] = {
1378
+ reactRoot,
1379
+ container: highlightLayer,
1380
+ textLayer: textLayer.div
1381
+ // textLayer.div for version >=3.0 and textLayer.textLayerDiv otherwise.
1382
+ };
1383
+ renderHighlightLayer(
1384
+ highlightBindingsRef.current[pageNumber],
1385
+ pageNumber
1386
+ );
1387
+ }
1388
+ }
1389
+ }
1390
+ };
1391
+ const isEditingOrHighlighting = () => {
1392
+ return Boolean(selectionRef.current) || Boolean(ghostHighlightRef.current) || isAreaSelectionInProgressRef.current || isEditInProgressRef.current;
1393
+ };
1394
+ const toggleEditInProgress = (flag) => {
1395
+ if (flag !== void 0) {
1396
+ isEditInProgressRef.current = flag;
1397
+ } else {
1398
+ isEditInProgressRef.current = !isEditInProgressRef.current;
1399
+ }
1400
+ if (viewerRef.current)
1401
+ viewerRef.current.viewer?.classList.toggle(
1402
+ "PdfHighlighter--disable-selection",
1403
+ isEditInProgressRef.current
1404
+ );
1405
+ };
1406
+ const removeGhostHighlight = () => {
1407
+ if (onRemoveGhostHighlight && ghostHighlightRef.current)
1408
+ onRemoveGhostHighlight(ghostHighlightRef.current);
1409
+ ghostHighlightRef.current = null;
1410
+ renderHighlightLayers();
1411
+ };
1412
+ const clearTextSelection = () => {
1413
+ selectionRef.current = null;
1414
+ const container = containerNodeRef.current;
1415
+ const selection = getWindow(container).getSelection();
1416
+ if (!container || !selection) return;
1417
+ selection.removeAllRanges();
1418
+ };
1419
+ const scrollToHighlight = (highlight) => {
1420
+ const { boundingRect, usePdfCoordinates } = highlight.position;
1421
+ const pageNumber = boundingRect.pageNumber;
1422
+ viewerRef.current.container.removeEventListener("scroll", handleScroll);
1423
+ const pageViewport = viewerRef.current.getPageView(
1424
+ pageNumber - 1
1425
+ ).viewport;
1426
+ viewerRef.current.scrollPageIntoView({
1427
+ pageNumber,
1428
+ destArray: [
1429
+ null,
1430
+ // null since we pass pageNumber already as an arg
1431
+ { name: "XYZ" },
1432
+ ...pageViewport.convertToPdfPoint(
1433
+ 0,
1434
+ // Default x coord
1435
+ scaledToViewport(boundingRect, pageViewport, usePdfCoordinates).top - SCROLL_MARGIN
1436
+ ),
1437
+ 0
1438
+ // Default z coord
1439
+ ]
1440
+ });
1441
+ scrolledToHighlightIdRef.current = highlight.id;
1442
+ renderHighlightLayers();
1443
+ setTimeout(() => {
1444
+ viewerRef.current.container.addEventListener("scroll", handleScroll, {
1445
+ once: true
1446
+ });
1447
+ }, 100);
1448
+ };
1449
+ const pdfHighlighterUtils = {
1450
+ isEditingOrHighlighting,
1451
+ getCurrentSelection: () => selectionRef.current,
1452
+ getGhostHighlight: () => ghostHighlightRef.current,
1453
+ removeGhostHighlight,
1454
+ toggleEditInProgress,
1455
+ isEditInProgress: () => isEditInProgressRef.current,
1456
+ isSelectionInProgress: () => Boolean(selectionRef.current) || isAreaSelectionInProgressRef.current,
1457
+ scrollToHighlight,
1458
+ getViewer: () => viewerRef.current,
1459
+ getTip: () => tip,
1460
+ setTip,
1461
+ updateTipPosition: updateTipPositionRef.current
1462
+ };
1463
+ utilsRef(pdfHighlighterUtils);
1464
+ const isFreetextMode = enableFreetextCreation?.({}) ?? false;
1465
+ const isImageMode = enableImageCreation?.({}) ?? false;
1466
+ let containerClassName = "PdfHighlighter";
1467
+ if (isFreetextMode) containerClassName += " PdfHighlighter--freetext-mode";
1468
+ if (isImageMode) containerClassName += " PdfHighlighter--image-mode";
1469
+ if (enableDrawingMode) containerClassName += " PdfHighlighter--drawing-mode";
1470
+ if (enableShapeMode) containerClassName += " PdfHighlighter--shape-mode";
1471
+ if (areaSelectionMode) containerClassName += " PdfHighlighter--area-mode";
1472
+ return /* @__PURE__ */ React6.createElement(PdfHighlighterContext.Provider, { value: pdfHighlighterUtils }, /* @__PURE__ */ React6.createElement(
1473
+ "div",
1474
+ {
1475
+ ref: containerNodeRef,
1476
+ className: containerClassName,
1477
+ onPointerDown: handleMouseDown,
1478
+ onPointerUp: handleMouseUp,
1479
+ style
1480
+ },
1481
+ /* @__PURE__ */ React6.createElement("div", { className: "pdfViewer" }),
1482
+ /* @__PURE__ */ React6.createElement("style", null, `
1483
+ .textLayer ::selection {
1484
+ background: ${textSelectionColor};
1485
+ }
1486
+ `),
1487
+ isViewerReady && /* @__PURE__ */ React6.createElement(
1488
+ TipContainer,
1489
+ {
1490
+ viewer: viewerRef.current,
1491
+ updateTipPositionRef
1492
+ }
1493
+ ),
1494
+ isViewerReady && enableAreaSelection && /* @__PURE__ */ React6.createElement(
1495
+ MouseSelection,
1496
+ {
1497
+ viewer: viewerRef.current,
1498
+ onChange: (isVisible) => isAreaSelectionInProgressRef.current = isVisible,
1499
+ enableAreaSelection,
1500
+ style: mouseSelectionStyle,
1501
+ onDragStart: () => disableTextSelection(viewerRef.current, true),
1502
+ onReset: () => {
1503
+ selectionRef.current = null;
1504
+ disableTextSelection(viewerRef.current, false);
1505
+ },
1506
+ onSelection: (viewportPosition, scaledPosition, image, resetSelection) => {
1507
+ selectionRef.current = {
1508
+ content: { image },
1509
+ type: "area",
1510
+ position: scaledPosition,
1511
+ makeGhostHighlight: () => {
1512
+ ghostHighlightRef.current = {
1513
+ position: scaledPosition,
1514
+ type: "area",
1515
+ content: { image }
1516
+ };
1517
+ onCreateGhostHighlight && onCreateGhostHighlight(ghostHighlightRef.current);
1518
+ resetSelection();
1519
+ renderHighlightLayers();
1520
+ return ghostHighlightRef.current;
1521
+ }
1522
+ };
1523
+ onSelectionFinished && onSelectionFinished(selectionRef.current);
1524
+ selectionTip && setTip({ position: viewportPosition, content: selectionTip });
1525
+ }
1526
+ }
1527
+ ),
1528
+ isViewerReady && enableDrawingMode && /* @__PURE__ */ React6.createElement(
1529
+ DrawingCanvas,
1530
+ {
1531
+ isActive: enableDrawingMode,
1532
+ strokeColor: drawingStrokeColor,
1533
+ strokeWidth: drawingStrokeWidth,
1534
+ viewer: viewerRef.current,
1535
+ onComplete: (dataUrl, position, strokes) => {
1536
+ console.log("PdfHighlighter: Drawing complete");
1537
+ onDrawingComplete?.(dataUrl, position, strokes);
1538
+ },
1539
+ onCancel: () => {
1540
+ console.log("PdfHighlighter: Drawing cancelled");
1541
+ onDrawingCancel?.();
1542
+ }
1543
+ }
1544
+ ),
1545
+ isViewerReady && enableShapeMode && /* @__PURE__ */ React6.createElement(
1546
+ ShapeCanvas,
1547
+ {
1548
+ isActive: !!enableShapeMode,
1549
+ shapeType: enableShapeMode,
1550
+ strokeColor: shapeStrokeColor,
1551
+ strokeWidth: shapeStrokeWidth,
1552
+ viewer: viewerRef.current,
1553
+ onComplete: (position, shape) => {
1554
+ console.log("PdfHighlighter: Shape complete", shape.shapeType);
1555
+ onShapeComplete?.(position, shape);
1556
+ },
1557
+ onCancel: () => {
1558
+ console.log("PdfHighlighter: Shape cancelled");
1559
+ onShapeCancel?.();
1560
+ }
1561
+ }
1562
+ )
1563
+ ));
1564
+ };
1565
+
1566
+ // src/components/TextHighlight.tsx
1567
+ import React7, {
1568
+ useState as useState6,
1569
+ useRef as useRef6,
1570
+ useEffect as useEffect4
1571
+ } from "react";
1572
+ var DefaultStyleIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
1573
+ var DefaultDeleteIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
1574
+ var HighlightIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 14l3 3v5h6v-5l3-3V9H6v5zm5-12h2v3h-2V2zM3.5 5.875L4.914 4.46l2.12 2.122L5.622 8 3.5 5.875zm13.46.71l2.123-2.12 1.414 1.414L18.375 8l-1.414-1.414z" }));
1575
+ var UnderlineIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }));
1576
+ var StrikethroughIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z" }));
1577
+ var DEFAULT_COLOR_PRESETS = [
1578
+ "rgba(255, 226, 143, 1)",
1579
+ // Yellow (default)
1580
+ "#ffcdd2",
1581
+ // Light red
1582
+ "#c8e6c9",
1583
+ // Light green
1584
+ "#bbdefb",
1585
+ // Light blue
1586
+ "#e1bee7"
1587
+ // Light purple
1588
+ ];
1589
+ var TextHighlight = ({
1590
+ highlight,
1591
+ onClick,
1592
+ onMouseOver,
1593
+ onMouseOut,
1594
+ isScrolledTo,
1595
+ onContextMenu,
1596
+ style,
1597
+ highlightColor = "rgba(255, 226, 143, 1)",
1598
+ highlightStyle = "highlight",
1599
+ onStyleChange,
1600
+ onDelete,
1601
+ styleIcon,
1602
+ deleteIcon,
1603
+ colorPresets = DEFAULT_COLOR_PRESETS
1604
+ }) => {
1605
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState6(false);
1606
+ const [isHovered, setIsHovered] = useState6(false);
1607
+ const stylePanelRef = useRef6(null);
1608
+ const containerRef = useRef6(null);
1609
+ useEffect4(() => {
1610
+ if (!isStylePanelOpen) return;
1611
+ const handleClickOutside = (e) => {
1612
+ if (stylePanelRef.current && !stylePanelRef.current.contains(e.target)) {
1613
+ setIsStylePanelOpen(false);
1614
+ }
1615
+ };
1616
+ const timeoutId = setTimeout(() => {
1617
+ document.addEventListener("mousedown", handleClickOutside);
1618
+ }, 0);
1619
+ return () => {
1620
+ clearTimeout(timeoutId);
1621
+ document.removeEventListener("mousedown", handleClickOutside);
1622
+ };
1623
+ }, [isStylePanelOpen]);
1624
+ const highlightClass = isScrolledTo ? "TextHighlight--scrolledTo" : "";
1625
+ const { rects } = highlight.position;
1626
+ const firstRect = rects[0];
1627
+ const getPartStyleClass = () => {
1628
+ switch (highlightStyle) {
1629
+ case "underline":
1630
+ return "TextHighlight__part--underline";
1631
+ case "strikethrough":
1632
+ return "TextHighlight__part--strikethrough";
1633
+ default:
1634
+ return "";
1635
+ }
1636
+ };
1637
+ const getPartStyle = (rect) => {
1638
+ const baseStyle = { ...rect, ...style };
1639
+ if (highlightStyle === "highlight") {
1640
+ baseStyle.backgroundColor = highlightColor;
1641
+ } else {
1642
+ baseStyle.backgroundColor = "transparent";
1643
+ baseStyle.color = highlightColor;
1644
+ }
1645
+ return baseStyle;
1646
+ };
1647
+ return /* @__PURE__ */ React7.createElement(
1648
+ "div",
1649
+ {
1650
+ className: `TextHighlight ${highlightClass}`,
1651
+ onContextMenu,
1652
+ ref: containerRef
1653
+ },
1654
+ (onStyleChange || onDelete) && firstRect && /* @__PURE__ */ React7.createElement(
1655
+ "div",
1656
+ {
1657
+ className: "TextHighlight__toolbar-wrapper",
1658
+ style: {
1659
+ position: "absolute",
1660
+ left: firstRect.left,
1661
+ top: firstRect.top - 28,
1662
+ paddingBottom: 12
1663
+ },
1664
+ onMouseEnter: () => setIsHovered(true),
1665
+ onMouseLeave: () => setIsHovered(false)
1666
+ },
1667
+ /* @__PURE__ */ React7.createElement(
1668
+ "div",
1669
+ {
1670
+ className: `TextHighlight__toolbar ${isHovered || isStylePanelOpen ? "TextHighlight__toolbar--visible" : ""}`
1671
+ },
1672
+ onStyleChange && /* @__PURE__ */ React7.createElement(
1673
+ "button",
1674
+ {
1675
+ className: "TextHighlight__style-button",
1676
+ onClick: (e) => {
1677
+ e.stopPropagation();
1678
+ setIsStylePanelOpen(!isStylePanelOpen);
1679
+ },
1680
+ title: "Change style",
1681
+ type: "button"
1682
+ },
1683
+ styleIcon || /* @__PURE__ */ React7.createElement(DefaultStyleIcon, null)
1684
+ ),
1685
+ onDelete && /* @__PURE__ */ React7.createElement(
1686
+ "button",
1687
+ {
1688
+ className: "TextHighlight__delete-button",
1689
+ onClick: (e) => {
1690
+ e.stopPropagation();
1691
+ onDelete();
1692
+ },
1693
+ title: "Delete",
1694
+ type: "button"
1695
+ },
1696
+ deleteIcon || /* @__PURE__ */ React7.createElement(DefaultDeleteIcon, null)
1697
+ )
1698
+ ),
1699
+ isStylePanelOpen && onStyleChange && /* @__PURE__ */ React7.createElement(
1700
+ "div",
1701
+ {
1702
+ className: "TextHighlight__style-panel",
1703
+ ref: stylePanelRef,
1704
+ onClick: (e) => e.stopPropagation()
1705
+ },
1706
+ /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Style"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-buttons" }, /* @__PURE__ */ React7.createElement(
1707
+ "button",
1708
+ {
1709
+ type: "button",
1710
+ className: `TextHighlight__style-type-button ${highlightStyle === "highlight" ? "active" : ""}`,
1711
+ onClick: () => onStyleChange({ highlightStyle: "highlight" }),
1712
+ title: "Highlight"
1713
+ },
1714
+ /* @__PURE__ */ React7.createElement(HighlightIcon, null)
1715
+ ), /* @__PURE__ */ React7.createElement(
1716
+ "button",
1717
+ {
1718
+ type: "button",
1719
+ className: `TextHighlight__style-type-button ${highlightStyle === "underline" ? "active" : ""}`,
1720
+ onClick: () => onStyleChange({ highlightStyle: "underline" }),
1721
+ title: "Underline"
1722
+ },
1723
+ /* @__PURE__ */ React7.createElement(UnderlineIcon, null)
1724
+ ), /* @__PURE__ */ React7.createElement(
1725
+ "button",
1726
+ {
1727
+ type: "button",
1728
+ className: `TextHighlight__style-type-button ${highlightStyle === "strikethrough" ? "active" : ""}`,
1729
+ onClick: () => onStyleChange({ highlightStyle: "strikethrough" }),
1730
+ title: "Strikethrough"
1731
+ },
1732
+ /* @__PURE__ */ React7.createElement(StrikethroughIcon, null)
1733
+ ))),
1734
+ /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Color"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-options" }, /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React7.createElement(
1735
+ "button",
1736
+ {
1737
+ key: c,
1738
+ type: "button",
1739
+ className: `TextHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
1740
+ style: { backgroundColor: c },
1741
+ onClick: () => onStyleChange({ highlightColor: c }),
1742
+ title: c
1743
+ }
1744
+ ))), /* @__PURE__ */ React7.createElement(
1745
+ "input",
1746
+ {
1747
+ type: "color",
1748
+ value: highlightColor,
1749
+ onChange: (e) => {
1750
+ onStyleChange({ highlightColor: e.target.value });
1751
+ }
1752
+ }
1753
+ )))
1754
+ )
1755
+ ),
1756
+ /* @__PURE__ */ React7.createElement(
1757
+ "div",
1758
+ {
1759
+ className: "TextHighlight__parts",
1760
+ onMouseEnter: () => setIsHovered(true),
1761
+ onMouseLeave: () => setIsHovered(false)
1762
+ },
1763
+ rects.map((rect, index) => /* @__PURE__ */ React7.createElement(
1764
+ "div",
1765
+ {
1766
+ onMouseOver,
1767
+ onMouseOut,
1768
+ onClick,
1769
+ key: index,
1770
+ style: getPartStyle(rect),
1771
+ className: `TextHighlight__part ${getPartStyleClass()}`
1772
+ }
1773
+ ))
1774
+ )
1775
+ );
1776
+ };
1777
+
1778
+ // src/components/MonitoredHighlightContainer.tsx
1779
+ import React9, { useRef as useRef8 } from "react";
1780
+
1781
+ // src/components/MouseMonitor.tsx
1782
+ import React8, { useEffect as useEffect5, useRef as useRef7 } from "react";
1783
+ var MouseMonitor = ({
1784
+ onMoveAway,
1785
+ paddingX,
1786
+ paddingY,
1787
+ children
1788
+ }) => {
1789
+ const containerRef = useRef7(null);
1790
+ const onMouseMove = (event) => {
1791
+ if (!containerRef.current) return;
1792
+ const { clientX, clientY } = event;
1793
+ const { left, top, width, height } = containerRef.current.getBoundingClientRect();
1794
+ const inBoundsX = clientX > left - paddingX && clientX < left + width + paddingX;
1795
+ const inBoundsY = clientY > top - paddingY && clientY < top + height + paddingY;
1796
+ if (!(inBoundsX && inBoundsY)) {
1797
+ onMoveAway();
1798
+ }
1799
+ };
1800
+ useEffect5(() => {
1801
+ document.addEventListener("mousemove", onMouseMove);
1802
+ return () => {
1803
+ document.removeEventListener("mousemove", onMouseMove);
1804
+ };
1805
+ }, []);
1806
+ return /* @__PURE__ */ React8.createElement("div", { ref: containerRef }, children);
1807
+ };
1808
+
1809
+ // src/components/MonitoredHighlightContainer.tsx
1810
+ var MonitoredHighlightContainer = ({
1811
+ onMouseEnter,
1812
+ highlightTip,
1813
+ onMouseLeave,
1814
+ children
1815
+ }) => {
1816
+ const mouseInRef = useRef8(false);
1817
+ const { setTip, isEditingOrHighlighting } = usePdfHighlighterContext();
1818
+ return /* @__PURE__ */ React9.createElement(
1819
+ "div",
1820
+ {
1821
+ onMouseEnter: () => {
1822
+ mouseInRef.current = true;
1823
+ onMouseEnter && onMouseEnter();
1824
+ if (isEditingOrHighlighting()) return;
1825
+ if (highlightTip) {
1826
+ const monitoredHighlightTip = /* @__PURE__ */ React9.createElement(
1827
+ MouseMonitor,
1828
+ {
1829
+ onMoveAway: () => {
1830
+ if (mouseInRef.current) {
1831
+ return;
1832
+ }
1833
+ setTip(null);
1834
+ onMouseLeave && onMouseLeave();
1835
+ },
1836
+ paddingX: 60,
1837
+ paddingY: 30
1838
+ },
1839
+ highlightTip.content
1840
+ );
1841
+ setTip({
1842
+ position: highlightTip.position,
1843
+ content: monitoredHighlightTip
1844
+ });
1845
+ }
1846
+ },
1847
+ onMouseLeave: () => {
1848
+ mouseInRef.current = false;
1849
+ !highlightTip && onMouseLeave && onMouseLeave();
1850
+ }
1851
+ },
1852
+ children
1853
+ );
1854
+ };
1855
+
1856
+ // src/components/AreaHighlight.tsx
1857
+ import React10, {
1858
+ useState as useState7,
1859
+ useRef as useRef9,
1860
+ useEffect as useEffect6
1861
+ } from "react";
1862
+ import { Rnd } from "react-rnd";
1863
+ var DefaultStyleIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
1864
+ var DefaultDeleteIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
1865
+ var DEFAULT_COLOR_PRESETS2 = [
1866
+ "rgba(255, 226, 143, 1)",
1867
+ // Yellow (default)
1868
+ "#ffcdd2",
1869
+ // Light red
1870
+ "#c8e6c9",
1871
+ // Light green
1872
+ "#bbdefb",
1873
+ // Light blue
1874
+ "#e1bee7"
1875
+ // Light purple
1876
+ ];
1877
+ var AreaHighlight = ({
1878
+ highlight,
1879
+ onChange,
1880
+ isScrolledTo,
1881
+ bounds,
1882
+ onContextMenu,
1883
+ onEditStart,
1884
+ style,
1885
+ highlightColor = "rgba(255, 226, 143, 1)",
1886
+ onStyleChange,
1887
+ onDelete,
1888
+ styleIcon,
1889
+ deleteIcon,
1890
+ colorPresets = DEFAULT_COLOR_PRESETS2
1891
+ }) => {
1892
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState7(false);
1893
+ const [isHovered, setIsHovered] = useState7(false);
1894
+ const stylePanelRef = useRef9(null);
1895
+ useEffect6(() => {
1896
+ if (!isStylePanelOpen) return;
1897
+ const handleClickOutside = (e) => {
1898
+ if (stylePanelRef.current && !stylePanelRef.current.contains(e.target)) {
1899
+ setIsStylePanelOpen(false);
1900
+ }
1901
+ };
1902
+ const timeoutId = setTimeout(() => {
1903
+ document.addEventListener("mousedown", handleClickOutside);
1904
+ }, 0);
1905
+ return () => {
1906
+ clearTimeout(timeoutId);
1907
+ document.removeEventListener("mousedown", handleClickOutside);
1908
+ };
1909
+ }, [isStylePanelOpen]);
1910
+ const highlightClass = isScrolledTo ? "AreaHighlight--scrolledTo" : "";
1911
+ const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
1912
+ const mergedStyle = {
1913
+ ...style,
1914
+ backgroundColor: highlightColor
1915
+ };
1916
+ return /* @__PURE__ */ React10.createElement(
1917
+ "div",
1918
+ {
1919
+ className: `AreaHighlight ${highlightClass}`,
1920
+ onContextMenu
1921
+ },
1922
+ (onStyleChange || onDelete) && /* @__PURE__ */ React10.createElement(
1923
+ "div",
1924
+ {
1925
+ className: "AreaHighlight__toolbar-wrapper",
1926
+ style: {
1927
+ position: "absolute",
1928
+ left: highlight.position.boundingRect.left,
1929
+ top: highlight.position.boundingRect.top - 28,
1930
+ paddingBottom: 12
1931
+ },
1932
+ onMouseEnter: () => setIsHovered(true),
1933
+ onMouseLeave: () => setIsHovered(false)
1934
+ },
1935
+ /* @__PURE__ */ React10.createElement(
1936
+ "div",
1937
+ {
1938
+ className: `AreaHighlight__toolbar ${isHovered || isStylePanelOpen ? "AreaHighlight__toolbar--visible" : ""}`
1939
+ },
1940
+ onStyleChange && /* @__PURE__ */ React10.createElement(
1941
+ "button",
1942
+ {
1943
+ className: "AreaHighlight__style-button",
1944
+ onClick: (e) => {
1945
+ e.stopPropagation();
1946
+ setIsStylePanelOpen(!isStylePanelOpen);
1947
+ },
1948
+ title: "Change color",
1949
+ type: "button"
1950
+ },
1951
+ styleIcon || /* @__PURE__ */ React10.createElement(DefaultStyleIcon2, null)
1952
+ ),
1953
+ onDelete && /* @__PURE__ */ React10.createElement(
1954
+ "button",
1955
+ {
1956
+ className: "AreaHighlight__delete-button",
1957
+ onClick: (e) => {
1958
+ e.stopPropagation();
1959
+ onDelete();
1960
+ },
1961
+ title: "Delete",
1962
+ type: "button"
1963
+ },
1964
+ deleteIcon || /* @__PURE__ */ React10.createElement(DefaultDeleteIcon2, null)
1965
+ )
1966
+ ),
1967
+ isStylePanelOpen && onStyleChange && /* @__PURE__ */ React10.createElement(
1968
+ "div",
1969
+ {
1970
+ className: "AreaHighlight__style-panel",
1971
+ ref: stylePanelRef,
1972
+ onClick: (e) => e.stopPropagation()
1973
+ },
1974
+ /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__style-row" }, /* @__PURE__ */ React10.createElement("label", null, "Color"), /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-options" }, /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React10.createElement(
1975
+ "button",
1976
+ {
1977
+ key: c,
1978
+ type: "button",
1979
+ className: `AreaHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
1980
+ style: { backgroundColor: c },
1981
+ onClick: () => onStyleChange({ highlightColor: c }),
1982
+ title: c
1983
+ }
1984
+ ))), /* @__PURE__ */ React10.createElement(
1985
+ "input",
1986
+ {
1987
+ type: "color",
1988
+ value: highlightColor,
1989
+ onChange: (e) => {
1990
+ onStyleChange({ highlightColor: e.target.value });
1991
+ }
1992
+ }
1993
+ )))
1994
+ )
1995
+ ),
1996
+ /* @__PURE__ */ React10.createElement(
1997
+ Rnd,
1998
+ {
1999
+ onMouseEnter: () => setIsHovered(true),
2000
+ onMouseLeave: () => setIsHovered(false),
2001
+ className: "AreaHighlight__part",
2002
+ onDragStop: (_, data) => {
2003
+ const boundingRect = {
2004
+ ...highlight.position.boundingRect,
2005
+ top: data.y,
2006
+ left: data.x
2007
+ };
2008
+ onChange && onChange(boundingRect);
2009
+ },
2010
+ onResizeStop: (_mouseEvent, _direction, ref, _delta, position) => {
2011
+ const boundingRect = {
2012
+ top: position.y,
2013
+ left: position.x,
2014
+ width: ref.offsetWidth,
2015
+ height: ref.offsetHeight,
2016
+ pageNumber: getPageFromElement(ref)?.number || -1
2017
+ };
2018
+ onChange && onChange(boundingRect);
2019
+ },
2020
+ onDragStart: onEditStart,
2021
+ onResizeStart: onEditStart,
2022
+ default: {
2023
+ x: highlight.position.boundingRect.left,
2024
+ y: highlight.position.boundingRect.top,
2025
+ width: highlight.position.boundingRect.width,
2026
+ height: highlight.position.boundingRect.height
2027
+ },
2028
+ key,
2029
+ bounds,
2030
+ onClick: (event) => {
2031
+ event.stopPropagation();
2032
+ event.preventDefault();
2033
+ },
2034
+ style: mergedStyle
2035
+ }
2036
+ )
2037
+ );
2038
+ };
2039
+
2040
+ // src/components/FreetextHighlight.tsx
2041
+ import React11, {
2042
+ useState as useState8,
2043
+ useRef as useRef10,
2044
+ useEffect as useEffect7
2045
+ } from "react";
2046
+ import { Rnd as Rnd2 } from "react-rnd";
2047
+ var DefaultDragIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("circle", { cx: "8", cy: "6", r: "2" }), /* @__PURE__ */ React11.createElement("circle", { cx: "16", cy: "6", r: "2" }), /* @__PURE__ */ React11.createElement("circle", { cx: "8", cy: "12", r: "2" }), /* @__PURE__ */ React11.createElement("circle", { cx: "16", cy: "12", r: "2" }), /* @__PURE__ */ React11.createElement("circle", { cx: "8", cy: "18", r: "2" }), /* @__PURE__ */ React11.createElement("circle", { cx: "16", cy: "18", r: "2" }));
2048
+ var DefaultEditIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }));
2049
+ var DefaultStyleIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
2050
+ var DefaultDeleteIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
2051
+ var DEFAULT_BACKGROUND_PRESETS = ["transparent", "#ffffc8", "#ffcdd2", "#c8e6c9", "#bbdefb", "#e1bee7"];
2052
+ var DEFAULT_TEXT_PRESETS = ["#333333", "#d32f2f", "#1976d2", "#388e3c", "#7b1fa2"];
2053
+ var FreetextHighlight = ({
2054
+ highlight,
2055
+ onChange,
2056
+ onTextChange,
2057
+ onStyleChange,
2058
+ isScrolledTo,
2059
+ bounds,
2060
+ onContextMenu,
2061
+ onEditStart,
2062
+ onEditEnd,
2063
+ style,
2064
+ color = "#333333",
2065
+ backgroundColor = "#ffffc8",
2066
+ fontFamily = "inherit",
2067
+ fontSize = "14px",
2068
+ dragIcon,
2069
+ editIcon,
2070
+ styleIcon,
2071
+ backgroundColorPresets = DEFAULT_BACKGROUND_PRESETS,
2072
+ textColorPresets = DEFAULT_TEXT_PRESETS,
2073
+ onDelete,
2074
+ deleteIcon
2075
+ }) => {
2076
+ const [isEditing, setIsEditing] = useState8(false);
2077
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState8(false);
2078
+ const [text, setText] = useState8(highlight.content?.text || "");
2079
+ const textareaRef = useRef10(null);
2080
+ const stylePanelRef = useRef10(null);
2081
+ useEffect7(() => {
2082
+ setText(highlight.content?.text || "");
2083
+ }, [highlight.content?.text]);
2084
+ useEffect7(() => {
2085
+ if (isEditing && textareaRef.current) {
2086
+ textareaRef.current.focus();
2087
+ textareaRef.current.select();
2088
+ }
2089
+ }, [isEditing]);
2090
+ useEffect7(() => {
2091
+ if (!isStylePanelOpen) return;
2092
+ const handleClickOutside = (e) => {
2093
+ if (stylePanelRef.current && !stylePanelRef.current.contains(e.target)) {
2094
+ setIsStylePanelOpen(false);
2095
+ }
2096
+ };
2097
+ const timeoutId = setTimeout(() => {
2098
+ document.addEventListener("mousedown", handleClickOutside);
2099
+ }, 0);
2100
+ return () => {
2101
+ clearTimeout(timeoutId);
2102
+ document.removeEventListener("mousedown", handleClickOutside);
2103
+ };
2104
+ }, [isStylePanelOpen]);
2105
+ const highlightClass = isScrolledTo ? "FreetextHighlight--scrolledTo" : "";
2106
+ const editingClass = isEditing ? "FreetextHighlight--editing" : "";
2107
+ const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
2108
+ const handleTextClick = (e) => {
2109
+ e.stopPropagation();
2110
+ if (!isEditing) {
2111
+ setIsEditing(true);
2112
+ onEditStart?.();
2113
+ }
2114
+ };
2115
+ const handleTextBlur = () => {
2116
+ if (isEditing) {
2117
+ setIsEditing(false);
2118
+ onTextChange?.(text);
2119
+ onEditEnd?.();
2120
+ }
2121
+ };
2122
+ const handleKeyDown = (e) => {
2123
+ if (e.key === "Escape") {
2124
+ e.preventDefault();
2125
+ setText(highlight.content?.text || "");
2126
+ setIsEditing(false);
2127
+ onEditEnd?.();
2128
+ } else if (e.key === "Enter" && !e.shiftKey) {
2129
+ e.preventDefault();
2130
+ setIsEditing(false);
2131
+ onTextChange?.(text);
2132
+ onEditEnd?.();
2133
+ }
2134
+ };
2135
+ const containerStyle = {
2136
+ backgroundColor,
2137
+ color,
2138
+ fontFamily,
2139
+ fontSize,
2140
+ ...style
2141
+ };
2142
+ return /* @__PURE__ */ React11.createElement(
2143
+ "div",
2144
+ {
2145
+ className: `FreetextHighlight ${highlightClass} ${editingClass}`,
2146
+ onContextMenu
2147
+ },
2148
+ /* @__PURE__ */ React11.createElement(
2149
+ Rnd2,
2150
+ {
2151
+ className: "FreetextHighlight__rnd",
2152
+ onDragStop: (_, data) => {
2153
+ const boundingRect = {
2154
+ ...highlight.position.boundingRect,
2155
+ top: data.y,
2156
+ left: data.x
2157
+ };
2158
+ onChange?.(boundingRect);
2159
+ },
2160
+ onDragStart: () => {
2161
+ if (!isEditing) {
2162
+ onEditStart?.();
2163
+ }
2164
+ },
2165
+ default: {
2166
+ x: highlight.position.boundingRect.left,
2167
+ y: highlight.position.boundingRect.top,
2168
+ width: highlight.position.boundingRect.width || 150,
2169
+ height: highlight.position.boundingRect.height || 80
2170
+ },
2171
+ minWidth: 100,
2172
+ minHeight: 50,
2173
+ key,
2174
+ bounds,
2175
+ enableResizing: {
2176
+ top: false,
2177
+ right: true,
2178
+ bottom: true,
2179
+ left: false,
2180
+ topRight: false,
2181
+ bottomRight: true,
2182
+ bottomLeft: false,
2183
+ topLeft: false
2184
+ },
2185
+ onResizeStop: (_e, _direction, ref, _delta, position) => {
2186
+ const boundingRect = {
2187
+ top: position.y,
2188
+ left: position.x,
2189
+ width: ref.offsetWidth,
2190
+ height: ref.offsetHeight,
2191
+ pageNumber: getPageFromElement(ref)?.number || highlight.position.boundingRect.pageNumber
2192
+ };
2193
+ onChange?.(boundingRect);
2194
+ },
2195
+ onResizeStart: () => {
2196
+ if (!isEditing) {
2197
+ onEditStart?.();
2198
+ }
2199
+ },
2200
+ cancel: ".FreetextHighlight__text, .FreetextHighlight__input, .FreetextHighlight__edit-button, .FreetextHighlight__style-button, .FreetextHighlight__style-panel, .FreetextHighlight__delete-button"
2201
+ },
2202
+ /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__container", style: containerStyle }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__toolbar" }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React11.createElement(DefaultDragIcon, null)), /* @__PURE__ */ React11.createElement(
2203
+ "button",
2204
+ {
2205
+ className: "FreetextHighlight__edit-button",
2206
+ onClick: handleTextClick,
2207
+ title: "Edit text",
2208
+ type: "button"
2209
+ },
2210
+ editIcon || /* @__PURE__ */ React11.createElement(DefaultEditIcon, null)
2211
+ ), /* @__PURE__ */ React11.createElement(
2212
+ "button",
2213
+ {
2214
+ className: "FreetextHighlight__style-button",
2215
+ onClick: (e) => {
2216
+ e.stopPropagation();
2217
+ setIsStylePanelOpen(!isStylePanelOpen);
2218
+ },
2219
+ title: "Change style",
2220
+ type: "button"
2221
+ },
2222
+ styleIcon || /* @__PURE__ */ React11.createElement(DefaultStyleIcon3, null)
2223
+ ), onDelete && /* @__PURE__ */ React11.createElement(
2224
+ "button",
2225
+ {
2226
+ className: "FreetextHighlight__delete-button",
2227
+ onClick: (e) => {
2228
+ e.stopPropagation();
2229
+ onDelete();
2230
+ },
2231
+ title: "Delete",
2232
+ type: "button"
2233
+ },
2234
+ deleteIcon || /* @__PURE__ */ React11.createElement(DefaultDeleteIcon3, null)
2235
+ )), isStylePanelOpen && /* @__PURE__ */ React11.createElement(
2236
+ "div",
2237
+ {
2238
+ className: "FreetextHighlight__style-panel",
2239
+ ref: stylePanelRef,
2240
+ onClick: (e) => e.stopPropagation()
2241
+ },
2242
+ /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__style-row" }, /* @__PURE__ */ React11.createElement("label", null, "Background"), /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__color-options" }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__color-presets" }, backgroundColorPresets.map((c) => /* @__PURE__ */ React11.createElement(
2243
+ "button",
2244
+ {
2245
+ key: c,
2246
+ type: "button",
2247
+ className: `FreetextHighlight__color-preset ${c === "transparent" ? "FreetextHighlight__color-preset--transparent" : ""} ${backgroundColor === c ? "active" : ""}`,
2248
+ style: c !== "transparent" ? { backgroundColor: c } : void 0,
2249
+ onClick: () => onStyleChange?.({ backgroundColor: c }),
2250
+ title: c === "transparent" ? "No background" : c
2251
+ }
2252
+ ))), /* @__PURE__ */ React11.createElement(
2253
+ "input",
2254
+ {
2255
+ type: "color",
2256
+ value: backgroundColor === "transparent" ? "#ffffff" : backgroundColor,
2257
+ onChange: (e) => {
2258
+ onStyleChange?.({ backgroundColor: e.target.value });
2259
+ }
2260
+ }
2261
+ ))),
2262
+ /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__style-row" }, /* @__PURE__ */ React11.createElement(
2263
+ "label",
2264
+ null,
2265
+ "Text Color"
2266
+ ), /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__color-options" }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__color-presets" }, textColorPresets.map((c) => /* @__PURE__ */ React11.createElement(
2267
+ "button",
2268
+ {
2269
+ key: c,
2270
+ type: "button",
2271
+ className: `FreetextHighlight__color-preset ${color === c ? "active" : ""}`,
2272
+ style: { backgroundColor: c },
2273
+ onClick: () => onStyleChange?.({ color: c }),
2274
+ title: c
2275
+ }
2276
+ ))), /* @__PURE__ */ React11.createElement(
2277
+ "input",
2278
+ {
2279
+ type: "color",
2280
+ value: color,
2281
+ onChange: (e) => {
2282
+ onStyleChange?.({ color: e.target.value });
2283
+ }
2284
+ }
2285
+ ))),
2286
+ /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__style-row" }, /* @__PURE__ */ React11.createElement("label", null, "Font Size"), /* @__PURE__ */ React11.createElement(
2287
+ "select",
2288
+ {
2289
+ value: fontSize,
2290
+ onChange: (e) => {
2291
+ onStyleChange?.({ fontSize: e.target.value });
2292
+ }
2293
+ },
2294
+ /* @__PURE__ */ React11.createElement("option", { value: "10px" }, "10px"),
2295
+ /* @__PURE__ */ React11.createElement("option", { value: "12px" }, "12px"),
2296
+ /* @__PURE__ */ React11.createElement("option", { value: "14px" }, "14px"),
2297
+ /* @__PURE__ */ React11.createElement("option", { value: "16px" }, "16px"),
2298
+ /* @__PURE__ */ React11.createElement("option", { value: "18px" }, "18px"),
2299
+ /* @__PURE__ */ React11.createElement("option", { value: "20px" }, "20px"),
2300
+ /* @__PURE__ */ React11.createElement("option", { value: "24px" }, "24px")
2301
+ )),
2302
+ /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__style-row" }, /* @__PURE__ */ React11.createElement("label", null, "Font"), /* @__PURE__ */ React11.createElement(
2303
+ "select",
2304
+ {
2305
+ value: fontFamily,
2306
+ onChange: (e) => {
2307
+ onStyleChange?.({ fontFamily: e.target.value });
2308
+ }
2309
+ },
2310
+ /* @__PURE__ */ React11.createElement("option", { value: "inherit" }, "Default"),
2311
+ /* @__PURE__ */ React11.createElement("option", { value: "Arial, sans-serif" }, "Arial"),
2312
+ /* @__PURE__ */ React11.createElement("option", { value: "Georgia, serif" }, "Georgia"),
2313
+ /* @__PURE__ */ React11.createElement("option", { value: "'Courier New', monospace" }, "Courier"),
2314
+ /* @__PURE__ */ React11.createElement("option", { value: "'Times New Roman', serif" }, "Times")
2315
+ ))
2316
+ ), /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__content" }, isEditing ? /* @__PURE__ */ React11.createElement(
2317
+ "textarea",
2318
+ {
2319
+ ref: textareaRef,
2320
+ className: "FreetextHighlight__input",
2321
+ value: text,
2322
+ onChange: (e) => setText(e.target.value),
2323
+ onBlur: handleTextBlur,
2324
+ onKeyDown: handleKeyDown,
2325
+ onClick: (e) => e.stopPropagation()
2326
+ }
2327
+ ) : /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__text" }, text || "New note")))
2328
+ )
2329
+ );
2330
+ };
2331
+
2332
+ // src/components/ImageHighlight.tsx
2333
+ import React12 from "react";
2334
+ import { Rnd as Rnd3 } from "react-rnd";
2335
+ var DefaultDragIcon2 = () => /* @__PURE__ */ React12.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React12.createElement("circle", { cx: "8", cy: "6", r: "2" }), /* @__PURE__ */ React12.createElement("circle", { cx: "16", cy: "6", r: "2" }), /* @__PURE__ */ React12.createElement("circle", { cx: "8", cy: "12", r: "2" }), /* @__PURE__ */ React12.createElement("circle", { cx: "16", cy: "12", r: "2" }), /* @__PURE__ */ React12.createElement("circle", { cx: "8", cy: "18", r: "2" }), /* @__PURE__ */ React12.createElement("circle", { cx: "16", cy: "18", r: "2" }));
2336
+ var DefaultDeleteIcon4 = () => /* @__PURE__ */ React12.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React12.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
2337
+ var ImageHighlight = ({
2338
+ highlight,
2339
+ onChange,
2340
+ isScrolledTo,
2341
+ bounds,
2342
+ onContextMenu,
2343
+ onEditStart,
2344
+ onEditEnd,
2345
+ style,
2346
+ dragIcon,
2347
+ onDelete,
2348
+ deleteIcon
2349
+ }) => {
2350
+ const highlightClass = isScrolledTo ? "ImageHighlight--scrolledTo" : "";
2351
+ const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
2352
+ const imageUrl = highlight.content?.image;
2353
+ return /* @__PURE__ */ React12.createElement(
2354
+ "div",
2355
+ {
2356
+ className: `ImageHighlight ${highlightClass}`,
2357
+ onContextMenu
2358
+ },
2359
+ /* @__PURE__ */ React12.createElement(
2360
+ Rnd3,
2361
+ {
2362
+ className: "ImageHighlight__rnd",
2363
+ onDragStop: (_, data) => {
2364
+ const boundingRect = {
2365
+ ...highlight.position.boundingRect,
2366
+ top: data.y,
2367
+ left: data.x
2368
+ };
2369
+ onChange?.(boundingRect);
2370
+ onEditEnd?.();
2371
+ },
2372
+ onDragStart: onEditStart,
2373
+ onResizeStop: (_e, _direction, ref, _delta, position) => {
2374
+ const boundingRect = {
2375
+ top: position.y,
2376
+ left: position.x,
2377
+ width: ref.offsetWidth,
2378
+ height: ref.offsetHeight,
2379
+ pageNumber: getPageFromElement(ref)?.number || highlight.position.boundingRect.pageNumber
2380
+ };
2381
+ onChange?.(boundingRect);
2382
+ onEditEnd?.();
2383
+ },
2384
+ onResizeStart: onEditStart,
2385
+ default: {
2386
+ x: highlight.position.boundingRect.left,
2387
+ y: highlight.position.boundingRect.top,
2388
+ width: highlight.position.boundingRect.width || 150,
2389
+ height: highlight.position.boundingRect.height || 100
2390
+ },
2391
+ minWidth: 50,
2392
+ minHeight: 50,
2393
+ key,
2394
+ bounds,
2395
+ lockAspectRatio: true,
2396
+ dragHandleClassName: "ImageHighlight__drag-handle",
2397
+ onClick: (event) => {
2398
+ event.stopPropagation();
2399
+ event.preventDefault();
2400
+ },
2401
+ style
2402
+ },
2403
+ /* @__PURE__ */ React12.createElement("div", { className: "ImageHighlight__container" }, /* @__PURE__ */ React12.createElement("div", { className: "ImageHighlight__toolbar" }, /* @__PURE__ */ React12.createElement("div", { className: "ImageHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React12.createElement(DefaultDragIcon2, null)), onDelete && /* @__PURE__ */ React12.createElement(
2404
+ "button",
2405
+ {
2406
+ className: "ImageHighlight__delete-button",
2407
+ onClick: (e) => {
2408
+ e.stopPropagation();
2409
+ onDelete();
2410
+ },
2411
+ title: "Delete",
2412
+ type: "button"
2413
+ },
2414
+ deleteIcon || /* @__PURE__ */ React12.createElement(DefaultDeleteIcon4, null)
2415
+ )), /* @__PURE__ */ React12.createElement("div", { className: "ImageHighlight__content" }, imageUrl ? /* @__PURE__ */ React12.createElement(
2416
+ "img",
2417
+ {
2418
+ src: imageUrl,
2419
+ alt: "Highlight",
2420
+ className: "ImageHighlight__image",
2421
+ draggable: false
2422
+ }
2423
+ ) : /* @__PURE__ */ React12.createElement("div", { className: "ImageHighlight__placeholder" }, "No image")))
2424
+ )
2425
+ );
2426
+ };
2427
+
2428
+ // src/components/SignaturePad.tsx
2429
+ import React13, { useRef as useRef11, useEffect as useEffect8, useCallback as useCallback3 } from "react";
2430
+ var SignaturePad = ({
2431
+ isOpen,
2432
+ onComplete,
2433
+ onClose,
2434
+ width = 400,
2435
+ height = 200
2436
+ }) => {
2437
+ const canvasRef = useRef11(null);
2438
+ const isDrawingRef = useRef11(false);
2439
+ const lastPosRef = useRef11({ x: 0, y: 0 });
2440
+ useEffect8(() => {
2441
+ if (!isOpen || !canvasRef.current) return;
2442
+ const canvas = canvasRef.current;
2443
+ const ctx = canvas.getContext("2d");
2444
+ if (!ctx) return;
2445
+ ctx.strokeStyle = "#000000";
2446
+ ctx.lineWidth = 2;
2447
+ ctx.lineCap = "round";
2448
+ ctx.lineJoin = "round";
2449
+ ctx.fillStyle = "white";
2450
+ ctx.fillRect(0, 0, width, height);
2451
+ }, [isOpen, width, height]);
2452
+ const getPosition = useCallback3(
2453
+ (e) => {
2454
+ const canvas = canvasRef.current;
2455
+ if (!canvas) return { x: 0, y: 0 };
2456
+ const rect = canvas.getBoundingClientRect();
2457
+ let clientX;
2458
+ let clientY;
2459
+ if ("touches" in e) {
2460
+ clientX = e.touches[0].clientX;
2461
+ clientY = e.touches[0].clientY;
2462
+ } else {
2463
+ clientX = e.clientX;
2464
+ clientY = e.clientY;
2465
+ }
2466
+ return {
2467
+ x: clientX - rect.left,
2468
+ y: clientY - rect.top
2469
+ };
2470
+ },
2471
+ []
2472
+ );
2473
+ const startDrawing = useCallback3(
2474
+ (e) => {
2475
+ e.preventDefault();
2476
+ isDrawingRef.current = true;
2477
+ lastPosRef.current = getPosition(e);
2478
+ },
2479
+ [getPosition]
2480
+ );
2481
+ const draw = useCallback3(
2482
+ (e) => {
2483
+ if (!isDrawingRef.current) return;
2484
+ e.preventDefault();
2485
+ const canvas = canvasRef.current;
2486
+ const ctx = canvas?.getContext("2d");
2487
+ if (!ctx) return;
2488
+ const currentPos = getPosition(e);
2489
+ ctx.beginPath();
2490
+ ctx.moveTo(lastPosRef.current.x, lastPosRef.current.y);
2491
+ ctx.lineTo(currentPos.x, currentPos.y);
2492
+ ctx.stroke();
2493
+ lastPosRef.current = currentPos;
2494
+ },
2495
+ [getPosition]
2496
+ );
2497
+ const stopDrawing = useCallback3(() => {
2498
+ isDrawingRef.current = false;
2499
+ }, []);
2500
+ useEffect8(() => {
2501
+ if (!isOpen) return;
2502
+ const canvas = canvasRef.current;
2503
+ if (!canvas) return;
2504
+ const handleMouseDown = (e) => startDrawing(e);
2505
+ const handleMouseMove = (e) => draw(e);
2506
+ const handleMouseUp = () => stopDrawing();
2507
+ const handleMouseLeave = () => stopDrawing();
2508
+ const handleTouchStart = (e) => startDrawing(e);
2509
+ const handleTouchMove = (e) => draw(e);
2510
+ const handleTouchEnd = () => stopDrawing();
2511
+ canvas.addEventListener("mousedown", handleMouseDown);
2512
+ canvas.addEventListener("mousemove", handleMouseMove);
2513
+ canvas.addEventListener("mouseup", handleMouseUp);
2514
+ canvas.addEventListener("mouseleave", handleMouseLeave);
2515
+ canvas.addEventListener("touchstart", handleTouchStart, { passive: false });
2516
+ canvas.addEventListener("touchmove", handleTouchMove, { passive: false });
2517
+ canvas.addEventListener("touchend", handleTouchEnd);
2518
+ return () => {
2519
+ canvas.removeEventListener("mousedown", handleMouseDown);
2520
+ canvas.removeEventListener("mousemove", handleMouseMove);
2521
+ canvas.removeEventListener("mouseup", handleMouseUp);
2522
+ canvas.removeEventListener("mouseleave", handleMouseLeave);
2523
+ canvas.removeEventListener("touchstart", handleTouchStart);
2524
+ canvas.removeEventListener("touchmove", handleTouchMove);
2525
+ canvas.removeEventListener("touchend", handleTouchEnd);
2526
+ };
2527
+ }, [isOpen, startDrawing, draw, stopDrawing]);
2528
+ const handleClear = () => {
2529
+ const canvas = canvasRef.current;
2530
+ const ctx = canvas?.getContext("2d");
2531
+ if (!ctx || !canvas) return;
2532
+ ctx.fillStyle = "white";
2533
+ ctx.fillRect(0, 0, width, height);
2534
+ };
2535
+ const handleDone = () => {
2536
+ const canvas = canvasRef.current;
2537
+ if (!canvas) return;
2538
+ const dataUrl = canvas.toDataURL("image/png");
2539
+ onComplete(dataUrl);
2540
+ };
2541
+ const handleOverlayClick = (e) => {
2542
+ if (e.target === e.currentTarget) {
2543
+ onClose();
2544
+ }
2545
+ };
2546
+ if (!isOpen) return null;
2547
+ return /* @__PURE__ */ React13.createElement("div", { className: "SignaturePad__overlay", onClick: handleOverlayClick }, /* @__PURE__ */ React13.createElement("div", { className: "SignaturePad__modal" }, /* @__PURE__ */ React13.createElement("h3", { className: "SignaturePad__title" }, "Draw your signature"), /* @__PURE__ */ React13.createElement(
2548
+ "canvas",
2549
+ {
2550
+ ref: canvasRef,
2551
+ className: "SignaturePad__canvas",
2552
+ width,
2553
+ height
2554
+ }
2555
+ ), /* @__PURE__ */ React13.createElement("div", { className: "SignaturePad__buttons" }, /* @__PURE__ */ React13.createElement(
2556
+ "button",
2557
+ {
2558
+ type: "button",
2559
+ className: "SignaturePad__button SignaturePad__button--clear",
2560
+ onClick: handleClear
2561
+ },
2562
+ "Clear"
2563
+ ), /* @__PURE__ */ React13.createElement(
2564
+ "button",
2565
+ {
2566
+ type: "button",
2567
+ className: "SignaturePad__button SignaturePad__button--cancel",
2568
+ onClick: onClose
2569
+ },
2570
+ "Cancel"
2571
+ ), /* @__PURE__ */ React13.createElement(
2572
+ "button",
2573
+ {
2574
+ type: "button",
2575
+ className: "SignaturePad__button SignaturePad__button--done",
2576
+ onClick: handleDone
2577
+ },
2578
+ "Done"
2579
+ ))));
2580
+ };
2581
+
2582
+ // src/components/DrawingHighlight.tsx
2583
+ import React14, { useState as useState9, useCallback as useCallback4, useEffect as useEffect9, useRef as useRef12 } from "react";
2584
+ import { Rnd as Rnd4 } from "react-rnd";
2585
+ var DRAWING_COLORS = ["#000000", "#FF0000", "#0000FF", "#00FF00", "#FFFF00"];
2586
+ var STROKE_WIDTHS = [
2587
+ { label: "Thin", value: 1 },
2588
+ { label: "Medium", value: 3 },
2589
+ { label: "Thick", value: 5 }
2590
+ ];
2591
+ var DefaultDragIcon3 = () => /* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("circle", { cx: "8", cy: "6", r: "2" }), /* @__PURE__ */ React14.createElement("circle", { cx: "16", cy: "6", r: "2" }), /* @__PURE__ */ React14.createElement("circle", { cx: "8", cy: "12", r: "2" }), /* @__PURE__ */ React14.createElement("circle", { cx: "16", cy: "12", r: "2" }), /* @__PURE__ */ React14.createElement("circle", { cx: "8", cy: "18", r: "2" }), /* @__PURE__ */ React14.createElement("circle", { cx: "16", cy: "18", r: "2" }));
2592
+ var DefaultDeleteIcon5 = () => /* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
2593
+ var renderStrokesToImage = (strokes, width, height) => {
2594
+ const canvas = document.createElement("canvas");
2595
+ canvas.width = width;
2596
+ canvas.height = height;
2597
+ const ctx = canvas.getContext("2d");
2598
+ if (!ctx) return "";
2599
+ strokes.forEach((stroke) => {
2600
+ if (stroke.points.length < 2) return;
2601
+ ctx.strokeStyle = stroke.color;
2602
+ ctx.lineWidth = stroke.width;
2603
+ ctx.lineCap = "round";
2604
+ ctx.lineJoin = "round";
2605
+ ctx.beginPath();
2606
+ ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
2607
+ stroke.points.slice(1).forEach((point) => {
2608
+ ctx.lineTo(point.x, point.y);
2609
+ });
2610
+ ctx.stroke();
2611
+ });
2612
+ return canvas.toDataURL("image/png");
2613
+ };
2614
+ var DrawingHighlight = ({
2615
+ highlight,
2616
+ onChange,
2617
+ isScrolledTo,
2618
+ bounds,
2619
+ onContextMenu,
2620
+ onEditStart,
2621
+ onEditEnd,
2622
+ style,
2623
+ dragIcon,
2624
+ onStyleChange,
2625
+ onDelete,
2626
+ deleteIcon
2627
+ }) => {
2628
+ const highlightClass = isScrolledTo ? "DrawingHighlight--scrolledTo" : "";
2629
+ const [showStyleControls, setShowStyleControls] = useState9(false);
2630
+ const styleControlsRef = useRef12(null);
2631
+ useEffect9(() => {
2632
+ if (!showStyleControls) return;
2633
+ const handleClickOutside = (e) => {
2634
+ if (styleControlsRef.current && !styleControlsRef.current.contains(e.target)) {
2635
+ setShowStyleControls(false);
2636
+ }
2637
+ };
2638
+ const timeoutId = setTimeout(() => {
2639
+ document.addEventListener("mousedown", handleClickOutside);
2640
+ }, 0);
2641
+ return () => {
2642
+ clearTimeout(timeoutId);
2643
+ document.removeEventListener("mousedown", handleClickOutside);
2644
+ };
2645
+ }, [showStyleControls]);
2646
+ const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
2647
+ const imageUrl = highlight.content?.image;
2648
+ const strokes = highlight.content?.strokes;
2649
+ const handleColorChange = useCallback4((newColor) => {
2650
+ if (!strokes || !onStyleChange) return;
2651
+ console.log("DrawingHighlight: Changing color to", newColor);
2652
+ const newStrokes = strokes.map((stroke) => ({
2653
+ ...stroke,
2654
+ color: newColor
2655
+ }));
2656
+ const newImage = renderStrokesToImage(
2657
+ newStrokes,
2658
+ highlight.position.boundingRect.width,
2659
+ highlight.position.boundingRect.height
2660
+ );
2661
+ onStyleChange(newImage, newStrokes);
2662
+ }, [strokes, onStyleChange, highlight.position.boundingRect.width, highlight.position.boundingRect.height]);
2663
+ const handleWidthChange = useCallback4((newWidth) => {
2664
+ if (!strokes || !onStyleChange) return;
2665
+ console.log("DrawingHighlight: Changing width to", newWidth);
2666
+ const newStrokes = strokes.map((stroke) => ({
2667
+ ...stroke,
2668
+ width: newWidth
2669
+ }));
2670
+ const newImage = renderStrokesToImage(
2671
+ newStrokes,
2672
+ highlight.position.boundingRect.width,
2673
+ highlight.position.boundingRect.height
2674
+ );
2675
+ onStyleChange(newImage, newStrokes);
2676
+ }, [strokes, onStyleChange, highlight.position.boundingRect.width, highlight.position.boundingRect.height]);
2677
+ const currentColor = strokes?.[0]?.color || "#000000";
2678
+ const currentWidth = strokes?.[0]?.width || 3;
2679
+ return /* @__PURE__ */ React14.createElement(
2680
+ "div",
2681
+ {
2682
+ className: `DrawingHighlight ${highlightClass}`,
2683
+ onContextMenu
2684
+ },
2685
+ /* @__PURE__ */ React14.createElement(
2686
+ Rnd4,
2687
+ {
2688
+ className: "DrawingHighlight__rnd",
2689
+ onDragStop: (_, data) => {
2690
+ const boundingRect = {
2691
+ ...highlight.position.boundingRect,
2692
+ top: data.y,
2693
+ left: data.x
2694
+ };
2695
+ onChange?.(boundingRect);
2696
+ onEditEnd?.();
2697
+ },
2698
+ onDragStart: onEditStart,
2699
+ onResizeStop: (_e, _direction, ref, _delta, position) => {
2700
+ const boundingRect = {
2701
+ top: position.y,
2702
+ left: position.x,
2703
+ width: ref.offsetWidth,
2704
+ height: ref.offsetHeight,
2705
+ pageNumber: getPageFromElement(ref)?.number || highlight.position.boundingRect.pageNumber
2706
+ };
2707
+ onChange?.(boundingRect);
2708
+ onEditEnd?.();
2709
+ },
2710
+ onResizeStart: onEditStart,
2711
+ default: {
2712
+ x: highlight.position.boundingRect.left,
2713
+ y: highlight.position.boundingRect.top,
2714
+ width: highlight.position.boundingRect.width || 150,
2715
+ height: highlight.position.boundingRect.height || 100
2716
+ },
2717
+ minWidth: 30,
2718
+ minHeight: 30,
2719
+ key,
2720
+ bounds,
2721
+ lockAspectRatio: false,
2722
+ dragHandleClassName: "DrawingHighlight__drag-handle",
2723
+ onClick: (event) => {
2724
+ event.stopPropagation();
2725
+ event.preventDefault();
2726
+ },
2727
+ style
2728
+ },
2729
+ /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__container" }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__toolbar" }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React14.createElement(DefaultDragIcon3, null)), strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement(
2730
+ "button",
2731
+ {
2732
+ type: "button",
2733
+ className: "DrawingHighlight__style-button",
2734
+ title: "Edit style",
2735
+ onClick: (e) => {
2736
+ e.stopPropagation();
2737
+ setShowStyleControls(!showStyleControls);
2738
+ }
2739
+ },
2740
+ /* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }))
2741
+ ), onDelete && /* @__PURE__ */ React14.createElement(
2742
+ "button",
2743
+ {
2744
+ className: "DrawingHighlight__delete-button",
2745
+ onClick: (e) => {
2746
+ e.stopPropagation();
2747
+ onDelete();
2748
+ },
2749
+ title: "Delete",
2750
+ type: "button"
2751
+ },
2752
+ deleteIcon || /* @__PURE__ */ React14.createElement(DefaultDeleteIcon5, null)
2753
+ )), showStyleControls && strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__style-controls", ref: styleControlsRef }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__color-picker" }, DRAWING_COLORS.map((color) => /* @__PURE__ */ React14.createElement(
2754
+ "button",
2755
+ {
2756
+ key: color,
2757
+ type: "button",
2758
+ className: `DrawingHighlight__color-button ${currentColor === color ? "active" : ""}`,
2759
+ style: { backgroundColor: color },
2760
+ onClick: (e) => {
2761
+ e.stopPropagation();
2762
+ handleColorChange(color);
2763
+ },
2764
+ title: `Color: ${color}`
2765
+ }
2766
+ ))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__width-picker" }, STROKE_WIDTHS.map((w) => /* @__PURE__ */ React14.createElement(
2767
+ "button",
2768
+ {
2769
+ key: w.value,
2770
+ type: "button",
2771
+ className: `DrawingHighlight__width-button ${currentWidth === w.value ? "active" : ""}`,
2772
+ onClick: (e) => {
2773
+ e.stopPropagation();
2774
+ handleWidthChange(w.value);
2775
+ },
2776
+ title: w.label
2777
+ },
2778
+ w.label
2779
+ )))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__content" }, imageUrl ? /* @__PURE__ */ React14.createElement(
2780
+ "img",
2781
+ {
2782
+ src: imageUrl,
2783
+ alt: "Drawing",
2784
+ className: "DrawingHighlight__image",
2785
+ draggable: false
2786
+ }
2787
+ ) : /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__placeholder" }, "No drawing")))
2788
+ )
2789
+ );
2790
+ };
2791
+
2792
+ // src/components/ShapeHighlight.tsx
2793
+ import React15, {
2794
+ useState as useState10,
2795
+ useRef as useRef13,
2796
+ useEffect as useEffect10
2797
+ } from "react";
2798
+ import { Rnd as Rnd5 } from "react-rnd";
2799
+ var DefaultStyleIcon4 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
2800
+ var DefaultDeleteIcon6 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
2801
+ var DEFAULT_COLOR_PRESETS3 = [
2802
+ "#000000",
2803
+ // Black
2804
+ "#FF0000",
2805
+ // Red
2806
+ "#0000FF",
2807
+ // Blue
2808
+ "#00AA00",
2809
+ // Green
2810
+ "#FF6600"
2811
+ // Orange
2812
+ ];
2813
+ var STROKE_WIDTHS2 = [
2814
+ { label: "Thin", value: 1 },
2815
+ { label: "Medium", value: 2 },
2816
+ { label: "Thick", value: 4 }
2817
+ ];
2818
+ var ShapeHighlight = ({
2819
+ highlight,
2820
+ onChange,
2821
+ isScrolledTo,
2822
+ bounds,
2823
+ onContextMenu,
2824
+ onEditStart,
2825
+ onEditEnd,
2826
+ style,
2827
+ shapeType = "rectangle",
2828
+ strokeColor = "#000000",
2829
+ strokeWidth = 2,
2830
+ onStyleChange,
2831
+ onDelete,
2832
+ styleIcon,
2833
+ deleteIcon,
2834
+ colorPresets = DEFAULT_COLOR_PRESETS3,
2835
+ startPoint,
2836
+ endPoint
2837
+ }) => {
2838
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState10(false);
2839
+ const [isHovered, setIsHovered] = useState10(false);
2840
+ const stylePanelRef = useRef13(null);
2841
+ useEffect10(() => {
2842
+ if (!isStylePanelOpen) return;
2843
+ const handleClickOutside = (e) => {
2844
+ if (stylePanelRef.current && !stylePanelRef.current.contains(e.target)) {
2845
+ setIsStylePanelOpen(false);
2846
+ }
2847
+ };
2848
+ const timeoutId = setTimeout(() => {
2849
+ document.addEventListener("mousedown", handleClickOutside);
2850
+ }, 0);
2851
+ return () => {
2852
+ clearTimeout(timeoutId);
2853
+ document.removeEventListener("mousedown", handleClickOutside);
2854
+ };
2855
+ }, [isStylePanelOpen]);
2856
+ const highlightClass = isScrolledTo ? "ShapeHighlight--scrolledTo" : "";
2857
+ const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
2858
+ const markerId = `arrowhead-${highlight.id}`;
2859
+ const renderShape = (width, height) => {
2860
+ switch (shapeType) {
2861
+ case "rectangle":
2862
+ return /* @__PURE__ */ React15.createElement(
2863
+ "svg",
2864
+ {
2865
+ className: "ShapeHighlight__svg",
2866
+ width,
2867
+ height,
2868
+ viewBox: `0 0 ${width} ${height}`
2869
+ },
2870
+ /* @__PURE__ */ React15.createElement(
2871
+ "rect",
2872
+ {
2873
+ x: strokeWidth / 2,
2874
+ y: strokeWidth / 2,
2875
+ width: width - strokeWidth,
2876
+ height: height - strokeWidth,
2877
+ stroke: strokeColor,
2878
+ strokeWidth,
2879
+ fill: "none"
2880
+ }
2881
+ )
2882
+ );
2883
+ case "circle":
2884
+ return /* @__PURE__ */ React15.createElement(
2885
+ "svg",
2886
+ {
2887
+ className: "ShapeHighlight__svg",
2888
+ width,
2889
+ height,
2890
+ viewBox: `0 0 ${width} ${height}`
2891
+ },
2892
+ /* @__PURE__ */ React15.createElement(
2893
+ "ellipse",
2894
+ {
2895
+ cx: width / 2,
2896
+ cy: height / 2,
2897
+ rx: width / 2 - strokeWidth / 2,
2898
+ ry: height / 2 - strokeWidth / 2,
2899
+ stroke: strokeColor,
2900
+ strokeWidth,
2901
+ fill: "none"
2902
+ }
2903
+ )
2904
+ );
2905
+ case "arrow": {
2906
+ const x1 = startPoint ? startPoint.x * width : strokeWidth;
2907
+ const y1 = startPoint ? startPoint.y * height : height / 2;
2908
+ const x2 = endPoint ? endPoint.x * width : width - strokeWidth - 10;
2909
+ const y2 = endPoint ? endPoint.y * height : height / 2;
2910
+ return /* @__PURE__ */ React15.createElement(
2911
+ "svg",
2912
+ {
2913
+ className: "ShapeHighlight__svg",
2914
+ width,
2915
+ height,
2916
+ viewBox: `0 0 ${width} ${height}`
2917
+ },
2918
+ /* @__PURE__ */ React15.createElement("defs", null, /* @__PURE__ */ React15.createElement(
2919
+ "marker",
2920
+ {
2921
+ id: markerId,
2922
+ markerWidth: "10",
2923
+ markerHeight: "7",
2924
+ refX: "9",
2925
+ refY: "3.5",
2926
+ orient: "auto"
2927
+ },
2928
+ /* @__PURE__ */ React15.createElement("polygon", { points: "0 0, 10 3.5, 0 7", fill: strokeColor })
2929
+ )),
2930
+ /* @__PURE__ */ React15.createElement(
2931
+ "line",
2932
+ {
2933
+ x1,
2934
+ y1,
2935
+ x2,
2936
+ y2,
2937
+ stroke: strokeColor,
2938
+ strokeWidth,
2939
+ markerEnd: `url(#${markerId})`
2940
+ }
2941
+ )
2942
+ );
2943
+ }
2944
+ default:
2945
+ return null;
2946
+ }
2947
+ };
2948
+ return /* @__PURE__ */ React15.createElement(
2949
+ "div",
2950
+ {
2951
+ className: `ShapeHighlight ${highlightClass}`,
2952
+ onContextMenu
2953
+ },
2954
+ (onStyleChange || onDelete) && /* @__PURE__ */ React15.createElement(
2955
+ "div",
2956
+ {
2957
+ className: "ShapeHighlight__toolbar-wrapper",
2958
+ style: {
2959
+ position: "absolute",
2960
+ left: highlight.position.boundingRect.left,
2961
+ top: highlight.position.boundingRect.top - 28,
2962
+ paddingBottom: 12
2963
+ },
2964
+ onMouseEnter: () => setIsHovered(true),
2965
+ onMouseLeave: () => setIsHovered(false)
2966
+ },
2967
+ /* @__PURE__ */ React15.createElement(
2968
+ "div",
2969
+ {
2970
+ className: `ShapeHighlight__toolbar ${isHovered || isStylePanelOpen ? "ShapeHighlight__toolbar--visible" : ""}`
2971
+ },
2972
+ onStyleChange && /* @__PURE__ */ React15.createElement(
2973
+ "button",
2974
+ {
2975
+ className: "ShapeHighlight__style-button",
2976
+ onClick: (e) => {
2977
+ e.stopPropagation();
2978
+ setIsStylePanelOpen(!isStylePanelOpen);
2979
+ },
2980
+ title: "Change style",
2981
+ type: "button"
2982
+ },
2983
+ styleIcon || /* @__PURE__ */ React15.createElement(DefaultStyleIcon4, null)
2984
+ ),
2985
+ onDelete && /* @__PURE__ */ React15.createElement(
2986
+ "button",
2987
+ {
2988
+ className: "ShapeHighlight__delete-button",
2989
+ onClick: (e) => {
2990
+ e.stopPropagation();
2991
+ onDelete();
2992
+ },
2993
+ title: "Delete",
2994
+ type: "button"
2995
+ },
2996
+ deleteIcon || /* @__PURE__ */ React15.createElement(DefaultDeleteIcon6, null)
2997
+ )
2998
+ ),
2999
+ isStylePanelOpen && onStyleChange && /* @__PURE__ */ React15.createElement(
3000
+ "div",
3001
+ {
3002
+ className: "ShapeHighlight__style-panel",
3003
+ ref: stylePanelRef,
3004
+ onClick: (e) => e.stopPropagation()
3005
+ },
3006
+ /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Color"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-options" }, /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React15.createElement(
3007
+ "button",
3008
+ {
3009
+ key: c,
3010
+ type: "button",
3011
+ className: `ShapeHighlight__color-preset ${strokeColor === c ? "active" : ""}`,
3012
+ style: { backgroundColor: c },
3013
+ onClick: () => onStyleChange({ strokeColor: c }),
3014
+ title: c
3015
+ }
3016
+ ))), /* @__PURE__ */ React15.createElement(
3017
+ "input",
3018
+ {
3019
+ type: "color",
3020
+ value: strokeColor,
3021
+ onChange: (e) => {
3022
+ onStyleChange({ strokeColor: e.target.value });
3023
+ }
3024
+ }
3025
+ ))),
3026
+ /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Width"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__width-options" }, STROKE_WIDTHS2.map((w) => /* @__PURE__ */ React15.createElement(
3027
+ "button",
3028
+ {
3029
+ key: w.value,
3030
+ type: "button",
3031
+ className: `ShapeHighlight__width-button ${strokeWidth === w.value ? "active" : ""}`,
3032
+ onClick: () => onStyleChange({ strokeWidth: w.value }),
3033
+ title: w.label
3034
+ },
3035
+ w.label
3036
+ ))))
3037
+ )
3038
+ ),
3039
+ /* @__PURE__ */ React15.createElement(
3040
+ Rnd5,
3041
+ {
3042
+ onMouseEnter: () => setIsHovered(true),
3043
+ onMouseLeave: () => setIsHovered(false),
3044
+ className: "ShapeHighlight__rnd",
3045
+ onDragStop: (_, data) => {
3046
+ const boundingRect = {
3047
+ ...highlight.position.boundingRect,
3048
+ top: data.y,
3049
+ left: data.x
3050
+ };
3051
+ onChange?.(boundingRect);
3052
+ onEditEnd?.();
3053
+ },
3054
+ onDragStart: onEditStart,
3055
+ onResizeStop: (_e, _direction, ref, _delta, position) => {
3056
+ const boundingRect = {
3057
+ top: position.y,
3058
+ left: position.x,
3059
+ width: ref.offsetWidth,
3060
+ height: ref.offsetHeight,
3061
+ pageNumber: getPageFromElement(ref)?.number || highlight.position.boundingRect.pageNumber
3062
+ };
3063
+ onChange?.(boundingRect);
3064
+ onEditEnd?.();
3065
+ },
3066
+ onResizeStart: onEditStart,
3067
+ default: {
3068
+ x: highlight.position.boundingRect.left,
3069
+ y: highlight.position.boundingRect.top,
3070
+ width: highlight.position.boundingRect.width || 100,
3071
+ height: highlight.position.boundingRect.height || 100
3072
+ },
3073
+ minWidth: 20,
3074
+ minHeight: 20,
3075
+ key,
3076
+ bounds,
3077
+ lockAspectRatio: shapeType === "circle",
3078
+ onClick: (event) => {
3079
+ event.stopPropagation();
3080
+ event.preventDefault();
3081
+ },
3082
+ style
3083
+ },
3084
+ /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__container" }, renderShape(
3085
+ highlight.position.boundingRect.width || 100,
3086
+ highlight.position.boundingRect.height || 100
3087
+ ))
3088
+ )
3089
+ );
3090
+ };
3091
+
3092
+ // src/components/PdfLoader.tsx
3093
+ import React16, { useEffect as useEffect11, useRef as useRef14, useState as useState11 } from "react";
3094
+ import { GlobalWorkerOptions, getDocument as getDocument2 } from "pdfjs-dist";
3095
+ var DEFAULT_BEFORE_LOAD = (progress) => /* @__PURE__ */ React16.createElement("div", { style: { color: "black" } }, "Loading ", Math.floor(progress.loaded / progress.total * 100), "%");
3096
+ var DEFAULT_ERROR_MESSAGE = (error) => /* @__PURE__ */ React16.createElement("div", { style: { color: "black" } }, error.message);
3097
+ var DEFAULT_ON_ERROR = (error) => {
3098
+ throw new Error(`Error loading PDF document: ${error.message}!`);
3099
+ };
3100
+ var DEFAULT_WORKER_SRC = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
3101
+ var PdfLoader = ({
3102
+ document: document2,
3103
+ beforeLoad = DEFAULT_BEFORE_LOAD,
3104
+ errorMessage = DEFAULT_ERROR_MESSAGE,
3105
+ children,
3106
+ onError = DEFAULT_ON_ERROR,
3107
+ workerSrc = DEFAULT_WORKER_SRC
3108
+ }) => {
3109
+ const pdfLoadingTaskRef = useRef14(null);
3110
+ const pdfDocumentRef = useRef14(null);
3111
+ const [error, setError] = useState11(null);
3112
+ const [loadingProgress, setLoadingProgress] = useState11(null);
3113
+ useEffect11(() => {
3114
+ GlobalWorkerOptions.workerSrc = workerSrc;
3115
+ pdfLoadingTaskRef.current = getDocument2(document2);
3116
+ pdfLoadingTaskRef.current.onProgress = (progress) => {
3117
+ setLoadingProgress(progress.loaded > progress.total ? null : progress);
3118
+ };
3119
+ pdfLoadingTaskRef.current.promise.then((pdfDocument) => {
3120
+ pdfDocumentRef.current = pdfDocument;
3121
+ }).catch((error2) => {
3122
+ if (error2.message != "Worker was destroyed") {
3123
+ setError(error2);
3124
+ onError(error2);
3125
+ }
3126
+ }).finally(() => {
3127
+ setLoadingProgress(null);
3128
+ });
3129
+ return () => {
3130
+ if (pdfLoadingTaskRef.current) {
3131
+ pdfLoadingTaskRef.current.destroy();
3132
+ }
3133
+ if (pdfDocumentRef.current) {
3134
+ pdfDocumentRef.current.destroy();
3135
+ }
3136
+ };
3137
+ }, [document2]);
3138
+ return error ? errorMessage(error) : loadingProgress ? beforeLoad(loadingProgress) : pdfDocumentRef.current && children(pdfDocumentRef.current);
3139
+ };
3140
+
3141
+ // src/lib/export-pdf.ts
3142
+ import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
3143
+ function parseColor(color) {
3144
+ const rgbaMatch = color.match(
3145
+ /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
3146
+ );
3147
+ if (rgbaMatch) {
3148
+ return {
3149
+ r: parseInt(rgbaMatch[1]) / 255,
3150
+ g: parseInt(rgbaMatch[2]) / 255,
3151
+ b: parseInt(rgbaMatch[3]) / 255,
3152
+ a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1
3153
+ };
3154
+ }
3155
+ const hex = color.replace("#", "");
3156
+ if (hex.length === 3) {
3157
+ return {
3158
+ r: parseInt(hex[0] + hex[0], 16) / 255,
3159
+ g: parseInt(hex[1] + hex[1], 16) / 255,
3160
+ b: parseInt(hex[2] + hex[2], 16) / 255,
3161
+ a: 1
3162
+ };
3163
+ }
3164
+ if (hex.length === 6) {
3165
+ return {
3166
+ r: parseInt(hex.slice(0, 2), 16) / 255,
3167
+ g: parseInt(hex.slice(2, 4), 16) / 255,
3168
+ b: parseInt(hex.slice(4, 6), 16) / 255,
3169
+ a: 1
3170
+ };
3171
+ }
3172
+ return { r: 1, g: 0.89, b: 0.56, a: 0.5 };
3173
+ }
3174
+ function scaledToPdfPoints(scaled, page) {
3175
+ const pdfWidth = page.getWidth();
3176
+ const pdfHeight = page.getHeight();
3177
+ const xRatio = pdfWidth / scaled.width;
3178
+ const yRatio = pdfHeight / scaled.height;
3179
+ const x = scaled.x1 * xRatio;
3180
+ const width = (scaled.x2 - scaled.x1) * xRatio;
3181
+ const height = (scaled.y2 - scaled.y1) * yRatio;
3182
+ const y = pdfHeight - scaled.y1 * yRatio - height;
3183
+ return { x, y, width, height };
3184
+ }
3185
+ function dataUrlToBytes(dataUrl) {
3186
+ const base64 = dataUrl.split(",")[1];
3187
+ const byteString = atob(base64);
3188
+ const bytes = new Uint8Array(byteString.length);
3189
+ for (let i = 0; i < byteString.length; i++) {
3190
+ bytes[i] = byteString.charCodeAt(i);
3191
+ }
3192
+ const type = dataUrl.includes("image/png") ? "png" : "jpg";
3193
+ return { bytes, type };
3194
+ }
3195
+ function wrapText(text, font, fontSize, maxWidth) {
3196
+ if (!text || maxWidth <= 0) return [];
3197
+ const lines = [];
3198
+ const paragraphs = text.split(/\n/);
3199
+ for (const paragraph of paragraphs) {
3200
+ if (!paragraph.trim()) {
3201
+ lines.push("");
3202
+ continue;
3203
+ }
3204
+ const words = paragraph.split(/\s+/);
3205
+ let currentLine = "";
3206
+ for (const word of words) {
3207
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
3208
+ const testWidth = font.widthOfTextAtSize(testLine, fontSize);
3209
+ if (testWidth <= maxWidth) {
3210
+ currentLine = testLine;
3211
+ } else {
3212
+ if (currentLine) {
3213
+ lines.push(currentLine);
3214
+ currentLine = "";
3215
+ }
3216
+ if (font.widthOfTextAtSize(word, fontSize) > maxWidth) {
3217
+ let remaining = word;
3218
+ while (remaining.length > 0) {
3219
+ let charCount = 1;
3220
+ while (charCount < remaining.length && font.widthOfTextAtSize(remaining.substring(0, charCount + 1), fontSize) <= maxWidth) {
3221
+ charCount++;
3222
+ }
3223
+ const chunk = remaining.substring(0, charCount);
3224
+ remaining = remaining.substring(charCount);
3225
+ if (remaining.length > 0) {
3226
+ lines.push(chunk);
3227
+ } else {
3228
+ currentLine = chunk;
3229
+ }
3230
+ }
3231
+ } else {
3232
+ currentLine = word;
3233
+ }
3234
+ }
3235
+ }
3236
+ if (currentLine) lines.push(currentLine);
3237
+ }
3238
+ return lines;
3239
+ }
3240
+ function groupByPage(highlights) {
3241
+ const map = /* @__PURE__ */ new Map();
3242
+ for (const h of highlights) {
3243
+ const pageNum = h.position.boundingRect.pageNumber;
3244
+ if (!map.has(pageNum)) map.set(pageNum, []);
3245
+ map.get(pageNum).push(h);
3246
+ }
3247
+ return map;
3248
+ }
3249
+ async function renderTextHighlight(page, highlight, options) {
3250
+ const colorStr = highlight.highlightColor || options.textHighlightColor || "rgba(255, 226, 143, 0.5)";
3251
+ const color = parseColor(colorStr);
3252
+ const highlightStyle = highlight.highlightStyle || "highlight";
3253
+ const rects = highlight.position.rects.length > 0 ? highlight.position.rects : [highlight.position.boundingRect];
3254
+ for (const rect of rects) {
3255
+ const { x, y, width, height } = scaledToPdfPoints(rect, page);
3256
+ if (highlightStyle === "highlight") {
3257
+ page.drawRectangle({
3258
+ x,
3259
+ y,
3260
+ width,
3261
+ height,
3262
+ color: rgb(color.r, color.g, color.b),
3263
+ opacity: color.a
3264
+ });
3265
+ } else if (highlightStyle === "underline") {
3266
+ const lineThickness = Math.max(1, height * 0.1);
3267
+ page.drawRectangle({
3268
+ x,
3269
+ y,
3270
+ width,
3271
+ height: lineThickness,
3272
+ color: rgb(color.r, color.g, color.b),
3273
+ opacity: color.a
3274
+ });
3275
+ } else if (highlightStyle === "strikethrough") {
3276
+ const lineThickness = Math.max(1, height * 0.1);
3277
+ const lineY = y + height / 2 - lineThickness / 2;
3278
+ page.drawRectangle({
3279
+ x,
3280
+ y: lineY,
3281
+ width,
3282
+ height: lineThickness,
3283
+ color: rgb(color.r, color.g, color.b),
3284
+ opacity: color.a
3285
+ });
3286
+ }
3287
+ }
3288
+ }
3289
+ async function renderAreaHighlight(page, highlight, options) {
3290
+ const colorStr = highlight.highlightColor || options.areaHighlightColor || "rgba(255, 226, 143, 0.5)";
3291
+ const color = parseColor(colorStr);
3292
+ const { x, y, width, height } = scaledToPdfPoints(
3293
+ highlight.position.boundingRect,
3294
+ page
3295
+ );
3296
+ page.drawRectangle({
3297
+ x,
3298
+ y,
3299
+ width,
3300
+ height,
3301
+ color: rgb(color.r, color.g, color.b),
3302
+ opacity: color.a
3303
+ });
3304
+ }
3305
+ async function renderFreetextHighlight(page, highlight, options, font) {
3306
+ const text = highlight.content?.text || "";
3307
+ const textColor = parseColor(
3308
+ highlight.color || options.defaultFreetextColor || "#333333"
3309
+ );
3310
+ const { x, y, width, height } = scaledToPdfPoints(
3311
+ highlight.position.boundingRect,
3312
+ page
3313
+ );
3314
+ const pdfHeight = page.getHeight();
3315
+ const yRatio = pdfHeight / highlight.position.boundingRect.height;
3316
+ const storedFontSize = parseInt(highlight.fontSize || "") || options.defaultFreetextFontSize || 14;
3317
+ const fontSize = storedFontSize * yRatio;
3318
+ console.log("Freetext export:", {
3319
+ storedFontSize,
3320
+ yRatio,
3321
+ fontSize,
3322
+ boxDimensions: { x, y, width, height },
3323
+ text: text.substring(0, 50)
3324
+ });
3325
+ const bgColorValue = highlight.backgroundColor || options.defaultFreetextBgColor || "#ffffc8";
3326
+ if (bgColorValue !== "transparent") {
3327
+ const bgColor = parseColor(bgColorValue);
3328
+ page.drawRectangle({
3329
+ x,
3330
+ y,
3331
+ width,
3332
+ height,
3333
+ color: rgb(bgColor.r, bgColor.g, bgColor.b),
3334
+ opacity: bgColor.a
3335
+ });
3336
+ }
3337
+ const padding = 4 * yRatio;
3338
+ const maxWidth = width - padding * 2;
3339
+ const lineHeight = fontSize * 1.3;
3340
+ if (maxWidth > 0 && text) {
3341
+ const lines = wrapText(text, font, fontSize, maxWidth);
3342
+ let currentY = y + height - fontSize - padding;
3343
+ for (const line of lines) {
3344
+ if (currentY < y + padding) break;
3345
+ if (line.trim()) {
3346
+ page.drawText(line, {
3347
+ x: x + padding,
3348
+ y: currentY,
3349
+ size: fontSize,
3350
+ font,
3351
+ color: rgb(textColor.r, textColor.g, textColor.b)
3352
+ });
3353
+ }
3354
+ currentY -= lineHeight;
3355
+ }
3356
+ }
3357
+ }
3358
+ function transformToRawCoordinates(page, x, y, width, height) {
3359
+ const rotation = page.getRotation().angle;
3360
+ const pageWidth = page.getWidth();
3361
+ const pageHeight = page.getHeight();
3362
+ if (rotation === 90) {
3363
+ return {
3364
+ x: y,
3365
+ y: pageWidth - x - width,
3366
+ width: height,
3367
+ height: width
3368
+ };
3369
+ } else if (rotation === 180) {
3370
+ return {
3371
+ x: pageWidth - x - width,
3372
+ y: pageHeight - y - height,
3373
+ width,
3374
+ height
3375
+ };
3376
+ } else if (rotation === 270) {
3377
+ return {
3378
+ x: pageHeight - y - height,
3379
+ y: x,
3380
+ width: height,
3381
+ height: width
3382
+ };
3383
+ }
3384
+ return { x, y, width, height };
3385
+ }
3386
+ async function renderImageHighlight(pdfDoc, page, highlight) {
3387
+ const imageDataUrl = highlight.content?.image;
3388
+ if (!imageDataUrl) return;
3389
+ try {
3390
+ const { bytes, type } = dataUrlToBytes(imageDataUrl);
3391
+ const image = type === "png" ? await pdfDoc.embedPng(bytes) : await pdfDoc.embedJpg(bytes);
3392
+ const visualCoords = scaledToPdfPoints(
3393
+ highlight.position.boundingRect,
3394
+ page
3395
+ );
3396
+ const rawCoords = transformToRawCoordinates(
3397
+ page,
3398
+ visualCoords.x,
3399
+ visualCoords.y,
3400
+ visualCoords.width,
3401
+ visualCoords.height
3402
+ );
3403
+ console.log("Image export:", {
3404
+ rotation: page.getRotation().angle,
3405
+ visualCoords,
3406
+ rawCoords
3407
+ });
3408
+ page.drawImage(image, {
3409
+ x: rawCoords.x,
3410
+ y: rawCoords.y,
3411
+ width: rawCoords.width,
3412
+ height: rawCoords.height
3413
+ });
3414
+ } catch (error) {
3415
+ console.error("Failed to embed image:", error);
3416
+ }
3417
+ }
3418
+ async function renderShapeHighlight(page, highlight) {
3419
+ const shapeType = highlight.content?.shape?.shapeType || highlight.shapeType || "rectangle";
3420
+ const strokeColorStr = highlight.content?.shape?.strokeColor || highlight.strokeColor || "#000000";
3421
+ const strokeWidth = highlight.content?.shape?.strokeWidth || highlight.strokeWidth || 2;
3422
+ const color = parseColor(strokeColorStr);
3423
+ const { x, y, width, height } = scaledToPdfPoints(
3424
+ highlight.position.boundingRect,
3425
+ page
3426
+ );
3427
+ switch (shapeType) {
3428
+ case "rectangle":
3429
+ page.drawRectangle({
3430
+ x,
3431
+ y,
3432
+ width,
3433
+ height,
3434
+ borderColor: rgb(color.r, color.g, color.b),
3435
+ borderWidth: strokeWidth,
3436
+ opacity: color.a
3437
+ });
3438
+ break;
3439
+ case "circle":
3440
+ page.drawEllipse({
3441
+ x: x + width / 2,
3442
+ y: y + height / 2,
3443
+ xScale: width / 2,
3444
+ yScale: height / 2,
3445
+ borderColor: rgb(color.r, color.g, color.b),
3446
+ borderWidth: strokeWidth,
3447
+ opacity: color.a
3448
+ });
3449
+ break;
3450
+ case "arrow": {
3451
+ const startPt = highlight.content?.shape?.startPoint;
3452
+ const endPt = highlight.content?.shape?.endPoint;
3453
+ const startX = startPt ? x + startPt.x * width : x;
3454
+ const startY = startPt ? y + (1 - startPt.y) * height : y + height / 2;
3455
+ const endX = endPt ? x + endPt.x * width : x + width;
3456
+ const endY = endPt ? y + (1 - endPt.y) * height : y + height / 2;
3457
+ page.drawLine({
3458
+ start: { x: startX, y: startY },
3459
+ end: { x: endX, y: endY },
3460
+ color: rgb(color.r, color.g, color.b),
3461
+ thickness: strokeWidth,
3462
+ opacity: color.a
3463
+ });
3464
+ const angle = Math.atan2(endY - startY, endX - startX);
3465
+ const arrowSize = Math.min(15, width * 0.2, height * 0.4);
3466
+ const arrowAngle = Math.PI / 6;
3467
+ page.drawLine({
3468
+ start: {
3469
+ x: endX - arrowSize * Math.cos(angle - arrowAngle),
3470
+ y: endY - arrowSize * Math.sin(angle - arrowAngle)
3471
+ },
3472
+ end: { x: endX, y: endY },
3473
+ color: rgb(color.r, color.g, color.b),
3474
+ thickness: strokeWidth,
3475
+ opacity: color.a
3476
+ });
3477
+ page.drawLine({
3478
+ start: {
3479
+ x: endX - arrowSize * Math.cos(angle + arrowAngle),
3480
+ y: endY - arrowSize * Math.sin(angle + arrowAngle)
3481
+ },
3482
+ end: { x: endX, y: endY },
3483
+ color: rgb(color.r, color.g, color.b),
3484
+ thickness: strokeWidth,
3485
+ opacity: color.a
3486
+ });
3487
+ break;
3488
+ }
3489
+ }
3490
+ }
3491
+ async function exportPdf(pdfSource, highlights, options = {}) {
3492
+ let pdfBytes;
3493
+ if (typeof pdfSource === "string") {
3494
+ const response = await fetch(pdfSource);
3495
+ pdfBytes = await response.arrayBuffer();
3496
+ } else {
3497
+ pdfBytes = pdfSource instanceof Uint8Array ? pdfSource.buffer.slice(
3498
+ pdfSource.byteOffset,
3499
+ pdfSource.byteOffset + pdfSource.byteLength
3500
+ ) : pdfSource;
3501
+ }
3502
+ const pdfDoc = await PDFDocument.load(pdfBytes);
3503
+ const pages = pdfDoc.getPages();
3504
+ const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
3505
+ const byPage = groupByPage(highlights);
3506
+ const totalPages = byPage.size;
3507
+ let currentPage = 0;
3508
+ for (const [pageNum, pageHighlights] of byPage) {
3509
+ const page = pages[pageNum - 1];
3510
+ if (!page) continue;
3511
+ for (const highlight of pageHighlights) {
3512
+ switch (highlight.type) {
3513
+ case "text":
3514
+ await renderTextHighlight(page, highlight, options);
3515
+ break;
3516
+ case "area":
3517
+ await renderAreaHighlight(page, highlight, options);
3518
+ break;
3519
+ case "freetext":
3520
+ await renderFreetextHighlight(page, highlight, options, font);
3521
+ break;
3522
+ case "image":
3523
+ await renderImageHighlight(pdfDoc, page, highlight);
3524
+ break;
3525
+ case "drawing":
3526
+ await renderImageHighlight(pdfDoc, page, highlight);
3527
+ break;
3528
+ case "shape":
3529
+ await renderShapeHighlight(page, highlight);
3530
+ break;
3531
+ default:
3532
+ await renderAreaHighlight(page, highlight, options);
3533
+ }
3534
+ }
3535
+ currentPage++;
3536
+ options.onProgress?.(currentPage, totalPages);
3537
+ }
3538
+ return pdfDoc.save();
3539
+ }
3540
+ export {
3541
+ AreaHighlight,
3542
+ DrawingCanvas,
3543
+ DrawingHighlight,
3544
+ FreetextHighlight,
3545
+ ImageHighlight,
3546
+ MonitoredHighlightContainer,
3547
+ PdfHighlighter,
3548
+ PdfLoader,
3549
+ ShapeCanvas,
3550
+ ShapeHighlight,
3551
+ SignaturePad,
3552
+ TextHighlight,
3553
+ exportPdf,
3554
+ scaledPositionToViewport,
3555
+ useHighlightContainerContext,
3556
+ usePdfHighlighterContext,
3557
+ viewportPositionToScaled
3558
+ };
19
3559
  //# sourceMappingURL=index.js.map