Compare commits

...

58 Commits

Author SHA1 Message Date
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
148 changed files with 4840 additions and 3786 deletions

View File

@@ -4,9 +4,10 @@ Please provide a summary of the changes, including relevant motivation and conte
Closes #(issue_number)
## Checklist:
## Checklist
- [ ] 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 commented my code, particularly in hard-to-understand areas
- [ ] 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
FROM alpine:3.20.2
FROM alpine:3.20.3
# Copy necessary files
COPY scripts /scripts

View File

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

View File

@@ -1,5 +1,5 @@
# use alpine
FROM alpine:3.20.2
FROM alpine:3.20.3
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>
# How to add new languages to Stirling-PDF
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/)
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
```html
<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
</a>
```
The data-language-code is the code used to reference the file in the next step.
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
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)
@@ -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.

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)
## 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
@@ -170,42 +172,42 @@ Stirling PDF currently supports 38!
| 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) |
| 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) |
| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) |
| Czech (Česky) (cs_CZ) | ![88%](https://geps.dev/progress/88) |
| Danish (Dansk) (da_DK) | ![9%](https://geps.dev/progress/9) |
| Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) |
| Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) |
| Czech (Česky) (cs_CZ) | ![87%](https://geps.dev/progress/87) |
| Danish (Dansk) (da_DK) | ![96%](https://geps.dev/progress/96) |
| Dutch (Nederlands) (nl_NL) | ![93%](https://geps.dev/progress/93) |
| English (English) (en_GB) | ![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) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) |
| Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) |
| Hungarian (Magyar) (hu_HU) | ![74%](https://geps.dev/progress/74) |
| French (Français) (fr_FR) | ![90%](https://geps.dev/progress/90) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![79%](https://geps.dev/progress/79) |
| Hindi (हिंदी) (hi_IN) | ![76%](https://geps.dev/progress/76) |
| Hungarian (Magyar) (hu_HU) | ![73%](https://geps.dev/progress/73) |
| 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) |
| Japanese (日本語) (ja_JP) | ![90%](https://geps.dev/progress/90) |
| Korean (한국어) (ko_KR) | ![82%](https://geps.dev/progress/82) |
| Norwegian (Norsk) (no_NB) | ![96%](https://geps.dev/progress/96) |
| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Japanese (日本語) (ja_JP) | ![89%](https://geps.dev/progress/89) |
| Korean (한국어) (ko_KR) | ![81%](https://geps.dev/progress/81) |
| Norwegian (Norsk) (no_NB) | ![95%](https://geps.dev/progress/95) |
| Polish (Polski) (pl_PL) | ![89%](https://geps.dev/progress/89) |
| Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) |
| Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) |
| Romanian (Română) (ro_RO) | ![38%](https://geps.dev/progress/38) |
| Russian (Русский) (ru_RU) | ![82%](https://geps.dev/progress/82) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![97%](https://geps.dev/progress/97) |
| Russian (Русский) (ru_RU) | ![81%](https://geps.dev/progress/81) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![76%](https://geps.dev/progress/76) |
| Simplified Chinese (简体中文) (zh_CN) | ![96%](https://geps.dev/progress/96) |
| Slovakian (Slovensky) (sk_SK) | ![90%](https://geps.dev/progress/90) |
| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) |
| Swedish (Svenska) (sv_SE) | ![38%](https://geps.dev/progress/38) |
| Thai (ไทย) (th_TH) | ![97%](https://geps.dev/progress/97) |
| Traditional Chinese (繁體中文) (zh_TW) | ![96%](https://geps.dev/progress/96) |
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
| Slovakian (Slovensky) (sk_SK) | ![89%](https://geps.dev/progress/89) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) |
| Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) |
| Traditional Chinese (繁體中文) (zh_TW) | ![95%](https://geps.dev/progress/95) |
| Turkish (Türkçe) (tr_TR) | ![96%](https://geps.dev/progress/96) |
| 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.)

View File

@@ -22,7 +22,7 @@ ext {
}
group = "stirling.software"
version = "0.28.3"
version = "0.29.0"
java {
// 17 is lowest but we support and recommend 21
@@ -184,14 +184,14 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
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"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.22.0"
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
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")
compileOnly "org.projectlombok:lombok:$lombokVersion"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
version: '3.3'
services:
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")
@Scope("request")
public boolean shouldShow() {
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
return showUpdate && showAdminResult;
}

View File

@@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component
public class MetricsFilter extends OncePerRequestFilter {
@@ -30,32 +31,16 @@ public class MetricsFilter extends OncePerRequestFilter {
throws ServletException, IOException {
String uri = request.getRequestURI();
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
// 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"))) {
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
Counter counter =
Counter.builder("http.requests")
.tag("uri", uri)
.tag("session", request.getSession().getId())
.tag("method", request.getMethod())
.tag("uri", uri)
.register(meterRegistry);
counter.increment();
// System.out.println("Counted");
}
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
public boolean getShowUpdateOnlyAdmins() {
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
if (!showUpdate) {
return showUpdate;
}

View File

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

View File

@@ -159,7 +159,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
};
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;
}
}

View File

@@ -1,5 +1,9 @@
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.context.MessageSource;
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.GrantedAuthority;
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.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import stirling.software.SPDF.config.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
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.UserRepository;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
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
super.onAuthenticationSuccess(request, response, authentication);
} else {
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
if (loginAttemptService.isBlocked(username)) {
if (session != null) {

View File

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

View File

@@ -43,7 +43,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
String usernameAttribute = oauth2.getUseAsUsername();
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -32,6 +34,13 @@ public class CropController {
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")
@Operation(
summary = "Crops a PDF document",
@@ -40,7 +49,8 @@ public class CropController {
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
PDDocument newDocument = new PDDocument();
PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -43,9 +45,16 @@ public class MergeController {
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
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
PDDocument mergedDoc = new PDDocument();
PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();
for (PDDocument doc : documents) {
for (PDPage page : doc.getPages()) {
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -35,6 +37,13 @@ public class MultiPageLayoutController {
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")
@Operation(
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);
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument newDocument = new PDDocument();
PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(PDRectangle.A4);
newDocument.addPage(newPage);

View File

@@ -3,16 +3,15 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -25,15 +24,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class PdfImageRemovalController {
// Service for removing images from PDFs
@Autowired private PdfImageRemovalService pdfImageRemovalService;
private final PdfImageRemovalService pdfImageRemovalService;
private final CustomPDDocumentFactory pdfDocumentFactory;
/**
* Constructor for dependency injection of PdfImageRemovalService.
*
* @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.pdfDocumentFactory = pdfDocumentFactory;
}
/**
@@ -53,14 +58,8 @@ public class PdfImageRemovalController {
description =
"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 {
MultipartFile pdf = file.getFileInput();
// Convert the MultipartFile to a byte array
byte[] pdfBytes = pdf.getBytes();
// Load the PDF document from the byte array
PDDocument document = Loader.loadPDF(pdfBytes);
// Load the PDF document
PDDocument document = pdfDocumentFactory.load(file);
// Remove images from the PDF document using the service
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
@@ -74,7 +73,8 @@ public class PdfImageRemovalController {
// Generate a new filename for the modified PDF
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
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.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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 stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -33,6 +35,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class PdfOverlayController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
@Operation(
summary = "Overlay PDF files in various modes",
@@ -56,7 +65,7 @@ public class PdfOverlayController {
// "FixedRepeatOverlay"
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()) {
Map<Integer, String> overlayGuide =
prepareOverlayGuide(

View File

@@ -10,6 +10,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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.api.PDFWithPageNums;
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.WebResponseUtils;
@@ -34,6 +36,13 @@ public class RearrangePagesPDFController {
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")
@Operation(
summary = "Remove pages from a PDF file",
@@ -45,7 +54,7 @@ public class RearrangePagesPDFController {
MultipartFile pdfFile = request.getFileInput();
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
String[] pageOrderArr = pagesToDelete.split(",");

View File

@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -29,6 +30,13 @@ public class RotationController {
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")
@Operation(
summary = "Rotate a PDF file",
@@ -39,7 +47,7 @@ public class RotationController {
MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle();
// Load the PDF document
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
PDDocument document = pdfDocumentFactory.load(request);
// Get the list of pages in the document
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -36,6 +38,13 @@ public class ScalePagesController {
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")
@Operation(
summary = "Change the size of a PDF page/document",
@@ -47,29 +56,11 @@ public class ScalePagesController {
String targetPDRectangle = request.getPageSize();
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 outputDocument = new PDDocument();
PDDocument outputDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);
int totalPages = sourceDocument.getNumberOfPages();
for (int i = 0; i < totalPages; i++) {
@@ -116,4 +107,45 @@ public class ScalePagesController {
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_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.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;
@@ -27,9 +28,8 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
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.utils.PdfUtils;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -38,6 +38,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class SplitPDFController {
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")
@Operation(
@@ -51,7 +57,7 @@ public class SplitPDFController {
// open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
System.out.println(
@@ -70,7 +76,8 @@ public class SplitPDFController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
@@ -79,7 +86,7 @@ public class SplitPDFController {
previousPageNumber = splitPoint + 1;
// Transfer metadata to split pdf
PdfUtils.setMetadataToPdf(splitDocument, metadata);
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
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.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;
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -43,6 +45,13 @@ public class SplitPdfBySectionsController {
private static final Logger logger =
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")
@Operation(
summary = "Split PDF pages into smaller sections",
@@ -65,7 +74,7 @@ public class SplitPdfBySectionsController {
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
if (merge) {
MergeController mergeController = new MergeController();
MergeController mergeController = new MergeController(pdfDocumentFactory);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mergeController.mergeDocuments(splitDocuments).save(baos);
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.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;
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.WebResponseUtils;
@@ -34,6 +36,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class SplitPdfBySizeController {
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")
@Operation(
@@ -84,7 +92,8 @@ public class SplitPdfBySizeController {
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
throws IOException {
long currentSize = 0;
PDDocument currentDoc = new PDDocument();
PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int fileIndex = 1;
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
@@ -121,7 +130,8 @@ public class SplitPdfBySizeController {
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
throws IOException {
int currentPageCount = 0;
PDDocument currentDoc = new PDDocument();
PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int fileIndex = 1;
for (PDPage page : sourceDocument.getPages()) {
currentDoc.addPage(page);
@@ -152,7 +162,8 @@ public class SplitPdfBySizeController {
int currentPageIndex = 0;
int fileIndex = 1;
for (int i = 0; i < documentCount; i++) {
PDDocument currentDoc = new PDDocument();
PDDocument currentDoc =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -32,6 +34,13 @@ public class ToSinglePageController {
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")
@Operation(
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
PDDocument newDocument = new PDDocument();
PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
newDocument.addPage(newPage);

View File

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

View File

@@ -1,30 +1,36 @@
package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
// Disabled for now
// @RestController
// @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertBookToPDFController {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
private final 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")
@Operation(

View File

@@ -1,28 +1,25 @@
package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
// Disabled for now
// @RestController
// @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF {
@Autowired
// @Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;

View File

@@ -16,6 +16,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.rendering.ImageType;
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;
@@ -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.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -43,6 +45,13 @@ public class ConvertImgPDFController {
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")
@Operation(
summary = "Convert PDF to image(s)",
@@ -178,7 +187,8 @@ public class ConvertImgPDFController {
boolean autoRotate = request.isAutoRotate();
// 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(
bytes,
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.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
// Disabled for now
// @RestController
// @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf {
@Autowired
// @Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;

View File

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

View File

@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 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.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -40,6 +41,13 @@ public class ConvertPDFToPDFA {
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")
@Operation(
summary = "Convert a PDF to a PDF/A",
@@ -54,7 +62,7 @@ public class ConvertPDFToPDFA {
byte[] pdfBytes = inputFile.getBytes();
// Load the PDF document
PDDocument document = Loader.loadPDF(pdfBytes);
PDDocument document = pdfDocumentFactory.load(pdfBytes);
// Get the document catalog
PDDocumentCatalog catalog = document.getDocumentCatalog();
@@ -101,18 +109,18 @@ public class ConvertPDFToPDFA {
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
.runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename);
try {
PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile());
// Return the optimized PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_PDFA.pdf";
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
} finally {
// Clean up the temporary files
Files.deleteIfExists(tempInputFile);
Files.deleteIfExists(tempOutputFile);
}
}
}

View File

@@ -6,6 +6,10 @@ import java.nio.file.Path;
import java.util.ArrayList;
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.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -26,6 +31,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
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")
@Operation(
summary = "Convert a URL to a PDF",
@@ -46,12 +60,12 @@ public class ConvertWebsiteToPDF {
}
Path tempOutputFile = null;
byte[] pdfBytes;
PDDocument doc = null;
try {
// Prepare the output file path
tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command
// Prepare the WeasyPrint command
List<String> command = new ArrayList<>();
command.add("weasyprint");
command.add(URL);
@@ -61,16 +75,23 @@ public class ConvertWebsiteToPDF {
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command);
// Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile);
} finally {
// Clean up the temporary files
Files.deleteIfExists(tempOutputFile);
}
// Convert URL to a safe filename
String outputFilename = convertURLToFileName(URL);
// Load the PDF using pdfDocumentFactory
doc = pdfDocumentFactory.load(tempOutputFile.toFile());
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) {

View File

@@ -30,13 +30,13 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
@RestController
@RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs")
public class ExtractController {
public class ExtractCSVController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation(
summary = "Extracts a PDF document to csv",
summary = "Extracts a CSV document from a PDF",
description =
"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 {

View File

@@ -12,11 +12,11 @@ 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.rendering.PDFRenderer;
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;
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@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_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")
@Operation(
summary = "Auto split PDF pages into separate documents",
@@ -59,73 +67,93 @@ public class AutoSplitPdfController {
MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode();
PDDocument document = Loader.loadPDF(file.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
PDDocument document = null;
List<PDDocument> splitDocuments = new ArrayList<>();
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
Path zipFile = null;
byte[] data = null;
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());
try {
document = pdfDocumentFactory.load(file.getInputStream());
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
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()
&& !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);
// Remove split documents that have no pages
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
zipFile = Files.createTempFile("split_documents", ".zip");
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
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);
Files.deleteIfExists(zipFile);
}
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
return WebResponseUtils.bytesToWebResponse(
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) {

View File

@@ -16,6 +16,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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 stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -40,6 +42,13 @@ public class BlankPageController {
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")
@Operation(
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)
throws IOException {
try (PDDocument document = new PDDocument()) {
try (PDDocument document = pdfDocumentFactory.createNewDocument()) {
for (PDPage page : pages) {
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -44,6 +46,13 @@ public class CompressController {
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")
@Operation(
summary = "Optimize PDF file",
@@ -258,7 +267,7 @@ public class CompressController {
}
// Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile);
Path finalFile = tempOutputFile;
// Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) {
// Log the occurrence
@@ -266,14 +275,15 @@ public class CompressController {
"Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again
pdfBytes = Files.readAllBytes(tempInputFile);
finalFile = tempInputFile;
}
// Return the optimized PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
return WebResponseUtils.pdfDocToWebResponse(
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
} finally {
// 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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.tags.Tag;
import stirling.software.SPDF.model.PdfMetadata;
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;
@RestController
@@ -37,6 +37,13 @@ public class FlattenController {
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")
@Operation(
summary = "Flatten PDF form fields or full page",
@@ -46,7 +53,6 @@ public class FlattenController {
MultipartFile file = request.getFileInput();
PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
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
// unselectable)
PDFRenderer pdfRenderer = new PDFRenderer(document);
PDDocument newDocument = new PDDocument();
PDDocument newDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document);
int numPages = document.getNumberOfPages();
for (int i = 0; i < numPages; i++) {
try {
@@ -80,7 +87,6 @@ public class FlattenController {
logger.error("exception", e);
}
}
PdfUtils.setMetadataToPdf(newDocument, metadata);
return WebResponseUtils.pdfDocToWebResponse(
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
}

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.api.misc;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
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.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -52,6 +54,13 @@ public class OCRController {
.collect(Collectors.toList());
}
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation(
summary = "Process a PDF file with OCR",
@@ -175,7 +184,7 @@ public class OCRController {
tempOutputFile = tempPdfWithoutImages;
}
// 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
String outputFilename =
@@ -196,7 +205,13 @@ public class OCRController {
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(outputFilename);
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();
// Add text file to the zip

View File

@@ -4,6 +4,7 @@ import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -27,6 +29,13 @@ public class OverlayImageController {
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")
@Operation(
summary = "Overlay image onto a PDF file",
@@ -41,7 +50,9 @@ public class OverlayImageController {
try {
byte[] pdfBytes = pdfFile.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(
result,

View File

@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
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.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;
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.WebResponseUtils;
@@ -36,6 +37,13 @@ public class PageNumbersController {
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")
@Operation(
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")
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
String customMargin = request.getCustomMargin();
int position = request.getPosition();
@@ -51,8 +60,9 @@ public class PageNumbersController {
String customText = request.getCustomText();
int pageNumber = startingNumber;
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;
switch (customMargin.toLowerCase()) {
case "small":
@@ -73,7 +83,7 @@ public class PageNumbersController {
break;
}
float fontSize = 12.0f;
float fontSize = font_size;
if (pagesToNumber == null || pagesToNumber.length() == 0) {
pagesToNumber = "all";
}
@@ -131,7 +141,20 @@ public class PageNumbersController {
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
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.showText(text);
contentStream.endText();

View File

@@ -8,6 +8,7 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -31,6 +33,13 @@ public class RepairController {
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")
@Operation(
summary = "Repair a PDF file",
@@ -58,7 +67,7 @@ public class RepairController {
.runCommandWithOutputHandling(command);
// Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile);
pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
// Return the optimized PDF as a response
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@@ -75,7 +76,8 @@ public class ShowJavascript {
return WebResponseUtils.bytesToWebResponse(
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 org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
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.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
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 stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -45,6 +46,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class StampController {
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public StampController(CustomPDDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@Operation(
summary = "Add stamp to a PDF file",
@@ -86,7 +94,7 @@ public class StampController {
}
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
PDDocument document = pdfDocumentFactory.load(pdfFile);
List<Integer> pageNumbers = request.getPageNumbersList(document, true);

View File

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

View File

@@ -16,7 +16,6 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Calendar;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
@@ -35,6 +34,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@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")
@Operation(
summary = "Sign PDF with a Digital Certificate",
@@ -122,7 +130,7 @@ public class CertSignController {
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sign(pdf.getBytes(), baos, createSignature, name, location, reason);
sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason);
return WebResponseUtils.boasToWebResponse(
baos,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
@@ -130,13 +138,14 @@ public class CertSignController {
}
private static void sign(
CustomPDDocumentFactory pdfDocumentFactory,
byte[] input,
OutputStream output,
CreateSignature instance,
String name,
String location,
String reason) {
try (PDDocument doc = Loader.loadPDF(input)) {
try (PDDocument doc = pdfDocumentFactory.load(input)) {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
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 org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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.PDFPasswordRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -30,6 +31,13 @@ public class PasswordController {
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")
@Operation(
summary = "Remove password from a PDF file",
@@ -39,8 +47,7 @@ public class PasswordController {
throws IOException {
MultipartFile fileInput = request.getFileInput();
String password = request.getPassword();
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
PDDocument document = pdfDocumentFactory.load(fileInput, password);
document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse(
document,
@@ -69,7 +76,7 @@ public class PasswordController {
boolean canPrint = request.isCanPrint();
boolean canPrintFaithful = request.isCanPrintFaithful();
PDDocument document = Loader.loadPDF(fileInput.getBytes());
PDDocument document = pdfDocumentFactory.load(fileInput);
AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent);

View File

@@ -5,12 +5,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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.api.security.RedactPdfRequest;
import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -35,6 +36,13 @@ public class RedactController {
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")
@Operation(
summary = "Redacts listOfText in a PDF document",
@@ -52,8 +60,7 @@ public class RedactController {
System.out.println(listOfTextString);
String[] listOfText = listOfTextString.split("\n");
byte[] bytes = file.getBytes();
PDDocument document = Loader.loadPDF(bytes);
PDDocument document = pdfDocumentFactory.load(file);
Color redactColor;
try {

View File

@@ -1,10 +1,8 @@
package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
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 stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -33,6 +33,13 @@ public class RemoveCertSignController {
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")
@Operation(
summary = "Remove digital signature from PDF",
@@ -42,14 +49,8 @@ public class RemoveCertSignController {
throws Exception {
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
PDDocument document = Loader.loadPDF(pdfBytes);
PDDocument document = pdfDocumentFactory.load(pdf);
// Get the document catalog
PDDocumentCatalog catalog = document.getDocumentCatalog();
@@ -67,14 +68,9 @@ public class RemoveCertSignController {
acroForm.flatten(fieldsToRemove, false);
}
}
// Save the modified document to the ByteArrayOutputStream
document.save(baos);
document.close();
// Return the modified PDF as a response
return WebResponseUtils.boasToWebResponse(
baos,
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_unsigned.pdf");
}

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
package stirling.software.SPDF.controller.web;
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 java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
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.ui.Model;
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.model.*;
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.repository.UserRepository;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
@Controller
@Slf4j
@Tag(name = "Account Security", description = "Account Security APIs")
@@ -48,7 +51,7 @@ public class AccountWebController {
Map<String, String> providerList = new HashMap<>();
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth != null) {
if (oauth.isSettingsValid()) {
providerList.put("oidc", oauth.getProvider());
@@ -79,7 +82,7 @@ public class AccountWebController {
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
model.addAttribute(
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
"oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled());
model.addAttribute("currentPage", "login");
@@ -342,7 +345,7 @@ public class AccountWebController {
// Retrieve username and other attributes
username =
userDetails.getAttribute(
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername());
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
// Add oAuth2 Login attributes to the model
model.addAttribute("oAuth2Login", true);
}

View File

@@ -2,11 +2,7 @@ package stirling.software.SPDF.controller.web;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
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 io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.StartupApplicationListener;
import stirling.software.SPDF.model.ApplicationProperties;
@RestController
@RequestMapping("/api/v1/info")
@Tag(name = "Info", description = "Info APIs")
@Slf4j
public class MetricsController {
@Autowired ApplicationProperties applicationProperties;
@@ -46,6 +43,7 @@ public class MetricsController {
this.metricsEnabled = metricsEnabled;
}
@Autowired
public MetricsController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@@ -66,11 +64,11 @@ public class MetricsController {
return ResponseEntity.ok(status);
}
@GetMapping("/loads")
@GetMapping("/load")
@Operation(
summary = "GET request count",
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(
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
Optional<String> endpoint) {
@@ -78,44 +76,33 @@ public class MetricsController {
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 && "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();
}
}
}
}
}
double count = getRequestCount("GET", endpoint);
return ResponseEntity.ok(count);
} catch (Exception e) {
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(
summary = "GET requests count for all endpoints",
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.");
}
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 && "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());
List<EndpointCount> results = getEndpointCounts("GET");
return ResponseEntity.ok(results);
} catch (Exception e) {
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 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")
public ResponseEntity<?> getUptime() {
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.PropertySource;
import lombok.Data;
import lombok.ToString;
import stirling.software.SPDF.config.YamlPropertySourceFactory;
import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider;
@@ -21,225 +23,56 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Configuration
@ConfigurationProperties(prefix = "")
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
@Data
public class ApplicationProperties {
private Security security;
private System system;
private Ui ui;
private Endpoints endpoints;
private Metrics metrics;
private AutomaticallyGenerated automaticallyGenerated;
private AutoPipeline autoPipeline;
private Legal legal = new Legal();
private Security security = new Security();
private System system = new System();
private Ui ui = new Ui();
private Endpoints endpoints = new Endpoints();
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);
public AutoPipeline getAutoPipeline() {
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
+ "]";
}
@Data
public static class AutoPipeline {
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 {
private Boolean enableLogin;
private Boolean csrfDisabled;
private InitialLogin initialLogin;
private OAUTH2 oauth2;
private InitialLogin initialLogin = new InitialLogin();
private OAUTH2 oauth2 = new OAUTH2();
private int loginAttemptCount;
private long loginResetTimeMinutes;
private String loginMethod = "all";
public String getLoginMethod() {
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
+ "]";
}
@Data
public static class InitialLogin {
private String username;
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")
+ "]";
}
@ToString.Exclude private String password;
}
@Data
public static class OAUTH2 {
private Boolean enabled = false;
private String issuer;
private String clientId;
private String clientSecret;
@ToString.Exclude private String clientSecret;
private Boolean autoCreateUser = false;
private Boolean blockRegistration = false;
private String useAsUsername;
@@ -247,74 +80,6 @@ public class ApplicationProperties {
private String provider;
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) {
List<String> scopesList =
Arrays.stream(scopes.split(","))
@@ -323,26 +88,12 @@ public class ApplicationProperties {
this.scopes.addAll(scopesList);
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
protected boolean isValid(String value, String name) {
if (value != null && !value.trim().isEmpty()) {
return true;
}
return false;
return value != null && !value.trim().isEmpty();
}
protected boolean isValid(Collection<String> value, String name) {
if (value != null && !value.isEmpty()) {
return true;
}
return false;
return value != null && !value.isEmpty();
}
public boolean isSettingsValid() {
@@ -353,31 +104,7 @@ public class ApplicationProperties {
&& isValid(this.getUseAsUsername(), "useAsUsername");
}
@Override
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
+ "]";
}
@Data
public static class Client {
private GoogleProvider google = new GoogleProvider();
private GithubProvider github = new GithubProvider();
@@ -392,50 +119,15 @@ public class ApplicationProperties {
case "keycloak":
return getKeycloak();
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 {
private String defaultLocale;
private Boolean googlevisibility;
@@ -443,184 +135,67 @@ public class ApplicationProperties {
private Boolean showUpdateOnlyAdmin;
private boolean customHTMLFiles;
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;
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 {
private String appName;
private String homeDescription;
private String appNameNavbar;
public String getAppName() {
if (appName != null && appName.trim().length() == 0) return null;
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
return appName != null && appName.trim().length() > 0 ? appName : null;
}
public String getHomeDescription() {
if (homeDescription != null && homeDescription.trim().length() == 0) return null;
return homeDescription;
}
public void setHomeDescription(String homeDescription) {
this.homeDescription = homeDescription;
return homeDescription != null && homeDescription.trim().length() > 0
? homeDescription
: null;
}
public String getAppNameNavbar() {
if (appNameNavbar != null && appNameNavbar.trim().length() == 0) return null;
return appNameNavbar;
}
public void setAppNameNavbar(String appNameNavbar) {
this.appNameNavbar = appNameNavbar;
}
@Override
public String toString() {
return "UserInterface [appName="
+ appName
+ ", homeDescription="
+ homeDescription
+ ", appNameNavbar="
+ appNameNavbar
+ "]";
return appNameNavbar != null && appNameNavbar.trim().length() > 0
? appNameNavbar
: null;
}
}
@Data
public static class Endpoints {
private List<String> toRemove;
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 {
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 {
private String key;
@ToString.Exclude private String key;
}
public String getKey() {
return key;
}
@Data
public static class EnterpriseEdition {
@ToString.Exclude private String key;
private CustomMetadata customMetadata = new CustomMetadata();
public void setKey(String key) {
this.key = key;
}
@Data
public static class CustomMetadata {
private boolean autoUpdateMetadata;
private String author;
private String creator;
private String producer;
@Override
public String toString() {
return "AutomaticallyGenerated [key="
+ (key != null && !key.isEmpty() ? "MASKED" : "NULL")
+ "]";
public String getCreator() {
return creator == null || creator.trim().isEmpty() ? "Stirling-PDF" : creator;
}
public String getProducer() {
return producer == null || producer.trim().isEmpty() ? "Stirling-PDF" : producer;
}
}
}
}

View File

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

View File

@@ -11,7 +11,7 @@ public class PDFWithPageSize extends PDFFile {
@Schema(
description =
"The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL.",
allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "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", "KEEP"})
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"})
private String customMargin;
private float fontSize;
private String fontType;
@Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9")
private int position;

View File

@@ -1,18 +1,20 @@
package stirling.software.SPDF.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import stirling.software.SPDF.model.User;
import java.util.Optional;
import stirling.software.SPDF.model.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsernameIgnoreCase(String 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);

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;
}
}

View File

@@ -6,7 +6,6 @@ import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
@@ -37,7 +36,7 @@ import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
public class PdfUtils {
@@ -383,9 +382,13 @@ public class PdfUtils {
}
public static byte[] imageToPdf(
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType)
MultipartFile[] files,
String fitOption,
boolean autoRotate,
String colorType,
CustomPDDocumentFactory pdfDocumentFactory)
throws IOException {
try (PDDocument doc = new PDDocument()) {
try (PDDocument doc = pdfDocumentFactory.createNewDocument()) {
for (MultipartFile file : files) {
String contentType = file.getContentType();
String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());
@@ -473,10 +476,15 @@ public class PdfUtils {
}
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 {
PDDocument document = Loader.loadPDF(pdfBytes);
PDDocument document = pdfDocumentFactory.load(pdfBytes);
// Get the first page of the PDF
int pages = document.getNumberOfPages();
@@ -506,30 +514,6 @@ public class PdfUtils {
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. */
private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {}

View File

@@ -4,16 +4,7 @@ public class RequestUriUtils {
public static boolean isStaticResource(String requestURI) {
return requestURI.startsWith("/css/")
|| 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");
return isStaticResource("", requestURI);
}
public static boolean isStaticResource(String contextPath, String requestURI) {
@@ -21,11 +12,37 @@ public class RequestUriUtils {
return requestURI.startsWith(contextPath + "/css/")
|| requestURI.startsWith(contextPath + "/fonts/")
|| requestURI.startsWith(contextPath + "/js/")
|| requestURI.endsWith(contextPath + "robots.txt")
|| requestURI.startsWith(contextPath + "/images/")
|| requestURI.startsWith(contextPath + "/public/")
|| requestURI.startsWith(contextPath + "/pdfjs/")
|| requestURI.startsWith(contextPath + "/login")
|| requestURI.endsWith(".svg")
|| requestURI.endsWith(".png")
|| requestURI.endsWith(".ico")
|| requestURI.endsWith(".webmanifest")
|| 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 #
###########
# 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
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Изберете PDF(и)
multiPdfPrompt=Изберете PDF (2+)
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
@@ -76,7 +77,11 @@ color=Цвят
sponsor=Спонсор
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Подайте
scalePages.title=Коригиране на мащаба на страницата
scalePages.header=Коригиране на мащаба на страницата
scalePages.pageSize=Размер на страница от документа.
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
scalePages.submit=Подайте

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
###########
# 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
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=PDF(s) auswählen
multiPdfPrompt=PDFs auswählen(2+)
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
info=Informationen
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Abschicken
scalePages.title=Seitengröße anpassen
scalePages.header=Seitengröße anpassen
scalePages.pageSize=Format der Seiten des Dokuments
scalePages.keepPageSize=Original Size
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
scalePages.submit=Abschicken

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
###########
# 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
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Seleccionar PDF(s)
multiPdfPrompt=Seleccionar PDFs (2+)
multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera
@@ -76,7 +77,11 @@ color=Color
sponsor=Patrocinador
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #
@@ -179,7 +184,7 @@ adminUserSettings.user=Usuario
adminUserSettings.addUser=Añadir Nuevo Usuario
adminUserSettings.deleteUser=Eliminar 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.roles=Roles
adminUserSettings.role=Rol
@@ -674,6 +679,7 @@ pageLayout.submit=Entregar
scalePages.title=Ajustar escala de la página
scalePages.header=Adjustar escala de la página
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.submit=Entregar

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
###########
# 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
addPageNumbers.fontSize=Font Size
addPageNumbers.fontName=Font Name
pdfPrompt=Válasszon PDF-fájlokat
multiPdfPrompt=Válasszon PDF-fájlokat (2+)
multiPdfDropPrompt=Válassza ki (vagy húzza ide) az összes szükséges PDF-fájlt
@@ -76,7 +77,11 @@ color=Color
sponsor=Sponsor
info=Info
legal.privacy=Privacy Policy
legal.terms=Terms and Conditions
legal.accessibility=Accessibility
legal.cookie=Cookie Policy
legal.impressum=Impressum
###############
# Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Elküldés
scalePages.title=Oldalméret beállítása
scalePages.header=Oldalméret beállítása
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.submit=Küldés

View File

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

View File

@@ -1,9 +1,10 @@
###########
# 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
addPageNumbers.fontSize=Dimensione del font
addPageNumbers.fontName=Nome del font
pdfPrompt=Scegli PDF
multiPdfPrompt=Scegli 2 o più PDF
multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF
@@ -76,7 +77,11 @@ color=Colore
sponsor=Sponsor
info=Info
legal.privacy=Informativa sulla privacy
legal.terms=Termini e Condizioni
legal.accessibility=Accessibilità
legal.cookie=Informativa sui cookie
legal.impressum=Impressum
###############
# Pipeline #
@@ -674,6 +679,7 @@ pageLayout.submit=Invia
scalePages.title=Regola la scala della pagina
scalePages.header=Regola la scala della pagina
scalePages.pageSize=Dimensione di una pagina del documento.
scalePages.keepPageSize=Dimensione originale
scalePages.scaleFactor=Livello di zoom (ritaglio) di una pagina.
scalePages.submit=Invia
@@ -802,7 +808,7 @@ ocr.submit=Scansiona testo nel PDF con OCR
extractImages.title=Estrai immagini
extractImages.header=Estrai immagini
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
@@ -964,7 +970,7 @@ watermark.selectText.6=spazio verticale (tra ogni filigrana):
watermark.selectText.7=Opacità (0% - 100%):
watermark.selectText.8=Tipo di 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.type.1=Testo
watermark.type.2=Immagine

View File

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

View File

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

View File

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

View File

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

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