Compare commits

...

60 Commits

Author SHA1 Message Date
sbplat
7fb605baa1 feat: only load pdfjs when its used 2024-09-14 21:02:10 -04:00
sbplat
7e2a53a02e feat: only load pdf-lib when its used 2024-09-14 20:55:35 -04:00
github-actions[bot]
d389b5e2f3 💾 Update Version (#1900)
💾 Sync Versions
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-14 23:44:13 +01:00
Anthony Stirling
de97492f39 Update build.gradle 2024-09-14 23:43:30 +01:00
Anthony Stirling
9661e94092 Css changes (#1899)
* #1841

Signed-off-by: a <a>

* #1841 for watermark

Signed-off-by: a <a>

* #1869 and ensure naming

Signed-off-by: a <a>

---------

Signed-off-by: a <a>
Co-authored-by: a <a>
2024-09-14 23:20:29 +01:00
Aharnish Solanki
2cfb553320 Fix: Left-align the submit button on each card for pdf operation (#1897) 2024-09-14 20:35:26 +01:00
Anthony Stirling
909a3347a0 Update messages_it_IT.properties 2024-09-14 16:37:02 +01:00
Anthony Stirling
de4144a1a4 Metadata handling for all PDF endpoints (#1894)
* Add image support to multi-tool page

Related to #278

* changes to support image types

* final touches

* final touches

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* Update translation files (#1888)

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>

* final touches

Signed-off-by: a <a>

---------

Signed-off-by: a <a>
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: a <a>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-09-14 16:29:39 +01:00
github-actions[bot]
bb1c859e0d 📝 Update README: Translation Progress Table (#1890)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-14 10:46:25 +01:00
Charan19001A0231
b2862a3fc4 Update add-watermark.html (#1893)
shifted add watermark submit button to left
2024-09-14 10:46:16 +01:00
albanobattistella
8788a7ee34 Update messages_it_IT.properties (#1891) 2024-09-13 18:17:19 +01:00
Anthony Stirling
8c01425eee Lots of changes (#1889)
* Add image support to multi-tool page

Related to #278

* changes to support image types

* final touches

* final touches

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* final touches

Signed-off-by: a <a>

* Update translation files (#1888)

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>

---------

Signed-off-by: a <a>
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: a <a>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-09-13 16:42:38 +01:00
Anthony Stirling
4d47e535b5 Update README.md 2024-09-12 13:30:05 +01:00
see-more
1fb78c3124 fix:Remove add image and Align download input file with same width as pdf file input (#1884)
fix:Remove add image button as the button was non functional. Align download input file with same width as pdf file input
2024-09-12 11:47:13 +00:00
Anthony Stirling
6a9dd4ea95 Delete CNAME 2024-09-10 22:59:45 +01:00
Anthony Stirling
0773b8e11b Create CNAME 2024-09-10 22:10:15 +01:00
github-actions[bot]
184d89c44a Update 3rd Party Licenses (#1873)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-09-10 11:19:24 +01:00
github-actions[bot]
df2e9bfc6e 📝 Update README: Translation Progress Table (#1872)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-10 11:18:49 +01:00
dependabot[bot]
0045fe852b Bump com.fathzer:javaluator from 3.0.4 to 3.0.5 (#1867)
Bumps [com.fathzer:javaluator](https://github.com/fathzer/javaluator) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/fathzer/javaluator/releases)
- [Commits](https://github.com/fathzer/javaluator/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: com.fathzer:javaluator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 11:18:24 +01:00
dependabot[bot]
7d46d61d9e Bump io.micrometer:micrometer-core from 1.13.3 to 1.13.4 (#1866)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.13.3 to 1.13.4.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.13.3...v1.13.4)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 11:14:30 +01:00
O2bmm
f8404ce9e9 Update messages_zh_CN.properties (#1871) 2024-09-10 11:14:01 +01:00
Tim
7fad973a77 Changed Spacing between between Buttons and Spacing of Settings Menu (#1864)
Co-authored-by: TSO <tim.sommer@bieber-marburg.de>
2024-09-10 08:02:56 +01:00
dependabot[bot]
6410a99cf3 Bump alpine from 3.20.2 to 3.20.3 (#1865)
Bumps alpine from 3.20.2 to 3.20.3.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 23:47:16 +01:00
github-actions[bot]
62e7c7e073 📝 Update README: Translation Progress Table (#1863)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 22:35:49 +01:00
Anthony Stirling
1d29a500b3 Update messages_en_US.properties 2024-09-09 22:19:23 +01:00
Anthony Stirling
08b085c763 Update messages_en_GB.properties 2024-09-09 22:19:06 +01:00
Tim
f256e8f029 Changed <br> to get a consistent overlay-pdf form (#1849)
Repaired the overlay-pdfs.counts.label to be displayed and not deleted on change
2024-09-09 18:58:35 +01:00
designtesbrot
0ad8c635ad fix(stamp): radius styles of color input (#1862)
Closes 1830
2024-09-09 18:58:04 +01:00
albanobattistella
12ff0ecac2 Update messages_it_IT.properties (#1854) 2024-09-09 14:50:57 +01:00
Anthony Stirling
291bad4a2a Update messages_en_US.properties 2024-09-09 11:35:36 +01:00
Anthony Stirling
c6ee96512a Update messages_en_GB.properties 2024-09-09 11:35:14 +01:00
Anthony Stirling
db563c765d Minor fixes stopping invalid sessions (#1850)
* Update UserAuthenticationFilter.java

* Update RequestUriUtils.java

* Update RequestUriUtils.java

* Update RequestUriUtilsTest.java
2024-09-08 22:06:46 +01:00
github-actions[bot]
6f52189ed2 📝 Update README: Translation Progress Table (#1851)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-08 21:59:06 +01:00
Ignacio Carrera
580313151b Page Scale: add pageSize KEEP #1798 (#1800)
* add `scalePages.keepPageSize` i18n key (#1798)

* add KEEP option to frontend (#1798)

* extract ScalePagesController.getTargetSize() (#1798)

* make ScalePageController honor `pageSize` value `KEEP`

* PR feedback: make caption shorter, avoid unnecessary verbosity (#1798)

* Update messages_ar_AR.properties

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-09-08 21:52:50 +01:00
Dinesh Sharma
765289c89e Fixed reduce extra space between input field & button #1829 (#1848)
* Add <br> when Bored Waiting Button Appears

* Added <br> below progress bar as well

* Removed <br>

Two <br> tags were added which were taking lot of space. I dynamically added space when needed.
2024-09-08 19:08:21 +00:00
Anthony Stirling
3d8686211d [Snyk] Security upgrade alpine from 3.20.2 to 3.20.3 (#1840)
fix: Dockerfile to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-7895537
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-7895537

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-08 13:11:47 +01:00
Anthony Stirling
0a98e3bde3 [Snyk] Security upgrade alpine from 3.20.2 to 3.20.3 (#1839)
fix: Dockerfile-ultra-lite to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-7895537
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-7895537

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-08 13:11:33 +01:00
FiratUsta
78211d09c5 [Bug Fix] Fix Firefox Page Drag Bug (#1837)
Fix a bug where the file drop prompt would show during page drag on Firefox.

Co-authored-by: kazandaki <ahmetfiratusta@gmail.com>
2024-09-07 22:34:36 +01:00
FiratUsta
82219dd899 [Bug Fix] Multiple Bug Fixes (#1836)
* Fix the add file button on the multi-tools page.

* Fix a bug where the page numbers wouldn't be removed on page move on Firefox.

---------

Co-authored-by: kazandaki <ahmetfiratusta@gmail.com>
2024-09-07 21:40:19 +01:00
FiratUsta
3c04486348 Add document splitting functionality to the multi-tools page (#1808)
* Add a split button on top of the insert button in multitool viewer.

* Add placeholder splitFileButtonCallback method.

* Remove unused splitFileButtonContainer element.

* Add this binding to setActions for splitFileButtonCallback

* Add test log for adding separators.

* Add test log for adding separators.

* Remove test logs and add visual indicators to separators instead.

* Add splitting functionality to multi-tools.

* Prevent trying to split from index 0.

* Hide the split button for the first page to avoid confusion.

* Change the class name 'cutBefore' to 'split-before' to fall mroe in line with already existing classes.

* Add dummy methods for splitting and compressing documents.

* Remove form submission, begin work on client side splitting.

* Add client side document splitting.

* Add client side archiving for the split documents.

* Fix a bug that adds an empty page to splitted documents due to a sorting error.

* Add a 'Split All' button and the relevant functionality.

---------

Co-authored-by: kazandaki <ahmetfiratusta@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-09-07 11:25:39 +01:00
Anthony Stirling
7ccb4d59b0 Footer link to Stirlingpdf.com (#1827)
* fix

* remove donate

* Footer to have link to website

---------

Co-authored-by: a <a>
2024-09-06 15:56:55 +01:00
github-actions[bot]
d9309563d6 📝 Update README: Translation Progress Table (#1826)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-06 11:19:44 +01:00
Anthony Stirling
9da7dd45c1 Update messages_ar_AR.properties (#1825)
* Update messages_ar_AR.properties

* Update messages_ar_AR.properties
2024-09-06 11:15:23 +01:00
github-actions[bot]
93e51e47ff 📝 Update README: Translation Progress Table (#1823)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-06 10:16:49 +01:00
Anthony Stirling
1fe5db3611 Update messages_ro_RO.properties (#1822)
* Update messages_ro_RO.properties

* Update messages_ro_RO.properties
2024-09-06 10:16:10 +01:00
github-actions[bot]
d9d39570a2 📝 Update README: Translation Progress Table (#1821)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-06 09:58:33 +01:00
Anthony Stirling
9a49a554b1 Update messages_sv_SE.properties (#1820) 2024-09-06 09:57:15 +01:00
github-actions[bot]
f2573fd297 📝 Update README: Translation Progress Table (#1816)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-05 20:28:06 +01:00
Anthony Stirling
f05a3927fd Update messages_da_DK.properties 2024-09-05 20:27:27 +01:00
Anthony Stirling
fbf97c486b Update messages_da_DK.properties 2024-09-05 20:20:59 +01:00
albanobattistella
87ca994ec6 Update messages_it_IT.properties (#1815) 2024-09-05 20:16:17 +01:00
Anthony Stirling
d72009dddb test (#1814)
* Update messages_da_DK.properties

* Update messages_da_DK.properties

* Update messages_da_DK.properties

* Update messages_da_DK.properties

* Update messages_da_DK.properties
2024-09-05 20:15:51 +01:00
github-actions[bot]
6944df435f 📝 Update README: Translation Progress Table (#1813)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-05 18:44:12 +01:00
github-actions[bot]
60ba90b32d Update translation files (#1812)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-09-05 18:17:29 +01:00
creator1999
c650a766a9 Added functionality to set font size and font type in both frontend and backend. (#1783)
* Added variables

* Added functionality to add font size and font type in both frontend and backend

* new changes suggested has been added

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-09-05 17:54:38 +01:00
FiratUsta
d5b0f1f4ab Fix insertFileButton referencing the old addPdfs method. (#1809) 2024-09-05 15:14:22 +01:00
github-actions[bot]
81dbfa220f 📝 Update README: Translation Progress Table (#1806)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-05 09:39:31 +01:00
Krishna Vamsi Sistla
86cbde4ec2 Language change contribution (Hindi) (#1799)
Update messages_hi_IN.properties

Added few words to hindi language
2024-09-05 09:29:47 +01:00
Ludy
ff519adebb Add info translation (#1791)
* adds the note about adding new translation tags

* Update pull_request_template.md

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-09-05 09:23:04 +01:00
Rudra-241
03887cc9f9 Feature: Split PDFs by Chapters/Bookmarks (#1786)
* feature:split pdf by chapters

* Update SplitPdfByChaptersController.java

* Update SplitPdfByChaptersController.java

* Update SplitPdfByChaptersController.java
2024-09-04 20:21:35 +01:00
156 changed files with 4855 additions and 3794 deletions

View File

@@ -4,9 +4,10 @@ Please provide a summary of the changes, including relevant motivation and conte
Closes #(issue_number) Closes #(issue_number)
## Checklist: ## Checklist
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code - [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings - [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)

View File

@@ -1,5 +1,5 @@
# Main stage # Main stage
FROM alpine:3.20.2 FROM alpine:3.20.3
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts

View File

@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
./gradlew clean build ./gradlew clean build
# Main stage # Main stage
FROM alpine:3.20.2 FROM alpine:3.20.3
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts

View File

@@ -1,5 +1,5 @@
# use alpine # use alpine
FROM alpine:3.20.2 FROM alpine:3.20.3
ARG VERSION_TAG ARG VERSION_TAG

View File

@@ -1,7 +1,6 @@
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1> <p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
</p> </p>
# How to add new languages to Stirling-PDF # How to add new languages to Stirling-PDF
Fork Stirling-PDF and make a new branch out of Main Fork Stirling-PDF and make a new branch out of Main
@@ -14,13 +13,14 @@ https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/stat
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/) Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
If your language isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia If your language isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
For example to add Polish you would add For example to add Polish you would add
```html ```html
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL"> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a> </a>
``` ```
The data-language-code is the code used to reference the file in the next step. The data-language-code is the code used to reference the file in the next step.
Start by copying the existing english property file Start by copying the existing english property file
@@ -29,7 +29,6 @@ Start by copying the existing english property file
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
Then simply translate all property entries within that file and make a PR into main for others to use! Then simply translate all property entries within that file and make a PR into main for others to use!
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves) If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves)
@@ -48,4 +47,10 @@ ignore = [
] ]
``` ```
## Add New Translation Tags
- **Important**: If you add any new translation tags, they must first be added to the `messages_en_GB.properties` file. This ensures consistency across all language files.
- New translation tags **must be added** to the `messages_en_GB.properties` file to maintain a reference for other languages.
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate. Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.

View File

@@ -100,6 +100,8 @@ Demo of the app is available [here](https://stirlingpdf.io).
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib) - [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
## How to use ## How to use
### Windows
For windows users download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe)
### Locally ### Locally
@@ -170,42 +172,42 @@ Stirling PDF currently supports 38!
| Language | Progress | | Language | Progress |
| ------------------------------------------- | -------------------------------------- | | ------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![44%](https://geps.dev/progress/44) | | Arabic (العربية) (ar_AR) | ![99%](https://geps.dev/progress/99) |
| Basque (Euskara) (eu_ES) | ![60%](https://geps.dev/progress/60) | | Basque (Euskara) (eu_ES) | ![60%](https://geps.dev/progress/60) |
| Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) | | Bulgarian (Български) (bg_BG) | ![91%](https://geps.dev/progress/91) |
| Catalan (Català) (ca_CA) | ![47%](https://geps.dev/progress/47) | | Catalan (Català) (ca_CA) | ![47%](https://geps.dev/progress/47) |
| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) | | Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) |
| Czech (Česky) (cs_CZ) | ![88%](https://geps.dev/progress/88) | | Czech (Česky) (cs_CZ) | ![87%](https://geps.dev/progress/87) |
| Danish (Dansk) (da_DK) | ![9%](https://geps.dev/progress/9) | | Danish (Dansk) (da_DK) | ![96%](https://geps.dev/progress/96) |
| Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) | | Dutch (Nederlands) (nl_NL) | ![93%](https://geps.dev/progress/93) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) | | French (Français) (fr_FR) | ![90%](https://geps.dev/progress/90) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) | | Greek (Ελληνικά) (el_GR) | ![79%](https://geps.dev/progress/79) |
| Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) | | Hindi (हिंदी) (hi_IN) | ![76%](https://geps.dev/progress/76) |
| Hungarian (Magyar) (hu_HU) | ![74%](https://geps.dev/progress/74) | | Hungarian (Magyar) (hu_HU) | ![73%](https://geps.dev/progress/73) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![74%](https://geps.dev/progress/74) | | Indonesia (Bahasa Indonesia) (id_ID) | ![74%](https://geps.dev/progress/74) |
| Irish (Gaeilge) (ga_IE) | ![96%](https://geps.dev/progress/96) | | Irish (Gaeilge) (ga_IE) | ![95%](https://geps.dev/progress/95) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![90%](https://geps.dev/progress/90) | | Japanese (日本語) (ja_JP) | ![89%](https://geps.dev/progress/89) |
| Korean (한국어) (ko_KR) | ![82%](https://geps.dev/progress/82) | | Korean (한국어) (ko_KR) | ![81%](https://geps.dev/progress/81) |
| Norwegian (Norsk) (no_NB) | ![96%](https://geps.dev/progress/96) | | Norwegian (Norsk) (no_NB) | ![95%](https://geps.dev/progress/95) |
| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) | | Polish (Polski) (pl_PL) | ![89%](https://geps.dev/progress/89) |
| Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) | | Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) |
| Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) | | Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![38%](https://geps.dev/progress/38) | | Romanian (Română) (ro_RO) | ![97%](https://geps.dev/progress/97) |
| Russian (Русский) (ru_RU) | ![82%](https://geps.dev/progress/82) | | Russian (Русский) (ru_RU) | ![81%](https://geps.dev/progress/81) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![76%](https://geps.dev/progress/76) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![76%](https://geps.dev/progress/76) |
| Simplified Chinese (简体中文) (zh_CN) | ![96%](https://geps.dev/progress/96) | | Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
| Slovakian (Slovensky) (sk_SK) | ![90%](https://geps.dev/progress/90) | | Slovakian (Slovensky) (sk_SK) | ![89%](https://geps.dev/progress/89) |
| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) | | Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![38%](https://geps.dev/progress/38) | | Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) |
| Thai (ไทย) (th_TH) | ![97%](https://geps.dev/progress/97) | | Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) |
| Traditional Chinese (繁體中文) (zh_TW) | ![96%](https://geps.dev/progress/96) | | Traditional Chinese (繁體中文) (zh_TW) | ![95%](https://geps.dev/progress/95) |
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) | | Turkish (Türkçe) (tr_TR) | ![96%](https://geps.dev/progress/96) |
| Ukrainian (Українська) (uk_UA) | ![87%](https://geps.dev/progress/87) | | Ukrainian (Українська) (uk_UA) | ![87%](https://geps.dev/progress/87) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![97%](https://geps.dev/progress/97) | | Vietnamese (Tiếng Việt) (vi_VN) | ![96%](https://geps.dev/progress/96) |
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)

View File

@@ -22,7 +22,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.28.3" version = "0.29.0"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -184,14 +184,14 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.13.3" implementation "io.micrometer:micrometer-core:1.13.4"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.22.0" implementation "org.commonmark:commonmark:0.22.0"
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0" implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0" implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.4" implementation "com.fathzer:javaluator:3.0.5"
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion") developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
compileOnly "org.projectlombok:lombok:$lombokVersion" compileOnly "org.projectlombok:lombok:$lombokVersion"

View File

@@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.28.3 appVersion: 0.29.0
description: locally hosted web application that allows you to perform various operations description: locally hosted web application that allows you to perform various operations
on PDF files on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF home: https://github.com/Stirling-Tools/Stirling-PDF

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security-Fat container_name: Stirling-PDF-Security-Fat

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security container_name: Stirling-PDF-Security

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Security container_name: Stirling-PDF-Security

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite-Security container_name: Stirling-PDF-Ultra-Lite-Security

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite container_name: Stirling-PDF-Ultra-Lite

View File

@@ -1,4 +1,3 @@
version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
container_name: Stirling-PDF container_name: Stirling-PDF

View File

@@ -0,0 +1,25 @@
package stirling.software.SPDF.EE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@Lazy
public class EEAppConfig {
private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class);
@Autowired ApplicationProperties applicationProperties;
@Bean(name = "RunningEE")
public boolean runningEnterpriseEdition() {
// TODO: Implement EE detection
return false;
}
}

View File

@@ -135,4 +135,29 @@ public class AppConfig {
} }
}; };
} }
@Bean(name = "termsAndConditions")
public String termsAndConditions() {
return applicationProperties.getLegal().getTermsAndConditions();
}
@Bean(name = "privacyPolicy")
public String privacyPolicy() {
return applicationProperties.getLegal().getPrivacyPolicy();
}
@Bean(name = "cookiePolicy")
public String cookiePolicy() {
return applicationProperties.getLegal().getCookiePolicy();
}
@Bean(name = "impressum")
public String impressum() {
return applicationProperties.getLegal().getImpressum();
}
@Bean(name = "accessibilityStatement")
public String accessibilityStatement() {
return applicationProperties.getLegal().getAccessibilityStatement();
}
} }

View File

@@ -18,7 +18,7 @@ class AppUpdateService {
@Bean(name = "shouldShow") @Bean(name = "shouldShow")
@Scope("request") @Scope("request")
public boolean shouldShow() { public boolean shouldShow() {
boolean showUpdate = applicationProperties.getSystem().getShowUpdate(); boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true; boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
return showUpdate && showAdminResult; return showUpdate && showAdminResult;
} }

View File

@@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component @Component
public class MetricsFilter extends OncePerRequestFilter { public class MetricsFilter extends OncePerRequestFilter {
@@ -30,32 +31,16 @@ public class MetricsFilter extends OncePerRequestFilter {
throws ServletException, IOException { throws ServletException, IOException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
// System.out.println("uri="+uri + ", method=" + request.getMethod() ); if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
// Ignore static resources
if (!(uri.startsWith("/js")
|| uri.startsWith("/v1/api-docs")
|| uri.endsWith("robots.txt")
|| uri.startsWith("/images")
|| uri.endsWith(".png")
|| uri.endsWith(".ico")
|| uri.endsWith(".css")
|| uri.endsWith(".map")
|| uri.endsWith(".svg")
|| uri.endsWith(".js")
|| uri.contains("swagger")
|| uri.startsWith("/api/v1/info")
|| uri.startsWith("/site.webmanifest")
|| uri.startsWith("/fonts")
|| uri.startsWith("/pdfjs"))) {
Counter counter = Counter counter =
Counter.builder("http.requests") Counter.builder("http.requests")
.tag("uri", uri) .tag("session", request.getSession().getId())
.tag("method", request.getMethod()) .tag("method", request.getMethod())
.tag("uri", uri)
.register(meterRegistry); .register(meterRegistry);
counter.increment(); counter.increment();
// System.out.println("Counted");
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@@ -0,0 +1,109 @@
package stirling.software.SPDF.config;
import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PdfMetadata;
@Service
public class PdfMetadataService {
private final ApplicationProperties applicationProperties;
private final String appVersion;
private final UserServiceInterface userService;
@Autowired
public PdfMetadataService(
ApplicationProperties applicationProperties,
@Qualifier("appVersion") String appVersion,
@Autowired(required = false) UserServiceInterface userService) {
this.applicationProperties = applicationProperties;
this.appVersion = appVersion;
this.userService = userService;
}
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
return PdfMetadata.builder()
.author(pdf.getDocumentInformation().getAuthor())
.producer(pdf.getDocumentInformation().getProducer())
.title(pdf.getDocumentInformation().getTitle())
.creator(pdf.getDocumentInformation().getCreator())
.subject(pdf.getDocumentInformation().getSubject())
.keywords(pdf.getDocumentInformation().getKeywords())
.creationDate(pdf.getDocumentInformation().getCreationDate())
.modificationDate(pdf.getDocumentInformation().getModificationDate())
.build();
}
public void setDefaultMetadata(PDDocument pdf) {
PdfMetadata metadata = extractMetadataFromPdf(pdf);
setMetadataToPdf(pdf, metadata);
}
public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
setMetadataToPdf(pdf, pdfMetadata, false);
}
public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata, boolean newlyCreated) {
if (newlyCreated || pdfMetadata.getCreationDate() == null) {
setNewDocumentMetadata(pdf, pdfMetadata);
}
setCommonMetadata(pdf, pdfMetadata);
}
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
String creator = "Stirling-PDF";
// if (applicationProperties
// .getEnterpriseEdition()
// .getCustomMetadata()
// .isAutoUpdateMetadata()) {
// producer =
//
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer();
// creator =
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
// title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle();
// if ("{filename}".equals(title)) {
// title = "Filename"; // Replace with actual filename logic
// } else if ("{unchanged}".equals(title)) {
// title = pdfMetadata.getTitle(); // Keep the original title
// }
// }
pdf.getDocumentInformation().setCreator(creator + " " + appVersion);
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
}
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
String producer = "Stirling-PDF";
String title = pdfMetadata.getTitle();
pdf.getDocumentInformation().setTitle(title);
pdf.getDocumentInformation().setProducer(producer + " " + appVersion);
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
String author = pdfMetadata.getAuthor();
// if (applicationProperties
// .getEnterpriseEdition()
// .getCustomMetadata()
// .isAutoUpdateMetadata()) {
// author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
// if (userService != null) {
// author = author.replace("username", userService.getCurrentUsername());
// }
// }
pdf.getDocumentInformation().setAuthor(author);
}
}

View File

@@ -20,7 +20,7 @@ class AppUpdateAuthService implements ShowAdminInterface {
@Override @Override
public boolean getShowUpdateOnlyAdmins() { public boolean getShowUpdateOnlyAdmins() {
boolean showUpdate = applicationProperties.getSystem().getShowUpdate(); boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
if (!showUpdate) { if (!showUpdate) {
return showUpdate; return showUpdate;
} }

View File

@@ -152,8 +152,8 @@ public class SecurityConfiguration {
.authenticated()); .authenticated());
// Handle OAUTH2 Logins // Handle OAUTH2 Logins
if (applicationProperties.getSecurity().getOAUTH2() != null if (applicationProperties.getSecurity().getOauth2() != null
&& applicationProperties.getSecurity().getOAUTH2().getEnabled() && applicationProperties.getSecurity().getOauth2().getEnabled()
&& !applicationProperties && !applicationProperties
.getSecurity() .getSecurity()
.getLoginMethod() .getLoginMethod()
@@ -222,7 +222,7 @@ public class SecurityConfiguration {
} }
private Optional<ClientRegistration> googleClientRegistration() { private Optional<ClientRegistration> googleClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) { if (oauth == null || !oauth.getEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@@ -251,7 +251,7 @@ public class SecurityConfiguration {
} }
private Optional<ClientRegistration> keycloakClientRegistration() { private Optional<ClientRegistration> keycloakClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) { if (oauth == null || !oauth.getEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@@ -275,7 +275,7 @@ public class SecurityConfiguration {
} }
private Optional<ClientRegistration> githubClientRegistration() { private Optional<ClientRegistration> githubClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) { if (oauth == null || !oauth.getEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@@ -304,7 +304,7 @@ public class SecurityConfiguration {
} }
private Optional<ClientRegistration> oidcClientRegistration() { private Optional<ClientRegistration> oidcClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null if (oauth == null
|| oauth.getIssuer() == null || oauth.getIssuer() == null
|| oauth.getIssuer().isEmpty() || oauth.getIssuer().isEmpty()
@@ -352,7 +352,7 @@ public class SecurityConfiguration {
String useAsUsername = String useAsUsername =
applicationProperties applicationProperties
.getSecurity() .getSecurity()
.getOAUTH2() .getOauth2()
.getUseAsUsername(); .getUseAsUsername();
Optional<User> userOpt = Optional<User> userOpt =
userService.findByUsernameIgnoreCase( userService.findByUsernameIgnoreCase(

View File

@@ -159,7 +159,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
}; };
for (String pattern : permitAllPatterns) { for (String pattern : permitAllPatterns) {
if (uri.startsWith(pattern) || uri.endsWith(".svg")) { if (uri.startsWith(pattern)
|| uri.endsWith(".svg")
|| uri.endsWith(".png")
|| uri.endsWith(".ico")) {
return true; return true;
} }
} }

View File

@@ -1,5 +1,9 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@@ -7,12 +11,14 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.config.DatabaseBackupInterface; import stirling.software.SPDF.config.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
@@ -23,10 +29,6 @@ import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.AuthorityRepository; import stirling.software.SPDF.repository.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service @Service
public class UserService implements UserServiceInterface { public class UserService implements UserServiceInterface {
@@ -341,4 +343,14 @@ public class UserService implements UserServiceInterface {
} }
} }
} }
public String getCurrentUsername() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else {
return principal.toString();
}
}
} }

View File

@@ -66,7 +66,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
// Redirect to the original destination // Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication); super.onAuthenticationSuccess(request, response, authentication);
} else { } else {
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
if (loginAttemptService.isBlocked(username)) { if (loginAttemptService.isBlocked(username)) {
if (session != null) { if (session != null) {

View File

@@ -43,7 +43,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
} }
return; return;
} }
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (authentication instanceof OAuth2AuthenticationToken) { if (authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;

View File

@@ -43,7 +43,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
@Override @Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
String usernameAttribute = oauth2.getUseAsUsername(); String usernameAttribute = oauth2.getUseAsUsername();
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) { if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
Client client = oauth2.getClient(); Client client = oauth2.getClient();

View File

@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -32,6 +34,13 @@ public class CropController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class); private static final Logger logger = LoggerFactory.getLogger(CropController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public CropController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/crop", consumes = "multipart/form-data") @PostMapping(value = "/crop", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Crops a PDF document", summary = "Crops a PDF document",
@@ -40,7 +49,8 @@ public class CropController {
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException { public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes()); PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
PDDocument newDocument = new PDDocument(); PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int totalPages = sourceDocument.getNumberOfPages(); int totalPages = sourceDocument.getNumberOfPages();

View File

@@ -22,6 +22,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -43,9 +45,16 @@ public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class); private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public MergeController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
// Merges a list of PDDocument objects into a single PDDocument // Merges a list of PDDocument objects into a single PDDocument
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
PDDocument mergedDoc = new PDDocument(); PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();
for (PDDocument doc : documents) { for (PDDocument doc : documents) {
for (PDPage page : doc.getPages()) { for (PDPage page : doc.getPages()) {
mergedDoc.addPage(page); mergedDoc.addPage(page);

View File

@@ -14,6 +14,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest; import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -35,6 +37,13 @@ public class MultiPageLayoutController {
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class); private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public MultiPageLayoutController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data") @PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Merge multiple pages of a PDF document into a single page", summary = "Merge multiple pages of a PDF document into a single page",
@@ -60,7 +69,8 @@ public class MultiPageLayoutController {
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument newDocument = new PDDocument(); PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(PDRectangle.A4); PDPage newPage = new PDPage(PDRectangle.A4);
newDocument.addPage(newPage); newDocument.addPage(newPage);

View File

@@ -3,16 +3,15 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.PdfImageRemovalService; import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -25,15 +24,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class PdfImageRemovalController { public class PdfImageRemovalController {
// Service for removing images from PDFs // Service for removing images from PDFs
@Autowired private PdfImageRemovalService pdfImageRemovalService; private final PdfImageRemovalService pdfImageRemovalService;
private final CustomPDDocumentFactory pdfDocumentFactory;
/** /**
* Constructor for dependency injection of PdfImageRemovalService. * Constructor for dependency injection of PdfImageRemovalService.
* *
* @param pdfImageRemovalService The service used for removing images from PDFs. * @param pdfImageRemovalService The service used for removing images from PDFs.
*/ */
public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) { @Autowired
public PdfImageRemovalController(
PdfImageRemovalService pdfImageRemovalService,
CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfImageRemovalService = pdfImageRemovalService; this.pdfImageRemovalService = pdfImageRemovalService;
this.pdfDocumentFactory = pdfDocumentFactory;
} }
/** /**
@@ -53,14 +58,8 @@ public class PdfImageRemovalController {
description = description =
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO") "This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException { public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
// Load the PDF document
MultipartFile pdf = file.getFileInput(); PDDocument document = pdfDocumentFactory.load(file);
// Convert the MultipartFile to a byte array
byte[] pdfBytes = pdf.getBytes();
// Load the PDF document from the byte array
PDDocument document = Loader.loadPDF(pdfBytes);
// Remove images from the PDF document using the service // Remove images from the PDF document using the service
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document); PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
@@ -74,7 +73,8 @@ public class PdfImageRemovalController {
// Generate a new filename for the modified PDF // Generate a new filename for the modified PDF
String mergedFileName = String mergedFileName =
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf"; file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_removed_images.pdf";
// Convert the byte array to a web response and return it // Convert the byte array to a web response and return it
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName); return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);

View File

@@ -12,6 +12,7 @@ import java.util.Map;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest; import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -33,6 +35,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs") @Tag(name = "General", description = "General APIs")
public class PdfOverlayController { public class PdfOverlayController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data") @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Overlay PDF files in various modes", summary = "Overlay PDF files in various modes",
@@ -56,7 +65,7 @@ public class PdfOverlayController {
// "FixedRepeatOverlay" // "FixedRepeatOverlay"
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes()); try (PDDocument basePdf = pdfDocumentFactory.load(baseFile);
Overlay overlay = new Overlay()) { Overlay overlay = new Overlay()) {
Map<Integer, String> overlayGuide = Map<Integer, String> overlayGuide =
prepareOverlayGuide( prepareOverlayGuide(

View File

@@ -10,6 +10,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.SortTypes; import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -34,6 +36,13 @@ public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages") @PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@Operation( @Operation(
summary = "Remove pages from a PDF file", summary = "Remove pages from a PDF file",
@@ -45,7 +54,7 @@ public class RearrangePagesPDFController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pagesToDelete = request.getPageNumbers(); String pagesToDelete = request.getPageNumbers();
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(pdfFile);
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(","); String[] pageOrderArr = pagesToDelete.split(",");

View File

@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.RotatePDFRequest; import stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -29,6 +30,13 @@ public class RotationController {
private static final Logger logger = LoggerFactory.getLogger(RotationController.class); private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public RotationController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf") @PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@Operation( @Operation(
summary = "Rotate a PDF file", summary = "Rotate a PDF file",
@@ -39,7 +47,7 @@ public class RotationController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle(); Integer angle = request.getAngle();
// Load the PDF document // Load the PDF document
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(request);
// Get the list of pages in the document // Get the list of pages in the document
PDPageTree pages = document.getPages(); PDPageTree pages = document.getPages();

View File

@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -27,6 +28,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.ScalePagesRequest; import stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -36,6 +38,13 @@ public class ScalePagesController {
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class); private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ScalePagesController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data") @PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Change the size of a PDF page/document", summary = "Change the size of a PDF page/document",
@@ -47,29 +56,11 @@ public class ScalePagesController {
String targetPDRectangle = request.getPageSize(); String targetPDRectangle = request.getPageSize();
float scaleFactor = request.getScaleFactor(); float scaleFactor = request.getScaleFactor();
Map<String, PDRectangle> sizeMap = new HashMap<>();
// Add A0 - A10
sizeMap.put("A0", PDRectangle.A0);
sizeMap.put("A1", PDRectangle.A1);
sizeMap.put("A2", PDRectangle.A2);
sizeMap.put("A3", PDRectangle.A3);
sizeMap.put("A4", PDRectangle.A4);
sizeMap.put("A5", PDRectangle.A5);
sizeMap.put("A6", PDRectangle.A6);
// Add other sizes
sizeMap.put("LETTER", PDRectangle.LETTER);
sizeMap.put("LEGAL", PDRectangle.LEGAL);
if (!sizeMap.containsKey(targetPDRectangle)) {
throw new IllegalArgumentException(
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
}
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument outputDocument = new PDDocument(); PDDocument outputDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);
int totalPages = sourceDocument.getNumberOfPages(); int totalPages = sourceDocument.getNumberOfPages();
for (int i = 0; i < totalPages; i++) { for (int i = 0; i < totalPages; i++) {
@@ -116,4 +107,45 @@ public class ScalePagesController {
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_scaled.pdf"); + "_scaled.pdf");
} }
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
if (targetPDRectangle.equals("KEEP")) {
if (sourceDocument.getNumberOfPages() == 0) {
return null;
}
// use the first page to determine the target page size
PDPage sourcePage = sourceDocument.getPage(0);
PDRectangle sourceSize = sourcePage.getMediaBox();
return sourceSize;
}
Map<String, PDRectangle> sizeMap = getSizeMap();
if (sizeMap.containsKey(targetPDRectangle)) {
return sizeMap.get(targetPDRectangle);
}
throw new IllegalArgumentException(
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL, KEEP");
}
private Map<String, PDRectangle> getSizeMap() {
Map<String, PDRectangle> sizeMap = new HashMap<>();
// Add A0 - A6
sizeMap.put("A0", PDRectangle.A0);
sizeMap.put("A1", PDRectangle.A1);
sizeMap.put("A2", PDRectangle.A2);
sizeMap.put("A3", PDRectangle.A3);
sizeMap.put("A4", PDRectangle.A4);
sizeMap.put("A5", PDRectangle.A5);
sizeMap.put("A6", PDRectangle.A6);
// Add other sizes
sizeMap.put("LETTER", PDRectangle.LETTER);
sizeMap.put("LEGAL", PDRectangle.LEGAL);
return sizeMap;
}
} }

View File

@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -27,9 +28,8 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -38,6 +38,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class SplitPDFController { public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class); private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/split-pages") @PostMapping(consumes = "multipart/form-data", value = "/split-pages")
@Operation( @Operation(
@@ -51,7 +57,7 @@ public class SplitPDFController {
// open the pdf document // open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document); // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages(); int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false); List<Integer> pageNumbers = request.getPageNumbersList(document, false);
System.out.println( System.out.println(
@@ -70,7 +76,8 @@ public class SplitPDFController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0; int previousPageNumber = 0;
for (int splitPoint : pageNumbers) { for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) { try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) { for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i); PDPage page = document.getPage(i);
splitDocument.addPage(page); splitDocument.addPage(page);
@@ -79,7 +86,7 @@ public class SplitPDFController {
previousPageNumber = splitPoint + 1; previousPageNumber = splitPoint + 1;
// Transfer metadata to split pdf // Transfer metadata to split pdf
PdfUtils.setMetadataToPdf(splitDocument, metadata); // PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos); splitDocument.save(baos);

View File

@@ -0,0 +1,307 @@
package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
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;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import stirling.software.SPDF.config.PdfMetadataService;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
public class SplitPdfByChaptersController {
private static final Logger logger =
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
private final PdfMetadataService pdfMetadataService;
@Autowired
public SplitPdfByChaptersController(PdfMetadataService pdfMetadataService) {
this.pdfMetadataService = pdfMetadataService;
}
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
@Operation(
summary = "Split PDFs by Chapters",
description = "Splits a PDF into chapters and returns a ZIP file.")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
throws Exception {
MultipartFile file = request.getFileInput();
boolean includeMetadata = request.getIncludeMetadata();
Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
}
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
// checks if the document is encrypted by an empty user password
if (sourceDocument.isEncrypted()) {
try {
sourceDocument.setAllSecurityToBeRemoved(true);
logger.info("Removing security from the source document ");
} catch (Exception e) {
logger.warn("Cannot decrypt the pdf");
}
}
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) {
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
String mergedTitle = "";
List<Bookmark> chaptersToBeRemoved = new ArrayList<>();
for (Bookmark bookmark : bookmarks) {
if (bookmark.getStartPage() == bookmark.getEndPage()) {
mergedTitle = mergedTitle.concat(bookmark.getTitle().concat(" "));
chaptersToBeRemoved.add(bookmark);
} else {
if (!mergedTitle.isEmpty()) {
if (mergedTitle.length() > 255) {
mergedTitle = mergedTitle.substring(0, 253) + "...";
}
bookmarks.set(
bookmarks.indexOf(bookmark),
new Bookmark(
mergedTitle, bookmark.getStartPage(), bookmark.getEndPage()));
}
mergedTitle = "";
}
}
bookmarks.removeAll(chaptersToBeRemoved);
return bookmarks;
}
private static List<Bookmark> extractOutlineItems(
PDDocument sourceDocument,
PDOutlineItem current,
List<Bookmark> bookmarks,
PDOutlineItem nextParent,
int level,
int maxLevel)
throws Exception {
while (current != null) {
String currentTitle = current.getTitle().replace("/", "");
int firstPage =
sourceDocument.getPages().indexOf(current.findDestinationPage(sourceDocument));
PDOutlineItem child = current.getFirstChild();
PDOutlineItem nextSibling = current.getNextSibling();
int endPage;
if (child != null && level < maxLevel) {
endPage =
sourceDocument
.getPages()
.indexOf(child.findDestinationPage(sourceDocument));
} else if (nextSibling != null) {
endPage =
sourceDocument
.getPages()
.indexOf(nextSibling.findDestinationPage(sourceDocument));
} else if (nextParent != null) {
endPage =
sourceDocument
.getPages()
.indexOf(nextParent.findDestinationPage(sourceDocument));
} else {
endPage = -2;
/*
happens when we have something like this:
Outline Item 2
Outline Item 2.1
Outline Item 2.1.1
Outline Item 2.2
Outline 2.2.1
Outline 2.2.2 <--- this item neither has an immediate next parent nor an immediate next sibling
Outline Item 3
*/
}
if (!bookmarks.isEmpty()
&& bookmarks.get(bookmarks.size() - 1).getEndPage() == -2
&& firstPage
>= bookmarks
.get(bookmarks.size() - 1)
.getStartPage()) { // for handling the above-mentioned case
Bookmark previousBookmark = bookmarks.get(bookmarks.size() - 1);
previousBookmark.setEndPage(firstPage);
}
bookmarks.add(new Bookmark(currentTitle, firstPage, endPage));
// Recursively process children
if (child != null && level < maxLevel) {
extractOutlineItems(
sourceDocument, child, bookmarks, nextSibling, level + 1, maxLevel);
}
current = nextSibling;
}
return bookmarks;
}
private Path createZipFile(
List<Bookmark> bookmarks, List<ByteArrayOutputStream> splitDocumentsBoas)
throws Exception {
Path zipFile = Files.createTempFile("split_documents", ".zip");
String fileNumberFormatter = "%0" + (Integer.toString(bookmarks.size()).length()) + "d ";
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
// split files will be named as "[FILE_NUMBER] [BOOKMARK_TITLE].pdf"
String fileName =
String.format(fileNumberFormatter, i)
+ bookmarks.get(i).getTitle()
+ ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile);
return zipFile;
}
public List<ByteArrayOutputStream> getSplitDocumentsBoas(
PDDocument sourceDocument, List<Bookmark> bookmarks, boolean includeMetadata)
throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
PdfMetadata metadata = null;
if (includeMetadata) {
metadata = pdfMetadataService.extractMetadataFromPdf(sourceDocument);
}
for (Bookmark bookmark : bookmarks) {
try (PDDocument splitDocument = new PDDocument()) {
boolean isSinglePage = (bookmark.getStartPage() == bookmark.getEndPage());
for (int i = bookmark.getStartPage();
i < bookmark.getEndPage() + (isSinglePage ? 1 : 0);
i++) {
PDPage page = sourceDocument.getPage(i);
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (includeMetadata) {
pdfMetadataService.setMetadataToPdf(splitDocument, metadata);
}
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
throw e;
}
}
return splitDocumentsBoas;
}
}
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
class Bookmark {
private String title;
private int startPage;
private int endPage;
}

View File

@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest; import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -43,6 +45,13 @@ public class SplitPdfBySectionsController {
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(SplitPdfBySectionsController.class); LoggerFactory.getLogger(SplitPdfBySectionsController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPdfBySectionsController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Split PDF pages into smaller sections", summary = "Split PDF pages into smaller sections",
@@ -65,7 +74,7 @@ public class SplitPdfBySectionsController {
Filenames.toSimpleFileName(file.getOriginalFilename()) Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", ""); .replaceFirst("[.][^.]+$", "");
if (merge) { if (merge) {
MergeController mergeController = new MergeController(); MergeController mergeController = new MergeController(pdfDocumentFactory);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
mergeController.mergeDocuments(splitDocuments).save(baos); mergeController.mergeDocuments(splitDocuments).save(baos);
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf"); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");

View File

@@ -12,6 +12,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest; import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -34,6 +36,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class SplitPdfBySizeController { public class SplitPdfBySizeController {
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class); private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPdfBySizeController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") @PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
@Operation( @Operation(
@@ -84,7 +92,8 @@ public class SplitPdfBySizeController {
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename) PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
throws IOException { throws IOException {
long currentSize = 0; long currentSize = 0;
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int fileIndex = 1; int fileIndex = 1;
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) { for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
@@ -121,7 +130,8 @@ public class SplitPdfBySizeController {
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename) PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
throws IOException { throws IOException {
int currentPageCount = 0; int currentPageCount = 0;
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int fileIndex = 1; int fileIndex = 1;
for (PDPage page : sourceDocument.getPages()) { for (PDPage page : sourceDocument.getPages()) {
currentDoc.addPage(page); currentDoc.addPage(page);
@@ -152,7 +162,8 @@ public class SplitPdfBySizeController {
int currentPageIndex = 0; int currentPageIndex = 0;
int fileIndex = 1; int fileIndex = 1;
for (int i = 0; i < documentCount; i++) { for (int i = 0; i < documentCount; i++) {
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0); int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
for (int j = 0; j < pagesToAdd; j++) { for (int j = 0; j < pagesToAdd; j++) {

View File

@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -32,6 +34,13 @@ public class ToSinglePageController {
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class); private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ToSinglePageController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page") @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
@Operation( @Operation(
summary = "Convert a multi-page PDF into a single long page PDF", summary = "Convert a multi-page PDF into a single long page PDF",
@@ -53,7 +62,8 @@ public class ToSinglePageController {
} }
// Create new document and page with calculated dimensions // Create new document and page with calculated dimensions
PDDocument newDocument = new PDDocument(); PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight)); PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
newDocument.addPage(newPage); newDocument.addPage(newPage);

View File

@@ -208,8 +208,8 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/saveUser") @PostMapping("/admin/saveUser")
public RedirectView saveUser( public RedirectView saveUser(
@RequestParam String username, @RequestParam(name = "username", required = true) String username,
@RequestParam(name = "password", required = false) String password, @RequestParam(name = "password", required = true) String password,
@RequestParam(name = "role") String role, @RequestParam(name = "role") String role,
@RequestParam(name = "authType") String authType, @RequestParam(name = "authType") String authType,
@RequestParam(name = "forceChange", required = false, defaultValue = "false") @RequestParam(name = "forceChange", required = false, defaultValue = "false")

View File

@@ -1,30 +1,36 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController // Disabled for now
@Tag(name = "Convert", description = "Convert APIs") // @RestController
@RequestMapping("/api/v1/convert") // @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertBookToPDFController { public class ConvertBookToPDFController {
@Autowired private final boolean bookAndHtmlFormatsInstalled;
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled; private final CustomPDDocumentFactory pdfDocumentFactory;
// @Autowired
public ConvertBookToPDFController(
CustomPDDocumentFactory pdfDocumentFactory,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
}
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf") @PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
@Operation( @Operation(

View File

@@ -1,28 +1,25 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController // Disabled for now
@Tag(name = "Convert", description = "Convert APIs") // @RestController
@RequestMapping("/api/v1/convert") // @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF { public class ConvertHtmlToPDF {
@Autowired // @Autowired
@Qualifier("bookAndHtmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;

View File

@@ -16,6 +16,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.CheckProgramInstall; import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
@@ -43,6 +45,13 @@ public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertImgPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img") @PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
@Operation( @Operation(
summary = "Convert PDF to image(s)", summary = "Convert PDF to image(s)",
@@ -178,7 +187,8 @@ public class ConvertImgPDFController {
boolean autoRotate = request.isAutoRotate(); boolean autoRotate = request.isAutoRotate();
// Convert the file to PDF and get the resulting bytes // Convert the file to PDF and get the resulting bytes
byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType); byte[] bytes =
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
bytes, bytes,
file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf"); file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");

View File

@@ -10,29 +10,26 @@ import org.commonmark.node.Node;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController // Disabled for now
@Tag(name = "Convert", description = "Convert APIs") // @RestController
@RequestMapping("/api/v1/convert") // @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf { public class ConvertMarkdownToPdf {
@Autowired // @Autowired
@Qualifier("bookAndHtmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -8,6 +9,8 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -20,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -29,7 +33,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertOfficeController { public class ConvertOfficeController {
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension // Check for valid file extension
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()); String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
if (originalFilename == null if (originalFilename == null
@@ -62,12 +66,10 @@ public class ConvertOfficeController {
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the converted PDF file // Read the converted PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile); return tempOutputFile.toFile();
return pdfBytes;
} finally { } finally {
// Clean up the temporary files // Clean up the temporary files
if (tempInputFile != null) Files.deleteIfExists(tempInputFile); if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile);
} }
} }
@@ -76,6 +78,13 @@ public class ConvertOfficeController {
return fileExtension.matches(extensionPattern); return fileExtension.matches(extensionPattern);
} }
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf") @PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
@Operation( @Operation(
summary = "Convert a file to a PDF using LibreOffice", summary = "Convert a file to a PDF using LibreOffice",
@@ -86,12 +95,18 @@ public class ConvertOfficeController {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
// unused but can start server instance if startup time is to long // unused but can start server instance if startup time is to long
// LibreOfficeListener.getInstance().start(); // LibreOfficeListener.getInstance().start();
File file = null;
try {
file = convertToPdf(inputFile);
byte[] pdfByteArray = convertToPdf(inputFile); PDDocument doc = pdfDocumentFactory.load(file);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfByteArray, doc,
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_convertedToPDF.pdf"); + "_convertedToPDF.pdf");
} finally {
if (file != null) file.delete();
}
} }
} }

View File

@@ -6,30 +6,27 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.PdfToBookRequest; import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController // Disabled for now
@Tag(name = "Convert", description = "Convert APIs") // @RestController
@RequestMapping("/api/v1/convert") // @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertPDFToBookController { public class ConvertPDFToBookController {
@Autowired // @Autowired
@Qualifier("bookAndHtmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;

View File

@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -17,6 +16,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest; import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -40,6 +41,13 @@ public class ConvertPDFToPDFA {
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class); private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertPDFToPDFA(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa") @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
@Operation( @Operation(
summary = "Convert a PDF to a PDF/A", summary = "Convert a PDF to a PDF/A",
@@ -54,7 +62,7 @@ public class ConvertPDFToPDFA {
byte[] pdfBytes = inputFile.getBytes(); byte[] pdfBytes = inputFile.getBytes();
// Load the PDF document // Load the PDF document
PDDocument document = Loader.loadPDF(pdfBytes); PDDocument document = pdfDocumentFactory.load(pdfBytes);
// Get the document catalog // Get the document catalog
PDDocumentCatalog catalog = document.getDocumentCatalog(); PDDocumentCatalog catalog = document.getDocumentCatalog();
@@ -101,18 +109,18 @@ public class ConvertPDFToPDFA {
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the optimized PDF file try {
byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile); PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile());
// Return the optimized PDF as a response
// Clean up the temporary files String outputFilename =
Files.deleteIfExists(tempInputFile); Filenames.toSimpleFileName(inputFile.getOriginalFilename())
Files.deleteIfExists(tempOutputFile); .replaceFirst("[.][^.]+$", "")
+ "_PDFA.pdf";
// Return the optimized PDF as a response return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
String outputFilename = } finally {
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) // Clean up the temporary files
.replaceFirst("[.][^.]+$", "") Files.deleteIfExists(tempInputFile);
+ "_PDFA.pdf"; Files.deleteIfExists(tempOutputFile);
return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename); }
} }
} }

View File

@@ -6,6 +6,10 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -16,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -26,6 +31,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF { public class ConvertWebsiteToPDF {
private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertWebsiteToPDF(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@Operation( @Operation(
summary = "Convert a URL to a PDF", summary = "Convert a URL to a PDF",
@@ -46,12 +60,12 @@ public class ConvertWebsiteToPDF {
} }
Path tempOutputFile = null; Path tempOutputFile = null;
byte[] pdfBytes; PDDocument doc = null;
try { try {
// Prepare the output file path // Prepare the output file path
tempOutputFile = Files.createTempFile("output_", ".pdf"); tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command // Prepare the WeasyPrint command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add("weasyprint"); command.add("weasyprint");
command.add(URL); command.add(URL);
@@ -61,16 +75,23 @@ public class ConvertWebsiteToPDF {
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the optimized PDF file // Load the PDF using pdfDocumentFactory
pdfBytes = Files.readAllBytes(tempOutputFile); doc = pdfDocumentFactory.load(tempOutputFile.toFile());
} finally {
// Clean up the temporary files
Files.deleteIfExists(tempOutputFile);
}
// Convert URL to a safe filename
String outputFilename = convertURLToFileName(URL);
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); // Convert URL to a safe filename
String outputFilename = convertURLToFileName(URL);
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
} finally {
if (tempOutputFile != null) {
try {
Files.deleteIfExists(tempOutputFile);
} catch (IOException e) {
logger.error("Error deleting temporary output file", e);
}
}
}
} }
private String convertURLToFileName(String url) { private String convertURLToFileName(String url) {

View File

@@ -30,13 +30,13 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ExtractController { public class ExtractCSVController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class); private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Extracts a PDF document to csv", summary = "Extracts a CSV document from a PDF",
description = description =
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception { public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {

View File

@@ -12,11 +12,11 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest; import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -49,6 +50,13 @@ public class AutoSplitPdfController {
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF"; private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public AutoSplitPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Auto split PDF pages into separate documents", summary = "Auto split PDF pages into separate documents",
@@ -59,73 +67,93 @@ public class AutoSplitPdfController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode(); boolean duplexMode = request.isDuplexMode();
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = null;
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
List<PDDocument> splitDocuments = new ArrayList<>(); List<PDDocument> splitDocuments = new ArrayList<>();
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); Path zipFile = null;
byte[] data = null;
for (int page = 0; page < document.getNumberOfPages(); ++page) { try {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); document = pdfDocumentFactory.load(file.getInputStream());
String result = decodeQRCode(bim); PDFRenderer pdfRenderer = new PDFRenderer(document);
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) { pdfRenderer.setSubsamplingAllowed(true);
splitDocuments.add(new PDDocument());
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
String result = decodeQRCode(bim);
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
splitDocuments.add(new PDDocument());
}
if (!splitDocuments.isEmpty()
&& !QR_CONTENT.equals(result)
&& !QR_CONTENT_OLD.equals(result)) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
} else if (page == 0) {
PDDocument firstDocument = new PDDocument();
firstDocument.addPage(document.getPage(page));
splitDocuments.add(firstDocument);
}
// If duplexMode is true and current page is a divider, then skip next page
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
page++;
}
} }
if (!splitDocuments.isEmpty() // Remove split documents that have no pages
&& !QR_CONTENT.equals(result) splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
&& !QR_CONTENT_OLD.equals(result)) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); zipFile = Files.createTempFile("split_documents", ".zip");
} else if (page == 0) { String filename =
PDDocument firstDocument = new PDDocument(); Filenames.toSimpleFileName(file.getOriginalFilename())
firstDocument.addPage(document.getPage(page)); .replaceFirst("[.][^.]+$", "");
splitDocuments.add(firstDocument);
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
for (int i = 0; i < splitDocuments.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
PDDocument splitDocument = splitDocuments.get(i);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
byte[] pdf = baos.toByteArray();
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
}
} }
// If duplexMode is true and current page is a divider, then skip next page
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
page++;
}
}
// Remove split documents that have no pages
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
for (PDDocument splitDocument : splitDocuments) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
splitDocument.close();
}
document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
}
} catch (Exception e) {
logger.error("exception", e);
} finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
}
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
// Clean up resources
if (document != null) {
try {
document.close();
} catch (IOException e) {
logger.error("Error closing main PDDocument", e);
}
}
for (PDDocument splitDoc : splitDocuments) {
try {
splitDoc.close();
} catch (IOException e) {
logger.error("Error closing split PDDocument", e);
}
}
if (zipFile != null) {
try {
Files.deleteIfExists(zipFile);
} catch (IOException e) {
logger.error("Error deleting temporary zip file", e);
}
}
}
} }
private static String decodeQRCode(BufferedImage bufferedImage) { private static String decodeQRCode(BufferedImage bufferedImage) {

View File

@@ -16,6 +16,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -40,6 +42,13 @@ public class BlankPageController {
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class); private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public BlankPageController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
@Operation( @Operation(
summary = "Remove blank pages from a PDF file", summary = "Remove blank pages from a PDF file",
@@ -124,7 +133,7 @@ public class BlankPageController {
public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName) public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName)
throws IOException { throws IOException {
try (PDDocument document = new PDDocument()) { try (PDDocument document = pdfDocumentFactory.createNewDocument()) {
for (PDPage page : pages) { for (PDPage page : pages) {
document.addPage(page); document.addPage(page);

View File

@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -32,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -44,6 +46,13 @@ public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class); private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public CompressController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
@Operation( @Operation(
summary = "Optimize PDF file", summary = "Optimize PDF file",
@@ -258,7 +267,7 @@ public class CompressController {
} }
// Read the optimized PDF file // Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile); pdfBytes = Files.readAllBytes(tempOutputFile);
Path finalFile = tempOutputFile;
// Check if optimized file is larger than the original // Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) { if (pdfBytes.length > inputFileSize) {
// Log the occurrence // Log the occurrence
@@ -266,14 +275,15 @@ public class CompressController {
"Optimized file is larger than the original. Returning the original file instead."); "Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again // Read the original file again
pdfBytes = Files.readAllBytes(tempInputFile); finalFile = tempInputFile;
} }
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf"; + "_Optimized.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.pdfDocToWebResponse(
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
} finally { } finally {
// Clean up the temporary files // Clean up the temporary files

View File

@@ -14,6 +14,7 @@ import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -25,9 +26,8 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.misc.FlattenRequest; import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -37,6 +37,13 @@ public class FlattenController {
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class); private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public FlattenController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/flatten") @PostMapping(consumes = "multipart/form-data", value = "/flatten")
@Operation( @Operation(
summary = "Flatten PDF form fields or full page", summary = "Flatten PDF form fields or full page",
@@ -46,7 +53,6 @@ public class FlattenController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
Boolean flattenOnlyForms = request.getFlattenOnlyForms(); Boolean flattenOnlyForms = request.getFlattenOnlyForms();
if (Boolean.TRUE.equals(flattenOnlyForms)) { if (Boolean.TRUE.equals(flattenOnlyForms)) {
@@ -60,7 +66,8 @@ public class FlattenController {
// flatten whole page aka convert each page to image and readd it (making text // flatten whole page aka convert each page to image and readd it (making text
// unselectable) // unselectable)
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
PDDocument newDocument = new PDDocument(); PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document);
int numPages = document.getNumberOfPages(); int numPages = document.getNumberOfPages();
for (int i = 0; i < numPages; i++) { for (int i = 0; i < numPages; i++) {
try { try {
@@ -80,7 +87,6 @@ public class FlattenController {
logger.error("exception", e); logger.error("exception", e);
} }
} }
PdfUtils.setMetadataToPdf(newDocument, metadata);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename())); newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
} }

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -28,6 +29,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest; import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -52,6 +54,13 @@ public class OCRController {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf") @PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation( @Operation(
summary = "Process a PDF file with OCR", summary = "Process a PDF file with OCR",
@@ -175,7 +184,7 @@ public class OCRController {
tempOutputFile = tempPdfWithoutImages; tempOutputFile = tempPdfWithoutImages;
} }
// Read the OCR processed PDF file // Read the OCR processed PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile); byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
// Return the OCR processed PDF as a response // Return the OCR processed PDF as a response
String outputFilename = String outputFilename =
@@ -196,7 +205,13 @@ public class OCRController {
// Add PDF file to the zip // Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(outputFilename); ZipEntry pdfEntry = new ZipEntry(outputFilename);
zipOut.putNextEntry(pdfEntry); zipOut.putNextEntry(pdfEntry);
Files.copy(tempOutputFile, zipOut); try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) {
byte[] buffer = new byte[1024];
int length;
while ((length = pdfInputStream.read(buffer)) != -1) {
zipOut.write(buffer, 0, length);
}
}
zipOut.closeEntry(); zipOut.closeEntry();
// Add text file to the zip // Add text file to the zip

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -27,6 +29,13 @@ public class OverlayImageController {
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class); private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public OverlayImageController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/add-image") @PostMapping(consumes = "multipart/form-data", value = "/add-image")
@Operation( @Operation(
summary = "Overlay image onto a PDF file", summary = "Overlay image onto a PDF file",
@@ -41,7 +50,9 @@ public class OverlayImageController {
try { try {
byte[] pdfBytes = pdfFile.getBytes(); byte[] pdfBytes = pdfFile.getBytes();
byte[] imageBytes = imageFile.getBytes(); byte[] imageBytes = imageFile.getBytes();
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage); byte[] result =
PdfUtils.overlayImage(
pdfDocumentFactory, pdfBytes, imageBytes, x, y, everyPage);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
result, result,

View File

@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -13,6 +12,7 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -36,6 +37,13 @@ public class PageNumbersController {
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public PageNumbersController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Add page numbers to a PDF document", summary = "Add page numbers to a PDF document",
@@ -43,6 +51,7 @@ public class PageNumbersController {
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request) public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String customMargin = request.getCustomMargin(); String customMargin = request.getCustomMargin();
int position = request.getPosition(); int position = request.getPosition();
@@ -51,8 +60,9 @@ public class PageNumbersController {
String customText = request.getCustomText(); String customText = request.getCustomText();
int pageNumber = startingNumber; int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes(); byte[] fileBytes = file.getBytes();
PDDocument document = Loader.loadPDF(fileBytes); PDDocument document = pdfDocumentFactory.load(fileBytes);
float font_size = request.getFontSize();
String font_type = request.getFontType();
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
case "small": case "small":
@@ -73,7 +83,7 @@ public class PageNumbersController {
break; break;
} }
float fontSize = 12.0f; float fontSize = font_size;
if (pagesToNumber == null || pagesToNumber.length() == 0) { if (pagesToNumber == null || pagesToNumber.length() == 0) {
pagesToNumber = "all"; pagesToNumber = "all";
} }
@@ -131,7 +141,20 @@ public class PageNumbersController {
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true); document, page, PDPageContentStream.AppendMode.APPEND, true, true);
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize); switch (font_type.toLowerCase()) {
case "helvetica":
contentStream.setFont(
new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize);
break;
case "courier":
contentStream.setFont(
new PDType1Font(Standard14Fonts.FontName.COURIER), fontSize);
break;
case "times":
contentStream.setFont(
new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN), fontSize);
break;
}
contentStream.newLineAtOffset(x, y); contentStream.newLineAtOffset(x, y);
contentStream.showText(text); contentStream.showText(text);
contentStream.endText(); contentStream.endText();

View File

@@ -8,6 +8,7 @@ import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -31,6 +33,13 @@ public class RepairController {
private static final Logger logger = LoggerFactory.getLogger(RepairController.class); private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public RepairController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/repair") @PostMapping(consumes = "multipart/form-data", value = "/repair")
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
@@ -58,7 +67,7 @@ public class RepairController {
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the optimized PDF file // Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile); pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =

View File

@@ -9,6 +9,7 @@ import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -75,7 +76,8 @@ public class ShowJavascript {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
script.getBytes(StandardCharsets.UTF_8), script.getBytes(StandardCharsets.UTF_8),
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js"); Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js",
MediaType.TEXT_PLAIN);
} }
} }
} }

View File

@@ -12,7 +12,6 @@ import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -25,6 +24,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddStampRequest; import stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -45,6 +46,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class StampController { public class StampController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public StampController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp") @PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@Operation( @Operation(
summary = "Add stamp to a PDF file", summary = "Add stamp to a PDF file",
@@ -86,7 +94,7 @@ public class StampController {
} }
// Load the input PDF // Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(pdfFile);
List<Integer> pageNumbers = request.getPageNumbersList(document, true); List<Integer> pageNumbers = request.getPageNumbersList(document, true);

View File

@@ -2,4 +2,6 @@ package stirling.software.SPDF.controller.api.pipeline;
public interface UserServiceInterface { public interface UserServiceInterface {
String getApiKeyForUser(String username); String getApiKeyForUser(String username);
String getCurrentUsername();
} }

View File

@@ -16,7 +16,6 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.signature.CreateSignatureBase; import org.apache.pdfbox.examples.signature.CreateSignatureBase;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
@@ -35,6 +34,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -47,6 +47,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -71,6 +72,13 @@ public class CertSignController {
} }
} }
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation( @Operation(
summary = "Sign PDF with a Digital Certificate", summary = "Sign PDF with a Digital Certificate",
@@ -122,7 +130,7 @@ public class CertSignController {
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
sign(pdf.getBytes(), baos, createSignature, name, location, reason); sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason);
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.boasToWebResponse(
baos, baos,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
@@ -130,13 +138,14 @@ public class CertSignController {
} }
private static void sign( private static void sign(
CustomPDDocumentFactory pdfDocumentFactory,
byte[] input, byte[] input,
OutputStream output, OutputStream output,
CreateSignature instance, CreateSignature instance,
String name, String name,
String location, String location,
String reason) { String reason) {
try (PDDocument doc = Loader.loadPDF(input)) { try (PDDocument doc = pdfDocumentFactory.load(input)) {
PDSignature signature = new PDSignature(); PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

View File

@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.AddPasswordRequest; import stirling.software.SPDF.model.api.security.AddPasswordRequest;
import stirling.software.SPDF.model.api.security.PDFPasswordRequest; import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -30,6 +31,13 @@ public class PasswordController {
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public PasswordController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/remove-password") @PostMapping(consumes = "multipart/form-data", value = "/remove-password")
@Operation( @Operation(
summary = "Remove password from a PDF file", summary = "Remove password from a PDF file",
@@ -39,8 +47,7 @@ public class PasswordController {
throws IOException { throws IOException {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String password = request.getPassword(); String password = request.getPassword();
PDDocument document = pdfDocumentFactory.load(fileInput, password);
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true); document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
@@ -69,7 +76,7 @@ public class PasswordController {
boolean canPrint = request.isCanPrint(); boolean canPrint = request.isCanPrint();
boolean canPrintFaithful = request.isCanPrintFaithful(); boolean canPrintFaithful = request.isCanPrintFaithful();
PDDocument document = Loader.loadPDF(fileInput.getBytes()); PDDocument document = pdfDocumentFactory.load(fileInput);
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent); ap.setCanExtractContent(!canExtractContent);

View File

@@ -5,12 +5,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.PDFText; import stirling.software.SPDF.model.PDFText;
import stirling.software.SPDF.model.api.security.RedactPdfRequest; import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.pdf.TextFinder; import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -35,6 +36,13 @@ public class RedactController {
private static final Logger logger = LoggerFactory.getLogger(RedactController.class); private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public RedactController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data") @PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Redacts listOfText in a PDF document", summary = "Redacts listOfText in a PDF document",
@@ -52,8 +60,7 @@ public class RedactController {
System.out.println(listOfTextString); System.out.println(listOfTextString);
String[] listOfText = listOfTextString.split("\n"); String[] listOfText = listOfTextString.split("\n");
byte[] bytes = file.getBytes(); PDDocument document = pdfDocumentFactory.load(file);
PDDocument document = Loader.loadPDF(bytes);
Color redactColor; Color redactColor;
try { try {

View File

@@ -1,10 +1,8 @@
package stirling.software.SPDF.controller.api.security; package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayOutputStream;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -12,6 +10,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -24,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -33,6 +33,13 @@ public class RemoveCertSignController {
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class); private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public RemoveCertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
@Operation( @Operation(
summary = "Remove digital signature from PDF", summary = "Remove digital signature from PDF",
@@ -42,14 +49,8 @@ public class RemoveCertSignController {
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
// Convert MultipartFile to byte[]
byte[] pdfBytes = pdf.getBytes();
// Create a ByteArrayOutputStream to hold the resulting PDF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Load the PDF document // Load the PDF document
PDDocument document = Loader.loadPDF(pdfBytes); PDDocument document = pdfDocumentFactory.load(pdf);
// Get the document catalog // Get the document catalog
PDDocumentCatalog catalog = document.getDocumentCatalog(); PDDocumentCatalog catalog = document.getDocumentCatalog();
@@ -67,14 +68,9 @@ public class RemoveCertSignController {
acroForm.flatten(fieldsToRemove, false); acroForm.flatten(fieldsToRemove, false);
} }
} }
// Save the modified document to the ByteArrayOutputStream
document.save(baos);
document.close();
// Return the modified PDF as a response // Return the modified PDF as a response
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
baos, document,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_unsigned.pdf"); + "_unsigned.pdf");
} }

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@@ -21,6 +20,7 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.SanitizePdfRequest; import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -40,6 +41,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs") @Tag(name = "Security", description = "Security APIs")
public class SanitizeController { public class SanitizeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public SanitizeController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
@Operation( @Operation(
summary = "Sanitize a PDF file", summary = "Sanitize a PDF file",
@@ -54,7 +62,7 @@ public class SanitizeController {
boolean removeLinks = request.isRemoveLinks(); boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = request.isRemoveFonts(); boolean removeFonts = request.isRemoveFonts();
PDDocument document = Loader.loadPDF(inputFile.getBytes()); PDDocument document = pdfDocumentFactory.load(inputFile);
if (removeJavaScript) { if (removeJavaScript) {
sanitizeJavaScript(document); sanitizeJavaScript(document);
} }

View File

@@ -11,7 +11,6 @@ import java.nio.file.Files;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -23,6 +22,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -36,6 +36,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.AddWatermarkRequest; import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -44,6 +45,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs") @Tag(name = "Security", description = "Security APIs")
public class WatermarkController { public class WatermarkController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public WatermarkController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark") @PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
@Operation( @Operation(
summary = "Add watermark to a PDF file", summary = "Add watermark to a PDF file",
@@ -64,7 +72,7 @@ public class WatermarkController {
boolean convertPdfToImage = request.isConvertPDFToImage(); boolean convertPdfToImage = request.isConvertPDFToImage();
// Load the input PDF // Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = pdfDocumentFactory.load(pdfFile);
// Create a page in the document // Create a page in the document
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {

View File

@@ -1,10 +1,10 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import com.fasterxml.jackson.core.JsonProcessingException; import java.time.Instant;
import com.fasterxml.jackson.databind.ObjectMapper; import java.time.temporal.ChronoUnit;
import io.swagger.v3.oas.annotations.tags.Tag; import java.util.*;
import jakarta.servlet.http.HttpServletRequest; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -13,6 +13,14 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.*; import stirling.software.SPDF.model.*;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
@@ -22,11 +30,6 @@ import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
@Controller @Controller
@Slf4j @Slf4j
@Tag(name = "Account Security", description = "Account Security APIs") @Tag(name = "Account Security", description = "Account Security APIs")
@@ -48,7 +51,7 @@ public class AccountWebController {
Map<String, String> providerList = new HashMap<>(); Map<String, String> providerList = new HashMap<>();
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth != null) { if (oauth != null) {
if (oauth.isSettingsValid()) { if (oauth.isSettingsValid()) {
providerList.put("oidc", oauth.getProvider()); providerList.put("oidc", oauth.getProvider());
@@ -79,7 +82,7 @@ public class AccountWebController {
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod()); model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
model.addAttribute( model.addAttribute(
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled()); "oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled());
model.addAttribute("currentPage", "login"); model.addAttribute("currentPage", "login");
@@ -342,7 +345,7 @@ public class AccountWebController {
// Retrieve username and other attributes // Retrieve username and other attributes
username = username =
userDetails.getAttribute( userDetails.getAttribute(
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername()); applicationProperties.getSecurity().getOauth2().getUseAsUsername());
// Add oAuth2 Login attributes to the model // Add oAuth2 Login attributes to the model
model.addAttribute("oAuth2Login", true); model.addAttribute("oAuth2Login", true);
} }

View File

@@ -2,11 +2,7 @@ package stirling.software.SPDF.controller.web;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Comparator; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -18,19 +14,20 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.StartupApplicationListener; import stirling.software.SPDF.config.StartupApplicationListener;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@RestController @RestController
@RequestMapping("/api/v1/info") @RequestMapping("/api/v1/info")
@Tag(name = "Info", description = "Info APIs") @Tag(name = "Info", description = "Info APIs")
@Slf4j
public class MetricsController { public class MetricsController {
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@@ -46,6 +43,7 @@ public class MetricsController {
this.metricsEnabled = metricsEnabled; this.metricsEnabled = metricsEnabled;
} }
@Autowired
public MetricsController(MeterRegistry meterRegistry) { public MetricsController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
} }
@@ -66,11 +64,11 @@ public class MetricsController {
return ResponseEntity.ok(status); return ResponseEntity.ok(status);
} }
@GetMapping("/loads") @GetMapping("/load")
@Operation( @Operation(
summary = "GET request count", summary = "GET request count",
description = description =
"This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") "This endpoint returns the total count of GET requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getPageLoads( public ResponseEntity<?> getPageLoads(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") @RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) { Optional<String> endpoint) {
@@ -78,44 +76,33 @@ public class MetricsController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
} }
try { try {
double count = getRequestCount("GET", endpoint);
double count = 0.0;
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "GET".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
System.out.println(
"loads "
+ endpoint.get()
+ " vs "
+ meter.getId().getTag("uri"));
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
} else {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
}
}
}
return ResponseEntity.ok(count); return ResponseEntity.ok(count);
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} }
} }
@GetMapping("/loads/all") @GetMapping("/load/unique")
@Operation(
summary = "Unique users count for GET requests",
description =
"This endpoint returns the count of unique users for GET requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getUniquePageLoads(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getUniqueUserCount("GET", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/load/all")
@Operation( @Operation(
summary = "GET requests count for all endpoints", summary = "GET requests count for all endpoints",
description = "This endpoint returns the count of GET requests for each endpoint.") description = "This endpoint returns the count of GET requests for each endpoint.")
@@ -124,37 +111,191 @@ public class MetricsController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
} }
try { try {
Map<String, Double> counts = new HashMap<>(); List<EndpointCount> results = getEndpointCounts("GET");
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "GET".equals(method)) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return ResponseEntity.ok(results); return ResponseEntity.ok(results);
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} }
} }
public class EndpointCount { @GetMapping("/load/all/unique")
@Operation(
summary = "Unique users count for GET requests for all endpoints",
description =
"This endpoint returns the count of unique users for GET requests for each endpoint.")
public ResponseEntity<?> getAllUniqueEndpointLoads() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getUniqueUserCounts("GET");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/requests")
@Operation(
summary = "POST request count",
description =
"This endpoint returns the total count of POST requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getRequestCount("POST", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/unique")
@Operation(
summary = "Unique users count for POST requests",
description =
"This endpoint returns the count of unique users for POST requests for a specific endpoint or all endpoints.")
public ResponseEntity<?> getUniqueTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = getUniqueUserCount("POST", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/all")
@Operation(
summary = "POST requests count for all endpoints",
description = "This endpoint returns the count of POST requests for each endpoint.")
public ResponseEntity<?> getAllPostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getEndpointCounts("POST");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/requests/all/unique")
@Operation(
summary = "Unique users count for POST requests for all endpoints",
description =
"This endpoint returns the count of unique users for POST requests for each endpoint.")
public ResponseEntity<?> getAllUniquePostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
List<EndpointCount> results = getUniqueUserCounts("POST");
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private double getRequestCount(String method, Optional<String> endpoint) {
log.info(
"Getting request count for method: {}, endpoint: {}",
method,
endpoint.orElse("all"));
double count =
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get()
.equals(counter.getId().getTag("uri")))
.mapToDouble(Counter::count)
.sum();
log.info("Request count: {}", count);
return count;
}
private List<EndpointCount> getEndpointCounts(String method) {
log.info("Getting endpoint counts for method: {}", method);
Map<String, Double> counts = new HashMap<>();
meterRegistry
.find("http.requests")
.tag("method", method)
.counters()
.forEach(
counter -> {
String uri = counter.getId().getTag("uri");
counts.merge(uri, counter.count(), Double::sum);
});
List<EndpointCount> result =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
log.info("Found {} endpoints with counts", result.size());
return result;
}
private double getUniqueUserCount(String method, Optional<String> endpoint) {
log.info(
"Getting unique user count for method: {}, endpoint: {}",
method,
endpoint.orElse("all"));
Set<String> uniqueUsers = new HashSet<>();
meterRegistry.find("http.requests").tag("method", method).counters().stream()
.filter(
counter ->
!endpoint.isPresent()
|| endpoint.get().equals(counter.getId().getTag("uri")))
.forEach(
counter -> {
String session = counter.getId().getTag("session");
if (session != null) {
uniqueUsers.add(session);
}
});
log.info("Unique user count: {}", uniqueUsers.size());
return uniqueUsers.size();
}
private List<EndpointCount> getUniqueUserCounts(String method) {
log.info("Getting unique user counts for method: {}", method);
Map<String, Set<String>> uniqueUsers = new HashMap<>();
meterRegistry
.find("http.requests")
.tag("method", method)
.counters()
.forEach(
counter -> {
String uri = counter.getId().getTag("uri");
String session = counter.getId().getTag("session");
if (uri != null && session != null) {
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
}
});
List<EndpointCount> result =
uniqueUsers.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
log.info("Found {} endpoints with unique user counts", result.size());
return result;
}
public static class EndpointCount {
private String endpoint; private String endpoint;
private double count; private double count;
@@ -180,86 +321,6 @@ public class MetricsController {
} }
} }
@GetMapping("/requests")
@Operation(
summary = "POST request count",
description =
"This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
public ResponseEntity<?> getTotalRequests(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = 0.0;
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "POST".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
} else {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
}
}
}
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/all")
@Operation(
summary = "POST requests count for all endpoints",
description = "This endpoint returns the count of POST requests for each endpoint.")
public ResponseEntity<?> getAllPostRequests() {
if (!metricsEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
Map<String, Double> counts = new HashMap<>();
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && "POST".equals(method)) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results =
counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/uptime") @GetMapping("/uptime")
public ResponseEntity<?> getUptime() { public ResponseEntity<?> getUptime() {
if (!metricsEnabled) { if (!metricsEnabled) {

View File

@@ -12,6 +12,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import lombok.Data;
import lombok.ToString;
import stirling.software.SPDF.config.YamlPropertySourceFactory; import stirling.software.SPDF.config.YamlPropertySourceFactory;
import stirling.software.SPDF.model.provider.GithubProvider; import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.GoogleProvider;
@@ -21,225 +23,56 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Configuration @Configuration
@ConfigurationProperties(prefix = "") @ConfigurationProperties(prefix = "")
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) @PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
@Data
public class ApplicationProperties { public class ApplicationProperties {
private Security security;
private System system; private Legal legal = new Legal();
private Ui ui; private Security security = new Security();
private Endpoints endpoints; private System system = new System();
private Metrics metrics; private Ui ui = new Ui();
private AutomaticallyGenerated automaticallyGenerated; private Endpoints endpoints = new Endpoints();
private AutoPipeline autoPipeline; private Metrics metrics = new Metrics();
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
private AutoPipeline autoPipeline = new AutoPipeline();
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class); private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
public AutoPipeline getAutoPipeline() { @Data
return autoPipeline != null ? autoPipeline : new AutoPipeline();
}
public void setAutoPipeline(AutoPipeline autoPipeline) {
this.autoPipeline = autoPipeline;
}
public Security getSecurity() {
return security != null ? security : new Security();
}
public void setSecurity(Security security) {
this.security = security;
}
public System getSystem() {
return system != null ? system : new System();
}
public void setSystem(System system) {
this.system = system;
}
public Ui getUi() {
return ui != null ? ui : new Ui();
}
public void setUi(Ui ui) {
this.ui = ui;
}
public Endpoints getEndpoints() {
return endpoints != null ? endpoints : new Endpoints();
}
public void setEndpoints(Endpoints endpoints) {
this.endpoints = endpoints;
}
public Metrics getMetrics() {
return metrics != null ? metrics : new Metrics();
}
public void setMetrics(Metrics metrics) {
this.metrics = metrics;
}
public AutomaticallyGenerated getAutomaticallyGenerated() {
return automaticallyGenerated != null
? automaticallyGenerated
: new AutomaticallyGenerated();
}
public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) {
this.automaticallyGenerated = automaticallyGenerated;
}
@Override
public String toString() {
return "ApplicationProperties [security="
+ security
+ ", system="
+ system
+ ", ui="
+ ui
+ ", endpoints="
+ endpoints
+ ", metrics="
+ metrics
+ ", automaticallyGenerated="
+ automaticallyGenerated
+ ", autoPipeline="
+ autoPipeline
+ "]";
}
public static class AutoPipeline { public static class AutoPipeline {
private String outputFolder; private String outputFolder;
public String getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(String outputFolder) {
this.outputFolder = outputFolder;
}
@Override
public String toString() {
return "AutoPipeline [outputFolder=" + outputFolder + "]";
}
} }
@Data
public static class Legal {
private String termsAndConditions;
private String privacyPolicy;
private String accessibilityStatement;
private String cookiePolicy;
private String impressum;
}
@Data
public static class Security { public static class Security {
private Boolean enableLogin; private Boolean enableLogin;
private Boolean csrfDisabled; private Boolean csrfDisabled;
private InitialLogin initialLogin; private InitialLogin initialLogin = new InitialLogin();
private OAUTH2 oauth2; private OAUTH2 oauth2 = new OAUTH2();
private int loginAttemptCount; private int loginAttemptCount;
private long loginResetTimeMinutes; private long loginResetTimeMinutes;
private String loginMethod = "all"; private String loginMethod = "all";
public String getLoginMethod() { @Data
return loginMethod;
}
public void setLoginMethod(String loginMethod) {
this.loginMethod = loginMethod;
}
public int getLoginAttemptCount() {
return loginAttemptCount;
}
public void setLoginAttemptCount(int loginAttemptCount) {
this.loginAttemptCount = loginAttemptCount;
}
public long getLoginResetTimeMinutes() {
return loginResetTimeMinutes;
}
public void setLoginResetTimeMinutes(long loginResetTimeMinutes) {
this.loginResetTimeMinutes = loginResetTimeMinutes;
}
public InitialLogin getInitialLogin() {
return initialLogin != null ? initialLogin : new InitialLogin();
}
public void setInitialLogin(InitialLogin initialLogin) {
this.initialLogin = initialLogin;
}
public OAUTH2 getOAUTH2() {
return oauth2 != null ? oauth2 : new OAUTH2();
}
public void setOAUTH2(OAUTH2 oauth2) {
this.oauth2 = oauth2;
}
public Boolean getEnableLogin() {
return enableLogin;
}
public void setEnableLogin(Boolean enableLogin) {
this.enableLogin = enableLogin;
}
public Boolean getCsrfDisabled() {
return csrfDisabled;
}
public void setCsrfDisabled(Boolean csrfDisabled) {
this.csrfDisabled = csrfDisabled;
}
@Override
public String toString() {
return "Security [enableLogin="
+ enableLogin
+ ", oauth2="
+ oauth2
+ ", initialLogin="
+ initialLogin
+ ", csrfDisabled="
+ csrfDisabled
+ ", loginMethod="
+ loginMethod
+ "]";
}
public static class InitialLogin { public static class InitialLogin {
private String username; private String username;
private String password; @ToString.Exclude private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "InitialLogin [username="
+ username
+ ", password="
+ (password != null && !password.isEmpty() ? "MASKED" : "NULL")
+ "]";
}
} }
@Data
public static class OAUTH2 { public static class OAUTH2 {
private Boolean enabled = false; private Boolean enabled = false;
private String issuer; private String issuer;
private String clientId; private String clientId;
private String clientSecret; @ToString.Exclude private String clientSecret;
private Boolean autoCreateUser = false; private Boolean autoCreateUser = false;
private Boolean blockRegistration = false; private Boolean blockRegistration = false;
private String useAsUsername; private String useAsUsername;
@@ -247,74 +80,6 @@ public class ApplicationProperties {
private String provider; private String provider;
private Client client = new Client(); private Client client = new Client();
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public Boolean getAutoCreateUser() {
return autoCreateUser;
}
public void setAutoCreateUser(Boolean autoCreateUser) {
this.autoCreateUser = autoCreateUser;
}
public Boolean getBlockRegistration() {
return blockRegistration;
}
public void setBlockRegistration(Boolean blockRegistration) {
this.blockRegistration = blockRegistration;
}
public String getUseAsUsername() {
return useAsUsername;
}
public void setUseAsUsername(String useAsUsername) {
this.useAsUsername = useAsUsername;
}
public String getProvider() {
return provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
public Collection<String> getScopes() {
return scopes;
}
public void setScopes(String scopes) { public void setScopes(String scopes) {
List<String> scopesList = List<String> scopesList =
Arrays.stream(scopes.split(",")) Arrays.stream(scopes.split(","))
@@ -323,26 +88,12 @@ public class ApplicationProperties {
this.scopes.addAll(scopesList); this.scopes.addAll(scopesList);
} }
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
protected boolean isValid(String value, String name) { protected boolean isValid(String value, String name) {
if (value != null && !value.trim().isEmpty()) { return value != null && !value.trim().isEmpty();
return true;
}
return false;
} }
protected boolean isValid(Collection<String> value, String name) { protected boolean isValid(Collection<String> value, String name) {
if (value != null && !value.isEmpty()) { return value != null && !value.isEmpty();
return true;
}
return false;
} }
public boolean isSettingsValid() { public boolean isSettingsValid() {
@@ -353,31 +104,7 @@ public class ApplicationProperties {
&& isValid(this.getUseAsUsername(), "useAsUsername"); && isValid(this.getUseAsUsername(), "useAsUsername");
} }
@Override @Data
public String toString() {
return "OAUTH2 [enabled="
+ enabled
+ ", issuer="
+ issuer
+ ", clientId="
+ clientId
+ ", clientSecret="
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
+ ", autoCreateUser="
+ autoCreateUser
+ ", blockRegistration="
+ blockRegistration
+ ", useAsUsername="
+ useAsUsername
+ ", provider="
+ provider
+ ", client="
+ client
+ ", scopes="
+ scopes
+ "]";
}
public static class Client { public static class Client {
private GoogleProvider google = new GoogleProvider(); private GoogleProvider google = new GoogleProvider();
private GithubProvider github = new GithubProvider(); private GithubProvider github = new GithubProvider();
@@ -392,50 +119,15 @@ public class ApplicationProperties {
case "keycloak": case "keycloak":
return getKeycloak(); return getKeycloak();
default: default:
break; throw new UnsupportedProviderException(
"Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
} }
throw new UnsupportedProviderException(
"Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
}
public GoogleProvider getGoogle() {
return google;
}
public void setGoogle(GoogleProvider google) {
this.google = google;
}
public GithubProvider getGithub() {
return github;
}
public void setGithub(GithubProvider github) {
this.github = github;
}
public KeycloakProvider getKeycloak() {
return keycloak;
}
public void setKeycloak(KeycloakProvider keycloak) {
this.keycloak = keycloak;
}
@Override
public String toString() {
return "Client [google="
+ google
+ ", github="
+ github
+ ", keycloak="
+ keycloak
+ "]";
} }
} }
} }
} }
@Data
public static class System { public static class System {
private String defaultLocale; private String defaultLocale;
private Boolean googlevisibility; private Boolean googlevisibility;
@@ -443,184 +135,67 @@ public class ApplicationProperties {
private Boolean showUpdateOnlyAdmin; private Boolean showUpdateOnlyAdmin;
private boolean customHTMLFiles; private boolean customHTMLFiles;
private String tessdataDir; private String tessdataDir;
public String getTessdataDir() {
return tessdataDir;
}
public void setTessdataDir(String tessdataDir) {
this.tessdataDir = tessdataDir;
}
public boolean isCustomHTMLFiles() {
return customHTMLFiles;
}
public void setCustomHTMLFiles(boolean customHTMLFiles) {
this.customHTMLFiles = customHTMLFiles;
}
public boolean getShowUpdateOnlyAdmin() {
return showUpdateOnlyAdmin;
}
public void setShowUpdateOnlyAdmin(boolean showUpdateOnlyAdmin) {
this.showUpdateOnlyAdmin = showUpdateOnlyAdmin;
}
public boolean getShowUpdate() {
return showUpdate;
}
public void setShowUpdate(boolean showUpdate) {
this.showUpdate = showUpdate;
}
private Boolean enableAlphaFunctionality; private Boolean enableAlphaFunctionality;
public Boolean getEnableAlphaFunctionality() {
return enableAlphaFunctionality;
}
public void setEnableAlphaFunctionality(Boolean enableAlphaFunctionality) {
this.enableAlphaFunctionality = enableAlphaFunctionality;
}
public String getDefaultLocale() {
return defaultLocale;
}
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public Boolean getGooglevisibility() {
return googlevisibility;
}
public void setGooglevisibility(Boolean googlevisibility) {
this.googlevisibility = googlevisibility;
}
@Override
public String toString() {
return "System [defaultLocale="
+ defaultLocale
+ ", googlevisibility="
+ googlevisibility
+ ", enableAlphaFunctionality="
+ enableAlphaFunctionality
+ ", showUpdate="
+ showUpdate
+ ", showUpdateOnlyAdmin="
+ showUpdateOnlyAdmin
+ "]";
}
} }
@Data
public static class Ui { public static class Ui {
private String appName; private String appName;
private String homeDescription; private String homeDescription;
private String appNameNavbar; private String appNameNavbar;
public String getAppName() { public String getAppName() {
if (appName != null && appName.trim().length() == 0) return null; return appName != null && appName.trim().length() > 0 ? appName : null;
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
} }
public String getHomeDescription() { public String getHomeDescription() {
if (homeDescription != null && homeDescription.trim().length() == 0) return null; return homeDescription != null && homeDescription.trim().length() > 0
return homeDescription; ? homeDescription
} : null;
public void setHomeDescription(String homeDescription) {
this.homeDescription = homeDescription;
} }
public String getAppNameNavbar() { public String getAppNameNavbar() {
if (appNameNavbar != null && appNameNavbar.trim().length() == 0) return null; return appNameNavbar != null && appNameNavbar.trim().length() > 0
return appNameNavbar; ? appNameNavbar
} : null;
public void setAppNameNavbar(String appNameNavbar) {
this.appNameNavbar = appNameNavbar;
}
@Override
public String toString() {
return "UserInterface [appName="
+ appName
+ ", homeDescription="
+ homeDescription
+ ", appNameNavbar="
+ appNameNavbar
+ "]";
} }
} }
@Data
public static class Endpoints { public static class Endpoints {
private List<String> toRemove; private List<String> toRemove;
private List<String> groupsToRemove; private List<String> groupsToRemove;
public List<String> getToRemove() {
return toRemove;
}
public void setToRemove(List<String> toRemove) {
this.toRemove = toRemove;
}
public List<String> getGroupsToRemove() {
return groupsToRemove;
}
public void setGroupsToRemove(List<String> groupsToRemove) {
this.groupsToRemove = groupsToRemove;
}
@Override
public String toString() {
return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]";
}
} }
@Data
public static class Metrics { public static class Metrics {
private Boolean enabled; private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "Metrics [enabled=" + enabled + "]";
}
} }
@Data
public static class AutomaticallyGenerated { public static class AutomaticallyGenerated {
private String key; @ToString.Exclude private String key;
}
public String getKey() { @Data
return key; public static class EnterpriseEdition {
} @ToString.Exclude private String key;
private CustomMetadata customMetadata = new CustomMetadata();
public void setKey(String key) { @Data
this.key = key; public static class CustomMetadata {
} private boolean autoUpdateMetadata;
private String author;
private String creator;
private String producer;
@Override public String getCreator() {
public String toString() { return creator == null || creator.trim().isEmpty() ? "Stirling-PDF" : creator;
return "AutomaticallyGenerated [key=" }
+ (key != null && !key.isEmpty() ? "MASKED" : "NULL")
+ "]"; public String getProducer() {
return producer == null || producer.trim().isEmpty() ? "Stirling-PDF" : producer;
}
} }
} }
} }

View File

@@ -1,7 +1,5 @@
package stirling.software.SPDF.model; package stirling.software.SPDF.model;
import jakarta.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -9,6 +7,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.persistence.*;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
public class User implements Serializable { public class User implements Serializable {

View File

@@ -11,7 +11,7 @@ public class PDFWithPageSize extends PDFFile {
@Schema( @Schema(
description = description =
"The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL.", "The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL, KEEP.",
allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL"}) allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL", "KEEP"})
private String pageSize; private String pageSize;
} }

View File

@@ -0,0 +1,21 @@
package stirling.software.SPDF.model.api;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class SplitPdfByChaptersRequest extends PDFFile {
@Schema(description = "Whether to include Metadata or not", example = "true")
private Boolean includeMetadata;
@Schema(description = "Whether to allow duplicates or not", example = "true")
private Boolean allowDuplicates;
@Schema(description = "Maximum bookmark level required", example = "2")
private Integer bookmarkLevel;
}

View File

@@ -15,6 +15,9 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
allowableValues = {"small", "medium", "large"}) allowableValues = {"small", "medium", "large"})
private String customMargin; private String customMargin;
private float fontSize;
private String fontType;
@Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9") @Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9")
private int position; private int position;

View File

@@ -1,18 +1,20 @@
package stirling.software.SPDF.repository; package stirling.software.SPDF.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.SPDF.model.User;
import java.util.Optional; import stirling.software.SPDF.model.User;
@Repository @Repository
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsernameIgnoreCase(String username); Optional<User> findByUsernameIgnoreCase(String username);
@Query("FROM User u LEFT JOIN FETCH u.settings where upper(u.username) = upper(:username)") @Query("FROM User u LEFT JOIN FETCH u.settings where upper(u.username) = upper(:username)")
Optional<User> findByUsernameIgnoreCaseWithSettings(String username); Optional<User> findByUsernameIgnoreCaseWithSettings(@Param("username") String username);
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);

View File

@@ -0,0 +1,100 @@
package stirling.software.SPDF.service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.config.PdfMetadataService;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.PDFFile;
@Component
public class CustomPDDocumentFactory {
private final PdfMetadataService pdfMetadataService;
@Autowired
public CustomPDDocumentFactory(PdfMetadataService pdfMetadataService) {
this.pdfMetadataService = pdfMetadataService;
}
public PDDocument createNewDocument() throws IOException {
PDDocument document = new PDDocument();
pdfMetadataService.setMetadataToPdf(document, PdfMetadata.builder().build(), true);
return document;
}
public PDDocument createNewDocumentBasedOnOldDocument(PDDocument oldDocument)
throws IOException {
PDDocument document = new PDDocument();
pdfMetadataService.setMetadataToPdf(
document, pdfMetadataService.extractMetadataFromPdf(oldDocument), true);
return document;
}
public byte[] loadToBytes(File file) throws IOException {
PDDocument document = load(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
// Close the document
document.close();
return baos.toByteArray();
}
public byte[] loadToBytes(byte[] bytes) throws IOException {
PDDocument document = load(bytes);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
// Close the document
document.close();
return baos.toByteArray();
}
// if loading from a file, assume the file has been made with Stirling-PDF
public PDDocument load(File file) throws IOException {
PDDocument document = Loader.loadPDF(file);
pdfMetadataService.setMetadataToPdf(document, PdfMetadata.builder().build(), true);
return document;
}
public PDDocument load(InputStream input) throws IOException {
return load(input.readAllBytes());
}
public PDDocument load(byte[] input) throws IOException {
PDDocument document = Loader.loadPDF(input);
pdfMetadataService.setDefaultMetadata(document);
return document;
}
public PDDocument load(PDFFile pdfFile) throws IOException {
return load(pdfFile.getFileInput());
}
public PDDocument load(MultipartFile pdfFile) throws IOException {
return load(pdfFile.getBytes());
}
public PDDocument load(String path) throws IOException {
return load(new File(path));
}
public PDDocument load(MultipartFile fileInput, String password) throws IOException {
return load(fileInput.getBytes(), password);
}
private PDDocument load(byte[] bytes, String password) throws IOException {
PDDocument document = Loader.loadPDF(bytes, password);
pdfMetadataService.setDefaultMetadata(document);
return document;
}
// Add other load methods as needed, following the same pattern
}

View File

@@ -262,5 +262,4 @@ public class GeneralUtils {
} }
return true; return true;
} }
} }

View File

@@ -6,7 +6,6 @@ import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@@ -37,7 +36,7 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames; import io.github.pixee.security.Filenames;
import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.service.CustomPDDocumentFactory;
public class PdfUtils { public class PdfUtils {
@@ -383,9 +382,13 @@ public class PdfUtils {
} }
public static byte[] imageToPdf( public static byte[] imageToPdf(
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) MultipartFile[] files,
String fitOption,
boolean autoRotate,
String colorType,
CustomPDDocumentFactory pdfDocumentFactory)
throws IOException { throws IOException {
try (PDDocument doc = new PDDocument()) { try (PDDocument doc = pdfDocumentFactory.createNewDocument()) {
for (MultipartFile file : files) { for (MultipartFile file : files) {
String contentType = file.getContentType(); String contentType = file.getContentType();
String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename()); String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());
@@ -473,10 +476,15 @@ public class PdfUtils {
} }
public static byte[] overlayImage( public static byte[] overlayImage(
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) CustomPDDocumentFactory pdfDocumentFactory,
byte[] pdfBytes,
byte[] imageBytes,
float x,
float y,
boolean everyPage)
throws IOException { throws IOException {
PDDocument document = Loader.loadPDF(pdfBytes); PDDocument document = pdfDocumentFactory.load(pdfBytes);
// Get the first page of the PDF // Get the first page of the PDF
int pages = document.getNumberOfPages(); int pages = document.getNumberOfPages();
@@ -506,30 +514,6 @@ public class PdfUtils {
return baos.toByteArray(); return baos.toByteArray();
} }
public static PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
return PdfMetadata.builder()
.author(pdf.getDocumentInformation().getAuthor())
.producer(pdf.getDocumentInformation().getProducer())
.title(pdf.getDocumentInformation().getTitle())
.creator(pdf.getDocumentInformation().getCreator())
.subject(pdf.getDocumentInformation().getSubject())
.keywords(pdf.getDocumentInformation().getKeywords())
.creationDate(pdf.getDocumentInformation().getCreationDate())
.modificationDate(pdf.getDocumentInformation().getModificationDate())
.build();
}
public static void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
pdf.getDocumentInformation().setAuthor(pdfMetadata.getAuthor());
pdf.getDocumentInformation().setProducer(pdfMetadata.getProducer());
pdf.getDocumentInformation().setTitle(pdfMetadata.getTitle());
pdf.getDocumentInformation().setCreator(pdfMetadata.getCreator());
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
pdf.getDocumentInformation().setCreationDate(pdfMetadata.getCreationDate());
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
}
/** Key for storing the dimensions of a rendered image in a map. */ /** Key for storing the dimensions of a rendered image in a map. */
private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {} private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {}

View File

@@ -4,16 +4,7 @@ public class RequestUriUtils {
public static boolean isStaticResource(String requestURI) { public static boolean isStaticResource(String requestURI) {
return requestURI.startsWith("/css/") return isStaticResource("", requestURI);
|| requestURI.startsWith("/fonts/")
|| requestURI.startsWith("/js/")
|| requestURI.startsWith("/images/")
|| requestURI.startsWith("/public/")
|| requestURI.startsWith("/pdfjs/")
|| requestURI.startsWith("/pdfjs-legacy/")
|| requestURI.endsWith(".svg")
|| requestURI.endsWith(".webmanifest")
|| requestURI.startsWith("/api/v1/info/status");
} }
public static boolean isStaticResource(String contextPath, String requestURI) { public static boolean isStaticResource(String contextPath, String requestURI) {
@@ -21,11 +12,37 @@ public class RequestUriUtils {
return requestURI.startsWith(contextPath + "/css/") return requestURI.startsWith(contextPath + "/css/")
|| requestURI.startsWith(contextPath + "/fonts/") || requestURI.startsWith(contextPath + "/fonts/")
|| requestURI.startsWith(contextPath + "/js/") || requestURI.startsWith(contextPath + "/js/")
|| requestURI.endsWith(contextPath + "robots.txt")
|| requestURI.startsWith(contextPath + "/images/") || requestURI.startsWith(contextPath + "/images/")
|| requestURI.startsWith(contextPath + "/public/") || requestURI.startsWith(contextPath + "/public/")
|| requestURI.startsWith(contextPath + "/pdfjs/") || requestURI.startsWith(contextPath + "/pdfjs/")
|| requestURI.startsWith(contextPath + "/login")
|| requestURI.endsWith(".svg") || requestURI.endsWith(".svg")
|| requestURI.endsWith(".png")
|| requestURI.endsWith(".ico")
|| requestURI.endsWith(".webmanifest") || requestURI.endsWith(".webmanifest")
|| requestURI.startsWith(contextPath + "/api/v1/info/status"); || requestURI.startsWith(contextPath + "/api/v1/info/status");
} }
public static boolean isTrackableResource(String requestURI) {
return isTrackableResource("", requestURI);
}
public static boolean isTrackableResource(String contextPath, String requestURI) {
return !(requestURI.startsWith("/js")
|| requestURI.startsWith("/v1/api-docs")
|| requestURI.endsWith("robots.txt")
|| requestURI.startsWith("/images")
|| requestURI.endsWith(".png")
|| requestURI.endsWith(".ico")
|| requestURI.endsWith(".css")
|| requestURI.endsWith(".map")
|| requestURI.endsWith(".svg")
|| requestURI.endsWith(".js")
|| requestURI.contains("swagger")
|| requestURI.startsWith("/api/v1/info")
|| requestURI.startsWith("/site.webmanifest")
|| requestURI.startsWith("/fonts")
|| requestURI.startsWith("/pdfjs"));
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Изберете PDF(и) pdfPrompt=Изберете PDF(и)
multiPdfPrompt=Изберете PDF (2+) multiPdfPrompt=Изберете PDF (2+)
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
@@ -76,7 +77,11 @@ color=Цвят
sponsor=Спонсор sponsor=Спонсор
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Подайте
scalePages.title=Коригиране на мащаба на страницата scalePages.title=Коригиране на мащаба на страницата
scalePages.header=Коригиране на мащаба на страницата scalePages.header=Коригиране на мащаба на страницата
scalePages.pageSize=Размер на страница от документа. scalePages.pageSize=Размер на страница от документа.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница. scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
scalePages.submit=Подайте scalePages.submit=Подайте

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Selecciona PDF(s) pdfPrompt=Selecciona PDF(s)
multiPdfPrompt=Selecciona PDFs (2+) multiPdfPrompt=Selecciona PDFs (2+)
multiPdfDropPrompt=Selecciona (o arrossega) els documents PDF multiPdfDropPrompt=Selecciona (o arrossega) els documents PDF
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Submit
scalePages.title=Adjust page-scale scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document. scalePages.pageSize=Size of a page of the document.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoom level (crop) of a page. scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit scalePages.submit=Submit

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Vyberte PDF soubory pdfPrompt=Vyberte PDF soubory
multiPdfPrompt=Vyberte PDF soubory (2+) multiPdfPrompt=Vyberte PDF soubory (2+)
multiPdfDropPrompt=Vyberte (nebo přetáhněte) všechny požadované PDF soubory multiPdfDropPrompt=Vyberte (nebo přetáhněte) všechny požadované PDF soubory
@@ -76,7 +77,11 @@ color=Barva
sponsor=Sponzor sponsor=Sponzor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Odeslat
scalePages.title=Upravit měřítko stránky scalePages.title=Upravit měřítko stránky
scalePages.header=Upravit měřítko stránky scalePages.header=Upravit měřítko stránky
scalePages.pageSize=Velikost stránky dokumentu. scalePages.pageSize=Velikost stránky dokumentu.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Úroveň přiblížení (oříznutí) stránky. scalePages.scaleFactor=Úroveň přiblížení (oříznutí) stránky.
scalePages.submit=Odeslat scalePages.submit=Odeslat

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=PDF(s) auswählen pdfPrompt=PDF(s) auswählen
multiPdfPrompt=PDFs auswählen(2+) multiPdfPrompt=PDFs auswählen(2+)
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin) multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
@@ -76,7 +77,11 @@ color=Farbe
sponsor=Sponsor sponsor=Sponsor
info=Informationen info=Informationen
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Abschicken
scalePages.title=Seitengröße anpassen scalePages.title=Seitengröße anpassen
scalePages.header=Seitengröße anpassen scalePages.header=Seitengröße anpassen
scalePages.pageSize=Format der Seiten des Dokuments scalePages.pageSize=Format der Seiten des Dokuments
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
scalePages.submit=Abschicken scalePages.submit=Abschicken

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Επιλογή PDF(s) pdfPrompt=Επιλογή PDF(s)
multiPdfPrompt=Επιλογή PDFs (2+) multiPdfPrompt=Επιλογή PDFs (2+)
multiPdfDropPrompt=Επιλογή (ή τράβηγμα αρχείου και απόθεση) όλων των PDF που χρειάζεστε multiPdfDropPrompt=Επιλογή (ή τράβηγμα αρχείου και απόθεση) όλων των PDF που χρειάζεστε
@@ -76,7 +77,11 @@ color=Χρώμα
sponsor=οστηρικτής sponsor=οστηρικτής
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Υποβολή
scalePages.title=Προσαρμογή κλίμακας σελίδας scalePages.title=Προσαρμογή κλίμακας σελίδας
scalePages.header=Προσαρμογή κλίμακας σελίδας scalePages.header=Προσαρμογή κλίμακας σελίδας
scalePages.pageSize=Μέγεθος μιας σελίδας του εγγράφου. scalePages.pageSize=Μέγεθος μιας σελίδας του εγγράφου.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Επίπεδο ζουμ (περικοπή) σελίδας. scalePages.scaleFactor=Επίπεδο ζουμ (περικοπή) σελίδας.
scalePages.submit=Υποβολή scalePages.submit=Υποβολή

View File

@@ -3,7 +3,8 @@
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Select PDF(s) pdfPrompt=Select PDF(s)
multiPdfPrompt=Select PDFs (2+) multiPdfPrompt=Select PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -114,7 +119,7 @@ navbar.darkmode=Dark Mode
navbar.language=Languages navbar.language=Languages
navbar.settings=Settings navbar.settings=Settings
navbar.allTools=Tools navbar.allTools=Tools
navbar.multiTool=Multi Tools navbar.multiTool=Multi Tool
navbar.sections.organize=Organize navbar.sections.organize=Organize
navbar.sections.convertTo=Convert to PDF navbar.sections.convertTo=Convert to PDF
navbar.sections.convertFrom=Convert from PDF navbar.sections.convertFrom=Convert from PDF
@@ -230,8 +235,8 @@ home.viewPdf.desc=View, annotate, add text or images
viewPdf.tags=view,read,annotate,text,image viewPdf.tags=view,read,annotate,text,image
home.multiTool.title=PDF Multi Tool home.multiTool.title=PDF Multi Tool
home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages home.multiTool.desc=Merge, Rotate, Rearrange, Split, and Remove pages
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move,delete,migrate,divide
home.merge.title=Merge home.merge.title=Merge
home.merge.desc=Easily merge multiple PDFs into one. home.merge.desc=Easily merge multiple PDFs into one.
@@ -674,6 +679,7 @@ pageLayout.submit=Submit
scalePages.title=Adjust page-scale scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document. scalePages.pageSize=Size of a page of the document.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoom level (crop) of a page. scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit scalePages.submit=Submit

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Select PDF(s) pdfPrompt=Select PDF(s)
multiPdfPrompt=Select PDFs (2+) multiPdfPrompt=Select PDFs (2+)
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -114,7 +119,7 @@ navbar.darkmode=Dark Mode
navbar.language=Languages navbar.language=Languages
navbar.settings=Settings navbar.settings=Settings
navbar.allTools=Tools navbar.allTools=Tools
navbar.multiTool=Multi Tools navbar.multiTool=Multi Tool
navbar.sections.organize=Organize navbar.sections.organize=Organize
navbar.sections.convertTo=Convert to PDF navbar.sections.convertTo=Convert to PDF
navbar.sections.convertFrom=Convert from PDF navbar.sections.convertFrom=Convert from PDF
@@ -230,8 +235,8 @@ home.viewPdf.desc=View, annotate, add text or images
viewPdf.tags=view,read,annotate,text,image viewPdf.tags=view,read,annotate,text,image
home.multiTool.title=PDF Multi Tool home.multiTool.title=PDF Multi Tool
home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages home.multiTool.desc=Merge, Rotate, Rearrange, Split, and Remove pages
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move,delete,migrate,divide
home.merge.title=Merge home.merge.title=Merge
home.merge.desc=Easily merge multiple PDFs into one. home.merge.desc=Easily merge multiple PDFs into one.
@@ -674,6 +679,7 @@ pageLayout.submit=Submit
scalePages.title=Adjust page-scale scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document. scalePages.pageSize=Size of a page of the document.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoom level (crop) of a page. scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit scalePages.submit=Submit

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Seleccionar PDF(s) pdfPrompt=Seleccionar PDF(s)
multiPdfPrompt=Seleccionar PDFs (2+) multiPdfPrompt=Seleccionar PDFs (2+)
multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera
@@ -76,7 +77,11 @@ color=Color
sponsor=Patrocinador sponsor=Patrocinador
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -179,7 +184,7 @@ adminUserSettings.user=Usuario
adminUserSettings.addUser=Añadir Nuevo Usuario adminUserSettings.addUser=Añadir Nuevo Usuario
adminUserSettings.deleteUser=Eliminar Usuario adminUserSettings.deleteUser=Eliminar Usuario
adminUserSettings.confirmDeleteUser=¿Se debe eliminar al usuario? adminUserSettings.confirmDeleteUser=¿Se debe eliminar al usuario?
adminUserSettings.confirmChangeUserStatus= Se debe habilitar/deshabilitar el usuario? adminUserSettings.confirmChangeUserStatus=Se debe habilitar/deshabilitar el usuario?
adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida. adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
adminUserSettings.roles=Roles adminUserSettings.roles=Roles
adminUserSettings.role=Rol adminUserSettings.role=Rol
@@ -674,6 +679,7 @@ pageLayout.submit=Entregar
scalePages.title=Ajustar escala de la página scalePages.title=Ajustar escala de la página
scalePages.header=Adjustar escala de la página scalePages.header=Adjustar escala de la página
scalePages.pageSize=Tamaño de la página del documento scalePages.pageSize=Tamaño de la página del documento
scalePages.keepPageSize=Tamaño Original
scalePages.scaleFactor=Nivel de zoom (recorte) de la página scalePages.scaleFactor=Nivel de zoom (recorte) de la página
scalePages.submit=Entregar scalePages.submit=Entregar

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Hautatu PDFa(k) pdfPrompt=Hautatu PDFa(k)
multiPdfPrompt=Hautatu PDFak (2+) multiPdfPrompt=Hautatu PDFak (2+)
multiPdfDropPrompt=Hautatu (edo arrastatu eta jaregin) nahi dituzun PDFak multiPdfDropPrompt=Hautatu (edo arrastatu eta jaregin) nahi dituzun PDFak
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Entregatu
scalePages.title=Doitu orrialdearen eskala scalePages.title=Doitu orrialdearen eskala
scalePages.header=Doitu orrialdearen eskala scalePages.header=Doitu orrialdearen eskala
scalePages.pageSize=Dokumentuaren orrialdearen tamaina scalePages.pageSize=Dokumentuaren orrialdearen tamaina
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Orriaren zoom maila (moztea) scalePages.scaleFactor=Orriaren zoom maila (moztea)
scalePages.submit=Entregatu scalePages.submit=Entregatu

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Sélectionnez le(s) PDF pdfPrompt=Sélectionnez le(s) PDF
multiPdfPrompt=Sélectionnez les PDF multiPdfPrompt=Sélectionnez les PDF
multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin
@@ -76,7 +77,11 @@ color=Couleur
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Fusionner
scalePages.title=Ajuster la taille ou léchelle scalePages.title=Ajuster la taille ou léchelle
scalePages.header=Ajuster la taille ou léchelle scalePages.header=Ajuster la taille ou léchelle
scalePages.pageSize=Taille dune page du document scalePages.pageSize=Taille dune page du document
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Niveau de zoom (recadrage) dune page scalePages.scaleFactor=Niveau de zoom (recadrage) dune page
scalePages.submit=Ajuster scalePages.submit=Ajuster

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Roghnaigh PDF(s) pdfPrompt=Roghnaigh PDF(s)
multiPdfPrompt=Roghnaigh PDFs (2+) multiPdfPrompt=Roghnaigh PDFs (2+)
multiPdfDropPrompt=Roghnaigh (nó tarraing & scaoil) gach PDF atá uait multiPdfDropPrompt=Roghnaigh (nó tarraing & scaoil) gach PDF atá uait
@@ -76,7 +77,11 @@ color=Dath
sponsor=Urraitheoir sponsor=Urraitheoir
info=Eolas info=Eolas
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Cuir isteach
scalePages.title=Coigeartaigh scála an leathanaigh scalePages.title=Coigeartaigh scála an leathanaigh
scalePages.header=Coigeartaigh scála an leathanaigh scalePages.header=Coigeartaigh scála an leathanaigh
scalePages.pageSize=Méid leathanach den doiciméad. scalePages.pageSize=Méid leathanach den doiciméad.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Leibhéal súmáil (barr) de leathanach. scalePages.scaleFactor=Leibhéal súmáil (barr) de leathanach.
scalePages.submit=Cuir isteach scalePages.submit=Cuir isteach

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=पीडीएफ़(फ़ाइलें) चुनें pdfPrompt=पीडीएफ़(फ़ाइलें) चुनें
multiPdfPrompt=पीडीएफ़(फ़ाइलें) चुनें (2+) multiPdfPrompt=पीडीएफ़(फ़ाइलें) चुनें (2+)
multiPdfDropPrompt=सभी पीडीएफ़(फ़ाइलें) को चुनें (या खींचें और छोड़ें) multiPdfDropPrompt=सभी पीडीएफ़(फ़ाइलें) को चुनें (या खींचें और छोड़ें)
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -109,25 +114,25 @@ pipelineOptions.validateButton=Validate
############# #############
# NAVBAR # # NAVBAR #
############# #############
navbar.favorite=Favorites navbar.favorite=पसंदीदा
navbar.darkmode=डार्क मोड navbar.darkmode=डार्क मोड
navbar.language=Languages navbar.language=भाषा
navbar.settings=सेटिंग्स navbar.settings=सेटिंग्स
navbar.allTools=Tools navbar.allTools=साधन
navbar.multiTool=Multi Tools navbar.multiTool=विभिन्न साधन
navbar.sections.organize=Organize navbar.sections.organize=संगठित करें
navbar.sections.convertTo=Convert to PDF navbar.sections.convertTo=पीडीएफ में कनवर्ट करें
navbar.sections.convertFrom=Convert from PDF navbar.sections.convertFrom=पीडीएफ से कनवर्ट करें
navbar.sections.security=Sign & Security navbar.sections.security=संकेत और सुरक्षा
navbar.sections.advance=Advanced navbar.sections.advance=उन्नत
navbar.sections.edit=View & Edit navbar.sections.edit=देखें और संपादित करें
############# #############
# SETTINGS # # SETTINGS #
############# #############
settings.title=सेटिंग्स settings.title=सेटिंग्स
settings.update=अपडेट उपलब्ध है settings.update=अपडेट उपलब्ध है
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available. settings.updateAvailable={0} वर्तमान स्थापित संस्करण है. एक नया संस्करण ({1}) उपलब्ध है.
settings.appVersion=ऐप संस्करण: settings.appVersion=ऐप संस्करण:
settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए): settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए):
settings.downloadOption.1=एक ही विंडो में खोलें settings.downloadOption.1=एक ही विंडो में खोलें
@@ -674,6 +679,7 @@ pageLayout.submit=प्रस्तुत क
scalePages.title=पृष्ठ-स्केल समायोजित करें scalePages.title=पृष्ठ-स्केल समायोजित करें
scalePages.header=पृष्ठ-स्केल समायोजित करें scalePages.header=पृष्ठ-स्केल समायोजित करें
scalePages.pageSize=दस्तावेज़ के पृष्ठ का आकार। scalePages.pageSize=दस्तावेज़ के पृष्ठ का आकार।
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=पृष्ठ का ज़ूम स्तर (क्रॉप)। scalePages.scaleFactor=पृष्ठ का ज़ूम स्तर (क्रॉप)।
scalePages.submit=प्रस्तुत करें scalePages.submit=प्रस्तुत करें

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Odaberi PDF(ove) pdfPrompt=Odaberi PDF(ove)
multiPdfPrompt=Odaberi PDF-ove (2+) multiPdfPrompt=Odaberi PDF-ove (2+)
multiPdfDropPrompt=Odaberi (ili povuci i ispusti) sve potrebne PDF-ove multiPdfDropPrompt=Odaberi (ili povuci i ispusti) sve potrebne PDF-ove
@@ -76,7 +77,11 @@ color=Boja
sponsor=Sponzor sponsor=Sponzor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Potvrdi
scalePages.title=Podesite veličinu stranice scalePages.title=Podesite veličinu stranice
scalePages.header=Podesite veličinu stranice scalePages.header=Podesite veličinu stranice
scalePages.pageSize=Veličina stranice dokumenta. scalePages.pageSize=Veličina stranice dokumenta.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Razina zumiranja (obrezivanje) stranice. scalePages.scaleFactor=Razina zumiranja (obrezivanje) stranice.
scalePages.submit=Potvrdi scalePages.submit=Potvrdi

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Válasszon PDF-fájlokat pdfPrompt=Válasszon PDF-fájlokat
multiPdfPrompt=Válasszon PDF-fájlokat (2+) multiPdfPrompt=Válasszon PDF-fájlokat (2+)
multiPdfDropPrompt=Válassza ki (vagy húzza ide) az összes szükséges PDF-fájlt multiPdfDropPrompt=Válassza ki (vagy húzza ide) az összes szükséges PDF-fájlt
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Elküldés
scalePages.title=Oldalméret beállítása scalePages.title=Oldalméret beállítása
scalePages.header=Oldalméret beállítása scalePages.header=Oldalméret beállítása
scalePages.pageSize=A dokumentum egy oldalának mérete. scalePages.pageSize=A dokumentum egy oldalának mérete.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Az oldal nagyításának szintje (vágás). scalePages.scaleFactor=Az oldal nagyításának szintje (vágás).
scalePages.submit=Küldés scalePages.submit=Küldés

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl=right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Pilih PDF pdfPrompt=Pilih PDF
multiPdfPrompt=Pilih PDF (2+) multiPdfPrompt=Pilih PDF (2+)
multiPdfDropPrompt=Pilih (atau seret & letakkan)) semua PDF yang Anda butuhkan multiPdfDropPrompt=Pilih (atau seret & letakkan)) semua PDF yang Anda butuhkan
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Kirim
scalePages.title=Sesuaikan skala halaman scalePages.title=Sesuaikan skala halaman
scalePages.header=Sesuaikan skala halaman scalePages.header=Sesuaikan skala halaman
scalePages.pageSize=Ukuran halaman dokumen. scalePages.pageSize=Ukuran halaman dokumen.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Tingkat zoom (potong) halaman. scalePages.scaleFactor=Tingkat zoom (potong) halaman.
scalePages.submit=Kirim scalePages.submit=Kirim

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Dimensione del font
addPageNumbers.fontName=Nome del font
pdfPrompt=Scegli PDF pdfPrompt=Scegli PDF
multiPdfPrompt=Scegli 2 o più PDF multiPdfPrompt=Scegli 2 o più PDF
multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF
@@ -76,7 +77,11 @@ color=Colore
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Informativa sulla privacy
legal.terms=Termini e Condizioni
legal.accessibility=Accessibilità
legal.cookie=Informativa sui cookie
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Invia
scalePages.title=Regola la scala della pagina scalePages.title=Regola la scala della pagina
scalePages.header=Regola la scala della pagina scalePages.header=Regola la scala della pagina
scalePages.pageSize=Dimensione di una pagina del documento. scalePages.pageSize=Dimensione di una pagina del documento.
scalePages.keepPageSize=Dimensione originale
scalePages.scaleFactor=Livello di zoom (ritaglio) di una pagina. scalePages.scaleFactor=Livello di zoom (ritaglio) di una pagina.
scalePages.submit=Invia scalePages.submit=Invia
@@ -802,7 +808,7 @@ ocr.submit=Scansiona testo nel PDF con OCR
extractImages.title=Estrai immagini extractImages.title=Estrai immagini
extractImages.header=Estrai immagini extractImages.header=Estrai immagini
extractImages.selectText=Seleziona il formato in cui salvare le immagini estratte extractImages.selectText=Seleziona il formato in cui salvare le immagini estratte
extractImages.allowDuplicates=Save duplicate images extractImages.allowDuplicates=Salva le immagini duplicate
extractImages.submit=Estrai extractImages.submit=Estrai
@@ -964,7 +970,7 @@ watermark.selectText.6=spazio verticale (tra ogni filigrana):
watermark.selectText.7=Opacità (0% - 100%): watermark.selectText.7=Opacità (0% - 100%):
watermark.selectText.8=Tipo di filigrana: watermark.selectText.8=Tipo di filigrana:
watermark.selectText.9=Immagine filigrana: watermark.selectText.9=Immagine filigrana:
watermark.selectText.10=Convert PDF to PDF-Image watermark.selectText.10=Converti PDF in PDF-Immagine
watermark.submit=Aggiungi Filigrana watermark.submit=Aggiungi Filigrana
watermark.type.1=Testo watermark.type.1=Testo
watermark.type.2=Immagine watermark.type.2=Immagine

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=PDFを選択 pdfPrompt=PDFを選択
multiPdfPrompt=PDFを選択 (2つ以上) multiPdfPrompt=PDFを選択 (2つ以上)
multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ) multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ)
@@ -76,7 +77,11 @@ color=色
sponsor=スポンサー sponsor=スポンサー
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=送信
scalePages.title=ページの縮尺の調整 scalePages.title=ページの縮尺の調整
scalePages.header=ページの縮尺の調整 scalePages.header=ページの縮尺の調整
scalePages.pageSize=1ページのサイズ scalePages.pageSize=1ページのサイズ
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=1ページの拡大レベル (トリミング)。 scalePages.scaleFactor=1ページの拡大レベル (トリミング)。
scalePages.submit=送信 scalePages.submit=送信

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=PDF 파일 선택 pdfPrompt=PDF 파일 선택
multiPdfPrompt=여러 PDF 파일 선택 multiPdfPrompt=여러 PDF 파일 선택
multiPdfDropPrompt=사용할 모든 PDF 문서를 선택(또는 드래그 앤 드롭)합니다 multiPdfDropPrompt=사용할 모든 PDF 문서를 선택(또는 드래그 앤 드롭)합니다
@@ -76,7 +77,11 @@ color=색상
sponsor=스폰서 sponsor=스폰서
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=확인
scalePages.title=페이지 배율 조절 scalePages.title=페이지 배율 조절
scalePages.header=페이지 배율 조절 scalePages.header=페이지 배율 조절
scalePages.pageSize=페이지의 크기를 조절합니다. scalePages.pageSize=페이지의 크기를 조절합니다.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=페이지 배율 조절 (잘라내기) scalePages.scaleFactor=페이지 배율 조절 (잘라내기)
scalePages.submit=제출 scalePages.submit=제출

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Selecteer PDF('s) pdfPrompt=Selecteer PDF('s)
multiPdfPrompt=Selecteer PDF's (2+) multiPdfPrompt=Selecteer PDF's (2+)
multiPdfDropPrompt=Selecteer (of sleep & zet neer) alle PDF's die je nodig hebt multiPdfDropPrompt=Selecteer (of sleep & zet neer) alle PDF's die je nodig hebt
@@ -76,7 +77,11 @@ color=Kleur
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Indienen
scalePages.title=Pagina-schaal aanpassen scalePages.title=Pagina-schaal aanpassen
scalePages.header=Pagina-schaal aanpassen scalePages.header=Pagina-schaal aanpassen
scalePages.pageSize=Grootte van een pagina van het document. scalePages.pageSize=Grootte van een pagina van het document.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoomniveau (uitsnede) van een pagina. scalePages.scaleFactor=Zoomniveau (uitsnede) van een pagina.
scalePages.submit=Indienen scalePages.submit=Indienen

View File

@@ -1,9 +1,10 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Velg PDF(er) pdfPrompt=Velg PDF(er)
multiPdfPrompt=Velg PDF-filer (2+) multiPdfPrompt=Velg PDF-filer (2+)
multiPdfDropPrompt=Velg (eller dra og slipp) alle PDF-ene du trenger multiPdfDropPrompt=Velg (eller dra og slipp) alle PDF-ene du trenger
@@ -76,7 +77,11 @@ color=Farge
sponsor=Sponsor sponsor=Sponsor
info=Info info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
############### ###############
# Pipeline # # Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Send inn
scalePages.title=Juster side-skala scalePages.title=Juster side-skala
scalePages.header=Juster side-skala scalePages.header=Juster side-skala
scalePages.pageSize=Størrelse på et ark i dokumentet. scalePages.pageSize=Størrelse på et ark i dokumentet.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoom-nivå (beskjær) for en side. scalePages.scaleFactor=Zoom-nivå (beskjær) for en side.
scalePages.submit=Send inn scalePages.submit=Send inn

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