diff --git a/frontend/src/components/tools/convert/ConvertSettings.tsx b/frontend/src/components/tools/convert/ConvertSettings.tsx
index 355ce042f..2d030a03f 100644
--- a/frontend/src/components/tools/convert/ConvertSettings.tsx
+++ b/frontend/src/components/tools/convert/ConvertSettings.tsx
@@ -12,6 +12,7 @@ import ConvertToImageSettings from "./ConvertToImageSettings";
import ConvertFromImageSettings from "./ConvertFromImageSettings";
import ConvertFromWebSettings from "./ConvertFromWebSettings";
import ConvertFromEmailSettings from "./ConvertFromEmailSettings";
+import ConvertToPdfaSettings from "./ConvertToPdfaSettings";
import { ConvertParameters } from "../../../hooks/tools/convert/useConvertParameters";
import {
FROM_FORMAT_OPTIONS,
@@ -114,6 +115,9 @@ const ConvertSettings = ({
downloadHtml: false,
includeAllRecipients: false,
});
+ onParameterChange('pdfaOptions', {
+ outputFormat: 'pdfa-1',
+ });
// Disable smart detection when manually changing source format
onParameterChange('isSmartDetection', false);
onParameterChange('smartDetectionType', 'none');
@@ -161,6 +165,9 @@ const ConvertSettings = ({
downloadHtml: false,
includeAllRecipients: false,
});
+ onParameterChange('pdfaOptions', {
+ outputFormat: 'pdfa-1',
+ });
};
@@ -274,6 +281,19 @@ const ConvertSettings = ({
>
)}
+ {/* PDF to PDF/A options */}
+ {parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa' && (
+ <>
+
+
+ >
+ )}
+
);
};
diff --git a/frontend/src/components/tools/convert/ConvertToPdfaSettings.tsx b/frontend/src/components/tools/convert/ConvertToPdfaSettings.tsx
new file mode 100644
index 000000000..0422ee780
--- /dev/null
+++ b/frontend/src/components/tools/convert/ConvertToPdfaSettings.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { Stack, Text, Select, Alert } from '@mantine/core';
+import { useTranslation } from 'react-i18next';
+import { ConvertParameters } from '../../../hooks/tools/convert/useConvertParameters';
+import { usePdfSignatureDetection } from '../../../hooks/usePdfSignatureDetection';
+
+interface ConvertToPdfaSettingsProps {
+ parameters: ConvertParameters;
+ onParameterChange: (key: keyof ConvertParameters, value: any) => void;
+ selectedFiles: File[];
+ disabled?: boolean;
+}
+
+const ConvertToPdfaSettings = ({
+ parameters,
+ onParameterChange,
+ selectedFiles,
+ disabled = false
+}: ConvertToPdfaSettingsProps) => {
+ const { t } = useTranslation();
+ const { hasDigitalSignatures, isChecking } = usePdfSignatureDetection(selectedFiles);
+
+ const pdfaFormatOptions = [
+ { value: 'pdfa-1', label: 'PDF/A-1b' },
+ { value: 'pdfa', label: 'PDF/A-2b' }
+ ];
+
+ return (
+
+ {t("convert.pdfaOptions", "PDF/A Options")}:
+
+ {hasDigitalSignatures && (
+
+
+ {t("convert.pdfaDigitalSignatureWarning", "The PDF contains a digital signature. This will be removed in the next step.")}
+
+
+ )}
+
+
+ {t("convert.outputFormat", "Output Format")}:
+
+
+ );
+};
+
+export default ConvertToPdfaSettings;
\ No newline at end of file
diff --git a/frontend/src/hooks/tools/convert/useConvertOperation.ts b/frontend/src/hooks/tools/convert/useConvertOperation.ts
index cf01ee147..1d8d3f08e 100644
--- a/frontend/src/hooks/tools/convert/useConvertOperation.ts
+++ b/frontend/src/hooks/tools/convert/useConvertOperation.ts
@@ -46,6 +46,8 @@ const shouldProcessFilesSeparately = (
parameters.toExtension === 'pdf' && !parameters.imageOptions.combineImages) ||
// PDF to image conversions (each PDF should generate its own image file)
(parameters.fromExtension === 'pdf' && isImageFormat(parameters.toExtension)) ||
+ // PDF to PDF/A conversions (each PDF should be processed separately)
+ (parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
// Web files to PDF conversions (each web file should generate its own PDF)
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
parameters.toExtension === 'pdf') ||
@@ -143,7 +145,7 @@ export const useConvertOperation = (): ConvertOperationHook => {
formData.append("fileInput", file);
});
- const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions } = parameters;
+ const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions } = parameters;
// Add conversion-specific parameters
if (isImageFormat(toExtension)) {
@@ -170,6 +172,9 @@ export const useConvertOperation = (): ConvertOperationHook => {
formData.append("maxAttachmentSizeMB", emailOptions.maxAttachmentSizeMB.toString());
formData.append("downloadHtml", emailOptions.downloadHtml.toString());
formData.append("includeAllRecipients", emailOptions.includeAllRecipients.toString());
+ } else if (fromExtension === 'pdf' && toExtension === 'pdfa') {
+ // PDF to PDF/A conversion with output format
+ formData.append("outputFormat", pdfaOptions.outputFormat);
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
// CSV extraction - always process all pages for simplified workflow
formData.append("pageNumbers", "all");
@@ -199,6 +204,7 @@ export const useConvertOperation = (): ConvertOperationHook => {
imageOptions: parameters.imageOptions,
htmlOptions: parameters.htmlOptions,
emailOptions: parameters.emailOptions,
+ pdfaOptions: parameters.pdfaOptions,
},
fileSize: selectedFiles[0].size
}
diff --git a/frontend/src/hooks/tools/convert/useConvertParameters.test.ts b/frontend/src/hooks/tools/convert/useConvertParameters.test.ts
index 7ed63fcd9..25cd4f3ad 100644
--- a/frontend/src/hooks/tools/convert/useConvertParameters.test.ts
+++ b/frontend/src/hooks/tools/convert/useConvertParameters.test.ts
@@ -23,6 +23,7 @@ describe('useConvertParameters', () => {
expect(result.current.parameters.emailOptions.maxAttachmentSizeMB).toBe(10);
expect(result.current.parameters.emailOptions.downloadHtml).toBe(false);
expect(result.current.parameters.emailOptions.includeAllRecipients).toBe(false);
+ expect(result.current.parameters.pdfaOptions.outputFormat).toBe('pdfa-1');
});
test('should update individual parameters', () => {
@@ -82,6 +83,18 @@ describe('useConvertParameters', () => {
expect(result.current.parameters.emailOptions.includeAllRecipients).toBe(true);
});
+ test('should update nested PDF/A options', () => {
+ const { result } = renderHook(() => useConvertParameters());
+
+ act(() => {
+ result.current.updateParameter('pdfaOptions', {
+ outputFormat: 'pdfa'
+ });
+ });
+
+ expect(result.current.parameters.pdfaOptions.outputFormat).toBe('pdfa');
+ });
+
test('should reset parameters to defaults', () => {
const { result } = renderHook(() => useConvertParameters());
diff --git a/frontend/src/hooks/tools/convert/useConvertParameters.ts b/frontend/src/hooks/tools/convert/useConvertParameters.ts
index 19c536d65..08fe4f762 100644
--- a/frontend/src/hooks/tools/convert/useConvertParameters.ts
+++ b/frontend/src/hooks/tools/convert/useConvertParameters.ts
@@ -32,6 +32,9 @@ export interface ConvertParameters {
downloadHtml: boolean;
includeAllRecipients: boolean;
};
+ pdfaOptions: {
+ outputFormat: string;
+ };
isSmartDetection: boolean;
smartDetectionType: 'mixed' | 'images' | 'web' | 'none';
}
@@ -67,6 +70,9 @@ const initialParameters: ConvertParameters = {
downloadHtml: false,
includeAllRecipients: false,
},
+ pdfaOptions: {
+ outputFormat: 'pdfa-1',
+ },
isSmartDetection: false,
smartDetectionType: 'none',
};
diff --git a/frontend/src/hooks/usePdfSignatureDetection.ts b/frontend/src/hooks/usePdfSignatureDetection.ts
new file mode 100644
index 000000000..96d80fb9d
--- /dev/null
+++ b/frontend/src/hooks/usePdfSignatureDetection.ts
@@ -0,0 +1,66 @@
+import { useState, useEffect } from 'react';
+import * as pdfjsLib from 'pdfjs-dist';
+
+export interface PdfSignatureDetectionResult {
+ hasDigitalSignatures: boolean;
+ isChecking: boolean;
+}
+
+export const usePdfSignatureDetection = (files: File[]): PdfSignatureDetectionResult => {
+ const [hasDigitalSignatures, setHasDigitalSignatures] = useState(false);
+ const [isChecking, setIsChecking] = useState(false);
+
+ useEffect(() => {
+ const checkForDigitalSignatures = async () => {
+ if (files.length === 0) {
+ setHasDigitalSignatures(false);
+ return;
+ }
+
+ setIsChecking(true);
+ let foundSignature = false;
+
+ try {
+ // Set up PDF.js worker
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-legacy/pdf.worker.mjs';
+
+ for (const file of files) {
+ const arrayBuffer = await file.arrayBuffer();
+
+ try {
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
+
+ for (let i = 1; i <= pdf.numPages; i++) {
+ const page = await pdf.getPage(i);
+ const annotations = await page.getAnnotations({ intent: 'display' });
+
+ annotations.forEach(annotation => {
+ if (annotation.subtype === 'Widget' && annotation.fieldType === 'Sig') {
+ foundSignature = true;
+ }
+ });
+
+ if (foundSignature) break;
+ }
+ } catch (error) {
+ console.warn('Error analyzing PDF for signatures:', error);
+ }
+
+ if (foundSignature) break;
+ }
+ } catch (error) {
+ console.warn('Error checking for digital signatures:', error);
+ }
+
+ setHasDigitalSignatures(foundSignature);
+ setIsChecking(false);
+ };
+
+ checkForDigitalSignatures();
+ }, [files]);
+
+ return {
+ hasDigitalSignatures,
+ isChecking
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/tests/convert/ConvertSmartDetectionIntegration.test.tsx b/frontend/src/tests/convert/ConvertSmartDetectionIntegration.test.tsx
index e08ae0462..f6be30407 100644
--- a/frontend/src/tests/convert/ConvertSmartDetectionIntegration.test.tsx
+++ b/frontend/src/tests/convert/ConvertSmartDetectionIntegration.test.tsx
@@ -322,6 +322,40 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
expect(formData.get('downloadHtml')).toBe('true');
expect(formData.get('includeAllRecipients')).toBe('true');
});
+
+ test('should send correct PDF/A parameters for pdf-to-pdfa conversion', async () => {
+ const { result: paramsResult } = renderHook(() => useConvertParameters(), {
+ wrapper: TestWrapper
+ });
+
+ const { result: operationResult } = renderHook(() => useConvertOperation(), {
+ wrapper: TestWrapper
+ });
+
+ const pdfFile = new File(['pdf content'], 'document.pdf', { type: 'application/pdf' });
+
+ // Set up PDF/A conversion parameters
+ act(() => {
+ paramsResult.current.updateParameter('fromExtension', 'pdf');
+ paramsResult.current.updateParameter('toExtension', 'pdfa');
+ paramsResult.current.updateParameter('pdfaOptions', {
+ outputFormat: 'pdfa'
+ });
+ });
+
+ await act(async () => {
+ await operationResult.current.executeOperation(
+ paramsResult.current.parameters,
+ [pdfFile]
+ );
+ });
+
+ const formData = mockedAxios.post.mock.calls[0][1] as FormData;
+ expect(formData.get('outputFormat')).toBe('pdfa');
+ expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/pdf/pdfa', expect.any(FormData), {
+ responseType: 'blob'
+ });
+ });
});
describe('Image Conversion Options Integration', () => {