This commit is contained in:
Balázs Szücs 2025-11-14 17:37:21 +00:00 committed by GitHub
commit 85e7022f34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 227 additions and 12 deletions

View File

@ -1107,8 +1107,11 @@
"textRtf": "Text/RTF",
"grayscale": "Greyscale",
"errorConversion": "An error occurred while converting the file.",
"cbzOptions": "CBZ to PDF Options",
"cbrOptions": "CBR to PDF Options",
"optimizeForEbook": "Optimize PDF for ebook readers (uses Ghostscript)",
"cbrOutputOptions": "PDF to CBR Options",
"cbrDpi": "DPI for image rendering",
"cbzOptions": "CBZ to PDF Options",
"cbzOutputOptions": "PDF to CBZ Options",
"cbzDpi": "DPI for image rendering"
},
@ -2206,7 +2209,7 @@
},
"cta": "Compare",
"loading": "Comparing...",
"summary": {
"baseHeading": "Original document",
"comparisonHeading": "Edited document",
@ -2262,7 +2265,7 @@
"body": "This comparison is taking longer than usual. You can let it continue or cancel it.",
"cancel": "Cancel comparison"
},
"newLine": "new-line",
"complex": {
"message": "One or both of the provided documents are large files, accuracy of comparison may be reduced"

View File

@ -0,0 +1,36 @@
import { Stack, Text, Checkbox } 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-settings">
<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;

View File

@ -16,6 +16,8 @@ import ConvertFromEmailSettings from "@app/components/tools/convert/ConvertFromE
import ConvertFromCbzSettings from "@app/components/tools/convert/ConvertFromCbzSettings";
import ConvertToCbzSettings from "@app/components/tools/convert/ConvertToCbzSettings";
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 {
FROM_FORMAT_OPTIONS,
@ -120,6 +122,12 @@ const ConvertSettings = ({
onParameterChange('pdfaOptions', {
outputFormat: 'pdfa-1',
});
onParameterChange('cbrOptions', {
optimizeForEbook: false,
});
onParameterChange('pdfToCbrOptions', {
dpi: 150,
});
onParameterChange('cbzOptions', {
optimizeForEbook: false,
});
@ -188,6 +196,12 @@ const ConvertSettings = ({
onParameterChange('pdfaOptions', {
outputFormat: 'pdfa-1',
});
onParameterChange('cbrOptions', {
optimizeForEbook: false,
});
onParameterChange('pdfToCbrOptions', {
dpi: 150,
});
onParameterChange('cbzOptions', {
optimizeForEbook: false,
});
@ -344,6 +358,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>
);
};

View File

@ -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-settings">
<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={(val) =>
typeof val === 'number' &&
onParameterChange('pdfToCbrOptions', { ...parameters.pdfToCbrOptions, dpi: val })
}
min={72}
max={600}
step={50}
disabled={disabled}
/>
</Stack>
);
};
export default ConvertToCbrSettings;

View File

@ -33,7 +33,9 @@ export const CONVERSION_ENDPOINTS = {
'pdf-pdfa': '/api/v1/convert/pdf/pdfa',
'html-pdf': '/api/v1/convert/html/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;
export const ENDPOINT_NAMES = {
@ -52,7 +54,9 @@ export const ENDPOINT_NAMES = {
'pdf-pdfa': 'pdf-to-pdfa',
'html-pdf': 'html-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;
@ -62,6 +66,7 @@ export const FROM_FORMAT_OPTIONS = [
{ value: 'image', label: 'Images', group: 'Multiple Files' },
{ value: 'pdf', label: 'PDF', group: 'Document' },
{ value: 'cbz', label: 'CBZ', group: 'Archive' },
{ value: 'cbr', label: 'CBR', group: 'Archive' },
{ value: 'docx', label: 'DOCX', group: 'Document' },
{ value: 'doc', label: 'DOC', group: 'Document' },
{ value: 'odt', label: 'ODT', group: 'Document' },
@ -93,6 +98,7 @@ export const TO_FORMAT_OPTIONS = [
{ value: 'docx', label: 'DOCX', group: 'Document' },
{ value: 'odt', label: 'ODT', group: 'Document' },
{ value: 'cbz', label: 'CBZ', group: 'Archive' },
{ value: 'cbr', label: 'CBR', group: 'Archive' },
{ value: 'csv', label: 'CSV', group: 'Spreadsheet' },
{ value: 'pptx', label: 'PPTX', group: 'Presentation' },
{ value: 'odp', label: 'ODP', group: 'Presentation' },
@ -113,7 +119,7 @@ export const TO_FORMAT_OPTIONS = [
export const CONVERSION_MATRIX: Record<string, string[]> = {
'any': ['pdf'], // Mixed files 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', 'cbz'],
'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa', 'cbz', 'cbr'],
'cbz': ['pdf'],
'docx': ['pdf'], 'doc': ['pdf'], 'odt': ['pdf'],
'xlsx': ['pdf'], 'xls': ['pdf'], 'ods': ['pdf'],
@ -123,7 +129,8 @@ export const CONVERSION_MATRIX: Record<string, string[]> = {
'zip': ['pdf'],
'md': ['pdf'],
'txt': ['pdf'], 'rtf': ['pdf'],
'eml': ['pdf']
'eml': ['pdf'],
'cbr': ['pdf']
};
// Map extensions to endpoint keys
@ -138,6 +145,7 @@ export const EXTENSION_TO_ENDPOINT: Record<string, Record<string, string>> = {
'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', 'md': 'pdf-to-markdown',
'html': 'pdf-to-html', 'xml': 'pdf-to-xml',
'pdfa': 'pdf-to-pdfa',
'cbr': 'pdf-to-cbr',
'cbz': 'pdf-to-cbz'
},
'cbz': { 'pdf': 'cbz-to-pdf' },
@ -150,7 +158,8 @@ export const EXTENSION_TO_ENDPOINT: Record<string, Record<string, string>> = {
'zip': { 'pdf': 'html-to-pdf' },
'md': { 'pdf': 'markdown-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];

View File

@ -13,9 +13,7 @@ export const CONVERT_SUPPORTED_FORMATS = [
// Email formats
'eml',
// Archive formats
'zip', 'cbz',
'zip', 'cbr', 'cbz',
// Other
'dbf', 'fods', 'vsd', 'vor', 'vor3', 'vor4', 'uop', 'pct', 'ps', 'pdf',
];

View File

@ -21,6 +21,8 @@ export const shouldProcessFilesSeparately = (
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
// PDF to text-like formats should be one output per input
(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)
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
parameters.toExtension === 'pdf') ||
@ -34,12 +36,12 @@ export const shouldProcessFilesSeparately = (
// Static function that can be used by both the hook and automation executor
export const buildConvertFormData = (parameters: ConvertParameters, selectedFiles: File[]): FormData => {
const formData = new FormData();
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions, cbrOptions, pdfToCbrOptions, cbzOptions, cbzOutputOptions } = parameters;
selectedFiles.forEach(file => {
formData.append("fileInput", file);
});
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions, cbzOptions, cbzOutputOptions } = parameters;
if (isImageFormat(toExtension)) {
formData.append("imageFormat", toExtension);
@ -67,6 +69,10 @@ export const buildConvertFormData = (parameters: ConvertParameters, selectedFile
formData.append("outputFormat", pdfaOptions.outputFormat);
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
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());
} else if (fromExtension === 'cbz' && toExtension === 'pdf') {
formData.append("optimizeForEbook", (cbzOptions?.optimizeForEbook ?? false).toString());
} else if (fromExtension === 'pdf' && toExtension === 'cbz') {

View File

@ -36,6 +36,12 @@ export interface ConvertParameters extends BaseParameters {
pdfaOptions: {
outputFormat: string;
};
cbrOptions: {
optimizeForEbook: boolean;
};
pdfToCbrOptions: {
dpi: number;
};
cbzOptions: {
optimizeForEbook: boolean;
};
@ -75,6 +81,12 @@ export const defaultParameters: ConvertParameters = {
pdfaOptions: {
outputFormat: 'pdfa-1',
},
cbrOptions: {
optimizeForEbook: false,
},
pdfToCbrOptions: {
dpi: 150,
},
cbzOptions: {
optimizeForEbook: false,
},

View File

@ -152,6 +152,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -225,6 +231,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -276,6 +288,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -336,6 +354,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -400,6 +424,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -462,6 +492,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -520,6 +556,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -575,6 +617,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -632,6 +680,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -686,6 +740,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -746,6 +806,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},
@ -805,6 +871,12 @@ describe('Convert Tool Integration Tests', () => {
pdfaOptions: {
outputFormat: ''
},
cbrOptions: {
optimizeForEbook: false
},
pdfToCbrOptions: {
dpi: 150
},
cbzOptions: {
optimizeForEbook: false
},

View File

@ -28,6 +28,8 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/pdf-to-pdfa': 'convert',
'/pdf-to-word': 'convert',
'/pdf-to-xml': 'convert',
'/cbr-to-pdf': 'convert',
'/pdf-to-cbr': 'convert',
'/cbz-to-pdf': 'convert',
'/pdf-to-cbz': 'convert',