mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
Render signature to pdf
This commit is contained in:
parent
c94ee388fc
commit
cfd00b2c71
@ -1623,6 +1623,7 @@
|
|||||||
"add": "Add",
|
"add": "Add",
|
||||||
"saved": "Saved Signatures",
|
"saved": "Saved Signatures",
|
||||||
"save": "Save Signature",
|
"save": "Save Signature",
|
||||||
|
"applySignatures": "Apply Signatures",
|
||||||
"personalSigs": "Personal Signatures",
|
"personalSigs": "Personal Signatures",
|
||||||
"sharedSigs": "Shared Signatures",
|
"sharedSigs": "Shared Signatures",
|
||||||
"noSavedSigs": "No saved signatures found",
|
"noSavedSigs": "No saved signatures found",
|
||||||
|
@ -91,11 +91,9 @@ const SignSettings = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle drawing mode activation
|
// Handle signature mode deactivation when switching types
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (parameters.signatureType === 'draw' && onActivateDrawMode) {
|
if (parameters.signatureType !== 'text' && onDeactivateSignature) {
|
||||||
onActivateDrawMode();
|
|
||||||
} else if (parameters.signatureType !== 'draw' && onDeactivateSignature) {
|
|
||||||
onDeactivateSignature();
|
onDeactivateSignature();
|
||||||
}
|
}
|
||||||
}, [parameters.signatureType]);
|
}, [parameters.signatureType]);
|
||||||
@ -140,19 +138,14 @@ const SignSettings = ({
|
|||||||
}
|
}
|
||||||
}, [parameters.signatureType, parameters.signatureData, imageSignatureData]);
|
}, [parameters.signatureType, parameters.signatureData, imageSignatureData]);
|
||||||
|
|
||||||
// Update draw settings when color or pen size changes
|
// Draw settings are no longer needed since draw mode is removed
|
||||||
useEffect(() => {
|
|
||||||
if (parameters.signatureType === 'draw' && onUpdateDrawSettings) {
|
|
||||||
onUpdateDrawSettings(selectedColor, penSize);
|
|
||||||
}
|
|
||||||
}, [selectedColor, penSize, parameters.signatureType]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{/* Signature Type Selection */}
|
{/* Signature Type Selection */}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={parameters.signatureType}
|
value={parameters.signatureType}
|
||||||
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')}
|
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'canvas')}
|
||||||
>
|
>
|
||||||
<Tabs.List grow>
|
<Tabs.List grow>
|
||||||
<Tabs.Tab value="canvas" style={{ fontSize: '0.8rem' }}>
|
<Tabs.Tab value="canvas" style={{ fontSize: '0.8rem' }}>
|
||||||
@ -164,9 +157,6 @@ const SignSettings = ({
|
|||||||
<Tabs.Tab value="text" style={{ fontSize: '0.8rem' }}>
|
<Tabs.Tab value="text" style={{ fontSize: '0.8rem' }}>
|
||||||
{t('sign.type.text', 'Text')}
|
{t('sign.type.text', 'Text')}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="draw" style={{ fontSize: '0.8rem' }}>
|
|
||||||
{t('sign.type.draw', 'Draw')}
|
|
||||||
</Tabs.Tab>
|
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@ -232,66 +222,15 @@ const SignSettings = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Direct PDF Drawing */}
|
|
||||||
{parameters.signatureType === 'draw' && (
|
|
||||||
<Paper withBorder p="md">
|
|
||||||
<Stack gap="md">
|
|
||||||
<Text fw={500}>Direct PDF Drawing</Text>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
Draw signatures and annotations directly on the PDF document.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* Drawing Controls */}
|
|
||||||
<Group gap="md" align="flex-end">
|
|
||||||
{/* Color Picker */}
|
|
||||||
<div>
|
|
||||||
<Text size="sm" fw={500} mb="xs">Color</Text>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
backgroundColor: selectedColor,
|
|
||||||
border: '1px solid #ccc',
|
|
||||||
borderRadius: 0,
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={() => setIsColorPickerOpen(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Pen Size */}
|
|
||||||
<div style={{ flexGrow: 1, maxWidth: '200px' }}>
|
|
||||||
<Text size="sm" fw={500} mb="xs">Pen Size</Text>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={penSize}
|
|
||||||
onChange={(e) => {
|
|
||||||
const size = parseInt(e.target.value);
|
|
||||||
if (!isNaN(size) && size >= 1 && size <= 200) {
|
|
||||||
setPenSize(size);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
min={1}
|
|
||||||
max={200}
|
|
||||||
disabled={disabled}
|
|
||||||
style={{ width: '100%', padding: '4px 8px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Instructions for placing signature */}
|
{/* Instructions for placing signature */}
|
||||||
{(parameters.signatureType === 'canvas' || parameters.signatureType === 'image' || parameters.signatureType === 'text') && (
|
<Alert color="blue" title={t('sign.instructions.title', 'How to add signature')}>
|
||||||
<Alert color="blue" title={t('sign.instructions.title', 'How to add signature')}>
|
<Text size="sm">
|
||||||
<Text size="sm">
|
{parameters.signatureType === 'canvas' && 'After drawing your signature in the canvas above, click "Update and Place" then click anywhere on the PDF to place it.'}
|
||||||
{parameters.signatureType === 'canvas' && 'After drawing your signature in the canvas above, click "Update and Place" then click anywhere on the PDF to place it.'}
|
{parameters.signatureType === 'image' && 'After uploading your signature image above, click anywhere on the PDF to place it.'}
|
||||||
{parameters.signatureType === 'image' && 'After uploading your signature image above, click anywhere on the PDF to place it.'}
|
{parameters.signatureType === 'text' && 'After entering your name above, click anywhere on the PDF to place your signature.'}
|
||||||
{parameters.signatureType === 'text' && 'After entering your name above, click anywhere on the PDF to place your signature.'}
|
</Text>
|
||||||
</Text>
|
</Alert>
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Color Picker Modal */}
|
{/* Color Picker Modal */}
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
@ -301,7 +240,7 @@ const SignSettings = ({
|
|||||||
onColorChange={setSelectedColor}
|
onColorChange={setSelectedColor}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Apply Signatures Button */}
|
||||||
{onSave && (
|
{onSave && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
@ -309,7 +248,7 @@ const SignSettings = ({
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{t('save', 'Save')}
|
{t('sign.applySignatures', 'Apply Signatures')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export interface SignatureAPI {
|
|||||||
updateDrawSettings: (color: string, size: number) => void;
|
updateDrawSettings: (color: string, size: number) => void;
|
||||||
deactivateTools: () => void;
|
deactivateTools: () => void;
|
||||||
applySignatureFromParameters: (params: SignParameters) => void;
|
applySignatureFromParameters: (params: SignParameters) => void;
|
||||||
|
getPageAnnotations: (pageIndex: number) => Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignatureAPIBridgeProps {}
|
export interface SignatureAPIBridgeProps {}
|
||||||
@ -344,6 +345,26 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgePro
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPageAnnotations: async (pageIndex: number): Promise<any[]> => {
|
||||||
|
if (!annotationApi || !annotationApi.getPageAnnotations) {
|
||||||
|
console.warn('getPageAnnotations not available');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pageAnnotationsTask = annotationApi.getPageAnnotations({ pageIndex });
|
||||||
|
if (pageAnnotationsTask && pageAnnotationsTask.toPromise) {
|
||||||
|
const annotations = await pageAnnotationsTask.toPromise();
|
||||||
|
console.log(`Retrieved ${annotations?.length || 0} annotations from page ${pageIndex}`);
|
||||||
|
return annotations || [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error getting annotations for page ${pageIndex}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
}), [annotationApi, signatureConfig]);
|
}), [annotationApi, signatureConfig]);
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,11 +13,12 @@ import { useViewer } from "../contexts/ViewerContext";
|
|||||||
import { generateThumbnailWithMetadata } from "../utils/thumbnailUtils";
|
import { generateThumbnailWithMetadata } from "../utils/thumbnailUtils";
|
||||||
import { createNewStirlingFileStub, createStirlingFile, StirlingFileStub, StirlingFile, FileId, extractFiles } from "../types/fileContext";
|
import { createNewStirlingFileStub, createStirlingFile, StirlingFileStub, StirlingFile, FileId, extractFiles } from "../types/fileContext";
|
||||||
import { createProcessedFile } from "../contexts/file/fileActions";
|
import { createProcessedFile } from "../contexts/file/fileActions";
|
||||||
|
import { PDFDocument, PDFName, PDFDict, PDFArray, rgb } from 'pdf-lib';
|
||||||
|
|
||||||
const Sign = (props: BaseToolProps) => {
|
const Sign = (props: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setWorkbench } = useNavigation();
|
const { setWorkbench } = useNavigation();
|
||||||
const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings, undo, redo, isPlacementMode } = useSignature();
|
const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings, undo, redo, isPlacementMode, signatureApiRef, getImageData } = useSignature();
|
||||||
const { actions } = useFileActions();
|
const { actions } = useFileActions();
|
||||||
const { consumeFiles, selectors } = useFileContext();
|
const { consumeFiles, selectors } = useFileContext();
|
||||||
const { exportActions } = useViewer();
|
const { exportActions } = useViewer();
|
||||||
@ -53,12 +54,58 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
// Save signed files to the system - apply signatures using EmbedPDF and replace original
|
// Save signed files to the system - apply signatures using EmbedPDF and replace original
|
||||||
const handleSaveToSystem = useCallback(async () => {
|
const handleSaveToSystem = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
// Use EmbedPDF's saveAsCopy to apply signatures and get ArrayBuffer
|
// Step 1: Extract all annotations from EmbedPDF before export
|
||||||
|
const allAnnotations: Array<{pageIndex: number, annotations: any[]}> = [];
|
||||||
|
|
||||||
|
if (signatureApiRef?.current) {
|
||||||
|
console.log('Extracting annotations from all pages...');
|
||||||
|
|
||||||
|
// We need to know how many pages to check - let's assume we check first few pages
|
||||||
|
// In a real implementation, we'd get the page count from somewhere
|
||||||
|
for (let pageIndex = 0; pageIndex < 10; pageIndex++) {
|
||||||
|
try {
|
||||||
|
const pageAnnotations = await signatureApiRef.current.getPageAnnotations(pageIndex);
|
||||||
|
if (pageAnnotations && pageAnnotations.length > 0) {
|
||||||
|
allAnnotations.push({pageIndex, annotations: pageAnnotations});
|
||||||
|
console.log(`Found ${pageAnnotations.length} annotations on page ${pageIndex + 1}`);
|
||||||
|
console.log('Annotation data:', pageAnnotations);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Page doesn't exist or no annotations, continue
|
||||||
|
console.log(`No annotations on page ${pageIndex + 1}:`, e);
|
||||||
|
if (pageIndex > 2) break; // Stop after checking first few pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Total annotations found: ${allAnnotations.reduce((sum, page) => sum + page.annotations.length, 0)}`);
|
||||||
|
|
||||||
|
// Step 2: Delete annotations from EmbedPDF before export (they'll be rendered manually)
|
||||||
|
if (allAnnotations.length > 0 && signatureApiRef?.current) {
|
||||||
|
console.log('Deleting annotations from EmbedPDF before export...');
|
||||||
|
for (const pageData of allAnnotations) {
|
||||||
|
for (const annotation of pageData.annotations) {
|
||||||
|
try {
|
||||||
|
await signatureApiRef.current.deleteAnnotation(annotation.id, pageData.pageIndex);
|
||||||
|
console.log(`Deleted annotation ${annotation.id} from page ${pageData.pageIndex}`);
|
||||||
|
} catch (deleteError) {
|
||||||
|
console.warn(`Failed to delete annotation ${annotation.id}:`, deleteError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Use EmbedPDF's saveAsCopy to get the base PDF (now without annotations)
|
||||||
const pdfArrayBuffer = await exportActions.saveAsCopy();
|
const pdfArrayBuffer = await exportActions.saveAsCopy();
|
||||||
|
|
||||||
if (pdfArrayBuffer) {
|
if (pdfArrayBuffer) {
|
||||||
|
console.log(`EmbedPDF exported PDF size: ${pdfArrayBuffer.byteLength} bytes`);
|
||||||
|
|
||||||
|
// Try loading with more permissive PDF-lib options
|
||||||
|
console.log('Attempting to load PDF with PDF-lib...');
|
||||||
|
|
||||||
// Convert ArrayBuffer to File
|
// Convert ArrayBuffer to File
|
||||||
const blob = new Blob([pdfArrayBuffer], { type: 'application/pdf' });
|
let blob = new Blob([pdfArrayBuffer], { type: 'application/pdf' });
|
||||||
|
|
||||||
// Get the current file - try from base.selectedFiles first, then from all files
|
// Get the current file - try from base.selectedFiles first, then from all files
|
||||||
let originalFile = null;
|
let originalFile = null;
|
||||||
@ -80,7 +127,248 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signedFile = new File([blob], originalFile.name, { type: 'application/pdf' });
|
let signedFile = new File([blob], originalFile.name, { type: 'application/pdf' });
|
||||||
|
|
||||||
|
// Step 3: Manually render extracted annotations onto the PDF using PDF-lib
|
||||||
|
if (allAnnotations.length > 0) {
|
||||||
|
try {
|
||||||
|
console.log('Manually rendering annotations onto PDF...');
|
||||||
|
const pdfArrayBufferForFlattening = await signedFile.arrayBuffer();
|
||||||
|
|
||||||
|
// Try different loading options to handle problematic PDFs
|
||||||
|
let pdfDoc: PDFDocument;
|
||||||
|
try {
|
||||||
|
pdfDoc = await PDFDocument.load(pdfArrayBufferForFlattening, {
|
||||||
|
ignoreEncryption: true,
|
||||||
|
capNumbers: false,
|
||||||
|
throwOnInvalidObject: false
|
||||||
|
});
|
||||||
|
console.log('✓ PDF loaded successfully with standard options');
|
||||||
|
} catch (loadError) {
|
||||||
|
console.warn('Failed to load with standard options, trying createProxy...');
|
||||||
|
try {
|
||||||
|
// Create a fresh PDF and copy pages instead of modifying
|
||||||
|
pdfDoc = await PDFDocument.create();
|
||||||
|
const sourcePdf = await PDFDocument.load(pdfArrayBufferForFlattening, {
|
||||||
|
ignoreEncryption: true,
|
||||||
|
throwOnInvalidObject: false
|
||||||
|
});
|
||||||
|
const pageIndices = sourcePdf.getPages().map((_, i) => i);
|
||||||
|
const copiedPages = await pdfDoc.copyPages(sourcePdf, pageIndices);
|
||||||
|
copiedPages.forEach(page => pdfDoc.addPage(page));
|
||||||
|
console.log('✓ PDF loaded by creating new document and copying pages');
|
||||||
|
} catch (copyError) {
|
||||||
|
console.error('Failed to load PDF with any method:', copyError);
|
||||||
|
throw copyError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = pdfDoc.getPages();
|
||||||
|
|
||||||
|
console.log(`PDF has ${pages.length} pages`);
|
||||||
|
|
||||||
|
let totalRendered = 0;
|
||||||
|
const annotationsToDelete: Array<{pageIndex: number, id: string}> = [];
|
||||||
|
|
||||||
|
|
||||||
|
for (const pageData of allAnnotations) {
|
||||||
|
const { pageIndex, annotations } = pageData;
|
||||||
|
|
||||||
|
if (pageIndex < pages.length) {
|
||||||
|
const page = pages[pageIndex];
|
||||||
|
const { width: pageWidth, height: pageHeight } = page.getSize();
|
||||||
|
|
||||||
|
for (const annotation of annotations) {
|
||||||
|
try {
|
||||||
|
console.log('Processing annotation:', annotation);
|
||||||
|
console.log('Annotation keys:', Object.keys(annotation));
|
||||||
|
|
||||||
|
// EmbedPDF annotations might have different property names
|
||||||
|
// Let's check for various possible rectangle properties
|
||||||
|
const rect = annotation.rect || annotation.bounds || annotation.rectangle || annotation.position;
|
||||||
|
console.log('Rect found:', rect);
|
||||||
|
|
||||||
|
if (rect) {
|
||||||
|
// Extract original annotation position and size
|
||||||
|
const originalX = rect.origin?.x || rect.x || rect.left || 0;
|
||||||
|
const originalY = rect.origin?.y || rect.y || rect.top || 0;
|
||||||
|
const width = rect.size?.width || rect.width || 100;
|
||||||
|
const height = rect.size?.height || rect.height || 50;
|
||||||
|
|
||||||
|
// Convert EmbedPDF coordinates to PDF-lib coordinates
|
||||||
|
// EmbedPDF uses top-left origin, PDF-lib uses bottom-left origin
|
||||||
|
const pdfX = originalX;
|
||||||
|
const pdfY = pageHeight - originalY - height;
|
||||||
|
|
||||||
|
console.log('Signature positioning:', {
|
||||||
|
originalX,
|
||||||
|
originalY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pdfX,
|
||||||
|
pdfY,
|
||||||
|
pageWidth,
|
||||||
|
pageHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try to get annotation image data - check multiple possible properties
|
||||||
|
console.log('Looking for image data in:', {
|
||||||
|
imageData: !!annotation.imageData,
|
||||||
|
appearance: !!annotation.appearance,
|
||||||
|
stampData: !!annotation.stampData,
|
||||||
|
imageSrc: !!annotation.imageSrc,
|
||||||
|
contents: !!annotation.contents,
|
||||||
|
data: !!annotation.data
|
||||||
|
});
|
||||||
|
|
||||||
|
let imageDataUrl = annotation.imageData || annotation.appearance || annotation.stampData || annotation.imageSrc || annotation.contents || annotation.data;
|
||||||
|
|
||||||
|
// If no image data found directly, try to get it from our storage using annotation ID
|
||||||
|
if (!imageDataUrl && annotation.id) {
|
||||||
|
const storedImageData = getImageData(annotation.id);
|
||||||
|
if (storedImageData) {
|
||||||
|
console.log('Found stored image data for annotation:', annotation.id);
|
||||||
|
imageDataUrl = storedImageData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageDataUrl) {
|
||||||
|
console.log('Found image data:', typeof imageDataUrl, imageDataUrl?.substring?.(0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageDataUrl && typeof imageDataUrl === 'string' && imageDataUrl.startsWith('data:image')) {
|
||||||
|
try {
|
||||||
|
// Convert data URL to bytes
|
||||||
|
const base64Data = imageDataUrl.split(',')[1];
|
||||||
|
const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||||
|
console.log(`Image data size: ${imageBytes.length} bytes`);
|
||||||
|
|
||||||
|
// Embed image in PDF based on data URL type
|
||||||
|
let image;
|
||||||
|
if (imageDataUrl.includes('data:image/jpeg') || imageDataUrl.includes('data:image/jpg')) {
|
||||||
|
console.log('Embedding as JPEG');
|
||||||
|
image = await pdfDoc.embedJpg(imageBytes);
|
||||||
|
} else if (imageDataUrl.includes('data:image/png')) {
|
||||||
|
console.log('Embedding as PNG');
|
||||||
|
image = await pdfDoc.embedPng(imageBytes);
|
||||||
|
} else {
|
||||||
|
console.log('Unknown image type, trying PNG as fallback');
|
||||||
|
image = await pdfDoc.embedPng(imageBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageDims = image.size();
|
||||||
|
console.log(`Image dimensions: ${imageDims.width}x${imageDims.height}`);
|
||||||
|
|
||||||
|
// Draw image on page at annotation position
|
||||||
|
page.drawImage(image, {
|
||||||
|
x: pdfX,
|
||||||
|
y: pdfY,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
});
|
||||||
|
|
||||||
|
totalRendered++;
|
||||||
|
annotationsToDelete.push({pageIndex, id: annotation.id});
|
||||||
|
console.log(`✓ SUCCESS: Rendered image annotation at (${pdfX}, ${pdfY}) size (${width}x${height})`);
|
||||||
|
} catch (imageError) {
|
||||||
|
console.error('Failed to render image annotation:', imageError);
|
||||||
|
}
|
||||||
|
} else if (annotation.content || annotation.text) {
|
||||||
|
// Handle text annotations
|
||||||
|
page.drawText(annotation.content || annotation.text, {
|
||||||
|
x: pdfX,
|
||||||
|
y: pdfY + height - 12, // Adjust for text baseline
|
||||||
|
size: 12,
|
||||||
|
color: rgb(0, 0, 0)
|
||||||
|
});
|
||||||
|
totalRendered++;
|
||||||
|
annotationsToDelete.push({pageIndex, id: annotation.id});
|
||||||
|
console.log(`Rendered text annotation: "${annotation.content || annotation.text}"`);
|
||||||
|
} else if (annotation.type === 14 || annotation.type === 15) {
|
||||||
|
// Handle ink annotations (drawn signatures)
|
||||||
|
// Type 14 = INK, Type 15 = could be another drawing type
|
||||||
|
console.log('Processing ink annotation:', annotation);
|
||||||
|
|
||||||
|
// For ink annotations, we'll draw a placeholder rectangle since we can't easily reconstruct the ink paths
|
||||||
|
page.drawRectangle({
|
||||||
|
x: pdfX,
|
||||||
|
y: pdfY,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
borderColor: rgb(0, 0, 0),
|
||||||
|
borderWidth: 2,
|
||||||
|
color: rgb(0.9, 0.9, 0.9), // Light gray background
|
||||||
|
opacity: 0.8
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add text indicating this was a drawn signature
|
||||||
|
page.drawText('Drawn Signature', {
|
||||||
|
x: pdfX + 5,
|
||||||
|
y: pdfY + height / 2,
|
||||||
|
size: 10,
|
||||||
|
color: rgb(0, 0, 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
totalRendered++;
|
||||||
|
annotationsToDelete.push({pageIndex, id: annotation.id});
|
||||||
|
console.log(`Rendered ink annotation placeholder at (${pdfX}, ${pdfY}) size (${width}x${height})`);
|
||||||
|
} else {
|
||||||
|
// Handle other annotation types
|
||||||
|
console.log(`Unknown annotation type ${annotation.type}:`, annotation);
|
||||||
|
|
||||||
|
// Draw a placeholder for unknown types
|
||||||
|
page.drawRectangle({
|
||||||
|
x: pdfX,
|
||||||
|
y: pdfY,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
borderColor: rgb(1, 0, 0),
|
||||||
|
borderWidth: 2,
|
||||||
|
color: rgb(1, 1, 0), // Yellow background
|
||||||
|
opacity: 0.5
|
||||||
|
});
|
||||||
|
|
||||||
|
totalRendered++;
|
||||||
|
annotationsToDelete.push({pageIndex, id: annotation.id});
|
||||||
|
console.log(`Rendered unknown annotation type ${annotation.type} as placeholder`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (annotationError) {
|
||||||
|
console.warn('Failed to render annotation:', annotationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully rendered ${totalRendered} annotations`);
|
||||||
|
|
||||||
|
// Annotations were already deleted from EmbedPDF before export
|
||||||
|
|
||||||
|
// Save the PDF with rendered annotations
|
||||||
|
const flattenedPdfBytes = await pdfDoc.save({ useObjectStreams: false, addDefaultPage: false });
|
||||||
|
console.log(`Original PDF size: ${pdfArrayBufferForFlattening.byteLength} bytes`);
|
||||||
|
console.log(`Modified PDF size: ${flattenedPdfBytes.length} bytes`);
|
||||||
|
|
||||||
|
const arrayBuffer = new ArrayBuffer(flattenedPdfBytes.length);
|
||||||
|
const uint8View = new Uint8Array(arrayBuffer);
|
||||||
|
uint8View.set(flattenedPdfBytes);
|
||||||
|
signedFile = new File([arrayBuffer], originalFile.name, { type: 'application/pdf' });
|
||||||
|
console.log('Manual annotation rendering completed');
|
||||||
|
|
||||||
|
// Verify the modified PDF can be loaded
|
||||||
|
try {
|
||||||
|
const verifyDoc = await PDFDocument.load(flattenedPdfBytes);
|
||||||
|
console.log(`✓ Verification: Modified PDF has ${verifyDoc.getPages().length} pages and can be loaded`);
|
||||||
|
} catch (verifyError) {
|
||||||
|
console.error('❌ Verification: Modified PDF cannot be loaded:', verifyError);
|
||||||
|
}
|
||||||
|
} catch (renderError) {
|
||||||
|
console.error('Failed to manually render annotations:', renderError);
|
||||||
|
console.warn('Signatures may only show as annotations');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No annotations found to render');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate thumbnail and metadata for the signed file
|
// Generate thumbnail and metadata for the signed file
|
||||||
const thumbnailResult = await generateThumbnailWithMetadata(signedFile);
|
const thumbnailResult = await generateThumbnailWithMetadata(signedFile);
|
||||||
@ -105,19 +393,28 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
// Replace the original file with the signed version
|
// Replace the original file with the signed version
|
||||||
await consumeFiles(inputFileIds, [outputStirlingFile], [outputStub]);
|
await consumeFiles(inputFileIds, [outputStirlingFile], [outputStub]);
|
||||||
|
|
||||||
// Reactivate the signature mode that was active before save
|
console.log('✓ File replaced in context, new file ID:', outputStub.id);
|
||||||
|
|
||||||
|
// Force refresh the viewer to show the flattened PDF
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (activeModeRef.current === 'draw') {
|
// Navigate away from viewer and back to force reload
|
||||||
activateDrawMode();
|
setWorkbench('fileEditor');
|
||||||
} else if (activeModeRef.current === 'placement') {
|
setTimeout(() => {
|
||||||
handleSignaturePlacement();
|
setWorkbench('viewer');
|
||||||
}
|
|
||||||
|
// Reactivate the signature mode that was active before save
|
||||||
|
if (activeModeRef.current === 'draw') {
|
||||||
|
activateDrawMode();
|
||||||
|
} else if (activeModeRef.current === 'placement') {
|
||||||
|
handleSignaturePlacement();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving signed document:', error);
|
console.error('Error saving signed document:', error);
|
||||||
}
|
}
|
||||||
}, [exportActions, base.selectedFiles, selectors, consumeFiles]);
|
}, [exportActions, base.selectedFiles, selectors, consumeFiles, signatureApiRef, getImageData]);
|
||||||
|
|
||||||
const getSteps = () => {
|
const getSteps = () => {
|
||||||
const steps = [];
|
const steps = [];
|
||||||
|
Loading…
Reference in New Issue
Block a user