feat(conversion): add PDF to EPUB/AZW3 conversion support and settings (#5434)

# Description of Changes


This pull request introduces support for converting PDF files to eBook
formats (EPUB and AZW3) in the frontend. It adds new user interface
options for PDF-to-eBook conversion, updates the conversion logic and
parameters, and ensures the new formats are integrated into the
conversion matrix and endpoints. The most important changes are grouped
below:

**PDF to eBook (EPUB/AZW3) Conversion Support**

* Added a new `ConvertToEpubSettings` component that provides UI
controls for PDF-to-eBook options, including chapter detection, target
device selection, and output format.
(`frontend/src/core/components/tools/convert/ConvertToEpubSettings.tsx`)
* Updated `ConvertSettings` to render the new eBook options when
converting from PDF to EPUB or AZW3, and set default values for these
options.
(`frontend/src/core/components/tools/convert/ConvertSettings.tsx`)
* Extended the `ConvertParameters` interface and default parameters to
include `epubOptions` for the new settings.
(`frontend/src/core/hooks/tools/convert/useConvertParameters.ts`)

**Conversion Logic and API Integration**

* Updated the conversion endpoints, endpoint names, and conversion
matrix to support PDF-to-EPUB/AZW3 conversions.
(`frontend/src/core/constants/convertConstants.ts`)
* Modified the conversion operation logic to handle `epubOptions` and
ensure that PDF-to-eBook conversions process each file separately and
send the correct options to the backend.
(`frontend/src/core/hooks/tools/convert/useConvertOperation.ts`)
**Localization and Tool Registry Updates**

* Added localization strings for the new eBook conversion options.
(`frontend/public/locales/en-GB/translation.toml`)
* Registered the new PDF-to-eBook operation in the tool catalog and test
helpers. (`frontend/src/core/data/useTranslatedToolRegistry.tsx`,
`frontend/src/core/tests/helpers/conversionEndpointDiscovery.ts`)



<img width="364" height="995" alt="image"
src="https://github.com/user-attachments/assets/c54c50c0-1b86-4074-aef8-b038c6caeb49"
/>

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

Signed-off-by: Balázs Szücs <[email protected]>
Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
Balázs Szücs
2026-01-13 22:22:23 +00:00
committed by GitHub
co-authored by Anthony Stirling
parent e7b030e6b5
commit b00bd760c8
8 changed files with 188 additions and 26 deletions
@@ -20,6 +20,7 @@ import ConvertToPdfaSettings from "@app/components/tools/convert/ConvertToPdfaSe
import ConvertFromCbrSettings from "@app/components/tools/convert/ConvertFromCbrSettings";
import ConvertToCbrSettings from "@app/components/tools/convert/ConvertToCbrSettings";
import ConvertFromEbookSettings from "@app/components/tools/convert/ConvertFromEbookSettings";
import ConvertToEpubSettings from "@app/components/tools/convert/ConvertToEpubSettings";
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
import {
FROM_FORMAT_OPTIONS,
@@ -163,6 +164,11 @@ const ConvertSettings = ({
includePageNumbers: false,
optimizeForEbook: false,
});
onParameterChange('epubOptions', {
detectChapters: true,
targetDevice: 'TABLET_PHONE_IMAGES',
outputFormat: 'EPUB',
});
onParameterChange('isSmartDetection', false);
onParameterChange('smartDetectionType', 'none');
};
@@ -423,6 +429,18 @@ const ConvertSettings = ({
</>
)}
{/* PDF to EPUB/AZW3 options */}
{parameters.fromExtension === 'pdf' && ['epub', 'azw3'].includes(parameters.toExtension) && (
<>
<Divider />
<ConvertToEpubSettings
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
</>
)}
</Stack>
);
};
@@ -0,0 +1,103 @@
import { Stack, Select, Checkbox } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters";
interface ConvertToEpubSettingsProps {
parameters: ConvertParameters;
onParameterChange: <K extends keyof ConvertParameters>(key: K, value: ConvertParameters[K]) => void;
disabled?: boolean;
}
const ConvertToEpubSettings = ({
parameters,
onParameterChange,
disabled = false
}: ConvertToEpubSettingsProps) => {
const { t } = useTranslation();
const handleDetectChaptersChange = (value: boolean) => {
onParameterChange('epubOptions', {
detectChapters: value,
targetDevice: parameters.epubOptions?.targetDevice ?? 'TABLET_PHONE_IMAGES',
outputFormat: parameters.epubOptions?.outputFormat ?? parameters.toExtension === 'azw3' ? 'AZW3' : 'EPUB',
});
};
const handleTargetDeviceChange = (value: string | null) => {
if (value) {
onParameterChange('epubOptions', {
detectChapters: parameters.epubOptions?.detectChapters ?? true,
targetDevice: value,
outputFormat: parameters.epubOptions?.outputFormat ?? parameters.toExtension === 'azw3' ? 'AZW3' : 'EPUB',
});
}
};
const handleOutputFormatChange = (value: string | null) => {
if (value) {
onParameterChange('epubOptions', {
detectChapters: parameters.epubOptions?.detectChapters ?? true,
targetDevice: parameters.epubOptions?.targetDevice ?? 'TABLET_PHONE_IMAGES',
outputFormat: value,
});
}
};
// Initialize epubOptions if not present, set output format based on toExtension
const epubOptions = parameters.epubOptions || {
detectChapters: true,
targetDevice: 'TABLET_PHONE_IMAGES',
outputFormat: parameters.toExtension === 'azw3' ? 'AZW3' : 'EPUB',
};
// Sync output format with selected target extension if not manually set
if (parameters.toExtension === 'azw3' && epubOptions.outputFormat !== 'AZW3') {
handleOutputFormatChange('AZW3');
} else if (parameters.toExtension === 'epub' && epubOptions.outputFormat !== 'EPUB') {
handleOutputFormatChange('EPUB');
}
return (
<Stack gap="sm" data-testid="epub-settings">
<Checkbox
label={t("convert.epubOptions.detectChapters", "Detect chapters")}
description={t("convert.epubOptions.detectChaptersDesc", "Detect headings that look like chapters and insert EPUB page breaks")}
checked={epubOptions.detectChapters}
onChange={(event) => handleDetectChaptersChange(event.currentTarget.checked)}
disabled={disabled}
/>
<Select
label={t("convert.epubOptions.targetDevice", "Target device")}
description={t("convert.epubOptions.targetDeviceDesc", "Choose an output profile optimized for the reader device")}
value={epubOptions.targetDevice}
onChange={handleTargetDeviceChange}
disabled={disabled}
data={[
{
value: 'TABLET_PHONE_IMAGES',
label: t("convert.epubOptions.tabletPhone", "Tablet/Phone (with images)")
},
{
value: 'KINDLE_EINK_TEXT',
label: t("convert.epubOptions.kindleEink", "Kindle e-Ink (text optimized)")
}
]}
/>
<Select
label={t("convert.epubOptions.outputFormat", "Output format")}
description={t("convert.epubOptions.outputFormatDesc", "Choose the output format for the ebook")}
value={epubOptions.outputFormat}
onChange={handleOutputFormatChange}
disabled={disabled}
data={[
{ value: 'EPUB', label: 'EPUB' },
{ value: 'AZW3', label: 'AZW3' }
]}
/>
</Stack>
);
};
export default ConvertToEpubSettings;