Initial set up

This commit is contained in:
Reece Browne
2025-09-20 01:59:04 +01:00
parent 3b87ca0c3c
commit a70472b172
13 changed files with 3030 additions and 1592 deletions

View File

@@ -0,0 +1,263 @@
import React, { useRef, useState } from 'react';
import { useTranslation } from "react-i18next";
import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert } from '@mantine/core';
import ButtonSelector from "../../shared/ButtonSelector";
import { SignParameters } from "../../../hooks/tools/sign/useSignParameters";
interface SignSettingsProps {
parameters: SignParameters;
onParameterChange: <K extends keyof SignParameters>(key: K, value: SignParameters[K]) => void;
disabled?: boolean;
onActivateDrawMode?: () => void;
onActivateSignaturePlacement?: () => void;
onDeactivateSignature?: () => void;
}
const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature }: SignSettingsProps) => {
const { t } = useTranslation();
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isDrawing, setIsDrawing] = useState(false);
const [signatureImage, setSignatureImage] = useState<File | null>(null);
// Drawing functions for signature canvas
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (!canvasRef.current || disabled) return;
setIsDrawing(true);
const rect = canvasRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
ctx.beginPath();
ctx.moveTo(x, y);
}
};
const draw = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (!isDrawing || !canvasRef.current || disabled) return;
const rect = canvasRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
ctx.lineTo(x, y);
ctx.stroke();
}
};
const stopDrawing = () => {
if (!isDrawing || disabled) return;
setIsDrawing(false);
// Save canvas as signature data
if (canvasRef.current) {
const dataURL = canvasRef.current.toDataURL('image/png');
console.log('Saving canvas signature data:', dataURL.substring(0, 50) + '...');
onParameterChange('signatureData', dataURL);
}
};
const clearCanvas = () => {
if (!canvasRef.current || disabled) return;
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
onParameterChange('signatureData', undefined);
}
};
// Handle signature image upload
const handleSignatureImageChange = (file: File | null) => {
console.log('Image file selected:', file);
if (file && !disabled) {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target?.result) {
console.log('Image loaded, saving to signatureData, length:', (e.target.result as string).length);
onParameterChange('signatureData', e.target.result as string);
}
};
reader.readAsDataURL(file);
setSignatureImage(file);
}
};
// Initialize canvas
React.useEffect(() => {
if (canvasRef.current && parameters.signatureType === 'draw') {
const ctx = canvasRef.current.getContext('2d');
if (ctx) {
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
}
}
}, [parameters.signatureType]);
return (
<Stack gap="md">
{/* Signature Type Selection */}
<div>
<Text size="sm" fw={500} mb="xs">
{t('sign.type.title', 'Signature Type')}
</Text>
<ButtonSelector
value={parameters.signatureType}
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw')}
options={[
{
value: 'draw',
label: t('sign.type.draw', 'Draw'),
},
{
value: 'image',
label: t('sign.type.image', 'Image'),
},
{
value: 'text',
label: t('sign.type.text', 'Text'),
},
]}
disabled={disabled}
/>
</div>
{/* Signature Creation based on type */}
{parameters.signatureType === 'draw' && (
<Paper withBorder p="md">
<Stack gap="sm">
<Group justify="space-between">
<Text fw={500}>{t('sign.draw.title', 'Draw your signature')}</Text>
<Button
variant="subtle"
color="red"
size="compact-sm"
onClick={clearCanvas}
disabled={disabled}
>
{t('sign.draw.clear', 'Clear')}
</Button>
</Group>
<canvas
ref={canvasRef}
width={400}
height={150}
style={{
border: '1px solid #ccc',
borderRadius: '4px',
cursor: disabled ? 'default' : 'crosshair',
backgroundColor: '#ffffff',
}}
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
onMouseLeave={stopDrawing}
/>
<Text size="sm" c="dimmed">
{t('sign.draw.hint', 'Click and drag to draw your signature')}
</Text>
</Stack>
</Paper>
)}
{parameters.signatureType === 'image' && (
<Stack gap="sm">
<FileInput
label={t('sign.image.label', 'Upload signature image')}
placeholder={t('sign.image.placeholder', 'Select image file')}
accept="image/*"
value={signatureImage}
onChange={(file) => {
console.log('FileInput onChange triggered with file:', file);
handleSignatureImageChange(file);
}}
disabled={disabled}
/>
<Text size="sm" c="dimmed">
{t('sign.image.hint', 'Upload a PNG or JPG image of your signature')}
</Text>
</Stack>
)}
{parameters.signatureType === 'text' && (
<Stack gap="sm">
<TextInput
label={t('sign.text.name', 'Signer Name')}
placeholder={t('sign.text.placeholder', 'Enter your full name')}
value={parameters.signerName || ''}
onChange={(e) => onParameterChange('signerName', e.target.value)}
disabled={disabled}
required
/>
</Stack>
)}
{/* Instructions for placing signature */}
<Alert color="blue" title={t('sign.instructions.title', 'How to add signature')}>
<Text size="sm">
{parameters.signatureType === 'draw' && t('sign.instructions.draw', 'Draw your signature above, then click "Draw Directly on PDF" to draw live, or "Place Canvas Signature" to place your drawn signature.')}
{parameters.signatureType === 'image' && t('sign.instructions.image', 'Upload your signature image above, then click "Activate Image Placement" to place it on the PDF.')}
{parameters.signatureType === 'text' && t('sign.instructions.text', 'Enter your name above, then click "Activate Text Signature" to place it on the PDF.')}
</Text>
<Group mt="sm" gap="sm">
{/* Universal activation button */}
{((parameters.signatureType === 'draw' && parameters.signatureData) ||
(parameters.signatureType === 'image' && parameters.signatureData) ||
(parameters.signatureType === 'text' && parameters.signerName)) && (
<Button
onClick={() => {
if (onActivateSignaturePlacement) {
onActivateSignaturePlacement();
}
}}
disabled={disabled}
>
{t('sign.activate', 'Activate Signature Placement')}
</Button>
)}
{/* Draw directly mode for draw type */}
{parameters.signatureType === 'draw' && (
<Button
variant="outline"
onClick={() => {
if (onActivateDrawMode) {
onActivateDrawMode();
}
}}
disabled={disabled}
>
{t('sign.activate.draw', 'Draw Directly on PDF')}
</Button>
)}
{/* Universal deactivate button */}
<Button
variant="subtle"
color="red"
onClick={() => {
if (onDeactivateSignature) {
onDeactivateSignature();
}
}}
disabled={disabled}
>
{t('sign.deactivate', 'Stop Placing Signatures')}
</Button>
</Group>
</Alert>
</Stack>
);
};
export default SignSettings;

View File

@@ -9,6 +9,8 @@ import { useViewer } from "../../contexts/ViewerContext";
import { LocalEmbedPDF } from './LocalEmbedPDF';
import { PdfViewerToolbar } from './PdfViewerToolbar';
import { ThumbnailSidebar } from './ThumbnailSidebar';
import { useNavigationState } from '../../contexts/NavigationContext';
import { useSignature } from '../../contexts/SignatureContext';
export interface EmbedPdfViewerProps {
sidebarsVisible: boolean;
@@ -33,6 +35,13 @@ const EmbedPdfViewerContent = ({
const zoomState = getZoomState();
const spreadState = getSpreadState();
// Check if we're in signature mode
const { selectedTool } = useNavigationState();
const isSignatureMode = selectedTool === 'sign';
// Get signature context
const { signatureApiRef } = useSignature();
// Get current file from FileContext
const { selectors } = useFileState();
@@ -178,6 +187,12 @@ const EmbedPdfViewerContent = ({
<LocalEmbedPDF
file={effectiveFile.file}
url={effectiveFile.url}
enableSignature={isSignatureMode}
signatureApiRef={signatureApiRef as React.RefObject<any>}
onSignatureAdded={(annotation) => {
console.log('Signature added:', annotation);
// Future: Handle signature completion
}}
/>
</Box>
</>

View File

@@ -18,6 +18,11 @@ import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { Rotation } from '@embedpdf/models';
// Import annotation plugins
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
import { AnnotationLayer, AnnotationPluginPackage, useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
import { PdfAnnotationSubtype } from '@embedpdf/models';
import { CustomSearchLayer } from './CustomSearchLayer';
import { ZoomAPIBridge } from './ZoomAPIBridge';
import ToolLoadingFallback from '../tools/ToolLoadingFallback';
@@ -29,13 +34,17 @@ import { SpreadAPIBridge } from './SpreadAPIBridge';
import { SearchAPIBridge } from './SearchAPIBridge';
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
import { RotateAPIBridge } from './RotateAPIBridge';
import { SignatureAPIBridge, SignatureAPI } from './SignatureAPIBridge';
interface LocalEmbedPDFProps {
file?: File | Blob;
url?: string | null;
enableSignature?: boolean;
onSignatureAdded?: (annotation: any) => void;
signatureApiRef?: React.RefObject<SignatureAPI>;
}
export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
export function LocalEmbedPDF({ file, url, enableSignature = false, onSignatureAdded, signatureApiRef }: LocalEmbedPDFProps) {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
// Convert File to URL if needed
@@ -78,6 +87,17 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
// Register selection plugin (depends on InteractionManager)
createPluginRegistration(SelectionPluginPackage),
// Register history plugin for undo/redo (recommended for annotations)
...(enableSignature ? [createPluginRegistration(HistoryPluginPackage)] : []),
// Register annotation plugin (depends on InteractionManager, Selection, History)
...(enableSignature ? [createPluginRegistration(AnnotationPluginPackage, {
annotationAuthor: 'Digital Signature',
autoCommit: true,
deactivateToolAfterCreate: false,
selectAfterCreate: true,
})] : []),
// Register pan plugin (depends on Viewport, InteractionManager)
createPluginRegistration(PanPluginPackage, {
defaultMode: 'mobile', // Try mobile mode which might be more permissive
@@ -161,7 +181,52 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
minHeight: 0,
minWidth: 0
}}>
<EmbedPDF engine={engine} plugins={plugins}>
<EmbedPDF
engine={engine}
plugins={plugins}
onInitialized={enableSignature ? async (registry) => {
const annotationPlugin = registry.getPlugin('annotation');
if (!annotationPlugin || !annotationPlugin.provides) return;
const annotationApi = annotationPlugin.provides();
if (!annotationApi) return;
// Add custom signature stamp tool for image signatures
annotationApi.addTool({
id: 'signatureStamp',
name: 'Digital Signature',
interaction: { exclusive: false, cursor: 'copy' },
matchScore: () => 0,
defaults: {
type: PdfAnnotationSubtype.STAMP,
// Image will be set dynamically when signature is created
},
});
// Add custom ink signature tool for drawn signatures
annotationApi.addTool({
id: 'signatureInk',
name: 'Signature Draw',
interaction: { exclusive: true, cursor: 'crosshair' },
matchScore: () => 0,
defaults: {
type: PdfAnnotationSubtype.INK,
color: '#000000',
opacity: 1.0,
borderWidth: 2,
},
});
// Listen for annotation events to notify parent component
if (onSignatureAdded) {
annotationApi.onAnnotationEvent((event: any) => {
if (event.type === 'create' && event.committed) {
onSignatureAdded(event.annotation);
}
});
}
} : undefined}
>
<ZoomAPIBridge />
<ScrollAPIBridge />
<SelectionAPIBridge />
@@ -170,6 +235,7 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
<SearchAPIBridge />
<ThumbnailAPIBridge />
<RotateAPIBridge />
{enableSignature && <SignatureAPIBridge ref={signatureApiRef} />}
<GlobalPointerProvider>
<Viewport
style={{
@@ -213,6 +279,18 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
{/* Selection layer for text interaction */}
<SelectionLayer pageIndex={pageIndex} scale={scale} />
{/* Annotation layer for signatures (only when enabled) */}
{enableSignature && (
<AnnotationLayer
pageIndex={pageIndex}
scale={scale}
pageWidth={width}
pageHeight={height}
rotation={rotation || 0}
selectionOutlineColor="#007ACC"
/>
)}
</div>
</PagePointerProvider>
</Rotate>

View File

@@ -0,0 +1,315 @@
import React, { useEffect, useMemo, useState } from 'react';
import { createPluginRegistration } from '@embedpdf/core';
import { EmbedPDF } from '@embedpdf/core/react';
import { usePdfiumEngine } from '@embedpdf/engines/react';
// Import the essential plugins
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
import { SelectionLayer, SelectionPluginPackage } from '@embedpdf/plugin-selection/react';
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
import { PanPluginPackage } from '@embedpdf/plugin-pan/react';
import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { Rotation } from '@embedpdf/models';
// Import annotation plugins
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
import { AnnotationLayer, AnnotationPluginPackage, useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
import { PdfAnnotationSubtype } from '@embedpdf/models';
import { CustomSearchLayer } from './CustomSearchLayer';
import { ZoomAPIBridge } from './ZoomAPIBridge';
import ToolLoadingFallback from '../tools/ToolLoadingFallback';
import { Center, Stack, Text } from '@mantine/core';
import { ScrollAPIBridge } from './ScrollAPIBridge';
import { SelectionAPIBridge } from './SelectionAPIBridge';
import { PanAPIBridge } from './PanAPIBridge';
import { SpreadAPIBridge } from './SpreadAPIBridge';
import { SearchAPIBridge } from './SearchAPIBridge';
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
import { RotateAPIBridge } from './RotateAPIBridge';
interface LocalEmbedPDFWithAnnotationsProps {
file?: File | Blob;
url?: string | null;
onAnnotationChange?: (annotations: any[]) => void;
}
export function LocalEmbedPDFWithAnnotations({
file,
url,
onAnnotationChange
}: LocalEmbedPDFWithAnnotationsProps) {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
// Convert File to URL if needed
useEffect(() => {
if (file) {
const objectUrl = URL.createObjectURL(file);
setPdfUrl(objectUrl);
return () => URL.revokeObjectURL(objectUrl);
} else if (url) {
setPdfUrl(url);
}
}, [file, url]);
// Create plugins configuration with annotation support
const plugins = useMemo(() => {
if (!pdfUrl) return [];
return [
createPluginRegistration(LoaderPluginPackage, {
loadingOptions: {
type: 'url',
pdfFile: {
id: 'stirling-pdf-signing-viewer',
url: pdfUrl,
},
},
}),
createPluginRegistration(ViewportPluginPackage, {
viewportGap: 10,
}),
createPluginRegistration(ScrollPluginPackage, {
strategy: ScrollStrategy.Vertical,
initialPage: 0,
}),
createPluginRegistration(RenderPluginPackage),
// Register interaction manager (required for annotations)
createPluginRegistration(InteractionManagerPluginPackage),
// Register selection plugin (depends on InteractionManager)
createPluginRegistration(SelectionPluginPackage),
// Register history plugin for undo/redo (recommended for annotations)
createPluginRegistration(HistoryPluginPackage),
// Register annotation plugin (depends on InteractionManager, Selection, History)
createPluginRegistration(AnnotationPluginPackage, {
annotationAuthor: 'Digital Signature',
autoCommit: true,
deactivateToolAfterCreate: false,
selectAfterCreate: true,
}),
// Register pan plugin
createPluginRegistration(PanPluginPackage, {
defaultMode: 'mobile',
}),
// Register zoom plugin
createPluginRegistration(ZoomPluginPackage, {
defaultZoomLevel: 1.4,
minZoom: 0.2,
maxZoom: 3.0,
}),
// Register tiling plugin
createPluginRegistration(TilingPluginPackage, {
tileSize: 768,
overlapPx: 5,
extraRings: 1,
}),
// Register spread plugin
createPluginRegistration(SpreadPluginPackage, {
defaultSpreadMode: SpreadMode.None,
}),
// Register search plugin
createPluginRegistration(SearchPluginPackage),
// Register thumbnail plugin
createPluginRegistration(ThumbnailPluginPackage),
// Register rotate plugin
createPluginRegistration(RotatePluginPackage, {
defaultRotation: Rotation.Degree0,
}),
];
}, [pdfUrl]);
// Initialize the engine
const { engine, isLoading, error } = usePdfiumEngine();
// Early return if no file or URL provided
if (!file && !url) {
return (
<Center h="100%" w="100%">
<Stack align="center" gap="md">
<div style={{ fontSize: '24px' }}>📄</div>
<Text c="dimmed" size="sm">
No PDF provided
</Text>
</Stack>
</Center>
);
}
if (isLoading || !engine || !pdfUrl) {
return <ToolLoadingFallback toolName="PDF Engine" />;
}
if (error) {
return (
<Center h="100%" w="100%">
<Stack align="center" gap="md">
<div style={{ fontSize: '24px' }}></div>
<Text c="red" size="sm" style={{ textAlign: 'center' }}>
Error loading PDF engine: {error.message}
</Text>
</Stack>
</Center>
);
}
return (
<div style={{
height: '100%',
width: '100%',
position: 'relative',
overflow: 'hidden',
flex: 1,
minHeight: 0,
minWidth: 0
}}>
<EmbedPDF
engine={engine}
plugins={plugins}
onInitialized={async (registry) => {
const annotationPlugin = registry.getPlugin('annotation');
if (!annotationPlugin || !annotationPlugin.provides) return;
const annotationApi = annotationPlugin.provides();
if (!annotationApi) return;
// Add custom signature stamp tool
annotationApi.addTool({
id: 'signatureStamp',
name: 'Digital Signature',
interaction: { exclusive: false, cursor: 'copy' },
matchScore: () => 0,
defaults: {
type: PdfAnnotationSubtype.STAMP,
// Will be set dynamically when user creates signature
},
});
// Add custom ink signature tool
annotationApi.addTool({
id: 'signatureInk',
name: 'Signature Draw',
interaction: { exclusive: true, cursor: 'crosshair' },
matchScore: () => 0,
defaults: {
type: PdfAnnotationSubtype.INK,
color: '#000000',
opacity: 1.0,
borderWidth: 2,
},
});
// Listen for annotation events to notify parent
if (onAnnotationChange) {
annotationApi.onAnnotationEvent((event: any) => {
if (event.committed) {
// Get all annotations and notify parent
// This is a simplified approach - in reality you'd need to get all annotations
onAnnotationChange([event.annotation]);
}
});
}
}}
>
<ZoomAPIBridge />
<ScrollAPIBridge />
<SelectionAPIBridge />
<PanAPIBridge />
<SpreadAPIBridge />
<SearchAPIBridge />
<ThumbnailAPIBridge />
<RotateAPIBridge />
<GlobalPointerProvider>
<Viewport
style={{
backgroundColor: 'var(--bg-surface)',
height: '100%',
width: '100%',
maxHeight: '100%',
maxWidth: '100%',
overflow: 'auto',
position: 'relative',
flex: 1,
minHeight: 0,
minWidth: 0,
contain: 'strict',
}}
>
<Scroller
renderPage={({ width, height, pageIndex, scale, rotation }: {
width: number;
height: number;
pageIndex: number;
scale: number;
rotation?: number;
}) => (
<Rotate pageSize={{ width, height }}>
<PagePointerProvider {...{
pageWidth: width,
pageHeight: height,
pageIndex,
scale,
rotation: rotation || 0
}}>
<div
style={{
width,
height,
position: 'relative',
userSelect: 'none',
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none'
}}
draggable={false}
onDragStart={(e) => e.preventDefault()}
onDrop={(e) => e.preventDefault()}
onDragOver={(e) => e.preventDefault()}
>
{/* High-resolution tile layer */}
<TilingLayer pageIndex={pageIndex} scale={scale} />
{/* Search highlight layer */}
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
{/* Selection layer for text interaction */}
<SelectionLayer pageIndex={pageIndex} scale={scale} />
{/* Annotation layer for signatures */}
<AnnotationLayer
pageIndex={pageIndex}
scale={scale}
pageWidth={width}
pageHeight={height}
rotation={rotation || 0}
selectionOutlineColor="#007ACC"
/>
</div>
</PagePointerProvider>
</Rotate>
)}
/>
</Viewport>
</GlobalPointerProvider>
</EmbedPDF>
</div>
);
}

View File

@@ -0,0 +1,174 @@
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;
deactivateTools: () => void;
applySignatureFromParameters: (params: SignParameters) => void;
}
export interface SignatureAPIBridgeProps {}
export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgeProps>((props, ref) => {
const { provides: annotationApi } = useAnnotationCapability();
const { signatureConfig } = useSignature();
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;
// Activate the built-in ink tool for drawing
annotationApi.setActiveTool('ink');
},
activateSignaturePlacementMode: () => {
console.log('SignatureAPIBridge.activateSignaturePlacementMode called');
console.log('annotationApi:', !!annotationApi, 'signatureConfig:', !!signatureConfig);
if (!annotationApi || !signatureConfig) return;
try {
console.log('Signature type:', signatureConfig.signatureType);
if (signatureConfig.signatureType === 'text' && signatureConfig.signerName) {
console.log('Activating freetext tool');
// Use freetext tool for text signatures
annotationApi.setActiveTool('freetext');
const activeTool = annotationApi.getActiveTool();
console.log('Freetext tool activated:', activeTool);
if (activeTool && activeTool.id === 'freetext') {
annotationApi.setToolDefaults('freetext', {
contents: signatureConfig.signerName,
fontSize: 16,
fontFamily: PdfStandardFont.Helvetica,
fontColor: '#000000',
});
}
} 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);
}
},
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(),
});
}
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(),
});
}
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';