mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
More robust
This commit is contained in:
parent
306117ed4b
commit
2c491c6bf4
@ -1,10 +1,11 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Stack, Button, Text, Alert, SegmentedControl, Divider, ActionIcon, Tooltip, Group, Box } from '@mantine/core';
|
||||
import { SignParameters } from "@app/hooks/tools/sign/useSignParameters";
|
||||
import { SuggestedToolsSection } from "@app/components/tools/shared/SuggestedToolsSection";
|
||||
import { useSignature } from "@app/contexts/SignatureContext";
|
||||
import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { PLACEMENT_ACTIVATION_DELAY, FILE_SWITCH_ACTIVATION_DELAY } from './signConstants';
|
||||
|
||||
// Import the new reusable components
|
||||
import { DrawingCanvas } from "@app/components/annotation/shared/DrawingCanvas";
|
||||
@ -174,12 +175,12 @@ const SignSettings = ({
|
||||
case 'image':
|
||||
return imageSignatureData ?? null;
|
||||
case 'text':
|
||||
return [
|
||||
(parameters.signerName ?? '').trim(),
|
||||
parameters.fontSize ?? 16,
|
||||
parameters.fontFamily ?? 'Helvetica',
|
||||
parameters.textColor ?? '#000000',
|
||||
].join('|');
|
||||
return JSON.stringify({
|
||||
signerName: (parameters.signerName ?? '').trim(),
|
||||
fontSize: parameters.fontSize ?? 16,
|
||||
fontFamily: parameters.fontFamily ?? 'Helvetica',
|
||||
textColor: parameters.textColor ?? '#000000',
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -355,7 +356,7 @@ const SignSettings = ({
|
||||
if (typeof window !== 'undefined') {
|
||||
const timer = window.setTimeout(() => {
|
||||
onActivateSignaturePlacement?.();
|
||||
}, 60);
|
||||
}, PLACEMENT_ACTIVATION_DELAY);
|
||||
return () => window.clearTimeout(timer);
|
||||
}
|
||||
|
||||
@ -392,7 +393,7 @@ const SignSettings = ({
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const timer = window.setTimeout(trigger, 60);
|
||||
const timer = window.setTimeout(trigger, PLACEMENT_ACTIVATION_DELAY);
|
||||
return () => window.clearTimeout(timer);
|
||||
}
|
||||
|
||||
@ -415,7 +416,7 @@ const SignSettings = ({
|
||||
if (typeof window !== 'undefined') {
|
||||
const timer = window.setTimeout(() => {
|
||||
onActivateSignaturePlacement?.();
|
||||
}, 80);
|
||||
}, FILE_SWITCH_ACTIVATION_DELAY);
|
||||
return () => window.clearTimeout(timer);
|
||||
}
|
||||
|
||||
|
||||
15
frontend/src/core/components/tools/sign/signConstants.ts
Normal file
15
frontend/src/core/components/tools/sign/signConstants.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Timeout delays (ms) to allow PDF viewer to complete rendering before activating placement mode
|
||||
export const PLACEMENT_ACTIVATION_DELAY = 60; // Standard delay for signature changes
|
||||
export const FILE_SWITCH_ACTIVATION_DELAY = 80; // Slightly longer delay when switching files
|
||||
|
||||
// Signature preview sizing
|
||||
export const MAX_PREVIEW_WIDTH_RATIO = 0.35; // Max preview width as percentage of container
|
||||
export const MAX_PREVIEW_HEIGHT_RATIO = 0.35; // Max preview height as percentage of container
|
||||
export const MAX_PREVIEW_WIDTH_REM = 15; // Absolute max width in rem
|
||||
export const MAX_PREVIEW_HEIGHT_REM = 10; // Absolute max height in rem
|
||||
export const MIN_SIGNATURE_DIMENSION_REM = 0.75; // Min dimension for visibility
|
||||
export const OVERLAY_EDGE_PADDING_REM = 0.25; // Padding from container edges
|
||||
|
||||
// Text signature padding (relative to font size)
|
||||
export const HORIZONTAL_PADDING_RATIO = 0.8;
|
||||
export const VERTICAL_PADDING_RATIO = 0.6;
|
||||
@ -6,28 +6,42 @@ import type { SignatureAPI } from '@app/components/viewer/viewerTypes';
|
||||
import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
|
||||
// Minimum allowed width/height (in pixels) for a signature image or text stamp.
|
||||
// This prevents rendering issues and ensures signatures are always visible and usable.
|
||||
const MIN_SIGNATURE_DIMENSION = 12;
|
||||
|
||||
// Use 2x oversampling to improve text rendering quality (anti-aliasing) when generating signature images.
|
||||
// This provides a good balance between visual fidelity and performance/memory usage.
|
||||
const TEXT_OVERSAMPLE_FACTOR = 2;
|
||||
|
||||
const extractDataUrl = (value: unknown, depth = 0): string | undefined => {
|
||||
const extractDataUrl = (value: unknown, depth = 0, visited: Set<unknown> = new Set()): string | undefined => {
|
||||
if (!value || depth > 6) return undefined;
|
||||
|
||||
// Prevent circular references
|
||||
if (typeof value === 'object' && visited.has(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value.startsWith('data:image') ? value : undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
const result = extractDataUrl(entry, depth + 1);
|
||||
if (result) return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
for (const key of Object.keys(value as Record<string, unknown>)) {
|
||||
const result = extractDataUrl((value as Record<string, unknown>)[key], depth + 1);
|
||||
if (result) return result;
|
||||
visited.add(value);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
const result = extractDataUrl(entry, depth + 1, visited);
|
||||
if (result) return result;
|
||||
}
|
||||
} else {
|
||||
for (const key of Object.keys(value as Record<string, unknown>)) {
|
||||
const result = extractDataUrl((value as Record<string, unknown>)[key], depth + 1, visited);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,17 @@ import { Box } from '@mantine/core';
|
||||
import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
|
||||
import { buildSignaturePreview, SignaturePreview } from '@app/utils/signaturePreview';
|
||||
import { useSignature } from '@app/contexts/SignatureContext';
|
||||
import {
|
||||
MAX_PREVIEW_WIDTH_RATIO,
|
||||
MAX_PREVIEW_HEIGHT_RATIO,
|
||||
MAX_PREVIEW_WIDTH_REM,
|
||||
MAX_PREVIEW_HEIGHT_REM,
|
||||
MIN_SIGNATURE_DIMENSION_REM,
|
||||
OVERLAY_EDGE_PADDING_REM,
|
||||
} from '@app/components/tools/sign/signConstants';
|
||||
|
||||
// Convert rem to pixels using browser's base font size (typically 16px)
|
||||
const remToPx = (rem: number) => rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
|
||||
interface SignaturePlacementOverlayProps {
|
||||
containerRef: React.RefObject<HTMLElement | null>;
|
||||
@ -78,8 +89,8 @@ export const SignaturePlacementOverlay: React.FC<SignaturePlacementOverlayProps>
|
||||
const containerWidth = container.clientWidth || 1;
|
||||
const containerHeight = container.clientHeight || 1;
|
||||
|
||||
const maxWidth = Math.min(containerWidth * 0.35, 240);
|
||||
const maxHeight = Math.min(containerHeight * 0.35, 160);
|
||||
const maxWidth = Math.min(containerWidth * MAX_PREVIEW_WIDTH_RATIO, remToPx(MAX_PREVIEW_WIDTH_REM));
|
||||
const maxHeight = Math.min(containerHeight * MAX_PREVIEW_HEIGHT_RATIO, remToPx(MAX_PREVIEW_HEIGHT_REM));
|
||||
|
||||
const scale = Math.min(
|
||||
1,
|
||||
@ -88,8 +99,8 @@ export const SignaturePlacementOverlay: React.FC<SignaturePlacementOverlayProps>
|
||||
);
|
||||
|
||||
return {
|
||||
width: Math.max(12, preview.width * scale),
|
||||
height: Math.max(12, preview.height * scale),
|
||||
width: Math.max(remToPx(MIN_SIGNATURE_DIMENSION_REM), preview.width * scale),
|
||||
height: Math.max(remToPx(MIN_SIGNATURE_DIMENSION_REM), preview.height * scale),
|
||||
};
|
||||
}, [preview, containerRef]);
|
||||
|
||||
@ -118,9 +129,10 @@ export const SignaturePlacementOverlay: React.FC<SignaturePlacementOverlayProps>
|
||||
|
||||
const width = scaledSize.width;
|
||||
const height = scaledSize.height;
|
||||
const edgePadding = remToPx(OVERLAY_EDGE_PADDING_REM);
|
||||
|
||||
const clampedLeft = Math.max(4, Math.min(cursor.x - width / 2, containerWidth - width - 4));
|
||||
const clampedTop = Math.max(4, Math.min(cursor.y - height / 2, containerHeight - height - 4));
|
||||
const clampedLeft = Math.max(edgePadding, Math.min(cursor.x - width / 2, containerWidth - width - edgePadding));
|
||||
const clampedTop = Math.max(edgePadding, Math.min(cursor.y - height / 2, containerHeight - height - edgePadding));
|
||||
|
||||
return {
|
||||
left: clampedLeft,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
|
||||
import { HORIZONTAL_PADDING_RATIO, VERTICAL_PADDING_RATIO } from '@app/components/tools/sign/signConstants';
|
||||
|
||||
export interface SignaturePreview {
|
||||
dataUrl: string;
|
||||
@ -33,8 +34,8 @@ export const buildSignaturePreview = async (config: SignParameters | null): Prom
|
||||
const fontFamily = config.fontFamily ?? 'Helvetica';
|
||||
const textColor = config.textColor ?? '#000000';
|
||||
|
||||
const paddingX = Math.round(fontSize * 0.8);
|
||||
const paddingY = Math.round(fontSize * 0.6);
|
||||
const paddingX = Math.round(fontSize * HORIZONTAL_PADDING_RATIO);
|
||||
const paddingY = Math.round(fontSize * VERTICAL_PADDING_RATIO);
|
||||
|
||||
const measureCanvas = document.createElement('canvas');
|
||||
const measureCtx = measureCanvas.getContext('2d');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user