mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
feat(convert): add support for CBR to PDF and PDF to CBR conversions
- Introduced `ConvertFromCbrSettings` and `ConvertToCbrSettings` components for handling conversion-specific settings. - Updated `ConvertSettings` to include CBR-related configurations. - Enhanced `useConvertParameters` with new parameters: `cbrOptions` and `pdfToCbrOptions`. - Updated locales with CBR-related translations. - Added endpoints and updated conversion matrix for CBR formats in `convertConstants`. - Modified `useConvertOperation` to handle CBR-specific parameters during form data preparation. Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
parent
4c0c9b28ef
commit
2122b1a323
@ -1079,7 +1079,11 @@
|
|||||||
"markdown": "Markdown",
|
"markdown": "Markdown",
|
||||||
"textRtf": "Text/RTF",
|
"textRtf": "Text/RTF",
|
||||||
"grayscale": "Greyscale",
|
"grayscale": "Greyscale",
|
||||||
"errorConversion": "An error occurred while converting the file."
|
"errorConversion": "An error occurred while converting the file.",
|
||||||
|
"cbrOptions": "CBR to PDF Options",
|
||||||
|
"optimizeForEbook": "Optimize PDF for ebook readers (uses Ghostscript)",
|
||||||
|
"cbrOutputOptions": "PDF to CBR Options",
|
||||||
|
"cbrDpi": "DPI for image rendering"
|
||||||
},
|
},
|
||||||
"imageToPdf": {
|
"imageToPdf": {
|
||||||
"tags": "conversion,img,jpg,picture,photo"
|
"tags": "conversion,img,jpg,picture,photo"
|
||||||
|
|||||||
@ -2021,6 +2021,10 @@
|
|||||||
"outputFormat": "Output Format",
|
"outputFormat": "Output Format",
|
||||||
"pdfaNote": "PDF/A-1b is more compatible, PDF/A-2b supports more features.",
|
"pdfaNote": "PDF/A-1b is more compatible, PDF/A-2b supports more features.",
|
||||||
"pdfaDigitalSignatureWarning": "The PDF contains a digital signature. This will be removed in the next step.",
|
"pdfaDigitalSignatureWarning": "The PDF contains a digital signature. This will be removed in the next step.",
|
||||||
|
"cbrOptions": "CBR to PDF Options",
|
||||||
|
"optimizeForEbook": "Optimize PDF for ebook readers (uses Ghostscript)",
|
||||||
|
"cbrOutputOptions": "PDF to CBR Options",
|
||||||
|
"cbrDpi": "DPI for image rendering",
|
||||||
"sanitize": {
|
"sanitize": {
|
||||||
"submit": "Sanitize PDF",
|
"submit": "Sanitize PDF",
|
||||||
"completed": "Sanitization completed successfully",
|
"completed": "Sanitization completed successfully",
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import {Checkbox, Stack, Text } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
|
||||||
|
|
||||||
|
interface ConvertFromCbrSettingsProps {
|
||||||
|
parameters: ConvertParameters;
|
||||||
|
onParameterChange: <K extends keyof ConvertParameters>(key: K, value: ConvertParameters[K]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConvertFromCbrSettings = ({
|
||||||
|
parameters,
|
||||||
|
onParameterChange,
|
||||||
|
disabled = false
|
||||||
|
}: ConvertFromCbrSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="sm" data-testid="cbr-options-section">
|
||||||
|
<Text size="sm" fw={500}>{t("convert.cbrOptions", "CBR Options")}:</Text>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
label={t('convert.optimizeForEbook', 'Optimize PDF for ebook readers (uses Ghostscript)')}
|
||||||
|
checked={parameters.cbrOptions.optimizeForEbook}
|
||||||
|
onChange={(event) => onParameterChange('cbrOptions', {
|
||||||
|
...parameters.cbrOptions,
|
||||||
|
optimizeForEbook: event.currentTarget.checked
|
||||||
|
})}
|
||||||
|
disabled={disabled}
|
||||||
|
data-testid="optimize-ebook-checkbox"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConvertFromCbrSettings;
|
||||||
@ -14,6 +14,8 @@ import ConvertFromImageSettings from "@app/components/tools/convert/ConvertFromI
|
|||||||
import ConvertFromWebSettings from "@app/components/tools/convert/ConvertFromWebSettings";
|
import ConvertFromWebSettings from "@app/components/tools/convert/ConvertFromWebSettings";
|
||||||
import ConvertFromEmailSettings from "@app/components/tools/convert/ConvertFromEmailSettings";
|
import ConvertFromEmailSettings from "@app/components/tools/convert/ConvertFromEmailSettings";
|
||||||
import ConvertToPdfaSettings from "@app/components/tools/convert/ConvertToPdfaSettings";
|
import ConvertToPdfaSettings from "@app/components/tools/convert/ConvertToPdfaSettings";
|
||||||
|
import ConvertFromCbrSettings from "@app/components/tools/convert/ConvertFromCbrSettings";
|
||||||
|
import ConvertToCbrSettings from "@app/components/tools/convert/ConvertToCbrSettings";
|
||||||
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
|
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
|
||||||
import {
|
import {
|
||||||
FROM_FORMAT_OPTIONS,
|
FROM_FORMAT_OPTIONS,
|
||||||
@ -118,6 +120,12 @@ const ConvertSettings = ({
|
|||||||
onParameterChange('pdfaOptions', {
|
onParameterChange('pdfaOptions', {
|
||||||
outputFormat: 'pdfa-1',
|
outputFormat: 'pdfa-1',
|
||||||
});
|
});
|
||||||
|
onParameterChange('cbrOptions', {
|
||||||
|
optimizeForEbook: false,
|
||||||
|
});
|
||||||
|
onParameterChange('pdfToCbrOptions', {
|
||||||
|
dpi: 150,
|
||||||
|
});
|
||||||
onParameterChange('isSmartDetection', false);
|
onParameterChange('isSmartDetection', false);
|
||||||
onParameterChange('smartDetectionType', 'none');
|
onParameterChange('smartDetectionType', 'none');
|
||||||
};
|
};
|
||||||
@ -180,6 +188,12 @@ const ConvertSettings = ({
|
|||||||
onParameterChange('pdfaOptions', {
|
onParameterChange('pdfaOptions', {
|
||||||
outputFormat: 'pdfa-1',
|
outputFormat: 'pdfa-1',
|
||||||
});
|
});
|
||||||
|
onParameterChange('cbrOptions', {
|
||||||
|
optimizeForEbook: false,
|
||||||
|
});
|
||||||
|
onParameterChange('pdfToCbrOptions', {
|
||||||
|
dpi: 150,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -306,6 +320,30 @@ const ConvertSettings = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* CBR to PDF options */}
|
||||||
|
{parameters.fromExtension === 'cbr' && parameters.toExtension === 'pdf' && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<ConvertFromCbrSettings
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={onParameterChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* PDF to CBR options */}
|
||||||
|
{parameters.fromExtension === 'pdf' && parameters.toExtension === 'cbr' && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<ConvertToCbrSettings
|
||||||
|
parameters={parameters}
|
||||||
|
onParameterChange={onParameterChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { Stack, Text, NumberInput } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
|
||||||
|
|
||||||
|
interface ConvertToCbrSettingsProps {
|
||||||
|
parameters: ConvertParameters;
|
||||||
|
onParameterChange: <K extends keyof ConvertParameters>(key: K, value: ConvertParameters[K]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConvertToCbrSettings = ({
|
||||||
|
parameters,
|
||||||
|
onParameterChange,
|
||||||
|
disabled = false
|
||||||
|
}: ConvertToCbrSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="sm" data-testid="cbr-output-options-section">
|
||||||
|
<Text size="sm" fw={500}>{t("convert.cbrOutputOptions", "PDF to CBR Options")}:</Text>
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
data-testid="cbr-dpi-input"
|
||||||
|
label={t("convert.cbrDpi", "DPI for image rendering")}
|
||||||
|
value={parameters.pdfToCbrOptions.dpi}
|
||||||
|
onChange={(value) => onParameterChange('pdfToCbrOptions', {
|
||||||
|
...parameters.pdfToCbrOptions,
|
||||||
|
dpi: typeof value === 'number' ? value : 150
|
||||||
|
})}
|
||||||
|
min={72}
|
||||||
|
max={600}
|
||||||
|
step={50}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConvertToCbrSettings;
|
||||||
@ -31,7 +31,9 @@ export const CONVERSION_ENDPOINTS = {
|
|||||||
'pdf-pdfa': '/api/v1/convert/pdf/pdfa',
|
'pdf-pdfa': '/api/v1/convert/pdf/pdfa',
|
||||||
'html-pdf': '/api/v1/convert/html/pdf',
|
'html-pdf': '/api/v1/convert/html/pdf',
|
||||||
'markdown-pdf': '/api/v1/convert/markdown/pdf',
|
'markdown-pdf': '/api/v1/convert/markdown/pdf',
|
||||||
'eml-pdf': '/api/v1/convert/eml/pdf'
|
'eml-pdf': '/api/v1/convert/eml/pdf',
|
||||||
|
'cbr-pdf': '/api/v1/convert/cbr/pdf',
|
||||||
|
'pdf-cbr': '/api/v1/convert/pdf/cbr'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ENDPOINT_NAMES = {
|
export const ENDPOINT_NAMES = {
|
||||||
@ -48,7 +50,9 @@ export const ENDPOINT_NAMES = {
|
|||||||
'pdf-pdfa': 'pdf-to-pdfa',
|
'pdf-pdfa': 'pdf-to-pdfa',
|
||||||
'html-pdf': 'html-to-pdf',
|
'html-pdf': 'html-to-pdf',
|
||||||
'markdown-pdf': 'markdown-to-pdf',
|
'markdown-pdf': 'markdown-to-pdf',
|
||||||
'eml-pdf': 'eml-to-pdf'
|
'eml-pdf': 'eml-to-pdf',
|
||||||
|
'cbr-pdf': 'cbr-to-pdf',
|
||||||
|
'pdf-cbr': 'pdf-to-cbr'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
||||||
@ -80,6 +84,7 @@ export const FROM_FORMAT_OPTIONS = [
|
|||||||
{ value: 'txt', label: 'TXT', group: 'Text' },
|
{ value: 'txt', label: 'TXT', group: 'Text' },
|
||||||
{ value: 'rtf', label: 'RTF', group: 'Text' },
|
{ value: 'rtf', label: 'RTF', group: 'Text' },
|
||||||
{ value: 'eml', label: 'EML', group: 'Email' },
|
{ value: 'eml', label: 'EML', group: 'Email' },
|
||||||
|
{ value: 'cbr', label: 'CBR', group: 'Archive' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TO_FORMAT_OPTIONS = [
|
export const TO_FORMAT_OPTIONS = [
|
||||||
@ -101,13 +106,14 @@ export const TO_FORMAT_OPTIONS = [
|
|||||||
{ value: 'webp', label: 'WEBP', group: 'Image' },
|
{ value: 'webp', label: 'WEBP', group: 'Image' },
|
||||||
{ value: 'html', label: 'HTML', group: 'Web' },
|
{ value: 'html', label: 'HTML', group: 'Web' },
|
||||||
{ value: 'xml', label: 'XML', group: 'Web' },
|
{ value: 'xml', label: 'XML', group: 'Web' },
|
||||||
|
{ value: 'cbr', label: 'CBR', group: 'Archive' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Conversion matrix - what each source format can convert to
|
// Conversion matrix - what each source format can convert to
|
||||||
export const CONVERSION_MATRIX: Record<string, string[]> = {
|
export const CONVERSION_MATRIX: Record<string, string[]> = {
|
||||||
'any': ['pdf'], // Mixed files always convert to PDF
|
'any': ['pdf'], // Mixed files always convert to PDF
|
||||||
'image': ['pdf'], // Multiple images always convert to PDF
|
'image': ['pdf'], // Multiple images always convert to PDF
|
||||||
'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa'],
|
'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa', 'cbr'],
|
||||||
'docx': ['pdf'], 'doc': ['pdf'], 'odt': ['pdf'],
|
'docx': ['pdf'], 'doc': ['pdf'], 'odt': ['pdf'],
|
||||||
'xlsx': ['pdf'], 'xls': ['pdf'], 'ods': ['pdf'],
|
'xlsx': ['pdf'], 'xls': ['pdf'], 'ods': ['pdf'],
|
||||||
'pptx': ['pdf'], 'ppt': ['pdf'], 'odp': ['pdf'],
|
'pptx': ['pdf'], 'ppt': ['pdf'], 'odp': ['pdf'],
|
||||||
@ -116,7 +122,8 @@ export const CONVERSION_MATRIX: Record<string, string[]> = {
|
|||||||
'zip': ['pdf'],
|
'zip': ['pdf'],
|
||||||
'md': ['pdf'],
|
'md': ['pdf'],
|
||||||
'txt': ['pdf'], 'rtf': ['pdf'],
|
'txt': ['pdf'], 'rtf': ['pdf'],
|
||||||
'eml': ['pdf']
|
'eml': ['pdf'],
|
||||||
|
'cbr': ['pdf']
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map extensions to endpoint keys
|
// Map extensions to endpoint keys
|
||||||
@ -130,7 +137,8 @@ export const EXTENSION_TO_ENDPOINT: Record<string, Record<string, string>> = {
|
|||||||
'csv': 'pdf-to-csv',
|
'csv': 'pdf-to-csv',
|
||||||
'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', 'md': 'pdf-to-markdown',
|
'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', 'md': 'pdf-to-markdown',
|
||||||
'html': 'pdf-to-html', 'xml': 'pdf-to-xml',
|
'html': 'pdf-to-html', 'xml': 'pdf-to-xml',
|
||||||
'pdfa': 'pdf-to-pdfa'
|
'pdfa': 'pdf-to-pdfa',
|
||||||
|
'cbr': 'pdf-to-cbr'
|
||||||
},
|
},
|
||||||
'docx': { 'pdf': 'file-to-pdf' }, 'doc': { 'pdf': 'file-to-pdf' }, 'odt': { 'pdf': 'file-to-pdf' },
|
'docx': { 'pdf': 'file-to-pdf' }, 'doc': { 'pdf': 'file-to-pdf' }, 'odt': { 'pdf': 'file-to-pdf' },
|
||||||
'xlsx': { 'pdf': 'file-to-pdf' }, 'xls': { 'pdf': 'file-to-pdf' }, 'ods': { 'pdf': 'file-to-pdf' },
|
'xlsx': { 'pdf': 'file-to-pdf' }, 'xls': { 'pdf': 'file-to-pdf' }, 'ods': { 'pdf': 'file-to-pdf' },
|
||||||
@ -141,7 +149,8 @@ export const EXTENSION_TO_ENDPOINT: Record<string, Record<string, string>> = {
|
|||||||
'zip': { 'pdf': 'html-to-pdf' },
|
'zip': { 'pdf': 'html-to-pdf' },
|
||||||
'md': { 'pdf': 'markdown-to-pdf' },
|
'md': { 'pdf': 'markdown-to-pdf' },
|
||||||
'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' },
|
'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' },
|
||||||
'eml': { 'pdf': 'eml-to-pdf' }
|
'eml': { 'pdf': 'eml-to-pdf' },
|
||||||
|
'cbr': { 'pdf': 'cbr-to-pdf' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ColorType = typeof COLOR_TYPES[keyof typeof COLOR_TYPES];
|
export type ColorType = typeof COLOR_TYPES[keyof typeof COLOR_TYPES];
|
||||||
|
|||||||
@ -21,6 +21,8 @@ export const shouldProcessFilesSeparately = (
|
|||||||
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
|
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
|
||||||
// PDF to text-like formats should be one output per input
|
// PDF to text-like formats should be one output per input
|
||||||
(parameters.fromExtension === 'pdf' && ['txt', 'rtf', 'csv'].includes(parameters.toExtension)) ||
|
(parameters.fromExtension === 'pdf' && ['txt', 'rtf', 'csv'].includes(parameters.toExtension)) ||
|
||||||
|
// PDF to CBR conversions (each PDF should generate its own archive)
|
||||||
|
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'cbr') ||
|
||||||
// Web files to PDF conversions (each web file should generate its own PDF)
|
// Web files to PDF conversions (each web file should generate its own PDF)
|
||||||
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
|
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
|
||||||
parameters.toExtension === 'pdf') ||
|
parameters.toExtension === 'pdf') ||
|
||||||
@ -39,7 +41,7 @@ export const buildConvertFormData = (parameters: ConvertParameters, selectedFile
|
|||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions } = parameters;
|
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions, cbrOptions, pdfToCbrOptions } = parameters;
|
||||||
|
|
||||||
if (isImageFormat(toExtension)) {
|
if (isImageFormat(toExtension)) {
|
||||||
formData.append("imageFormat", toExtension);
|
formData.append("imageFormat", toExtension);
|
||||||
@ -67,6 +69,10 @@ export const buildConvertFormData = (parameters: ConvertParameters, selectedFile
|
|||||||
formData.append("outputFormat", pdfaOptions.outputFormat);
|
formData.append("outputFormat", pdfaOptions.outputFormat);
|
||||||
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
|
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
|
||||||
formData.append("pageNumbers", "all");
|
formData.append("pageNumbers", "all");
|
||||||
|
} else if (fromExtension === 'cbr' && toExtension === 'pdf') {
|
||||||
|
formData.append("optimizeForEbook", cbrOptions.optimizeForEbook.toString());
|
||||||
|
} else if (fromExtension === 'pdf' && toExtension === 'cbr') {
|
||||||
|
formData.append("dpi", pdfToCbrOptions.dpi.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
|
|||||||
@ -36,6 +36,12 @@ export interface ConvertParameters extends BaseParameters {
|
|||||||
pdfaOptions: {
|
pdfaOptions: {
|
||||||
outputFormat: string;
|
outputFormat: string;
|
||||||
};
|
};
|
||||||
|
cbrOptions: {
|
||||||
|
optimizeForEbook: boolean;
|
||||||
|
};
|
||||||
|
pdfToCbrOptions: {
|
||||||
|
dpi: number;
|
||||||
|
};
|
||||||
isSmartDetection: boolean;
|
isSmartDetection: boolean;
|
||||||
smartDetectionType: 'mixed' | 'images' | 'web' | 'none';
|
smartDetectionType: 'mixed' | 'images' | 'web' | 'none';
|
||||||
}
|
}
|
||||||
@ -69,6 +75,12 @@ export const defaultParameters: ConvertParameters = {
|
|||||||
pdfaOptions: {
|
pdfaOptions: {
|
||||||
outputFormat: 'pdfa-1',
|
outputFormat: 'pdfa-1',
|
||||||
},
|
},
|
||||||
|
cbrOptions: {
|
||||||
|
optimizeForEbook: false,
|
||||||
|
},
|
||||||
|
pdfToCbrOptions: {
|
||||||
|
dpi: 150,
|
||||||
|
},
|
||||||
isSmartDetection: false,
|
isSmartDetection: false,
|
||||||
smartDetectionType: 'none',
|
smartDetectionType: 'none',
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user