@drvillo/react-browser-e-signing 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # React Browser E-Signing
2
+
3
+ `@drvillo/react-browser-e-signing` — browser-only PDF e-signing for React: field placement, typed or drawn signatures, `pdf-lib` embedding.
4
+
5
+ ## Features
6
+
7
+ - Browser-only PDF signing flow with no server upload
8
+ - Drag + resize field placement for signature, full name, title, and date
9
+ - Typed signature rendering with handwriting fonts or freehand drawing
10
+ - PDF modification with `pdf-lib`
11
+ - SHA-256 hash generation for integrity checks
12
+ - React components, hooks, and pure utility functions
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @drvillo/react-browser-e-signing
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```tsx
23
+ import { defaults, modifyPdf, sha256, useFieldPlacement } from '@drvillo/react-browser-e-signing'
24
+ ```
25
+
26
+ Use the demo app for a complete end-to-end integration:
27
+
28
+ ```bash
29
+ pnpm install
30
+ pnpm demo
31
+ ```
32
+
33
+ ## Public API
34
+
35
+ ### Components
36
+
37
+ - `PdfViewer`
38
+ - `FieldOverlay`
39
+ - `SignatureField`
40
+ - `FieldPalette`
41
+ - `SignerDetailsPanel`
42
+ - `SignaturePreview`
43
+ - `SignaturePad`
44
+ - `SigningComplete`
45
+
46
+ ### Hooks
47
+
48
+ - `usePdfDocument`
49
+ - `useFieldPlacement`
50
+ - `useSignatureRenderer`
51
+
52
+ ### Pure Utilities
53
+
54
+ - `modifyPdf`
55
+ - `mapToPoints`
56
+ - `mapFromPoints`
57
+ - `loadSignatureFont`
58
+ - `SIGNATURE_FONTS`
59
+ - `sha256`
60
+
61
+ ### Types
62
+
63
+ - `FieldPlacement`
64
+ - `FieldType`
65
+ - `SignerInfo`
66
+ - `SignatureStyle`
67
+ - `SigningResult`
68
+ - `PdfPageDimensions`
69
+
70
+ ## Tailwind Consumer Setup
71
+
72
+ This package ships Tailwind classes in components. Add the package path to your Tailwind content scan:
73
+
74
+ ```js
75
+ // tailwind.config.js
76
+ export default {
77
+ content: [
78
+ './src/**/*.{ts,tsx}',
79
+ './node_modules/@drvillo/react-browser-e-signing/src/**/*.{ts,tsx}',
80
+ ],
81
+ }
82
+ ```
83
+
84
+ ## Development Scripts
85
+
86
+ ```bash
87
+ pnpm build
88
+ pnpm dev
89
+ pnpm demo
90
+ pnpm demo:build
91
+ pnpm test
92
+ pnpm typecheck
93
+ ```
94
+
95
+ ## Notes
96
+
97
+ - `PdfViewer` sets `react-pdf` worker from CDN by default using the runtime PDF.js version.
98
+ - Browser test config skips execution when Playwright Chromium is not available in the environment.
99
+
100
+ ## Limitations
101
+
102
+ - Single signer workflow in v0.1
103
+ - Visual e-signature annotation + hash integrity, not cryptographic PAdES signatures
104
+ - Desktop-first drag/resize experience
@@ -0,0 +1,192 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import { ReactNode } from 'react';
4
+
5
+ interface PdfViewerProps {
6
+ pdfData: ArrayBuffer | null;
7
+ numPages: number;
8
+ scale: number;
9
+ onScaleChange: (nextScale: number) => void;
10
+ onDocumentLoadSuccess: (numPages: number) => void;
11
+ onPageDimensions: (input: {
12
+ pageIndex: number;
13
+ widthPt: number;
14
+ heightPt: number;
15
+ }) => void;
16
+ renderOverlay?: (pageIndex: number) => ReactNode;
17
+ }
18
+ declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
19
+
20
+ type FieldType = 'signature' | 'fullName' | 'title' | 'date';
21
+ interface FieldPlacement {
22
+ id: string;
23
+ type: FieldType;
24
+ pageIndex: number;
25
+ xPercent: number;
26
+ yPercent: number;
27
+ widthPercent: number;
28
+ heightPercent: number;
29
+ }
30
+ interface SignerInfo {
31
+ firstName: string;
32
+ lastName: string;
33
+ title: string;
34
+ }
35
+ interface SignatureStyleTyped {
36
+ mode: 'typed';
37
+ fontFamily: string;
38
+ }
39
+ interface SignatureStyleDrawn {
40
+ mode: 'drawn';
41
+ dataUrl: string;
42
+ }
43
+ type SignatureStyle = SignatureStyleTyped | SignatureStyleDrawn;
44
+ interface SigningResult {
45
+ pdfBytes: Uint8Array;
46
+ sha256: string;
47
+ }
48
+ interface PdfPageDimensions {
49
+ pageIndex: number;
50
+ widthPt: number;
51
+ heightPt: number;
52
+ }
53
+ interface PointRect {
54
+ x: number;
55
+ y: number;
56
+ width: number;
57
+ height: number;
58
+ }
59
+ interface SignatureFieldPreview {
60
+ signatureDataUrl: string | null;
61
+ fullName: string;
62
+ title: string;
63
+ dateText: string;
64
+ }
65
+
66
+ interface FieldOverlayProps {
67
+ pageIndex: number;
68
+ fields: FieldPlacement[];
69
+ selectedFieldType: FieldType | null;
70
+ onAddField: (input: {
71
+ pageIndex: number;
72
+ type: FieldType;
73
+ xPercent: number;
74
+ yPercent: number;
75
+ }) => void;
76
+ onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void;
77
+ onRemoveField: (fieldId: string) => void;
78
+ preview: SignatureFieldPreview;
79
+ }
80
+ declare function FieldOverlay({ pageIndex, fields, selectedFieldType, onAddField, onUpdateField, onRemoveField, preview, }: FieldOverlayProps): react_jsx_runtime.JSX.Element;
81
+
82
+ interface SignatureFieldProps {
83
+ field: FieldPlacement;
84
+ onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void;
85
+ onRemoveField: (fieldId: string) => void;
86
+ preview: SignatureFieldPreview;
87
+ }
88
+ declare function SignatureField({ field, onUpdateField, onRemoveField, preview }: SignatureFieldProps): react_jsx_runtime.JSX.Element;
89
+
90
+ interface FieldPaletteProps {
91
+ selectedFieldType: FieldType | null;
92
+ onSelectFieldType: (fieldType: FieldType | null) => void;
93
+ }
94
+ declare function FieldPalette({ selectedFieldType, onSelectFieldType }: FieldPaletteProps): react_jsx_runtime.JSX.Element;
95
+
96
+ interface SignerDetailsPanelProps {
97
+ signerInfo: SignerInfo;
98
+ onSignerInfoChange: (nextSignerInfo: SignerInfo) => void;
99
+ }
100
+ declare function SignerDetailsPanel({ signerInfo, onSignerInfoChange }: SignerDetailsPanelProps): react_jsx_runtime.JSX.Element;
101
+
102
+ interface SignaturePreviewProps {
103
+ signerName: string;
104
+ style: SignatureStyle;
105
+ signatureDataUrl: string | null;
106
+ isRendering: boolean;
107
+ onStyleChange: (nextStyle: SignatureStyle) => void;
108
+ }
109
+ declare function SignaturePreview({ signerName, style, signatureDataUrl, isRendering, onStyleChange, }: SignaturePreviewProps): react_jsx_runtime.JSX.Element;
110
+
111
+ interface SignaturePadProps {
112
+ onDrawn: (signatureDataUrl: string) => void;
113
+ }
114
+ declare function SignaturePad({ onDrawn }: SignaturePadProps): react_jsx_runtime.JSX.Element;
115
+
116
+ interface SigningCompleteProps {
117
+ signerName: string;
118
+ fieldCount: number;
119
+ signedAt: string;
120
+ documentHash: string;
121
+ downloadUrl: string;
122
+ fileName?: string;
123
+ onReset: () => void;
124
+ }
125
+ declare function SigningComplete({ signerName, fieldCount, signedAt, documentHash, downloadUrl, fileName, onReset, }: SigningCompleteProps): react_jsx_runtime.JSX.Element;
126
+
127
+ type PdfInput = File | Blob | ArrayBuffer | Uint8Array | null;
128
+ declare function usePdfDocument(pdfInput: PdfInput): {
129
+ pdfData: ArrayBuffer | null;
130
+ numPages: number;
131
+ scale: number;
132
+ setScale: react.Dispatch<react.SetStateAction<number>>;
133
+ pageDimensions: PdfPageDimensions[];
134
+ setPageDimension: (pageIndex: number, widthPt: number, heightPt: number) => void;
135
+ handleDocumentLoadSuccess: (loadedPages: number) => void;
136
+ hasPdf: boolean;
137
+ isLoading: boolean;
138
+ errorMessage: string | null;
139
+ };
140
+
141
+ interface UseFieldPlacementOptions {
142
+ defaultWidthPercent?: number;
143
+ defaultHeightPercent?: number;
144
+ }
145
+ interface AddFieldInput {
146
+ pageIndex: number;
147
+ type: FieldType;
148
+ xPercent: number;
149
+ yPercent: number;
150
+ }
151
+ declare function useFieldPlacement(options?: UseFieldPlacementOptions): {
152
+ addField: ({ pageIndex, type, xPercent, yPercent }: AddFieldInput) => FieldPlacement;
153
+ updateField: (id: string, partial: Partial<FieldPlacement>) => void;
154
+ removeField: (id: string) => void;
155
+ clearFields: () => void;
156
+ fields: FieldPlacement[];
157
+ };
158
+
159
+ interface UseSignatureRendererInput {
160
+ signerName: string;
161
+ style: SignatureStyle;
162
+ }
163
+ declare function useSignatureRenderer({ signerName, style }: UseSignatureRendererInput): {
164
+ signatureDataUrl: string | null;
165
+ isRendering: boolean;
166
+ };
167
+
168
+ interface ModifyPdfInput {
169
+ pdfBytes: Uint8Array;
170
+ fields: FieldPlacement[];
171
+ signer: SignerInfo;
172
+ signatureDataUrl: string;
173
+ pageDimensions: PdfPageDimensions[];
174
+ dateText?: string;
175
+ }
176
+ declare function modifyPdf({ pdfBytes, fields, signer, signatureDataUrl, pageDimensions, dateText, }: ModifyPdfInput): Promise<Uint8Array>;
177
+
178
+ declare function mapToPoints(field: FieldPlacement, page: PdfPageDimensions): PointRect;
179
+ declare function mapFromPoints(rect: PointRect, page: PdfPageDimensions): Pick<FieldPlacement, 'xPercent' | 'yPercent' | 'widthPercent' | 'heightPercent'>;
180
+
181
+ declare const SIGNATURE_FONTS: readonly ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
182
+ declare function loadSignatureFont(fontFamily: string): Promise<void>;
183
+
184
+ declare function sha256(data: Uint8Array): Promise<string>;
185
+
186
+ declare const defaults: {
187
+ readonly SIGNATURE_FONTS: readonly ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
188
+ readonly DEFAULT_FIELD_WIDTH_PERCENT: 25;
189
+ readonly DEFAULT_FIELD_HEIGHT_PERCENT: 5;
190
+ };
191
+
192
+ export { FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfViewer, SIGNATURE_FONTS, SignatureField, type SignatureFieldPreview, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
package/dist/index.js ADDED
@@ -0,0 +1,808 @@
1
+ import { pdfjs, Document, Page } from 'react-pdf';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
4
+ import SignaturePadLibrary from 'signature_pad';
5
+ import fontkit from '@pdf-lib/fontkit';
6
+ import { PDFDocument, StandardFonts } from 'pdf-lib';
7
+
8
+ // src/components/pdf-viewer.tsx
9
+ if (typeof window !== "undefined") {
10
+ pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
11
+ }
12
+ var MIN_SCALE = 0.5;
13
+ var MAX_SCALE = 2;
14
+ var SCALE_STEP = 0.1;
15
+ function PdfViewer({
16
+ pdfData,
17
+ numPages,
18
+ scale,
19
+ onScaleChange,
20
+ onDocumentLoadSuccess,
21
+ onPageDimensions,
22
+ renderOverlay
23
+ }) {
24
+ if (!pdfData)
25
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-slate-300 bg-slate-50 p-8 text-center text-sm text-slate-500", children: "Upload a PDF to begin" });
26
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
27
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border border-slate-300 bg-white p-3", children: [
28
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-slate-700", children: [
29
+ "Pages: ",
30
+ numPages || "\u2014"
31
+ ] }),
32
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
33
+ /* @__PURE__ */ jsx(
34
+ "button",
35
+ {
36
+ type: "button",
37
+ className: "rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50",
38
+ onClick: () => onScaleChange(Math.max(MIN_SCALE, Number((scale - SCALE_STEP).toFixed(2)))),
39
+ children: "-"
40
+ }
41
+ ),
42
+ /* @__PURE__ */ jsxs("span", { className: "w-16 text-center text-sm text-slate-700", children: [
43
+ Math.round(scale * 100),
44
+ "%"
45
+ ] }),
46
+ /* @__PURE__ */ jsx(
47
+ "button",
48
+ {
49
+ type: "button",
50
+ className: "rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50",
51
+ onClick: () => onScaleChange(Math.min(MAX_SCALE, Number((scale + SCALE_STEP).toFixed(2)))),
52
+ children: "+"
53
+ }
54
+ )
55
+ ] })
56
+ ] }),
57
+ /* @__PURE__ */ jsx(
58
+ Document,
59
+ {
60
+ file: pdfData,
61
+ onLoadSuccess: (loadedPdf) => onDocumentLoadSuccess(loadedPdf.numPages),
62
+ loading: /* @__PURE__ */ jsx("div", { className: "text-sm text-slate-500", children: "Loading PDF..." }),
63
+ error: /* @__PURE__ */ jsx("div", { className: "text-sm text-red-600", children: "Unable to render this PDF." }),
64
+ children: /* @__PURE__ */ jsx("div", { className: "space-y-6", children: Array.from({ length: numPages }, (_, pageIndex) => /* @__PURE__ */ jsxs("div", { className: "relative mx-auto w-fit rounded bg-white p-2 shadow", children: [
65
+ /* @__PURE__ */ jsx(
66
+ Page,
67
+ {
68
+ pageNumber: pageIndex + 1,
69
+ scale,
70
+ renderTextLayer: false,
71
+ renderAnnotationLayer: false,
72
+ onLoadSuccess: (page) => onPageDimensions({
73
+ pageIndex,
74
+ widthPt: page.view[2],
75
+ heightPt: page.view[3]
76
+ })
77
+ }
78
+ ),
79
+ renderOverlay?.(pageIndex)
80
+ ] }, `pdf-page-${pageIndex}`)) })
81
+ }
82
+ )
83
+ ] });
84
+ }
85
+ var MIN_WIDTH_PERCENT = 8;
86
+ var MIN_HEIGHT_PERCENT = 3;
87
+ function clampPercent(value) {
88
+ if (value < 0) return 0;
89
+ if (value > 100) return 100;
90
+ return value;
91
+ }
92
+ function getFieldPreviewText(field, preview) {
93
+ if (field.type === "fullName") return preview.fullName;
94
+ if (field.type === "title") return preview.title;
95
+ if (field.type === "date") return preview.dateText;
96
+ return "";
97
+ }
98
+ function SignatureField({ field, onUpdateField, onRemoveField, preview }) {
99
+ const rootRef = useRef(null);
100
+ const dragStateRef = useRef(null);
101
+ const resizeStateRef = useRef(null);
102
+ function handleDragPointerDown(event) {
103
+ event.stopPropagation();
104
+ if (event.button !== 0) return;
105
+ if (!rootRef.current?.parentElement) return;
106
+ dragStateRef.current = {
107
+ startClientX: event.clientX,
108
+ startClientY: event.clientY,
109
+ startXPercent: field.xPercent,
110
+ startYPercent: field.yPercent
111
+ };
112
+ event.currentTarget.setPointerCapture(event.pointerId);
113
+ }
114
+ function handleDragPointerMove(event) {
115
+ if (!dragStateRef.current) return;
116
+ const parentElement = rootRef.current?.parentElement;
117
+ if (!parentElement) return;
118
+ const deltaX = event.clientX - dragStateRef.current.startClientX;
119
+ const deltaY = event.clientY - dragStateRef.current.startClientY;
120
+ const deltaXPercent = deltaX / parentElement.clientWidth * 100;
121
+ const deltaYPercent = deltaY / parentElement.clientHeight * 100;
122
+ const maxX = 100 - field.widthPercent;
123
+ const maxY = 100 - field.heightPercent;
124
+ onUpdateField(field.id, {
125
+ xPercent: clampPercent(Math.min(maxX, dragStateRef.current.startXPercent + deltaXPercent)),
126
+ yPercent: clampPercent(Math.min(maxY, dragStateRef.current.startYPercent + deltaYPercent))
127
+ });
128
+ }
129
+ function handleDragPointerUp(event) {
130
+ dragStateRef.current = null;
131
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
132
+ event.currentTarget.releasePointerCapture(event.pointerId);
133
+ }
134
+ function handleResizePointerDown(event) {
135
+ event.stopPropagation();
136
+ if (event.button !== 0) return;
137
+ resizeStateRef.current = {
138
+ startClientX: event.clientX,
139
+ startClientY: event.clientY,
140
+ startWidthPercent: field.widthPercent,
141
+ startHeightPercent: field.heightPercent
142
+ };
143
+ event.currentTarget.setPointerCapture(event.pointerId);
144
+ }
145
+ function handleResizePointerMove(event) {
146
+ if (!resizeStateRef.current) return;
147
+ const parentElement = rootRef.current?.parentElement;
148
+ if (!parentElement) return;
149
+ const deltaX = event.clientX - resizeStateRef.current.startClientX;
150
+ const deltaY = event.clientY - resizeStateRef.current.startClientY;
151
+ const deltaWidthPercent = deltaX / parentElement.clientWidth * 100;
152
+ const deltaHeightPercent = deltaY / parentElement.clientHeight * 100;
153
+ const maxWidth = 100 - field.xPercent;
154
+ const maxHeight = 100 - field.yPercent;
155
+ const widthPercent = clampPercent(
156
+ Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))
157
+ );
158
+ const heightPercent = clampPercent(
159
+ Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent))
160
+ );
161
+ onUpdateField(field.id, { widthPercent, heightPercent });
162
+ }
163
+ function handleResizePointerUp(event) {
164
+ resizeStateRef.current = null;
165
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
166
+ event.currentTarget.releasePointerCapture(event.pointerId);
167
+ }
168
+ const previewText = getFieldPreviewText(field, preview);
169
+ return /* @__PURE__ */ jsxs(
170
+ "div",
171
+ {
172
+ ref: rootRef,
173
+ className: "absolute rounded border-2 border-blue-500 bg-blue-50/80 shadow-sm select-none",
174
+ style: {
175
+ left: `${field.xPercent}%`,
176
+ top: `${field.yPercent}%`,
177
+ width: `${field.widthPercent}%`,
178
+ height: `${field.heightPercent}%`
179
+ },
180
+ onPointerDown: handleDragPointerDown,
181
+ onPointerMove: handleDragPointerMove,
182
+ onPointerUp: handleDragPointerUp,
183
+ children: [
184
+ /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full items-start justify-between gap-2 p-1.5 text-[11px] text-slate-800", children: [
185
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 overflow-hidden", children: [
186
+ /* @__PURE__ */ jsx("div", { className: "truncate font-semibold capitalize", children: field.type }),
187
+ field.type === "signature" && preview.signatureDataUrl ? /* @__PURE__ */ jsx(
188
+ "img",
189
+ {
190
+ src: preview.signatureDataUrl,
191
+ alt: "signature preview",
192
+ className: "mt-1 h-[calc(100%-18px)] max-h-full w-full object-contain",
193
+ draggable: false
194
+ }
195
+ ) : /* @__PURE__ */ jsx("div", { className: "truncate text-slate-600", children: previewText || "\u2014" })
196
+ ] }),
197
+ /* @__PURE__ */ jsx(
198
+ "button",
199
+ {
200
+ type: "button",
201
+ className: "rounded bg-white/80 px-1 text-xs text-red-600 hover:bg-white",
202
+ onPointerDown: (event) => event.stopPropagation(),
203
+ onClick: (event) => {
204
+ event.stopPropagation();
205
+ onRemoveField(field.id);
206
+ },
207
+ "aria-label": "Remove field",
208
+ children: "\xD7"
209
+ }
210
+ )
211
+ ] }),
212
+ /* @__PURE__ */ jsx(
213
+ "div",
214
+ {
215
+ className: "absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-nwse-resize rounded-full bg-blue-600",
216
+ onPointerDown: handleResizePointerDown,
217
+ onPointerMove: handleResizePointerMove,
218
+ onPointerUp: handleResizePointerUp
219
+ }
220
+ )
221
+ ]
222
+ }
223
+ );
224
+ }
225
+ function FieldOverlay({
226
+ pageIndex,
227
+ fields,
228
+ selectedFieldType,
229
+ onAddField,
230
+ onUpdateField,
231
+ onRemoveField,
232
+ preview
233
+ }) {
234
+ function handleOverlayPointerDown(event) {
235
+ if (!selectedFieldType) return;
236
+ if (event.button !== 0) return;
237
+ if (event.target !== event.currentTarget) return;
238
+ const rect = event.currentTarget.getBoundingClientRect();
239
+ const xPercent = (event.clientX - rect.left) / rect.width * 100;
240
+ const yPercent = (event.clientY - rect.top) / rect.height * 100;
241
+ onAddField({
242
+ pageIndex,
243
+ type: selectedFieldType,
244
+ xPercent,
245
+ yPercent
246
+ });
247
+ }
248
+ const pageFields = fields.filter((field) => field.pageIndex === pageIndex);
249
+ return /* @__PURE__ */ jsx(
250
+ "div",
251
+ {
252
+ className: `absolute inset-0 z-20 rounded ${selectedFieldType ? "cursor-crosshair" : "cursor-default"}`,
253
+ onPointerDown: handleOverlayPointerDown,
254
+ "aria-label": `Field overlay page ${pageIndex + 1}`,
255
+ children: pageFields.map((field) => /* @__PURE__ */ jsx(
256
+ SignatureField,
257
+ {
258
+ field,
259
+ onUpdateField,
260
+ onRemoveField,
261
+ preview
262
+ },
263
+ field.id
264
+ ))
265
+ }
266
+ );
267
+ }
268
+ var FIELD_LABELS = {
269
+ signature: "Signature",
270
+ fullName: "Full Name",
271
+ title: "Title",
272
+ date: "Date"
273
+ };
274
+ var FIELD_TYPES = ["signature", "fullName", "title", "date"];
275
+ function FieldPalette({ selectedFieldType, onSelectFieldType }) {
276
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-slate-300 bg-white p-2", children: FIELD_TYPES.map((fieldType) => {
277
+ const isSelected = selectedFieldType === fieldType;
278
+ return /* @__PURE__ */ jsx(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ className: `rounded-md border px-3 py-1.5 text-sm font-medium transition ${isSelected ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700 hover:bg-slate-100"}`,
283
+ onClick: () => onSelectFieldType(isSelected ? null : fieldType),
284
+ "aria-pressed": isSelected,
285
+ children: FIELD_LABELS[fieldType]
286
+ },
287
+ fieldType
288
+ );
289
+ }) });
290
+ }
291
+ function SignerDetailsPanel({ signerInfo, onSignerInfoChange }) {
292
+ function handleInputChange(fieldName, fieldValue) {
293
+ onSignerInfoChange({
294
+ ...signerInfo,
295
+ [fieldName]: fieldValue
296
+ });
297
+ }
298
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-slate-300 bg-white p-4", children: [
299
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-slate-800", children: "Signer Details" }),
300
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
301
+ "First Name",
302
+ /* @__PURE__ */ jsx(
303
+ "input",
304
+ {
305
+ className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
306
+ value: signerInfo.firstName,
307
+ onChange: (event) => handleInputChange("firstName", event.target.value)
308
+ }
309
+ )
310
+ ] }),
311
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
312
+ "Last Name",
313
+ /* @__PURE__ */ jsx(
314
+ "input",
315
+ {
316
+ className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
317
+ value: signerInfo.lastName,
318
+ onChange: (event) => handleInputChange("lastName", event.target.value)
319
+ }
320
+ )
321
+ ] }),
322
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
323
+ "Title",
324
+ /* @__PURE__ */ jsx(
325
+ "input",
326
+ {
327
+ className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
328
+ value: signerInfo.title,
329
+ onChange: (event) => handleInputChange("title", event.target.value)
330
+ }
331
+ )
332
+ ] })
333
+ ] });
334
+ }
335
+
336
+ // src/lib/signature-fonts.ts
337
+ var loadedFonts = /* @__PURE__ */ new Set();
338
+ var SIGNATURE_FONTS = ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
339
+ function buildGoogleFontsCssUrl(family) {
340
+ const encoded = family.trim().replace(/\s+/g, "+");
341
+ return `https://fonts.googleapis.com/css2?family=${encoded}&display=swap`;
342
+ }
343
+ async function loadCssFromGoogleFonts(url) {
344
+ const response = await fetch(url);
345
+ if (!response.ok) throw new Error(`Unable to load font css from ${url}`);
346
+ return response.text();
347
+ }
348
+ function extractFontSource(cssText) {
349
+ const sourceMatch = cssText.match(/src:\s*url\(([^)]+)\)\s*format\(['"]?([^'")]+)['"]?\)/i);
350
+ if (!sourceMatch) return null;
351
+ return sourceMatch[1].replace(/['"]/g, "");
352
+ }
353
+ async function loadSignatureFont(fontFamily) {
354
+ if (loadedFonts.has(fontFamily)) return;
355
+ if (typeof document === "undefined") return;
356
+ if (typeof FontFace === "undefined") return;
357
+ const cssUrl = buildGoogleFontsCssUrl(fontFamily);
358
+ const cssText = await loadCssFromGoogleFonts(cssUrl);
359
+ const fontSource = extractFontSource(cssText);
360
+ if (!fontSource) throw new Error(`Unable to extract font source for ${fontFamily}`);
361
+ const fontFace = new FontFace(fontFamily, `url(${fontSource})`);
362
+ await fontFace.load();
363
+ const fontSet = document.fonts;
364
+ fontSet.add(fontFace);
365
+ loadedFonts.add(fontFamily);
366
+ }
367
+ function SignaturePad({ onDrawn }) {
368
+ const canvasRef = useRef(null);
369
+ const signaturePadRef = useRef(null);
370
+ useEffect(() => {
371
+ if (!canvasRef.current) return;
372
+ const signaturePad = new SignaturePadLibrary(canvasRef.current, {
373
+ minWidth: 1,
374
+ maxWidth: 2.5,
375
+ penColor: "#111827",
376
+ backgroundColor: "rgba(255,255,255,0)"
377
+ });
378
+ signaturePadRef.current = signaturePad;
379
+ function emitSignatureIfNotEmpty() {
380
+ if (signaturePad.isEmpty()) return;
381
+ onDrawn(signaturePad.toDataURL("image/png"));
382
+ }
383
+ signaturePad.addEventListener("endStroke", emitSignatureIfNotEmpty);
384
+ return () => {
385
+ signaturePad.removeEventListener("endStroke", emitSignatureIfNotEmpty);
386
+ signaturePad.off();
387
+ signaturePadRef.current = null;
388
+ };
389
+ }, [onDrawn]);
390
+ function handleClear() {
391
+ signaturePadRef.current?.clear();
392
+ onDrawn("");
393
+ }
394
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
395
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width: 420, height: 140, className: "w-full rounded border border-slate-300 bg-white" }),
396
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ type: "button",
400
+ className: "rounded border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50",
401
+ onClick: handleClear,
402
+ children: "Clear"
403
+ }
404
+ ) })
405
+ ] });
406
+ }
407
+ function SignaturePreview({
408
+ signerName,
409
+ style,
410
+ signatureDataUrl,
411
+ isRendering,
412
+ onStyleChange
413
+ }) {
414
+ const canShowPreview = useMemo(() => signerName.trim().length > 0, [signerName]);
415
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-slate-300 bg-white p-4", children: [
416
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-slate-800", children: "Signature" }),
417
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
418
+ /* @__PURE__ */ jsx(
419
+ "button",
420
+ {
421
+ type: "button",
422
+ className: `rounded border px-2 py-1 text-xs ${style.mode === "typed" ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700"}`,
423
+ onClick: () => onStyleChange({ mode: "typed", fontFamily: style.mode === "typed" ? style.fontFamily : SIGNATURE_FONTS[0] }),
424
+ children: "Typed"
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsx(
428
+ "button",
429
+ {
430
+ type: "button",
431
+ className: `rounded border px-2 py-1 text-xs ${style.mode === "drawn" ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700"}`,
432
+ onClick: () => onStyleChange({ mode: "drawn", dataUrl: style.mode === "drawn" ? style.dataUrl : "" }),
433
+ children: "Drawn"
434
+ }
435
+ )
436
+ ] }),
437
+ style.mode === "typed" ? /* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
438
+ "Font",
439
+ /* @__PURE__ */ jsx(
440
+ "select",
441
+ {
442
+ className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
443
+ value: style.fontFamily,
444
+ onChange: (event) => onStyleChange({ mode: "typed", fontFamily: event.target.value }),
445
+ children: SIGNATURE_FONTS.map((fontFamily) => /* @__PURE__ */ jsx("option", { value: fontFamily, children: fontFamily }, fontFamily))
446
+ }
447
+ )
448
+ ] }) : /* @__PURE__ */ jsx(SignaturePad, { onDrawn: (dataUrl) => onStyleChange({ mode: "drawn", dataUrl }) }),
449
+ /* @__PURE__ */ jsx("div", { className: "rounded border border-dashed border-slate-300 bg-slate-50 p-3", children: !canShowPreview ? /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Enter signer first and last name to render a signature." }) : isRendering ? /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Rendering signature..." }) : signatureDataUrl ? /* @__PURE__ */ jsx("img", { src: signatureDataUrl, alt: "Signature preview", className: "h-24 w-full object-contain" }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "No signature available yet." }) })
450
+ ] });
451
+ }
452
+ function SigningComplete({
453
+ signerName,
454
+ fieldCount,
455
+ signedAt,
456
+ documentHash,
457
+ downloadUrl,
458
+ fileName = "signed-document.pdf",
459
+ onReset
460
+ }) {
461
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border border-emerald-300 bg-emerald-50 p-4", children: [
462
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-emerald-900", children: "Document Signed" }),
463
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-sm text-emerald-900", children: [
464
+ /* @__PURE__ */ jsxs("p", { children: [
465
+ "Signer: ",
466
+ signerName || "Unknown"
467
+ ] }),
468
+ /* @__PURE__ */ jsxs("p", { children: [
469
+ "Fields applied: ",
470
+ fieldCount
471
+ ] }),
472
+ /* @__PURE__ */ jsxs("p", { children: [
473
+ "Signed at: ",
474
+ signedAt
475
+ ] })
476
+ ] }),
477
+ /* @__PURE__ */ jsxs("div", { className: "rounded border border-emerald-200 bg-white p-3", children: [
478
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium uppercase tracking-wide text-slate-500", children: "SHA-256" }),
479
+ /* @__PURE__ */ jsx("p", { className: "break-all font-mono text-xs text-slate-800", children: documentHash })
480
+ ] }),
481
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
482
+ /* @__PURE__ */ jsx(
483
+ "a",
484
+ {
485
+ href: downloadUrl,
486
+ download: fileName,
487
+ className: "rounded bg-emerald-700 px-3 py-1.5 text-sm text-white hover:bg-emerald-800",
488
+ children: "Download Signed PDF"
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsx(
492
+ "button",
493
+ {
494
+ type: "button",
495
+ className: "rounded border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-50",
496
+ onClick: onReset,
497
+ children: "Sign Another"
498
+ }
499
+ )
500
+ ] })
501
+ ] });
502
+ }
503
+ function isArrayBuffer(value) {
504
+ return value instanceof ArrayBuffer;
505
+ }
506
+ function isUint8Array(value) {
507
+ return value instanceof Uint8Array;
508
+ }
509
+ async function convertPdfInputToArrayBuffer(pdfInput) {
510
+ if (isArrayBuffer(pdfInput)) return pdfInput;
511
+ if (isUint8Array(pdfInput)) {
512
+ const copy = new Uint8Array(pdfInput.byteLength);
513
+ copy.set(pdfInput);
514
+ return copy.buffer;
515
+ }
516
+ return pdfInput.arrayBuffer();
517
+ }
518
+ function usePdfDocument(pdfInput) {
519
+ const [pdfData, setPdfData] = useState(null);
520
+ const [numPages, setNumPages] = useState(0);
521
+ const [scale, setScale] = useState(1);
522
+ const [pageDimensions, setPageDimensions] = useState([]);
523
+ const [errorMessage, setErrorMessage] = useState(null);
524
+ const [isLoading, setIsLoading] = useState(false);
525
+ useEffect(() => {
526
+ if (!pdfInput) {
527
+ setPdfData(null);
528
+ setNumPages(0);
529
+ setPageDimensions([]);
530
+ setErrorMessage(null);
531
+ return;
532
+ }
533
+ let isMounted = true;
534
+ setIsLoading(true);
535
+ setErrorMessage(null);
536
+ convertPdfInputToArrayBuffer(pdfInput).then((arrayBuffer) => {
537
+ if (!isMounted) return;
538
+ setPdfData(arrayBuffer);
539
+ }).catch((error) => {
540
+ if (!isMounted) return;
541
+ const message = error instanceof Error ? error.message : "Unable to read PDF data";
542
+ setErrorMessage(message);
543
+ setPdfData(null);
544
+ }).finally(() => {
545
+ if (!isMounted) return;
546
+ setIsLoading(false);
547
+ });
548
+ return () => {
549
+ isMounted = false;
550
+ };
551
+ }, [pdfInput]);
552
+ function handleDocumentLoadSuccess(loadedPages) {
553
+ setNumPages(loadedPages);
554
+ setPageDimensions(
555
+ (previousDimensions) => previousDimensions.filter((dimension) => dimension.pageIndex < loadedPages).sort((left, right) => left.pageIndex - right.pageIndex)
556
+ );
557
+ }
558
+ function setPageDimension(pageIndex, widthPt, heightPt) {
559
+ setPageDimensions((previousDimensions) => {
560
+ const nextDimensions = previousDimensions.filter((entry) => entry.pageIndex !== pageIndex);
561
+ nextDimensions.push({ pageIndex, widthPt, heightPt });
562
+ nextDimensions.sort((left, right) => left.pageIndex - right.pageIndex);
563
+ return nextDimensions;
564
+ });
565
+ }
566
+ const hasPdf = useMemo(() => pdfData !== null, [pdfData]);
567
+ return {
568
+ pdfData,
569
+ numPages,
570
+ scale,
571
+ setScale,
572
+ pageDimensions,
573
+ setPageDimension,
574
+ handleDocumentLoadSuccess,
575
+ hasPdf,
576
+ isLoading,
577
+ errorMessage
578
+ };
579
+ }
580
+ function clampPercent2(value) {
581
+ if (Number.isNaN(value)) return 0;
582
+ if (value < 0) return 0;
583
+ if (value > 100) return 100;
584
+ return value;
585
+ }
586
+ function buildFieldId() {
587
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
588
+ return crypto.randomUUID();
589
+ }
590
+ return `field-${Date.now()}-${Math.random().toString(16).slice(2)}`;
591
+ }
592
+ function useFieldPlacement(options = {}) {
593
+ const defaultWidthPercent = options.defaultWidthPercent ?? 25;
594
+ const defaultHeightPercent = options.defaultHeightPercent ?? 5;
595
+ const [fields, setFields] = useState([]);
596
+ const addField = useCallback(
597
+ ({ pageIndex, type, xPercent, yPercent }) => {
598
+ const field = {
599
+ id: buildFieldId(),
600
+ pageIndex,
601
+ type,
602
+ xPercent: clampPercent2(xPercent),
603
+ yPercent: clampPercent2(yPercent),
604
+ widthPercent: clampPercent2(defaultWidthPercent),
605
+ heightPercent: clampPercent2(defaultHeightPercent)
606
+ };
607
+ setFields((previousFields) => [...previousFields, field]);
608
+ return field;
609
+ },
610
+ [defaultHeightPercent, defaultWidthPercent]
611
+ );
612
+ const updateField = useCallback((id, partial) => {
613
+ setFields(
614
+ (previousFields) => previousFields.map((field) => {
615
+ if (field.id !== id) return field;
616
+ return {
617
+ ...field,
618
+ ...partial,
619
+ xPercent: partial.xPercent === void 0 ? field.xPercent : clampPercent2(partial.xPercent),
620
+ yPercent: partial.yPercent === void 0 ? field.yPercent : clampPercent2(partial.yPercent),
621
+ widthPercent: partial.widthPercent === void 0 ? field.widthPercent : clampPercent2(partial.widthPercent),
622
+ heightPercent: partial.heightPercent === void 0 ? field.heightPercent : clampPercent2(partial.heightPercent)
623
+ };
624
+ })
625
+ );
626
+ }, []);
627
+ const removeField = useCallback((id) => {
628
+ setFields((previousFields) => previousFields.filter((field) => field.id !== id));
629
+ }, []);
630
+ const clearFields = useCallback(() => {
631
+ setFields([]);
632
+ }, []);
633
+ const fieldActions = useMemo(
634
+ () => ({
635
+ addField,
636
+ updateField,
637
+ removeField,
638
+ clearFields
639
+ }),
640
+ [addField, clearFields, removeField, updateField]
641
+ );
642
+ return {
643
+ fields,
644
+ ...fieldActions
645
+ };
646
+ }
647
+ function buildCanvas() {
648
+ return document.createElement("canvas");
649
+ }
650
+ function drawTypedSignature({
651
+ signerName,
652
+ fontFamily
653
+ }) {
654
+ const canvas = buildCanvas();
655
+ const context = canvas.getContext("2d");
656
+ if (!context) return "";
657
+ const padding = 16;
658
+ const fontSize = 56;
659
+ context.font = `${fontSize}px "${fontFamily}"`;
660
+ const metrics = context.measureText(signerName);
661
+ canvas.width = Math.max(240, Math.ceil(metrics.width + padding * 2));
662
+ canvas.height = 100;
663
+ context.clearRect(0, 0, canvas.width, canvas.height);
664
+ context.font = `${fontSize}px "${fontFamily}"`;
665
+ context.fillStyle = "#111827";
666
+ context.textBaseline = "middle";
667
+ context.fillText(signerName, padding, canvas.height / 2);
668
+ return canvas.toDataURL("image/png");
669
+ }
670
+ function useSignatureRenderer({ signerName, style }) {
671
+ const [signatureDataUrl, setSignatureDataUrl] = useState(null);
672
+ const [isRendering, setIsRendering] = useState(false);
673
+ const normalizedName = useMemo(() => signerName.trim(), [signerName]);
674
+ useEffect(() => {
675
+ if (!normalizedName) {
676
+ setSignatureDataUrl(null);
677
+ return;
678
+ }
679
+ if (style.mode === "drawn") {
680
+ setSignatureDataUrl(style.dataUrl || null);
681
+ return;
682
+ }
683
+ let isActive = true;
684
+ setIsRendering(true);
685
+ loadSignatureFont(style.fontFamily).then(() => {
686
+ if (!isActive) return;
687
+ const dataUrl = drawTypedSignature({ signerName: normalizedName, fontFamily: style.fontFamily });
688
+ setSignatureDataUrl(dataUrl || null);
689
+ }).catch(() => {
690
+ if (!isActive) return;
691
+ setSignatureDataUrl(null);
692
+ }).finally(() => {
693
+ if (!isActive) return;
694
+ setIsRendering(false);
695
+ });
696
+ return () => {
697
+ isActive = false;
698
+ };
699
+ }, [normalizedName, style]);
700
+ return {
701
+ signatureDataUrl,
702
+ isRendering
703
+ };
704
+ }
705
+
706
+ // src/lib/coordinate-mapper.ts
707
+ function clampPercent3(value) {
708
+ if (Number.isNaN(value)) return 0;
709
+ if (value < 0) return 0;
710
+ if (value > 100) return 100;
711
+ return value;
712
+ }
713
+ function mapToPoints(field, page) {
714
+ const xPercent = clampPercent3(field.xPercent);
715
+ const yPercent = clampPercent3(field.yPercent);
716
+ const widthPercent = clampPercent3(field.widthPercent);
717
+ const heightPercent = clampPercent3(field.heightPercent);
718
+ const width = widthPercent / 100 * page.widthPt;
719
+ const height = heightPercent / 100 * page.heightPt;
720
+ const x = xPercent / 100 * page.widthPt;
721
+ const y = page.heightPt - yPercent / 100 * page.heightPt - height;
722
+ return { x, y, width, height };
723
+ }
724
+ function mapFromPoints(rect, page) {
725
+ const widthPercent = rect.width / page.widthPt * 100;
726
+ const heightPercent = rect.height / page.heightPt * 100;
727
+ const xPercent = rect.x / page.widthPt * 100;
728
+ const yPercent = (page.heightPt - rect.y - rect.height) / page.heightPt * 100;
729
+ return {
730
+ xPercent: clampPercent3(xPercent),
731
+ yPercent: clampPercent3(yPercent),
732
+ widthPercent: clampPercent3(widthPercent),
733
+ heightPercent: clampPercent3(heightPercent)
734
+ };
735
+ }
736
+
737
+ // src/lib/pdf-modifier.ts
738
+ function fullNameFromSigner(signer) {
739
+ return [signer.firstName, signer.lastName].filter(Boolean).join(" ").trim();
740
+ }
741
+ function dataUrlToBytes(dataUrl) {
742
+ const base64 = dataUrl.split(",")[1];
743
+ if (!base64) throw new Error("Invalid signature data URL");
744
+ const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
745
+ const bytes = new Uint8Array(binary.length);
746
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
747
+ return bytes;
748
+ }
749
+ async function modifyPdf({
750
+ pdfBytes,
751
+ fields,
752
+ signer,
753
+ signatureDataUrl,
754
+ pageDimensions,
755
+ dateText
756
+ }) {
757
+ const pdfDocument = await PDFDocument.load(pdfBytes);
758
+ pdfDocument.registerFontkit(fontkit);
759
+ const pages = pdfDocument.getPages();
760
+ const helveticaFont = await pdfDocument.embedFont(StandardFonts.Helvetica);
761
+ const signatureImageBytes = signatureDataUrl ? dataUrlToBytes(signatureDataUrl) : null;
762
+ const signatureImage = signatureImageBytes ? await pdfDocument.embedPng(signatureImageBytes) : null;
763
+ const resolvedDateText = dateText || (/* @__PURE__ */ new Date()).toLocaleDateString();
764
+ for (const field of fields) {
765
+ const page = pages[field.pageIndex];
766
+ const pageDimension = pageDimensions.find((entry) => entry.pageIndex === field.pageIndex);
767
+ if (!page || !pageDimension) continue;
768
+ const pointRect = mapToPoints(field, pageDimension);
769
+ if (field.type === "signature" && signatureImage) {
770
+ page.drawImage(signatureImage, {
771
+ x: pointRect.x,
772
+ y: pointRect.y,
773
+ width: pointRect.width,
774
+ height: pointRect.height
775
+ });
776
+ continue;
777
+ }
778
+ const textValue = field.type === "fullName" ? fullNameFromSigner(signer) : field.type === "title" ? signer.title : field.type === "date" ? resolvedDateText : "";
779
+ if (!textValue) continue;
780
+ const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6));
781
+ page.drawText(textValue, {
782
+ x: pointRect.x + 2,
783
+ y: pointRect.y + Math.max(0, (pointRect.height - textSize) / 2),
784
+ size: textSize,
785
+ font: helveticaFont
786
+ });
787
+ }
788
+ return pdfDocument.save();
789
+ }
790
+
791
+ // src/lib/hash.ts
792
+ async function sha256(data) {
793
+ const dataBuffer = new Uint8Array(data.byteLength);
794
+ dataBuffer.set(data);
795
+ const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
796
+ return Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
797
+ }
798
+
799
+ // src/index.ts
800
+ var defaults = {
801
+ SIGNATURE_FONTS: ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"],
802
+ DEFAULT_FIELD_WIDTH_PERCENT: 25,
803
+ DEFAULT_FIELD_HEIGHT_PERCENT: 5
804
+ };
805
+
806
+ export { FieldOverlay, FieldPalette, PdfViewer, SIGNATURE_FONTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
807
+ //# sourceMappingURL=index.js.map
808
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/pdf-viewer.tsx","../src/components/signature-field.tsx","../src/components/field-overlay.tsx","../src/components/field-palette.tsx","../src/components/signer-details-panel.tsx","../src/lib/signature-fonts.ts","../src/components/signature-pad.tsx","../src/components/signature-preview.tsx","../src/components/signing-complete.tsx","../src/hooks/use-pdf-document.ts","../src/hooks/use-field-placement.ts","../src/hooks/use-signature-renderer.ts","../src/lib/coordinate-mapper.ts","../src/lib/pdf-modifier.ts","../src/lib/hash.ts","../src/index.ts"],"names":["jsxs","jsx","useRef","useEffect","useMemo","clampPercent","useState"],"mappings":"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","file":"index.js","sourcesContent":["import { Document, Page, pdfjs } from 'react-pdf'\nimport type { ReactNode } from 'react'\n\nif (typeof window !== 'undefined') {\n pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n}\n\ninterface PdfViewerProps {\n pdfData: ArrayBuffer | null\n numPages: number\n scale: number\n onScaleChange: (nextScale: number) => void\n onDocumentLoadSuccess: (numPages: number) => void\n onPageDimensions: (input: { pageIndex: number; widthPt: number; heightPt: number }) => void\n renderOverlay?: (pageIndex: number) => ReactNode\n}\n\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 2\nconst SCALE_STEP = 0.1\n\nexport function PdfViewer({\n pdfData,\n numPages,\n scale,\n onScaleChange,\n onDocumentLoadSuccess,\n onPageDimensions,\n renderOverlay,\n}: PdfViewerProps) {\n if (!pdfData)\n return (\n <div className=\"rounded-lg border border-dashed border-slate-300 bg-slate-50 p-8 text-center text-sm text-slate-500\">\n Upload a PDF to begin\n </div>\n )\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg border border-slate-300 bg-white p-3\">\n <div className=\"text-sm text-slate-700\">Pages: {numPages || '—'}</div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50\"\n onClick={() => onScaleChange(Math.max(MIN_SCALE, Number((scale - SCALE_STEP).toFixed(2))))}\n >\n -\n </button>\n <span className=\"w-16 text-center text-sm text-slate-700\">{Math.round(scale * 100)}%</span>\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50\"\n onClick={() => onScaleChange(Math.min(MAX_SCALE, Number((scale + SCALE_STEP).toFixed(2))))}\n >\n +\n </button>\n </div>\n </div>\n\n <Document\n file={pdfData}\n onLoadSuccess={(loadedPdf) => onDocumentLoadSuccess(loadedPdf.numPages)}\n loading={<div className=\"text-sm text-slate-500\">Loading PDF...</div>}\n error={<div className=\"text-sm text-red-600\">Unable to render this PDF.</div>}\n >\n <div className=\"space-y-6\">\n {Array.from({ length: numPages }, (_, pageIndex) => (\n <div key={`pdf-page-${pageIndex}`} className=\"relative mx-auto w-fit rounded bg-white p-2 shadow\">\n <Page\n pageNumber={pageIndex + 1}\n scale={scale}\n renderTextLayer={false}\n renderAnnotationLayer={false}\n onLoadSuccess={(page) =>\n onPageDimensions({\n pageIndex,\n widthPt: page.view[2],\n heightPt: page.view[3],\n })\n }\n />\n {renderOverlay?.(pageIndex)}\n </div>\n ))}\n </div>\n </Document>\n </div>\n )\n}\n","import { useRef } from 'react'\nimport type { FieldPlacement, SignatureFieldPreview } from '../types'\nimport type { PointerEvent } from 'react'\n\ninterface SignatureFieldProps {\n field: FieldPlacement\n onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void\n onRemoveField: (fieldId: string) => void\n preview: SignatureFieldPreview\n}\n\ninterface DragState {\n startClientX: number\n startClientY: number\n startXPercent: number\n startYPercent: number\n}\n\ninterface ResizeState {\n startClientX: number\n startClientY: number\n startWidthPercent: number\n startHeightPercent: number\n}\n\nconst MIN_WIDTH_PERCENT = 8\nconst MIN_HEIGHT_PERCENT = 3\n\nfunction clampPercent(value: number): number {\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nfunction getFieldPreviewText(field: FieldPlacement, preview: SignatureFieldPreview): string {\n if (field.type === 'fullName') return preview.fullName\n if (field.type === 'title') return preview.title\n if (field.type === 'date') return preview.dateText\n return ''\n}\n\nexport function SignatureField({ field, onUpdateField, onRemoveField, preview }: SignatureFieldProps) {\n const rootRef = useRef<HTMLDivElement | null>(null)\n const dragStateRef = useRef<DragState | null>(null)\n const resizeStateRef = useRef<ResizeState | null>(null)\n\n function handleDragPointerDown(event: PointerEvent<HTMLDivElement>): void {\n event.stopPropagation()\n if (event.button !== 0) return\n if (!rootRef.current?.parentElement) return\n\n dragStateRef.current = {\n startClientX: event.clientX,\n startClientY: event.clientY,\n startXPercent: field.xPercent,\n startYPercent: field.yPercent,\n }\n\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n function handleDragPointerMove(event: PointerEvent<HTMLDivElement>): void {\n if (!dragStateRef.current) return\n const parentElement = rootRef.current?.parentElement\n if (!parentElement) return\n\n const deltaX = event.clientX - dragStateRef.current.startClientX\n const deltaY = event.clientY - dragStateRef.current.startClientY\n const deltaXPercent = (deltaX / parentElement.clientWidth) * 100\n const deltaYPercent = (deltaY / parentElement.clientHeight) * 100\n const maxX = 100 - field.widthPercent\n const maxY = 100 - field.heightPercent\n\n onUpdateField(field.id, {\n xPercent: clampPercent(Math.min(maxX, dragStateRef.current.startXPercent + deltaXPercent)),\n yPercent: clampPercent(Math.min(maxY, dragStateRef.current.startYPercent + deltaYPercent)),\n })\n }\n\n function handleDragPointerUp(event: PointerEvent<HTMLDivElement>): void {\n dragStateRef.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId))\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n\n function handleResizePointerDown(event: PointerEvent<HTMLDivElement>): void {\n event.stopPropagation()\n if (event.button !== 0) return\n\n resizeStateRef.current = {\n startClientX: event.clientX,\n startClientY: event.clientY,\n startWidthPercent: field.widthPercent,\n startHeightPercent: field.heightPercent,\n }\n\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n function handleResizePointerMove(event: PointerEvent<HTMLDivElement>): void {\n if (!resizeStateRef.current) return\n const parentElement = rootRef.current?.parentElement\n if (!parentElement) return\n\n const deltaX = event.clientX - resizeStateRef.current.startClientX\n const deltaY = event.clientY - resizeStateRef.current.startClientY\n const deltaWidthPercent = (deltaX / parentElement.clientWidth) * 100\n const deltaHeightPercent = (deltaY / parentElement.clientHeight) * 100\n\n const maxWidth = 100 - field.xPercent\n const maxHeight = 100 - field.yPercent\n const widthPercent = clampPercent(\n Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))\n )\n const heightPercent = clampPercent(\n Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent))\n )\n\n onUpdateField(field.id, { widthPercent, heightPercent })\n }\n\n function handleResizePointerUp(event: PointerEvent<HTMLDivElement>): void {\n resizeStateRef.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId))\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n\n const previewText = getFieldPreviewText(field, preview)\n\n return (\n <div\n ref={rootRef}\n className=\"absolute rounded border-2 border-blue-500 bg-blue-50/80 shadow-sm select-none\"\n style={{\n left: `${field.xPercent}%`,\n top: `${field.yPercent}%`,\n width: `${field.widthPercent}%`,\n height: `${field.heightPercent}%`,\n }}\n onPointerDown={handleDragPointerDown}\n onPointerMove={handleDragPointerMove}\n onPointerUp={handleDragPointerUp}\n >\n <div className=\"flex h-full w-full items-start justify-between gap-2 p-1.5 text-[11px] text-slate-800\">\n <div className=\"min-w-0 flex-1 overflow-hidden\">\n <div className=\"truncate font-semibold capitalize\">{field.type}</div>\n {field.type === 'signature' && preview.signatureDataUrl ? (\n <img\n src={preview.signatureDataUrl}\n alt=\"signature preview\"\n className=\"mt-1 h-[calc(100%-18px)] max-h-full w-full object-contain\"\n draggable={false}\n />\n ) : (\n <div className=\"truncate text-slate-600\">{previewText || '—'}</div>\n )}\n </div>\n <button\n type=\"button\"\n className=\"rounded bg-white/80 px-1 text-xs text-red-600 hover:bg-white\"\n onPointerDown={(event) => event.stopPropagation()}\n onClick={(event) => {\n event.stopPropagation()\n onRemoveField(field.id)\n }}\n aria-label=\"Remove field\"\n >\n ×\n </button>\n </div>\n\n <div\n className=\"absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-nwse-resize rounded-full bg-blue-600\"\n onPointerDown={handleResizePointerDown}\n onPointerMove={handleResizePointerMove}\n onPointerUp={handleResizePointerUp}\n />\n </div>\n )\n}\n","import type { FieldPlacement, FieldType, SignatureFieldPreview } from '../types'\nimport { SignatureField } from './signature-field'\nimport type { PointerEvent } from 'react'\n\ninterface FieldOverlayProps {\n pageIndex: number\n fields: FieldPlacement[]\n selectedFieldType: FieldType | null\n onAddField: (input: { pageIndex: number; type: FieldType; xPercent: number; yPercent: number }) => void\n onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void\n onRemoveField: (fieldId: string) => void\n preview: SignatureFieldPreview\n}\n\nexport function FieldOverlay({\n pageIndex,\n fields,\n selectedFieldType,\n onAddField,\n onUpdateField,\n onRemoveField,\n preview,\n}: FieldOverlayProps) {\n function handleOverlayPointerDown(event: PointerEvent<HTMLDivElement>): void {\n if (!selectedFieldType) return\n if (event.button !== 0) return\n if (event.target !== event.currentTarget) return\n const rect = event.currentTarget.getBoundingClientRect()\n const xPercent = ((event.clientX - rect.left) / rect.width) * 100\n const yPercent = ((event.clientY - rect.top) / rect.height) * 100\n onAddField({\n pageIndex,\n type: selectedFieldType,\n xPercent,\n yPercent,\n })\n }\n\n const pageFields = fields.filter((field) => field.pageIndex === pageIndex)\n\n return (\n <div\n className={`absolute inset-0 z-20 rounded ${selectedFieldType ? 'cursor-crosshair' : 'cursor-default'}`}\n onPointerDown={handleOverlayPointerDown}\n aria-label={`Field overlay page ${pageIndex + 1}`}\n >\n {pageFields.map((field) => (\n <SignatureField\n key={field.id}\n field={field}\n onUpdateField={onUpdateField}\n onRemoveField={onRemoveField}\n preview={preview}\n />\n ))}\n </div>\n )\n}\n","import type { FieldType } from '../types'\n\ninterface FieldPaletteProps {\n selectedFieldType: FieldType | null\n onSelectFieldType: (fieldType: FieldType | null) => void\n}\n\nconst FIELD_LABELS: Record<FieldType, string> = {\n signature: 'Signature',\n fullName: 'Full Name',\n title: 'Title',\n date: 'Date',\n}\n\nconst FIELD_TYPES: FieldType[] = ['signature', 'fullName', 'title', 'date']\n\nexport function FieldPalette({ selectedFieldType, onSelectFieldType }: FieldPaletteProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-2 rounded-lg border border-slate-300 bg-white p-2\">\n {FIELD_TYPES.map((fieldType) => {\n const isSelected = selectedFieldType === fieldType\n return (\n <button\n key={fieldType}\n type=\"button\"\n className={`rounded-md border px-3 py-1.5 text-sm font-medium transition ${\n isSelected\n ? 'border-blue-600 bg-blue-600 text-white'\n : 'border-slate-300 bg-slate-50 text-slate-700 hover:bg-slate-100'\n }`}\n onClick={() => onSelectFieldType(isSelected ? null : fieldType)}\n aria-pressed={isSelected}\n >\n {FIELD_LABELS[fieldType]}\n </button>\n )\n })}\n </div>\n )\n}\n","import type { SignerInfo } from '../types'\n\ninterface SignerDetailsPanelProps {\n signerInfo: SignerInfo\n onSignerInfoChange: (nextSignerInfo: SignerInfo) => void\n}\n\nexport function SignerDetailsPanel({ signerInfo, onSignerInfoChange }: SignerDetailsPanelProps) {\n function handleInputChange(fieldName: keyof SignerInfo, fieldValue: string): void {\n onSignerInfoChange({\n ...signerInfo,\n [fieldName]: fieldValue,\n })\n }\n\n return (\n <div className=\"space-y-3 rounded-lg border border-slate-300 bg-white p-4\">\n <h2 className=\"text-sm font-semibold text-slate-800\">Signer Details</h2>\n\n <label className=\"block text-sm text-slate-700\">\n First Name\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.firstName}\n onChange={(event) => handleInputChange('firstName', event.target.value)}\n />\n </label>\n\n <label className=\"block text-sm text-slate-700\">\n Last Name\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.lastName}\n onChange={(event) => handleInputChange('lastName', event.target.value)}\n />\n </label>\n\n <label className=\"block text-sm text-slate-700\">\n Title\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.title}\n onChange={(event) => handleInputChange('title', event.target.value)}\n />\n </label>\n </div>\n )\n}\n","const loadedFonts = new Set<string>()\n\nexport const SIGNATURE_FONTS = ['Dancing Script', 'Great Vibes', 'Sacramento', 'Alex Brush'] as const\n\nfunction buildGoogleFontsCssUrl(family: string): string {\n const encoded = family.trim().replace(/\\s+/g, '+')\n return `https://fonts.googleapis.com/css2?family=${encoded}&display=swap`\n}\n\nasync function loadCssFromGoogleFonts(url: string): Promise<string> {\n const response = await fetch(url)\n if (!response.ok) throw new Error(`Unable to load font css from ${url}`)\n return response.text()\n}\n\nfunction extractFontSource(cssText: string): string | null {\n const sourceMatch = cssText.match(/src:\\s*url\\(([^)]+)\\)\\s*format\\(['\"]?([^'\")]+)['\"]?\\)/i)\n if (!sourceMatch) return null\n return sourceMatch[1].replace(/['\"]/g, '')\n}\n\nexport async function loadSignatureFont(fontFamily: string): Promise<void> {\n if (loadedFonts.has(fontFamily)) return\n if (typeof document === 'undefined') return\n if (typeof FontFace === 'undefined') return\n\n const cssUrl = buildGoogleFontsCssUrl(fontFamily)\n const cssText = await loadCssFromGoogleFonts(cssUrl)\n const fontSource = extractFontSource(cssText)\n if (!fontSource) throw new Error(`Unable to extract font source for ${fontFamily}`)\n\n const fontFace = new FontFace(fontFamily, `url(${fontSource})`)\n await fontFace.load()\n const fontSet = document.fonts as unknown as { add: (font: FontFace) => void }\n fontSet.add(fontFace)\n loadedFonts.add(fontFamily)\n}\n\nexport function buildSignatureFontCssUrl(fontFamily: string): string {\n return buildGoogleFontsCssUrl(fontFamily)\n}\n","import SignaturePadLibrary from 'signature_pad'\nimport { useEffect, useRef } from 'react'\n\ninterface SignaturePadProps {\n onDrawn: (signatureDataUrl: string) => void\n}\n\nexport function SignaturePad({ onDrawn }: SignaturePadProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const signaturePadRef = useRef<SignaturePadLibrary | null>(null)\n\n useEffect(() => {\n if (!canvasRef.current) return\n\n const signaturePad = new SignaturePadLibrary(canvasRef.current, {\n minWidth: 1,\n maxWidth: 2.5,\n penColor: '#111827',\n backgroundColor: 'rgba(255,255,255,0)',\n })\n\n signaturePadRef.current = signaturePad\n\n function emitSignatureIfNotEmpty(): void {\n if (signaturePad.isEmpty()) return\n onDrawn(signaturePad.toDataURL('image/png'))\n }\n\n signaturePad.addEventListener('endStroke', emitSignatureIfNotEmpty)\n\n return () => {\n signaturePad.removeEventListener('endStroke', emitSignatureIfNotEmpty)\n signaturePad.off()\n signaturePadRef.current = null\n }\n }, [onDrawn])\n\n function handleClear(): void {\n signaturePadRef.current?.clear()\n onDrawn('')\n }\n\n return (\n <div className=\"space-y-2\">\n <canvas ref={canvasRef} width={420} height={140} className=\"w-full rounded border border-slate-300 bg-white\" />\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50\"\n onClick={handleClear}\n >\n Clear\n </button>\n </div>\n </div>\n )\n}\n","import { useMemo } from 'react'\nimport { SIGNATURE_FONTS } from '../lib/signature-fonts'\nimport type { SignatureStyle } from '../types'\nimport { SignaturePad } from './signature-pad'\n\ninterface SignaturePreviewProps {\n signerName: string\n style: SignatureStyle\n signatureDataUrl: string | null\n isRendering: boolean\n onStyleChange: (nextStyle: SignatureStyle) => void\n}\n\nexport function SignaturePreview({\n signerName,\n style,\n signatureDataUrl,\n isRendering,\n onStyleChange,\n}: SignaturePreviewProps) {\n const canShowPreview = useMemo(() => signerName.trim().length > 0, [signerName])\n\n return (\n <div className=\"space-y-3 rounded-lg border border-slate-300 bg-white p-4\">\n <h2 className=\"text-sm font-semibold text-slate-800\">Signature</h2>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n className={`rounded border px-2 py-1 text-xs ${style.mode === 'typed' ? 'border-blue-600 bg-blue-600 text-white' : 'border-slate-300 bg-slate-50 text-slate-700'}`}\n onClick={() => onStyleChange({ mode: 'typed', fontFamily: style.mode === 'typed' ? style.fontFamily : SIGNATURE_FONTS[0] })}\n >\n Typed\n </button>\n <button\n type=\"button\"\n className={`rounded border px-2 py-1 text-xs ${style.mode === 'drawn' ? 'border-blue-600 bg-blue-600 text-white' : 'border-slate-300 bg-slate-50 text-slate-700'}`}\n onClick={() => onStyleChange({ mode: 'drawn', dataUrl: style.mode === 'drawn' ? style.dataUrl : '' })}\n >\n Drawn\n </button>\n </div>\n\n {style.mode === 'typed' ? (\n <label className=\"block text-sm text-slate-700\">\n Font\n <select\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={style.fontFamily}\n onChange={(event) => onStyleChange({ mode: 'typed', fontFamily: event.target.value })}\n >\n {SIGNATURE_FONTS.map((fontFamily) => (\n <option key={fontFamily} value={fontFamily}>\n {fontFamily}\n </option>\n ))}\n </select>\n </label>\n ) : (\n <SignaturePad onDrawn={(dataUrl) => onStyleChange({ mode: 'drawn', dataUrl })} />\n )}\n\n <div className=\"rounded border border-dashed border-slate-300 bg-slate-50 p-3\">\n {!canShowPreview ? (\n <p className=\"text-xs text-slate-500\">Enter signer first and last name to render a signature.</p>\n ) : isRendering ? (\n <p className=\"text-xs text-slate-500\">Rendering signature...</p>\n ) : signatureDataUrl ? (\n <img src={signatureDataUrl} alt=\"Signature preview\" className=\"h-24 w-full object-contain\" />\n ) : (\n <p className=\"text-xs text-slate-500\">No signature available yet.</p>\n )}\n </div>\n </div>\n )\n}\n","interface SigningCompleteProps {\n signerName: string\n fieldCount: number\n signedAt: string\n documentHash: string\n downloadUrl: string\n fileName?: string\n onReset: () => void\n}\n\nexport function SigningComplete({\n signerName,\n fieldCount,\n signedAt,\n documentHash,\n downloadUrl,\n fileName = 'signed-document.pdf',\n onReset,\n}: SigningCompleteProps) {\n return (\n <div className=\"space-y-4 rounded-lg border border-emerald-300 bg-emerald-50 p-4\">\n <h2 className=\"text-base font-semibold text-emerald-900\">Document Signed</h2>\n <div className=\"space-y-1 text-sm text-emerald-900\">\n <p>Signer: {signerName || 'Unknown'}</p>\n <p>Fields applied: {fieldCount}</p>\n <p>Signed at: {signedAt}</p>\n </div>\n\n <div className=\"rounded border border-emerald-200 bg-white p-3\">\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-slate-500\">SHA-256</p>\n <p className=\"break-all font-mono text-xs text-slate-800\">{documentHash}</p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={downloadUrl}\n download={fileName}\n className=\"rounded bg-emerald-700 px-3 py-1.5 text-sm text-white hover:bg-emerald-800\"\n >\n Download Signed PDF\n </a>\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-50\"\n onClick={onReset}\n >\n Sign Another\n </button>\n </div>\n </div>\n )\n}\n","import { useEffect, useMemo, useState } from 'react'\nimport type { PdfPageDimensions } from '../types'\n\nexport type PdfInput = File | Blob | ArrayBuffer | Uint8Array | null\n\nfunction isArrayBuffer(value: unknown): value is ArrayBuffer {\n return value instanceof ArrayBuffer\n}\n\nfunction isUint8Array(value: unknown): value is Uint8Array {\n return value instanceof Uint8Array\n}\n\nasync function convertPdfInputToArrayBuffer(pdfInput: Exclude<PdfInput, null>): Promise<ArrayBuffer> {\n if (isArrayBuffer(pdfInput)) return pdfInput\n if (isUint8Array(pdfInput)) {\n const copy = new Uint8Array(pdfInput.byteLength)\n copy.set(pdfInput)\n return copy.buffer\n }\n return pdfInput.arrayBuffer()\n}\n\nexport function usePdfDocument(pdfInput: PdfInput) {\n const [pdfData, setPdfData] = useState<ArrayBuffer | null>(null)\n const [numPages, setNumPages] = useState(0)\n const [scale, setScale] = useState(1)\n const [pageDimensions, setPageDimensions] = useState<PdfPageDimensions[]>([])\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n\n useEffect(() => {\n if (!pdfInput) {\n setPdfData(null)\n setNumPages(0)\n setPageDimensions([])\n setErrorMessage(null)\n return\n }\n\n let isMounted = true\n\n setIsLoading(true)\n setErrorMessage(null)\n convertPdfInputToArrayBuffer(pdfInput)\n .then((arrayBuffer) => {\n if (!isMounted) return\n setPdfData(arrayBuffer)\n })\n .catch((error: unknown) => {\n if (!isMounted) return\n const message = error instanceof Error ? error.message : 'Unable to read PDF data'\n setErrorMessage(message)\n setPdfData(null)\n })\n .finally(() => {\n if (!isMounted) return\n setIsLoading(false)\n })\n\n return () => {\n isMounted = false\n }\n }, [pdfInput])\n\n function handleDocumentLoadSuccess(loadedPages: number): void {\n setNumPages(loadedPages)\n setPageDimensions((previousDimensions) =>\n previousDimensions\n .filter((dimension) => dimension.pageIndex < loadedPages)\n .sort((left, right) => left.pageIndex - right.pageIndex)\n )\n }\n\n function setPageDimension(pageIndex: number, widthPt: number, heightPt: number): void {\n setPageDimensions((previousDimensions) => {\n const nextDimensions = previousDimensions.filter((entry) => entry.pageIndex !== pageIndex)\n nextDimensions.push({ pageIndex, widthPt, heightPt })\n nextDimensions.sort((left, right) => left.pageIndex - right.pageIndex)\n return nextDimensions\n })\n }\n\n const hasPdf = useMemo(() => pdfData !== null, [pdfData])\n\n return {\n pdfData,\n numPages,\n scale,\n setScale,\n pageDimensions,\n setPageDimension,\n handleDocumentLoadSuccess,\n hasPdf,\n isLoading,\n errorMessage,\n }\n}\n","import { useCallback, useMemo, useState } from 'react'\nimport type { FieldPlacement, FieldType } from '../types'\n\ninterface UseFieldPlacementOptions {\n defaultWidthPercent?: number\n defaultHeightPercent?: number\n}\n\ninterface AddFieldInput {\n pageIndex: number\n type: FieldType\n xPercent: number\n yPercent: number\n}\n\nfunction clampPercent(value: number): number {\n if (Number.isNaN(value)) return 0\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nfunction buildFieldId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `field-${Date.now()}-${Math.random().toString(16).slice(2)}`\n}\n\nexport function useFieldPlacement(options: UseFieldPlacementOptions = {}) {\n const defaultWidthPercent = options.defaultWidthPercent ?? 25\n const defaultHeightPercent = options.defaultHeightPercent ?? 5\n const [fields, setFields] = useState<FieldPlacement[]>([])\n\n const addField = useCallback(\n ({ pageIndex, type, xPercent, yPercent }: AddFieldInput) => {\n const field: FieldPlacement = {\n id: buildFieldId(),\n pageIndex,\n type,\n xPercent: clampPercent(xPercent),\n yPercent: clampPercent(yPercent),\n widthPercent: clampPercent(defaultWidthPercent),\n heightPercent: clampPercent(defaultHeightPercent),\n }\n setFields((previousFields) => [...previousFields, field])\n return field\n },\n [defaultHeightPercent, defaultWidthPercent]\n )\n\n const updateField = useCallback((id: string, partial: Partial<FieldPlacement>) => {\n setFields((previousFields) =>\n previousFields.map((field) => {\n if (field.id !== id) return field\n return {\n ...field,\n ...partial,\n xPercent: partial.xPercent === undefined ? field.xPercent : clampPercent(partial.xPercent),\n yPercent: partial.yPercent === undefined ? field.yPercent : clampPercent(partial.yPercent),\n widthPercent:\n partial.widthPercent === undefined ? field.widthPercent : clampPercent(partial.widthPercent),\n heightPercent:\n partial.heightPercent === undefined ? field.heightPercent : clampPercent(partial.heightPercent),\n }\n })\n )\n }, [])\n\n const removeField = useCallback((id: string) => {\n setFields((previousFields) => previousFields.filter((field) => field.id !== id))\n }, [])\n\n const clearFields = useCallback(() => {\n setFields([])\n }, [])\n\n const fieldActions = useMemo(\n () => ({\n addField,\n updateField,\n removeField,\n clearFields,\n }),\n [addField, clearFields, removeField, updateField]\n )\n\n return {\n fields,\n ...fieldActions,\n }\n}\n","import { useEffect, useMemo, useState } from 'react'\nimport { loadSignatureFont } from '../lib/signature-fonts'\nimport type { SignatureStyle } from '../types'\n\ninterface UseSignatureRendererInput {\n signerName: string\n style: SignatureStyle\n}\n\nfunction buildCanvas(): HTMLCanvasElement {\n return document.createElement('canvas')\n}\n\nfunction drawTypedSignature({\n signerName,\n fontFamily,\n}: {\n signerName: string\n fontFamily: string\n}): string {\n const canvas = buildCanvas()\n const context = canvas.getContext('2d')\n if (!context) return ''\n\n const padding = 16\n const fontSize = 56\n context.font = `${fontSize}px \"${fontFamily}\"`\n const metrics = context.measureText(signerName)\n\n canvas.width = Math.max(240, Math.ceil(metrics.width + padding * 2))\n canvas.height = 100\n\n context.clearRect(0, 0, canvas.width, canvas.height)\n context.font = `${fontSize}px \"${fontFamily}\"`\n context.fillStyle = '#111827'\n context.textBaseline = 'middle'\n context.fillText(signerName, padding, canvas.height / 2)\n\n return canvas.toDataURL('image/png')\n}\n\nexport function useSignatureRenderer({ signerName, style }: UseSignatureRendererInput) {\n const [signatureDataUrl, setSignatureDataUrl] = useState<string | null>(null)\n const [isRendering, setIsRendering] = useState(false)\n\n const normalizedName = useMemo(() => signerName.trim(), [signerName])\n\n useEffect(() => {\n if (!normalizedName) {\n setSignatureDataUrl(null)\n return\n }\n\n if (style.mode === 'drawn') {\n setSignatureDataUrl(style.dataUrl || null)\n return\n }\n\n let isActive = true\n setIsRendering(true)\n\n loadSignatureFont(style.fontFamily)\n .then(() => {\n if (!isActive) return\n const dataUrl = drawTypedSignature({ signerName: normalizedName, fontFamily: style.fontFamily })\n setSignatureDataUrl(dataUrl || null)\n })\n .catch(() => {\n if (!isActive) return\n setSignatureDataUrl(null)\n })\n .finally(() => {\n if (!isActive) return\n setIsRendering(false)\n })\n\n return () => {\n isActive = false\n }\n }, [normalizedName, style])\n\n return {\n signatureDataUrl,\n isRendering,\n }\n}\n","import type { FieldPlacement, PdfPageDimensions, PointRect } from '../types'\n\nfunction clampPercent(value: number): number {\n if (Number.isNaN(value)) return 0\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nexport function mapToPoints(field: FieldPlacement, page: PdfPageDimensions): PointRect {\n const xPercent = clampPercent(field.xPercent)\n const yPercent = clampPercent(field.yPercent)\n const widthPercent = clampPercent(field.widthPercent)\n const heightPercent = clampPercent(field.heightPercent)\n\n const width = (widthPercent / 100) * page.widthPt\n const height = (heightPercent / 100) * page.heightPt\n const x = (xPercent / 100) * page.widthPt\n const y = page.heightPt - (yPercent / 100) * page.heightPt - height\n\n return { x, y, width, height }\n}\n\nexport function mapFromPoints(\n rect: PointRect,\n page: PdfPageDimensions\n): Pick<FieldPlacement, 'xPercent' | 'yPercent' | 'widthPercent' | 'heightPercent'> {\n const widthPercent = (rect.width / page.widthPt) * 100\n const heightPercent = (rect.height / page.heightPt) * 100\n const xPercent = (rect.x / page.widthPt) * 100\n const yPercent = ((page.heightPt - rect.y - rect.height) / page.heightPt) * 100\n\n return {\n xPercent: clampPercent(xPercent),\n yPercent: clampPercent(yPercent),\n widthPercent: clampPercent(widthPercent),\n heightPercent: clampPercent(heightPercent),\n }\n}\n","import fontkit from '@pdf-lib/fontkit'\nimport { PDFDocument, StandardFonts } from 'pdf-lib'\nimport { mapToPoints } from './coordinate-mapper'\nimport type { FieldPlacement, PdfPageDimensions, SignerInfo } from '../types'\n\ninterface ModifyPdfInput {\n pdfBytes: Uint8Array\n fields: FieldPlacement[]\n signer: SignerInfo\n signatureDataUrl: string\n pageDimensions: PdfPageDimensions[]\n dateText?: string\n}\n\nfunction fullNameFromSigner(signer: SignerInfo): string {\n return [signer.firstName, signer.lastName].filter(Boolean).join(' ').trim()\n}\n\nfunction dataUrlToBytes(dataUrl: string): Uint8Array {\n const base64 = dataUrl.split(',')[1]\n if (!base64) throw new Error('Invalid signature data URL')\n const binary =\n typeof atob === 'function'\n ? atob(base64)\n : Buffer.from(base64, 'base64').toString('binary')\n const bytes = new Uint8Array(binary.length)\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index)\n return bytes\n}\n\nexport async function modifyPdf({\n pdfBytes,\n fields,\n signer,\n signatureDataUrl,\n pageDimensions,\n dateText,\n}: ModifyPdfInput): Promise<Uint8Array> {\n const pdfDocument = await PDFDocument.load(pdfBytes)\n pdfDocument.registerFontkit(fontkit)\n const pages = pdfDocument.getPages()\n const helveticaFont = await pdfDocument.embedFont(StandardFonts.Helvetica)\n\n const signatureImageBytes = signatureDataUrl ? dataUrlToBytes(signatureDataUrl) : null\n const signatureImage = signatureImageBytes ? await pdfDocument.embedPng(signatureImageBytes) : null\n const resolvedDateText = dateText || new Date().toLocaleDateString()\n\n for (const field of fields) {\n const page = pages[field.pageIndex]\n const pageDimension = pageDimensions.find((entry) => entry.pageIndex === field.pageIndex)\n if (!page || !pageDimension) continue\n\n const pointRect = mapToPoints(field, pageDimension)\n if (field.type === 'signature' && signatureImage) {\n page.drawImage(signatureImage, {\n x: pointRect.x,\n y: pointRect.y,\n width: pointRect.width,\n height: pointRect.height,\n })\n continue\n }\n\n const textValue =\n field.type === 'fullName'\n ? fullNameFromSigner(signer)\n : field.type === 'title'\n ? signer.title\n : field.type === 'date'\n ? resolvedDateText\n : ''\n if (!textValue) continue\n\n const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6))\n page.drawText(textValue, {\n x: pointRect.x + 2,\n y: pointRect.y + Math.max(0, (pointRect.height - textSize) / 2),\n size: textSize,\n font: helveticaFont,\n })\n }\n\n return pdfDocument.save()\n}\n","export async function sha256(data: Uint8Array): Promise<string> {\n const dataBuffer = new Uint8Array(data.byteLength)\n dataBuffer.set(data)\n const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer)\n return Array.from(new Uint8Array(hashBuffer))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n","export { PdfViewer } from './components/pdf-viewer'\nexport { FieldOverlay } from './components/field-overlay'\nexport { SignatureField } from './components/signature-field'\nexport { FieldPalette } from './components/field-palette'\nexport { SignerDetailsPanel } from './components/signer-details-panel'\nexport { SignaturePreview } from './components/signature-preview'\nexport { SignaturePad } from './components/signature-pad'\nexport { SigningComplete } from './components/signing-complete'\n\nexport { usePdfDocument } from './hooks/use-pdf-document'\nexport { useFieldPlacement } from './hooks/use-field-placement'\nexport { useSignatureRenderer } from './hooks/use-signature-renderer'\n\nexport { modifyPdf } from './lib/pdf-modifier'\nexport { mapToPoints, mapFromPoints } from './lib/coordinate-mapper'\nexport { loadSignatureFont, SIGNATURE_FONTS } from './lib/signature-fonts'\nexport { sha256 } from './lib/hash'\n\nexport type {\n FieldPlacement,\n FieldType,\n SignerInfo,\n SignatureStyle,\n SigningResult,\n PdfPageDimensions,\n SignatureFieldPreview,\n} from './types'\n\nexport const defaults = {\n SIGNATURE_FONTS: ['Dancing Script', 'Great Vibes', 'Sacramento', 'Alex Brush'],\n DEFAULT_FIELD_WIDTH_PERCENT: 25,\n DEFAULT_FIELD_HEIGHT_PERCENT: 5,\n} as const\n"]}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@drvillo/react-browser-e-signing",
3
+ "version": "0.1.0",
4
+ "description": "React PDF e-signing in the browser — fields, handwriting, pdf-lib",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/drvillo/react-browser-e-signing.git"
8
+ },
9
+ "homepage": "https://github.com/drvillo/react-browser-e-signing#readme",
10
+ "bugs": "https://github.com/drvillo/react-browser-e-signing/issues",
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "postinstall": "pnpm run install:browsers",
28
+ "install:browsers": "playwright install chromium",
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "demo": "vite",
32
+ "demo:build": "vite build",
33
+ "test": "pnpm run test:unit && pnpm run test:browser",
34
+ "test:unit": "vitest run --config vitest.config.ts",
35
+ "test:browser": "vitest run --config vitest.browser.config.ts",
36
+ "typecheck": "tsc --noEmit",
37
+ "prepublishOnly": "npm run build",
38
+ "publish:npm": "npm publish"
39
+ },
40
+ "keywords": [
41
+ "e-signature",
42
+ "pdf",
43
+ "sign",
44
+ "browser",
45
+ "react"
46
+ ],
47
+ "author": "",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "@pdf-lib/fontkit": "^1.1.1",
51
+ "pdf-lib": "^1.17.1",
52
+ "react-pdf": "^9.0.0",
53
+ "signature_pad": "^5.0.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "^18.0.0 || ^19.0.0",
57
+ "react-dom": "^18.0.0 || ^19.0.0"
58
+ },
59
+ "devDependencies": {
60
+ "@tailwindcss/vite": "^4.2.2",
61
+ "@testing-library/react": "^16.3.0",
62
+ "@types/node": "^24.6.0",
63
+ "@types/react": "^19.1.13",
64
+ "@types/react-dom": "^19.1.9",
65
+ "@vitejs/plugin-react": "^5.0.2",
66
+ "@vitest/browser-playwright": "^4.0.0",
67
+ "happy-dom": "^20.0.0",
68
+ "playwright": "^1.55.0",
69
+ "react": "^19.1.1",
70
+ "react-dom": "^19.1.1",
71
+ "tailwindcss": "^4.1.13",
72
+ "tsup": "^8.5.0",
73
+ "typescript": "^5.9.2",
74
+ "vite": "^7.1.5",
75
+ "vitest": "^4.0.0"
76
+ }
77
+ }