Stirling-PDF/frontend/src/components/viewer/SignatureAPIBridge.tsx

298 lines
11 KiB
TypeScript
Raw Normal View History

2025-09-20 01:59:04 +01:00
import React, { useImperativeHandle, forwardRef, useEffect } from 'react';
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
import { PdfAnnotationSubtype, PdfStandardFont, PdfTextAlignment, PdfVerticalAlignment, uuidV4 } from '@embedpdf/models';
import { SignParameters } from '../../hooks/tools/sign/useSignParameters';
import { useSignature } from '../../contexts/SignatureContext';
export interface SignatureAPI {
addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => void;
addTextSignature: (text: string, x: number, y: number, pageIndex: number) => void;
activateDrawMode: () => void;
activateSignaturePlacementMode: () => void;
2025-09-23 18:16:21 +01:00
activateDeleteMode: () => void;
deleteAnnotation: (annotationId: string, pageIndex: number) => void;
updateDrawSettings: (color: string, size: number) => void;
2025-09-20 01:59:04 +01:00
deactivateTools: () => void;
applySignatureFromParameters: (params: SignParameters) => void;
}
export interface SignatureAPIBridgeProps {}
export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgeProps>((props, ref) => {
const { provides: annotationApi } = useAnnotationCapability();
const { signatureConfig } = useSignature();
2025-09-23 18:16:21 +01:00
// Enable keyboard deletion of selected annotations
useEffect(() => {
if (!annotationApi) return;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Delete' || event.key === 'Backspace') {
const selectedAnnotation = annotationApi.getSelectedAnnotation?.();
if (selectedAnnotation) {
const annotation = selectedAnnotation as any;
const pageIndex = annotation.object?.pageIndex || 0;
const id = annotation.object?.id;
if (id) {
annotationApi.deleteAnnotation(pageIndex, id);
}
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [annotationApi]);
2025-09-20 01:59:04 +01:00
useImperativeHandle(ref, () => ({
addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => {
if (!annotationApi) return;
// Create image stamp annotation
annotationApi.createAnnotation(pageIndex, {
type: PdfAnnotationSubtype.STAMP,
rect: {
origin: { x, y },
size: { width, height }
},
author: 'Digital Signature',
subject: 'Digital Signature',
pageIndex: pageIndex,
id: uuidV4(),
created: new Date(),
});
},
addTextSignature: (text: string, x: number, y: number, pageIndex: number) => {
if (!annotationApi) return;
// Create text annotation for signature
annotationApi.createAnnotation(pageIndex, {
type: PdfAnnotationSubtype.FREETEXT,
rect: {
origin: { x, y },
size: { width: 200, height: 50 }
},
contents: text,
author: 'Digital Signature',
fontSize: 16,
fontColor: '#000000',
fontFamily: PdfStandardFont.Helvetica,
textAlign: PdfTextAlignment.Left,
verticalAlign: PdfVerticalAlignment.Top,
opacity: 1,
pageIndex: pageIndex,
id: uuidV4(),
created: new Date(),
});
},
activateDrawMode: () => {
if (!annotationApi) return;
2025-09-20 01:59:04 +01:00
// Activate the built-in ink tool for drawing
annotationApi.setActiveTool('ink');
// Set default ink tool properties (black color, 2px width)
const activeTool = annotationApi.getActiveTool();
if (activeTool && activeTool.id === 'ink') {
annotationApi.setToolDefaults('ink', {
color: '#000000',
2025-09-23 12:24:58 +01:00
thickness: 2,
lineWidth: 2,
strokeWidth: 2,
width: 2
});
}
2025-09-20 01:59:04 +01:00
},
activateSignaturePlacementMode: () => {
console.log('SignatureAPIBridge.activateSignaturePlacementMode called');
console.log('annotationApi:', !!annotationApi, 'signatureConfig:', !!signatureConfig);
if (!annotationApi || !signatureConfig) return;
try {
console.log('Signature type:', signatureConfig.signatureType);
2025-09-23 12:24:58 +01:00
console.log('Font settings:', { fontFamily: signatureConfig.fontFamily, fontSize: signatureConfig.fontSize });
2025-09-20 01:59:04 +01:00
if (signatureConfig.signatureType === 'text' && signatureConfig.signerName) {
2025-09-23 12:24:58 +01:00
console.log('Trying different text tool names');
// Try different tool names for text annotations
const textToolNames = ['freetext', 'text', 'textbox', 'annotation-text'];
let activatedTool = null;
for (const toolName of textToolNames) {
console.log(`Trying tool: ${toolName}`);
annotationApi.setActiveTool(toolName);
const tool = annotationApi.getActiveTool();
console.log(`Tool result for ${toolName}:`, tool);
if (tool && tool.id === toolName) {
activatedTool = tool;
console.log(`Successfully activated ${toolName}`);
annotationApi.setToolDefaults(toolName, {
contents: signatureConfig.signerName,
fontSize: signatureConfig.fontSize || 16,
fontFamily: signatureConfig.fontFamily === 'Times-Roman' ? PdfStandardFont.Times_Roman :
signatureConfig.fontFamily === 'Courier' ? PdfStandardFont.Courier :
PdfStandardFont.Helvetica,
fontColor: '#000000',
});
break;
}
}
if (!activatedTool) {
console.log('No text tool could be activated, trying stamp as fallback');
// Fallback: create a simple text image as stamp
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (ctx) {
const fontSize = signatureConfig.fontSize || 16;
const fontFamily = signatureConfig.fontFamily || 'Helvetica';
canvas.width = Math.max(200, signatureConfig.signerName.length * fontSize * 0.6);
canvas.height = fontSize + 20;
ctx.fillStyle = '#000000';
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(signatureConfig.signerName, 10, canvas.height / 2);
const dataURL = canvas.toDataURL();
annotationApi.setActiveTool('stamp');
const stampTool = annotationApi.getActiveTool();
if (stampTool && stampTool.id === 'stamp') {
annotationApi.setToolDefaults('stamp', {
imageSrc: dataURL,
subject: `Text Signature - ${signatureConfig.signerName}`,
});
}
}
2025-09-20 01:59:04 +01:00
}
} else if (signatureConfig.signatureData) {
console.log('Activating stamp tool');
// Use stamp tool for image/canvas signatures
annotationApi.setActiveTool('stamp');
const activeTool = annotationApi.getActiveTool();
console.log('Stamp tool activated:', activeTool);
if (activeTool && activeTool.id === 'stamp') {
annotationApi.setToolDefaults('stamp', {
imageSrc: signatureConfig.signatureData,
subject: `Digital Signature - ${signatureConfig.reason || 'Document signing'}`,
});
}
}
} catch (error) {
console.error('Error activating signature tool:', error);
}
},
updateDrawSettings: (color: string, size: number) => {
2025-09-23 12:24:58 +01:00
if (!annotationApi) return;
2025-09-23 12:24:58 +01:00
// Always update ink tool defaults - use multiple property names for compatibility
annotationApi.setToolDefaults('ink', {
color: color,
2025-09-23 12:24:58 +01:00
thickness: size,
lineWidth: size,
strokeWidth: size,
width: size
});
2025-09-23 12:24:58 +01:00
// Force reactivate ink tool to ensure new settings take effect
annotationApi.setActiveTool(null); // Deactivate first
setTimeout(() => {
annotationApi.setActiveTool('ink'); // Reactivate with new settings
}, 50);
},
2025-09-20 01:59:04 +01:00
2025-09-23 18:16:21 +01:00
activateDeleteMode: () => {
if (!annotationApi) return;
// Activate selection tool to allow selecting and deleting annotations
// Users can click annotations to select them, then press Delete key or right-click to delete
annotationApi.setActiveTool('select');
},
deleteAnnotation: (annotationId: string, pageIndex: number) => {
if (!annotationApi) return;
// Delete specific annotation by ID
annotationApi.deleteAnnotation(pageIndex, annotationId);
},
2025-09-20 01:59:04 +01:00
deactivateTools: () => {
if (!annotationApi) return;
annotationApi.setActiveTool(null);
},
applySignatureFromParameters: (params: SignParameters) => {
if (!annotationApi || !params.signaturePosition) return;
const { x, y, width, height, page } = params.signaturePosition;
switch (params.signatureType) {
case 'image':
if (params.signatureData) {
annotationApi.createAnnotation(page, {
type: PdfAnnotationSubtype.STAMP,
rect: {
origin: { x, y },
size: { width, height }
},
author: 'Digital Signature',
subject: `Digital Signature - ${params.reason || 'Document signing'}`,
pageIndex: page,
id: uuidV4(),
created: new Date(),
});
2025-09-23 18:16:21 +01:00
// Switch to select mode after placing signature so it can be easily deleted
setTimeout(() => {
annotationApi.setActiveTool('select');
}, 100);
2025-09-20 01:59:04 +01:00
}
break;
case 'text':
if (params.signerName) {
annotationApi.createAnnotation(page, {
type: PdfAnnotationSubtype.FREETEXT,
rect: {
origin: { x, y },
size: { width, height }
},
contents: params.signerName,
author: 'Digital Signature',
fontSize: 16,
fontColor: '#000000',
fontFamily: PdfStandardFont.Helvetica,
textAlign: PdfTextAlignment.Left,
verticalAlign: PdfVerticalAlignment.Top,
opacity: 1,
pageIndex: page,
id: uuidV4(),
created: new Date(),
});
2025-09-23 18:16:21 +01:00
// Switch to select mode after placing signature so it can be easily deleted
setTimeout(() => {
annotationApi.setActiveTool('select');
}, 100);
2025-09-20 01:59:04 +01:00
}
break;
case 'draw':
// For draw mode, we activate the tool and let user draw
annotationApi.setActiveTool('ink');
break;
}
},
}), [annotationApi, signatureConfig]);
return null; // This is a bridge component with no UI
});
SignatureAPIBridge.displayName = 'SignatureAPIBridge';