Compare commits

...
Author SHA1 Message Date
Anthony Stirling 5d3739e1d3 urls 2025-09-26 14:48:15 +01:00
Anthony Stirling 8aa52a4fa6 remove tools 2025-09-26 14:43:38 +01:00
EthanHealy01 0bdc6466ca add the reorganize pages tool (#4506) 2025-09-26 12:49:18 +01:00
EthanHealy01 f2a6e95fcf Feature/remove images (#4503) 2025-09-26 12:46:02 +01:00
EthanHealy01 0c08764669 add attatchments tool (#4502) 2025-09-26 12:45:31 +01:00
Anthony Stirling 9758e871d4 feat: Add React-based extract-images tool (#4501) 2025-09-26 12:45:15 +01:00
Anthony Stirling 18fa16f08e Invert colors (#4498) 2025-09-26 12:44:25 +01:00
Anthony StirlingandClaude 233b710b78 Convert extract-image-scans to React component (#4505)
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-26 12:38:10 +01:00
Reece Browne d613a4659e Feature/v2/exportpdf (#4487)
# Description of Changes

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

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

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-26 10:28:09 +01:00
Reece Browne 03f484e0c0 Fix (#4495)
# Description of Changes

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

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

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-26 10:03:35 +01:00
EthanHealy01 fd52dc0226 Feature/toasts and error handling (#4496)
# Description of Changes

- Added error handling and toast notifications

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-25 21:03:53 +01:00
EthanHealy01 21b1428ab5 Feature/v2/fuzzy tool search (#4482)
# Description of Changes

<!--
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)
- [x] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-25 16:32:30 +01:00
Anthony Stirling 1219cebd07 Language stuff (#4490)
# Description of Changes

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

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

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-25 12:50:19 +01:00
Anthony Stirling 166f6d2d98 path (#4488)
# Description of Changes

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

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

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-24 20:37:51 +01:00
Anthony Stirling 6441dc1d6f fixes V2 (#4483)
# Description of Changes

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

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

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-24 15:01:18 +01:00
25bedf064f Update PDF.js integration and dependencies (#4360)
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2025-09-24 13:07:27 +01:00
EthanHealy01 13eff6b333 Addition of the Add Stamp to PDF tool (#4440) 2025-09-23 14:01:42 +01:00
46a4a978fc Booklet and server sign (#4371)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: a <a>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2025-09-23 11:24:48 +01:00
c76edebf0f Add Crop to V2 (#4471)
# Description of Changes
Add Crop to V2

---------

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-22 14:06:20 +01:00
f6df414425 Make Viewer always accessible (#4468)
# Description of Changes
Make Viewer always accessible. Also deletes some dead code I noticed
while I was working.

---------

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2025-09-22 12:11:34 +01:00
Anthony StirlingandConnorYoh 28177d9509 API Cleanup (#4459)
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-22 11:56:07 +01:00
James BruntonandEthanHealy01 64beb4d076 Disable language selection for everything other than English GB (#4467)
# Description of Changes
For the first release of V2, we'll not have any reasonable translations
for anything other than English GB, so with that in mind, this PR
disables language selection for anything other than English GB, with a
tooltip saying the other languages are coming soon. I also split the JSX
up a little bit while I was at it to make it easier to manage.

---------

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2025-09-22 10:50:49 +00:00
James Brunton 9cbd1f7f0c Normalise tool IDs and fix file history to render IDs properly (#4470)
# Description of Changes
Normalise tool IDs and fix file history to render IDs properly
2025-09-22 11:46:56 +01:00
065bb46c1e Feature/v2/embed pdf (#4437)
Switched to Embed pdf for viewer

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: James Brunton <james@stirlingpdf.com>
2025-09-19 15:35:51 +01:00
Anthony Stirling f29bbfd15b update some langs in V2 (#4469) 2025-09-19 12:43:11 +01:00
James Brunton ae7be50ec2 Make accordion hook (#4464)
# Description of Changes
Make accordion hook, which controls step collapsed state, enforcing only
one step being open at any time
2025-09-18 13:33:54 +01:00
EthanHealy01 d2de8e54aa change bulk selection panel to allow more versatile input (#4394)
# Description of Changes

- Add features to BulkSelectionPanel to allow more versatility when
selecting pages
- Make changes to Tooltip to: Remove non-existent props delayAppearance,
fixed defaults no hardcoded maxWidth, and documented new props
(closeOnOutside, containerStyle, minWidth). Clarify pinned vs. unpinned
outside-click logic, hover/focus interactions, and event/ref
preservation.
- Made top controls show full text always rather than dynamically
display the text only for the selected items
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-18 10:19:52 +00:00
James Brunton 06e5205302 V2 rotate (#4452)
# Description of Changes
Add Rotate tool to V2
2025-09-18 11:04:12 +01:00
James Brunton a5693ee116 V2 change metadata (#4433)
# Description of Changes
Add Change Metadata tool
2025-09-18 09:41:39 +00:00
EthanHealy01 756cbc4780 Feature/v2/remove pages (#4445)
# Description of Changes

- Addition of the remove pages tool
- Addition of the remove blank pages tool

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.
2025-09-18 09:51:55 +01:00
James Brunton 7ff1c66d09 Remove custom response handler from Merge (#4457)
# Description of Changes
Remove custom response handler from Merge. Also make `filePrefix`
mandatory for `multiFile` tools to make the output more visually
different since you get a new 'V1' file rather than 'V2' of the current
file.
2025-09-17 15:11:36 +01:00
ConnorYohandConnor Yoh b51c2e42a6 Removed optionality for 'onDownloadFile', removed fallback in fileEditorThumbnail. (#4456)
Removed optionality for 'onDownloadFile', removed fallback in
fileEditorThumbnail.

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-17 11:53:04 +01:00
James Brunton 6a94e10e30 Delete Claude local settings (#4455)
# Description of Changes
Delete Claude local settings, which shouldn't really be in the repo.
Note that this is already in the `.gitignore` file, so there's no need
to change that as well.
2025-09-17 11:43:06 +01:00
Anthony Stirling 49a10a3865 Change fallback base URL to 'https://stirling.com' 2025-09-17 08:34:55 +01:00
ConnorYohandConnor Yoh 190178a471 Feature/v2/filehistory (#4370)
File History

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-16 15:08:11 +01:00
ConnorYohandConnor Yoh 8e8b417f5e V2 Tool - Auto split (#4446)
integrated auto split, with flattened split tool

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-16 13:08:54 +01:00
ConnorYohandConnor Yoh a57373b968 V2 Flatten split options to remove layers of drop downs (#4439)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-09-15 16:11:29 +00:00
James Brunton 7dad484aa7 Improve type info on param hooks (#4438)
# Description of Changes
Changes it so that callers of `useBaseTool` know what actual type the
parameters hook that they passed in returned, so they can actually make
use of any extra methods that that params hook has.
2025-09-15 14:28:18 +01:00
James Brunton cfdb6eaa1e Add Adjust Page Scale tool to V2 (#4429)
# Description of Changes
Add Adjust Page Scale tool to V2
2025-09-12 17:25:22 +01:00
James Brunton 8a367aab54 Change tips icon to i circle (#4430)
# Description of Changes

## Before

<img width="102" height="35" alt="image"
src="https://github.com/user-attachments/assets/fcb85906-85b6-41e1-9162-4084c0e684ec"
/>

## After

<img width="103" height="45" alt="image"
src="https://github.com/user-attachments/assets/241d61d8-d3c4-4dbf-a6af-4fda0867734d"
/>
2025-09-10 18:19:05 +01:00
James Brunton f3fd85d777 Add Merge UI to V2 (#4235)
# Description of Changes
Add UI for Merge into V2.
2025-09-10 13:06:23 +00:00
James Brunton 9d723eae69 Add auto-redact to V2 (#4417)
# Description of Changes
Adds auto-redact tool to V2, with manual-redact in the UI but explicitly
disabled.

Also creates a shared component for the large buttons we're using in a
couple different tools and uses consistently.
2025-09-10 14:03:11 +01:00
James Brunton 494ef801a2 Improve npm scripts (#4424)
# Description of Changes
Change NPM scripts so they call each other (single source of truth) and
add a command to run type checking, linting and tests (to give
confidence CI will pass).
2025-09-09 16:18:09 +01:00
LudyandConnorYoh e8af4f6b35 Set i18n to load only current language (#4359)
This pull request introduces a minor configuration change to the i18n
setup in the frontend. The change improves language loading behavior by
ensuring only the current language is loaded, which can help optimize
performance and prevent unnecessary resource usage.

* Added the `load: 'currentOnly'` option to the i18n initialization in
`frontend/src/i18n.ts`, so only the current language is loaded.

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-08 10:05:49 +01:00
stirlingbot[bot]andConnorYoh c25985e49e Update Frontend 3rd Party Licenses (#4319)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-09-08 08:58:22 +00:00
James Brunton 316be5eac5 Fix types of onParameterChange methods (#4415)
# Description of Changes
Fix types of onParameterChange methods
2025-09-08 09:55:30 +01:00
11d23a2d43 V2 Auto rename (#4244)
# Description of Changes

This pull request introduces the new "Auto Rename PDF" tool to the
frontend, enabling users to automatically rename PDF files based on
their content. The implementation includes UI components, parameter
handling, operation logic, localization, and enhancements to the file
response utilities to support backend-provided filenames. Below are the
most important changes grouped by theme:

**Feature: Auto Rename PDF Tool**

- Added the main `AutoRename` tool component (`AutoRename.tsx`) and
registered it in the tool registry, enabling selection and execution of
the auto-rename operation in the UI.
[[1]](diffhunk://#diff-3647ca39d46d109d122d4cd6cbfe981beb4189d05b1b446e5c46824eb98a4a88R1-R80)
[[2]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969R17)
[[3]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969L359-R366)
[[4]](diffhunk://#diff-29427b8d06a23772c56645fc4b72af2980c813605abc162e3d47c2e39d026d06L25-R26)
- Implemented the settings panel (`AutoRenameSettings.tsx`) and
parameter management hook (`useAutoRenameParameters.ts`), allowing users
to configure options such as using the first text as a fallback for the
filename.
[[1]](diffhunk://#diff-b2f9474c8e5a7a42df00a12ffd2d31a785895fe1096e8ca515e6af5633a4d648R1-R27)
[[2]](diffhunk://#diff-8798a1ef451233bf3a1bf8825c12c5b434ad1a17a1beb1ca21fd972fdaceb50cR1-R19)
- Created the operation hook (`useAutoRenameOperation.ts`) to handle API
requests, error handling, and result processing for the auto-rename
feature.

**Localization**

- Added English (US and GB) translations for the new tool, including UI
labels, descriptions, error messages, and settings.
[[1]](diffhunk://#diff-e4d543afa388d9eb8a423e45dfebb91641e3558d00848d70b285ebb91c40b249R1048-R1066)
[[2]](diffhunk://#diff-14c707e28788a3a84ed5293ff6689be73d4bca00e155beaf090f9b37c978babbR1321-R1339)

**File Response Handling Enhancements**

- Updated the file response processor and related hooks to support
preserving backend-provided filenames via the `Content-Disposition`
header, ensuring files are renamed according to backend results.
[[1]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0R11)
[[2]](diffhunk://#diff-97ea1c842d4b269c566a3085d8555ded7f9b462d9ce8dc73706bec79fe3973e0L49-R51)
[[3]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191R52-R58)
[[4]](diffhunk://#diff-d44da7f96721d9829f3c20bf9c7ac5b9e156b647d2c75d76e861c8c09abc5191L175-R183)
[[5]](diffhunk://#diff-fa8af80f4d87370d58e3a5b79df675d201f0c3aa753eda89cec03ff027c4213dL13-R21)
[[6]](diffhunk://#diff-efa525dbdeceaeb5701aa3d2303bf1d533541f65a92d985f94f33b8e87b036d1R2-R37)

These changes collectively deliver a new advanced tool for users to
automatically rename PDFs, with robust parameter handling, user
interface integration, and proper handling of filenames as determined by
backend logic.
---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

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

### Testing (if applicable)

- [ ] 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.

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2025-09-05 17:12:52 +01:00
504 changed files with 45281 additions and 69892 deletions
-22
View File
@@ -1,22 +0,0 @@
{
"permissions": {
"allow": [
"Bash(chmod:*)",
"Bash(mkdir:*)",
"Bash(./gradlew:*)",
"Bash(grep:*)",
"Bash(cat:*)",
"Bash(find:*)",
"Bash(npm test)",
"Bash(npm test:*)",
"Bash(ls:*)",
"Bash(npx tsc:*)",
"Bash(node:*)",
"Bash(npm run dev:*)",
"Bash(sed:*)",
"Bash(npm run typecheck:*)"
],
"deny": [],
"defaultMode": "acceptEdits"
}
}
+85 -89
View File
@@ -3,7 +3,15 @@ name: Auto PR V2 Deployment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
workflow_dispatch:
inputs:
pr:
description: "PR number to deploy"
required: true
allow_fork:
description: "Allow deploying fork PR?"
required: false
default: "false"
permissions:
contents: read
@@ -12,102 +20,93 @@ permissions:
jobs:
check-pr:
if: github.event.action != 'closed'
if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
should_deploy: ${{ steps.check-conditions.outputs.should_deploy }}
pr_number: ${{ github.event.number }}
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
should_deploy: ${{ steps.decide.outputs.should_deploy }}
is_fork: ${{ steps.resolve.outputs.is_fork }}
allow_fork: ${{ steps.decide.outputs.allow_fork }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
pr_repository: ${{ steps.resolve.outputs.repository }}
pr_ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
with:
egress-policy: audit
- name: Check deployment conditions
id: check-conditions
- name: Resolve PR info
id: resolve
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { owner, repo } = context.repo;
let prNumber = context.eventName === 'workflow_dispatch'
? parseInt(process.env.INPUT_PR, 10)
: context.payload.number;
if (!Number.isInteger(prNumber)) { core.setFailed('Invalid PR number'); return; }
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
core.setOutput('pr_number', String(prNumber));
core.setOutput('repository', pr.head.repo.full_name);
core.setOutput('ref', pr.head.ref);
core.setOutput('is_fork', String(pr.head.repo.fork));
core.setOutput('base_ref', pr.base.ref);
core.setOutput('author', pr.user.login);
core.setOutput('state', pr.state);
- name: Decide deploy
id: decide
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
STATE: ${{ steps.resolve.outputs.state }}
IS_FORK: ${{ steps.resolve.outputs.is_fork }}
# nur bei workflow_dispatch gesetzt:
ALLOW_FORK_INPUT: ${{ inputs.allow_fork }}
# für Auto-PR-Logik:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
PR_BASE: ${{ steps.resolve.outputs.base_ref }}
PR_AUTHOR: ${{ steps.resolve.outputs.author }}
run: |
echo "PR Title: $PR_TITLE"
echo "PR Author: $PR_AUTHOR"
echo "PR Branch: $PR_BRANCH"
echo "PR Base Branch: ${{ github.event.pull_request.base.ref }}"
# Define authorized users
authorized_users=(
"Frooodle"
"sf298"
"Ludy87"
"LaserKaspar"
"sbplat"
"reecebrowne"
"DarioGii"
"ConnorYoh"
"EthanHealy01"
"jbrunton96"
)
# Check if author is in the authorized list
is_authorized=false
for user in "${authorized_users[@]}"; do
if [[ "$PR_AUTHOR" == "$user" ]]; then
is_authorized=true
break
set -e
# Standard: nichts deployen
should=false
allow_fork="$(echo "${ALLOW_FORK_INPUT:-false}" | tr '[:upper:]' '[:lower:]')"
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
if [ "$STATE" != "open" ]; then
echo "PR not open -> skip"
else
if [ "$IS_FORK" = "true" ] && [ "$allow_fork" != "true" ]; then
echo "Fork PR and allow_fork=false -> skip"
else
should=true
fi
fi
done
# If PR is targeting V2 and user is authorized, deploy unconditionally
PR_BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
if [[ "$PR_BASE_BRANCH" == "V2" && "$is_authorized" == "true" ]]; then
echo "✅ Deployment forced: PR targets V2 and author is authorized."
echo "should_deploy=true" >> $GITHUB_OUTPUT
exit 0
fi
# Otherwise, continue with original keyword checks
has_v2_keyword=false
if [[ "$PR_TITLE" =~ [Vv]2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?[Tt]wo ]]; then
has_v2_keyword=true
fi
has_branch_keyword=false
if [[ "$PR_BRANCH" =~ [Vv]2 ]] || [[ "$PR_BRANCH" =~ [Rr]eact ]]; then
has_branch_keyword=true
fi
if [[ "$is_authorized" == "true" && ( "$has_v2_keyword" == "true" || "$has_branch_keyword" == "true" ) ]]; then
echo "✅ Deployment conditions met"
echo "should_deploy=true" >> $GITHUB_OUTPUT
else
echo "❌ Deployment conditions not met"
echo " - Authorized user: $is_authorized"
echo " - Has V2 keyword in title: $has_v2_keyword"
echo " - Has V2/React keyword in branch: $has_branch_keyword"
echo "should_deploy=false" >> $GITHUB_OUTPUT
auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "DarioGii" "ConnorYoh" "EthanHealy01" "jbrunton96")
is_auth=false; for u in "${auth_users[@]}"; do [ "$u" = "$PR_AUTHOR" ] && is_auth=true && break; done
if [ "$PR_BASE" = "V2" ] && [ "$is_auth" = true ]; then
should=true
else
title_has_v2=false; echo "$PR_TITLE" | grep -qiE 'v2|version.?2|version.?two' && title_has_v2=true
branch_has_kw=false; echo "$PR_BRANCH" | grep -qiE 'v2|react' && branch_has_kw=true
if [ "$is_auth" = true ] && { [ "$title_has_v2" = true ] || [ "$branch_has_kw" = true ]; }; then
should=true
fi
fi
fi
- name: Get PR repository and ref
id: get-pr-info
if: steps.check-conditions.outputs.should_deploy == 'true'
run: |
# For forks, use the full repository name, for internal PRs use the current repo
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
repository="${{ github.event.pull_request.head.repo.full_name }}"
else
repository="${{ github.repository }}"
fi
echo "repository=$repository" >> $GITHUB_OUTPUT
echo "ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
echo "should_deploy=$should" >> $GITHUB_OUTPUT
echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT
deploy-v2-pr:
needs: check-pr
runs-on: ubuntu-latest
if: needs.check-pr.outputs.should_deploy == 'true'
if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true')
# Concurrency control - only one deployment per PR at a time
concurrency:
group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }}
@@ -119,7 +118,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
with:
egress-policy: audit
@@ -154,13 +153,13 @@ jobs:
issue_number: prNumber,
per_page: 100
});
const v2Comments = comments.filter(comment =>
comment.body.includes('🚀 **Auto-deploying V2 version**') ||
comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') ||
comment.body.includes('❌ **V2 Auto-deployment failed**')
);
for (const comment of v2Comments) {
console.log(`Deleting old V2 comment: ${comment.id}`);
await github.rest.issues.deleteComment({
@@ -177,7 +176,6 @@ jobs:
issue_number: prNumber,
body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.`
});
return newComment.id;
- name: Checkout PR
@@ -188,7 +186,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # Fetch full history for commit hash detection
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
@@ -212,7 +209,7 @@ jobs:
if [ -z "$FRONTEND_HASH" ]; then
FRONTEND_HASH="no-frontend-changes"
fi
# Get last commit that touched backend code, docker/backend, or docker/compose
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$BACKEND_HASH" ]; then
@@ -321,7 +318,7 @@ jobs:
SWAGGER_SERVER_URL: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
baseUrl: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
restart: on-failure:5
stirling-pdf-v2-frontend:
container_name: stirling-pdf-v2-frontend-pr-${{ needs.check-pr.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
@@ -354,7 +351,7 @@ jobs:
# Clean up unused Docker resources to save space
docker system prune -af --volumes || true
# Clean up old backend/frontend images (older than 2 weeks)
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
@@ -492,7 +489,7 @@ jobs:
# Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
# Note: We don't remove the commit-based images since they can be reused across PRs
# Only remove PR-specific containers and directories
ENDSSH
@@ -501,5 +498,4 @@ jobs:
if: always()
run: |
rm -f ../private.key
continue-on-error: true
continue-on-error: true
+69 -5
View File
@@ -32,18 +32,29 @@ jobs:
with:
egress-policy: audit
- name: Check out code
- name: Checkout PR head (default)
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup GitHub App Bot
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout BASE branch (safe script)
if: github.event_name == 'pull_request'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.base.sha }}
path: base
fetch-depth: 1
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
@@ -53,12 +64,45 @@ jobs:
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
env:
NPM_CONFIG_IGNORE_SCRIPTS: "true"
run: npm ci --ignore-scripts --audit=false --fund=false
- name: Generate frontend license report
- name: Generate frontend license report (internal PR)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
working-directory: frontend
env:
PR_IS_FORK: "false"
run: npm run generate-licenses
- name: Generate frontend license report (fork PRs, pinned)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
env:
NPM_CONFIG_IGNORE_SCRIPTS: "true"
working-directory: frontend
run: |
mkdir -p src/assets
npx --yes license-report --only=prod --output=json > src/assets/3rdPartyLicenses.json
- name: Postprocess with project script (BASE version)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
env:
PR_IS_FORK: "true"
run: |
node base/frontend/scripts/generate-licenses.js \
--input frontend/src/assets/3rdPartyLicenses.json
- name: Copy postprocessed artifacts back (fork PRs)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: |
mkdir -p frontend/src/assets
if [ -f "base/frontend/src/assets/3rdPartyLicenses.json" ]; then
cp base/frontend/src/assets/3rdPartyLicenses.json frontend/src/assets/3rdPartyLicenses.json
fi
if [ -f "base/frontend/src/assets/license-warnings.json" ]; then
cp base/frontend/src/assets/license-warnings.json frontend/src/assets/license-warnings.json
fi
- name: Check for license warnings
run: |
if [ -f "frontend/src/assets/license-warnings.json" ]; then
@@ -69,7 +113,7 @@ jobs:
# PR Event: Check licenses and comment on PR
- name: Delete previous license check comments
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
@@ -101,8 +145,28 @@ jobs:
});
}
- name: Summarize results (fork PRs)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: |
{
echo "## Frontend License Check"
echo ""
if [ "${LICENSE_WARNINGS_EXIST}" = "true" ]; then
echo "❌ **Failed** incompatible or unknown licenses found."
if [ -f "frontend/src/assets/license-warnings.json" ]; then
echo ""
echo "### Warnings"
jq -r '.warnings[] | "- \(.message)"' frontend/src/assets/license-warnings.json || true
fi
else
echo "✅ **Passed** no license warnings detected."
fi
echo ""
echo "_Note: This is a fork PR. PR comments are disabled; use this summary._"
} >> "$GITHUB_STEP_SUMMARY"
- name: Comment on PR - License Check Results
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
+7
View File
@@ -203,3 +203,10 @@ id_ed25519.pub
# node_modules
node_modules/
# Translation temp files
*_compact.json
*compact*.json
test_batch.json
*.backup.*.json
frontend/public/locales/*/translation.backup*.json
+4 -1
View File
@@ -139,5 +139,8 @@
"app/core/src/main/java",
"app/common/src/main/java",
"app/proprietary/src/main/java"
]
],
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}
+4 -6
View File
@@ -39,7 +39,7 @@ Frontend designed for **stateful document processing**:
#### FileContext - Central State Management
**Location**: `src/contexts/FileContext.tsx`
- **Active files**: Currently loaded PDFs and their variants
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
- **IndexedDB persistence**: File storage with thumbnail caching
- **Preview system**: Tools can preview results (e.g., Split → Viewer → back to Split) without context pollution
@@ -89,7 +89,6 @@ return useToolOperation({
endpoint: '/api/v1/misc/compress-pdf',
buildFormData: (params, file: File) => { /* single file */ },
multiFileEndpoint: false,
filePrefix: 'compressed_'
});
```
@@ -103,7 +102,7 @@ return useToolOperation({
endpoint: '/api/v1/general/split-pages',
buildFormData: (params, files: File[]) => { /* all files */ },
multiFileEndpoint: true,
filePrefix: 'split_'
filePrefix: 'split_',
});
```
@@ -115,13 +114,12 @@ return useToolOperation({
return useToolOperation({
operationType: 'convert',
customProcessor: async (params, files) => { /* custom logic */ },
filePrefix: 'converted_'
});
```
**Benefits**:
- **No Timeouts**: Operations run until completion (supports 100GB+ files)
- **Consistent**: All tools follow same pattern and interface
- **Consistent**: All tools follow same pattern and interface
- **Maintainable**: Single responsibility hooks, easy to test and modify
- **i18n Ready**: Built-in internationalization support
- **Type Safe**: Full TypeScript support with generic interfaces
@@ -185,7 +183,7 @@ return useToolOperation({
## Frontend Architecture Status
- **Core Status**: React SPA architecture complete with multi-tool workflow support
- **State Management**: FileContext handles all file operations and tool navigation
- **State Management**: FileContext handles all file operations and tool navigation
- **File Processing**: Production-ready with memory management for large PDF workflows (up to 100GB+)
- **Tool Integration**: Modular hook architecture with `useToolOperation` orchestrator
- Individual hooks: `useToolState`, `useToolApiCalls`, `useToolResources`
@@ -6,6 +6,8 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
/**
* Shortcut for a POST endpoint that is executed through the Stirling "autojob" framework.
*
@@ -29,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
@RequestBody(required = true)
public @interface AutoJobPostMapping {
/** Alias for {@link RequestMapping#value} the path mapping of the endpoint. */
@@ -0,0 +1,48 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Account Security API controllers.
* Includes @RestController, @RequestMapping("/api/v1/account"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/account")
@Tag(
name = "Account Security",
description =
"""
Account security and protection services for user safety and organizational compliance.
This endpoint group provides account security capabilities for organizations requiring
enhanced protection against unauthorized access, security threats, and compliance violations.
Common use cases:
• Corporate security policy compliance and SOX, HIPAA, GDPR requirements
• Fraud prevention, identity theft protection, and account compromise recovery
• Multi-factor authentication implementation and insider threat mitigation
• Account recovery and emergency access procedures
Business applications:
• Enterprise risk management, security governance, and customer trust protection
• Legal liability reduction and insurance requirement fulfillment
• Audit preparation, compliance reporting, and business continuity management
Operational scenarios:
• Security incident response, forensic investigation, and user training
• Emergency account lockdown, suspicious activity monitoring, and compliance documentation
Target users: Security administrators, compliance officers, and organizations
prioritizing account security and regulatory compliance.
""")
public @interface AccountSecurityApi {}
@@ -0,0 +1,48 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Admin Settings API controllers.
* Includes @RestController, @RequestMapping("/api/v1/admin/settings"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/settings")
@Tag(
name = "Admin Settings",
description =
"""
System administration and configuration management for enterprise deployments.
This endpoint group provides administrative control for organizations deploying
Stirling PDF in production environments with multi-user scenarios.
Common use cases:
• Enterprise deployment configuration and multi-tenant environment management
• Security policy enforcement, compliance monitoring, and capacity planning
• Operational maintenance, troubleshooting, and enterprise infrastructure integration
• Disaster recovery and business continuity preparation
Business applications:
• Corporate IT governance, policy enforcement, and compliance reporting
• Cost optimization, SLA monitoring, and vendor management oversight
• Risk management and security incident response
Operational scenarios:
• 24/7 production monitoring, scheduled maintenance, and system updates
• Emergency response, change management, and performance optimization
Target users: IT administrators, system engineers, and operations teams
responsible for enterprise-grade document processing infrastructure.
""")
public @interface AdminApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Admin Server Certificate API controllers.
* Includes @RestController, @RequestMapping("/api/v1/admin/server-certificate"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/server-certificate")
@Tag(
name = "Admin - Server Certificate",
description =
"""
Server certificate management for secure enterprise deployments and encrypted communications.
This endpoint group provides certificate lifecycle management for organizations
requiring secure communications in document processing infrastructure.
Common use cases:
• Corporate security compliance and encrypted communications for healthcare/finance
• Customer data protection, internal audits, and multi-environment standardization
• Third-party security assessments and disaster recovery security measures
Business applications:
• Enterprise security governance, client trust protection, and secure B2B exchange
• Legal requirement fulfillment, liability reduction, and M&A security preparation
Operational scenarios:
• Certificate renewal, emergency replacement, and security incident response
• Multi-site deployment coordination and cloud migration preparation
Target users: Security administrators, compliance officers, and IT infrastructure
teams requiring enterprise-grade security for document processing systems.
""")
public @interface AdminServerCertificateApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Analysis API controllers.
* Includes @RestController, @RequestMapping("/api/v1/analysis"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/analysis")
@Tag(
name = "Analysis",
description =
"""
Document analysis and information extraction services for content intelligence and insights.
This endpoint group provides analytical capabilities to understand document structure,
extract information, and generate insights from PDF content for automated processing.
Common use cases:
• Document inventory management and content audit for compliance verification
• Quality assurance workflows and business intelligence analytics
• Migration planning, accessibility evaluation, and document forensics
Business applications:
• Legal discovery, financial document review, and healthcare records analysis
• Academic research, government processing, and publishing optimization
Operational scenarios:
• Large-scale profiling, migration assessment, and performance optimization
• Automated quality control and content strategy development
Target users: Data analysts, QA teams, administrators, and business intelligence
professionals requiring detailed document insights.
""")
public @interface AnalysisApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Configuration API controllers.
* Includes @RestController, @RequestMapping("/api/v1/config"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/config")
@Tag(
name = "Config",
description =
"""
System configuration management for deployment optimization and operational control.
This endpoint group provides system configuration capabilities for organizations
deploying and operating Stirling PDF in various environments.
Common use cases:
• Environment-specific deployment and performance tuning for varying workloads
• Resource optimization, cost management, and infrastructure integration
• Compliance configuration, disaster recovery, and multi-environment standardization
Business applications:
• Operational cost optimization, SLA compliance, and risk management
• Vendor integration, change management, and capacity planning
Operational scenarios:
• System deployment, performance troubleshooting, and emergency changes
• Planned maintenance and multi-site deployment coordination
Target users: System administrators, DevOps engineers, and IT operations teams
responsible for deployment configuration and system optimization.
""")
public @interface ConfigApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Convert API controllers.
* Includes @RestController, @RequestMapping("/api/v1/convert"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/convert")
@Tag(
name = "Convert",
description =
"""
Document format transformation services for cross-platform compatibility and workflow integration.
This endpoint group enables transformation between various formats, supporting
diverse business workflows and system integrations for mixed document ecosystems.
Common use cases:
• Legacy system integration, document migration, and cross-platform sharing
• Archive standardization, publishing preparation, and content adaptation
• Accessibility compliance and mobile-friendly document preparation
Business applications:
• Enterprise content management, digital publishing, and educational platforms
• Legal document processing, healthcare interoperability, and government standardization
Integration scenarios:
• API-driven pipelines, automated workflow preparation, and batch conversions
• Real-time format adaptation for user requests
Target users: System integrators, content managers, digital archivists, and
organizations requiring flexible document format interoperability.
""")
public @interface ConvertApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Database Management API controllers.
* Includes @RestController, @RequestMapping("/api/v1/database"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/database")
@Tag(
name = "Database",
description =
"""
Database operations for data protection and business continuity management.
This endpoint group provides essential database operations for organizations requiring
reliable data protection and recovery capabilities.
Common use cases:
• Regular data backup, disaster recovery, and business continuity planning
• System migration, compliance management, and development environment support
• Operational troubleshooting and scheduled maintenance operations
Business applications:
• Risk management, regulatory compliance, and operational resilience
• Data governance, change management, and quality assurance support
Operational scenarios:
• Routine backup, emergency recovery, and system maintenance preparation
• Data migration projects and performance monitoring
Target users: Operations teams, system administrators, and organizations requiring
reliable data protection and operational database management.
""")
public @interface DatabaseApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Database Management API controllers.
* Includes @RestController, @RequestMapping("/api/v1/admin/database"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/admin/database")
@Tag(
name = "Database Management",
description =
"""
Enterprise database administration for production data management and business continuity.
This endpoint group provides database administration capabilities for organizations
operating Stirling PDF in production environments.
Common use cases:
• Business continuity, disaster recovery, and regulatory compliance requirements
• Performance optimization, data security, and system migration projects
• Audit preparation, compliance reporting, and cost optimization
Business applications:
• Enterprise risk management, regulatory compliance, and SLA monitoring
• Data retention policies, security incident response, and vendor oversight
Operational scenarios:
• Scheduled maintenance, emergency recovery, and capacity planning
• Performance troubleshooting and multi-environment deployment coordination
Target users: Database administrators, IT operations teams, and enterprise
administrators responsible for production data management and system reliability.
""")
public @interface DatabaseManagementApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Filter API controllers.
* Includes @RestController, @RequestMapping("/api/v1/filter"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/filter")
@Tag(
name = "Filter",
description =
"""
Document content filtering and search operations for information discovery and organization.
This endpoint group enables intelligent content discovery and organization within
document collections for content-based processing and information extraction.
Common use cases:
• Legal discovery, research organization, and compliance auditing
• Content moderation, academic research, and business intelligence
• Quality assurance and content validation workflows
Business applications:
• Contract analysis, financial review, and healthcare records organization
• Government processing, educational curation, and IP protection
Workflow scenarios:
• Large-scale processing, automated classification, and information extraction
• Document preparation for further processing or analysis
Target users: Legal professionals, researchers, compliance officers, and
organizations requiring intelligent document content discovery and organization.
""")
public @interface FilterApi {}
@@ -0,0 +1,42 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for General PDF processing API controllers.
* Includes @RestController, @RequestMapping("/api/v1/general"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/general")
@Tag(
name = "General",
description =
"""
Core PDF processing operations for fundamental document manipulation workflows.
This endpoint group provides essential PDF functionality that forms the foundation
of most document processing workflows across various industries.
Common use cases:
• Document preparation for archival systems and content organization
• File preparation for distribution, accessibility compliance, and batch processing
• Document consolidation for reporting and legal compliance workflows
Typical applications:
• Content management, publishing workflows, and educational content distribution
• Business process automation and archive management
Target users: Content managers, document processors, and organizations requiring
reliable foundational PDF manipulation capabilities.
""")
public @interface GeneralApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Info API controllers.
* Includes @RestController, @RequestMapping("/api/v1/info"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/info")
@Tag(
name = "Info",
description =
"""
System information and operational insights for monitoring and performance management.
This endpoint group provides system information and operational metrics for organizations
operating Stirling PDF in production environments.
Common use cases:
• System health monitoring, performance optimization, and capacity planning
• Troubleshooting, compliance monitoring, and SLA reporting
• Cost optimization, usage analysis, and security monitoring
Business applications:
• Operational cost management, business continuity monitoring, and vendor management
• Compliance reporting, strategic planning, and customer service tracking
Operational scenarios:
• 24/7 monitoring, scheduled maintenance, and emergency response coordination
• System upgrade planning and capacity scaling decisions
Target users: Operations teams, system administrators, and management teams requiring
operational insights and system performance visibility.
""")
public @interface InfoApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Miscellaneous API controllers.
* Includes @RestController, @RequestMapping("/api/v1/misc"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/misc")
@Tag(
name = "Misc",
description =
"""
Specialized utilities and supplementary tools for enhanced document processing workflows.
This endpoint group provides utility operations that support core document processing
tasks and address specific workflow needs in real-world scenarios.
Common use cases:
• Document optimization for bandwidth-limited environments and storage cost management
• Document repair, content extraction, and validation for quality assurance
• Accessibility improvement and custom processing for specialized needs
Business applications:
• Web publishing optimization, email attachment management, and archive efficiency
• Mobile compatibility, print production, and legacy document recovery
Operational scenarios:
• Batch processing, quality control, and performance optimization
• Troubleshooting and recovery of problematic documents
Target users: System administrators, document specialists, and organizations requiring
specialized document processing and optimization tools.
""")
public @interface MiscApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Pipeline API controllers.
* Includes @RestController, @RequestMapping("/api/v1/pipeline"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/pipeline")
@Tag(
name = "Pipeline",
description =
"""
Automated document processing workflows for complex multi-stage business operations.
This endpoint group enables organizations to create sophisticated document processing
workflows that combine multiple operations into streamlined, repeatable processes.
Common use cases:
• Invoice processing, legal document review, and healthcare records standardization
• Government processing, educational content preparation, and publishing automation
• Contract lifecycle management and approval processes
Business applications:
• Automated compliance reporting, large-scale migration, and quality assurance
• Archive preparation, content delivery, and document approval workflows
Operational scenarios:
• Scheduled batch processing and event-driven document processing
• Multi-department coordination and business system integration
Target users: Business process managers, IT automation specialists, and organizations
requiring consistent, repeatable document processing workflows.
""")
public @interface PipelineApi {}
@@ -0,0 +1,47 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Proprietary UI Data API controllers.
* Includes @RestController, @RequestMapping("/api/v1/proprietary/ui-data"), and OpenAPI @Tag. Note:
* Controllers using this annotation should also add @EnterpriseEndpoint.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/proprietary/ui-data")
@Tag(
name = "Proprietary UI Data",
description =
"""
Enterprise user interface data services for commercial deployments and advanced business features.
This endpoint group provides enhanced data services for commercial and enterprise features,
supporting advanced business workflows and professional-grade functionality.
Common use cases:
• Enterprise-grade dashboards, multi-tenant deployment, and business intelligence
• Organizational hierarchy management and commercial feature licensing
• Professional support integration and advanced workflow automation
Business applications:
• ERP integration, CRM development, and executive reporting dashboards
• Multi-subsidiary management, professional service delivery, and compliance interfaces
Operational scenarios:
• Large-scale deployment management and white-label solution development
• Advanced system integration and commercial feature rollout
Target users: Enterprise administrators, business analysts, and organizations utilizing
commercial features and advanced business capabilities.
""")
public @interface ProprietaryUiDataApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Security API controllers.
* Includes @RestController, @RequestMapping("/api/v1/security"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/security")
@Tag(
name = "Security",
description =
"""
Document security and protection services for confidential and sensitive content.
This endpoint group provides essential security operations for organizations handling
sensitive documents and materials requiring controlled access.
Common use cases:
• Legal confidentiality, healthcare privacy (HIPAA), and financial regulatory compliance
• Government classified handling, corporate IP protection, and educational privacy (FERPA)
• Contract security for business transactions
Business applications:
• Document authentication, confidential sharing, and secure archiving
• Content watermarking, access control, and privacy protection through redaction
Industry scenarios:
• Legal discovery, medical records exchange, financial audit documentation
• Enterprise policy enforcement and data governance
Target users: Legal professionals, healthcare administrators, compliance officers,
government agencies, and enterprises handling sensitive content.
""")
public @interface SecurityApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Settings API controllers.
* Includes @RestController, @RequestMapping("/api/v1/settings"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/settings")
@Tag(
name = "Settings",
description =
"""
User preferences and application customization for personalized workflow optimization.
This endpoint group provides preference management capabilities for users and
organizations to customize their document processing experience.
Common use cases:
• Workflow optimization, accessibility compliance, and corporate branding
• Multi-language support, user personalization, and business system integration
• Organizational policy compliance
Business applications:
• Corporate branding, productivity optimization, and accessibility compliance
• Change management facilitation and training efficiency improvement
Operational scenarios:
• User onboarding, department-specific customization, and system migration
• Multi-tenant customization and project-based configuration adjustments
Target users: End users, department managers, and organizations focused on optimizing
user experience and workflow efficiency through personalization.
""")
public @interface SettingsApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for Team Management API controllers.
* Includes @RestController, @RequestMapping("/api/v1/team"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/team")
@Tag(
name = "Team",
description =
"""
Team management and collaboration services for organized document processing workflows.
This endpoint group enables organizations to structure collaborative document processing
activities through team-based organization and resource management.
Common use cases:
• Department-based processing, project collaboration, and cross-functional coordination
• Client-specific team isolation, temporary project teams, and training coordination
• Compliance team coordination for regulatory processing
Business applications:
• Matrix organization support, client service delivery, and cost center allocation
• Scalable collaboration, knowledge management, and team-based quality assurance
Operational scenarios:
• Large-scale processing coordination and temporary team formation
• M&A integration, remote collaboration, and knowledge transfer management
Target users: Team leaders, project managers, and organizations requiring structured
collaborative environments for document processing activities.
""")
public @interface TeamApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for UI Data API controllers.
* Includes @RestController, @RequestMapping("/api/v1/ui-data"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/ui-data")
@Tag(
name = "UI Data",
description =
"""
User interface data services for dynamic frontend applications and user experience customization.
This endpoint group provides data services for frontend applications to render personalized
interfaces and deliver optimized experiences based on system configuration.
Common use cases:
• Dynamic UI customization, multi-language support, and feature configuration
• Real-time status delivery, corporate branding, and mobile optimization
• Progressive web application (PWA) configuration
Business applications:
• Brand customization, user experience optimization, and accessibility compliance
• Multi-tenant customization, training support, and performance optimization
Operational scenarios:
• Frontend deployment, UI A/B testing, and system integration
• Mobile synchronization and offline capability enhancement
Target users: Frontend developers, UI/UX designers, and organizations requiring
customizable user interfaces and optimized user experiences.
""")
public @interface UiDataApi {}
@@ -0,0 +1,46 @@
package stirling.software.common.annotations.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* Combined annotation for User Management API controllers.
* Includes @RestController, @RequestMapping("/api/v1/user"), and OpenAPI @Tag.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1/user")
@Tag(
name = "User",
description =
"""
User management and authentication services for multi-user and enterprise environments.
This endpoint group provides user lifecycle management capabilities for organizations
deploying Stirling PDF in multi-user scenarios.
Common use cases:
• Employee onboarding/offboarding and corporate access control
• Department-based permissions, regulatory compliance, and SSO integration
• Multi-tenant deployment and guest user access management
Business applications:
• Enterprise IAM integration, security governance, and cost allocation
• Compliance reporting, workflow management, and partner collaboration
Operational scenarios:
• Large-scale provisioning, automated HR integration, and emergency access
• User migration and self-service profile maintenance
Target users: IT administrators, HR departments, and organizations requiring
structured user management and enterprise identity integration.
""")
public @interface UserApi {}
@@ -4,6 +4,8 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@@ -11,15 +13,20 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@EqualsAndHashCode
@Schema(description = "PDF file input - either upload a file or provide a server-side file ID")
public class PDFFile {
@Schema(
description = "The input PDF file",
contentMediaType = "application/pdf",
format = "binary")
@Schema(description = "The input PDF file", format = "binary")
private MultipartFile fileInput;
@Schema(
description = "File ID for server-side files (can be used instead of fileInput)",
example = "a1b2c3d4-5678-90ab-cdef-ghijklmnopqr")
description =
"File ID for server-side files (can be used instead of fileInput if job was previously done on file in async mode)")
private String fileId;
@AssertTrue(message = "Either fileInput or fileId must be provided")
@Schema(hidden = true)
private boolean isValid() {
return (fileInput != null && (fileId == null || fileId.trim().isEmpty()))
|| (fileId != null && !fileId.trim().isEmpty() && fileInput == null);
}
}
@@ -0,0 +1,42 @@
package stirling.software.common.service;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Getter;
public interface ServerCertificateServiceInterface {
boolean isEnabled();
boolean hasServerCertificate();
void initializeServerCertificate();
KeyStore getServerKeyStore() throws Exception;
String getServerCertificatePassword();
X509Certificate getServerCertificate() throws Exception;
byte[] getServerCertificatePublicKey() throws Exception;
void uploadServerCertificate(InputStream p12Stream, String password) throws Exception;
void deleteServerCertificate() throws Exception;
ServerCertificateInfo getServerCertificateInfo() throws Exception;
@Getter
@AllArgsConstructor
class ServerCertificateInfo {
private final boolean exists;
private final String subject;
private final String issuer;
private final Date validFrom;
private final Date validTo;
}
}
@@ -237,6 +237,7 @@ public class EndpointConfiguration {
addEndpointToGroup("PageOps", "pdf-organizer");
addEndpointToGroup("PageOps", "rotate-pdf");
addEndpointToGroup("PageOps", "multi-page-layout");
addEndpointToGroup("PageOps", "booklet-imposition");
addEndpointToGroup("PageOps", "scale-pages");
addEndpointToGroup("PageOps", "crop");
addEndpointToGroup("PageOps", "extract-page");
@@ -366,6 +367,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "cert-sign");
addEndpointToGroup("Java", "remove-cert-sign");
addEndpointToGroup("Java", "multi-page-layout");
addEndpointToGroup("Java", "booklet-imposition");
addEndpointToGroup("Java", "scale-pages");
addEndpointToGroup("Java", "add-page-numbers");
addEndpointToGroup("Java", "auto-rename");
@@ -0,0 +1,188 @@
package stirling.software.SPDF.config;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springframework.stereotype.Component;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
/**
* Global OpenAPI customizer that adds standard error responses (400, 413, 422, 500) to all API
* operations under /api/v1/** paths.
*/
@Component
public class GlobalErrorResponseCustomizer implements GlobalOpenApiCustomizer {
@Override
public void customise(OpenAPI openApi) {
if (openApi.getPaths() == null) {
return;
}
openApi.getPaths()
.forEach(
(path, pathItem) -> {
if (path.startsWith("/api/v1/")) {
addErrorResponsesToPathItem(pathItem);
}
});
}
private void addErrorResponsesToPathItem(PathItem pathItem) {
if (pathItem.getPost() != null) {
addStandardErrorResponses(pathItem.getPost());
}
if (pathItem.getPut() != null) {
addStandardErrorResponses(pathItem.getPut());
}
if (pathItem.getPatch() != null) {
addStandardErrorResponses(pathItem.getPatch());
}
if (pathItem.getDelete() != null) {
addStandardErrorResponses(pathItem.getDelete());
}
if (pathItem.getGet() != null) {
addStandardErrorResponses(pathItem.getGet());
}
}
private void addStandardErrorResponses(Operation operation) {
if (operation.getResponses() == null) {
return;
}
// Only add error responses if they don't already exist
if (!operation.getResponses().containsKey("400")) {
operation.getResponses().addApiResponse("400", create400Response());
}
if (!operation.getResponses().containsKey("413")) {
operation.getResponses().addApiResponse("413", create413Response());
}
if (!operation.getResponses().containsKey("422")) {
operation.getResponses().addApiResponse("422", create422Response());
}
if (!operation.getResponses().containsKey("500")) {
operation.getResponses().addApiResponse("500", create500Response());
}
}
private ApiResponse create400Response() {
return new ApiResponse()
.description(
"Bad request - Invalid input parameters, unsupported format, or corrupted file")
.content(
new Content()
.addMediaType(
"application/json",
new MediaType()
.schema(
createErrorSchema(
400,
"Invalid input parameters or corrupted file",
"/api/v1/example/endpoint"))
.example(
createErrorExample(
400,
"Invalid input parameters or corrupted file",
"/api/v1/example/endpoint"))));
}
private ApiResponse create413Response() {
return new ApiResponse()
.description("Payload too large - File exceeds maximum allowed size")
.content(
new Content()
.addMediaType(
"application/json",
new MediaType()
.schema(
createErrorSchema(
413,
"File size exceeds maximum allowed limit",
"/api/v1/example/endpoint"))
.example(
createErrorExample(
413,
"File size exceeds maximum allowed limit",
"/api/v1/example/endpoint"))));
}
private ApiResponse create422Response() {
return new ApiResponse()
.description("Unprocessable entity - File is valid but cannot be processed")
.content(
new Content()
.addMediaType(
"application/json",
new MediaType()
.schema(
createErrorSchema(
422,
"File is valid but cannot be processed",
"/api/v1/example/endpoint"))
.example(
createErrorExample(
422,
"File is valid but cannot be processed",
"/api/v1/example/endpoint"))));
}
private ApiResponse create500Response() {
return new ApiResponse()
.description("Internal server error - Unexpected error during processing")
.content(
new Content()
.addMediaType(
"application/json",
new MediaType()
.schema(
createErrorSchema(
500,
"Unexpected error during processing",
"/api/v1/example/endpoint"))
.example(
createErrorExample(
500,
"Unexpected error during processing",
"/api/v1/example/endpoint"))));
}
private Schema<?> createErrorSchema(int status, String message, String path) {
return new Schema<>()
.type("object")
.addProperty("status", new Schema<>().type("integer").example(status))
.addProperty("error", new Schema<>().type("string").example(getErrorType(status)))
.addProperty("message", new Schema<>().type("string").example(message))
.addProperty(
"timestamp",
new Schema<>()
.type("string")
.format("date-time")
.example("2024-01-15T10:30:00Z"))
.addProperty("path", new Schema<>().type("string").example(path));
}
private Object createErrorExample(int status, String message, String path) {
return java.util.Map.of(
"status", status,
"error", getErrorType(status),
"message", message,
"timestamp", "2024-01-15T10:30:00Z",
"path", path);
}
private String getErrorType(int status) {
return switch (status) {
case 400 -> "Bad Request";
case 413 -> "Payload Too Large";
case 422 -> "Unprocessable Entity";
case 500 -> "Internal Server Error";
default -> "Error";
};
}
}
@@ -1,5 +1,8 @@
package stirling.software.SPDF.config;
import java.util.List;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -8,6 +11,10 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
@@ -42,8 +49,7 @@ public class OpenApiConfig {
new License()
.name("MIT")
.url(
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE")
.identifier("MIT"))
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE"))
.termsOfService("https://www.stirlingpdf.com/terms")
.contact(
new Contact()
@@ -52,7 +58,7 @@ public class OpenApiConfig {
.email("contact@stirlingpdf.com"))
.description(DEFAULT_DESCRIPTION);
OpenAPI openAPI = new OpenAPI().info(info);
OpenAPI openAPI = new OpenAPI().info(info).openapi("3.0.3");
// Add server configuration from environment variable
String swaggerServerUrl = System.getenv("SWAGGER_SERVER_URL");
@@ -61,16 +67,83 @@ public class OpenApiConfig {
openAPI.addServersItem(server);
}
// Add ErrorResponse schema to components
Schema<?> errorResponseSchema =
new Schema<>()
.type("object")
.addProperty(
"timestamp",
new Schema<>()
.type("string")
.format("date-time")
.description("Error timestamp"))
.addProperty(
"status",
new Schema<>().type("integer").description("HTTP status code"))
.addProperty(
"error", new Schema<>().type("string").description("Error type"))
.addProperty(
"message",
new Schema<>().type("string").description("Error message"))
.addProperty(
"path", new Schema<>().type("string").description("Request path"))
.description("Standard error response format");
Components components = new Components().addSchemas("ErrorResponse", errorResponseSchema);
if (!applicationProperties.getSecurity().getEnableLogin()) {
return openAPI.components(new Components());
return openAPI.components(components);
} else {
SecurityScheme apiKeyScheme =
new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-KEY");
return openAPI.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
components.addSecuritySchemes("apiKey", apiKeyScheme);
return openAPI.components(components)
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
}
}
@Bean
OpenApiCustomizer pdfFileOneOfCustomizer() {
return openApi -> {
var components = openApi.getComponents();
var schemas = components.getSchemas();
// Define the two shapes
var upload =
new ObjectSchema()
.name("PDFFileUpload")
.description("Upload a PDF file")
.addProperty("fileInput", new StringSchema().format("binary"))
.addRequiredItem("fileInput");
var ref =
new ObjectSchema()
.name("PDFFileRef")
.description("Reference a server-side file")
.addProperty(
"fileId",
new StringSchema()
.example("a1b2c3d4-5678-90ab-cdef-ghijklmnopqr"))
.addRequiredItem("fileId");
schemas.put("PDFFileUpload", upload);
schemas.put("PDFFileRef", ref);
// Create the oneOf schema
var pdfFileOneOf =
new ComposedSchema()
.oneOf(
List.of(
new Schema<>()
.$ref("#/components/schemas/PDFFileUpload"),
new Schema<>().$ref("#/components/schemas/PDFFileRef")))
.description("Either upload a file or provide a server-side file ID");
// Replace PDFFile schema
schemas.put("PDFFile", pdfFileOneOf);
};
}
}
@@ -0,0 +1,83 @@
package stirling.software.SPDF.config;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringDocConfig {
@Bean
public GroupedOpenApi pdfProcessingApi(
@Qualifier("pdfFileOneOfCustomizer") OpenApiCustomizer pdfFileOneOfCustomizer) {
return GroupedOpenApi.builder()
.group("file-processing")
.displayName("File Processing")
.pathsToMatch("/api/v1/**")
.pathsToExclude(
"/api/v1/admin/**",
"/api/v1/user/**",
"/api/v1/settings/**",
"/api/v1/team/**",
"/api/v1/ui-data/**",
"/api/v1/proprietary/ui-data/**",
"/api/v1/info/**",
"/api/v1/general/job/**",
"/api/v1/general/files/**")
.addOpenApiCustomizer(pdfFileOneOfCustomizer)
.addOpenApiCustomizer(
openApi -> {
openApi.info(
openApi.getInfo()
.title("Stirling PDF - Processing API")
.description(
"API documentation for PDF processing operations including conversion, manipulation, security, and utilities."));
})
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("management")
.displayName("Management")
.pathsToMatch(
"/api/v1/admin/**",
"/api/v1/user/**",
"/api/v1/settings/**",
"/api/v1/team/**")
.addOpenApiCustomizer(
openApi -> {
openApi.info(
openApi.getInfo()
.title("Stirling PDF - Admin API")
.description(
"API documentation for administrative functions, user management, and system configuration."));
})
.build();
}
@Bean
public GroupedOpenApi systemApi() {
return GroupedOpenApi.builder()
.group("system")
.displayName("System & UI API")
.pathsToMatch(
"/api/v1/ui-data/**",
"/api/v1/proprietary/ui-data/**",
"/api/v1/info/**",
"/api/v1/general/job/**",
"/api/v1/general/files/**")
.addOpenApiCustomizer(
openApi -> {
openApi.info(
openApi.getInfo()
.title("Stirling PDF - System API")
.description(
"API documentation for system information, UI data, and utility endpoints."));
})
.build();
}
}
@@ -0,0 +1,58 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to CSV conversions. Use for endpoints that convert PDF tables to
* CSV format.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF tables extracted successfully to CSV format",
content = {
@Content(
mediaType = "text/csv",
schema =
@Schema(
type = "string",
format = "binary",
description =
"CSV file containing extracted table data")),
@Content(
mediaType = "application/zip",
schema =
@Schema(
type = "string",
format = "binary",
description =
"ZIP archive containing multiple CSV files when multiple tables are extracted"))
}),
@ApiResponse(
responseCode = "400",
description = "Invalid PDF file or no tables found for extraction",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "204", description = "No tables found in the PDF"),
@ApiResponse(
responseCode = "500",
description = "Internal server error during table extraction",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface CsvConversionResponse {}
@@ -0,0 +1,30 @@
package stirling.software.SPDF.config.swagger;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Standard error response")
public class ErrorResponse {
@Schema(description = "HTTP status code", example = "400")
private int status;
@Schema(
description = "Error message describing what went wrong",
example = "Invalid PDF file or corrupted data")
private String message;
@Schema(description = "Timestamp when the error occurred", example = "2024-01-15T10:30:00Z")
private String timestamp;
@Schema(
description = "Request path where the error occurred",
example = "/api/v1/{endpoint-path}")
private String path;
}
@@ -0,0 +1,67 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for filter operations that conditionally return the original file. Use
* for operations like text filters, page count filters, size filters, etc. Returns the original PDF
* if condition is met, otherwise returns no content (204).
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Filter condition met - returns the original PDF file",
content =
@Content(
mediaType = "application/pdf",
schema =
@Schema(
type = "string",
format = "binary",
description = "The original PDF file"))),
@ApiResponse(
responseCode = "204",
description = "Filter condition not met - no content returned",
content = @Content()),
@ApiResponse(
responseCode = "400",
description = "Bad request - Invalid filter parameters or corrupted PDF",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "413",
description = "Payload too large - File exceeds maximum allowed size",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "422",
description =
"Unprocessable entity - PDF is valid but cannot be analyzed for filtering",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error - Unexpected error during PDF analysis",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface FilterResponse {}
@@ -0,0 +1,33 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to HTML conversions. Use for endpoints that convert PDF to HTML
* format.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF converted successfully to HTML format",
content =
@Content(
mediaType = "text/html",
schema =
@Schema(
type = "string",
format = "binary",
description = "HTML file converted from PDF")))
})
public @interface HtmlConversionResponse {}
@@ -0,0 +1,34 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for JavaScript extraction from PDFs. Use for endpoints that extract
* JavaScript code from PDF documents.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "JavaScript extracted successfully from PDF",
content =
@Content(
mediaType = "text/plain",
schema =
@Schema(
type = "string",
format = "binary",
description =
"JavaScript code extracted from PDF")))
})
public @interface JavaScriptResponse {}
@@ -0,0 +1,47 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for operations that return JSON data or analysis results. Use for
* analysis operations, metadata extraction, info operations, etc.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "Analysis or data extraction completed successfully",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(
type = "object",
description =
"JSON object containing the requested data or analysis results"))),
@ApiResponse(
responseCode = "400",
description = "Invalid PDF file or request parameters",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error during processing",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface JsonDataResponse {}
@@ -0,0 +1,34 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to Markdown conversions. Use for endpoints that convert PDF to
* Markdown format.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF converted successfully to Markdown format",
content =
@Content(
mediaType = "text/markdown",
schema =
@Schema(
type = "string",
format = "binary",
description =
"Markdown file converted from PDF")))
})
public @interface MarkdownConversionResponse {}
@@ -0,0 +1,71 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for operations that may return multiple files or a ZIP archive. Use for
* operations like PDF to images, split PDF, or multiple file conversions.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description =
"Files processed successfully. Returns single file or ZIP archive containing multiple files.",
content = {
@Content(
mediaType = "application/pdf",
schema =
@Schema(
type = "string",
format = "binary",
description = "Single PDF file result")),
@Content(
mediaType = "application/zip",
schema =
@Schema(
type = "string",
format = "binary",
description =
"ZIP archive containing multiple output files")),
@Content(
mediaType = "image/png",
schema =
@Schema(
type = "string",
format = "binary",
description = "Single image file (PNG)")),
@Content(
mediaType = "image/jpeg",
schema =
@Schema(
type = "string",
format = "binary",
description = "Single image file (JPEG)"))
}),
@ApiResponse(
responseCode = "400",
description = "Invalid PDF file or request parameters",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error during processing",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface MultiFileResponse {}
@@ -0,0 +1,66 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to PowerPoint presentation conversions. Use for endpoints that
* convert PDF to PPTX format.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF converted successfully to PowerPoint presentation",
content =
@Content(
mediaType =
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
schema =
@Schema(
type = "string",
format = "binary",
description =
"Microsoft PowerPoint presentation (PPTX)"))),
@ApiResponse(
responseCode = "400",
description =
"Bad request - Invalid input parameters, unsupported format, or corrupted PDF",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "413",
description = "Payload too large - File exceeds maximum allowed size",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "422",
description =
"Unprocessable entity - PDF is valid but cannot be converted to PowerPoint format",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description =
"Internal server error - Unexpected error during PowerPoint conversion",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface PowerPointConversionResponse {}
@@ -0,0 +1,47 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* Standard API response annotation for PDF operations that take PDF input and return PDF output.
* Use for single PDF input → single PDF output (SISO) operations like rotate, compress, etc.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF processed successfully",
content =
@Content(
mediaType = "application/pdf",
schema =
@Schema(
type = "string",
format = "binary",
description = "The processed PDF file"))),
@ApiResponse(
responseCode = "400",
description = "Invalid PDF file or request parameters",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error during processing",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface StandardPdfResponse {}
@@ -0,0 +1,69 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to plain text conversions. Use for endpoints that extract text
* content from PDF.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF text extracted successfully",
content = {
@Content(
mediaType = "text/plain",
schema =
@Schema(
type = "string",
description =
"Plain text content extracted from PDF")),
@Content(
mediaType = "application/rtf",
schema =
@Schema(
type = "string",
format = "binary",
description = "Rich Text Format document"))
}),
@ApiResponse(
responseCode = "400",
description = "Bad request - Invalid input parameters or corrupted PDF",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "413",
description = "Payload too large - File exceeds maximum allowed size",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "422",
description = "Unprocessable entity - PDF is valid but text extraction failed",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error - Unexpected error during text extraction",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface TextPlainConversionResponse {}
@@ -0,0 +1,72 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to Word document conversions. Use for endpoints that convert PDF
* to DOCX/DOC formats.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF converted successfully to Word document",
content = {
@Content(
mediaType =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
schema =
@Schema(
type = "string",
format = "binary",
description = "Microsoft Word document (DOCX)")),
@Content(
mediaType = "application/msword",
schema =
@Schema(
type = "string",
format = "binary",
description = "Microsoft Word document (DOC)"))
}),
@ApiResponse(
responseCode = "400",
description =
"Bad request - Invalid input parameters, unsupported format, or corrupted PDF",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "413",
description = "Payload too large - File exceeds maximum allowed size",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "422",
description =
"Unprocessable entity - PDF is valid but cannot be converted to Word format",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "Internal server error - Unexpected error during Word conversion",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
public @interface WordConversionResponse {}
@@ -0,0 +1,33 @@
package stirling.software.SPDF.config.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
/**
* API response annotation for PDF to XML conversions. Use for endpoints that convert PDF to XML
* format.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "PDF converted successfully to XML format",
content =
@Content(
mediaType = "application/xml",
schema =
@Schema(
type = "string",
format = "binary",
description = "XML file converted from PDF")))
})
public @interface XmlConversionResponse {}
@@ -14,23 +14,23 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.JsonDataResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.AnalysisApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
@RestController
@RequestMapping("/api/v1/analysis")
@Tag(name = "Analysis", description = "Analysis APIs")
@AnalysisApi
@RequiredArgsConstructor
public class AnalysisController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/page-count", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get PDF page count",
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
@@ -41,6 +41,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/basic-info", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get basic PDF information",
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
@@ -55,6 +56,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/document-properties", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get PDF document properties",
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
@@ -78,6 +80,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get page dimensions for all pages",
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
@@ -98,6 +101,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/form-fields", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get form field information",
description =
@@ -121,6 +125,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/annotation-info", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get annotation information",
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
@@ -145,6 +150,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/font-info", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get font information",
description =
@@ -167,6 +173,7 @@ public class AnalysisController {
}
@AutoJobPostMapping(value = "/security-info", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Get security information",
description =
@@ -0,0 +1,324 @@
package stirling.software.SPDF.controller.api;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.BookletImpositionRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class BookletImpositionController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/booklet-imposition", consumes = "multipart/form-data")
@Operation(
summary = "Create a booklet with proper page imposition",
description =
"This operation combines page reordering for booklet printing with multi-page layout. "
+ "It rearranges pages in the correct order for booklet printing and places multiple pages "
+ "on each sheet for proper folding and binding. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> createBookletImposition(
@ModelAttribute BookletImpositionRequest request) throws IOException {
MultipartFile file = request.getFileInput();
int pagesPerSheet = request.getPagesPerSheet();
boolean addBorder = Boolean.TRUE.equals(request.getAddBorder());
String spineLocation =
request.getSpineLocation() != null ? request.getSpineLocation() : "LEFT";
boolean addGutter = Boolean.TRUE.equals(request.getAddGutter());
float gutterSize = request.getGutterSize();
boolean doubleSided = Boolean.TRUE.equals(request.getDoubleSided());
String duplexPass = request.getDuplexPass() != null ? request.getDuplexPass() : "BOTH";
boolean flipOnShortEdge = Boolean.TRUE.equals(request.getFlipOnShortEdge());
// Validate pages per sheet for booklet - only 2-up landscape is proper booklet
if (pagesPerSheet != 2) {
throw new IllegalArgumentException(
"Booklet printing uses 2 pages per side (landscape). For 4-up, use the N-up feature.");
}
PDDocument sourceDocument = pdfDocumentFactory.load(file);
int totalPages = sourceDocument.getNumberOfPages();
// Create proper booklet with signature-based page ordering
PDDocument newDocument =
createSaddleBooklet(
sourceDocument,
totalPages,
addBorder,
spineLocation,
addGutter,
gutterSize,
doubleSided,
duplexPass,
flipOnShortEdge);
sourceDocument.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
newDocument.save(baos);
newDocument.close();
byte[] result = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse(
result,
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_booklet.pdf");
}
private static int padToMultipleOf4(int n) {
return (n + 3) / 4 * 4;
}
private static class Side {
final int left, right;
final boolean isBack;
Side(int left, int right, boolean isBack) {
this.left = left;
this.right = right;
this.isBack = isBack;
}
}
private static List<Side> saddleStitchSides(
int totalPagesOriginal,
boolean doubleSided,
String duplexPass,
boolean flipOnShortEdge) {
int N = padToMultipleOf4(totalPagesOriginal);
List<Side> out = new ArrayList<>();
int sheets = N / 4;
for (int s = 0; s < sheets; s++) {
int a = N - 1 - (s * 2); // left, front
int b = (s * 2); // right, front
int c = (s * 2) + 1; // left, back
int d = N - 2 - (s * 2); // right, back
// clamp to -1 (blank) if >= totalPagesOriginal
a = (a < totalPagesOriginal) ? a : -1;
b = (b < totalPagesOriginal) ? b : -1;
c = (c < totalPagesOriginal) ? c : -1;
d = (d < totalPagesOriginal) ? d : -1;
// Handle duplex pass selection
boolean includeFront = "BOTH".equals(duplexPass) || "FIRST".equals(duplexPass);
boolean includeBack = "BOTH".equals(duplexPass) || "SECOND".equals(duplexPass);
if (includeFront) {
out.add(new Side(a, b, false)); // front side
}
if (includeBack) {
// For short-edge duplex, swap back-side left/right
// Note: flipOnShortEdge is ignored in manual duplex mode since users physically
// flip the stack
if (doubleSided && flipOnShortEdge) {
out.add(new Side(d, c, true)); // swapped back side (automatic duplex only)
} else {
out.add(new Side(c, d, true)); // normal back side
}
}
}
return out;
}
private PDDocument createSaddleBooklet(
PDDocument src,
int totalPages,
boolean addBorder,
String spineLocation,
boolean addGutter,
float gutterSize,
boolean doubleSided,
String duplexPass,
boolean flipOnShortEdge)
throws IOException {
PDDocument dst = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(src);
// Derive paper size from source document's first page CropBox
PDRectangle srcBox = src.getPage(0).getCropBox();
PDRectangle portraitPaper = new PDRectangle(srcBox.getWidth(), srcBox.getHeight());
// Force landscape for booklet (Acrobat booklet uses landscape paper to fold to portrait)
PDRectangle pageSize = new PDRectangle(portraitPaper.getHeight(), portraitPaper.getWidth());
// Validate and clamp gutter size
if (gutterSize < 0) gutterSize = 0;
if (gutterSize >= pageSize.getWidth() / 2f) gutterSize = pageSize.getWidth() / 2f - 1f;
List<Side> sides = saddleStitchSides(totalPages, doubleSided, duplexPass, flipOnShortEdge);
for (Side side : sides) {
PDPage out = new PDPage(pageSize);
dst.addPage(out);
float cellW = pageSize.getWidth() / 2f;
float cellH = pageSize.getHeight();
// For RIGHT spine (RTL), swap left/right placements
boolean rtl = "RIGHT".equalsIgnoreCase(spineLocation);
int leftCol = rtl ? 1 : 0;
int rightCol = rtl ? 0 : 1;
// Apply gutter margins with centered gap option
float g = addGutter ? gutterSize : 0f;
float leftCellX = leftCol * cellW + (g / 2f);
float rightCellX = rightCol * cellW - (g / 2f);
float leftCellW = cellW - (g / 2f);
float rightCellW = cellW - (g / 2f);
// Create LayerUtility once per page for efficiency
LayerUtility layerUtility = new LayerUtility(dst);
try (PDPageContentStream cs =
new PDPageContentStream(
dst, out, PDPageContentStream.AppendMode.APPEND, true, true)) {
if (addBorder) {
cs.setLineWidth(1.5f);
cs.setStrokingColor(Color.BLACK);
}
// draw left cell
drawCell(
src,
dst,
cs,
layerUtility,
side.left,
leftCellX,
0f,
leftCellW,
cellH,
addBorder);
// draw right cell
drawCell(
src,
dst,
cs,
layerUtility,
side.right,
rightCellX,
0f,
rightCellW,
cellH,
addBorder);
}
}
return dst;
}
private void drawCell(
PDDocument src,
PDDocument dst,
PDPageContentStream cs,
LayerUtility layerUtility,
int pageIndex,
float cellX,
float cellY,
float cellW,
float cellH,
boolean addBorder)
throws IOException {
if (pageIndex < 0) {
// Draw border for blank cell if needed
if (addBorder) {
cs.addRect(cellX, cellY, cellW, cellH);
cs.stroke();
}
return;
}
PDPage srcPage = src.getPage(pageIndex);
PDRectangle r = srcPage.getCropBox(); // Use CropBox instead of MediaBox
int rot = (srcPage.getRotation() + 360) % 360;
// Calculate scale factors, accounting for rotation
float sx = cellW / r.getWidth();
float sy = cellH / r.getHeight();
float s = Math.min(sx, sy);
// If rotated 90/270 degrees, swap dimensions for fitting
if (rot == 90 || rot == 270) {
sx = cellW / r.getHeight();
sy = cellH / r.getWidth();
s = Math.min(sx, sy);
}
float drawnW = (rot == 90 || rot == 270) ? r.getHeight() * s : r.getWidth() * s;
float drawnH = (rot == 90 || rot == 270) ? r.getWidth() * s : r.getHeight() * s;
// Center in cell, accounting for CropBox offset
float tx = cellX + (cellW - drawnW) / 2f - r.getLowerLeftX() * s;
float ty = cellY + (cellH - drawnH) / 2f - r.getLowerLeftY() * s;
cs.saveGraphicsState();
cs.transform(Matrix.getTranslateInstance(tx, ty));
cs.transform(Matrix.getScaleInstance(s, s));
// Apply rotation if needed (rotate about origin), then translate to keep in cell
switch (rot) {
case 90:
cs.transform(Matrix.getRotateInstance(Math.PI / 2, 0, 0));
// After 90° CCW, the content spans x in [-r.getHeight(), 0] and y in [0,
// r.getWidth()]
cs.transform(Matrix.getTranslateInstance(0, -r.getWidth()));
break;
case 180:
cs.transform(Matrix.getRotateInstance(Math.PI, 0, 0));
cs.transform(Matrix.getTranslateInstance(-r.getWidth(), -r.getHeight()));
break;
case 270:
cs.transform(Matrix.getRotateInstance(3 * Math.PI / 2, 0, 0));
// After 270° CCW, the content spans x in [0, r.getHeight()] and y in
// [-r.getWidth(), 0]
cs.transform(Matrix.getTranslateInstance(-r.getHeight(), 0));
break;
default:
// 0°: no-op
}
// Reuse LayerUtility passed from caller
PDFormXObject form = layerUtility.importPageAsForm(src, pageIndex);
cs.drawForm(form);
cs.restoreGraphicsState();
// Draw border on top of form to ensure visibility
if (addBorder) {
cs.addRect(cellX, cellY, cellW, cellH);
cs.stroke();
}
}
}
@@ -12,28 +12,26 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class CropController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/crop", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Crops a PDF document",
description =
@@ -14,30 +14,28 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.JsonDataResponse;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@GeneralApi
@Slf4j
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class EditTableOfContentsController {
@@ -45,6 +43,7 @@ public class EditTableOfContentsController {
private final ObjectMapper objectMapper;
@AutoJobPostMapping(value = "/extract-bookmarks", consumes = "multipart/form-data")
@JsonDataResponse
@Operation(
summary = "Extract PDF Bookmarks",
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
@@ -153,6 +152,7 @@ public class EditTableOfContentsController {
}
@AutoJobPostMapping(value = "/edit-table-of-contents", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Edit Table of Contents",
description = "Add or edit bookmarks/table of contents in a PDF document.")
@@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
@@ -20,30 +21,29 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfErrorUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@GeneralApi
@Slf4j
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class MergeController {
@@ -114,6 +114,32 @@ public class MergeController {
}
}
// Parse client file IDs from JSON string
private String[] parseClientFileIds(String clientFileIds) {
if (clientFileIds == null || clientFileIds.trim().isEmpty()) {
return new String[0];
}
try {
// Simple JSON array parsing - remove brackets and split by comma
String trimmed = clientFileIds.trim();
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
String inside = trimmed.substring(1, trimmed.length() - 1).trim();
if (inside.isEmpty()) {
return new String[0];
}
String[] parts = inside.split(",");
String[] result = new String[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = parts[i].trim().replaceAll("^\"|\"$", "");
}
return result;
}
} catch (Exception e) {
log.warn("Failed to parse client file IDs: {}", clientFileIds, e);
}
return new String[0];
}
// Adds a table of contents to the merged document using filenames as chapter titles
private void addTableOfContents(PDDocument mergedDocument, MultipartFile[] files) {
// Create the document outline
@@ -155,6 +181,7 @@ public class MergeController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
@StandardPdfResponse
@Operation(
summary = "Merge multiple PDF files into one",
description =
@@ -179,15 +206,48 @@ public class MergeController {
PDFMergerUtility mergerUtility = new PDFMergerUtility();
long totalSize = 0;
for (MultipartFile multipartFile : files) {
List<Integer> invalidIndexes = new ArrayList<>();
for (int index = 0; index < files.length; index++) {
MultipartFile multipartFile = files[index];
totalSize += multipartFile.getSize();
File tempFile =
GeneralUtils.convertMultipartFileToFile(
multipartFile); // Convert MultipartFile to File
filesToDelete.add(tempFile); // Add temp file to the list for later deletion
// Pre-validate each PDF so we can report which one(s) are broken
// Use the original MultipartFile to avoid deleting the tempFile during validation
try (PDDocument ignored = pdfDocumentFactory.load(multipartFile)) {
// OK
} catch (IOException e) {
ExceptionUtils.logException("PDF pre-validate", e);
invalidIndexes.add(index);
}
mergerUtility.addSource(tempFile); // Add source file to the merger utility
}
if (!invalidIndexes.isEmpty()) {
// Parse client file IDs (always present from frontend)
String[] clientIds = parseClientFileIds(request.getClientFileIds());
// Map invalid indexes to client IDs
List<String> errorFileIds = new ArrayList<>();
for (Integer index : invalidIndexes) {
if (index < clientIds.length) {
errorFileIds.add(clientIds[index]);
}
}
String payload =
String.format(
"{\"errorFileIds\":%s,\"message\":\"Some of the selected files can't be merged\"}",
errorFileIds.toString());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.body(payload.getBytes(StandardCharsets.UTF_8));
}
mergedTempFile = Files.createTempFile("merged-", ".pdf").toFile();
mergerUtility.setDestinationFileName(mergedTempFile.getAbsolutePath());
@@ -13,30 +13,28 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class MultiPageLayoutController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Merge multiple pages of a PDF document into a single page",
description =
@@ -6,16 +6,15 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -24,9 +23,7 @@ import stirling.software.common.util.WebResponseUtils;
* Controller class for handling PDF image removal requests. Provides an endpoint to remove images
* from a PDF file to reduce its size.
*/
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class PdfImageRemovalController {
@@ -47,11 +44,12 @@ public class PdfImageRemovalController {
* @throws IOException If an error occurs while processing the PDF file.
*/
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
@StandardPdfResponse
@Operation(
summary = "Remove images from file to reduce the file size.",
description =
"This endpoint remove images from file to reduce the file size.Input:PDF"
+ " Output:PDF Type:MISO")
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
// Load the PDF document
PDDocument document = pdfDocumentFactory.load(file);
@@ -15,31 +15,29 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class PdfOverlayController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Overlay PDF files in various modes",
description =
@@ -9,36 +9,34 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@GeneralApi
@Slf4j
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class RearrangePagesPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@StandardPdfResponse
@Operation(
summary = "Remove pages from a PDF file",
description =
@@ -238,6 +236,7 @@ public class RearrangePagesPDFController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
@StandardPdfResponse
@Operation(
summary = "Rearrange pages in a PDF file",
description =
@@ -7,31 +7,29 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class RotationController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@StandardPdfResponse
@Operation(
summary = "Rotate a PDF file",
description =
@@ -14,31 +14,29 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class ScalePagesController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Change the size of a PDF page/document",
description =
@@ -5,25 +5,21 @@ import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.SettingsApi;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
@Controller
@Tag(name = "Settings", description = "Settings APIs")
@RequestMapping("/api/v1/settings")
@SettingsApi
@RequiredArgsConstructor
@Hidden
public class SettingsController {
@@ -15,33 +15,31 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@GeneralApi
@Slf4j
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class SplitPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/split-pages")
@MultiFileResponse
@Operation(
summary = "Split a PDF file into separate documents",
description =
@@ -15,13 +15,10 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -30,18 +27,18 @@ import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.model.PdfMetadata;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.PdfMetadataService;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@GeneralApi
@Slf4j
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class SplitPdfByChaptersController {
@@ -118,6 +115,7 @@ public class SplitPdfByChaptersController {
}
@AutoJobPostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
@MultiFileResponse
@Operation(
summary = "Split PDFs by Chapters",
description = "Splits a PDF into chapters and returns a ZIP file.")
@@ -20,30 +20,28 @@ import org.apache.pdfbox.util.Matrix;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class SplitPdfBySectionsController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@MultiFileResponse
@Operation(
summary = "Split PDF pages into smaller sections",
description =
@@ -12,34 +12,32 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@GeneralApi
@Slf4j
@Tag(name = "General", description = "General APIs")
@RequiredArgsConstructor
public class SplitPdfBySizeController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
@MultiFileResponse
@Operation(
summary = "Auto split PDF pages into separate documents based on size or count",
description =
@@ -12,28 +12,26 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@GeneralApi
@RequiredArgsConstructor
public class ToSinglePageController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
@StandardPdfResponse
@Operation(
summary = "Convert a multi-page PDF into a single long page PDF",
description =
@@ -15,14 +15,11 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -30,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.Dependency;
import stirling.software.SPDF.model.SignatureFile;
import stirling.software.SPDF.service.SignatureService;
import stirling.software.common.annotations.api.UiDataApi;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.ApplicationProperties;
@@ -38,9 +36,7 @@ import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
@Slf4j
@RestController
@RequestMapping("/api/v1/ui-data")
@Tag(name = "UI Data", description = "APIs for React UI data")
@UiDataApi
public class UIDataController {
private final ApplicationProperties applicationProperties;
@@ -8,18 +8,17 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.converters.EmlToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -28,9 +27,7 @@ import stirling.software.common.util.EmlToPdf;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs")
@ConvertApi
@Slf4j
@RequiredArgsConstructor
public class ConvertEmlToPDF {
@@ -41,6 +38,7 @@ public class ConvertEmlToPDF {
private final CustomHtmlSanitizer customHtmlSanitizer;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/eml/pdf")
@StandardPdfResponse
@Operation(
summary = "Convert EML to PDF",
description =
@@ -2,17 +2,16 @@ package stirling.software.SPDF.controller.api.converters;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -22,9 +21,7 @@ import stirling.software.common.util.FileToPdf;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
@ConvertApi
@RequiredArgsConstructor
public class ConvertHtmlToPDF {
@@ -37,6 +34,7 @@ public class ConvertHtmlToPDF {
private final CustomHtmlSanitizer customHtmlSanitizer;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/html/pdf")
@StandardPdfResponse
@Operation(
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
description =
@@ -19,20 +19,20 @@ import org.apache.pdfbox.rendering.ImageType;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils;
@@ -42,16 +42,15 @@ import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@ConvertApi
@Slf4j
@Tag(name = "Convert", description = "Convert APIs")
@RequiredArgsConstructor
public class ConvertImgPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/img")
@MultiFileResponse
@Operation(
summary = "Convert PDF to image(s)",
description =
@@ -212,6 +211,7 @@ public class ConvertImgPDFController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/img/pdf")
@StandardPdfResponse
@Operation(
summary = "Convert images to a PDF file",
description =
@@ -12,17 +12,16 @@ import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.GeneralFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -32,9 +31,7 @@ import stirling.software.common.util.FileToPdf;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
@ConvertApi
@RequiredArgsConstructor
public class ConvertMarkdownToPdf {
@@ -46,6 +43,7 @@ public class ConvertMarkdownToPdf {
private final CustomHtmlSanitizer customHtmlSanitizer;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
@StandardPdfResponse
@Operation(
summary = "Convert a Markdown file to PDF",
description =
@@ -13,17 +13,15 @@ import org.apache.commons.io.FilenameUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.api.GeneralFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -32,9 +30,7 @@ import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
@ConvertApi
@RequiredArgsConstructor
public class ConvertOfficeController {
@@ -2,20 +2,17 @@ package stirling.software.SPDF.controller.api.converters;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.swagger.HtmlConversionResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.util.PDFToFile;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
@ConvertApi
public class ConvertPDFToHtml {
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/html")
@@ -23,6 +20,7 @@ public class ConvertPDFToHtml {
summary = "Convert PDF to HTML",
description =
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
@HtmlConversionResponse
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile file) throws Exception {
MultipartFile inputFile = file.getFileInput();
PDFToFile pdfToFile = new PDFToFile();
@@ -7,34 +7,35 @@ import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.PowerPointConversionResponse;
import stirling.software.SPDF.config.swagger.TextPlainConversionResponse;
import stirling.software.SPDF.config.swagger.WordConversionResponse;
import stirling.software.SPDF.config.swagger.XmlConversionResponse;
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.PDFToFile;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs")
@ConvertApi
@RequiredArgsConstructor
public class ConvertPDFToOffice {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
@PowerPointConversionResponse
@Operation(
summary = "Convert PDF to Presentation format",
description =
@@ -50,6 +51,7 @@ public class ConvertPDFToOffice {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/text")
@TextPlainConversionResponse
@Operation(
summary = "Convert PDF to Text or RTF format",
description =
@@ -78,6 +80,7 @@ public class ConvertPDFToOffice {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/word")
@WordConversionResponse
@Operation(
summary = "Convert PDF to Word document",
description =
@@ -92,6 +95,7 @@ public class ConvertPDFToOffice {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
@XmlConversionResponse
@Operation(
summary = "Convert PDF to XML",
description =
@@ -55,30 +55,28 @@ import org.apache.xmpbox.xml.XmpSerializer;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@ConvertApi
@Slf4j
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToPDFA {
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
@StandardPdfResponse
@Operation(
summary = "Convert a PDF to a PDF/A",
description =
@@ -9,17 +9,16 @@ import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -29,10 +28,8 @@ import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@ConvertApi
@Slf4j
@RequestMapping("/api/v1/convert")
@RequiredArgsConstructor
public class ConvertWebsiteToPDF {
@@ -41,6 +38,7 @@ public class ConvertWebsiteToPDF {
private final ApplicationProperties applicationProperties;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@StandardPdfResponse
@Operation(
summary = "Convert a URL to a PDF",
description =
@@ -18,18 +18,17 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.CsvConversionResponse;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import technology.tabula.ObjectExtractor;
@@ -37,9 +36,7 @@ import technology.tabula.Page;
import technology.tabula.Table;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
@RestController
@RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs")
@ConvertApi
@Slf4j
@RequiredArgsConstructor
public class ExtractCSVController {
@@ -47,6 +44,7 @@ public class ExtractCSVController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@CsvConversionResponse
@Operation(
summary = "Extracts a CSV document from a PDF",
description =
@@ -7,16 +7,14 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.FilterResponse;
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
@@ -24,20 +22,20 @@ import stirling.software.SPDF.model.api.filter.FileSizeRequest;
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.FilterApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/filter")
@Tag(name = "Filter", description = "Filter APIs")
@FilterApi
@RequiredArgsConstructor
public class FilterController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
@FilterResponse
@Operation(
summary = "Checks if a PDF contains set text, returns true if does",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -56,6 +54,7 @@ public class FilterController {
// TODO
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
@FilterResponse
@Operation(
summary = "Checks if a PDF contains an image",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -72,6 +71,7 @@ public class FilterController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
@FilterResponse
@Operation(
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -105,6 +105,7 @@ public class FilterController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
@FilterResponse
@Operation(
summary = "Checks if a PDF is of a certain size",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -148,6 +149,7 @@ public class FilterController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
@FilterResponse
@Operation(
summary = "Checks if a PDF is a set file size",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -181,6 +183,7 @@ public class FilterController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
@FilterResponse
@Operation(
summary = "Checks if a PDF is of a certain rotation",
description = "Input:PDF Output:Boolean Type:SISO")
@@ -6,28 +6,25 @@ import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.AddAttachmentRequest;
import stirling.software.SPDF.service.AttachmentServiceInterface;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@MiscApi
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class AttachmentController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@@ -35,6 +32,7 @@ public class AttachmentController {
private final AttachmentServiceInterface pdfAttachmentService;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-attachments")
@StandardPdfResponse
@Operation(
summary = "Add attachments to PDF",
description =
@@ -10,26 +10,22 @@ import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class AutoRenameController {
@@ -19,8 +19,6 @@ import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.google.zxing.*;
@@ -28,20 +26,19 @@ import com.google.zxing.common.HybridBinarizer;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class AutoSplitPdfController {
@@ -103,6 +100,7 @@ public class AutoSplitPdfController {
}
@AutoJobPostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
@MultiFileResponse
@Operation(
summary = "Auto split PDF pages into separate documents",
description =
@@ -17,27 +17,23 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class BlankPageController {
@@ -34,13 +34,10 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -50,8 +47,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
@@ -59,10 +58,8 @@ import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class CompressController {
@@ -659,6 +656,7 @@ public class CompressController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
@StandardPdfResponse
@Operation(
summary = "Optimize PDF file",
description =
@@ -6,29 +6,36 @@ import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.annotations.api.ConfigApi;
import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.ServerCertificateServiceInterface;
@RestController
@Tag(name = "Config", description = "Configuration APIs")
@RequestMapping("/api/v1/config")
@RequiredArgsConstructor
@ConfigApi
@Hidden
public class ConfigController {
private final ApplicationProperties applicationProperties;
private final ApplicationContext applicationContext;
private final EndpointConfiguration endpointConfiguration;
private final ServerCertificateServiceInterface serverCertificateService;
public ConfigController(
ApplicationProperties applicationProperties,
ApplicationContext applicationContext,
EndpointConfiguration endpointConfiguration,
@org.springframework.beans.factory.annotation.Autowired(required = false)
ServerCertificateServiceInterface serverCertificateService) {
this.applicationProperties = applicationProperties;
this.applicationContext = applicationContext;
this.endpointConfiguration = endpointConfiguration;
this.serverCertificateService = serverCertificateService;
}
@GetMapping("/app-config")
public ResponseEntity<Map<String, Object>> getAppConfig() {
@@ -62,6 +69,11 @@ public class ConfigController {
// Premium/Enterprise settings
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
// Server certificate settings
configData.put(
"serverCertificateEnabled",
serverCertificateService != null && serverCertificateService.isEnabled());
// Legal settings
configData.put(
"termsAndConditions", applicationProperties.getLegal().getTermsAndConditions());
@@ -13,26 +13,22 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class DecompressPdfController {
@@ -19,18 +19,17 @@ import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils;
@@ -39,10 +38,8 @@ import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class ExtractImageScansController {
@@ -51,6 +48,7 @@ public class ExtractImageScansController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
@MultiFileResponse
@Operation(
summary = "Extract image scans from an input file",
description =
@@ -27,34 +27,32 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.ImageProcessingUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class ExtractImagesController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/extract-images")
@MultiFileResponse
@Operation(
summary = "Extract images from a PDF file",
description =
@@ -13,32 +13,30 @@ import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class FlattenController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/flatten")
@StandardPdfResponse
@Operation(
summary = "Flatten PDF form fields or full page",
description =
@@ -17,21 +17,20 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.MetadataRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class MetadataController {
@@ -53,6 +52,7 @@ public class MetadataController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/update-metadata")
@StandardPdfResponse
@Operation(
summary = "Update metadata of a PDF file",
description =
@@ -18,20 +18,19 @@ import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
@@ -42,9 +41,7 @@ import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@Slf4j
@RequiredArgsConstructor
public class OCRController {
@@ -77,6 +74,7 @@ public class OCRController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@StandardPdfResponse
@Operation(
summary = "Process a PDF file with OCR",
description =
@@ -5,27 +5,23 @@ import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
@RequiredArgsConstructor
public class OverlayImageController {
@@ -13,31 +13,29 @@ import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@RequiredArgsConstructor
public class PageNumbersController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
summary = "Add page numbers to a PDF document",
description =
@@ -20,19 +20,14 @@ import org.apache.pdfbox.printing.PDFPageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
import stirling.software.common.annotations.api.MiscApi;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@Slf4j
public class PrintFileController {
@@ -6,19 +6,18 @@ import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ProcessExecutor;
@@ -27,9 +26,7 @@ import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@Slf4j
@RequiredArgsConstructor
public class RepairController {
@@ -47,6 +44,7 @@ public class RepairController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/repair")
@StandardPdfResponse
@Operation(
summary = "Repair a PDF file",
description =
@@ -7,21 +7,17 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@RequiredArgsConstructor
public class ReplaceAndInvertColorController {
@@ -19,13 +19,10 @@ import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
@@ -34,12 +31,11 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ScannerEffectRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous PDF APIs")
@MiscApi
@RequiredArgsConstructor
@Slf4j
public class ScannerEffectController {
@@ -9,30 +9,28 @@ import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.JavaScriptResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@RequiredArgsConstructor
public class ShowJavascript {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/show-javascript")
@JavaScriptResponse
@Operation(
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
description = "desc. Input:PDF Output:JS Type:SISO")
@@ -26,26 +26,23 @@ import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@MiscApi
@RequiredArgsConstructor
public class StampController {
@@ -53,6 +50,7 @@ public class StampController {
private final TempFileManager tempFileManager;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@StandardPdfResponse
@Operation(
summary = "Add stamp to a PDF file",
description =
@@ -239,34 +237,54 @@ public class StampController {
PDRectangle pageSize = page.getMediaBox();
float x, y;
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);
y =
calculatePositionY(
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
}
// Split the stampText into multiple lines
String[] lines = stampText.split("\\\\n");
String[] lines = stampText.split("\\r?\\n|\\\\n");
// Calculate dynamic line height based on font ascent and descent
float ascent = font.getFontDescriptor().getAscent();
float descent = font.getFontDescriptor().getDescent();
float lineHeight = ((ascent - descent) / 1000) * fontSize;
// Compute a single pivot for the entire text block to avoid line-by-line wobble
float capHeight = calculateTextCapHeight(font, fontSize);
float blockHeight = Math.max(lineHeight, lineHeight * Math.max(1, lines.length));
float maxWidth = 0f;
for (String ln : lines) {
maxWidth = Math.max(maxWidth, calculateTextWidth(ln, font, fontSize));
}
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
// Base positioning on the true multi-line block size
x = calculatePositionX(pageSize, position, maxWidth, null, 0, null, margin);
y = calculatePositionY(pageSize, position, blockHeight, margin);
}
// After anchoring the block, draw from the top line downward
float adjustedX = x;
float adjustedY = y;
float pivotX = adjustedX + maxWidth / 2f;
float pivotY = adjustedY + blockHeight / 2f;
// Apply rotation about the block center at the graphics state level
contentStream.saveGraphicsState();
contentStream.transform(Matrix.getTranslateInstance(pivotX, pivotY));
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
contentStream.transform(Matrix.getTranslateInstance(-pivotX, -pivotY));
contentStream.beginText();
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
// Set the text matrix for each line with rotation
contentStream.setTextMatrix(
Matrix.getRotateInstance(Math.toRadians(rotation), x, y - (i * lineHeight)));
// Start from top line: yTop = adjustedY + blockHeight - capHeight
float yLine = adjustedY + blockHeight - capHeight - (i * lineHeight);
contentStream.setTextMatrix(Matrix.getTranslateInstance(adjustedX, yLine));
contentStream.showText(line);
}
contentStream.endText();
contentStream.restoreGraphicsState();
}
private void addImageStamp(
@@ -310,9 +328,17 @@ public class StampController {
}
contentStream.saveGraphicsState();
contentStream.transform(Matrix.getTranslateInstance(x, y));
// Rotate and scale about the center of the image
float centerX = x + (desiredPhysicalWidth / 2f);
float centerY = y + (desiredPhysicalHeight / 2f);
contentStream.transform(Matrix.getTranslateInstance(centerX, centerY));
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
contentStream.drawImage(
xobject,
-desiredPhysicalWidth / 2f,
-desiredPhysicalHeight / 2f,
desiredPhysicalWidth,
desiredPhysicalHeight);
contentStream.restoreGraphicsState();
}
@@ -12,24 +12,21 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@MiscApi
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class UnlockPDFFormsController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@@ -38,6 +35,7 @@ public class UnlockPDFFormsController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/unlock-pdf-forms")
@StandardPdfResponse
@Operation(
summary = "Remove read-only property from form fields",
description =
@@ -12,31 +12,29 @@ import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.MultiFileResponse;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.model.api.HandleDataRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.PipelineApi;
import stirling.software.common.service.PostHogService;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/pipeline")
@PipelineApi
@Slf4j
@Tag(name = "Pipeline", description = "Pipeline APIs")
@RequiredArgsConstructor
public class PipelineController {
@@ -47,6 +45,13 @@ public class PipelineController {
private final PostHogService postHogService;
@AutoJobPostMapping(value = "/handleData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@MultiFileResponse
@Operation(
summary = "Execute automated PDF processing pipeline",
description =
"This endpoint processes multiple PDF files through a configurable pipeline of operations. "
+ "Users provide files and a JSON configuration defining the sequence of operations to perform. "
+ "Input:PDF Output:PDF/ZIP Type:MIMO")
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
throws JsonMappingException, JsonProcessingException {
MultipartFile[] files = request.getFileInput();
@@ -53,6 +53,7 @@ import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -68,12 +69,13 @@ import io.micrometer.common.util.StringUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.ServerCertificateServiceInterface;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -81,7 +83,6 @@ import stirling.software.common.util.WebResponseUtils;
@RequestMapping("/api/v1/security")
@Slf4j
@Tag(name = "Security", description = "Security APIs")
@RequiredArgsConstructor
public class CertSignController {
static {
@@ -101,6 +102,15 @@ public class CertSignController {
}
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final ServerCertificateServiceInterface serverCertificateService;
public CertSignController(
CustomPDFDocumentFactory pdfDocumentFactory,
@Autowired(required = false)
ServerCertificateServiceInterface serverCertificateService) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.serverCertificateService = serverCertificateService;
}
private static void sign(
CustomPDFDocumentFactory pdfDocumentFactory,
@@ -144,6 +154,7 @@ public class CertSignController {
MediaType.APPLICATION_FORM_URLENCODED_VALUE
},
value = "/cert-sign")
@StandardPdfResponse
@Operation(
summary = "Sign PDF with a Digital Certificate",
description =
@@ -175,6 +186,7 @@ public class CertSignController {
}
KeyStore ks = null;
String keystorePassword = password;
switch (certType) {
case "PEM":
@@ -193,6 +205,24 @@ public class CertSignController {
ks = KeyStore.getInstance("JKS");
ks.load(jksfile.getInputStream(), password.toCharArray());
break;
case "SERVER":
if (serverCertificateService == null) {
throw ExceptionUtils.createIllegalArgumentException(
"error.serverCertificateNotAvailable",
"Server certificate service is not available in this edition");
}
if (!serverCertificateService.isEnabled()) {
throw ExceptionUtils.createIllegalArgumentException(
"error.serverCertificateDisabled",
"Server certificate feature is disabled");
}
if (!serverCertificateService.hasServerCertificate()) {
throw ExceptionUtils.createIllegalArgumentException(
"error.serverCertificateNotFound", "No server certificate configured");
}
ks = serverCertificateService.getServerKeyStore();
keystorePassword = serverCertificateService.getServerCertificatePassword();
break;
default:
throw ExceptionUtils.createIllegalArgumentException(
"error.invalidArgument",
@@ -200,7 +230,7 @@ public class CertSignController {
"certificate type: " + certType);
}
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
CreateSignature createSignature = new CreateSignature(ks, keystorePassword.toCharArray());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sign(
pdfDocumentFactory,
@@ -46,8 +46,6 @@ import org.apache.xmpbox.xml.XmpSerializer;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -55,21 +53,20 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.JsonDataResponse;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.SecurityApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/security")
@SecurityApi
@Slf4j
@Tag(name = "Security", description = "Security APIs")
@RequiredArgsConstructor
public class GetInfoOnPDF {
@@ -189,6 +186,7 @@ public class GetInfoOnPDF {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
@JsonDataResponse
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput();
@@ -7,32 +7,30 @@ import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.SecurityApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/security")
@Tag(name = "Security", description = "Security APIs")
@SecurityApi
@RequiredArgsConstructor
public class PasswordController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-password")
@StandardPdfResponse
@Operation(
summary = "Remove password from a PDF file",
description =
@@ -59,6 +57,7 @@ public class PasswordController {
}
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-password")
@StandardPdfResponse
@Operation(
summary = "Add password to a PDF file",
description =
@@ -18,22 +18,21 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.PDFText;
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.SecurityApi;
import stirling.software.common.model.api.security.RedactionArea;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
@@ -41,10 +40,8 @@ import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.propertyeditor.StringToArrayListPropertyEditor;
@RestController
@RequestMapping("/api/v1/security")
@SecurityApi
@Slf4j
@Tag(name = "Security", description = "Security APIs")
@RequiredArgsConstructor
public class RedactController {
@@ -57,13 +54,15 @@ public class RedactController {
}
@AutoJobPostMapping(value = "/redact", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
operationId = "redactPdfManual",
summary = "Redacts areas and pages in a PDF document",
description =
"This operation takes an input PDF file with a list of areas, page"
+ " number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF,"
+ " Type:SISO")
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
public ResponseEntity<byte[]> redactPdfManual(@ModelAttribute ManualRedactPdfRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
List<RedactionArea> redactionAreas = request.getRedactions();
@@ -191,7 +190,9 @@ public class RedactController {
}
@AutoJobPostMapping(value = "/auto-redact", consumes = "multipart/form-data")
@StandardPdfResponse
@Operation(
operationId = "redactPdfAuto",
summary = "Redacts listOfText in a PDF document",
description =
"This operation takes an input PDF file and redacts the provided listOfText."

Some files were not shown because too many files have changed in this diff Show More