Compare commits

...

217 Commits

Author SHA1 Message Date
Anthony Stirling
08205ed32d Custom uid (#883)
* init

* user and pass to just pass lang update

* session management fixes and avoid demo user locking

* fix for UMASK and extract cleanups
2024-03-08 20:49:19 +00:00
Anthony Stirling
9246b42057 Login fixes (#881)
* init

* user and pass to just pass lang update

* session management fixes and avoid demo user locking

* Hardening suggestions for Stirling-PDF / loginFixes (#882)

Switch order of literals to prevent NullPointerException

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-03-08 18:06:40 +00:00
albanobattistella
67e4d6e3a2 Update messages_it_IT.properties (#878) 2024-03-07 21:47:21 +00:00
Anthony Stirling
cf4613d043 Password fix and others (#875)
* init

* user and pass to just pass lang update
2024-03-07 20:12:07 +00:00
Eric
2f703796e9 fix(SplitPDF): create immutable list for dynamic additions (#877) 2024-03-07 12:05:26 -05:00
albanobattistella
731dc3f3dc Update messages_it_IT.properties (#870) 2024-03-06 22:24:30 +00:00
Ludy
97472310f2 Show the user roles as real names (#867)
* Show the user roles as real names

* Add error message

* Update Role.java

* default Language without translation

* Update messages_el_GR.properties
2024-03-06 22:14:02 +00:00
Anthony Stirling
ece1d071c0 non root user and fix book/html calibre (#856)
* non root user and fix book/html calibre

* version bump

* Update docker-compose-latest.yml

* remove customApp

---------

Co-authored-by: systo <systo@host.docker.internal>
2024-03-04 20:51:49 +00:00
albanobattistella
20f532c872 Update messages_it_IT.properties (#858)
Co-authored-by: Eric <71648843+sbplat@users.noreply.github.com>
2024-03-04 20:36:18 +00:00
Ludy
bdcccfd937 🐛 Fix: index out of bounds #861 #842 (#863)
* 🐛 Fix: `index out of bounds` #861 #842

* Update RearrangePagesPDFController.java

* Update RearrangePagesPDFController.java
2024-03-04 20:14:45 +00:00
Ludy
146b8f0103 Corrected the reading of the port. See: #834 (#855)
* Corrected the reading of the port. See: #834

* Removed outdated import

---------

Co-authored-by: Eric <71648843+sbplat@users.noreply.github.com>
2024-02-25 19:15:03 -05:00
懒猫
c8a37245fa Added option to split PDF into multiple parts and merge into one PDF (#841)
* Added option to split PDF into multiple parts and merge into one PDF

* Use the mergeDocuments method in MergeController to implement merging

---------

Co-authored-by: Eric <71648843+sbplat@users.noreply.github.com>
2024-02-24 15:26:35 -05:00
Parth P Shah
af68c70239 Update SPdfApplication.java (#853)
* Update SPdfApplication.java

* Update SPdfApplication.java

* Update SPdfApplication.java

---------

Co-authored-by: Eric <71648843+sbplat@users.noreply.github.com>
2024-02-24 15:22:47 -05:00
Ludy
5bd544dcd7 Removed: Duplicate Properties Keys pt_PT (#854) 2024-02-24 19:45:07 +00:00
Anthony Stirling
642b85069d Merge pull request #852 from parth-p-shah/application-file-fixes
Application file fixes, added loggers instead of sysout
2024-02-23 19:03:04 +00:00
Parth P Shah
6fef4ea82c Update SPdfApplication.java 2024-02-24 00:01:20 +05:30
Parth P Shah
8670afb96f Revert "Update SPdfApplication.java"
This reverts commit 33f8d60900.
2024-02-23 23:46:42 +05:30
Parth P Shah
33f8d60900 Update SPdfApplication.java 2024-02-23 23:46:36 +05:30
albanobattistella
4e2156ad79 Update messages_it_IT.properties (#839) 2024-02-21 17:30:55 -05:00
Anthony Stirling
a07245224e Merge pull request #833 from seku80/main
Add messages_pt_PT.properties
2024-02-19 22:34:00 +00:00
seku80
f96a4cdb59 Update pt_PT
Fixed missing line #14 and removing duplicate entry line #870
2024-02-19 16:46:35 +00:00
seku80
efea22aa6e Update languages.html 2024-02-19 16:19:29 +00:00
seku80
ae9a7dc580 Add files via upload 2024-02-19 16:13:16 +00:00
seku80
7135ace1aa Add files via upload 2024-02-19 15:03:28 +00:00
Anthony Stirling
625275124a fix for #818 2024-02-18 15:47:19 +00:00
Anthony Stirling
c96ebccae4 Download message for game 2024-02-18 13:30:56 +00:00
Anthony Stirling
20cb460a7e controls and sizing bounds 2024-02-18 09:45:50 +00:00
Anthony Stirling
3a62d19979 game const 2024-02-18 09:21:30 +00:00
Ludy
51ad741744 Fix 746 (#825)
* Fix: #746

* formatting
2024-02-18 07:40:30 +00:00
Anthony Stirling
673f005fe6 Game fixes and ocr docs (#824) 2024-02-17 23:23:07 +00:00
Ludy
8d9f0361d0 Fix Serbia Language (#822)
* Fix Serbia Language

* Rename messages_sr_Latn_RS.properties to messages_sr_LATN_RS.properties

* Update languages.html

* Update README.md
2024-02-17 19:56:56 +00:00
Anthony Stirling
56e3ec1219 Update HowToUseOCR.md 2024-02-17 19:28:32 +00:00
dependabot[bot]
a0acafcefc Bump org.springframework:spring-webmvc from 6.1.2 to 6.1.3 (#713)
Bumps [org.springframework:spring-webmvc](https://github.com/spring-projects/spring-framework) from 6.1.2 to 6.1.3.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.2...v6.1.3)

---
updated-dependencies:
- dependency-name: org.springframework:spring-webmvc
  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-02-17 13:24:38 +00:00
dependabot[bot]
918f5954b7 Bump io.micrometer:micrometer-core from 1.12.2 to 1.12.3 (#806)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.12.2 to 1.12.3.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.2...v1.12.3)

---
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-02-17 13:23:10 +00:00
albanobattistella
148dcdaee7 Update messages_it_IT.properties (#816) 2024-02-17 13:22:15 +00:00
Anthony Stirling
a5f0777892 Update ConfigInitializer.java for auto settings removal 2024-02-17 13:10:00 +00:00
Ludy
010426d488 Document (#803)
* Update HowToAddNewLanguage.md

* Update HowToUseOCR.md

* Update LocalRunGuide.md

* Update README.md

* Update LocalRunGuide.md

* Update README.md

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-02-16 22:42:56 +00:00
Aliarev
6fc9c7be90 Update french translation (#814) 2024-02-16 22:24:26 +00:00
Sherif Metwally
094fde9801 Update-helm-chart (#815)
* upgrade app in helm chart to 0.20.2

* update and correct UI env variable names in chart values.yaml
2024-02-16 22:23:11 +00:00
Ludy
e4a76e96af HTML, CSS, JS and JAVA corrections (#810)
* CSS corrections

* HTML corrections

* JS corrections

* JAVA corrections

* remove tab

* CSS corrections 2

* JS corrections 2

* back to the roots

* max-linie 127

* add slash hr|br

* return bootstrap-icons.css

* return bootstrap-icons.min.css

* return bootstrap.min.css

* Update bootstrap-icons.css

* Update bootstrap-icons.min.css

* Update bootstrap-icons.min.css

* Update bootstrap.min.css

* CSS corrections

* HTML corrections

* JS corrections

* JAVA corrections

* remove tab

* CSS corrections 2

* JS corrections 2

* back to the roots

* max-linie 127

* add slash hr|br

* return bootstrap-icons.css

* Update bootstrap-icons.css

* Bootstrap CSS

* Update prism.css
2024-02-16 21:49:06 +00:00
Anthony Stirling
68f582bcb9 Merge pull request #811 from NeilJared/main
Update messages_es_ES.properties
2024-02-14 21:27:59 +00:00
NeilJared
639aed7120 Update messages_es_ES.properties
Updated es_ES translation
2024-02-14 12:22:47 +01:00
NeilJared
994bb4d1d2 Update messages_es_ES.properties
Minor bug fixes: es_ES translation
2024-02-14 11:57:34 +01:00
Anthony Stirling
80b11a55fa Merge pull request #804 from Ludy87/missing_pageSelectionPrompt
add missing propertie pageSelectionPrompt
2024-02-11 22:43:08 +00:00
Ludy87
3cfb554623 missing propertie pageSelectionPrompt 2024-02-11 21:10:04 +01:00
Eric
e84f9c5946 Merge pull request #796 from Ludy87/languages
Languages
2024-02-11 14:41:15 -05:00
Ludy87
17cc31d6e7 add placeholder translation 2024-02-11 14:32:34 -05:00
Ludy87
0c6e10a6dd add separator 2024-02-11 14:32:34 -05:00
Ludy87
297c57631f remove dublicate -> change showJS.tags to autoRedact.tags 2024-02-11 14:32:34 -05:00
Ludy87
bd4e252bb6 added missing strings 2024-02-11 14:32:33 -05:00
Ludy87
0ce34c70bc Update messages_de_DE.properties 2024-02-11 14:32:33 -05:00
Ludy87
4df75cfba1 Update messages_de_DE.properties 2024-02-11 14:32:33 -05:00
Ludy87
2aa435bcfb double spaces removed 2024-02-11 14:32:33 -05:00
Ludy87
0a4a9e6947 Removed unnecessary characters and added DE missing strings 2024-02-11 14:32:32 -05:00
Anthony Stirling
d5860d0b55 Update PdfToTextOrRTFRequest.java 2024-02-11 18:47:43 +00:00
Eric
6a487ce514 Merge pull request #802 from Stirling-Tools/normalize_files
refactor: normalize all files (strip trailing whitespace + convert CRLF to LF)
2024-02-11 13:11:37 -05:00
sbplat
4f3b85e66b refactor: normalize files that contained both CRLF and LF 2024-02-11 12:14:21 -05:00
sbplat
370cd97e05 misc: update .git-blame-ignore-revs to ignore normalize files commit in blame 2024-02-11 11:54:03 -05:00
sbplat
55d4fda01b refactor: normalize files 2024-02-11 11:47:00 -05:00
Anthony Stirling
3dd0471e22 Merge pull request #799 from Stirling-Tools/icon
Icon changes!
2024-02-10 16:40:00 +00:00
Anthony Stirling
5a4efa81a7 more icon changes 2024-02-10 16:22:59 +00:00
Anthony Stirling
ea59c12b27 icon changes 2024-02-10 16:17:47 +00:00
Anthony Stirling
9600f91dda bigger icons 2024-02-10 16:11:45 +00:00
Anthony Stirling
a9f93b014a icon change 2024-02-10 15:56:41 +00:00
Anthony Stirling
bf62e389f7 Merge branch 'main' of git@github.com:Frooodle/Stirling-PDF.git into
main
2024-02-10 14:55:53 +00:00
Anthony Stirling
26af6b5636 cleanups formatting 2024-02-10 14:52:59 +00:00
Anthony Stirling
0fabfea56d page adjusts for stamp 2024-02-10 14:52:27 +00:00
Anthony Stirling
7a9417a62f Merge pull request #794 from mannam11/fixed-stamptool-wrong-filename-generaion-#757
fixed wrong filename generation through stamp tool #757
2024-02-10 11:21:19 +00:00
Anthony Stirling
eb45005baa Merge branch 'main' into fixed-stamptool-wrong-filename-generaion-#757 2024-02-10 11:20:21 +00:00
Anthony Stirling
a4f923eb3a Update StampController.java 2024-02-10 11:19:22 +00:00
Anthony Stirling
e1c3561997 version bump and expose ports 2024-02-10 10:29:17 +00:00
mannam11
bf8b902100 Merge branch 'main' into fixed-stamptool-wrong-filename-generaion-#757 2024-02-10 15:57:38 +05:30
Anthony Stirling
8a5883501a Merge pull request #797 from Stirling-Tools/fixes
Fixes for docker changes plus others
2024-02-10 00:33:49 +00:00
Anthony Stirling
fd8f3ce019 Merge pull request #798 from Stirling-Tools/pixeebot/fixes
Hardening suggestions for Stirling-PDF / fixes
2024-02-10 00:33:33 +00:00
Anthony Stirling
6f72096953 more fixes 2024-02-10 00:21:00 +00:00
Anthony Stirling
5a52e3d6dd other changes 2024-02-10 00:08:54 +00:00
pixeebot[bot]
23672cd18d Modernize and secure temp file creation 2024-02-10 00:08:18 +00:00
pixeebot[bot]
68c0941666 Sanitized user-provided file names in HTTP multipart uploads 2024-02-10 00:08:18 +00:00
Anthony Stirling
96e399a617 changing html and book labels 2024-02-10 00:00:07 +00:00
Anthony Stirling
22343e507d fixes 2024-02-09 23:45:18 +00:00
Anthony Stirling
8a143d139c Merge remote-tracking branch 'origin/main' into fixes 2024-02-09 23:30:25 +00:00
Anthony Stirling
15ad46fe1c book htmk 2024-02-09 23:24:25 +00:00
Anthony Stirling
2473f0d034 Update build.gradle 2024-02-09 23:22:16 +00:00
Anthony Stirling
f211eefc85 Merge pull request #624 from Zoey2936/main
switch images to alpine
2024-02-09 23:22:07 +00:00
mannam
9da88b7652 fixed wrong filename generation through stamp tool #757 2024-02-09 19:35:34 +05:30
Anthony Stirling
729c8006d2 Merge pull request #789 from mannam11/fixed_remove_pages_exception#761
Fixed remove pages un-handled exception with space in input #761
2024-02-08 17:25:17 +00:00
Anthony Stirling
0d5b790443 Merge branch 'main' into fixed_remove_pages_exception#761 2024-02-08 17:19:30 +00:00
Anthony Stirling
aa16035137 Merge pull request #787 from Stirling-Tools/fix_stamp_margins
fix: use the same margins for x and y in the stamp feature
2024-02-08 17:18:31 +00:00
mannam
41686883ee Fixed remove pages un-handled exception with space in input #761 2024-02-08 21:03:57 +05:30
Eric
2e0790c893 Merge pull request #788 from Stirling-Tools/pixeebot/fix_stamp_margins
Hardening suggestions for Stirling-PDF / fix_stamp_margins
2024-02-07 21:52:21 -05:00
pixeebot[bot]
4e937a6024 Sanitized user-provided file names in HTTP multipart uploads 2024-02-08 02:46:39 +00:00
sbplat
4af58118c9 fix: use the same margins for x and y in the stamp feature 2024-02-07 21:40:33 -05:00
Zoey
aa2ad33c1d fix libreoffice install 2024-02-07 06:15:32 +01:00
Anthony Stirling
c7005bc07f Merge pull request #780 from NeilJared/main
Updated es_ES translation (100% completed)
2024-02-06 09:40:30 +00:00
NeilJared
3f932ebec9 Update messages_es_ES.properties
Updated es_ES translation (100%)
2024-02-06 10:03:55 +01:00
Stirling-PDF-Bot
296f265391 Update 3rd Party Licenses 2024-02-06 08:39:13 +00:00
Anthony Stirling
ca8519cb10 Merge remote-tracking branch 'origin/main' into fixes 2024-02-06 00:01:15 +00:00
Anthony Stirling
734d76a3b5 test 2024-02-06 00:00:49 +00:00
Anthony Stirling
f5a39ed514 Merge pull request #776 from michelheusschen/fix/dutch-small-corrections
fix: small corrections for Dutch language
2024-02-04 18:53:04 +00:00
Michel Heusschen
96f4e5eac7 fix: small corrections for Dutch language 2024-02-04 18:08:55 +01:00
Anthony Stirling
48be772703 Update build.gradle 2024-02-03 22:54:33 +00:00
Anthony Stirling
a9edb49723 Merge pull request #772 from Stirling-Tools/pixeebot/drip-2024-02-02-pixee-java/switch-literal-first
Switch order of literals to prevent NullPointerException
2024-02-02 00:31:13 +00:00
pixeebot[bot]
95471a2fba Switch order of literals to prevent NullPointerException 2024-02-02 00:29:18 +00:00
Anthony Stirling
36c277961f Merge pull request #771 from Stirling-Tools/pixeebot/drip-2024-02-02-pixee-java/upgrade-tempfile-to-nio
Modernize and secure temp file creation
2024-02-02 00:20:39 +00:00
pixeebot[bot]
734fff5618 Modernize and secure temp file creation 2024-02-02 00:15:46 +00:00
Anthony Stirling
16136b2f6f Merge pull request #769 from Stirling-Tools/pixeebot/drip-2024-02-01-pixee-java/sanitize-spring-multipart-filename
Sanitized user-provided file names in HTTP multipart uploads
2024-02-01 23:51:48 +00:00
pixeebot[bot]
c8dfe10a7c Sanitized user-provided file names in HTTP multipart uploads 2024-02-01 23:48:27 +00:00
Anthony Stirling
c8481fdbef Merge pull request #768 from Stirling-Tools/pixeebot/drip-2024-02-01-pixee-java/sandbox-url-creation
Sandboxed URL creation to prevent SSRF attacks
2024-02-01 23:37:41 +00:00
pixeebot[bot]
8e0c02a151 Sandboxed URL creation to prevent SSRF attacks 2024-02-01 23:35:05 +00:00
Anthony Stirling
271906097d Merge pull request #767 from Stirling-Tools/pixeebot/drip-2024-02-01-pixee-java/harden-process-creation
Introduced protections against system command injection
2024-02-01 23:22:59 +00:00
pixeebot[bot]
91caa2a097 Introduced protections against system command injection 2024-02-01 23:18:24 +00:00
Anthony Stirling
6105451e08 Merge pull request #766 from Stirling-Tools/pixeebot/drip-2024-02-01-pixee-java/limit-readline
Protect `readLine()` against DoS
2024-02-01 23:05:22 +00:00
pixeebot[bot]
450e090252 Protect readLine() against DoS 2024-02-01 23:01:04 +00:00
Anthony Stirling
86635f85b4 Merge pull request #764 from Stirling-Tools/pixeebot/drip-2024-02-01-pixee-java/harden-zip-entry-paths
Introduced protections against "zip slip"  attacks
2024-02-01 22:50:55 +00:00
Anthony Stirling
a7214a2171 Merge branch 'main' into pixeebot/drip-2024-02-01-pixee-java/harden-zip-entry-paths 2024-02-01 22:46:16 +00:00
Anthony Stirling
61cd473e6c Merge pull request #763 from Stirling-Tools/Frooodle-patch-1
Frooodle patch 1
2024-02-01 22:45:59 +00:00
Anthony Stirling
d67690d995 Update build.gradle 2024-02-01 22:43:25 +00:00
pixeebot[bot]
e20f4fe31a Introduced protections against "zip slip" attacks 2024-02-01 22:41:49 +00:00
Anthony Stirling
2deb40bb6d Update build.gradle 2024-02-01 22:41:13 +00:00
Anthony Stirling
bfee745cca Update build.gradle 2024-02-01 22:40:36 +00:00
Anthony Stirling
68d390e633 Merge pull request #707 from tkymmm/main
Update messages_ja_JP.properties
2024-02-01 09:23:28 +00:00
Eric
a884f1b3d4 Merge branch 'main' into main 2024-02-01 00:13:14 -05:00
Zoey
d190ae0cf3 add @testing (3/3) 2024-01-31 21:02:22 +01:00
Zoey
bb08a63296 add @testing (2/3) 2024-01-31 21:02:22 +01:00
Zoey
3debc1b0df add @testing (1/3) 2024-01-31 21:02:22 +01:00
Zoey
e560028097 Update Dockerfile-lite 2024-01-31 21:02:22 +01:00
Zoey
5afcbdbc8b use patched libreoffice version 2024-01-31 21:02:22 +01:00
Zoey
aaaf3ffe34 fix some things 2024-01-31 21:02:22 +01:00
Zoey
b043e666ae switch images to alpine
Signed-off-by: Zoey <zoey@z0ey.de>
2024-01-31 21:02:22 +01:00
Eric
cda8f7b27d Merge pull request #758 from Stirling-Tools/watermark_newline
feat: support '\n' literal in add watermark
2024-01-31 12:56:16 -05:00
Eric
d524fcc157 Merge branch 'main' into watermark_newline 2024-01-31 12:55:29 -05:00
Eric
e05e34f217 Merge pull request #754 from Stirling-Tools/fix_extract_image
fix: infinite recursion in `ImageFinder`
2024-01-31 12:55:07 -05:00
Eric
73bbb516d2 Merge branch 'main' into fix_extract_image 2024-01-31 12:51:47 -05:00
Anthony Stirling
c2aaa65228 Merge pull request #755 from ProvaTeams/Add-required-attribute-to-input-file
Add required attribute to input file
2024-01-31 17:37:45 +00:00
Anthony Stirling
91722af8b0 Merge branch 'main' into Add-required-attribute-to-input-file 2024-01-31 17:20:35 +00:00
Eric
71d33f6047 Merge pull request #760 from Stirling-Tools/pr_template
misc: update pull request template
2024-01-31 11:39:03 -05:00
sbplat
903faadff3 misc: update pull request template 2024-01-31 11:04:40 -05:00
ProvaTeams
55020d45f8 Using the flag logic 2024-01-31 14:09:16 +00:00
sbplat
2d37c707e2 feat: support '\n' literal in add watermark 2024-01-31 00:54:51 -05:00
ProvaTeams
b00f8c80ec Add required attribute to input file 2024-01-29 19:13:57 +01:00
sbplat
53afb865c5 refactor: replace ImageFinder with getAllImages using strategy behind ExtractImagesController 2024-01-29 11:23:58 -05:00
Anthony Stirling
6f3e317484 Merge pull request #736 from michelheusschen/update-dutch-translation
Update dutch translation
2024-01-29 08:18:43 +00:00
Michel Heusschen
e77d2847ea add new translations 2024-01-29 07:49:02 +01:00
Michel Heusschen
46abae9acc Merge branch 'main' of https://github.com/michelheusschen/Stirling-PDF into update-dutch-translation 2024-01-29 07:27:28 +01:00
Anthony Stirling
571320b9ba Merge pull request #703 from cloud-erik/main
Formating of language string in example
2024-01-28 20:23:21 +00:00
Anthony Stirling
07e9fcb6cc Merge pull request #737 from michelheusschen/fix/merge-tool-file-selection
fix: show only selected files for merge tool
2024-01-28 20:22:45 +00:00
Anthony Stirling
7a8719743d Merge pull request #751 from albanobattistella/patch-8
Update messages_it_IT.properties
2024-01-28 20:21:54 +00:00
albanobattistella
746f2d0949 Update messages_it_IT.properties 2024-01-28 21:18:57 +01:00
Anthony Stirling
3986858adb Update build.gradle 2024-01-28 20:11:41 +00:00
Anthony Stirling
589cb8d91f Add files via upload 2024-01-28 20:11:15 +00:00
Anthony Stirling
705c75e51d Merge pull request #749 from Stirling-Tools/Frooodle-patch-1
Auto split fix
2024-01-28 18:18:50 +00:00
Anthony Stirling
6acb593411 Update AutoSplitPdfController.java 2024-01-28 18:17:32 +00:00
Anthony Stirling
8060451713 Update AutoSplitPdfController.java 2024-01-28 18:16:59 +00:00
Anthony Stirling
6130f14d5a Merge pull request #748 from Stirling-Tools/pipelineFixes
Pipeline fixes
2024-01-28 17:48:48 +00:00
Anthony Stirling
0fbc461877 Merge branch 'main' into pipelineFixes 2024-01-28 17:41:17 +00:00
Anthony Stirling
89e461e4f6 formats 2024-01-28 17:39:07 +00:00
Anthony Stirling
ba4ad1aff9 langs 2024-01-28 17:37:02 +00:00
Anthony Stirling
be1904749b Add stamp, fix html, change accepts 2024-01-28 17:36:17 +00:00
Michel Heusschen
166fa0eb87 fix: show only selected files for merge tool 2024-01-23 16:06:57 +01:00
Michel Heusschen
9a06e7a3ca typo 2024-01-23 14:14:54 +01:00
Michel Heusschen
cb12af2d95 update Dutch (nl_NL) translation 2024-01-23 14:11:53 +01:00
Eric
46032b8ebb Merge pull request #719 from dhenry437/fix/multi-tool-filename
Multi-tool bug with dropping files
2024-01-22 14:28:07 -05:00
Anthony Stirling
4a6bd60466 Merge branch 'main' into fix/multi-tool-filename 2024-01-22 18:38:25 +00:00
Eric
f85c8ea5ec Merge pull request #730 from Stirling-Tools/fix-remove-blank-pages
fix: remove blank pages not handling EXIT_FAILURE code properly
2024-01-22 10:58:59 -05:00
sbplat
06ef09035d fix: remove blank pages not handling EXIT_FAILURE code properly 2024-01-22 10:39:29 -05:00
Anthony Stirling
13301e4606 Merge branch 'main' into fix/multi-tool-filename 2024-01-20 22:29:59 +00:00
Anthony Stirling
b59651a0fb Merge pull request #722 from phfuh/push
Update messages_de_DE.properties
2024-01-20 19:20:53 +00:00
Anthony Stirling
86b8d7f804 Merge pull request #723 from andrewdolphin/patch-1
Remove bootstrap css from view-pdf.html
2024-01-20 19:13:54 +00:00
andrewdolphin
e4dded3faa Remove bootstrap css from view-pdf.html that is causing issues when adding images 2024-01-19 10:31:05 +00:00
Anthony Stirling
75cf3ed0c1 Resolve wkhtml and formatting 2024-01-18 23:28:39 +00:00
Anthony Stirling
2fa68be36b pipeline fixes 2024-01-18 21:57:41 +00:00
phfuh
d09b252a4a Update messages_de_DE.properties
Translated the new entries, some corrections
2024-01-18 19:48:21 +01:00
sbplat
a5165b04cd fix(multi-tool): refactor fileInput.js into a class, fix filename variable typos, and update updateFilename logic for dropping files 2024-01-18 01:08:31 -05:00
Dan Henry
1bd17eded6 call updateFilenameInput on file drop 2024-01-18 12:08:32 +11:00
Dan Henry
18d289d3b7 Move filename input logic to its own function 2024-01-18 12:07:02 +11:00
Stirling-PDF-Bot
e43e6d18b9 Update 3rd Party Licenses 2024-01-17 23:52:12 +00:00
tkymmm
ec5a3c5948 Update messages_ja_JP.properties
Updated Japanese translation.
2024-01-15 10:16:46 +09:00
Stirling-PDF-Bot
89c0e721b8 Update 3rd Party Licenses 2024-01-15 00:13:30 +00:00
cloud-erik
c807d20590 Update docker-compose-latest-lite-security.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:32:34 +01:00
cloud-erik
686af16cf5 Update docker-compose-latest-lite.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:32:23 +01:00
cloud-erik
219dd7834f Update docker-compose-latest-security.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:32:12 +01:00
cloud-erik
1b83fda349 Update docker-compose-latest-ultra-lite-security.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:32:00 +01:00
cloud-erik
490acddc65 Update docker-compose-latest-ultra-lite.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:31:45 +01:00
cloud-erik
e69ed06b4f Update docker-compose-latest.yml
Exemple DEFAULTLOCALE formating
2024-01-14 10:29:21 +01:00
Anthony Stirling
2fe9b5a24b Merge pull request #699 from Stirling-Tools/pdfbox3
PDFBox v3 upgrade plus resolution to reversed text
2024-01-13 10:13:00 +00:00
Anthony Stirling
3912f42128 changeCreds message 2024-01-13 10:08:42 +00:00
Anthony Stirling
801e307005 0.20 bump 2024-01-13 01:09:41 +00:00
Anthony Stirling
c8acddb251 Resolve split sections 2024-01-13 01:05:43 +00:00
Anthony Stirling
d8cf7e81b9 fixes 2024-01-13 00:55:43 +00:00
Anthony Stirling
c4ad442ec3 remove logs 2024-01-13 00:46:17 +00:00
Anthony Stirling
c8e5023ec1 fix 2024-01-13 00:37:19 +00:00
Anthony Stirling
5281d7a49a pdfbox3 upgrade and fix 2024-01-12 23:15:27 +00:00
Anthony Stirling
77dcf04cfe Merge pull request #697 from Stirling-Tools/contributing
docs: add contributing guide
2024-01-11 23:07:37 +00:00
sbplat
b6523e9989 Merge branch 'contributing' of https://github.com/Stirling-Tools/Stirling-PDF into contributing 2024-01-11 17:39:22 -05:00
sbplat
d52a00185b Merge branch 'main' into contributing 2024-01-11 17:39:03 -05:00
sbplat
00487275a7 docs: list supported languages in readme 2024-01-11 17:38:05 -05:00
Anthony Stirling
823c8eb53e Merge pull request #686 from PeterDaveHelloKitchen/Markdown
Specify code block language in Markdown for syntax highlighting
2024-01-11 22:38:02 +00:00
Anthony Stirling
e9b8981a35 Merge pull request #698 from Stirling-Tools/Sf298-patch-1
Updated demo site
2024-01-11 22:30:10 +00:00
Saud Fatayerji
575e0b3e54 Updated demo site 2024-01-11 22:07:56 +03:00
sbplat
db931717a1 Merge branch 'main' into contributing 2024-01-11 10:47:55 -05:00
sbplat
787c59efd3 Merge pull request #694 from iLern/bugfix-md2pdf
Enable support for tables in the conversion from Markdown to HTML
2024-01-11 10:47:40 -05:00
sbplat
45aead89e3 docs: add contributing guide 2024-01-11 10:35:34 -05:00
TieStone
81d49b722b add table support in md2pdf transport 2024-01-11 14:54:01 +08:00
TieStone
ab9e7bbb8c add table support in md2pdf transport 2024-01-11 14:54:01 +08:00
TieStone
ee223d0405 add table support 2024-01-11 14:54:01 +08:00
Stirling-PDF-Bot
99050ad73e Update 3rd Party Licenses 2024-01-11 14:40:26 +08:00
sbplat
9fc873e973 Merge pull request #683 from Stirling-Tools/version_fix
fix: version showing as 0.0.0
2024-01-10 09:28:43 -05:00
Peter Dave Hello
52fe4c6aa6 Specify code block language in Markdown for syntax highlighting 2024-01-10 19:17:09 +08:00
sbplat
66df7053bb Merge branch 'main' into version_fix 2024-01-09 21:17:57 -05:00
sbplat
edde1a6436 fix: version showing as 0.0.0 2024-01-09 21:13:35 -05:00
Anthony Stirling
50ee829e5f Merge pull request #682 from Stirling-Tools/ebook
Ebook
2024-01-10 00:41:54 +00:00
Anthony Stirling
1924dfb4f1 Merge branch 'main' into ebook 2024-01-10 00:40:17 +00:00
Anthony Stirling
873a4ecb7e revert 2024-01-10 00:39:26 +00:00
Anthony Stirling
32da14acbf log removal 2024-01-10 00:37:55 +00:00
Anthony Stirling
139c793b5e 0.19.1 2024-01-10 00:33:22 +00:00
Anthony Stirling
e717d83f75 fixes and timeouts 2024-01-10 00:33:07 +00:00
Anthony Stirling
ef12c2f892 Add ebook support 2024-01-09 22:39:21 +00:00
320 changed files with 28242 additions and 24456 deletions

View File

@@ -1,2 +1,5 @@
# Formatting # Formatting
5f771b785130154ed47952635b7acef371ffe0ec 5f771b785130154ed47952635b7acef371ffe0ec
# Normalize files
55d4fda01b2f39f5b7d7b4fda5214bd7ff0fd5dd

View File

@@ -1,4 +1,18 @@
# License Agreement for Contributions # Description
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under MPL 2.0 (Mozilla Public License Version 2.0) license.
Please provide a summary of the changes, including relevant motivation and context.
Closes #(issue_number)
## 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
## Contributor License Agreement
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
(This does not change the general open-source nature of Stirling-PDF, simply moving from one license to another license) (This does not change the general open-source nature of Stirling-PDF, simply moving from one license to another license)

View File

@@ -1,3 +0,0 @@
# License Agreement for Contributions
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under MPL 2.0 (Mozilla Public License Version 2.0) license.
(This does not change the open-source nature of Stirling-PDF, simply moving from one license to another license)

View File

@@ -3,7 +3,7 @@ name: Push Docker Image with VersionNumber
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- master - master
- main - main
permissions: permissions:
@@ -15,13 +15,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v3.5.2 - uses: actions/checkout@v3.5.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v3.11.0
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- uses: gradle/gradle-build-action@v2.4.2 - uses: gradle/gradle-build-action@v2.4.2
env: env:
@@ -32,11 +32,11 @@ jobs:
- name: Make Gradle wrapper executable - name: Make Gradle wrapper executable
run: chmod +x gradlew run: chmod +x gradlew
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2.1.0 uses: docker/login-action@v2.1.0
with: with:
@@ -53,7 +53,7 @@ jobs:
- name: Convert repository owner to lowercase - name: Convert repository owner to lowercase
id: repoowner id: repoowner
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
- name: Generate tags - name: Generate tags
id: meta id: meta
uses: docker/metadata-action@v4.4.0 uses: docker/metadata-action@v4.4.0
@@ -82,7 +82,7 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
@@ -99,7 +99,7 @@ jobs:
tags: | tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v4.0.0 uses: docker/build-push-action@v4.0.0
@@ -112,7 +112,7 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta2.outputs.tags }} tags: ${{ steps.meta2.outputs.tags }}
labels: ${{ steps.meta2.outputs.labels }} labels: ${{ steps.meta2.outputs.labels }}
build-args: build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
@@ -129,7 +129,7 @@ jobs:
tags: | tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-lite,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-lite,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=latest-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-lite - name: Build and push Dockerfile-lite
uses: docker/build-push-action@v4.0.0 uses: docker/build-push-action@v4.0.0
@@ -142,7 +142,7 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta3.outputs.tags }} tags: ${{ steps.meta3.outputs.tags }}
labels: ${{ steps.meta3.outputs.labels }} labels: ${{ steps.meta3.outputs.labels }}
build-args: build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
- name: Build and Push Helm Chart - name: Build and Push Helm Chart

View File

@@ -1,7 +1,7 @@
name: Release Artifacts name: Release Artifacts
on: on:
release: release:
types: [created] types: [created]
permissions: permissions:
contents: write contents: write
@@ -19,13 +19,13 @@ jobs:
file_suffix: '' file_suffix: ''
steps: steps:
- uses: actions/checkout@v3.5.2 - uses: actions/checkout@v3.5.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v3.11.0
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew
@@ -42,11 +42,11 @@ jobs:
asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe
tag: ${{ github.ref }} tag: ${{ github.ref }}
overwrite: true overwrite: true
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
- name: Upload jar binaries to release - name: Upload jar binaries to release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
with: with:

View File

@@ -3,7 +3,7 @@ name: Update Swagger
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- master - master
jobs: jobs:
push: push:
@@ -12,13 +12,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v3.5.2 - uses: actions/checkout@v3.5.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v3.11.0
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew

View File

@@ -30,7 +30,7 @@ jobs:
- name: Run Docker Compose Tests - name: Run Docker Compose Tests
run: | run: |
chmod +x ./gradlew chmod +x ./gradlew
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"

252
.gitignore vendored
View File

@@ -1,127 +1,127 @@
### Eclipse ### ### Eclipse ###
.metadata .metadata
bin/ bin/
tmp/ tmp/
*.tmp *.tmp
*.bak *.bak
*.swp *.swp
*~.nib *~.nib
local.properties local.properties
.settings/ .settings/
.loadpath .loadpath
.recommenders .recommenders
.classpath .classpath
.project .project
version.properties version.properties
pipeline/watchedFolders/ pipeline/watchedFolders/
pipeline/finishedFolders/ pipeline/finishedFolders/
#### Stirling-PDF Files ### #### Stirling-PDF Files ###
customFiles/ customFiles/
configs/ configs/
watchedFolders/ watchedFolders/
# Gradle # Gradle
.gradle .gradle
.lock .lock
# External tool builders # External tool builders
.externalToolBuilders/ .externalToolBuilders/
# Locally stored "Eclipse launch configurations" # Locally stored "Eclipse launch configurations"
*.launch *.launch
# PyDev specific (Python IDE for Eclipse) # PyDev specific (Python IDE for Eclipse)
*.pydevproject *.pydevproject
# CDT-specific (C/C++ Development Tooling) # CDT-specific (C/C++ Development Tooling)
.cproject .cproject
# CDT- autotools # CDT- autotools
.autotools .autotools
# Java annotation processor (APT) # Java annotation processor (APT)
.factorypath .factorypath
# PDT-specific (PHP Development Tools) # PDT-specific (PHP Development Tools)
.buildpath .buildpath
# sbteclipse plugin # sbteclipse plugin
.target .target
# Tern plugin # Tern plugin
.tern-project .tern-project
# TeXlipse plugin # TeXlipse plugin
.texlipse .texlipse
# STS (Spring Tool Suite) # STS (Spring Tool Suite)
.springBeans .springBeans
# Code Recommenders # Code Recommenders
.recommenders/ .recommenders/
# Annotation Processing # Annotation Processing
.apt_generated/ .apt_generated/
.apt_generated_test/ .apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse) # Scala IDE specific (Scala & Java development for Eclipse)
.cache-main .cache-main
.scala_dependencies .scala_dependencies
.worksheet .worksheet
# Uncomment this line if you wish to ignore the project description file. # Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations: # Typically, this file would be tracked if it contains build/dependency configurations:
#.project #.project
### Eclipse Patch ### ### Eclipse Patch ###
# Spring Boot Tooling # Spring Boot Tooling
.sts4-cache/ .sts4-cache/
### Git ### ### Git ###
# Created by git for backups. To disable backups in Git: # Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false # $ git config --global mergetool.keepBackup false
*.orig *.orig
# Created by git when using merge tools for conflicts # Created by git when using merge tools for conflicts
*.BACKUP.* *.BACKUP.*
*.BASE.* *.BASE.*
*.LOCAL.* *.LOCAL.*
*.REMOTE.* *.REMOTE.*
*_BACKUP_*.txt *_BACKUP_*.txt
*_BASE_*.txt *_BASE_*.txt
*_LOCAL_*.txt *_LOCAL_*.txt
*_REMOTE_*.txt *_REMOTE_*.txt
### Java ### ### Java ###
# Compiled class file # Compiled class file
*.class *.class
# Log file # Log file
*.log *.log
# BlueJ files # BlueJ files
*.ctxt *.ctxt
# Mobile Tools for Java (J2ME) # Mobile Tools for Java (J2ME)
.mtj.tmp/ .mtj.tmp/
# Package Files # # Package Files #
*.jar *.jar
*.war *.war
*.nar *.nar
*.ear *.ear
*.zip *.zip
*.tar.gz *.tar.gz
*.rar *.rar
*.db *.db
/build /build
/.vscode /.vscode
/.idea /.idea
# Ignore Mac DS_Store files # Ignore Mac DS_Store files
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store

40
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,40 @@
# Contributing to Stirling-PDF
Thank you for your interest in contributing to Stirling-PDF! There are many ways to contribute other than writing code. For example, reporting bugs, creating suggestions, and adding or modifying translations.
## Issue Guidelines
Issues can be used to report bugs, request features, or ask questions. If you have a question, you could also ask us in our [Discord](https://discord.gg/FJUSXUSYec).
Before opening an issue, please check to make sure someone hasn't already opened an issue about it.
## Pull Requests
Before you start working on an issue, please comment on (or create) the issue and wait for it to be assigned to you. If someone has already been assigned but didn't have the time to work on it lately, please communicate with them and ask if they're still working on it. This is to avoid multiple people working on the same issue.
Once you have been assigned an issue, you can start working on it. When you are ready to submit your changes, open a pull request.
For a detailed pull request tutorial, see [this guide](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github).
Please make sure your Pull Request adheres to the following guidelines:
- Use the PR template provided.
- Keep your Pull Request title succinct, detailed and to the point.
- Keep commits atomic. One commit should contain one change. If you want to make multiple changes, submit multiple Pull Requests.
- Commits should be clear, concise and easy to understand.
- References to the Issue number in the Pull Request and/or Commit message.
## Translations
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
## Fixing Bugs or Adding a New Feature
First, make sure you've read the section [Pull Requests](#pull-requests).
To build from source, please follow this [Guide](LocalRunGuide.md).
If, at any point of time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
## License
By contributing to this project, you agree that your contributions will be licensed under the [GPL 3 License](LICENSE). You also acknowledge and agree that your contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.

View File

@@ -1,47 +1,62 @@
# Use the base image # Main stage
FROM frooodle/stirling-pdf-base:version8 FROM alpine:3.19.1
ARG VERSION_TAG # Copy necessary files
COPY scripts /scripts
# Set Environment Variables COPY pipeline /pipeline
ENV DOCKER_ENABLE_SECURITY=false \ COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
HOME=/home/stirlingpdfuser \ COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
VERSION_TAG=$VERSION_TAG \ COPY build/libs/*.jar app.jar
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
# PUID=1000 \ ARG VERSION_TAG
# PGID=1000 \
# UMASK=022 \
# Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \
# Create user and group VERSION_TAG=$VERSION_TAG \
##RUN groupadd -g $PGID stirlingpdfgroup && \ JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ HOME=/home/stirlingpdfuser \
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME PUID=1000 \
PGID=1000 \
# Set up necessary directories and permissions UMASK=022
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /logs /customFiles /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
##&& \
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \ # JDK for app
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
# Copy necessary files echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
COPY ./scripts/* /scripts/ apk add --no-cache \
COPY ./pipeline/ /pipeline/ ca-certificates \
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ tzdata \
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ tini \
COPY build/libs/*.jar app.jar bash \
curl \
# Set font cache and permissions openjdk17-jre \
RUN fc-cache -f -v && chmod +x /scripts/* su-exec \
# Doc conversion
##&& \ libreoffice@testing \
## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ # OCR MY PDF (unpaper for descew and other advanced featues)
## chmod +x /scripts/init.sh ocrmypdf \
tesseract-ocr-data-eng \
# Expose necessary ports # CV
EXPOSE 8080 py3-opencv \
# python3/pip
# Set user and run command python3 && \
##USER stirlingpdfuser wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
ENTRYPOINT ["/scripts/init.sh"] # uno unoconv and HTML
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \
chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \
# User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar
EXPOSE 8080
# Set user and run command
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,65 +1,61 @@
# Build jbig2enc in a separate stage # use alpine
FROM bellsoft/liberica-openjdk-debian:17 FROM alpine:3.19.1
ARG VERSION_TAG ARG VERSION_TAG
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libreoffice-core \
libreoffice-common \
libreoffice-writer \
libreoffice-calc \
libreoffice-impress \
unoconv && \
rm -rf /var/lib/apt/lists/*
# Set Environment Variables # Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \ ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
# PUID=1000 \ PUID=1000 \
# PGID=1000 \ PGID=1000 \
# UMASK=022 \ UMASK=022
# Create user and group
#RUN groupadd -g $PGID stirlingpdfgroup && \
# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
# Set up necessary directories and permissions
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
# Copy necessary files # Copy necessary files
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY ./pipeline/ /pipeline/ COPY pipeline /pipeline
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
COPY build/libs/*.jar app.jar COPY build/libs/*.jar app.jar
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk add --no-cache \
ca-certificates \
tzdata \
tini \
bash \
curl \
openjdk17-jre \
su-exec \
# Doc conversion
libreoffice@testing \
# python and pip
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
# uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
# Set up necessary directories and permissions
mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
# Set font cache and permissions # Set font cache and permissions
RUN fc-cache -f -v && \ fc-cache -f -v && \
chmod +x /scripts/init-without-ocr.sh && \ chmod +x /scripts/*.sh && \
chmod +x /scripts/download-security-jar.sh # User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar
# chown stirlingpdfuser:stirlingpdfgroup /app.jar
# Expose the application port
EXPOSE 8080
# Set environment variables # Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF ENV ENDPOINTS_GROUPS_TO_REMOVE=OpenCV,OCRmyPDF
ENV DOCKER_ENABLE_SECURITY=false ENV DOCKER_ENABLE_SECURITY=false
EXPOSE 8080
# Run the application # Run the application
#USER stirlingpdfuser
ENTRYPOINT ["/scripts/init-without-ocr.sh"] ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,5 +1,5 @@
# Build jbig2enc in a separate stage # use alpine
FROM bellsoft/liberica-openjdk-alpine:17 FROM alpine:3.19.1
ARG VERSION_TAG ARG VERSION_TAG
@@ -7,40 +7,44 @@ ARG VERSION_TAG
ENV DOCKER_ENABLE_SECURITY=false \ ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
# PUID=1000 \ PUID=1000 \
# PGID=1000 \ PGID=1000 \
# UMASK=022 \ UMASK=022
# Create user and group using Alpine's addgroup and adduser # Copy necessary files
#RUN addgroup -g $PGID stirlingpdfgroup && \ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
# adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \ COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME COPY pipeline /pipeline
# Set up necessary directories and permissions
#RUN mkdir -p /scripts /configs /customFiles && \
# chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY ./pipeline/ /pipeline/
COPY build/libs/*.jar app.jar COPY build/libs/*.jar app.jar
# Set font cache and permissions
#RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
RUN chmod +x /scripts/init-without-ocr.sh && \ # Set up necessary directories and permissions
chmod +x /scripts/download-security-jar.sh && \
apk add --no-cache curl
# Expose the application port RUN mkdir /configs /logs /customFiles && \
EXPOSE 8080 chmod +x /scripts/*.sh && \
apk add --no-cache \
ca-certificates \
tzdata \
tini \
bash \
curl \
su-exec \
openjdk17-jre && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
# User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar
# Set environment variables # Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
ENTRYPOINT ["/scripts/init-without-ocr.sh"] EXPOSE 8080
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
# Run the application # Run the application
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,43 +0,0 @@
# Main stage
FROM ubuntu:latest AS base
# JDK for app
RUN apt-get update && \
apt-get install -y --no-install-recommends \
openjdk-17-jre && \
rm -rf /var/lib/apt/lists/*
# Doc conversion
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libreoffice-core \
libreoffice-common \
libreoffice-writer \
libreoffice-calc \
libreoffice-impress \
python3-uno \
curl \
unoconv && \
rm -rf /var/lib/apt/lists/*
# OCR MY PDF (unpaper for descew and other advanced featues)
RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common gnupg2 && \
add-apt-repository ppa:alex-p/tesseract-ocr5 && apt install -y --no-install-recommends tesseract-ocr && \
apt-get update && \
apt-get install -y --no-install-recommends \
ghostscript \
python3-pip \
ocrmypdf \
unpaper && \
rm -rf /var/lib/apt/lists/* && \
mv /usr/share/tesseract-ocr /usr/share/tesseract-ocr-original && \
pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir --upgrade ocrmypdf && \
pip install --no-cache-dir --upgrade pillow==10.0.1 reportlab==3.6.13 wheel==0.38.1 setuptools==65.5.1 pyjwt==2.4.0 cryptography==39.0.1
#CV and HTML
RUN pip install --no-cache-dir opencv-python-headless WeasyPrint

View File

@@ -1,46 +1,46 @@
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript | | Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript |
|---------------------|---------|---------|----------|-------|------|--------|--------|-------------|----------|----------|------------| |---------------------|---------|---------|----------|-------|------|--------|--------|-------------|----------|----------|------------|
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | adjust-contrast | ✔️ | | | | | | | | | | ✔️ |
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | auto-split-pdf | ✔️ | | | | | | | | | ✔️ | |
| crop | ✔️ | | | | | | | | | ✔️ | | | crop | ✔️ | | | | | | | | | ✔️ | |
| extract-page | ✔️ | | | | | | | | | ✔️ | | | extract-page | ✔️ | | | | | | | | | ✔️ | |
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | | | merge-pdfs | ✔️ | | | | | | | | | ✔️ | |
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | | | multi-page-layout | ✔️ | | | | | | | | | ✔️ | |
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ | | pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ |
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | | | pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | |
| remove-pages | ✔️ | | | | | | | | | ✔️ | | | remove-pages | ✔️ | | | | | | | | | ✔️ | |
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | | | rotate-pdf | ✔️ | | | | | | | | | ✔️ | |
| scale-pages | ✔️ | | | | | | | | | ✔️ | | | scale-pages | ✔️ | | | | | | | | | ✔️ | |
| split-pdfs | ✔️ | | | | | | | | | ✔️ | | | split-pdfs | ✔️ | | | | | | | | | ✔️ | |
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | | | file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-img | | ✔️ | | | | | | | | ✔️ | | | pdf-to-img | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | |
| xlsx-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | | | xlsx-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
| add-password | | | ✔️ | | | | | | | ✔️ | | | add-password | | | ✔️ | | | | | | | ✔️ | |
| add-watermark | | | ✔️ | | | | | | | ✔️ | | | add-watermark | | | ✔️ | | | | | | | ✔️ | |
| cert-sign | | | ✔️ | | | | | | | ✔️ | | | cert-sign | | | ✔️ | | | | | | | ✔️ | |
| change-permissions | | | ✔️ | | | | | | | ✔️ | | | change-permissions | | | ✔️ | | | | | | | ✔️ | |
| remove-password | | | ✔️ | | | | | | | ✔️ | | | remove-password | | | ✔️ | | | | | | | ✔️ | |
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | | | sanitize-pdf | | | ✔️ | | | | | | | ✔️ | |
| add-image | | | | ✔️ | | | | | | ✔️ | | | add-image | | | | ✔️ | | | | | | ✔️ | |
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | | | add-page-numbers | | | | ✔️ | | | | | | ✔️ | |
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | auto-rename | | | | ✔️ | | | | | | ✔️ | |
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | change-metadata | | | | ✔️ | | | | | | ✔️ | |
| compare | | | | ✔️ | | | | | | | ✔️ | | compare | | | | ✔️ | | | | | | | ✔️ |
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
| extract-images | | | | ✔️ | | | | | | ✔️ | | | extract-images | | | | ✔️ | | | | | | ✔️ | |
| flatten | | | | ✔️ | | | | | | | ✔️ | | flatten | | | | ✔️ | | | | | | | ✔️ |
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | |
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | repair | | | | ✔️ | ✔️ | | | ✔️ | | | |
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | show-javascript | | | | ✔️ | | | | | | | ✔️ |
| sign | | | | ✔️ | | | | | | | ✔️ | | sign | | | | ✔️ | | | | | | | ✔️ |

View File

@@ -2,7 +2,7 @@
### Whilst Pipelines are in alpha... ### Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting You must enable this alpha functionality by setting
``` ```yaml
system: system:
enableAlphaFunctionality: true enableAlphaFunctionality: true
``` ```

View File

@@ -9,21 +9,21 @@ Fork Stirling-PDF and make a new branch out of Main
Then add reference to the language in the navbar by adding a new language entry to the dropdown Then add reference to the language in the navbar by adding a new language entry to the dropdown
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
and add a flag svg file to and add a flag svg file to
https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/) Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia If your language isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
For example to add Polish you would add For example to add Polish you would add
``` ```html
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL"> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a> </a>
``` ```
The data-language-code is the code used to reference the file in the next step. The data-language-code is the code used to reference the file in the next step.
Start by copying the existing english property file Start by copying the existing english property file
[https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties) [https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
@@ -32,7 +32,7 @@ Copy and rename it to messages_{your data-language-code here}.properties, in the
Then simply translate all property entries within that file and make a PR into main for others to use! Then simply translate all property entries within that file and make a PR into main for others to use!
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but wont be able to verify the translations themselves) If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves)

View File

@@ -2,12 +2,12 @@
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker. This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
## My OCR used to work and now doesnt! ## My OCR used to work and now doesn't!
Please update your tesseract docker volume path version from 4.00 to 5 The paths have changed for the tessadata locations on new docker images, please use ``/usr/share/tessdata`` (Others should still work for backwards compatability but might not)
## How does the OCR Work ## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition. Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.
All credit goes to them for this awesome work! All credit goes to them for this awesome work!
## Language Packs ## Language Packs
@@ -21,13 +21,13 @@ Depending on your requirements, you can choose the appropriate language pack for
### Installing Language Packs ### Installing Language Packs
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/5/tessdata` (Debian) or `/usr/share/tesseract/tessdata` (Fedora) 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
# DO NOT REMOVE EXISTING ENG.TRAINEDDATA, IT'S REQUIRED. # DO NOT REMOVE EXISTING ENG.TRAINEDDATA, IT'S REQUIRED.
#### Docker #### Docker
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs. If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
#### Docker Compose #### Docker Compose
Modify your `docker-compose.yml` file to include the following volume configuration: Modify your `docker-compose.yml` file to include the following volume configuration:
@@ -37,14 +37,14 @@ services:
your_service_name: your_service_name:
image: your_docker_image_name image: your_docker_image_name
volumes: volumes:
- /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata - /location/of/trainingData:/usr/share/tessdata
``` ```
#### Docker run #### Docker run
Add the following to your existing docker run command Add the following to your existing docker run command
```bash ```bash
-v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata -v /location/of/trainingData:/usr/share/tessdata
``` ```
#### Non-Docker #### Non-Docker

88
Jenkinsfile vendored
View File

@@ -1,45 +1,45 @@
pipeline { pipeline {
agent any agent any
stages { stages {
stage('Build') { stage('Build') {
steps { steps {
sh 'chmod 755 gradlew' sh 'chmod 755 gradlew'
sh './gradlew build' sh './gradlew build'
} }
} }
stage('Docker Build') { stage('Docker Build') {
steps { steps {
script { script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim() def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion" def image = "frooodle/s-pdf:$appVersion"
sh "docker build -t $image ." sh "docker build -t $image ."
} }
} }
} }
stage('Docker Push') { stage('Docker Push') {
steps { steps {
script { script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim() def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion" def image = "frooodle/s-pdf:$appVersion"
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) { withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN" sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "docker push $image" sh "docker push $image"
} }
} }
} }
} }
stage('Helm Push') { stage('Helm Push') {
steps { steps {
script { script {
//TODO: Read chartVersion from Chart.yaml //TODO: Read chartVersion from Chart.yaml
def chartVersion = '1.0.0' def chartVersion = '1.0.0'
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) { withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN" sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "helm package chart/stirling-pdf" sh "helm package chart/stirling-pdf"
sh "helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle" sh "helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle"
} }
} }
} }
} }
} }
} }

View File

@@ -45,7 +45,7 @@ sudo apt-get update
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ java-17-openjdk python3 python3-pip sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ java-17-openjdk python3 python3-pip
``` ```
For Fedora-based systems use this command: For Fedora-based systems use this command:
```bash ```bash
sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-17-openjdk python3 python3-pip sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-17-openjdk python3 python3-pip
@@ -65,7 +65,7 @@ sudo make install
``` ```
### Step 3: Install Additional Software ### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for patern recognition functionality. Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality.
Install the following software: Install the following software:
@@ -95,14 +95,14 @@ For Debian-based systems, you can use the following command:
```bash ```bash
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
``` ```
For Fedora: For Fedora:
```bash ```bash
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
``` ```
### Step 4: Clone and Build Stirling-PDF ### Step 4: Clone and Build Stirling-PDF
@@ -139,8 +139,8 @@ Easiest is to use the langpacks provided by your repositories. Skip the other st
Manual: Manual:
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/5/tessdata` 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
3. 3.
Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED. **IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
@@ -264,10 +264,10 @@ sudo systemctl restart stirlingpdf.service
Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme. Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme.
You can do this in the terminal by using the `export` command or -D arguements to java -jar command: You can do this in the terminal by using the `export` command or -D argument to java -jar command:
```bash ```bash
export APP_HOME_NAME="Stirling PDF" export APP_HOME_NAME="Stirling PDF"
or or
-DAPP_HOME_NAME="Stirling PDF" -DAPP_HOME_NAME="Stirling PDF"
``` ```

View File

@@ -2,7 +2,7 @@
## Whilst Pipelines are in alpha... ## Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting You must enable this alpha functionality by setting
``` ```yaml
system: system:
enableAlphaFunctionality: true enableAlphaFunctionality: true
``` ```

134
README.md
View File

@@ -6,7 +6,7 @@
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
[![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex) [![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex)
[![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle) [![Github Sponsor](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) [![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
@@ -16,15 +16,13 @@ Stirling PDF makes no outbound calls for any record keeping or tracking.
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point. All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
Please feel free to submit feature requests or report bugs either through GitHub issues or on our [Discord](https://discord.gg/Cn8pWhQRxZ)
![stirling-home](images/stirling-home.png) ![stirling-home](images/stirling-home.png)
## Features ## Features
- Dark mode support. - Dark mode support.
- Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example) - Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example)
- Parallel file processing and downloads - Parallel file processing and downloads
- API for integration with external scripts - API for integration with external scripts
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation) - Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
@@ -32,56 +30,56 @@ Please feel free to submit feature requests or report bugs either through GitHub
### **Page Operations** ### **Page Operations**
- View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts) - View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts)
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages. - Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
- Merge multiple PDFs together into a single resultant file. - Merge multiple PDFs together into a single resultant file.
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files. - Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
- Reorganize PDF pages into different orders. - Reorganize PDF pages into different orders.
- Rotate PDFs in 90-degree increments. - Rotate PDFs in 90-degree increments.
- Remove pages. - Remove pages.
- Multi-page layout (Format PDFs into a multi-paged page). - Multi-page layout (Format PDFs into a multi-paged page).
- Scale page contents size by set %. - Scale page contents size by set %.
- Adjust Contrast. - Adjust Contrast.
- Crop PDF. - Crop PDF.
- Auto Split PDF (With physically scanned page dividers). - Auto Split PDF (With physically scanned page dividers).
- Extract page(s). - Extract page(s).
- Convert PDF to a single page. - Convert PDF to a single page.
### **Conversion Operations** ### **Conversion Operations**
- Convert PDFs to and from images. - Convert PDFs to and from images.
- Convert any common file to PDF (using LibreOffice). - Convert any common file to PDF (using LibreOffice).
- Convert PDF to Word/Powerpoint/Others (using LibreOffice). - Convert PDF to Word/Powerpoint/Others (using LibreOffice).
- Convert HTML to PDF. - Convert HTML to PDF.
- URL to PDF. - URL to PDF.
- Markdown to PDF. - Markdown to PDF.
### **Security & Permissions** ### **Security & Permissions**
- Add and remove passwords. - Add and remove passwords.
- Change/set PDF Permissions. - Change/set PDF Permissions.
- Add watermark(s). - Add watermark(s).
- Certify/sign PDFs. - Certify/sign PDFs.
- Sanitize PDFs. - Sanitize PDFs.
- Auto-redact text. - Auto-redact text.
### **Other Operations** ### **Other Operations**
- Add/Generate/Write signatures. - Add/Generate/Write signatures.
- Repair PDFs. - Repair PDFs.
- Detect and remove blank pages. - Detect and remove blank pages.
- Compare 2 PDFs and show differences in text. - Compare 2 PDFs and show differences in text.
- Add images to PDFs. - Add images to PDFs.
- Compress PDFs to decrease their filesize (Using OCRMyPDF). - Compress PDFs to decrease their filesize (Using OCRMyPDF).
- Extract images from PDF. - Extract images from PDF.
- Extract images from Scans. - Extract images from Scans.
- Add page numbers. - Add page numbers.
- Auto rename file by detecting PDF header text. - Auto rename file by detecting PDF header text.
- OCR on PDF (Using OCRMyPDF). - OCR on PDF (Using OCRMyPDF).
- PDF/A conversion (Using OCRMyPDF). - PDF/A conversion (Using OCRMyPDF).
- Edit metadata. - Edit metadata.
- Flatten PDFs. - Flatten PDFs.
- Get all information on a PDF to view or export as JSON. - Get all information on a PDF to view or export as JSON.
For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md) For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de Demo of the app is available [here](https://stirlingpdf.io). username: demo, password: demo
## Technologies used ## Technologies used
- Spring Boot + Thymeleaf - Spring Boot + Thymeleaf
@@ -109,23 +107,24 @@ For people that don't mind about space optimization just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
Docker Run Docker Run
``` ```bash
docker run -d \ docker run -d \
-p 8080:8080 \ -p 8080:8080 \
-v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata \ -v /location/of/trainingData:/usr/share/tessdata \
-v /location/of/extraConfigs:/configs \ -v /location/of/extraConfigs:/configs \
-v /location/of/logs:/logs \ -v /location/of/logs:/logs \
-e DOCKER_ENABLE_SECURITY=false \ -e DOCKER_ENABLE_SECURITY=false \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
--name stirling-pdf \ --name stirling-pdf \
frooodle/s-pdf:latest frooodle/s-pdf:latest
Can also add these for customisation but are not required Can also add these for customisation but are not required
-v /location/of/customFiles:/customFiles \ -v /location/of/customFiles:/customFiles \
``` ```
Docker Compose Docker Compose
``` ```yaml
version: '3.3' version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
@@ -133,12 +132,13 @@ services:
ports: ports:
- '8080:8080' - '8080:8080'
volumes: volumes:
- /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata #Required for extra OCR languages - /location/of/trainingData:/usr/share/tessdata #Required for extra OCR languages
- /location/of/extraConfigs:/configs - /location/of/extraConfigs:/configs
# - /location/of/customFiles:/customFiles/ # - /location/of/customFiles:/customFiles/
# - /location/of/logs:/logs/ # - /location/of/logs:/logs/
environment: environment:
- DOCKER_ENABLE_SECURITY=false - DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
``` ```
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman". Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
@@ -146,7 +146,8 @@ Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "pod
## Enable OCR/Compression feature ## Enable OCR/Compression feature
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
## Want to add your own language? ## Supported Languages
Stirling PDF currently supports 26! Stirling PDF currently supports 26!
- English (English) (en_GB) - English (English) (en_GB)
- English (US) (en_US) - English (US) (en_US)
@@ -173,17 +174,11 @@ Stirling PDF currently supports 26!
- Hindi (हिंदी) (hi_IN) - Hindi (हिंदी) (hi_IN)
- Hungarian (Magyar) (hu_HU) - Hungarian (Magyar) (hu_HU)
- Bulgarian (Български) (bg_BG) - Bulgarian (Български) (bg_BG)
- Sebian Latin alphabet (Srpski) (sr-Latn-RS) - Sebian Latin alphabet (Srpski) (sr_LATN_RS)
If you want to add your own language to Stirling-PDF please refer ## Contributing (creating issues, translations, fixing bugs, etc.)
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md
And please create a PR to merge it back in so others can use it!
## How to View
1. Open a web browser and navigate to `http://localhost:8080/`
2. Use the application by following the instructions on the website.
Please see our [Contributing Guide](CONTRIBUTING.md)!
## Customisation ## Customisation
Stirling PDF allows easy customization of the app. Stirling PDF allows easy customization of the app.
@@ -197,7 +192,7 @@ This file is located in the ``/configs`` directory and follows standard YAML for
Environment variables are also supported and would override the settings file Environment variables are also supported and would override the settings file
For example in the settings.yml you have For example in the settings.yml you have
``` ```yaml
system: system:
defaultLocale: 'en-US' defaultLocale: 'en-US'
``` ```
@@ -205,7 +200,7 @@ system:
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE`` To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
The Current list of settings is The Current list of settings is
``` ```yaml
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: true csrfDisabled: true
@@ -235,6 +230,7 @@ metrics:
- ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app`` - ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app``
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values - ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login) - ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login)
- ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS`` to download calibre onto stirling-pdf enabling pdf to/from book and advanced html conversion
## API ## API
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
@@ -243,9 +239,9 @@ For those wanting to use Stirling-PDFs backend API to link with their own custom
## Login authentication ## Login authentication
![stirling-login](images/login-light.png) ![stirling-login](images/login-light.png)
### Prerequisites: ### Prerequisites:
- User must have the folder ./configs volumed within docker so that it is retained during updates. - User must have the folder ./configs volumed within docker so that it is retained during updates.
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables. - Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true`` - Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation). - Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).
@@ -266,10 +262,10 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
- Progress bar/Tracking - Progress bar/Tracking
- Full custom logic pipelines to combine multiple operations together. - Full custom logic pipelines to combine multiple operations together.
- Folder support with auto scanning to perform operations on - Folder support with auto scanning to perform operations on
- Redact text (Via UI not just automated way) - Redact text (Via UI not just automated way)
- Add Forms - Add Forms
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing - Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
- Fill forms mannual and automatic - Fill forms manually or automatically
### Q2: Why is my application downloading .htm files? ### Q2: Why is my application downloading .htm files?
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files. This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.

View File

@@ -1,64 +1,64 @@
|Technology | Ultra-Lite | Lite | Full | |Technology | Ultra-Lite | Lite | Full |
|----------------|:----------:|:----:|:----:| |----------------|:----------:|:----:|:----:|
| Java | ✔️ | ✔️ | ✔️ | | Java | ✔️ | ✔️ | ✔️ |
| JavaScript | ✔️ | ✔️ | ✔️ | | JavaScript | ✔️ | ✔️ | ✔️ |
| Libre | | ✔️ | ✔️ | | Libre | | ✔️ | ✔️ |
| Python | | | ✔️ | | Python | | | ✔️ |
| OpenCV | | | ✔️ | | OpenCV | | | ✔️ |
| OCRmyPDF | | | ✔️ | | OCRmyPDF | | | ✔️ |
Operation | Ultra-Lite | Lite | Full Operation | Ultra-Lite | Lite | Full
--------------------|------------|------|----- --------------------|------------|------|-----
add-page-numbers | ✔️ | ✔️ | ✔️ add-page-numbers | ✔️ | ✔️ | ✔️
add-password | ✔️ | ✔️ | ✔️ add-password | ✔️ | ✔️ | ✔️
add-image | ✔️ | ✔️ | ✔️ add-image | ✔️ | ✔️ | ✔️
add-watermark | ✔️ | ✔️ | ✔️ add-watermark | ✔️ | ✔️ | ✔️
adjust-contrast | ✔️ | ✔️ | ✔️ adjust-contrast | ✔️ | ✔️ | ✔️
auto-split-pdf | ✔️ | ✔️ | ✔️ auto-split-pdf | ✔️ | ✔️ | ✔️
auto-redact | ✔️ | ✔️ | ✔️ auto-redact | ✔️ | ✔️ | ✔️
auto-rename | ✔️ | ✔️ | ✔️ auto-rename | ✔️ | ✔️ | ✔️
cert-sign | ✔️ | ✔️ | ✔️ cert-sign | ✔️ | ✔️ | ✔️
crop | ✔️ | ✔️ | ✔️ crop | ✔️ | ✔️ | ✔️
change-metadata | ✔️ | ✔️ | ✔️ change-metadata | ✔️ | ✔️ | ✔️
change-permissions | ✔️ | ✔️ | ✔️ change-permissions | ✔️ | ✔️ | ✔️
compare | ✔️ | ✔️ | ✔️ compare | ✔️ | ✔️ | ✔️
extract-page | ✔️ | ✔️ | ✔️ extract-page | ✔️ | ✔️ | ✔️
extract-images | ✔️ | ✔️ | ✔️ extract-images | ✔️ | ✔️ | ✔️
flatten | ✔️ | ✔️ | ✔️ flatten | ✔️ | ✔️ | ✔️
get-info-on-pdf | ✔️ | ✔️ | ✔️ get-info-on-pdf | ✔️ | ✔️ | ✔️
img-to-pdf | ✔️ | ✔️ | ✔️ img-to-pdf | ✔️ | ✔️ | ✔️
markdown-to-pdf | ✔️ | ✔️ | ✔️ markdown-to-pdf | ✔️ | ✔️ | ✔️
merge-pdfs | ✔️ | ✔️ | ✔️ merge-pdfs | ✔️ | ✔️ | ✔️
multi-page-layout | ✔️ | ✔️ | ✔️ multi-page-layout | ✔️ | ✔️ | ✔️
overlay-pdf | ✔️ | ✔️ | ✔️ overlay-pdf | ✔️ | ✔️ | ✔️
pdf-organizer | ✔️ | ✔️ | ✔️ pdf-organizer | ✔️ | ✔️ | ✔️
pdf-to-csv | ✔️ | ✔️ | ✔️ pdf-to-csv | ✔️ | ✔️ | ✔️
pdf-to-img | ✔️ | ✔️ | ✔️ pdf-to-img | ✔️ | ✔️ | ✔️
pdf-to-single-page | ✔️ | ✔️ | ✔️ pdf-to-single-page | ✔️ | ✔️ | ✔️
remove-pages | ✔️ | ✔️ | ✔️ remove-pages | ✔️ | ✔️ | ✔️
remove-password | ✔️ | ✔️ | ✔️ remove-password | ✔️ | ✔️ | ✔️
rotate-pdf | ✔️ | ✔️ | ✔️ rotate-pdf | ✔️ | ✔️ | ✔️
sanitize-pdf | ✔️ | ✔️ | ✔️ sanitize-pdf | ✔️ | ✔️ | ✔️
scale-pages | ✔️ | ✔️ | ✔️ scale-pages | ✔️ | ✔️ | ✔️
sign | ✔️ | ✔️ | ✔️ sign | ✔️ | ✔️ | ✔️
show-javascript | ✔️ | ✔️ | ✔️ show-javascript | ✔️ | ✔️ | ✔️
split-by-size-or-count | ✔️ | ✔️ | ✔️ split-by-size-or-count | ✔️ | ✔️ | ✔️
split-pdf-by-sections | ✔️ | ✔️ | ✔️ split-pdf-by-sections | ✔️ | ✔️ | ✔️
split-pdfs | ✔️ | ✔️ | ✔️ split-pdfs | ✔️ | ✔️ | ✔️
file-to-pdf | | ✔️ | ✔️ file-to-pdf | | ✔️ | ✔️
pdf-to-html | | ✔️ | ✔️ pdf-to-html | | ✔️ | ✔️
pdf-to-presentation | | ✔️ | ✔️ pdf-to-presentation | | ✔️ | ✔️
pdf-to-text | | ✔️ | ✔️ pdf-to-text | | ✔️ | ✔️
pdf-to-word | | ✔️ | ✔️ pdf-to-word | | ✔️ | ✔️
pdf-to-xml | | ✔️ | ✔️ pdf-to-xml | | ✔️ | ✔️
repair | | ✔️ | ✔️ repair | | ✔️ | ✔️
xlsx-to-pdf | | ✔️ | ✔️ xlsx-to-pdf | | ✔️ | ✔️
compress-pdf | | | ✔️ compress-pdf | | | ✔️
extract-image-scans | | | ✔️ extract-image-scans | | | ✔️
ocr-pdf | | | ✔️ ocr-pdf | | | ✔️
pdf-to-pdfa | | | ✔️ pdf-to-pdfa | | | ✔️
remove-blanks | | | ✔️ remove-blanks | | | ✔️

View File

@@ -1,18 +1,18 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.2.1' id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.3' id 'io.spring.dependency-management' version '1.1.3'
id 'org.springdoc.openapi-gradle-plugin' version '1.8.0' id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
id 'edu.sc.seis.launch4j' version '3.0.5' id 'edu.sc.seis.launch4j' version '3.0.5'
id 'com.diffplug.spotless' version '6.23.3' id 'com.diffplug.spotless' version '6.25.0'
id 'com.github.jk1.dependency-license-report' version '2.5' id 'com.github.jk1.dependency-license-report' version '2.5'
} }
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
group = 'stirling.software' group = 'stirling.software'
version = '0.19.0' version = '0.22.0'
sourceCompatibility = '17' sourceCompatibility = '17'
repositories { repositories {
@@ -89,22 +89,24 @@ dependencies {
//security updates //security updates
implementation 'ch.qos.logback:logback-classic:1.4.14' implementation 'ch.qos.logback:logback-classic:1.4.14'
implementation 'ch.qos.logback:logback-core:1.4.14' implementation 'ch.qos.logback:logback-core:1.4.14'
implementation 'org.springframework:spring-webmvc:6.1.2' implementation 'org.springframework:spring-webmvc:6.1.3'
implementation("io.github.pixee:java-security-toolkit:1.1.2")
implementation 'org.yaml:snakeyaml:2.2' implementation 'org.yaml:snakeyaml:2.2'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1' implementation 'org.springframework.boot:spring-boot-starter-web:3.2.2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.2'
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.1' implementation 'org.springframework.boot:spring-boot-starter-security:3.2.2'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.1" implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.2"
//2.2.x requires rebuild of DB file.. need migration path //2.2.x requires rebuild of DB file.. need migration path
implementation "com.h2database:h2:2.1.214" implementation "com.h2database:h2:2.1.214"
} }
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1' testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.2'
// Batik // Batik
implementation 'org.apache.xmlgraphics:batik-all:1.17' implementation 'org.apache.xmlgraphics:batik-all:1.17'
@@ -136,26 +138,27 @@ dependencies {
implementation ('com.opencsv:opencsv:5.9') { implementation ('com.opencsv:opencsv:5.9') {
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation ('org.apache.pdfbox:pdfbox:2.0.30'){ implementation ('org.apache.pdfbox:pdfbox:3.0.1'){
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation ('org.apache.pdfbox:xmpbox:2.0.30'){ implementation ('org.apache.pdfbox:xmpbox:3.0.1'){
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation 'org.bouncycastle:bcprov-jdk18on:1.77' implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77' implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.2'
implementation 'io.micrometer:micrometer-core' implementation 'io.micrometer:micrometer-core:1.12.3'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2' implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation 'org.commonmark:commonmark:0.21.0' implementation 'org.commonmark:commonmark:0.21.0'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.21.0'
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core // https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools:3.2.2")
compileOnly 'org.projectlombok:lombok:1.18.30' compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.28' annotationProcessor 'org.projectlombok:lombok:1.18.28'
} }
@@ -163,6 +166,9 @@ dependencies {
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
dependsOn 'spotlessApply' dependsOn 'spotlessApply'
} }
compileJava {
options.compilerArgs << '-parameters'
}
task writeVersion { task writeVersion {
def propsFile = file('src/main/resources/version.properties') def propsFile = file('src/main/resources/version.properties')

View File

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

View File

@@ -43,6 +43,6 @@ spec:
name: http name: http
{{- end }} {{- end }}
protocol: TCP protocol: TCP
selector: selector:
{{- include "stirlingpdf.selectorLabels" . | nindent 4 }} {{- include "stirlingpdf.selectorLabels" . | nindent 4 }}

View File

@@ -16,11 +16,11 @@ commonLabels: {}
# team_name: dev # team_name: dev
envs: [] envs: []
# - name: PP_HOME_NAME # - name: UI_APP_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: APP_HOME_DESCRIPTION # - name: UI_HOME_DESCRIPTION
# value: "Your locally hosted one-stop-shop for all your PDF needs." # value: "Your locally hosted one-stop-shop for all your PDF needs."
# - name: APP_NAVBAR_NAME # - name: UI_APP_NAVBAR_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: ALLOW_GOOGLE_VISIBILITY # - name: ALLOW_GOOGLE_VISIBILITY
# value: "true" # value: "true"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,310 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg <svg
width="99.537987mm"
height="95.209366mm"
viewBox="0 0 99.537987 95.209366"
version="1.1" version="1.1"
id="svg745" id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 512 512"
style="enable-background:new 0 0 512 512;"
xml:space="preserve" xml:space="preserve"
inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)" sodipodi:docname="favicon.svg"
sodipodi:docname="stirling.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
inkscape:export-filename="stirling.png" inkscape:export-filename="favicon.png"
inkscape:export-xdpi="80" inkscape:export-xdpi="96"
inkscape:export-ydpi="80" inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview xmlns:svg="http://www.w3.org/2000/svg"><defs
id="namedview747" id="defs173">
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0" <linearGradient
inkscape:showpageshadow="2" id="XMLID_5_"
inkscape:pageopacity="0.0" gradientUnits="userSpaceOnUse"
inkscape:pagecheckerboard="0" x1="304.496"
inkscape:deskcolor="#d1d1d1" y1="422.9102"
inkscape:document-units="mm" x2="316.036"
showgrid="false" y2="326.2626">
inkscape:zoom="0.914906" <stop
inkscape:cx="184.17193" offset="0"
inkscape:cy="509.88845" style="stop-color:#DCF1F3"
inkscape:window-width="2293" id="stop156" />
inkscape:window-height="1387" <stop
inkscape:window-x="122" offset="1"
inkscape:window-y="25" style="stop-color:#C2C2C9"
inkscape:window-maximized="0" id="stop158" />
inkscape:current-layer="svg745" /><defs </linearGradient>
id="defs742"><linearGradient
inkscape:collect="always" </defs><sodipodi:namedview
id="linearGradient72198"><stop id="namedview171"
style="stop-color:#ccd6d7;stop-opacity:1;" pagecolor="#ffffff"
offset="0" bordercolor="#000000"
id="stop72194" /><stop borderopacity="0.25"
style="stop-color:#0f3a3f;stop-opacity:1;" inkscape:showpageshadow="2"
offset="1" inkscape:pageopacity="0.0"
id="stop72196" /></linearGradient><linearGradient inkscape:pagecheckerboard="0"
inkscape:collect="always" inkscape:deskcolor="#d1d1d1"
id="linearGradient71928"><stop showgrid="false"
style="stop-color:#4b0005;stop-opacity:1;" inkscape:zoom="1.4142136"
offset="0" inkscape:cx="219.91021"
id="stop71924" /><stop inkscape:cy="232.63813"
style="stop-color:#8f000c;stop-opacity:1;" inkscape:window-width="3840"
offset="1" inkscape:window-height="2054"
id="stop71926" /></linearGradient><linearGradient inkscape:window-x="2869"
inkscape:collect="always" inkscape:window-y="-11"
id="linearGradient71920"><stop inkscape:window-maximized="1"
style="stop-color:#4b0005;stop-opacity:1;" inkscape:current-layer="XMLID_4_" />
offset="0" <style
id="stop71916" /><stop type="text/css"
style="stop-color:#8f000c;stop-opacity:1;" id="style150">
offset="1" .st0{fill:#FFFFFF;}
id="stop71918" /></linearGradient><linearGradient .st1{fill:#C02223;}
inkscape:collect="always" .st2{fill:#882425;}
id="linearGradient69598"><stop .st3{fill:url(#XMLID_5_);}
style="stop-color:#6a0007;stop-opacity:1;" .st4{fill:url(#XMLID_7_);}
offset="0" </style>
id="stop69594" /><stop
style="stop-color:#b8000f;stop-opacity:1;" <g
offset="1" id="XMLID_4_">
id="stop69596" /></linearGradient><linearGradient <path
inkscape:collect="always" id="XMLID_131_"
id="linearGradient46361"><stop class="st1"
style="stop-color:#f7f6f8;stop-opacity:1;" d="M 347.01402,14.355825 98.978019,69.02261 C 73.825483,74.547445 55.942464,96.792175 55.942464,122.52628 v 315.06096 c 0,22.39012 16.719895,41.14548 38.819234,43.76251 L 224.8861,498.36042 339.48636,384.26465 455.76603,265.15425 453.73057,84.870162 C 453.43979,62.916214 433.08513,46.632491 411.71274,51.284984 l -28.78729,6.251786 0.14539,-13.666697 C 383.36162,24.678542 365.62399,10.284894 347.01402,14.355825 Z"
offset="0" sodipodi:nodetypes="ccssccccccccc"
id="stop46359" /><stop style="stroke-width:1.45391" /><path
style="stop-color:#b7b7b5;stop-opacity:1;" id="XMLID_117_"
offset="1" class="st2"
id="stop46357" /></linearGradient><linearGradient d="m 383.21622,57.53677 v 285.8375 L 456.05681,265.00885 454.02135,78.763767 C 453.87595,59.863016 436.28372,45.905539 417.81914,49.97647 Z"
inkscape:collect="always" style="stroke-width:1.45391" /><polygon
id="linearGradient40554"><stop id="XMLID_18_"
style="stop-color:#f4f2f4;stop-opacity:1;" class="st3"
offset="0" points="234.7,422.6 368.5,387.7 393.5,262.2 "
id="stop40550" /><stop style="fill:url(#XMLID_5_)"
style="stop-color:#57767b;stop-opacity:1;" transform="matrix(1.4556308,0,0,1.4548265,-116.73161,-116.45231)" />
offset="1" <linearGradient
id="stop40552" /></linearGradient><linearGradient id="XMLID_7_"
inkscape:collect="always" gradientUnits="userSpaceOnUse"
id="linearGradient39095"><stop x1="223.0838"
style="stop-color:#285459;stop-opacity:1;" y1="372.7559"
offset="0" x2="241.4174"
id="stop39093" /><stop y2="114.557"
style="stop-color:#a6b6af;stop-opacity:1;" gradientTransform="matrix(1.4539039,0,0,1.4539039,-116.19976,-116.20474)">
offset="1" <stop
id="stop39091" /></linearGradient><linearGradient offset="0"
inkscape:collect="always" style="stop-color:#DCF1F3"
id="linearGradient36672"><stop id="stop163" />
style="stop-color:#da453f;stop-opacity:1;" <stop
offset="0" offset="1"
id="stop36668" /><stop style="stop-color:#C2C2C9"
style="stop-color:#a60008;stop-opacity:1;" id="stop165" />
offset="1" </linearGradient>
id="stop36670" /></linearGradient><linearGradient <path
inkscape:collect="always" id="XMLID_6_"
id="linearGradient19524"><stop class="st4"
style="stop-color:#3a4b4f;stop-opacity:1;" d="m 282.89686,214.84917 c 0,0 -22.24473,-28.93269 -38.67384,-36.78377 -10.46811,-4.94327 -26.02489,-6.83335 -38.23768,-0.72695 -18.02841,9.0142 -19.91848,34.31213 -3.34397,44.34406 3.92553,2.47165 9.15959,4.50711 15.99294,6.10641 36.63838,8.43264 97.12077,25.87949 89.70587,96.10304 0,0 -4.21633,65.86185 -73.56753,73.42215 -12.2128,1.30851 -24.57098,0.43617 -36.493,-2.32625 -16.42911,-3.63476 -45.50719,-11.04967 -59.75545,-19.91849 l -2.61703,-75.16682 h 6.97875 c 0,0 13.81208,33.43978 53.06749,49.57812 7.26952,2.90781 15.26599,4.07093 22.97168,2.90781 9.74116,-1.45391 21.22699,-6.68796 25.87949,-22.53551 0,0 7.85108,-23.11707 -32.85823,-35.76604 -32.56744,-10.17733 -63.24481,-20.64543 -75.89378,-54.95757 -5.961,-16.28371 -6.97874,-34.31212 -2.90781,-51.61358 5.37944,-22.53551 20.79082,-54.23062 64.40794,-67.89732 0,0 57.28381,-15.55677 96.53922,5.52484 l -1.74468,89.70587 z"
offset="0" style="fill:url(#XMLID_7_);stroke-width:1.45391" />
id="stop19522" /><stop </g>
style="stop-color:#617979;stop-opacity:0.97461927;" </svg>
offset="1"
id="stop19520" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient17996"><stop
style="stop-color:#401016;stop-opacity:1;"
offset="0"
id="stop17994" /><stop
style="stop-color:#761f19;stop-opacity:1;"
offset="1"
id="stop17992" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient15569"><stop
style="stop-color:#8ea8ad;stop-opacity:1;"
offset="0"
id="stop15565" /><stop
style="stop-color:#e9e7eb;stop-opacity:1;"
offset="1"
id="stop15567" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient15557"><stop
style="stop-color:#9b0e11;stop-opacity:1;"
offset="0"
id="stop15553" /><stop
style="stop-color:#370707;stop-opacity:1;"
offset="1"
id="stop15555" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15557"
id="linearGradient15559"
x1="120.06672"
y1="63.25761"
x2="135.16347"
y2="78.078682"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15569"
id="linearGradient15571"
x1="127.97037"
y1="101.66144"
x2="133.88971"
y2="104.77026"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient17996"
id="linearGradient17998"
x1="117.9284"
y1="86.055084"
x2="130.67392"
y2="76.945541"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19524"
id="linearGradient19528"
x1="130.98172"
y1="82.402977"
x2="135.72115"
y2="86.45166"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient36672"
id="linearGradient36674"
x1="63.797714"
y1="74.81752"
x2="96.636673"
y2="120.29293"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient39095"
id="linearGradient39097"
x1="120.54506"
y1="124.76902"
x2="128.04152"
y2="126.0704"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient40554"
id="linearGradient40556"
x1="113.84585"
y1="114.04285"
x2="119.65858"
y2="128.50244"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient46361"
id="linearGradient46363"
x1="73.993439"
y1="114.13906"
x2="94.845322"
y2="71.247383"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient69598"
id="linearGradient69600"
x1="95.854446"
y1="114.66749"
x2="103.77842"
y2="120.1887"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient71920"
id="linearGradient71922"
x1="98.580376"
y1="87.186958"
x2="118.09738"
y2="101.19449"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient71928"
id="linearGradient71930"
x1="78.278267"
y1="97.433273"
x2="92.313202"
y2="104.33479"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient72198"
id="linearGradient72200"
x1="125.76636"
y1="138.46817"
x2="123.3327"
y2="126.03291"
gradientUnits="userSpaceOnUse" /></defs><g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="background"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><rect
style="display:inline;fill:#ccd6d7;fill-opacity:1;stroke:none;stroke-width:4.13755;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.2"
id="rect72067"
width="99.481979"
height="94.999512"
x="51.476147"
y="44.680138" /></g><g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="shadow"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient72200);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 84.146049,134.73858 c 0,0 11.721038,2.48294 17.938661,2.91673 6.21763,0.43378 14.75251,0.59994 22.41237,-0.43379 8.01008,-1.081 13.19907,-2.22733 14.50043,-2.66112 1.30136,-0.43379 4.00784,-1.24297 4.15244,-2.25514 0.1446,-1.01217 -3.4703,-2.74733 -6.21763,-3.32571 -2.74732,-0.57838 -12.72444,-1.44596 -14.89337,-1.44596 -2.16894,0 -37.892901,7.20499 -37.892901,7.20499 z"
id="path72192"
sodipodi:nodetypes="cssssssc" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Origami"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient40556);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 84.460552,134.86721 35.165798,-6.85679 16.15467,-42.7383 z"
id="path960"
sodipodi:nodetypes="cccc" /><path
style="fill:url(#linearGradient15571);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 135.71493,85.428056 0.3984,45.049024 -9.66213,-20.46173 z"
id="path964"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient39097);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 119.60518,128.00293 16.5337,2.48693 -9.68769,-20.5512 z"
id="path966"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient15559);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 118.42209,57.022622 12.70423,-2.766809 -0.0785,25.087175 -12.43878,-13.376518 z"
id="path968"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;fill:url(#linearGradient19528);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 135.72114,85.386768 -4.84219,-6.459493 0.0305,11.126604 z"
id="path970"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient17998);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 119.10273,65.682415 11.96883,13.44935 -0.0899,10.819868 -11.88577,11.430427 z"
id="path972"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;fill:url(#linearGradient36674);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 62.145635,130.15618 62.043392,65.435258 c 0,0 0.179321,-2.778132 1.89516,-4.036097 1.874923,-1.374597 4.341768,-1.894096 4.341768,-1.894096 l 50.91788,-11.349167 -0.0113,53.144272 -34.733274,33.56547 z"
id="path958"
sodipodi:nodetypes="ccsccccc" /></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Letter"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient69600);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 94.780881,91.406803 16.870379,17.074877 -23.723345,23.00249 -18.122131,-17.99816 5.497473,-2.80607 18.404054,-0.0511 2.35163,-8.23071 z"
id="path54894"
sodipodi:nodetypes="cccccccc" /><path
style="display:inline;fill:url(#linearGradient71930);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 72.440405,92.224764 16.15467,15.745686 4.089788,-6.79927 z"
id="path54892" /><path
style="display:inline;fill:url(#linearGradient71922);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 95.138739,84.965385 1.124691,-14.109776 22.92453,22.286787 0.008,8.164604 -3.28863,3.16649 z"
id="path54890"
sodipodi:nodetypes="cccccc"
inkscape:label="path54890" /><path
style="display:inline;fill:url(#linearGradient46363);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 95.138739,84.965385 h 1.226936 l -0.05112,-14.109776 c 0,0 -5.776827,-3.220709 -12.167126,-2.40275 -6.390296,0.817957 -8.151582,2.1248 -10.58233,4.396523 -1.90229,1.777838 -2.913974,3.527446 -3.987546,7.157132 -0.512646,1.733226 -0.281963,5.988892 0.613471,8.537436 0.664591,1.891528 2.453873,4.294281 4.958868,6.134686 2.662335,1.956002 8.281825,3.527443 8.281825,3.527443 0,0 5.134614,1.887351 5.572338,4.294281 0.308137,1.69437 -0.102243,3.22071 -1.635914,4.95887 -1.258314,1.42609 -3.62969,1.99377 -6.288054,1.07357 -2.658364,-0.92021 -6.139514,-3.85065 -7.106009,-4.90775 -0.817958,-0.89464 -2.820665,-3.06173 -2.890231,-4.294021 -0.01209,-0.214205 -1.229505,-0.0963 -1.229505,-0.0963 l -0.0723,14.256941 5.879073,2.24938 c 0,0 5.214483,1.78929 8.946415,1.43143 3.731934,-0.35786 7.617235,-0.51122 11.604778,-5.16336 3.987542,-4.65213 3.680812,-12.831715 2.147141,-15.899056 -1.533673,-3.067344 -3.561212,-6.138812 -10.480087,-8.281826 -5.776829,-1.789283 -7.872846,-3.01622 -8.128458,-4.396524 -0.255611,-1.380305 0.0091,-4.253646 2.760607,-5.214481 3.220711,-1.124693 5.623462,-0.05112 7.05489,1.12469 1.431425,1.175817 5.572339,5.623462 5.572339,5.623462 z"
id="path46355"
sodipodi:nodetypes="cccssssscssssscccssssssscc" /></g></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -15,13 +15,13 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
DOCKER_ENABLE_SECURITY: "true" DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite UI_APPNAME: Stirling-PDF-Lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest

View File

@@ -20,7 +20,7 @@ services:
environment: environment:
DOCKER_ENABLE_SECURITY: "false" DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite UI_APPNAME: Stirling-PDF-Lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest

View File

@@ -15,13 +15,13 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
DOCKER_ENABLE_SECURITY: "true" DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
UI_APPNAMENAVBAR: Stirling-PDF Latest UI_APPNAMENAVBAR: Stirling-PDF Latest

View File

@@ -15,13 +15,13 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
DOCKER_ENABLE_SECURITY: "true" DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true" SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite UI_APPNAME: Stirling-PDF-Lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest

View File

@@ -20,7 +20,7 @@ services:
environment: environment:
DOCKER_ENABLE_SECURITY: "false" DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Ultra-lite UI_APPNAME: Stirling-PDF-Ultra-lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Ultra-lite Latest UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Ultra-lite Latest
UI_APPNAMENAVBAR: Stirling-PDF-Ultra-lite Latest UI_APPNAMENAVBAR: Stirling-PDF-Ultra-lite Latest

View File

@@ -15,13 +15,13 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:
DOCKER_ENABLE_SECURITY: "false" DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false" SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en_US SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
UI_APPNAMENAVBAR: Stirling-PDF Latest UI_APPNAMENAVBAR: Stirling-PDF Latest

182
gradlew.bat vendored
View File

@@ -1,91 +1,91 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE% exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

View File

@@ -6,7 +6,8 @@
"parameters": { "parameters": {
"horizontalDivisions": 2, "horizontalDivisions": 2,
"verticalDivisions": 2, "verticalDivisions": 2,
"fileInput": "automated" "fileInput": "automated",
"merge": false
} }
}, },
{ {
@@ -30,4 +31,4 @@
}, },
"outputDir": "{outputFolder}", "outputDir": "{outputFolder}",
"outputFileName": "{filename}" "outputFileName": "{filename}"
} }

View File

@@ -16,7 +16,7 @@ public class PropSync {
Map<String, String> enProps = linesToProps(enLines); Map<String, String> enProps = linesToProps(enLines);
for (File file : files) { for (File file : files) {
if (!file.getName().equals("messages_en_GB.properties")) { if (!"messages_en_GB.properties".equals(file.getName())) {
System.out.println("Processing file: " + file.getName()); System.out.println("Processing file: " + file.getName());
List<String> lines; List<String> lines;
try { try {

View File

@@ -1,37 +0,0 @@
import cv2
import sys
import argparse
import numpy as np
def is_blank_image(image_path, threshold=10, white_percent=99, white_value=255, blur_size=5):
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
print(f"Error: Unable to read the image file: {image_path}")
return False
# Apply Gaussian blur to reduce noise
blurred_image = cv2.GaussianBlur(image, (blur_size, blur_size), 0)
_, thresholded_image = cv2.threshold(blurred_image, white_value - threshold, white_value, cv2.THRESH_BINARY)
# Calculate the percentage of white pixels in the thresholded image
white_pixels = np.sum(thresholded_image == white_value)
white_pixel_percentage = (white_pixels / thresholded_image.size) * 100
print(f"Page has white pixel percent of {white_pixel_percentage}")
return white_pixel_percentage >= white_percent
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Detect if an image is considered blank or not.')
parser.add_argument('image_path', help='The path to the image file.')
parser.add_argument('-t', '--threshold', type=int, default=10, help='Threshold for determining white pixels. The default value is 10.')
parser.add_argument('-w', '--white_percent', type=float, default=99, help='The percentage of white pixels for an image to be considered blank. The default value is 99.')
args = parser.parse_args()
blank = is_blank_image(args.image_path, args.threshold, args.white_percent)
# Return code 1: The image is considered blank.
# Return code 0: The image is not considered blank.
sys.exit(int(blank))

View File

@@ -4,7 +4,7 @@ if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
if [ ! -f app-security.jar ]; then if [ ! -f app-security.jar ]; then
echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar" echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar
# If the first download attempt failed, try with the 'v' prefix # If the first download attempt failed, try with the 'v' prefix
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar" echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar"
@@ -14,6 +14,8 @@ if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
if [ $? -eq 0 ]; then # checks if curl was successful if [ $? -eq 0 ]; then # checks if curl was successful
rm -f app.jar rm -f app.jar
ln -s app-security.jar app.jar ln -s app-security.jar app.jar
chown stirlingpdfuser:stirlingpdfgroup app.jar
chmod 755 app.jar
fi fi
fi fi
fi fi

View File

@@ -1,6 +1,24 @@
#!/bin/sh #!/bin/sh
# Update the user and group IDs as per environment variables
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
usermod -o -u "$PUID" stirlingpdfuser
fi
if [ ! -z "$PGID" ] && [ "$PGID" != "$(id -g stirlingpdfgroup)" ]; then
groupmod -o -g "$PGID" stirlingpdfgroup
fi
umask "$UMASK"
echo "Setting permissions and ownership for necessary directories..."
chown -R stirlingpdfuser:stirlingpdfgroup /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
apk add --no-cache calibre@testing
fi
/scripts/download-security-jar.sh /scripts/download-security-jar.sh
# Run the main command # Run the main command
exec "$@" exec su-exec stirlingpdfuser "$@"

View File

@@ -2,25 +2,57 @@
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files # Copy the original tesseract-ocr files to the volume directory without overwriting existing files
echo "Copying original files without overwriting existing files" echo "Copying original files without overwriting existing files"
mkdir -p /usr/share/tesseract-ocr mkdir -p /usr/share/tessdata
cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr cp -rn /usr/share/tessdata-original/* /usr/share/tessdata
if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then
cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tesseract-ocr/5/tessdata/ || true; cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata || true;
fi fi
if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
fi
# Update the user and group IDs as per environment variables
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
usermod -o -u "$PUID" stirlingpdfuser
fi
if [ ! -z "$PGID" ] && [ "$PGID" != "$(id -g stirlingpdfgroup)" ]; then
groupmod -o -g "$PGID" stirlingpdfgroup
fi
umask "$UMASK"
echo "Setting permissions and ownership for necessary directories..."
chown -R stirlingpdfuser:stirlingpdfgroup /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles
# Check if TESSERACT_LANGS environment variable is set and is not empty # Check if TESSERACT_LANGS environment variable is set and is not empty
if [[ -n "$TESSERACT_LANGS" ]]; then if [[ -n "$TESSERACT_LANGS" ]]; then
# Convert comma-separated values to a space-separated list # Convert comma-separated values to a space-separated list
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ') LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
# Install each language pack # Install each language pack
for LANG in $LANGS; do for LANG in $LANGS; do
apt-get install -y "tesseract-ocr-$LANG" if [[ $LANG =~ $pattern ]]; then
apk add --no-cache "tesseract-ocr-data-$LANG"
else
echo "Skipping invalid language code"
fi
done done
fi fi
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
apk add --no-cache calibre@testing
fi
/scripts/download-security-jar.sh /scripts/download-security-jar.sh
# Run the main command # Run the main command and switch to stirling user for rest of run
exec "$@" exec su-exec stirlingpdfuser "$@"

View File

@@ -2,7 +2,7 @@ import argparse
import sys import sys
import cv2 import cv2
import numpy as np import numpy as np
import os import os
def find_photo_boundaries(image, background_color, tolerance=30, min_area=10000, min_contour_area=500): def find_photo_boundaries(image, background_color, tolerance=30, min_area=10000, min_contour_area=500):
mask = cv2.inRange(image, background_color - tolerance, background_color + tolerance) mask = cv2.inRange(image, background_color - tolerance, background_color + tolerance)
@@ -49,9 +49,9 @@ def auto_rotate(image, angle_threshold=1):
angles = [] angles = []
for rho, theta in lines[:, 0]: for rho, theta in lines[:, 0]:
angles.append((theta * 180) / np.pi - 90) angles.append((theta * 180) / np.pi - 90)
angle = np.median(angles) angle = np.median(angles)
if abs(angle) < angle_threshold: if abs(angle) < angle_threshold:
return image return image
@@ -65,16 +65,16 @@ def auto_rotate(image, angle_threshold=1):
def crop_borders(image, border_color, tolerance=30): def crop_borders(image, border_color, tolerance=30):
mask = cv2.inRange(image, border_color - tolerance, border_color + tolerance) mask = cv2.inRange(image, border_color - tolerance, border_color + tolerance)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) == 0: if len(contours) == 0:
return image return image
largest_contour = max(contours, key=cv2.contourArea) largest_contour = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(largest_contour) x, y, w, h = cv2.boundingRect(largest_contour)
return image[y:y+h, x:x+w] return image[y:y+h, x:x+w]
def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0): def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0):
image = cv2.imread(input_file) image = cv2.imread(input_file)
background_color = estimate_background_color(image) background_color = estimate_background_color(image)
@@ -110,7 +110,7 @@ if __name__ == "__main__":
parser.add_argument("--min_contour_area", type=int, default=500, help="Sets the minimum contour area threshold for a photo (default: 500).") parser.add_argument("--min_contour_area", type=int, default=500, help="Sets the minimum contour area threshold for a photo (default: 500).")
parser.add_argument("--angle_threshold", type=int, default=10, help="Sets the minimum absolute angle required for the image to be rotated (default: 10).") parser.add_argument("--angle_threshold", type=int, default=10, help="Sets the minimum absolute angle required for the image to be rotated (default: 10).")
parser.add_argument("--border_size", type=int, default=0, help="Sets the size of the border added and removed to prevent white borders in the output (default: 0).") parser.add_argument("--border_size", type=int, default=0, help="Sets the size of the border added and removed to prevent white borders in the output (default: 0).")
args = parser.parse_args() args = parser.parse_args()
split_photos(args.input_file, args.output_directory, tolerance=args.tolerance, min_area=args.min_area, min_contour_area=args.min_contour_area, angle_threshold=args.angle_threshold, border_size=args.border_size) split_photos(args.input_file, args.output_directory, tolerance=args.tolerance, min_area=args.min_area, min_contour_area=args.min_contour_area, angle_threshold=args.angle_threshold, border_size=args.border_size)

View File

@@ -6,6 +6,8 @@ import java.net.Socket;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import io.github.pixee.security.SystemCommand;
public class LibreOfficeListener { public class LibreOfficeListener {
private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes
@@ -44,7 +46,7 @@ public class LibreOfficeListener {
} }
// Start the listener process // Start the listener process
process = Runtime.getRuntime().exec("unoconv --listener"); process = SystemCommand.runCommand(Runtime.getRuntime(), "unoconv --listener");
lastActivityTime = System.currentTimeMillis(); lastActivityTime = System.currentTimeMillis();
// Start a background thread to monitor the activity timeout // Start a background thread to monitor the activity timeout

View File

@@ -1,48 +1,67 @@
package stirling.software.SPDF; package stirling.software.SPDF;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.model.ApplicationProperties;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
public class SPdfApplication { public class SPdfApplication {
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
@Autowired private Environment env; @Autowired private Environment env;
@Autowired ApplicationProperties applicationProperties;
private static String serverPortStatic;
@Value("${server.port:8080}")
public void setServerPortStatic(String port) {
SPdfApplication.serverPortStatic = port;
}
@PostConstruct @PostConstruct
public void init() { public void init() {
// Check if the BROWSER_OPEN environment variable is set to true // Check if the BROWSER_OPEN environment variable is set to true
String browserOpenEnv = env.getProperty("BROWSER_OPEN"); String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) { if (browserOpen) {
try { try {
String url = "http://localhost:" + getPort(); String url = "http://localhost:" + getNonStaticPort();
String os = System.getProperty("os.name").toLowerCase(); String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime(); Runtime rt = Runtime.getRuntime();
if (os.contains("win")) { if (os.contains("win")) {
// For Windows // For Windows
rt.exec("rundll32 url.dll,FileProtocolHandler " + url); SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("Error opening browser: {}", e.getMessage());
} }
} }
logger.info("Running configs {}", applicationProperties.toString());
} }
public static void main(String[] args) { public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class); SpringApplication app = new SpringApplication(SPdfApplication.class);
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
if (Files.exists(Paths.get("configs/settings.yml"))) { if (Files.exists(Paths.get("configs/settings.yml"))) {
@@ -50,7 +69,7 @@ public class SPdfApplication {
Collections.singletonMap( Collections.singletonMap(
"spring.config.additional-location", "file:configs/settings.yml")); "spring.config.additional-location", "file:configs/settings.yml"));
} else { } else {
System.out.println( logger.warn(
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
} }
app.run(args); app.run(args);
@@ -58,24 +77,30 @@ public class SPdfApplication {
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block Thread.currentThread().interrupt();
e.printStackTrace(); throw new RuntimeException("Thread interrupted while sleeping", e);
} }
GeneralUtils.createDir("customFiles/static/"); try {
GeneralUtils.createDir("customFiles/templates/"); Files.createDirectories(Path.of("customFiles/static/"));
Files.createDirectories(Path.of("customFiles/templates/"));
System.out.println("Stirling-PDF Started."); } catch (Exception e) {
logger.error("Error creating directories: {}", e.getMessage());
String url = "http://localhost:" + getPort(); }
System.out.println("Navigate to " + url); printStartupLogs();
} }
public static String getPort() { private static void printStartupLogs() {
String port = System.getProperty("local.server.port"); logger.info("Stirling-PDF Started.");
if (port == null || port.isEmpty()) { String url = "http://localhost:" + getStaticPort();
port = "8080"; logger.info("Navigate to {}", url);
} }
return port;
public static String getStaticPort() {
return serverPortStatic;
}
public String getNonStaticPort() {
return serverPortStatic;
} }
} }

View File

@@ -1,8 +1,15 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@@ -24,8 +31,15 @@ public class AppConfig {
@Bean(name = "appVersion") @Bean(name = "appVersion")
public String appVersion() { public String appVersion() {
String version = getClass().getPackage().getImplementationVersion(); Resource resource = new ClassPathResource("version.properties");
return (version != null) ? version : "0.0.0"; Properties props = new Properties();
try {
props.load(resource.getInputStream());
return props.getProperty("version");
} catch (IOException e) {
e.printStackTrace();
}
return "0.0.0";
} }
@Bean(name = "homeText") @Bean(name = "homeText")
@@ -57,4 +71,18 @@ public class AppConfig {
if (appName == null) appName = System.getenv("rateLimit"); if (appName == null) appName = System.getenv("rateLimit");
return (appName != null) ? Boolean.valueOf(appName) : false; return (appName != null) ? Boolean.valueOf(appName) : false;
} }
@Bean(name = "RunningInDocker")
public boolean runningInDocker() {
return Files.exists(Paths.get("/.dockerenv"));
}
@Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookAndHtmlFormatsInstalled() {
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
if (installOps == null) {
installOps = System.getenv("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
}
return "true".equalsIgnoreCase(installOps);
}
} }

View File

@@ -79,12 +79,22 @@ public class ConfigInitializer
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
}; };
Function<String, Integer> getIndentationLevel =
line -> {
int count = 0;
for (char ch : line.toCharArray()) {
if (ch == ' ') count++;
else break;
}
return count;
};
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
for (String line : templateLines) { for (String line : templateLines) {
String key = extractKey.apply(line); String key = extractKey.apply(line);
if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) { if ("AutomaticallyGenerated:".equalsIgnoreCase(line.trim())) {
insideAutoGenerated = true; insideAutoGenerated = true;
mergedLines.add(line); mergedLines.add(line);
continue; continue;
@@ -134,10 +144,77 @@ public class ConfigInitializer
.map(extractKey) .map(extractKey)
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
if (!isPresentInTemplate && !isCommented.apply(userLine)) { if (!isPresentInTemplate && !isCommented.apply(userLine)) {
mergedLines.add(userLine); if (!childOfTemplateEntry(
isCommented,
extractKey,
getIndentationLevel,
userLines,
userLine,
templateLines)) {
// check if userLine is a child of a entry within templateLines or not, if child
// of parent in templateLines then dont add to mergedLines, if anything else
// then add
mergedLines.add(userLine);
}
} }
} }
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
} }
// New method to check if a userLine is a child of an entry in templateLines
boolean childOfTemplateEntry(
Function<String, Boolean> isCommented,
Function<String, String> extractKey,
Function<String, Integer> getIndentationLevel,
List<String> userLines,
String userLine,
List<String> templateLines) {
String userKey = extractKey.apply(userLine).trim();
int userIndentation = getIndentationLevel.apply(userLine);
// Start by assuming the line is not a child of an entry in templateLines
boolean isChild = false;
// Iterate backwards through userLines from the current line to find any parent
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
String potentialParentLine = userLines.get(i);
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
// Check if we've reached a potential parent based on indentation
if (parentIndentation < userIndentation) {
String parentKey = extractKey.apply(potentialParentLine).trim();
// Now, check if this potential parent or any of its parents exist in templateLines
boolean parentExistsInTemplate =
templateLines.stream()
.filter(line -> !isCommented.apply(line)) // Skip commented lines
.anyMatch(
templateLine -> {
String templateKey =
extractKey.apply(templateLine).trim();
return parentKey.equalsIgnoreCase(templateKey);
});
if (!parentExistsInTemplate) {
// If the parent does not exist in template, check the next level parent
userIndentation =
parentIndentation; // Update userIndentation to the parent's indentation
// for next iteration
if (parentIndentation == 0) {
// If we've reached the top-level parent and it's not in template, the
// original line is considered not a child
isChild = false;
break;
}
} else {
// If any parent exists in template, the original line is considered a child
isChild = true;
break;
}
}
}
return isChild; // Return true if the line is not a child of any entry in templateLines
}
} }

View File

@@ -9,11 +9,14 @@ import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Service @Service
@DependsOn({"bookAndHtmlFormatsInstalled"})
public class EndpointConfiguration { public class EndpointConfiguration {
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
@@ -21,9 +24,14 @@ public class EndpointConfiguration {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private boolean bookAndHtmlFormatsInstalled;
@Autowired @Autowired
public EndpointConfiguration(ApplicationProperties applicationProperties) { public EndpointConfiguration(
ApplicationProperties applicationProperties,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
init(); init();
processEnvironmentConfigs(); processEnvironmentConfigs();
} }
@@ -132,7 +140,6 @@ public class EndpointConfiguration {
// CLI // CLI
addEndpointToGroup("CLI", "compress-pdf"); addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans"); addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks");
addEndpointToGroup("CLI", "repair"); addEndpointToGroup("CLI", "repair");
addEndpointToGroup("CLI", "pdf-to-pdfa"); addEndpointToGroup("CLI", "pdf-to-pdfa");
addEndpointToGroup("CLI", "file-to-pdf"); addEndpointToGroup("CLI", "file-to-pdf");
@@ -145,6 +152,12 @@ public class EndpointConfiguration {
addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "ocr-pdf");
addEndpointToGroup("CLI", "html-to-pdf"); addEndpointToGroup("CLI", "html-to-pdf");
addEndpointToGroup("CLI", "url-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf");
addEndpointToGroup("CLI", "book-to-pdf");
addEndpointToGroup("CLI", "pdf-to-book");
// Calibre
addEndpointToGroup("Calibre", "book-to-pdf");
addEndpointToGroup("Calibre", "pdf-to-book");
// python // python
addEndpointToGroup("Python", "extract-image-scans"); addEndpointToGroup("Python", "extract-image-scans");
@@ -204,6 +217,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-by-size-or-count"); addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections"); addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", "remove-blanks");
// Javascript // Javascript
addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "pdf-organizer");
@@ -215,7 +229,9 @@ public class EndpointConfiguration {
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
if (!bookAndHtmlFormatsInstalled) {
groupsToRemove.add("Calibre");
}
if (endpointsToRemove != null) { if (endpointsToRemove != null) {
for (String endpoint : endpointsToRemove) { for (String endpoint : endpointsToRemove) {
disableEndpoint(endpoint.trim()); disableEndpoint(endpoint.trim());

View File

@@ -1,6 +1,7 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
@@ -12,15 +13,19 @@ import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.User;
@Component @Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired private final LoginAttemptService loginAttemptService; @Autowired private final LoginAttemptService loginAttemptService;
@Autowired @Autowired private final UserService userService; // Inject the UserService
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
public CustomAuthenticationFailureHandler(
LoginAttemptService loginAttemptService, UserService userService) {
this.loginAttemptService = loginAttemptService; this.loginAttemptService = loginAttemptService;
this.userService = userService;
} }
@Override @Override
@@ -33,17 +38,28 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
logger.error("Failed login attempt from IP: " + ip); logger.error("Failed login attempt from IP: " + ip);
String username = request.getParameter("username"); String username = request.getParameter("username");
if (loginAttemptService.loginAttemptCheck(username)) { if (!isDemoUser(username)) {
setDefaultFailureUrl("/login?error=locked"); if (loginAttemptService.loginAttemptCheck(username)) {
} else {
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
setDefaultFailureUrl("/login?error=badcredentials");
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
setDefaultFailureUrl("/login?error=locked"); setDefaultFailureUrl("/login?error=locked");
} else {
if (exception.getClass().isAssignableFrom(LockedException.class)) {
setDefaultFailureUrl("/login?error=locked");
}
} }
} }
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
setDefaultFailureUrl("/login?error=badcredentials");
}
super.onAuthenticationFailure(request, response, exception); super.onAuthenticationFailure(request, response, exception);
} }
private boolean isDemoUser(String username) {
Optional<User> user = userService.findByUsername(username);
return user.isPresent()
&& user.get().getAuthorities().stream()
.anyMatch(authority -> "ROLE_DEMO_USER".equals(authority.getAuthority()));
}
} }

View File

@@ -9,6 +9,9 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@@ -18,6 +21,7 @@ import org.springframework.security.web.authentication.rememberme.PersistentToke
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@Configuration @Configuration
@@ -44,6 +48,11 @@ public class SecurityConfiguration {
@Autowired private FirstLoginFilter firstLoginFilter; @Autowired private FirstLoginFilter firstLoginFilter;
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@@ -53,6 +62,14 @@ public class SecurityConfiguration {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement(
sessionManagement ->
sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(3)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?logout=true"));
http.formLogin( http.formLogin(
formLogin -> formLogin ->
formLogin formLogin
@@ -62,7 +79,7 @@ public class SecurityConfiguration {
.defaultSuccessUrl("/") .defaultSuccessUrl("/")
.failureHandler( .failureHandler(
new CustomAuthenticationFailureHandler( new CustomAuthenticationFailureHandler(
loginAttemptService)) loginAttemptService, userService))
.permitAll()) .permitAll())
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
.logout( .logout(
@@ -71,7 +88,19 @@ public class SecurityConfiguration {
new AntPathRequestMatcher("/logout")) new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true") .logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true) // Invalidate session .invalidateHttpSession(true) // Invalidate session
.deleteCookies("JSESSIONID", "remember-me")) .deleteCookies("JSESSIONID", "remember-me")
.addLogoutHandler(
(request, response, authentication) -> {
HttpSession session =
request.getSession(
false);
if (session != null) {
String sessionId = session.getId();
sessionRegistry()
.removeSessionInformation(
sessionId);
}
}))
.rememberMe( .rememberMe(
rememberMeConfigurer -> rememberMeConfigurer ->
rememberMeConfigurer // Use the configurator directly rememberMeConfigurer // Use the configurator directly

View File

@@ -1,13 +1,14 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -37,9 +38,7 @@ public class CropController {
description = description =
"This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException { public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
PDDocument sourceDocument =
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
PDDocument newDocument = new PDDocument(); PDDocument newDocument = new PDDocument();
@@ -53,7 +52,8 @@ public class CropController {
// Create a new page with the size of the source page // Create a new page with the size of the source page
PDPage newPage = new PDPage(sourcePage.getMediaBox()); PDPage newPage = new PDPage(sourcePage.getMediaBox());
newDocument.addPage(newPage); newDocument.addPage(newPage);
PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage); PDPageContentStream contentStream =
new PDPageContentStream(newDocument, newPage, AppendMode.OVERWRITE, true, true);
// Import the source page as a form XObject // Import the source page as a form XObject
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);

View File

@@ -1,16 +1,17 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.PDFMergerUtility; import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -27,6 +28,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -36,7 +38,7 @@ public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class); private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException { public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
PDDocument mergedDoc = new PDDocument(); PDDocument mergedDoc = new PDDocument();
for (PDDocument doc : documents) { for (PDDocument doc : documents) {
for (PDPage page : doc.getPages()) { for (PDPage page : doc.getPages()) {
@@ -84,8 +86,8 @@ public class MergeController {
}; };
case "byPDFTitle": case "byPDFTitle":
return (file1, file2) -> { return (file1, file2) -> {
try (PDDocument doc1 = PDDocument.load(file1.getInputStream()); try (PDDocument doc1 = Loader.loadPDF(file1.getBytes());
PDDocument doc2 = PDDocument.load(file2.getInputStream())) { PDDocument doc2 = Loader.loadPDF(file2.getBytes())) {
String title1 = doc1.getDocumentInformation().getTitle(); String title1 = doc1.getDocumentInformation().getTitle();
String title2 = doc2.getDocumentInformation().getTitle(); String title2 = doc2.getDocumentInformation().getTitle();
return title1.compareTo(title2); return title1.compareTo(title2);
@@ -106,6 +108,7 @@ public class MergeController {
"This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO") "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form) public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
throws IOException { throws IOException {
List<File> filesToDelete = new ArrayList<File>();
try { try {
MultipartFile[] files = form.getFileInput(); MultipartFile[] files = form.getFileInput();
Arrays.sort(files, getSortComparator(form.getSortType())); Arrays.sort(files, getSortComparator(form.getSortType()));
@@ -113,20 +116,27 @@ public class MergeController {
PDFMergerUtility mergedDoc = new PDFMergerUtility(); PDFMergerUtility mergedDoc = new PDFMergerUtility();
ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream(); ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream();
for (MultipartFile file : files) { for (MultipartFile multipartFile : files) {
mergedDoc.addSource(new ByteArrayInputStream(file.getBytes())); File tempFile = GeneralUtils.convertMultipartFileToFile(multipartFile);
filesToDelete.add(tempFile);
mergedDoc.addSource(tempFile);
} }
mergedDoc.setDestinationFileName( mergedDoc.setDestinationFileName(
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
mergedDoc.setDestinationStream(docOutputstream); mergedDoc.setDestinationStream(docOutputstream);
mergedDoc.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
mergedDoc.mergeDocuments(null);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
docOutputstream.toByteArray(), mergedDoc.getDestinationFileName()); docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Error in merge pdf process", ex); logger.error("Error in merge pdf process", ex);
throw ex; throw ex;
} finally {
for (File file : filesToDelete) {
file.delete();
}
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import java.awt.Color;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -20,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -57,7 +59,7 @@ public class MultiPageLayoutController {
: (int) Math.sqrt(pagesPerSheet); : (int) Math.sqrt(pagesPerSheet);
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
PDDocument sourceDocument = PDDocument.load(file.getInputStream()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument newDocument = new PDDocument(); PDDocument newDocument = new PDDocument();
PDPage newPage = new PDPage(PDRectangle.A4); PDPage newPage = new PDPage(PDRectangle.A4);
newDocument.addPage(newPage); newDocument.addPage(newPage);
@@ -135,6 +137,7 @@ public class MultiPageLayoutController {
byte[] result = baos.toByteArray(); byte[] result = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
result, result,
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf"); Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_layoutChanged.pdf");
} }
} }

View File

@@ -3,11 +3,13 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -53,7 +56,7 @@ public class PdfOverlayController {
// "FixedRepeatOverlay" // "FixedRepeatOverlay"
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream()); try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes());
Overlay overlay = new Overlay()) { Overlay overlay = new Overlay()) {
Map<Integer, String> overlayGuide = Map<Integer, String> overlayGuide =
prepareOverlayGuide( prepareOverlayGuide(
@@ -74,7 +77,8 @@ public class PdfOverlayController {
overlay.overlay(overlayGuide).save(outputStream); overlay.overlay(overlayGuide).save(outputStream);
byte[] data = outputStream.toByteArray(); byte[] data = outputStream.toByteArray();
String outputFilename = String outputFilename =
baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(baseFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf"; // Remove file extension and append .pdf + "_overlayed.pdf"; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
@@ -131,10 +135,10 @@ public class PdfOverlayController {
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length; overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
} }
try (PDDocument overlayPdf = PDDocument.load(overlayFiles[overlayFileIndex])) { try (PDDocument overlayPdf = Loader.loadPDF(overlayFiles[overlayFileIndex])) {
PDDocument singlePageDocument = new PDDocument(); PDDocument singlePageDocument = new PDDocument();
singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay)); singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));
File tempFile = File.createTempFile("overlay-page-", ".pdf"); File tempFile = Files.createTempFile("overlay-page-", ".pdf").toFile();
singlePageDocument.save(tempFile); singlePageDocument.save(tempFile);
singlePageDocument.close(); singlePageDocument.close();
@@ -147,7 +151,7 @@ public class PdfOverlayController {
} }
private int getNumberOfPages(File file) throws IOException { private int getNumberOfPages(File file) throws IOException {
try (PDDocument doc = PDDocument.load(file)) { try (PDDocument doc = Loader.loadPDF(file)) {
return doc.getNumberOfPages(); return doc.getNumberOfPages();
} }
} }
@@ -159,7 +163,7 @@ public class PdfOverlayController {
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length]; File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
// Load the overlay document to check its page count // Load the overlay document to check its page count
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) { try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {
int overlayPageCount = overlayPdf.getNumberOfPages(); int overlayPageCount = overlayPdf.getNumberOfPages();
if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) { if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) {
overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath()); overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());
@@ -181,7 +185,7 @@ public class PdfOverlayController {
int repeatCount = counts[i]; int repeatCount = counts[i];
// Load the overlay document to check its page count // Load the overlay document to check its page count
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) { try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {
int overlayPageCount = overlayPdf.getNumberOfPages(); int overlayPageCount = overlayPdf.getNumberOfPages();
for (int j = 0; j < repeatCount; j++) { for (int j = 0; j < repeatCount; j++) {
for (int page = 0; page < overlayPageCount; page++) { for (int page = 0; page < overlayPageCount; page++) {

View File

@@ -2,8 +2,10 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -15,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -42,13 +45,15 @@ public class RearrangePagesPDFController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pagesToDelete = request.getPageNumbers(); String pagesToDelete = request.getPageNumbers();
PDDocument document = PDDocument.load(pdfFile.getBytes()); PDDocument document = Loader.loadPDF(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(","); String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove = List<Integer> pagesToRemove =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages(), true);
Collections.sort(pagesToRemove);
for (int i = pagesToRemove.size() - 1; i >= 0; i--) { for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i); int pageIndex = pagesToRemove.get(i);
@@ -56,7 +61,9 @@ public class RearrangePagesPDFController {
} }
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_removed_pages.pdf");
} }
private List<Integer> removeFirst(int totalPages) { private List<Integer> removeFirst(int totalPages) {
@@ -179,7 +186,7 @@ public class RearrangePagesPDFController {
String sortType = request.getCustomMode(); String sortType = request.getCustomMode();
try { try {
// Load the input PDF // Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream()); PDDocument document = Loader.loadPDF(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
@@ -188,7 +195,7 @@ public class RearrangePagesPDFController {
if (sortType != null && sortType.length() > 0) { if (sortType != null && sortType.length() > 0) {
newPageOrder = processSortTypes(sortType, totalPages); newPageOrder = processSortTypes(sortType, totalPages);
} else { } else {
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, true);
} }
logger.info("newPageOrder = " + newPageOrder); logger.info("newPageOrder = " + newPageOrder);
logger.info("totalPages = " + totalPages); logger.info("totalPages = " + totalPages);
@@ -210,7 +217,8 @@ public class RearrangePagesPDFController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_rearranged.pdf"); + "_rearranged.pdf");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed rearranging documents", e); logger.error("Failed rearranging documents", e);

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
@@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -37,7 +39,7 @@ public class RotationController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle(); Integer angle = request.getAngle();
// Load the PDF document // Load the PDF document
PDDocument document = PDDocument.load(pdfFile.getBytes()); PDDocument document = Loader.loadPDF(pdfFile.getBytes());
// Get the list of pages in the document // Get the list of pages in the document
PDPageTree pages = document.getPages(); PDPageTree pages = document.getPages();
@@ -48,6 +50,8 @@ public class RotationController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf"); Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_rotated.pdf");
} }
} }

View File

@@ -5,6 +5,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -66,7 +68,7 @@ public class ScalePagesController {
PDRectangle targetSize = sizeMap.get(targetPDRectangle); PDRectangle targetSize = sizeMap.get(targetPDRectangle);
PDDocument sourceDocument = PDDocument.load(file.getBytes()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument outputDocument = new PDDocument(); PDDocument outputDocument = new PDDocument();
int totalPages = sourceDocument.getNumberOfPages(); int totalPages = sourceDocument.getNumberOfPages();
@@ -83,7 +85,11 @@ public class ScalePagesController {
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true); outputDocument,
newPage,
PDPageContentStream.AppendMode.APPEND,
true,
true);
float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2; float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2; float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
@@ -107,6 +113,7 @@ public class ScalePagesController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), baos.toByteArray(),
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_scaled.pdf");
} }
} }

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@@ -11,6 +10,7 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -46,12 +47,16 @@ public class SplitPDFController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers(); String pages = request.getPageNumbers();
// open the pdf document // open the pdf document
InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
List<Integer> pageNumbers = request.getPageNumbersList(document); PDDocument document = Loader.loadPDF(file.getBytes());
if (!pageNumbers.contains(document.getNumberOfPages() - 1))
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
if (!pageNumbers.contains(document.getNumberOfPages() - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(document.getNumberOfPages() - 1); pageNumbers.add(document.getNumberOfPages() - 1);
}
logger.info( logger.info(
"Splitting PDF into pages: {}", "Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
@@ -83,7 +88,9 @@ public class SplitPDFController {
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file // loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) { for (int i = 0; i < splitDocumentsBoas.size(); i++) {

View File

@@ -9,10 +9,12 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
@@ -24,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -45,13 +48,26 @@ public class SplitPdfBySectionsController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = PDDocument.load(file.getInputStream()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
// Process the PDF based on split parameters // Process the PDF based on split parameters
int horiz = request.getHorizontalDivisions() + 1; int horiz = request.getHorizontalDivisions() + 1;
int verti = request.getVerticalDivisions() + 1; int verti = request.getVerticalDivisions() + 1;
boolean merge = request.isMerge();
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz); List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
if (merge) {
MergeController mergeController = new MergeController();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mergeController.mergeDocuments(splitDocuments).save(baos);
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(),
filename + "_split.pdf",
MediaType.APPLICATION_OCTET_STREAM);
}
for (PDDocument doc : splitDocuments) { for (PDDocument doc : splitDocuments) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
doc.save(baos); doc.save(baos);
@@ -62,7 +78,6 @@ public class SplitPdfBySectionsController {
sourceDocument.close(); sourceDocument.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
@@ -115,13 +130,13 @@ public class SplitPdfBySectionsController {
document, document.getPages().indexOf(originalPage)); document, document.getPages().indexOf(originalPage));
try (PDPageContentStream contentStream = try (PDPageContentStream contentStream =
new PDPageContentStream(subDoc, subPage)) { new PDPageContentStream(
subDoc, subPage, AppendMode.APPEND, true, true)) {
// Set clipping area and position // Set clipping area and position
float translateX = -subPageWidth * i; float translateX = -subPageWidth * i;
float translateY = height - subPageHeight * (verticalDivisions - j);
// Code for google Docs pdfs.. // float translateY = height - subPageHeight * (verticalDivisions - j);
// float translateY = -subPageHeight * (verticalDivisions - 1 - j); float translateY = -subPageHeight * (verticalDivisions - 1 - j);
contentStream.saveGraphicsState(); contentStream.saveGraphicsState();
contentStream.addRect(0, 0, subPageWidth, subPageHeight); contentStream.addRect(0, 0, subPageWidth, subPageHeight);

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -42,7 +44,7 @@ public class SplitPdfBySizeController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = PDDocument.load(file.getInputStream()); PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
// 0 = size, 1 = page count, 2 = doc count // 0 = size, 1 = page count, 2 = doc count
int type = request.getSplitType(); int type = request.getSplitType();
@@ -119,7 +121,9 @@ public class SplitPdfBySizeController {
sourceDocument.close(); sourceDocument.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {

View File

@@ -4,6 +4,7 @@ import java.awt.geom.AffineTransform;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -40,7 +41,7 @@ public class ToSinglePageController {
throws IOException { throws IOException {
// Load the source document // Load the source document
PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream()); PDDocument sourceDocument = Loader.loadPDF(request.getFileInput().getBytes());
// Calculate total height and max width // Calculate total height and max width
float totalHeight = 0; float totalHeight = 0;

View File

@@ -10,9 +10,13 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -20,13 +24,17 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UsernameAndPass;
@Controller @Controller
@Tag(name = "User", description = "User APIs")
@RequestMapping("/api/v1/user") @RequestMapping("/api/v1/user")
public class UserController { public class UserController {
@@ -34,61 +42,16 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register") @PostMapping("/register")
public String register( public String register(@ModelAttribute UsernameAndPass requestModel, Model model) {
@RequestParam String username, @RequestParam String password, Model model) { if (userService.usernameExists(requestModel.getUsername())) {
if (userService.usernameExists(username)) {
model.addAttribute("error", "Username already exists"); model.addAttribute("error", "Username already exists");
return "register"; return "register";
} }
userService.saveUser(username, password); userService.saveUser(requestModel.getUsername(), requestModel.getPassword());
return "redirect:/login?registered=true"; return "redirect:/login?registered=true";
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username-and-password")
public RedirectView changeUsernameAndPassword(
Principal principal,
@RequestParam String currentPassword,
@RequestParam String newUsername,
@RequestParam String newPassword,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated");
}
Optional<User> userOpt = userService.findByUsername(principal.getName());
if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/change-creds?messageType=userNotFound");
}
User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/change-creds?messageType=incorrectPassword");
}
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
return new RedirectView("/change-creds?messageType=usernameExists");
}
userService.changePassword(user, newPassword);
if (newUsername != null
&& newUsername.length() > 0
&& !user.getUsername().equals(newUsername)) {
userService.changeUsername(user, newUsername);
}
userService.changeFirstUse(user, false);
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView("/login?messageType=credsUpdated");
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username") @PostMapping("/change-username")
public RedirectView changeUsername( public RedirectView changeUsername(
@@ -128,6 +91,39 @@ public class UserController {
return new RedirectView("/login?messageType=credsUpdated"); return new RedirectView("/login?messageType=credsUpdated");
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-password-on-login")
public RedirectView changePasswordOnLogin(
Principal principal,
@RequestParam String currentPassword,
@RequestParam String newPassword,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated");
}
Optional<User> userOpt = userService.findByUsername(principal.getName());
if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/change-creds?messageType=userNotFound");
}
User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/change-creds?messageType=incorrectPassword");
}
userService.changePassword(user, newPassword);
userService.changeFirstUse(user, false);
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView("/login?messageType=credsUpdated");
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-password") @PostMapping("/change-password")
public RedirectView changePassword( public RedirectView changePassword(
@@ -211,18 +207,38 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/deleteUser/{username}") @PostMapping("/admin/deleteUser/{username}")
public String deleteUser(@PathVariable String username, Authentication authentication) { public RedirectView deleteUser(@PathVariable String username, Authentication authentication) {
if (!userService.usernameExists(username)) {
return new RedirectView("/addUsers?messageType=deleteUsernameExists");
}
// Get the currently authenticated username // Get the currently authenticated username
String currentUsername = authentication.getName(); String currentUsername = authentication.getName();
// Check if the provided username matches the current session's username // Check if the provided username matches the current session's username
if (currentUsername.equals(username)) { if (currentUsername.equals(username)) {
throw new IllegalArgumentException("Cannot delete currently logined in user."); return new RedirectView("/addUsers?messageType=deleteCurrentUser");
} }
invalidateUserSessions(username);
userService.deleteUser(username); userService.deleteUser(username);
return "redirect:/addUsers"; return new RedirectView("/addUsers");
}
@Autowired private SessionRegistry sessionRegistry;
private void invalidateUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation session :
sessionRegistry.getAllSessions(principal, false)) {
session.expireNow();
}
}
}
}
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")

View File

@@ -0,0 +1,69 @@
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.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
public class ConvertBookToPDFController {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
@Operation(
summary =
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
description =
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
MultipartFile fileInput = request.getFileInput();
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
}
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion.");
}
String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
if (originalFilename != null) {
String originalFilenameLower = originalFilename.toLowerCase();
if (!originalFilenameLower.endsWith(".epub")
&& !originalFilenameLower.endsWith(".mobi")
&& !originalFilenameLower.endsWith(".azw3")
&& !originalFilenameLower.endsWith(".fb2")
&& !originalFilenameLower.endsWith(".txt")
&& !originalFilenameLower.endsWith(".docx")) {
throw new IllegalArgumentException(
"File must be in .epub, .mobi, .azw3, .fb2, .txt, or .docx format.");
}
}
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "")
+ ".pdf"; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@@ -1,5 +1,7 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -7,10 +9,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.GeneralFile; import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -19,12 +22,17 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF { public class ConvertHtmlToPDF {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
@Operation( @Operation(
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
description = description =
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format.") "This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception { public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
if (fileInput == null) { if (fileInput == null) {
@@ -32,12 +40,17 @@ public class ConvertHtmlToPDF {
"Please provide an HTML or ZIP file for conversion."); "Please provide an HTML or ZIP file for conversion.");
} }
String originalFilename = fileInput.getOriginalFilename(); String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
if (originalFilename == null if (originalFilename == null
|| (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) { || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) {
throw new IllegalArgumentException("File must be either .html or .zip format."); throw new IllegalArgumentException("File must be either .html or .zip format.");
} }
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(fileInput.getBytes(), originalFilename); byte[] pdfBytes =
FileToPdf.convertHtmlToPdf(
request,
fileInput.getBytes(),
originalFilename,
bookAndHtmlFormatsInstalled);
String outputFilename = String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "") originalFilename.replaceFirst("[.][^.]+$", "")

View File

@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -54,9 +55,11 @@ public class ConvertImgPDFController {
colorTypeResult = ImageType.BINARY; colorTypeResult = ImageType.BINARY;
} }
// returns bytes for image // returns bytes for image
boolean singleImage = singleOrMultiple.equals("single"); boolean singleImage = "single".equals(singleOrMultiple);
byte[] result = null; byte[] result = null;
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try { try {
result = result =
PdfUtils.convertFromPdf( PdfUtils.convertFromPdf(
@@ -96,7 +99,7 @@ public class ConvertImgPDFController {
@Operation( @Operation(
summary = "Convert images to a PDF file", summary = "Convert images to a PDF file",
description = description =
"This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?") "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:MISO")
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request) public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request)
throws IOException { throws IOException {
MultipartFile[] file = request.getFileInput(); MultipartFile[] file = request.getFileInput();
@@ -113,6 +116,6 @@ public class ConvertImgPDFController {
private String getMediaType(String imageFormat) { private String getMediaType(String imageFormat) {
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat); String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
return mimeType.equals("null") ? "application/octet-stream" : mimeType; return "null".equals(mimeType) ? "application/octet-stream" : mimeType;
} }
} }

View File

@@ -1,8 +1,17 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.util.List;
import java.util.Map;
import org.commonmark.Extension;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.Node; import org.commonmark.node.Node;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer; 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.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -10,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,11 +32,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf { public class ConvertMarkdownToPdf {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
@Operation( @Operation(
summary = "Convert a Markdown file to PDF", summary = "Convert a Markdown file to PDF",
description = description =
"This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format.") "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format. Input:MARKDOWN Output:PDF Type:SISO")
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request) public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -35,18 +49,30 @@ public class ConvertMarkdownToPdf {
throw new IllegalArgumentException("Please provide a Markdown file for conversion."); throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
} }
String originalFilename = fileInput.getOriginalFilename(); String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
if (originalFilename == null || !originalFilename.endsWith(".md")) { if (originalFilename == null || !originalFilename.endsWith(".md")) {
throw new IllegalArgumentException("File must be in .md format."); throw new IllegalArgumentException("File must be in .md format.");
} }
// Convert Markdown to HTML using CommonMark // Convert Markdown to HTML using CommonMark
Parser parser = Parser.builder().build(); List<Extension> extensions = List.of(TablesExtension.create());
Parser parser = Parser.builder().extensions(extensions).build();
Node document = parser.parse(new String(fileInput.getBytes())); Node document = parser.parse(new String(fileInput.getBytes()));
HtmlRenderer renderer = HtmlRenderer.builder().build(); HtmlRenderer renderer =
HtmlRenderer.builder()
.attributeProviderFactory(context -> new TableAttributeProvider())
.extensions(extensions)
.build();
String htmlContent = renderer.render(document); String htmlContent = renderer.render(document);
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html"); byte[] pdfBytes =
FileToPdf.convertHtmlToPdf(
null,
htmlContent.getBytes(),
"converted.html",
bookAndHtmlFormatsInstalled);
String outputFilename = String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "") originalFilename.replaceFirst("[.][^.]+$", "")
@@ -54,3 +80,12 @@ public class ConvertMarkdownToPdf {
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }
class TableAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
if (node instanceof TableBlock) {
attributes.put("class", "table table-striped");
}
}
}

View File

@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -31,7 +32,7 @@ public class ConvertOfficeController {
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension // Check for valid file extension
String originalFilename = inputFile.getOriginalFilename(); String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
if (originalFilename == null if (originalFilename == null
|| !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension"); throw new IllegalArgumentException("Invalid file extension");
@@ -89,7 +90,8 @@ public class ConvertOfficeController {
byte[] pdfByteArray = convertToPdf(inputFile); byte[] pdfByteArray = convertToPdf(inputFile);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfByteArray, pdfByteArray,
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_convertedToPDF.pdf"); + "_convertedToPDF.pdf");
} }
} }

View File

@@ -0,0 +1,103 @@
package stirling.software.SPDF.controller.api.converters;
import java.nio.file.Files;
import java.nio.file.Path;
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")
public class ConvertPDFToBookController {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
@Operation(
summary =
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
description =
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
throws Exception {
MultipartFile fileInput = request.getFileInput();
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
}
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion.");
}
// Validate the output format
String outputFormat = request.getOutputFormat().toLowerCase();
List<String> allowedFormats =
Arrays.asList(
"epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb",
"lrf");
if (!allowedFormats.contains(outputFormat)) {
throw new IllegalArgumentException("Invalid output format: " + outputFormat);
}
byte[] outputFileBytes;
List<String> command = new ArrayList<>();
Path tempOutputFile =
Files.createTempFile(
"output_",
"." + outputFormat); // Use the output format for the file extension
Path tempInputFile = null;
try {
// Create temp input file from the provided PDF
tempInputFile = Files.createTempFile("input_", ".pdf"); // Assuming input is always PDF
Files.write(tempInputFile, fileInput.getBytes());
command.add("ebook-convert");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
.runCommandWithOutputHandling(command);
outputFileBytes = Files.readAllBytes(tempOutputFile);
} finally {
// Clean up temporary files
if (tempInputFile != null) {
Files.deleteIfExists(tempInputFile);
}
Files.deleteIfExists(tempOutputFile);
}
String outputFilename =
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "."
+ outputFormat; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
}
}

View File

@@ -2,6 +2,10 @@ package stirling.software.SPDF.controller.api.converters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -9,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -17,6 +22,7 @@ import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
import stirling.software.SPDF.model.api.converters.PdfToWordRequest; import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
import stirling.software.SPDF.utils.PDFToFile; import stirling.software.SPDF.utils.PDFToFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@@ -59,9 +65,21 @@ public class ConvertPDFToOffice {
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
if ("txt".equals(request.getOutputFormat())) {
PDFToFile pdfToFile = new PDFToFile(); try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
return WebResponseUtils.bytesToWebResponse(
text.getBytes(),
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ ".txt",
MediaType.TEXT_PLAIN);
}
} else {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
} }
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word") @PostMapping(consumes = "multipart/form-data", value = "/pdf/word")

View File

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -63,7 +64,9 @@ public class ConvertPDFToPDFA {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -6,6 +6,8 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -26,6 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF { public class ConvertWebsiteToPDF {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@Operation( @Operation(
summary = "Convert a URL to a PDF", summary = "Convert a URL to a PDF",
@@ -47,7 +53,11 @@ public class ConvertWebsiteToPDF {
// Prepare the OCRmyPDF command // Prepare the OCRmyPDF command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add("weasyprint"); if (!bookAndHtmlFormatsInstalled) {
command.add("weasyprint");
} else {
command.add("wkhtmltopdf");
}
command.add(URL); command.add(URL);
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());

View File

@@ -1,10 +1,10 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayInputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -44,8 +44,7 @@ public class ExtractController {
ArrayList<String> tableData = new ArrayList<>(); ArrayList<String> tableData = new ArrayList<>();
int columnsCount = 0; int columnsCount = 0;
try (PDDocument document = try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
final double res = 72; // PDF units are at 72 DPI final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper(); PDFTableStripper stripper = new PDFTableStripper();
PDPage pdPage = document.getPage(form.getPageId() - 1); PDPage pdPage = document.getPage(form.getPageId() - 1);

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.filters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
@@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,10 +41,10 @@ public class FilterController {
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes());
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) if (PdfUtils.hasText(pdfDocument, pageNumber, text))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, inputFile.getOriginalFilename()); pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
return null; return null;
} }
@@ -56,10 +58,10 @@ public class FilterController {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes());
if (PdfUtils.hasImages(pdfDocument, pageNumber)) if (PdfUtils.hasImages(pdfDocument, pageNumber))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, inputFile.getOriginalFilename()); pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
return null; return null;
} }
@@ -73,7 +75,7 @@ public class FilterController {
String pageCount = request.getPageCount(); String pageCount = request.getPageCount();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = Loader.loadPDF(inputFile.getBytes());
int actualPageCount = document.getNumberOfPages(); int actualPageCount = document.getNumberOfPages();
boolean valid = false; boolean valid = false;
@@ -107,7 +109,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = Loader.loadPDF(inputFile.getBytes());
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);
PDRectangle actualPageSize = firstPage.getMediaBox(); PDRectangle actualPageSize = firstPage.getMediaBox();
@@ -183,7 +185,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = Loader.loadPDF(inputFile.getBytes());
// Get the rotation of the first page // Get the rotation of the first page
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);

View File

@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.text.TextPosition;
@@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -43,7 +45,7 @@ public class AutoRenameController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback(); Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
PDDocument document = PDDocument.load(file.getInputStream()); PDDocument document = Loader.loadPDF(file.getBytes());
PDFTextStripper reader = PDFTextStripper reader =
new PDFTextStripper() { new PDFTextStripper() {
class LineInfo { class LineInfo {
@@ -132,7 +134,8 @@ public class AutoRenameController {
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf"); return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
} else { } else {
logger.info("File has no good title to be found"); logger.info("File has no good title to be found");
return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename()); return WebResponseUtils.pdfDocToWebResponse(
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
} }
} }
} }

View File

@@ -5,7 +5,6 @@ import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt; import java.awt.image.DataBufferInt;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@@ -13,6 +12,7 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -31,6 +31,7 @@ import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -43,6 +44,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class AutoSplitPdfController { public class AutoSplitPdfController {
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
@Operation( @Operation(
@@ -54,8 +56,7 @@ public class AutoSplitPdfController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode(); boolean duplexMode = request.isDuplexMode();
InputStream inputStream = file.getInputStream(); PDDocument document = Loader.loadPDF(file.getBytes());
PDDocument document = PDDocument.load(inputStream);
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
List<PDDocument> splitDocuments = new ArrayList<>(); List<PDDocument> splitDocuments = new ArrayList<>();
@@ -64,12 +65,13 @@ public class AutoSplitPdfController {
for (int page = 0; page < document.getNumberOfPages(); ++page) { for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
String result = decodeQRCode(bim); String result = decodeQRCode(bim);
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
if (QR_CONTENT.equals(result) && page != 0) {
splitDocuments.add(new PDDocument()); splitDocuments.add(new PDDocument());
} }
if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result)) { if (!splitDocuments.isEmpty()
&& !QR_CONTENT.equals(result)
&& !QR_CONTENT_OLD.equals(result)) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
} else if (page == 0) { } else if (page == 0) {
PDDocument firstDocument = new PDDocument(); PDDocument firstDocument = new PDDocument();
@@ -78,7 +80,7 @@ public class AutoSplitPdfController {
} }
// If duplexMode is true and current page is a divider, then skip next page // If duplexMode is true and current page is a divider, then skip next page
if (duplexMode && QR_CONTENT.equals(result)) { if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
page++; page++;
} }
} }
@@ -96,7 +98,9 @@ public class AutoSplitPdfController {
document.close(); document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {

View File

@@ -2,22 +2,20 @@ package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import javax.imageio.ImageIO; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -26,13 +24,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -40,6 +37,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class BlankPageController { public class BlankPageController {
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
@Operation( @Operation(
summary = "Remove blank pages from a PDF file", summary = "Remove blank pages from a PDF file",
@@ -53,7 +52,7 @@ public class BlankPageController {
PDDocument document = null; PDDocument document = null;
try { try {
document = PDDocument.load(inputFile.getInputStream()); document = Loader.loadPDF(inputFile.getBytes());
PDPageTree pages = document.getDocumentCatalog().getPages(); PDPageTree pages = document.getDocumentCatalog().getPages();
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
@@ -62,56 +61,35 @@ public class BlankPageController {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
for (PDPage page : pages) { for (PDPage page : pages) {
System.out.println("checking page " + pageIndex); logger.info("checking page " + pageIndex);
textStripper.setStartPage(pageIndex + 1); textStripper.setStartPage(pageIndex + 1);
textStripper.setEndPage(pageIndex + 1); textStripper.setEndPage(pageIndex + 1);
String pageText = textStripper.getText(document); String pageText = textStripper.getText(document);
boolean hasText = !pageText.trim().isEmpty(); boolean hasText = !pageText.trim().isEmpty();
Boolean blank = false;
if (hasText) { if (hasText) {
pagesToKeepIndex.add(pageIndex); logger.info("page " + pageIndex + " has text, not blank");
System.out.println("page " + pageIndex + " has text"); blank = false;
} else { } else {
boolean hasImages = PdfUtils.hasImagesOnPage(page); boolean hasImages = PdfUtils.hasImagesOnPage(page);
if (hasImages) { if (hasImages) {
System.out.println("page " + pageIndex + " has image"); logger.info("page " + pageIndex + " has image, running blank detection");
Path tempFile = Files.createTempFile("image_", ".png");
// Render image and save as temp file // Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300); BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
ImageIO.write(image, "png", tempFile.toFile()); blank = isBlankImage(image, threshold, whitePercent, threshold);
List<String> command =
new ArrayList<>(
Arrays.asList(
"python3",
System.getProperty("user.dir")
+ "/scripts/detect-blank-pages.py",
tempFile.toString(),
"--threshold",
String.valueOf(threshold),
"--white_percent",
String.valueOf(whitePercent)));
// Run CLI command
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// does contain data
if (returnCode.getRc() == 0) {
System.out.println(
"page " + pageIndex + " has image which is not blank");
pagesToKeepIndex.add(pageIndex);
} else {
System.out.println("Skipping, Image was blank for page #" + pageIndex);
}
} }
} }
if (blank) {
logger.info("Skipping, Image was blank for page #" + pageIndex);
} else {
logger.info("page " + pageIndex + " has image which is not blank");
pagesToKeepIndex.add(pageIndex);
}
pageIndex++; pageIndex++;
} }
System.out.print("pagesToKeep=" + pagesToKeepIndex.size());
// Remove pages not present in pagesToKeepIndex // Remove pages not present in pagesToKeepIndex
List<Integer> pageIndices = List<Integer> pageIndices =
IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList()); IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
@@ -124,7 +102,8 @@ public class BlankPageController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_blanksRemoved.pdf"); + "_blanksRemoved.pdf");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -133,4 +112,30 @@ public class BlankPageController {
if (document != null) document.close(); if (document != null) document.close();
} }
} }
public static boolean isBlankImage(
BufferedImage image, int threshold, double whitePercent, int blurSize) {
if (image == null) {
logger.info("Error: Image is null");
return false;
}
// Convert to binary image based on the threshold
int whitePixels = 0;
int totalPixels = image.getWidth() * image.getHeight();
for (int i = 0; i < image.getHeight(); i++) {
for (int j = 0; j < image.getWidth(); j++) {
int color = image.getRGB(j, i) & 0xFF;
if (color >= 255 - threshold) {
whitePixels++;
}
}
}
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
logger.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
return whitePixelPercentage >= whitePercent;
}
} }

View File

@@ -13,6 +13,7 @@ import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -147,7 +149,7 @@ public class CompressController {
if (expectedOutputSize != null && autoMode) { if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile); long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize > expectedOutputSize) { if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { try (PDDocument doc = Loader.loadPDF(new File(tempOutputFile.toString()))) {
long previousFileSize = 0; long previousFileSize = 0;
double scaleFactor = 1.0; double scaleFactor = 1.0;
while (true) { while (true) {
@@ -263,7 +265,9 @@ public class CompressController {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -17,6 +16,7 @@ import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -72,113 +72,146 @@ public class ExtractImageScansController {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1); String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
List<String> images = new ArrayList<>(); List<String> images = new ArrayList<>();
// Check if input file is a PDF List<Path> tempImageFiles = new ArrayList<>();
if (extension.equalsIgnoreCase("pdf")) { Path tempInputFile = null;
// Load PDF document Path tempZipFile = null;
try (PDDocument document = List<Path> tempDirs = new ArrayList<>();
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
PDFRenderer pdfRenderer = new PDFRenderer(document); try {
int pageCount = document.getNumberOfPages(); // Check if input file is a PDF
images = new ArrayList<>(); if ("pdf".equalsIgnoreCase(extension)) {
// Load PDF document
// Create images of all pages try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
for (int i = 0; i < pageCount; i++) { PDFRenderer pdfRenderer = new PDFRenderer(document);
// Create temp file to save the image int pageCount = document.getNumberOfPages();
Path tempFile = Files.createTempFile("image_", ".png"); images = new ArrayList<>();
// Render image and save as temp file // Create images of all pages
BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300); for (int i = 0; i < pageCount; i++) {
ImageIO.write(image, "png", tempFile.toFile()); // Create temp file to save the image
Path tempFile = Files.createTempFile("image_", ".png");
// Add temp file path to images list
images.add(tempFile.toString()); // Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300);
ImageIO.write(image, "png", tempFile.toFile());
// Add temp file path to images list
images.add(tempFile.toString());
tempImageFiles.add(tempFile);
}
}
} else {
tempInputFile = Files.createTempFile("input_", "." + extension);
Files.copy(
form.getFileInput().getInputStream(),
tempInputFile,
StandardCopyOption.REPLACE_EXISTING);
// Add input file path to images list
images.add(tempInputFile.toString());
}
List<byte[]> processedImageBytes = new ArrayList<>();
// Process each image
for (int i = 0; i < images.size(); i++) {
Path tempDir = Files.createTempDirectory("openCV_output");
tempDirs.add(tempDir);
List<String> command =
new ArrayList<>(
Arrays.asList(
"python3",
"./scripts/split_photos.py",
images.get(i),
tempDir.toString(),
"--angle_threshold",
String.valueOf(form.getAngleThreshold()),
"--tolerance",
String.valueOf(form.getTolerance()),
"--min_area",
String.valueOf(form.getMinArea()),
"--min_contour_area",
String.valueOf(form.getMinContourArea()),
"--border_size",
String.valueOf(form.getBorderSize())));
// Run CLI command
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Read the output photos in temp directory
List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
for (Path tempOutputFile : tempOutputFiles) {
byte[] imageBytes = Files.readAllBytes(tempOutputFile);
processedImageBytes.add(imageBytes);
}
// Clean up the temporary directory
FileUtils.deleteDirectory(tempDir.toFile());
}
// Create zip file if multiple images
if (processedImageBytes.size() > 1) {
String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip";
tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut =
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add processed images to the zip
for (int i = 0; i < processedImageBytes.size(); i++) {
ZipEntry entry =
new ZipEntry(
fileName.replaceFirst("[.][^.]+$", "")
+ "_"
+ (i + 1)
+ ".png");
zipOut.putNextEntry(entry);
zipOut.write(processedImageBytes.get(i));
zipOut.closeEntry();
}
}
byte[] zipBytes = Files.readAllBytes(tempZipFile);
// Clean up the temporary zip file
Files.delete(tempZipFile);
return WebResponseUtils.bytesToWebResponse(
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
} else {
// Return the processed image as a response
byte[] imageBytes = processedImageBytes.get(0);
return WebResponseUtils.bytesToWebResponse(
imageBytes,
fileName.replaceFirst("[.][^.]+$", "") + ".png",
MediaType.IMAGE_PNG);
}
} finally {
// Cleanup logic for all temporary files and directories
tempImageFiles.forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (IOException e) {
logger.error("Failed to delete temporary image file: " + path, e);
} }
} });
} else {
Path tempInputFile = Files.createTempFile("input_", "." + extension);
Files.copy(
form.getFileInput().getInputStream(),
tempInputFile,
StandardCopyOption.REPLACE_EXISTING);
// Add input file path to images list
images.add(tempInputFile.toString());
}
List<byte[]> processedImageBytes = new ArrayList<>(); if (tempZipFile != null && Files.exists(tempZipFile)) {
try {
// Process each image Files.delete(tempZipFile);
for (int i = 0; i < images.size(); i++) { } catch (IOException e) {
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
Path tempDir = Files.createTempDirectory("openCV_output");
List<String> command =
new ArrayList<>(
Arrays.asList(
"python3",
"./scripts/split_photos.py",
images.get(i),
tempDir.toString(),
"--angle_threshold",
String.valueOf(form.getAngleThreshold()),
"--tolerance",
String.valueOf(form.getTolerance()),
"--min_area",
String.valueOf(form.getMinArea()),
"--min_contour_area",
String.valueOf(form.getMinContourArea()),
"--border_size",
String.valueOf(form.getBorderSize())));
// Run CLI command
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Read the output photos in temp directory
List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
for (Path tempOutputFile : tempOutputFiles) {
byte[] imageBytes = Files.readAllBytes(tempOutputFile);
processedImageBytes.add(imageBytes);
}
// Clean up the temporary directory
FileUtils.deleteDirectory(tempDir.toFile());
}
// Create zip file if multiple images
if (processedImageBytes.size() > 1) {
String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut =
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add processed images to the zip
for (int i = 0; i < processedImageBytes.size(); i++) {
ZipEntry entry =
new ZipEntry(
fileName.replaceFirst("[.][^.]+$", "")
+ "_"
+ (i + 1)
+ ".png");
zipOut.putNextEntry(entry);
zipOut.write(processedImageBytes.get(i));
zipOut.closeEntry();
} }
} }
byte[] zipBytes = Files.readAllBytes(tempZipFile); tempDirs.forEach(dir -> {
try {
// Clean up the temporary zip file FileUtils.deleteDirectory(dir.toFile());
Files.delete(tempZipFile); } catch (IOException e) {
logger.error("Failed to delete temporary directory: " + dir, e);
return WebResponseUtils.bytesToWebResponse( }
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); });
} else {
// Return the processed image as a response
byte[] imageBytes = processedImageBytes.get(0);
return WebResponseUtils.bytesToWebResponse(
imageBytes,
fileName.replaceFirst("[.][^.]+$", "") + ".png",
MediaType.IMAGE_PNG);
} }
} }
} }

View File

@@ -14,6 +14,7 @@ import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -53,7 +55,7 @@ public class ExtractImagesController {
System.out.println( System.out.println(
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
PDDocument document = PDDocument.load(file.getBytes()); PDDocument document = Loader.loadPDF(file.getBytes());
// Create ByteArrayOutputStream to write zip file to byte array // Create ByteArrayOutputStream to write zip file to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -65,7 +67,9 @@ public class ExtractImagesController {
zos.setLevel(Deflater.BEST_COMPRESSION); zos.setLevel(Deflater.BEST_COMPRESSION);
int imageIndex = 1; int imageIndex = 1;
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
int pageNum = 0; int pageNum = 0;
Set<Integer> processedImages = new HashSet<>(); Set<Integer> processedImages = new HashSet<>();
// Iterate over each page // Iterate over each page
@@ -84,19 +88,19 @@ public class ExtractImagesController {
// Convert image to desired format // Convert image to desired format
RenderedImage renderedImage = image.getImage(); RenderedImage renderedImage = image.getImage();
BufferedImage bufferedImage = null; BufferedImage bufferedImage = null;
if (format.equalsIgnoreCase("png")) { if ("png".equalsIgnoreCase(format)) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),
renderedImage.getHeight(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_ARGB); BufferedImage.TYPE_INT_ARGB);
} else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) { } else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),
renderedImage.getHeight(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_RGB); BufferedImage.TYPE_INT_RGB);
} else if (format.equalsIgnoreCase("gif")) { } else if ("gif".equalsIgnoreCase(format)) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),

View File

@@ -16,6 +16,7 @@ import java.util.Random;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -28,11 +29,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -49,7 +50,7 @@ public class FakeScanControllerWIP {
// TODO // TODO
@Hidden @Hidden
@PostMapping(consumes = "multipart/form-data", value = "/fakeScan") // @PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
description = description =
@@ -57,7 +58,7 @@ public class FakeScanControllerWIP {
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
PDDocument document = PDDocument.load(inputFile.getBytes()); PDDocument document = Loader.loadPDF(inputFile.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
for (int page = 0; page < document.getNumberOfPages(); ++page) { for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
@@ -141,7 +142,9 @@ public class FakeScanControllerWIP {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_scanned.pdf";
return WebResponseUtils.boasToWebResponse(baos, outputFilename); return WebResponseUtils.boasToWebResponse(baos, outputFilename);
} }
} }

View File

@@ -7,6 +7,7 @@ import java.util.Calendar;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDDocumentInformation;
@@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -67,7 +69,7 @@ public class MetadataController {
allRequestParams = new java.util.HashMap<String, String>(); allRequestParams = new java.util.HashMap<String, String>();
} }
// Load the PDF file into a PDDocument // Load the PDF file into a PDDocument
PDDocument document = PDDocument.load(pdfFile.getBytes()); PDDocument document = Loader.loadPDF(pdfFile.getBytes());
// Get the document information from the PDF // Get the document information from the PDF
PDDocumentInformation info = document.getDocumentInformation(); PDDocumentInformation info = document.getDocumentInformation();
@@ -108,15 +110,15 @@ public class MetadataController {
for (Entry<String, String> entry : allRequestParams.entrySet()) { for (Entry<String, String> entry : allRequestParams.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
// Check if the key is a standard metadata key // Check if the key is a standard metadata key
if (!key.equalsIgnoreCase("Author") if (!"Author".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("CreationDate") && !"CreationDate".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Creator") && !"Creator".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Keywords") && !"Keywords".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("modificationDate") && !"modificationDate".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Producer") && !"Producer".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Subject") && !"Subject".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Title") && !"Title".equalsIgnoreCase(key)
&& !key.equalsIgnoreCase("Trapped") && !"Trapped".equalsIgnoreCase(key)
&& !key.contains("customKey") && !key.contains("customKey")
&& !key.contains("customValue")) { && !key.contains("customValue")) {
info.setCustomMetadataValue(key, entry.getValue()); info.setCustomMetadataValue(key, entry.getValue());
@@ -163,6 +165,8 @@ public class MetadataController {
document.setDocumentInformation(info); document.setDocumentInformation(info);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf"); Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_metadata.pdf");
} }
} }

View File

@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -40,7 +41,7 @@ public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class); private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata"; String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
if (files == null) { if (files == null) {
return Collections.emptyList(); return Collections.emptyList();
@@ -74,7 +75,7 @@ public class OCRController {
throw new IOException("Please select at least one language."); throw new IOException("Please select at least one language.");
} }
if (!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) { if (!"hocr".equals(ocrRenderType) && !"sandwich".equals(ocrRenderType)) {
throw new IOException("ocrRenderType wrong"); throw new IOException("ocrRenderType wrong");
} }
@@ -127,7 +128,7 @@ public class OCRController {
if (cleanFinal != null && cleanFinal) { if (cleanFinal != null && cleanFinal) {
command.add("--clean-final"); command.add("--clean-final");
} }
if (ocrType != null && !ocrType.equals("")) { if (ocrType != null && !"".equals(ocrType)) {
if ("skip-text".equals(ocrType)) { if ("skip-text".equals(ocrType)) {
command.add("--skip-text"); command.add("--skip-text");
} else if ("force-ocr".equals(ocrType)) { } else if ("force-ocr".equals(ocrType)) {
@@ -182,12 +183,16 @@ public class OCRController {
// Return the OCR processed PDF as a response // Return the OCR processed PDF as a response
String outputFilename = String outputFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_OCR.pdf";
if (sidecar != null && sidecar) { if (sidecar != null && sidecar) {
// Create a zip file containing both the PDF and the text file // Create a zip file containing both the PDF and the text file
String outputZipFilename = String outputZipFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_OCR.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip"); Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = try (ZipOutputStream zipOut =

View File

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,7 +31,7 @@ public class OverlayImageController {
@Operation( @Operation(
summary = "Overlay image onto a PDF file", summary = "Overlay image onto a PDF file",
description = description =
"This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO") "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:SISO")
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) { public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
MultipartFile imageFile = request.getImageFile(); MultipartFile imageFile = request.getImageFile();
@@ -44,7 +45,9 @@ public class OverlayImageController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
result, result,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"); Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to add image to PDF", e); logger.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

View File

@@ -4,11 +4,13 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -48,7 +51,7 @@ public class PageNumbersController {
String customText = request.getCustomText(); String customText = request.getCustomText();
int pageNumber = startingNumber; int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes(); byte[] fileBytes = file.getBytes();
PDDocument document = PDDocument.load(fileBytes); PDDocument document = Loader.loadPDF(fileBytes);
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
@@ -71,7 +74,6 @@ public class PageNumbersController {
} }
float fontSize = 12.0f; float fontSize = 12.0f;
PDType1Font font = PDType1Font.HELVETICA;
if (pagesToNumber == null || pagesToNumber.length() == 0) { if (pagesToNumber == null || pagesToNumber.length() == 0) {
pagesToNumber = "all"; pagesToNumber = "all";
} }
@@ -92,7 +94,7 @@ public class PageNumbersController {
.replace("{total}", String.valueOf(document.getNumberOfPages())) .replace("{total}", String.valueOf(document.getNumberOfPages()))
.replace( .replace(
"{filename}", "{filename}",
file.getOriginalFilename() Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")) .replaceFirst("[.][^.]+$", ""))
: String.valueOf(pageNumber); : String.valueOf(pageNumber);
@@ -127,9 +129,9 @@ public class PageNumbersController {
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true); document, page, PDPageContentStream.AppendMode.APPEND, true, true);
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(font, fontSize); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize);
contentStream.newLineAtOffset(x, y); contentStream.newLineAtOffset(x, y);
contentStream.showText(text); contentStream.showText(text);
contentStream.endText(); contentStream.endText();
@@ -144,7 +146,8 @@ public class PageNumbersController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), baos.toByteArray(),
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_numbersAdded.pdf",
MediaType.APPLICATION_PDF); MediaType.APPLICATION_PDF);
} }
} }

View File

@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -65,7 +66,9 @@ public class RepairController {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_repaired.pdf"; Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_repaired.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api.misc;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -36,7 +38,7 @@ public class ShowJavascript {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String script = ""; String script = "";
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
if (document.getDocumentCatalog() != null if (document.getDocumentCatalog() != null
&& document.getDocumentCatalog().getNames() != null) { && document.getDocumentCatalog().getNames() != null) {
@@ -53,7 +55,8 @@ public class ShowJavascript {
script += script +=
"// File: " "// File: "
+ inputFile.getOriginalFilename() + Filenames.toSimpleFileName(
inputFile.getOriginalFilename())
+ ", Script: " + ", Script: "
+ name + name
+ "\n" + "\n"
@@ -65,12 +68,14 @@ public class ShowJavascript {
if (script.isEmpty()) { if (script.isEmpty()) {
script = script =
"PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript"; "PDF '"
+ Filenames.toSimpleFileName(inputFile.getOriginalFilename())
+ "' does not contain Javascript";
} }
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
script.getBytes(StandardCharsets.UTF_8), script.getBytes(StandardCharsets.UTF_8),
inputFile.getOriginalFilename() + ".js"); Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js");
} }
} }
} }

View File

@@ -0,0 +1,320 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
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;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
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.core.io.ClassPathResource;
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.misc.AddStampRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class StampController {
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@Operation(
summary = "Add stamp to a PDF file",
description =
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput();
String stampType = request.getStampType();
String stampText = request.getStampText();
MultipartFile stampImage = request.getStampImage();
String alphabet = request.getAlphabet();
float fontSize = request.getFontSize();
float rotation = request.getRotation();
float opacity = request.getOpacity();
int position = request.getPosition(); // Updated to use 1-9 positioning logic
float overrideX = request.getOverrideX(); // New field for X override
float overrideY = request.getOverrideY(); // New field for Y override
String customColor = request.getCustomColor();
float marginFactor;
switch (request.getCustomMargin().toLowerCase()) {
case "small":
marginFactor = 0.02f;
break;
case "medium":
marginFactor = 0.035f;
break;
case "large":
marginFactor = 0.05f;
break;
case "x-large":
marginFactor = 0.075f;
break;
default:
marginFactor = 0.035f;
break;
}
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
for (int pageIndex : pageNumbers) {
int zeroBasedIndex = pageIndex - 1;
if (zeroBasedIndex >= 0 && zeroBasedIndex < document.getNumberOfPages()) {
PDPage page = document.getPage(zeroBasedIndex);
PDRectangle pageSize = page.getMediaBox();
float margin = marginFactor * (pageSize.getWidth() + pageSize.getHeight()) / 2;
PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState);
if ("text".equalsIgnoreCase(stampType)) {
addTextStamp(
contentStream,
stampText,
document,
page,
rotation,
position,
fontSize,
alphabet,
overrideX,
overrideY,
margin,
customColor);
} else if ("image".equalsIgnoreCase(stampType)) {
addImageStamp(
contentStream,
stampImage,
document,
page,
rotation,
position,
fontSize,
overrideX,
overrideY,
margin);
}
contentStream.close();
}
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_stamped.pdf");
}
private void addTextStamp(
PDPageContentStream contentStream,
String stampText,
PDDocument document,
PDPage page,
float rotation,
int position, // 1-9 positioning logic
float fontSize,
String alphabet,
float overrideX, // X override
float overrideY,
float margin,
String colorString) // Y override
throws IOException {
String resourceDir = "";
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
switch (alphabet) {
case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
break;
case "japanese":
resourceDir = "static/fonts/Meiryo.ttf";
break;
case "korean":
resourceDir = "static/fonts/malgun.ttf";
break;
case "chinese":
resourceDir = "static/fonts/SimSun.ttf";
break;
case "roman":
default:
resourceDir = "static/fonts/NotoSans-Regular.ttf";
break;
}
if (!"".equals(resourceDir)) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
File tempFile = Files.createTempFile("NotoSansFont", fileExtension).toFile();
try (InputStream is = classPathResource.getInputStream();
FileOutputStream os = new FileOutputStream(tempFile)) {
IOUtils.copy(is, os);
}
font = PDType0Font.load(document, tempFile);
tempFile.deleteOnExit();
}
contentStream.setFont(font, fontSize);
Color redactColor;
try {
if (!colorString.startsWith("#")) {
colorString = "#" + colorString;
}
redactColor = Color.decode(colorString);
} catch (NumberFormatException e) {
redactColor = Color.LIGHT_GRAY;
}
contentStream.setNonStrokingColor(redactColor);
PDRectangle pageSize = page.getMediaBox();
float x, y;
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);
y =
calculatePositionY(
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
}
contentStream.beginText();
contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(rotation), x, y));
contentStream.showText(stampText);
contentStream.endText();
}
private void addImageStamp(
PDPageContentStream contentStream,
MultipartFile stampImage,
PDDocument document,
PDPage page,
float rotation,
int position, // 1-9 positioning logic
float fontSize,
float overrideX,
float overrideY,
float margin)
throws IOException {
// Load the stamp image
BufferedImage image = ImageIO.read(stampImage.getInputStream());
// Compute width based on original aspect ratio
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
// Desired physical height (in PDF points)
float desiredPhysicalHeight = fontSize;
// Desired physical width based on the aspect ratio
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
// Convert the BufferedImage to PDImageXObject
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
PDRectangle pageSize = page.getMediaBox();
float x, y;
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, null, 0, null, margin);
y = calculatePositionY(pageSize, position, fontSize, margin);
}
contentStream.saveGraphicsState();
contentStream.transform(Matrix.getTranslateInstance(x, y));
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
contentStream.restoreGraphicsState();
}
private float calculatePositionX(
PDRectangle pageSize,
int position,
float contentWidth,
PDFont font,
float fontSize,
String text,
float margin)
throws IOException {
float actualWidth =
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
switch (position % 3) {
case 1: // Left
return pageSize.getLowerLeftX() + margin;
case 2: // Center
return (pageSize.getWidth() - actualWidth) / 2;
case 0: // Right
return pageSize.getUpperRightX() - actualWidth - margin;
default:
return 0;
}
}
private float calculatePositionY(
PDRectangle pageSize, int position, float height, float margin) {
switch ((position - 1) / 3) {
case 0: // Top
return pageSize.getUpperRightY() - height - margin;
case 1: // Middle
return (pageSize.getHeight() - height) / 2;
case 2: // Bottom
return pageSize.getLowerLeftY() + margin;
default:
return 0;
}
}
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
return font.getStringWidth(text) / 1000 * fontSize;
}
private float calculateTextCapHeight(PDFont font, float fontSize) {
return font.getFontDescriptor().getCapHeight() / 1000 * fontSize;
}
}

View File

@@ -1,6 +1,8 @@
package stirling.software.SPDF.controller.api.pipeline; package stirling.software.SPDF.controller.api.pipeline;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -34,11 +36,62 @@ public class ApiDocService {
private String getApiDocsUrl() { private String getApiDocsUrl() {
String contextPath = servletContext.getContextPath(); String contextPath = servletContext.getContextPath();
String port = SPdfApplication.getPort(); String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/v1/api-docs"; return "http://localhost:" + port + contextPath + "/v1/api-docs";
} }
Map<String, List<String>> outputToFileTypes = new HashMap<>();
public List getExtensionTypes(boolean output, String operationName) {
if (outputToFileTypes.size() == 0) {
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
outputToFileTypes.put(
"IMAGE",
Arrays.asList(
"png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff", "svg", "psd",
"ai", "eps"));
outputToFileTypes.put(
"ZIP",
Arrays.asList("zip", "rar", "7z", "tar", "gz", "bz2", "xz", "lz", "lzma", "z"));
outputToFileTypes.put("WORD", Arrays.asList("doc", "docx", "odt", "rtf"));
outputToFileTypes.put("CSV", Arrays.asList("csv"));
outputToFileTypes.put("JS", Arrays.asList("js", "jsx"));
outputToFileTypes.put("HTML", Arrays.asList("html", "htm", "xhtml"));
outputToFileTypes.put("JSON", Arrays.asList("json"));
outputToFileTypes.put("TXT", Arrays.asList("txt", "text", "md", "markdown"));
outputToFileTypes.put("PPT", Arrays.asList("ppt", "pptx", "odp"));
outputToFileTypes.put("XML", Arrays.asList("xml", "xsd", "xsl"));
outputToFileTypes.put(
"BOOK", Arrays.asList("epub", "mobi", "azw3", "fb2", "txt", "docx"));
// type.
}
if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
loadApiDocumentation();
}
if (!apiDocumentation.containsKey(operationName)) {
return null;
}
ApiEndpoint endpoint = apiDocumentation.get(operationName);
String description = endpoint.getDescription();
Pattern pattern = null;
if (output) {
pattern = Pattern.compile("Output:(\\w+)");
} else {
pattern = Pattern.compile("Input:(\\w+)");
}
Matcher matcher = pattern.matcher(description);
while (matcher.find()) {
String type = matcher.group(1).toUpperCase();
if (outputToFileTypes.containsKey(type)) {
return outputToFileTypes.get(type);
}
}
return null;
}
@Autowired(required = false) @Autowired(required = false)
private UserServiceInterface userService; private UserServiceInterface userService;

View File

@@ -11,6 +11,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -35,6 +36,9 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.github.pixee.security.ZipSecurity;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import stirling.software.SPDF.SPdfApplication; import stirling.software.SPDF.SPdfApplication;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
@@ -60,7 +64,7 @@ public class PipelineProcessor {
private String getBaseUrl() { private String getBaseUrl() {
String contextPath = servletContext.getContextPath(); String contextPath = servletContext.getContextPath();
String port = SPdfApplication.getPort(); String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/"; return "http://localhost:" + port + contextPath + "/";
} }
@@ -82,15 +86,11 @@ public class PipelineProcessor {
operation, operation,
isMultiInputOperation); isMultiInputOperation);
Map<String, Object> parameters = pipelineOperation.getParameters(); Map<String, Object> parameters = pipelineOperation.getParameters();
String inputFileExtension = ""; List<String> inputFileTypes = apiDocService.getExtensionTypes(false, operation);
if (inputFileTypes == null) {
// TODO inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
// if (operationNode.has("inputFileType")) { }
// inputFileExtension = operationNode.get("inputFileType").asText(); // List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
// } else {
inputFileExtension = ".pdf";
// }
final String finalInputFileExtension = inputFileExtension;
String url = getBaseUrl() + operation; String url = getBaseUrl() + operation;
@@ -98,38 +98,42 @@ public class PipelineProcessor {
if (!isMultiInputOperation) { if (!isMultiInputOperation) {
for (Resource file : outputFiles) { for (Resource file : outputFiles) {
boolean hasInputFileType = false; boolean hasInputFileType = false;
if (file.getFilename().endsWith(inputFileExtension)) { for (String extension : inputFileTypes) {
hasInputFileType = true; if ("ALL".equals(extension) || file.getFilename().endsWith(extension)) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); hasInputFileType = true;
body.add("fileInput", file); MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("fileInput", file);
for (Entry<String, Object> entry : parameters.entrySet()) { for (Entry<String, Object> entry : parameters.entrySet()) {
body.add(entry.getKey(), entry.getValue()); body.add(entry.getKey(), entry.getValue());
}
ResponseEntity<byte[]> response = sendWebRequest(url, body);
// If the operation is filter and the response body is null or empty,
// skip
// this
// file
if (operation.startsWith("filter-")
&& (response.getBody() == null
|| response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation);
continue;
}
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
processOutputFiles(operation, response, newOutputFiles);
} }
ResponseEntity<byte[]> response = sendWebRequest(url, body);
// If the operation is filter and the response body is null or empty, skip
// this
// file
if (operation.startsWith("filter-")
&& (response.getBody() == null || response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation);
continue;
}
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
processOutputFiles(operation, file.getFilename(), response, newOutputFiles);
} }
if (!hasInputFileType) { if (!hasInputFileType) {
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ inputFileExtension + String.join(", ", inputFileTypes)
+ " found for operation " + " found for operation "
+ operation); + operation);
hasErrors = true; hasErrors = true;
@@ -138,13 +142,19 @@ public class PipelineProcessor {
} else { } else {
// Filter and collect all files that match the inputFileExtension // Filter and collect all files that match the inputFileExtension
List<Resource> matchingFiles = List<Resource> matchingFiles;
outputFiles.stream() if (inputFileTypes.contains("ALL")) {
.filter( matchingFiles = new ArrayList<>(outputFiles);
file -> } else {
file.getFilename() final List<String> finalinputFileTypes = inputFileTypes;
.endsWith(finalInputFileExtension)) matchingFiles =
.collect(Collectors.toList()); outputFiles.stream()
.filter(
file ->
finalinputFileTypes.stream()
.anyMatch(file.getFilename()::endsWith))
.collect(Collectors.toList());
}
// Check if there are matching files // Check if there are matching files
if (!matchingFiles.isEmpty()) { if (!matchingFiles.isEmpty()) {
@@ -164,11 +174,7 @@ public class PipelineProcessor {
// Handle the response // Handle the response
if (response.getStatusCode().equals(HttpStatus.OK)) { if (response.getStatusCode().equals(HttpStatus.OK)) {
processOutputFiles( processOutputFiles(operation, response, newOutputFiles);
operation,
matchingFiles.get(0).getFilename(),
response,
newOutputFiles);
} else { } else {
// Log error if the response status is not OK // Log error if the response status is not OK
logPrintStream.println( logPrintStream.println(
@@ -178,7 +184,7 @@ public class PipelineProcessor {
} else { } else {
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ inputFileExtension + String.join(", ", inputFileTypes)
+ " found for multi-input operation " + " found for multi-input operation "
+ operation); + operation);
hasErrors = true; hasErrors = true;
@@ -211,11 +217,29 @@ public class PipelineProcessor {
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class); return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
} }
public static String removeTrailingNaming(String filename) {
// Splitting filename into name and extension
int dotIndex = filename.lastIndexOf(".");
if (dotIndex == -1) {
// No extension found
return filename;
}
String name = filename.substring(0, dotIndex);
String extension = filename.substring(dotIndex);
// Finding the last underscore
int underscoreIndex = name.lastIndexOf("_");
if (underscoreIndex == -1) {
// No underscore found
return filename;
}
// Removing the last part and reattaching the extension
return name.substring(0, underscoreIndex) + extension;
}
private List<Resource> processOutputFiles( private List<Resource> processOutputFiles(
String operation, String operation, ResponseEntity<byte[]> response, List<Resource> newOutputFiles)
String fileName,
ResponseEntity<byte[]> response,
List<Resource> newOutputFiles)
throws IOException { throws IOException {
// Define filename // Define filename
String newFilename; String newFilename;
@@ -227,7 +251,7 @@ public class PipelineProcessor {
newFilename = extractFilename(response); newFilename = extractFilename(response);
} else { } else {
// Otherwise, keep the original filename. // Otherwise, keep the original filename.
newFilename = fileName; newFilename = removeTrailingNaming(extractFilename(response));
} }
// Check if the response body is a zip file // Check if the response body is a zip file
@@ -312,7 +336,7 @@ public class PipelineProcessor {
new ByteArrayResource(file.getBytes()) { new ByteArrayResource(file.getBytes()) {
@Override @Override
public String getFilename() { public String getFilename() {
return file.getOriginalFilename(); return Filenames.toSimpleFileName(file.getOriginalFilename());
} }
}; };
outputFiles.add(fileResource); outputFiles.add(fileResource);
@@ -335,7 +359,7 @@ public class PipelineProcessor {
List<Resource> unzippedFiles = new ArrayList<>(); List<Resource> unzippedFiles = new ArrayList<>();
try (ByteArrayInputStream bais = new ByteArrayInputStream(data); try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ZipInputStream zis = new ZipInputStream(bais)) { ZipInputStream zis = ZipSecurity.createHardenedInputStream(bais)) {
ZipEntry entry; ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) { while ((entry = zis.getNextEntry()) != null) {

View File

@@ -16,6 +16,7 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.signature.CreateSignatureBase; import org.apache.pdfbox.examples.signature.CreateSignatureBase;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
@@ -41,6 +42,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -73,7 +75,7 @@ public class CertSignController {
@Operation( @Operation(
summary = "Sign PDF with a Digital Certificate", summary = "Sign PDF with a Digital Certificate",
description = description =
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO") "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
@@ -122,7 +124,9 @@ public class CertSignController {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
sign(pdf.getBytes(), baos, createSignature, name, location, reason); sign(pdf.getBytes(), baos, createSignature, name, location, reason);
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.boasToWebResponse(
baos, pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf"); baos,
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_signed.pdf");
} }
private static void sign( private static void sign(
@@ -132,7 +136,7 @@ public class CertSignController {
String name, String name,
String location, String location,
String reason) { String reason) {
try (PDDocument doc = PDDocument.load(input)) { try (PDDocument doc = Loader.loadPDF(input)) {
PDSignature signature = new PDSignature(); PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

View File

@@ -11,11 +11,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSInputStream; import org.apache.pdfbox.cos.COSInputStream;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -87,7 +85,7 @@ public class GetInfoOnPDF {
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
try (PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); ) { try (PDDocument pdfBoxDoc = Loader.loadPDF(inputFile.getBytes()); ) {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ObjectNode jsonOutput = objectMapper.createObjectNode(); ObjectNode jsonOutput = objectMapper.createObjectNode();
@@ -129,17 +127,6 @@ public class GetInfoOnPDF {
boolean hasCompression = false; boolean hasCompression = false;
String compressionType = "None"; String compressionType = "None";
COSDocument cosDoc = pdfBoxDoc.getDocument();
for (COSObject cosObject : cosDoc.getObjects()) {
if (cosObject.getObject() instanceof COSStream) {
COSStream cosStream = (COSStream) cosObject.getObject();
if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) {
hasCompression = true;
compressionType = "Object Streams";
break;
}
}
}
basicInfo.put("Compression", hasCompression); basicInfo.put("Compression", hasCompression);
if (hasCompression) basicInfo.put("CompressionType", compressionType); if (hasCompression) basicInfo.put("CompressionType", compressionType);
@@ -343,7 +330,6 @@ public class GetInfoOnPDF {
permissionsNode.put("CanModify", ap.canModify()); permissionsNode.put("CanModify", ap.canModify());
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations()); permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
permissionsNode.put("CanPrint", ap.canPrint()); permissionsNode.put("CanPrint", ap.canPrint());
permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded());
encryption.set( encryption.set(
"Permissions", permissionsNode); // set the node under "Permissions" "Permissions", permissionsNode); // set the node under "Permissions"

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
@@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -38,11 +40,12 @@ public class PasswordController {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String password = request.getPassword(); String password = request.getPassword();
PDDocument document = PDDocument.load(fileInput.getBytes(), password); PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true); document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_password_removed.pdf"); + "_password_removed.pdf");
} }
@@ -66,7 +69,7 @@ public class PasswordController {
boolean canPrint = request.isCanPrint(); boolean canPrint = request.isCanPrint();
boolean canPrintFaithful = request.isCanPrintFaithful(); boolean canPrintFaithful = request.isCanPrintFaithful();
PDDocument document = PDDocument.load(fileInput.getBytes()); PDDocument document = Loader.loadPDF(fileInput.getBytes());
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent); ap.setCanExtractContent(!canExtractContent);
@@ -87,10 +90,13 @@ public class PasswordController {
if ("".equals(ownerPassword) && "".equals(password)) if ("".equals(ownerPassword) && "".equals(password))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_permissions.pdf"); + "_permissions.pdf");
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf"); Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_passworded.pdf");
} }
} }

View File

@@ -2,14 +2,15 @@ package stirling.software.SPDF.controller.api.security;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
@@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -57,7 +59,7 @@ public class RedactController {
System.out.println(listOfTextString); System.out.println(listOfTextString);
String[] listOfText = listOfTextString.split("\n"); String[] listOfText = listOfTextString.split("\n");
byte[] bytes = file.getBytes(); byte[] bytes = file.getBytes();
PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes)); PDDocument document = Loader.loadPDF(bytes);
Color redactColor; Color redactColor;
try { try {
@@ -86,7 +88,9 @@ public class RedactController {
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight())); PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
imageDocument.addPage(newPage); imageDocument.addPage(newPage);
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim); PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage); PDPageContentStream contentStream =
new PDPageContentStream(
imageDocument, newPage, AppendMode.APPEND, true, true);
contentStream.drawImage(pdImage, 0, 0); contentStream.drawImage(pdImage, 0, 0);
contentStream.close(); contentStream.close();
} }
@@ -101,7 +105,8 @@ public class RedactController {
byte[] pdfContent = baos.toByteArray(); byte[] pdfContent = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfContent, pdfContent,
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf"); Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_redacted.pdf");
} }
private void redactFoundText( private void redactFoundText(

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -52,7 +54,7 @@ public class SanitizeController {
boolean removeLinks = request.isRemoveLinks(); boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = request.isRemoveFonts(); boolean removeFonts = request.isRemoveFonts();
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
if (removeJavaScript) { if (removeJavaScript) {
sanitizeJavaScript(document); sanitizeJavaScript(document);
} }
@@ -75,7 +77,8 @@ public class SanitizeController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf"); + "_sanitized.pdf");
} }
} }

View File

@@ -6,16 +6,19 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
@@ -28,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -58,7 +62,7 @@ public class WatermarkController {
int heightSpacer = request.getHeightSpacer(); int heightSpacer = request.getHeightSpacer();
// Load the input PDF // Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream()); PDDocument document = Loader.loadPDF(pdfFile.getBytes());
// Create a page in the document // Create a page in the document
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
@@ -66,14 +70,14 @@ public class WatermarkController {
// Get the page's content stream // Get the page's content stream
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true); document, page, PDPageContentStream.AppendMode.APPEND, true, true);
// Set transparency // Set transparency
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity); graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState); contentStream.setGraphicsStateParameters(graphicsState);
if (watermarkType.equalsIgnoreCase("text")) { if ("text".equalsIgnoreCase(watermarkType)) {
addTextWatermark( addTextWatermark(
contentStream, contentStream,
watermarkText, watermarkText,
@@ -84,7 +88,7 @@ public class WatermarkController {
heightSpacer, heightSpacer,
fontSize, fontSize,
alphabet); alphabet);
} else if (watermarkType.equalsIgnoreCase("image")) { } else if ("image".equalsIgnoreCase(watermarkType)) {
addImageWatermark( addImageWatermark(
contentStream, contentStream,
watermarkImage, watermarkImage,
@@ -102,7 +106,9 @@ public class WatermarkController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf"); Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_watermarked.pdf");
} }
private void addTextWatermark( private void addTextWatermark(
@@ -117,7 +123,7 @@ public class WatermarkController {
String alphabet) String alphabet)
throws IOException { throws IOException {
String resourceDir = ""; String resourceDir = "";
PDFont font = PDType1Font.HELVETICA_BOLD; PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
switch (alphabet) { switch (alphabet) {
case "arabic": case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf"; resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
@@ -137,10 +143,10 @@ public class WatermarkController {
break; break;
} }
if (!resourceDir.equals("")) { if (!"".equals(resourceDir)) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir); ClassPathResource classPathResource = new ClassPathResource(resourceDir);
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(".")); String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
File tempFile = File.createTempFile("NotoSansFont", fileExtension); File tempFile = Files.createTempFile("NotoSansFont", fileExtension).toFile();
try (InputStream is = classPathResource.getInputStream(); try (InputStream is = classPathResource.getInputStream();
FileOutputStream os = new FileOutputStream(tempFile)) { FileOutputStream os = new FileOutputStream(tempFile)) {
IOUtils.copy(is, os); IOUtils.copy(is, os);
@@ -153,9 +159,16 @@ public class WatermarkController {
contentStream.setFont(font, fontSize); contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY); contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
String[] textLines = watermarkText.split("\\\\n");
float maxLineWidth = 0;
for (int i = 0; i < textLines.length; ++i) {
maxLineWidth = Math.max(maxLineWidth, font.getStringWidth(textLines[i]));
}
// Set size and location of text watermark // Set size and location of text watermark
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; float watermarkWidth = widthSpacer + maxLineWidth * fontSize / 1000;
float watermarkHeight = heightSpacer + fontSize; float watermarkHeight = heightSpacer + fontSize * textLines.length;
float pageWidth = page.getMediaBox().getWidth(); float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight(); float pageHeight = page.getMediaBox().getHeight();
int watermarkRows = (int) (pageHeight / watermarkHeight + 1); int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
@@ -170,7 +183,12 @@ public class WatermarkController {
(float) Math.toRadians(rotation), (float) Math.toRadians(rotation),
j * watermarkWidth, j * watermarkWidth,
i * watermarkHeight)); i * watermarkHeight));
contentStream.showText(watermarkText);
for (int k = 0; k < textLines.length; ++k) {
contentStream.showText(textLines[k]);
contentStream.newLineAtOffset(0, -fontSize);
}
contentStream.endText(); contentStream.endText();
} }
} }

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.web;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +34,8 @@ public class AccountWebController {
return "redirect:/"; return "redirect:/";
} }
model.addAttribute("currentPage", "login");
if (request.getParameter("error") != null) { if (request.getParameter("error") != null) {
model.addAttribute("error", request.getParameter("error")); model.addAttribute("error", request.getParameter("error"));
@@ -53,6 +56,7 @@ public class AccountWebController {
public String showAddUserForm(Model model, Authentication authentication) { public String showAddUserForm(Model model, Authentication authentication) {
List<User> allUsers = userRepository.findAll(); List<User> allUsers = userRepository.findAll();
Iterator<User> iterator = allUsers.iterator(); Iterator<User> iterator = allUsers.iterator();
Map<String, String> roleDetails = Role.getAllRoleDetails();
while (iterator.hasNext()) { while (iterator.hasNext()) {
User user = iterator.next(); User user = iterator.next();
@@ -60,6 +64,7 @@ public class AccountWebController {
for (Authority authority : user.getAuthorities()) { for (Authority authority : user.getAuthorities()) {
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
iterator.remove(); iterator.remove();
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
break; // Break out of the inner loop once the user is removed break; // Break out of the inner loop once the user is removed
} }
} }
@@ -68,6 +73,7 @@ public class AccountWebController {
model.addAttribute("users", allUsers); model.addAttribute("users", allUsers);
model.addAttribute("currentUsername", authentication.getName()); model.addAttribute("currentUsername", authentication.getName());
model.addAttribute("roleDetails", roleDetails);
return "addUsers"; return "addUsers";
} }
@@ -112,6 +118,7 @@ public class AccountWebController {
model.addAttribute("role", user.get().getRolesAsString()); model.addAttribute("role", user.get().getRolesAsString());
model.addAttribute("settings", settingsJson); model.addAttribute("settings", settingsJson);
model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
model.addAttribute("currentPage", "account");
} }
} else { } else {
return "redirect:/"; return "redirect:/";

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -12,6 +13,22 @@ import io.swagger.v3.oas.annotations.tags.Tag;
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ConverterWebController { public class ConverterWebController {
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/book-to-pdf")
@Hidden
public String convertBookToPdfForm(Model model) {
model.addAttribute("currentPage", "book-to-pdf");
return "convert/book-to-pdf";
}
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/img-to-pdf") @GetMapping("/img-to-pdf")
@Hidden @Hidden
public String convertImgToPdfForm(Model model) { public String convertImgToPdfForm(Model model) {

View File

@@ -1,7 +1,8 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -38,7 +39,8 @@ public class HomeWebController {
model.addAttribute("currentPage", "licenses"); model.addAttribute("currentPage", "licenses");
Resource resource = new ClassPathResource("static/3rdPartyLicenses.json"); Resource resource = new ClassPathResource("static/3rdPartyLicenses.json");
try { try {
String json = new String(Files.readAllBytes(resource.getFile().toPath())); InputStream is = resource.getInputStream();
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
Map<String, List<Dependency>> data = Map<String, List<Dependency>> data =
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {}); mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});

View File

@@ -84,7 +84,7 @@ public class MetricsController {
for (Meter meter : meterRegistry.getMeters()) { for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) { if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method"); String method = meter.getId().getTag("method");
if (method != null && method.equals("GET")) { if (method != null && "GET".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) { if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) { if (!endpoint.get().startsWith("/")) {
@@ -129,7 +129,7 @@ public class MetricsController {
for (Meter meter : meterRegistry.getMeters()) { for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) { if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method"); String method = meter.getId().getTag("method");
if (method != null && method.equals("GET")) { if (method != null && "GET".equals(method)) {
String uri = meter.getId().getTag("uri"); String uri = meter.getId().getTag("uri");
if (uri != null) { if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0); double currentCount = counts.getOrDefault(uri, 0.0);
@@ -197,7 +197,7 @@ public class MetricsController {
for (Meter meter : meterRegistry.getMeters()) { for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) { if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method"); String method = meter.getId().getTag("method");
if (method != null && method.equals("POST")) { if (method != null && "POST".equals(method)) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) { if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) { if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get()); endpoint = Optional.of("/" + endpoint.get());
@@ -235,7 +235,7 @@ public class MetricsController {
for (Meter meter : meterRegistry.getMeters()) { for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) { if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method"); String method = meter.getId().getTag("method");
if (method != null && method.equals("POST")) { if (method != null && "POST".equals(method)) {
String uri = meter.getId().getTag("uri"); String uri = meter.getId().getTag("uri");
if (uri != null) { if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0); double currentCount = counts.getOrDefault(uri, 0.0);

View File

@@ -39,6 +39,13 @@ public class OtherWebController {
return "misc/show-javascript"; return "misc/show-javascript";
} }
@GetMapping("/stamp")
@Hidden
public String stampForm(Model model) {
model.addAttribute("currentPage", "stamp");
return "misc/stamp";
}
@GetMapping("/add-page-numbers") @GetMapping("/add-page-numbers")
@Hidden @Hidden
public String addPageNumbersForm(Model model) { public String addPageNumbersForm(Model model) {
@@ -75,7 +82,7 @@ public class OtherWebController {
} }
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata"; String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
if (files == null) { if (files == null) {
return Collections.emptyList(); return Collections.emptyList();

View File

@@ -1,34 +1,43 @@
package stirling.software.SPDF.model; package stirling.software.SPDF.model;
import java.util.LinkedHashMap;
import java.util.Map;
public enum Role { public enum Role {
// Unlimited access // Unlimited access
ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE), ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.admin"),
// Unlimited access // Unlimited access
USER("ROLE_USER", Integer.MAX_VALUE, Integer.MAX_VALUE), USER("ROLE_USER", Integer.MAX_VALUE, Integer.MAX_VALUE, "adminUserSettings.user"),
// 40 API calls Per Day, 40 web calls // 40 API calls Per Day, 40 web calls
LIMITED_API_USER("ROLE_LIMITED_API_USER", 40, 40), LIMITED_API_USER("ROLE_LIMITED_API_USER", 40, 40, "adminUserSettings.apiUser"),
// 20 API calls Per Day, 20 web calls // 20 API calls Per Day, 20 web calls
EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20), EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20, "adminUserSettings.extraApiUser"),
// 0 API calls per day and 20 web calls // 0 API calls per day and 20 web calls
WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20), WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20, "adminUserSettings.webOnlyUser"),
INTERNAL_API_USER("STIRLING-PDF-BACKEND-API-USER", Integer.MAX_VALUE, Integer.MAX_VALUE), INTERNAL_API_USER(
"STIRLING-PDF-BACKEND-API-USER",
Integer.MAX_VALUE,
Integer.MAX_VALUE,
"adminUserSettings.internalApiUser"),
DEMO_USER("ROLE_DEMO_USER", 100, 100); DEMO_USER("ROLE_DEMO_USER", 100, 100, "adminUserSettings.demoUser");
private final String roleId; private final String roleId;
private final int apiCallsPerDay; private final int apiCallsPerDay;
private final int webCallsPerDay; private final int webCallsPerDay;
private final String roleName;
Role(String roleId, int apiCallsPerDay, int webCallsPerDay) { Role(String roleId, int apiCallsPerDay, int webCallsPerDay, String roleName) {
this.roleId = roleId; this.roleId = roleId;
this.apiCallsPerDay = apiCallsPerDay; this.apiCallsPerDay = apiCallsPerDay;
this.webCallsPerDay = webCallsPerDay; this.webCallsPerDay = webCallsPerDay;
this.roleName = roleName;
} }
public String getRoleId() { public String getRoleId() {
@@ -43,6 +52,27 @@ public enum Role {
return webCallsPerDay; return webCallsPerDay;
} }
public String getRoleName() {
return roleName;
}
public static String getRoleNameByRoleId(String roleId) {
// Using the fromString method to get the Role enum based on the roleId
Role role = fromString(roleId);
// Return the roleName of the found Role enum
return role.getRoleName();
}
// Method to retrieve all role IDs and role names
public static Map<String, String> getAllRoleDetails() {
// Using LinkedHashMap to preserve order
Map<String, String> roleDetails = new LinkedHashMap<>();
for (Role role : Role.values()) {
roleDetails.put(role.getRoleId(), role.getRoleName());
}
return roleDetails;
}
public static Role fromString(String roleId) { public static Role fromString(String roleId) {
for (Role role : Role.values()) { for (Role role : Role.values()) {
if (role.getRoleId().equalsIgnoreCase(roleId)) { if (role.getRoleId().equalsIgnoreCase(roleId)) {

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