diff --git a/frontend/src/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/components/annotation/shared/TextInputWithFont.tsx index b85511cd5..b7af60295 100644 --- a/frontend/src/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/components/annotation/shared/TextInputWithFont.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Stack, TextInput, Select, Combobox, useCombobox } from '@mantine/core'; +import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box } from '@mantine/core'; import { useTranslation } from 'react-i18next'; +import { ColorPicker } from './ColorPicker'; interface TextInputWithFontProps { text: string; @@ -9,6 +10,8 @@ interface TextInputWithFontProps { onFontSizeChange: (size: number) => void; fontFamily: string; onFontFamilyChange: (family: string) => void; + textColor?: string; + onTextColorChange?: (color: string) => void; disabled?: boolean; label?: string; placeholder?: string; @@ -21,6 +24,8 @@ export const TextInputWithFont: React.FC = ({ onFontSizeChange, fontFamily, onFontFamilyChange, + textColor = '#000000', + onTextColorChange, disabled = false, label, placeholder @@ -28,6 +33,7 @@ export const TextInputWithFont: React.FC = ({ const { t } = useTranslation(); const [fontSizeInput, setFontSizeInput] = useState(fontSize.toString()); const fontSizeCombobox = useCombobox(); + const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); // Sync font size input with prop changes useEffect(() => { @@ -42,7 +48,7 @@ export const TextInputWithFont: React.FC = ({ { value: 'Georgia', label: 'Georgia' }, ]; - const fontSizeOptions = ['8', '12', '16', '20', '24', '28', '32', '36', '40', '48']; + const fontSizeOptions = ['8', '12', '16', '20', '24', '28', '32', '36', '40', '48', '56', '64', '72', '80', '96', '112', '128', '144', '160', '176', '192', '200']; return ( @@ -66,61 +72,101 @@ export const TextInputWithFont: React.FC = ({ allowDeselect={false} /> - {/* Font Size */} - { - setFontSizeInput(optionValue); - const size = parseInt(optionValue); - if (!isNaN(size)) { - onFontSizeChange(size); - } - fontSizeCombobox.closeDropdown(); - }} - store={fontSizeCombobox} - withinPortal={false} - > - - { - const value = event.currentTarget.value; - setFontSizeInput(value); + {/* Font Size and Color */} + + { + setFontSizeInput(optionValue); + const size = parseInt(optionValue); + if (!isNaN(size)) { + onFontSizeChange(size); + } + fontSizeCombobox.closeDropdown(); + }} + store={fontSizeCombobox} + withinPortal={false} + > + + { + const value = event.currentTarget.value; + setFontSizeInput(value); - // Parse and validate the typed value in real-time - const size = parseInt(value); - if (!isNaN(size) && size >= 8 && size <= 72) { - onFontSizeChange(size); + // Parse and validate the typed value in real-time + const size = parseInt(value); + if (!isNaN(size) && size >= 8 && size <= 200) { + onFontSizeChange(size); + } + + fontSizeCombobox.openDropdown(); + fontSizeCombobox.updateSelectedOptionIndex(); + }} + onClick={() => fontSizeCombobox.openDropdown()} + onFocus={() => fontSizeCombobox.openDropdown()} + onBlur={() => { + fontSizeCombobox.closeDropdown(); + // Clean up invalid values on blur + const size = parseInt(fontSizeInput); + if (isNaN(size) || size < 8 || size > 200) { + setFontSizeInput(fontSize.toString()); + } else { + onFontSizeChange(size); + } + }} + disabled={disabled} + /> + + + + + {fontSizeOptions.map((size) => ( + + {size}px + + ))} + + + + + {/* Text Color Picker */} + {onTextColorChange && ( + + !disabled && setIsColorPickerOpen(true)} + style={{ cursor: disabled ? 'default' : 'pointer' }} + rightSection={ + } + /> + + )} + - fontSizeCombobox.openDropdown(); - fontSizeCombobox.updateSelectedOptionIndex(); - }} - onClick={() => fontSizeCombobox.openDropdown()} - onFocus={() => fontSizeCombobox.openDropdown()} - onBlur={() => { - fontSizeCombobox.closeDropdown(); - // Clean up invalid values on blur - const size = parseInt(fontSizeInput); - if (isNaN(size) || size < 8 || size > 72) { - setFontSizeInput(fontSize.toString()); - } - }} - disabled={disabled} - /> - - - - - {fontSizeOptions.map((size) => ( - - {size}px - - ))} - - - + {/* Color Picker Modal */} + {onTextColorChange && ( + setIsColorPickerOpen(false)} + selectedColor={textColor} + onColorChange={onTextColorChange} + /> + )} ); }; \ No newline at end of file diff --git a/frontend/src/components/tools/sign/SignSettings.tsx b/frontend/src/components/tools/sign/SignSettings.tsx index 6ea797d7c..9585bad1c 100644 --- a/frontend/src/components/tools/sign/SignSettings.tsx +++ b/frontend/src/components/tools/sign/SignSettings.tsx @@ -99,7 +99,7 @@ const SignSettings = ({ } }, [parameters.signatureType]); - // Handle text signature activation + // Handle text signature activation (including fontSize and fontFamily changes) useEffect(() => { if (parameters.signatureType === 'text' && parameters.signerName && parameters.signerName.trim() !== '') { if (onActivateSignaturePlacement) { @@ -114,7 +114,7 @@ const SignSettings = ({ onDeactivateSignature(); } } - }, [parameters.signatureType, parameters.signerName, onActivateSignaturePlacement, onDeactivateSignature]); + }, [parameters.signatureType, parameters.signerName, parameters.fontSize, parameters.fontFamily, onActivateSignaturePlacement, onDeactivateSignature]); // Handle signature data updates useEffect(() => { @@ -237,6 +237,8 @@ const SignSettings = ({ onFontSizeChange={(size) => onParameterChange('fontSize', size)} fontFamily={parameters.fontFamily || 'Helvetica'} onFontFamilyChange={(family) => onParameterChange('fontFamily', family)} + textColor={parameters.textColor || '#000000'} + onTextColorChange={(color) => onParameterChange('textColor', color)} disabled={disabled} /> )} diff --git a/frontend/src/components/viewer/SignatureAPIBridge.tsx b/frontend/src/components/viewer/SignatureAPIBridge.tsx index 1b04c6c3d..b22f66b03 100644 --- a/frontend/src/components/viewer/SignatureAPIBridge.tsx +++ b/frontend/src/components/viewer/SignatureAPIBridge.tsx @@ -101,6 +101,10 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI addTextSignature: (text: string, x: number, y: number, pageIndex: number) => { if (!annotationApi) return; + const textColor = signatureConfig?.textColor || '#000000'; + const fontSize = signatureConfig?.fontSize || 16; + const fontFamily = signatureConfig?.fontFamily || 'Helvetica'; + // Create text annotation for signature annotationApi.createAnnotation(pageIndex, { type: PdfAnnotationSubtype.FREETEXT, @@ -110,8 +114,8 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI }, contents: text, author: 'Digital Signature', - fontSize: 16, - fontColor: '#000000', + fontSize: fontSize, + fontColor: textColor, fontFamily: PdfStandardFont.Helvetica, textAlign: PdfTextAlignment.Left, verticalAlign: PdfVerticalAlignment.Top, @@ -150,45 +154,31 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI try { if (signatureConfig.signatureType === 'text' && signatureConfig.signerName) { - // Try different tool names for text annotations - const textToolNames = ['freetext', 'text', 'textbox', 'annotation-text']; - let activatedTool = null; - - for (const toolName of textToolNames) { - annotationApi.setActiveTool(toolName); - const tool = annotationApi.getActiveTool(); - - if (tool && tool.id === toolName) { - activatedTool = tool; - 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; - } - } + // Skip native text tools - always use stamp for consistent sizing + const activatedTool = null; if (!activatedTool) { - // Fallback: create a simple text image as stamp + // Create text image as stamp with actual pixel size matching desired display size const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (ctx) { - const fontSize = signatureConfig.fontSize || 16; + const baseFontSize = signatureConfig.fontSize || 16; const fontFamily = signatureConfig.fontFamily || 'Helvetica'; + const textColor = signatureConfig.textColor || '#000000'; - canvas.width = Math.max(200, signatureConfig.signerName.length * fontSize * 0.6); - canvas.height = fontSize + 20; - ctx.fillStyle = '#000000'; - ctx.font = `${fontSize}px ${fontFamily}`; + // Canvas pixel size = display size (EmbedPDF uses pixel dimensions directly) + canvas.width = Math.max(200, signatureConfig.signerName.length * baseFontSize * 0.6); + canvas.height = baseFontSize + 20; + + ctx.fillStyle = textColor; + ctx.font = `${baseFontSize}px ${fontFamily}`; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(signatureConfig.signerName, 10, canvas.height / 2); const dataURL = canvas.toDataURL(); + // Deactivate and reactivate to force refresh + annotationApi.setActiveTool(null); annotationApi.setActiveTool('stamp'); const stampTool = annotationApi.getActiveTool(); if (stampTool && stampTool.id === 'stamp') { @@ -307,6 +297,9 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI case 'text': if (params.signerName) { + const textColor = params.textColor || '#000000'; + const fontSize = params.fontSize || 16; + annotationApi.createAnnotation(page, { type: PdfAnnotationSubtype.FREETEXT, rect: { @@ -315,8 +308,8 @@ export const SignatureAPIBridge = forwardRef(function SignatureAPI }, contents: params.signerName, author: 'Digital Signature', - fontSize: 16, - fontColor: '#000000', + fontSize: fontSize, + fontColor: textColor, fontFamily: PdfStandardFont.Helvetica, textAlign: PdfTextAlignment.Left, verticalAlign: PdfVerticalAlignment.Top, diff --git a/frontend/src/hooks/tools/sign/useSignParameters.ts b/frontend/src/hooks/tools/sign/useSignParameters.ts index c96a31103..bc379a36d 100644 --- a/frontend/src/hooks/tools/sign/useSignParameters.ts +++ b/frontend/src/hooks/tools/sign/useSignParameters.ts @@ -17,6 +17,7 @@ export interface SignParameters { signerName?: string; fontFamily?: string; fontSize?: number; + textColor?: string; } export const DEFAULT_PARAMETERS: SignParameters = { @@ -26,6 +27,7 @@ export const DEFAULT_PARAMETERS: SignParameters = { signerName: '', fontFamily: 'Helvetica', fontSize: 16, + textColor: '#000000', }; const validateSignParameters = (parameters: SignParameters): boolean => {