Compare commits

...

184 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
309 changed files with 27547 additions and 24542 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

@@ -1,47 +1,62 @@
# Use the base image # Main stage
FROM frooodle/stirling-pdf-base:version8 FROM alpine:3.19.1
# Copy necessary files
COPY scripts /scripts
COPY pipeline /pipeline
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 build/libs/*.jar app.jar
ARG VERSION_TAG ARG VERSION_TAG
# Set Environment Variables # Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \ ENV DOCKER_ENABLE_SECURITY=false \
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 \ HOME=/home/stirlingpdfuser \
# PGID=1000 \ PUID=1000 \
# UMASK=022 \ PGID=1000 \
UMASK=022
# Create user and group # JDK for app
##RUN groupadd -g $PGID stirlingpdfgroup && \ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME 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 \
# OCR MY PDF (unpaper for descew and other advanced featues)
ocrmypdf \
tesseract-ocr-data-eng \
# CV
py3-opencv \
# python3/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 && \
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
# Set up necessary directories and permissions
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 && \
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original
# Copy necessary files
COPY ./scripts/* /scripts/
COPY ./pipeline/ /pipeline/
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 build/libs/*.jar app.jar
# Set font cache and permissions
RUN fc-cache -f -v && chmod +x /scripts/*
##&& \
## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
## chmod +x /scripts/init.sh
# Expose necessary ports
EXPOSE 8080 EXPOSE 8080
# Set user and run command # Set user and run command
##USER stirlingpdfuser ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
ENTRYPOINT ["/scripts/init.sh"]
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] 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

@@ -12,7 +12,7 @@ https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/temp
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
@@ -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,8 +2,8 @@
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.
@@ -21,7 +21,7 @@ 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.
@@ -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

View File

@@ -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:
@@ -139,7 +139,7 @@ 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,7 +264,7 @@ 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"

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)
@@ -110,10 +110,11 @@ Docker Run
```bash ```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
@@ -131,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".
@@ -172,7 +174,7 @@ 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)
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)
@@ -228,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
@@ -262,7 +265,7 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
- 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,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.20.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'
@@ -147,8 +149,8 @@ dependencies {
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'
@@ -156,7 +158,7 @@ dependencies {
// 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'
} }

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

@@ -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">
<linearGradient
id="XMLID_5_"
gradientUnits="userSpaceOnUse"
x1="304.496"
y1="422.9102"
x2="316.036"
y2="326.2626">
<stop
offset="0"
style="stop-color:#DCF1F3"
id="stop156" />
<stop
offset="1"
style="stop-color:#C2C2C9"
id="stop158" />
</linearGradient>
</defs><sodipodi:namedview
id="namedview171"
pagecolor="#ffffff" pagecolor="#ffffff"
bordercolor="#666666" bordercolor="#000000"
borderopacity="1.0" borderopacity="0.25"
inkscape:showpageshadow="2" inkscape:showpageshadow="2"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0" inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false" showgrid="false"
inkscape:zoom="0.914906" inkscape:zoom="1.4142136"
inkscape:cx="184.17193" inkscape:cx="219.91021"
inkscape:cy="509.88845" inkscape:cy="232.63813"
inkscape:window-width="2293" inkscape:window-width="3840"
inkscape:window-height="1387" inkscape:window-height="2054"
inkscape:window-x="122" inkscape:window-x="2869"
inkscape:window-y="25" inkscape:window-y="-11"
inkscape:window-maximized="0" inkscape:window-maximized="1"
inkscape:current-layer="svg745" /><defs inkscape:current-layer="XMLID_4_" />
id="defs742"><linearGradient <style
inkscape:collect="always" type="text/css"
id="linearGradient72198"><stop id="style150">
style="stop-color:#ccd6d7;stop-opacity:1;" .st0{fill:#FFFFFF;}
.st1{fill:#C02223;}
.st2{fill:#882425;}
.st3{fill:url(#XMLID_5_);}
.st4{fill:url(#XMLID_7_);}
</style>
<g
id="XMLID_4_">
<path
id="XMLID_131_"
class="st1"
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"
sodipodi:nodetypes="ccssccccccccc"
style="stroke-width:1.45391" /><path
id="XMLID_117_"
class="st2"
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"
style="stroke-width:1.45391" /><polygon
id="XMLID_18_"
class="st3"
points="234.7,422.6 368.5,387.7 393.5,262.2 "
style="fill:url(#XMLID_5_)"
transform="matrix(1.4556308,0,0,1.4548265,-116.73161,-116.45231)" />
<linearGradient
id="XMLID_7_"
gradientUnits="userSpaceOnUse"
x1="223.0838"
y1="372.7559"
x2="241.4174"
y2="114.557"
gradientTransform="matrix(1.4539039,0,0,1.4539039,-116.19976,-116.20474)">
<stop
offset="0" offset="0"
id="stop72194" /><stop style="stop-color:#DCF1F3"
style="stop-color:#0f3a3f;stop-opacity:1;" id="stop163" />
<stop
offset="1" offset="1"
id="stop72196" /></linearGradient><linearGradient style="stop-color:#C2C2C9"
inkscape:collect="always" id="stop165" />
id="linearGradient71928"><stop </linearGradient>
style="stop-color:#4b0005;stop-opacity:1;" <path
offset="0" id="XMLID_6_"
id="stop71924" /><stop class="st4"
style="stop-color:#8f000c;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="1" style="fill:url(#XMLID_7_);stroke-width:1.45391" />
id="stop71926" /></linearGradient><linearGradient </g>
inkscape:collect="always" </svg>
id="linearGradient71920"><stop
style="stop-color:#4b0005;stop-opacity:1;"
offset="0"
id="stop71916" /><stop
style="stop-color:#8f000c;stop-opacity:1;"
offset="1"
id="stop71918" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient69598"><stop
style="stop-color:#6a0007;stop-opacity:1;"
offset="0"
id="stop69594" /><stop
style="stop-color:#b8000f;stop-opacity:1;"
offset="1"
id="stop69596" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient46361"><stop
style="stop-color:#f7f6f8;stop-opacity:1;"
offset="0"
id="stop46359" /><stop
style="stop-color:#b7b7b5;stop-opacity:1;"
offset="1"
id="stop46357" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient40554"><stop
style="stop-color:#f4f2f4;stop-opacity:1;"
offset="0"
id="stop40550" /><stop
style="stop-color:#57767b;stop-opacity:1;"
offset="1"
id="stop40552" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient39095"><stop
style="stop-color:#285459;stop-opacity:1;"
offset="0"
id="stop39093" /><stop
style="stop-color:#a6b6af;stop-opacity:1;"
offset="1"
id="stop39091" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient36672"><stop
style="stop-color:#da453f;stop-opacity:1;"
offset="0"
id="stop36668" /><stop
style="stop-color:#a60008;stop-opacity:1;"
offset="1"
id="stop36670" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient19524"><stop
style="stop-color:#3a4b4f;stop-opacity:1;"
offset="0"
id="stop19522" /><stop
style="stop-color:#617979;stop-opacity:0.97461927;"
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

View File

@@ -6,7 +6,8 @@
"parameters": { "parameters": {
"horizontalDivisions": 2, "horizontalDivisions": 2,
"verticalDivisions": 2, "verticalDivisions": 2,
"fileInput": "automated" "fileInput": "automated",
"merge": false
} }
}, },
{ {

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

@@ -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

@@ -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

@@ -77,16 +77,12 @@ public class AppConfig {
return Files.exists(Paths.get("/.dockerenv")); return Files.exists(Paths.get("/.dockerenv"));
} }
@Bean(name = "bookFormatsInstalled") @Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookFormatsInstalled() { public boolean bookAndHtmlFormatsInstalled() {
return applicationProperties.getSystem().getCustomApplications().isInstallBookFormats(); 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);
@Bean(name = "htmlFormatsInstalled")
public boolean htmlFormatsInstalled() {
return applicationProperties
.getSystem()
.getCustomApplications()
.isInstallAdvancedHtmlToPDF();
} }
} }

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)) {
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); 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

@@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Service @Service
@DependsOn({"bookFormatsInstalled"}) @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<>();
@@ -24,14 +24,14 @@ public class EndpointConfiguration {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private boolean bookFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;
@Autowired @Autowired
public EndpointConfiguration( public EndpointConfiguration(
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
@Qualifier("bookFormatsInstalled") boolean bookFormatsInstalled) { @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.bookFormatsInstalled = bookFormatsInstalled; this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
init(); init();
processEnvironmentConfigs(); processEnvironmentConfigs();
} }
@@ -140,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");
@@ -218,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");
@@ -229,7 +229,7 @@ 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 (!bookFormatsInstalled) { if (!bookAndHtmlFormatsInstalled) {
groupsToRemove.add("Calibre"); groupsToRemove.add("Calibre");
} }
if (endpointsToRemove != null) { if (endpointsToRemove != null) {

View File

@@ -1,98 +0,0 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@Component
public class PostStartupProcesses {
@Autowired ApplicationProperties applicationProperties;
@Autowired
@Qualifier("RunningInDocker")
private boolean runningInDocker;
@Autowired
@Qualifier("bookFormatsInstalled")
private boolean bookFormatsInstalled;
@Autowired
@Qualifier("htmlFormatsInstalled")
private boolean htmlFormatsInstalled;
private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class);
@PostConstruct
public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException {
List<List<String>> commands = new ArrayList<>();
// Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable
if (bookFormatsInstalled) {
List<String> tmpList = new ArrayList<>();
// Set up the timezone configuration commands
tmpList.addAll(
Arrays.asList(
"sh",
"-c",
"echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections; "
+ "echo 'tzdata tzdata/Zones/Europe select Berlin' | debconf-set-selections"));
commands.add(tmpList);
// Install calibre with DEBIAN_FRONTEND set to noninteractive
tmpList = new ArrayList<>();
tmpList.addAll(
Arrays.asList(
"sh",
"-c",
"DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends calibre"));
commands.add(tmpList);
}
// Checking for DOCKER_INSTALL_HTML_FORMATS environment variable
if (htmlFormatsInstalled) {
List<String> tmpList = new ArrayList<>();
// Add -y flag for automatic yes to prompts and --no-install-recommends to reduce size
tmpList.addAll(
Arrays.asList(
"apt-get", "install", "wkhtmltopdf", "-y", "--no-install-recommends"));
commands.add(tmpList);
}
if (!commands.isEmpty()) {
// Run the command
if (runningInDocker) {
List<String> tmpList = new ArrayList<>();
tmpList.addAll(Arrays.asList("apt-get", "update"));
commands.add(0, tmpList);
for (List<String> list : commands) {
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.INSTALL_APP, true)
.runCommandWithOutputHandling(list);
logger.info("RC for app installs {}", returnCode.getRc());
}
} else {
logger.info(
"Not running inside Docker so skipping automated install process with command.");
}
} else {
if (runningInDocker) {
logger.info("No custom apps to install.");
}
}
}
}

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 (!isDemoUser(username)) {
if (loginAttemptService.loginAttemptCheck(username)) { if (loginAttemptService.loginAttemptCheck(username)) {
setDefaultFailureUrl("/login?error=locked"); setDefaultFailureUrl("/login?error=locked");
} else { } else {
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { if (exception.getClass().isAssignableFrom(LockedException.class)) {
setDefaultFailureUrl("/login?error=badcredentials");
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
setDefaultFailureUrl("/login?error=locked"); 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

@@ -38,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()) {

View File

@@ -21,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;
@@ -136,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,6 +3,7 @@ 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;
@@ -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;
@@ -75,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(
@@ -135,7 +138,7 @@ public class PdfOverlayController {
try (PDDocument overlayPdf = Loader.loadPDF(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();

View File

@@ -2,6 +2,7 @@ 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.Loader;
@@ -16,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;
@@ -49,7 +51,9 @@ public class RearrangePagesPDFController {
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);
@@ -57,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) {
@@ -189,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);
@@ -211,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

@@ -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;
@@ -49,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

@@ -22,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;
@@ -112,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

@@ -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;
@@ -49,9 +50,13 @@ public class SplitPDFController {
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = Loader.loadPDF(file.getBytes());
List<Integer> pageNumbers = request.getPageNumbersList(document); List<Integer> pageNumbers = request.getPageNumbersList(document, true);
if (!pageNumbers.contains(document.getNumberOfPages() - 1)) 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

@@ -26,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;
@@ -52,8 +53,21 @@ public class SplitPdfBySectionsController {
// 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);
@@ -64,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))) {
@@ -122,7 +135,7 @@ public class SplitPdfBySectionsController {
// Set clipping area and position // Set clipping area and position
float translateX = -subPageWidth * i; float translateX = -subPageWidth * i;
//float translateY = height - subPageHeight * (verticalDivisions - j); // float translateY = height - subPageHeight * (verticalDivisions - j);
float translateY = -subPageHeight * (verticalDivisions - 1 - j); float translateY = -subPageHeight * (verticalDivisions - 1 - j);
contentStream.saveGraphicsState(); contentStream.saveGraphicsState();

View File

@@ -20,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;
@@ -120,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

@@ -10,6 +10,9 @@ 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;
@@ -28,7 +31,6 @@ 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.UpdateUserDetails;
import stirling.software.SPDF.model.api.user.UsernameAndPass; import stirling.software.SPDF.model.api.user.UsernameAndPass;
@Controller @Controller
@@ -50,53 +52,6 @@ public class UserController {
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,
@ModelAttribute UpdateUserDetails requestModel,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
String currentPassword = requestModel.getPassword();
String newPassword = requestModel.getNewPassword();
String newUsername = requestModel.getNewUsername();
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(
@@ -136,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(
@@ -219,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);
return new RedirectView("/addUsers");
} }
userService.deleteUser(username); @Autowired private SessionRegistry sessionRegistry;
return "redirect:/addUsers";
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

@@ -9,6 +9,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,28 +23,28 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertBookToPDFController { public class ConvertBookToPDFController {
@Autowired @Autowired
@Qualifier("bookFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf") @PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
@Operation( @Operation(
summary = summary =
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF", "Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
description = description =
"(Requires bookFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.") "(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 { public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
if (!bookFormatsInstalled) { if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"bookFormatsInstalled flag is False, this functionality is not avaiable"); "bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
} }
if (fileInput == null) { if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion."); throw new IllegalArgumentException("Please provide a file for conversion.");
} }
String originalFilename = fileInput.getOriginalFilename(); String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
if (originalFilename != null) { if (originalFilename != null) {
String originalFilenameLower = originalFilename.toLowerCase(); String originalFilenameLower = originalFilename.toLowerCase();

View File

@@ -9,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;
@@ -22,15 +23,16 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertHtmlToPDF { public class ConvertHtmlToPDF {
@Autowired @Autowired
@Qualifier("htmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean htmlFormatsInstalled; 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) {
@@ -38,14 +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 = byte[] pdfBytes =
FileToPdf.convertHtmlToPdf( FileToPdf.convertHtmlToPdf(
fileInput.getBytes(), originalFilename, htmlFormatsInstalled); 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

@@ -19,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;
@@ -32,14 +33,14 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertMarkdownToPdf { public class ConvertMarkdownToPdf {
@Autowired @Autowired
@Qualifier("htmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean htmlFormatsInstalled; 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();
@@ -48,7 +49,7 @@ 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.");
} }
@@ -68,7 +69,10 @@ public class ConvertMarkdownToPdf {
byte[] pdfBytes = byte[] pdfBytes =
FileToPdf.convertHtmlToPdf( FileToPdf.convertHtmlToPdf(
htmlContent.getBytes(), "converted.html", htmlFormatsInstalled); null,
htmlContent.getBytes(),
"converted.html",
bookAndHtmlFormatsInstalled);
String outputFilename = String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "") originalFilename.replaceFirst("[.][^.]+$", "")

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

@@ -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;
@@ -29,22 +30,22 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertPDFToBookController { public class ConvertPDFToBookController {
@Autowired @Autowired
@Qualifier("bookFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book") @PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
@Operation( @Operation(
summary = summary =
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF", "Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
description = description =
"(Requires bookFormatsInstalled 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") "(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) public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
if (!bookFormatsInstalled) { if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"bookFormatsInstalled flag is False, this functionality is not avaiable"); "bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
} }
if (fileInput == null) { if (fileInput == null) {
@@ -92,7 +93,8 @@ public class ConvertPDFToBookController {
} }
String outputFilename = String outputFilename =
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "." + "."
+ outputFormat; // Remove file extension and append .pdf + outputFormat; // Remove file extension and append .pdf

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,10 +65,22 @@ 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())) {
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
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(); PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import"); return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
} }
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word") @PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
@Operation( @Operation(

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

@@ -29,8 +29,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertWebsiteToPDF { public class ConvertWebsiteToPDF {
@Autowired @Autowired
@Qualifier("htmlFormatsInstalled") @Qualifier("bookAndHtmlFormatsInstalled")
private boolean htmlFormatsInstalled; private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@Operation( @Operation(
@@ -53,7 +53,7 @@ public class ConvertWebsiteToPDF {
// Prepare the OCRmyPDF command // Prepare the OCRmyPDF command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
if (!htmlFormatsInstalled) { if (!bookAndHtmlFormatsInstalled) {
command.add("weasyprint"); command.add("weasyprint");
} else { } else {
command.add("wkhtmltopdf"); command.add("wkhtmltopdf");

View File

@@ -13,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;
@@ -43,7 +44,7 @@ public class FilterController {
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes()); 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;
} }
@@ -60,7 +61,7 @@ public class FilterController {
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes()); 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;
} }

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;
@@ -133,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

@@ -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(
@@ -63,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();
@@ -77,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++;
} }
} }
@@ -95,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,23 +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.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;
@@ -27,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
@@ -41,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",
@@ -63,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 = if (blank) {
new ArrayList<>( logger.info("Skipping, Image was blank for page #" + pageIndex);
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 { } else {
System.out.println("Skipping, Image was blank for page #" + pageIndex); 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());
@@ -125,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();
@@ -134,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

@@ -29,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;
@@ -264,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

@@ -73,8 +73,14 @@ public class ExtractImageScansController {
List<String> images = new ArrayList<>(); List<String> images = new ArrayList<>();
List<Path> tempImageFiles = new ArrayList<>();
Path tempInputFile = null;
Path tempZipFile = null;
List<Path> tempDirs = new ArrayList<>();
try {
// Check if input file is a PDF // Check if input file is a PDF
if (extension.equalsIgnoreCase("pdf")) { if ("pdf".equalsIgnoreCase(extension)) {
// Load PDF document // Load PDF document
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
@@ -92,10 +98,11 @@ public class ExtractImageScansController {
// Add temp file path to images list // Add temp file path to images list
images.add(tempFile.toString()); images.add(tempFile.toString());
tempImageFiles.add(tempFile);
} }
} }
} else { } else {
Path tempInputFile = Files.createTempFile("input_", "." + extension); tempInputFile = Files.createTempFile("input_", "." + extension);
Files.copy( Files.copy(
form.getFileInput().getInputStream(), form.getFileInput().getInputStream(),
tempInputFile, tempInputFile,
@@ -110,6 +117,7 @@ public class ExtractImageScansController {
for (int i = 0; i < images.size(); i++) { for (int i = 0; i < images.size(); i++) {
Path tempDir = Files.createTempDirectory("openCV_output"); Path tempDir = Files.createTempDirectory("openCV_output");
tempDirs.add(tempDir);
List<String> command = List<String> command =
new ArrayList<>( new ArrayList<>(
Arrays.asList( Arrays.asList(
@@ -146,7 +154,7 @@ public class ExtractImageScansController {
// Create zip file if multiple images // Create zip file if multiple images
if (processedImageBytes.size() > 1) { if (processedImageBytes.size() > 1) {
String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip"; String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip"); tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = try (ZipOutputStream zipOut =
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
@@ -179,5 +187,31 @@ public class ExtractImageScansController {
fileName.replaceFirst("[.][^.]+$", "") + ".png", fileName.replaceFirst("[.][^.]+$", "") + ".png",
MediaType.IMAGE_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);
}
});
if (tempZipFile != null && Files.exists(tempZipFile)) {
try {
Files.delete(tempZipFile);
} catch (IOException e) {
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
}
}
tempDirs.forEach(dir -> {
try {
FileUtils.deleteDirectory(dir.toFile());
} catch (IOException e) {
logger.error("Failed to delete temporary directory: " + dir, e);
}
});
}
} }
} }

View File

@@ -29,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;
@@ -66,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
@@ -85,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

@@ -29,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;
@@ -50,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 =
@@ -142,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

@@ -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;
@@ -109,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());
@@ -164,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

@@ -21,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;
@@ -93,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);
@@ -145,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

@@ -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;
@@ -54,7 +55,8 @@ public class ShowJavascript {
script += script +=
"// File: " "// File: "
+ inputFile.getOriginalFilename() + Filenames.toSimpleFileName(
inputFile.getOriginalFilename())
+ ", Script: " + ", Script: "
+ name + name
+ "\n" + "\n"
@@ -66,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,7 +98,8 @@ 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) {
if ("ALL".equals(extension) || file.getFilename().endsWith(extension)) {
hasInputFileType = true; hasInputFileType = true;
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("fileInput", file); body.add("fileInput", file);
@@ -109,11 +110,13 @@ public class PipelineProcessor {
ResponseEntity<byte[]> response = sendWebRequest(url, body); ResponseEntity<byte[]> response = sendWebRequest(url, body);
// If the operation is filter and the response body is null or empty, skip // If the operation is filter and the response body is null or empty,
// skip
// this // this
// file // file
if (operation.startsWith("filter-") if (operation.startsWith("filter-")
&& (response.getBody() == null || response.getBody().length == 0)) { && (response.getBody() == null
|| response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation); logger.info("Skipping file due to failing {}", operation);
continue; continue;
} }
@@ -123,13 +126,14 @@ public class PipelineProcessor {
hasErrors = true; hasErrors = true;
continue; continue;
} }
processOutputFiles(operation, file.getFilename(), response, newOutputFiles); processOutputFiles(operation, 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;
if (inputFileTypes.contains("ALL")) {
matchingFiles = new ArrayList<>(outputFiles);
} else {
final List<String> finalinputFileTypes = inputFileTypes;
matchingFiles =
outputFiles.stream() outputFiles.stream()
.filter( .filter(
file -> file ->
file.getFilename() finalinputFileTypes.stream()
.endsWith(finalInputFileExtension)) .anyMatch(file.getFilename()::endsWith))
.collect(Collectors.toList()); .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

@@ -42,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;
@@ -74,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();
@@ -123,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(

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;
@@ -43,7 +44,8 @@ public class PasswordController {
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");
} }
@@ -88,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

@@ -25,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;
@@ -104,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

@@ -28,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;
@@ -76,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,6 +6,7 @@ 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;
@@ -30,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;
@@ -75,7 +77,7 @@ public class WatermarkController {
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,
@@ -86,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,
@@ -104,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(
@@ -139,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);
@@ -155,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);
@@ -172,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

@@ -13,7 +13,7 @@ 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("#{bookFormatsInstalled}") @ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/book-to-pdf") @GetMapping("/book-to-pdf")
@Hidden @Hidden
public String convertBookToPdfForm(Model model) { public String convertBookToPdfForm(Model model) {
@@ -21,7 +21,7 @@ public class ConverterWebController {
return "convert/book-to-pdf"; return "convert/book-to-pdf";
} }
@ConditionalOnExpression("#{bookFormatsInstalled}") @ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book") @GetMapping("/pdf-to-book")
@Hidden @Hidden
public String convertPdfToBookForm(Model model) { public String convertPdfToBookForm(Model model) {

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

@@ -210,7 +210,6 @@ public class ApplicationProperties {
private String rootURIPath; private String rootURIPath;
private String customStaticFilePath; private String customStaticFilePath;
private Integer maxFileSize; private Integer maxFileSize;
private CustomApplications customApplications;
private Boolean enableAlphaFunctionality; private Boolean enableAlphaFunctionality;
@@ -262,14 +261,6 @@ public class ApplicationProperties {
this.maxFileSize = maxFileSize; this.maxFileSize = maxFileSize;
} }
public CustomApplications getCustomApplications() {
return customApplications != null ? customApplications : new CustomApplications();
}
public void setCustomApplications(CustomApplications customApplications) {
this.customApplications = customApplications;
}
@Override @Override
public String toString() { public String toString() {
return "System [defaultLocale=" return "System [defaultLocale="
@@ -282,42 +273,10 @@ public class ApplicationProperties {
+ customStaticFilePath + customStaticFilePath
+ ", maxFileSize=" + ", maxFileSize="
+ maxFileSize + maxFileSize
+ ", customApplications="
+ customApplications
+ ", enableAlphaFunctionality=" + ", enableAlphaFunctionality="
+ enableAlphaFunctionality + enableAlphaFunctionality
+ "]"; + "]";
} }
public static class CustomApplications {
private boolean installBookFormats;
private boolean installAdvancedHtmlToPDF;
public boolean isInstallBookFormats() {
return installBookFormats;
}
public void setInstallBookFormats(boolean installBookFormats) {
this.installBookFormats = installBookFormats;
}
public boolean isInstallAdvancedHtmlToPDF() {
return installAdvancedHtmlToPDF;
}
public void setInstallAdvancedHtmlToPDF(boolean installAdvancedHtmlToPDF) {
this.installAdvancedHtmlToPDF = installAdvancedHtmlToPDF;
}
@Override
public String toString() {
return "CustomApplications [installBookFormats="
+ installBookFormats
+ ", installAdvancedHtmlToPDF="
+ installAdvancedHtmlToPDF
+ "]";
}
}
} }
public static class Ui { public static class Ui {

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)) {

View File

@@ -44,6 +44,9 @@ public class User {
@Column(name = "isFirstLogin") @Column(name = "isFirstLogin")
private Boolean isFirstLogin = false; private Boolean isFirstLogin = false;
@Column(name = "roleName")
private String roleName;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Authority> authorities = new HashSet<>(); private Set<Authority> authorities = new HashSet<>();
@@ -53,6 +56,10 @@ public class User {
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id")) @CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings. private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings.
public String getRoleName() {
return Role.getRoleNameByRoleId(getRolesAsString());
}
public boolean isFirstLogin() { public boolean isFirstLogin() {
return isFirstLogin != null && isFirstLogin; return isFirstLogin != null && isFirstLogin;
} }

View File

@@ -25,7 +25,7 @@ public class PDFWithPageNums extends PDFFile {
private String pageNumbers; private String pageNumbers;
@Hidden @Hidden
public List<Integer> getPageNumbersList() { public List<Integer> getPageNumbersList(boolean zeroCount) {
int pageCount = 0; int pageCount = 0;
try { try {
pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages(); pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages();
@@ -33,13 +33,13 @@ public class PDFWithPageNums extends PDFFile {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return GeneralUtils.parsePageString(pageNumbers, pageCount); return GeneralUtils.parsePageString(pageNumbers, pageCount, zeroCount);
} }
@Hidden @Hidden
public List<Integer> getPageNumbersList(PDDocument doc) { public List<Integer> getPageNumbersList(PDDocument doc, boolean zeroCount) {
int pageCount = 0; int pageCount = 0;
pageCount = doc.getNumberOfPages(); pageCount = doc.getNumberOfPages();
return GeneralUtils.parsePageString(pageNumbers, pageCount); return GeneralUtils.parsePageString(pageNumbers, pageCount, zeroCount);
} }
} }

View File

@@ -15,4 +15,7 @@ public class SplitPdfBySectionsRequest extends PDFFile {
@Schema(description = "Number of vertical divisions for each PDF page", example = "2") @Schema(description = "Number of vertical divisions for each PDF page", example = "2")
private int verticalDivisions; private int verticalDivisions;
@Schema(description = "Merge the split documents into a single PDF", example = "true")
private boolean merge;
} }

View File

@@ -0,0 +1,17 @@
package stirling.software.SPDF.model.api.converters;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.SPDF.model.api.PDFFile;
@Data
@EqualsAndHashCode(callSuper = true)
public class HTMLToPdfRequest extends PDFFile {
@Schema(
description = "Zoom level for displaying the website. Default is '1'.",
defaultValue = "1")
private float zoom;
}

View File

@@ -12,6 +12,6 @@ public class PdfToTextOrRTFRequest extends PDFFile {
@Schema( @Schema(
description = "The output Text or RTF format", description = "The output Text or RTF format",
allowableValues = {"rtf", "txt:Text"}) allowableValues = {"rtf", "txt"})
private String outputFormat; private String outputFormat;
} }

View File

@@ -0,0 +1,68 @@
package stirling.software.SPDF.model.api.misc;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.SPDF.model.api.PDFWithPageNums;
@Data
@EqualsAndHashCode(callSuper = true)
public class AddStampRequest extends PDFWithPageNums {
@Schema(
description = "The stamp type (text or image)",
allowableValues = {"text", "image"},
required = true)
private String stampType;
@Schema(description = "The stamp text")
private String stampText;
@Schema(description = "The stamp image")
private MultipartFile stampImage;
@Schema(
description = "The selected alphabet",
allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"},
defaultValue = "roman")
private String alphabet = "roman";
@Schema(description = "The font size of the stamp text", example = "30")
private float fontSize = 30;
@Schema(description = "The rotation of the stamp in degrees", example = "0")
private float rotation = 0;
@Schema(description = "The opacity of the stamp (0.0 - 1.0)", example = "0.5")
private float opacity;
@Schema(
description =
"Position for stamp placement based on a 1-9 grid (1: bottom-left, 2: bottom-center, ..., 9: top-right)",
example = "1")
private int position;
@Schema(
description =
"Override X coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
example = "-1")
private float overrideX = -1; // Default to -1 indicating no override
@Schema(
description =
"Override Y coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
example = "-1")
private float overrideY = -1; // Default to -1 indicating no override
@Schema(
description = "Specifies the margin size for the stamp.",
allowableValues = {"small", "medium", "large", "x-large"},
defaultValue = "medium")
private String customMargin = "medium";
@Schema(description = "The color for stamp", defaultValue = "#d3d3d3")
private String customColor = "#d3d3d3";
}

View File

@@ -1,131 +0,0 @@
package stirling.software.SPDF.pdf;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorName;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
public class ImageFinder extends org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine {
private boolean hasImages = false;
public ImageFinder(PDPage page) {
super(page);
}
public boolean hasImages() {
return hasImages;
}
@Override
protected void processOperator(Operator operator, List<COSBase> operands) throws IOException {
String operation = operator.getName();
if (operation.equals(OperatorName.DRAW_OBJECT)) {
COSBase base = operands.get(0);
if (base instanceof COSName) {
COSName objectName = (COSName) base;
PDXObject xobject = getResources().getXObject(objectName);
if (xobject instanceof PDImageXObject) {
hasImages = true;
} else if (xobject instanceof PDFormXObject) {
PDFormXObject form = (PDFormXObject) xobject;
ImageFinder innerFinder = new ImageFinder(getPage());
innerFinder.processPage(getPage());
if (innerFinder.hasImages()) {
hasImages = true;
}
}
}
}
super.processOperator(operator, operands);
}
@Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void drawImage(PDImage pdImage) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void clip(int windingRule) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void moveTo(float x, float y) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void lineTo(float x, float y) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3)
throws IOException {
// TODO Auto-generated method stub
}
@Override
public Point2D getCurrentPoint() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public void closePath() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void endPath() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void strokePath() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void fillPath(int windingRule) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void fillAndStrokePath(int windingRule) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void shadingFill(COSName shadingName) throws IOException {
// TODO Auto-generated method stub
}
// ... rest of the overridden methods
}

View File

@@ -1,6 +1,8 @@
package stirling.software.SPDF.utils; package stirling.software.SPDF.utils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -11,12 +13,18 @@ import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import io.github.pixee.security.ZipSecurity;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
public class FileToPdf { public class FileToPdf {
public static byte[] convertHtmlToPdf( public static byte[] convertHtmlToPdf(
byte[] fileBytes, String fileName, boolean htmlFormatsInstalled) HTMLToPdfRequest request,
byte[] fileBytes,
String fileName,
boolean htmlFormatsInstalled)
throws IOException, InterruptedException { throws IOException, InterruptedException {
Path tempOutputFile = Files.createTempFile("output_", ".pdf"); Path tempOutputFile = Files.createTempFile("output_", ".pdf");
@@ -27,46 +35,52 @@ public class FileToPdf {
tempInputFile = Files.createTempFile("input_", ".html"); tempInputFile = Files.createTempFile("input_", ".html");
Files.write(tempInputFile, fileBytes); Files.write(tempInputFile, fileBytes);
} else { } else {
tempInputFile = unzipAndGetMainHtml(fileBytes); tempInputFile = Files.createTempFile("input_", ".zip");
Files.write(tempInputFile, fileBytes);
} }
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
if (!htmlFormatsInstalled) { if (!htmlFormatsInstalled) {
command.add("weasyprint"); command.add("weasyprint");
} else {
command.add("wkhtmltopdf");
command.add("--enable-local-file-access");
}
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode;
if (fileName.endsWith(".zip")) {
if (htmlFormatsInstalled) {
// command.add(1, "--allow");
// command.add(2, tempInputFile.getParent().toString());
}
returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(
command, tempInputFile.getParent().toFile());
} else { } else {
command.add("ebook-convert");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
command.add("--paper-size");
command.add("a4");
if (request.getZoom() != 1.0) {
// Create a temporary CSS file
File tempCssFile = Files.createTempFile("customStyle", ".css").toFile();
try (FileWriter writer = new FileWriter(tempCssFile)) {
// Write the CSS rule to the file
writer.write("body { zoom: " + request.getZoom() + "; }");
}
command.add("--extra-css");
command.add(tempCssFile.getAbsolutePath());
}
}
ProcessExecutorResult returnCode;
returnCode = returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
}
pdfBytes = Files.readAllBytes(tempOutputFile); pdfBytes = Files.readAllBytes(tempOutputFile);
} catch (IOException e) {
pdfBytes = Files.readAllBytes(tempOutputFile);
if (pdfBytes.length < 1) {
throw e;
}
} finally { } finally {
// Clean up temporary files // Clean up temporary files
Files.delete(tempOutputFile); Files.delete(tempOutputFile);
Files.delete(tempInputFile); Files.delete(tempInputFile);
if (fileName.endsWith(".zip")) {
GeneralUtils.deleteDirectory(tempInputFile.getParent());
}
} }
return pdfBytes; return pdfBytes;
@@ -74,7 +88,8 @@ public class FileToPdf {
private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException { private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException {
Path tempDirectory = Files.createTempDirectory("unzipped_"); Path tempDirectory = Files.createTempDirectory("unzipped_");
try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(fileBytes))) { try (ZipInputStream zipIn =
ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) {
ZipEntry entry = zipIn.getNextEntry(); ZipEntry entry = zipIn.getNextEntry();
while (entry != null) { while (entry != null) {
Path filePath = tempDirectory.resolve(entry.getName()); Path filePath = tempDirectory.resolve(entry.getName());
@@ -102,7 +117,7 @@ public class FileToPdf {
// Prioritize 'index.html' if it exists, otherwise use the first .html file // Prioritize 'index.html' if it exists, otherwise use the first .html file
for (Path htmlFile : htmlFiles) { for (Path htmlFile : htmlFiles) {
if (htmlFile.getFileName().toString().equals("index.html")) { if ("index.html".equals(htmlFile.getFileName().toString())) {
return htmlFile; return htmlFile;
} }
} }
@@ -130,7 +145,6 @@ public class FileToPdf {
command.add("ebook-convert"); command.add("ebook-convert");
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE) ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);

View File

@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -13,14 +12,18 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls;
public class GeneralUtils { public class GeneralUtils {
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = File.createTempFile("temp", null); File tempFile = Files.createTempFile("temp", null).toFile();
try (FileOutputStream os = new FileOutputStream(tempFile)) { try (FileOutputStream os = new FileOutputStream(tempFile)) {
os.write(multipartFile.getBytes()); os.write(multipartFile.getBytes());
} }
@@ -57,7 +60,8 @@ public class GeneralUtils {
public static boolean isValidURL(String urlStr) { public static boolean isValidURL(String urlStr) {
try { try {
new URL(urlStr); Urls.create(
urlStr, Urls.HTTP_PROTOCOLS, HostValidator.DENY_COMMON_INFRASTRUCTURE_TARGETS);
return true; return true;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
return false; return false;
@@ -112,17 +116,36 @@ public class GeneralUtils {
} }
public static List<Integer> parsePageString(String pageOrder, int totalPages) { public static List<Integer> parsePageString(String pageOrder, int totalPages) {
return parsePageList(pageOrder.split(","), totalPages); return parsePageString(pageOrder, totalPages, false);
}
public static List<Integer> parsePageString(
String pageOrder, int totalPages, boolean isOneBased) {
if (pageOrder == null || pageOrder.isEmpty()) {
return Collections.singletonList(1);
}
if (pageOrder.matches("\\d+")) {
// Convert the single number string to an integer and return it in a list
return Collections.singletonList(Integer.parseInt(pageOrder));
}
return parsePageList(pageOrder.split(","), totalPages, isOneBased);
} }
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) { public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
return parsePageList(pageOrderArr, totalPages, false);
}
public static List<Integer> parsePageList(
String[] pageOrderArr, int totalPages, boolean isOneBased) {
List<Integer> newPageOrder = new ArrayList<>(); List<Integer> newPageOrder = new ArrayList<>();
int adjustmentFactor = isOneBased ? 1 : 0;
// loop through the page order array // loop through the page order array
for (String element : pageOrderArr) { for (String element : pageOrderArr) {
if (element.equalsIgnoreCase("all")) { if ("all".equalsIgnoreCase(element)) {
for (int i = 0; i < totalPages; i++) { for (int i = 0; i < totalPages; i++) {
newPageOrder.add(i); newPageOrder.add(i + adjustmentFactor);
} }
// As all pages are already added, no need to check further // As all pages are already added, no need to check further
break; break;
@@ -135,11 +158,11 @@ public class GeneralUtils {
if (element.contains("n")) { if (element.contains("n")) {
String[] parts = element.split("n"); String[] parts = element.split("n");
if (!parts[0].equals("") && parts[0] != null) { if (!"".equals(parts[0]) && parts[0] != null) {
coefficient = Integer.parseInt(parts[0]); coefficient = Integer.parseInt(parts[0]);
coefficientExists = true; coefficientExists = true;
} }
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { if (parts.length > 1 && !"".equals(parts[1]) && parts[1] != null) {
constant = Integer.parseInt(parts[1]); constant = Integer.parseInt(parts[1]);
constantExists = true; constantExists = true;
} }
@@ -153,7 +176,7 @@ public class GeneralUtils {
pageNum += constantExists ? constant : 0; pageNum += constantExists ? constant : 0;
if (pageNum <= totalPages && pageNum > 0) { if (pageNum <= totalPages && pageNum > 0) {
newPageOrder.add(pageNum - 1); newPageOrder.add(pageNum - adjustmentFactor);
} }
} }
} else if (element.contains("-")) { } else if (element.contains("-")) {
@@ -168,11 +191,11 @@ public class GeneralUtils {
// loop through the range of pages // loop through the range of pages
for (int j = start; j <= end; j++) { for (int j = start; j <= end; j++) {
// print the current index // print the current index
newPageOrder.add(j - 1); newPageOrder.add(j - adjustmentFactor);
} }
} else { } else {
// if the element is a single page // if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1); newPageOrder.add(Integer.parseInt(element) - adjustmentFactor);
} }
} }

View File

@@ -20,6 +20,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
public class PDFToFile { public class PDFToFile {
@@ -32,7 +34,7 @@ public class PDFToFile {
} }
// Get the original PDF file name without the extension // Get the original PDF file name without the extension
String originalPdfFileName = inputFile.getOriginalFilename(); String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.')); String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
// Validate output format // Validate output format
@@ -87,7 +89,7 @@ public class PDFToFile {
if (outputFiles.size() == 1) { if (outputFiles.size() == 1) {
// Return single output file // Return single output file
File outputFile = outputFiles.get(0); File outputFile = outputFiles.get(0);
if (outputFormat.equals("txt:Text")) { if ("txt:Text".equals(outputFormat)) {
outputFormat = "txt"; outputFormat = "txt";
} }
fileName = pdfBaseName + "." + outputFormat; fileName = pdfBaseName + "." + outputFormat;

View File

@@ -2,8 +2,10 @@ package stirling.software.SPDF.utils;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@@ -16,11 +18,15 @@ import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
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;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
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;
@@ -31,7 +37,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.pdf.ImageFinder; import io.github.pixee.security.Filenames;
public class PdfUtils { public class PdfUtils {
@@ -62,6 +68,23 @@ public class PdfUtils {
} }
} }
public static List<RenderedImage> getAllImages(PDResources resources) throws IOException {
List<RenderedImage> images = new ArrayList<>();
for (COSName name : resources.getXObjectNames()) {
PDXObject object = resources.getXObject(name);
if (object instanceof PDImageXObject) {
images.add(((PDImageXObject) object).getImage());
} else if (object instanceof PDFormXObject) {
images.addAll(getAllImages(((PDFormXObject) object).getResources()));
}
}
return images;
}
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
String[] pageOrderArr = pagesToCheck.split(","); String[] pageOrderArr = pagesToCheck.split(",");
List<Integer> pageList = List<Integer> pageList =
@@ -94,9 +117,7 @@ public class PdfUtils {
} }
public static boolean hasImagesOnPage(PDPage page) throws IOException { public static boolean hasImagesOnPage(PDPage page) throws IOException {
ImageFinder imageFinder = new ImageFinder(page); return getAllImages(page.getResources()).size() > 0;
imageFinder.processPage(page);
return imageFinder.hasImages();
} }
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
@@ -113,7 +134,7 @@ public class PdfUtils {
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
String pdfText = ""; String pdfText = "";
if (pagesToCheck == null || pagesToCheck.equals("all")) { if (pagesToCheck == null || "all".equals(pagesToCheck)) {
pdfText = textStripper.getText(pdfDocument); pdfText = textStripper.getText(pdfDocument);
} else { } else {
// remove whitespaces // remove whitespaces
@@ -199,8 +220,8 @@ public class PdfUtils {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (singleImage) { if (singleImage) {
if (imageType.toLowerCase().equals("tiff") if ("tiff".equals(imageType.toLowerCase())
|| imageType.toLowerCase().equals("tif")) { || "tif".equals(imageType.toLowerCase())) {
// Write the images to the output stream as a TIFF with multiple frames // Write the images to the output stream as a TIFF with multiple frames
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
ImageWriteParam param = writer.getDefaultWriteParam(); ImageWriteParam param = writer.getDefaultWriteParam();
@@ -280,7 +301,7 @@ public class PdfUtils {
try (PDDocument doc = new PDDocument()) { try (PDDocument doc = new PDDocument()) {
for (MultipartFile file : files) { for (MultipartFile file : files) {
String contentType = file.getContentType(); String contentType = file.getContentType();
String originalFilename = file.getOriginalFilename(); String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());
if (originalFilename != null if (originalFilename != null
&& (originalFilename.toLowerCase().endsWith(".tiff") && (originalFilename.toLowerCase().endsWith(".tiff")
|| originalFilename.toLowerCase().endsWith(".tif"))) { || originalFilename.toLowerCase().endsWith(".tif"))) {
@@ -301,7 +322,7 @@ public class PdfUtils {
ImageProcessingUtils.convertColorType(image, colorType); ImageProcessingUtils.convertColorType(image, colorType);
// Use JPEGFactory if it's JPEG since JPEG is lossy // Use JPEGFactory if it's JPEG since JPEG is lossy
PDImageXObject pdImage = PDImageXObject pdImage =
(contentType != null && contentType.equals("image/jpeg")) (contentType != null && "image/jpeg".equals(contentType))
? JPEGFactory.createFromImage(doc, convertedImage) ? JPEGFactory.createFromImage(doc, convertedImage)
: LosslessFactory.createFromImage(doc, convertedImage); : LosslessFactory.createFromImage(doc, convertedImage);
addImageToDocument(doc, pdImage, fitOption, autoRotate); addImageToDocument(doc, pdImage, fitOption, autoRotate);

View File

@@ -16,6 +16,8 @@ import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import io.github.pixee.security.BoundedLineReader;
public class ProcessExecutor { public class ProcessExecutor {
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class); private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
@@ -109,7 +111,10 @@ public class ProcessExecutor {
process.getErrorStream(), process.getErrorStream(),
StandardCharsets.UTF_8))) { StandardCharsets.UTF_8))) {
String line; String line;
while ((line = errorReader.readLine()) != null) { while ((line =
BoundedLineReader.readLine(
errorReader, 5_000_000))
!= null) {
errorLines.add(line); errorLines.add(line);
if (liveUpdates) logger.info(line); if (liveUpdates) logger.info(line);
} }
@@ -130,7 +135,10 @@ public class ProcessExecutor {
process.getInputStream(), process.getInputStream(),
StandardCharsets.UTF_8))) { StandardCharsets.UTF_8))) {
String line; String line;
while ((line = outputReader.readLine()) != null) { while ((line =
BoundedLineReader.readLine(
outputReader, 5_000_000))
!= null) {
outputLines.add(line); outputLines.add(line);
if (liveUpdates) logger.info(line); if (liveUpdates) logger.info(line);
} }

View File

@@ -12,6 +12,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
public class WebResponseUtils { public class WebResponseUtils {
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> boasToWebResponse(
@@ -26,7 +28,7 @@ public class WebResponseUtils {
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file) public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file)
throws IOException { throws IOException {
String fileName = file.getOriginalFilename(); String fileName = Filenames.toSimpleFileName(file.getOriginalFilename());
MediaType mediaType = MediaType.parseMediaType(file.getContentType()); MediaType mediaType = MediaType.parseMediaType(file.getContentType());
byte[] bytes = file.getBytes(); byte[] bytes = file.getBytes();

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,6 +11,7 @@ imgPrompt=اختر صورة
genericSubmit=إرسال genericSubmit=إرسال
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل): pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=اذهب goToPage=اذهب
true=\u0635\u062D\u064A\u062D true=\u0635\u062D\u064A\u062D
false=\u062E\u0637\u0623 false=\u062E\u0637\u0623
@@ -19,6 +20,7 @@ save=\u062D\u0641\u0638
close=\u0625\u063A\u0644\u0627\u0642 close=\u0625\u063A\u0644\u0627\u0642
filesSelected=الملفات المحددة filesSelected=الملفات المحددة
noFavourites=لم تتم إضافة أي مفضلات noFavourites=لم تتم إضافة أي مفضلات
downloadComplete=Download Complete
bored=الانتظار بالملل؟ bored=الانتظار بالملل؟
alphabet=\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629 alphabet=\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629
downloadPdf=تنزيل PDF downloadPdf=تنزيل PDF
@@ -42,14 +44,17 @@ red=Red
green=Green green=Green
blue=Blue blue=Blue
custom=Custom... custom=Custom...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems! WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Powered by poweredBy=Powered by
yes=Yes
no=No
changedCredsMessage=Credentials changed! changedCredsMessage=Credentials changed!
notAuthenticatedMessage=User not authenticated. notAuthenticatedMessage=User not authenticated.
userNotFoundMessage=User not found. userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect. incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists. usernameExistsMessage=New Username already exists.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
############### ###############
@@ -68,6 +73,7 @@ pipelineOptions.header=Pipeline Configuration
pipelineOptions.pipelineNameLabel=Pipeline Name pipelineOptions.pipelineNameLabel=Pipeline Name
pipelineOptions.saveSettings=Save Operation Settings pipelineOptions.saveSettings=Save Operation Settings
pipelineOptions.pipelineNamePrompt=Enter pipeline name here pipelineOptions.pipelineNamePrompt=Enter pipeline name here
pipelineOptions.selectOperation=Select Operation
pipelineOptions.addOperationButton=Add operation pipelineOptions.addOperationButton=Add operation
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Download pipelineOptions.saveButton=Download
@@ -104,7 +110,7 @@ settings.accountSettings=Account Settings
changeCreds.title=Change Credentials changeCreds.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=New Username changeCreds.newUsername=New Username
changeCreds.oldPassword=Current Password changeCreds.oldPassword=Current Password
changeCreds.newPassword=New Password changeCreds.newPassword=New Password
@@ -118,7 +124,7 @@ account.accountSettings=Account Settings
account.adminSettings=Admin Settings - View and Add Users account.adminSettings=Admin Settings - View and Add Users
account.userControlSettings=User Control Settings account.userControlSettings=User Control Settings
account.changeUsername=Change Username account.changeUsername=Change Username
account.changeUsername=Change Username account.newUsername=New Username
account.password=Confirmation Password account.password=Confirmation Password
account.oldPassword=Old password account.oldPassword=Old password
account.newPassword=New Password account.newPassword=New Password
@@ -143,9 +149,11 @@ adminUserSettings.roles=Roles
adminUserSettings.role=Role adminUserSettings.role=Role
adminUserSettings.actions=Actions adminUserSettings.actions=Actions
adminUserSettings.apiUser=Limited API User adminUserSettings.apiUser=Limited API User
adminUserSettings.extraApiUser=Additional Limited API User
adminUserSettings.webOnlyUser=Web Only User adminUserSettings.webOnlyUser=Web Only User
adminUserSettings.demoUser=Demo User (No custom settings) adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.forceChange=Force user to change username/password on login adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
############# #############
@@ -364,7 +372,7 @@ showJS.tags=JS
home.autoRedact.title=Auto Redact home.autoRedact.title=Auto Redact
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
showJS.tags=JS autoRedact.tags=Redact,Hide,black out,black,marker,hidden
home.tableExtraxt.title=PDF to CSV home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
@@ -384,6 +392,20 @@ home.split-by-sections.title=Split PDF by Sections
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=Add Stamp to PDF
home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@@ -459,6 +481,37 @@ HTMLToPDF.header=HTML To PDF
HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required
HTMLToPDF.submit=Convert HTMLToPDF.submit=Convert
HTMLToPDF.credit=Uses WeasyPrint HTMLToPDF.credit=Uses WeasyPrint
HTMLToPDF.zoom=Zoom level for displaying the website.
HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default)
HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default)
HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default)
HTMLToPDF.printBackground=Render the background of websites.
HTMLToPDF.defaultHeader=Enable Default Header (Name and page number)
HTMLToPDF.cssMediaType=Change the CSS media type of the page.
HTMLToPDF.none=None
HTMLToPDF.print=Print
HTMLToPDF.screen=Screen
#AddStampRequest
AddStampRequest.header=Stamp PDF
AddStampRequest.title=Stamp PDF
AddStampRequest.stampType=Stamp Type
AddStampRequest.stampText=Stamp Text
AddStampRequest.stampImage=Stamp Image
AddStampRequest.alphabet=Alphabet
AddStampRequest.fontSize=Font/Image Size
AddStampRequest.rotation=Rotation
AddStampRequest.opacity=Opacity
AddStampRequest.position=Position
AddStampRequest.overrideX=Override X Coordinate
AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Submit
#sanitizePDF #sanitizePDF
@@ -584,6 +637,18 @@ compare.document.1=المستند 1
compare.document.2=المستند 2 compare.document.2=المستند 2
compare.submit=يقارن compare.submit=يقارن
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign #sign
sign.title=تسجيل الدخول sign.title=تسجيل الدخول
@@ -829,7 +894,6 @@ changeMetadata.keywords=\u0627\u0644\u0643\u0644\u0645\u0627\u062A \u0627\u0644\
changeMetadata.modDate=\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u062A\u0639\u062F\u064A\u0644 (yyyy / MM / dd HH: mm: ss): changeMetadata.modDate=\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u062A\u0639\u062F\u064A\u0644 (yyyy / MM / dd HH: mm: ss):
changeMetadata.producer=\u0627\u0644\u0645\u0646\u062A\u062C: changeMetadata.producer=\u0627\u0644\u0645\u0646\u062A\u062C:
changeMetadata.subject=\u0627\u0644\u0645\u0648\u0636\u0648\u0639: changeMetadata.subject=\u0627\u0644\u0645\u0648\u0636\u0648\u0639:
changeMetadata.title=\u0627\u0644\u0639\u0646\u0648\u0627\u0646:
changeMetadata.trapped=\u0645\u062D\u0627\u0635\u0631: changeMetadata.trapped=\u0645\u062D\u0627\u0635\u0631:
changeMetadata.selectText.4=\u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0635\u0641\u064A\u0629 \u0623\u062E\u0631\u0649: changeMetadata.selectText.4=\u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0635\u0641\u064A\u0629 \u0623\u062E\u0631\u0649:
changeMetadata.selectText.5=\u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0627\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u062E\u0635\u0635 changeMetadata.selectText.5=\u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0627\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u062E\u0635\u0635
@@ -921,7 +985,7 @@ split-by-sections.vertical.label=Vertical Divisions
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
split-by-sections.vertical.placeholder=Enter number of vertical divisions split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One PDF
#licenses #licenses
licenses.nav=Licenses licenses.nav=Licenses

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,6 +11,7 @@ imgPrompt=Изберете изображение(я)
genericSubmit=Подайте genericSubmit=Подайте
processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла
pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1): pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Давай goToPage=Давай
true=Вярно true=Вярно
false=Невярно false=Невярно
@@ -19,6 +20,7 @@ save=Съхранете
close=Затворете close=Затворете
filesSelected=избрани файлове filesSelected=избрани файлове
noFavourites=Няма добавени любими noFavourites=Няма добавени любими
downloadComplete=Download Complete
bored=Отекчени сте да чакате? bored=Отекчени сте да чакате?
alphabet=Азбука alphabet=Азбука
downloadPdf=Изтеглете PDF downloadPdf=Изтеглете PDF
@@ -42,14 +44,17 @@ red=Червено
green=Зелено green=Зелено
blue=Синьо blue=Синьо
custom=Персонализиране... custom=Персонализиране...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems! WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Powered by poweredBy=Powered by
yes=Yes
no=No
changedCredsMessage=Идентификационните данни са променени! changedCredsMessage=Идентификационните данни са променени!
notAuthenticatedMessage=Потребителят не е автентикиран. notAuthenticatedMessage=Потребителят не е автентикиран.
userNotFoundMessage=Потребителят не е намерен userNotFoundMessage=Потребителят не е намерен
incorrectPasswordMessage=Текущата парола е неправилна. incorrectPasswordMessage=Текущата парола е неправилна.
usernameExistsMessage=Новият потребител вече съществува. usernameExistsMessage=Новият потребител вече съществува.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
############### ###############
@@ -68,6 +73,7 @@ pipelineOptions.header=Pipeline Configuration
pipelineOptions.pipelineNameLabel=Pipeline Name pipelineOptions.pipelineNameLabel=Pipeline Name
pipelineOptions.saveSettings=Save Operation Settings pipelineOptions.saveSettings=Save Operation Settings
pipelineOptions.pipelineNamePrompt=Enter pipeline name here pipelineOptions.pipelineNamePrompt=Enter pipeline name here
pipelineOptions.selectOperation=Select Operation
pipelineOptions.addOperationButton=Add operation pipelineOptions.addOperationButton=Add operation
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Download pipelineOptions.saveButton=Download
@@ -104,7 +110,7 @@ settings.accountSettings=Настройки на акаунта
changeCreds.title=Промяна на идентификационните данни changeCreds.title=Промяна на идентификационните данни
changeCreds.header=Актуализирайте данните за акаунта си changeCreds.header=Актуализирайте данните за акаунта си
changeCreds.changeUserAndPassword=Използвате идентификационни данни за вход по подразбиране. Моля, въведете нова парола (и потребителско име, ако искате) changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=Ново потребителско име changeCreds.newUsername=Ново потребителско име
changeCreds.oldPassword=Текуща парола changeCreds.oldPassword=Текуща парола
changeCreds.newPassword=Нова парола changeCreds.newPassword=Нова парола
@@ -118,7 +124,7 @@ account.accountSettings=Настройки на акаунта
account.adminSettings=Настройки на администратора - Преглед и добавяне на потребители account.adminSettings=Настройки на администратора - Преглед и добавяне на потребители
account.userControlSettings=Настройки за потребителски контрол account.userControlSettings=Настройки за потребителски контрол
account.changeUsername=Промени потребител account.changeUsername=Промени потребител
account.changeUsername=Промени потребител account.newUsername=Ново потребителско име
account.password=Парола за потвърждение account.password=Парола за потвърждение
account.oldPassword=Стара парола account.oldPassword=Стара парола
account.newPassword=Нова парола account.newPassword=Нова парола
@@ -143,8 +149,10 @@ adminUserSettings.roles=Роли
adminUserSettings.role=Роля adminUserSettings.role=Роля
adminUserSettings.actions=Действия adminUserSettings.actions=Действия
adminUserSettings.apiUser=Ограничен API потребител adminUserSettings.apiUser=Ограничен API потребител
adminUserSettings.extraApiUser=Additional Limited API User
adminUserSettings.webOnlyUser=Само за уеб-потребител adminUserSettings.webOnlyUser=Само за уеб-потребител
adminUserSettings.demoUser=Demo User (No custom settings) adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
adminUserSettings.submit=Съхранете потребителя adminUserSettings.submit=Съхранете потребителя
@@ -360,11 +368,11 @@ PdfToSinglePage.tags=единична страница
home.showJS.title=Показване на Javascript home.showJS.title=Показване на Javascript
home.showJS.desc=Търси и показва всеки JS, инжектиран в PDF home.showJS.desc=Търси и показва всеки JS, инжектиран в PDF
showJS.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит showJS.tags=JS
home.autoRedact.title=Автоматично редактиране home.autoRedact.title=Автоматично редактиране
home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст
showJS.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
home.tableExtraxt.title=PDF to CSV home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
@@ -384,6 +392,20 @@ home.split-by-sections.title=Split PDF by Sections
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=Add Stamp to PDF
home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@@ -459,6 +481,37 @@ HTMLToPDF.header=HTML към PDF
HTMLToPDF.help=Приема HTML файлове и ZIP файлове, съдържащи html/css/изображения и т.н HTMLToPDF.help=Приема HTML файлове и ZIP файлове, съдържащи html/css/изображения и т.н
HTMLToPDF.submit=Преобразуване HTMLToPDF.submit=Преобразуване
HTMLToPDF.credit=Използва WeasyPrint HTMLToPDF.credit=Използва WeasyPrint
HTMLToPDF.zoom=Zoom level for displaying the website.
HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default)
HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default)
HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default)
HTMLToPDF.printBackground=Render the background of websites.
HTMLToPDF.defaultHeader=Enable Default Header (Name and page number)
HTMLToPDF.cssMediaType=Change the CSS media type of the page.
HTMLToPDF.none=None
HTMLToPDF.print=Print
HTMLToPDF.screen=Screen
#AddStampRequest
AddStampRequest.header=Stamp PDF
AddStampRequest.title=Stamp PDF
AddStampRequest.stampType=Stamp Type
AddStampRequest.stampText=Stamp Text
AddStampRequest.stampImage=Stamp Image
AddStampRequest.alphabet=Alphabet
AddStampRequest.fontSize=Font/Image Size
AddStampRequest.rotation=Rotation
AddStampRequest.opacity=Opacity
AddStampRequest.position=Position
AddStampRequest.overrideX=Override X Coordinate
AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Submit
#sanitizePDF #sanitizePDF
@@ -584,6 +637,18 @@ compare.document.1=Документ 1
compare.document.2=Документ 2 compare.document.2=Документ 2
compare.submit=Сравнявай compare.submit=Сравнявай
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign #sign
sign.title=Подпишете sign.title=Подпишете
@@ -829,7 +894,6 @@ changeMetadata.keywords=Ключови думи:
changeMetadata.modDate=Дата на промяна (гггг/ММ/дд ЧЧ:мм:сс): changeMetadata.modDate=Дата на промяна (гггг/ММ/дд ЧЧ:мм:сс):
changeMetadata.producer=Продуцент: changeMetadata.producer=Продуцент:
changeMetadata.subject=Тема: changeMetadata.subject=Тема:
changeMetadata.title=Заглавие:
changeMetadata.trapped=В капан: changeMetadata.trapped=В капан:
changeMetadata.selectText.4=Други метаданни: changeMetadata.selectText.4=Други метаданни:
changeMetadata.selectText.5=Добавяне на персонализиране метаданни changeMetadata.selectText.5=Добавяне на персонализиране метаданни
@@ -921,7 +985,7 @@ split-by-sections.vertical.label=Vertical Divisions
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
split-by-sections.vertical.placeholder=Enter number of vertical divisions split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One PDF
#licenses #licenses
licenses.nav=Licenses licenses.nav=Licenses

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,6 +11,7 @@ imgPrompt=Selecciona Imatge(s)
genericSubmit=Envia genericSubmit=Envia
processTimeWarning=Alerta: Aquest procés pot tardar 1 minut depenent de la mida de l'arxiu processTimeWarning=Alerta: Aquest procés pot tardar 1 minut depenent de la mida de l'arxiu
pageOrderPrompt=Ordre de Pàgines (Llista separada per comes) : pageOrderPrompt=Ordre de Pàgines (Llista separada per comes) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Anar goToPage=Anar
true=Verdader true=Verdader
false=Fals false=Fals
@@ -19,6 +20,7 @@ save=Desa
close=Tanca close=Tanca
filesSelected=fitxers seleccionats filesSelected=fitxers seleccionats
noFavourites=No s'ha afegit cap favorit noFavourites=No s'ha afegit cap favorit
downloadComplete=Download Complete
bored=Avorrit esperant? bored=Avorrit esperant?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Descarregueu PDF downloadPdf=Descarregueu PDF
@@ -42,14 +44,17 @@ red=Vermell
green=Verd green=Verd
blue=Blau blue=Blau
custom=Personalitzat... custom=Personalitzat...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems! WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Powered by poweredBy=Powered by
yes=Yes
no=No
changedCredsMessage=Credentials changed! changedCredsMessage=Credentials changed!
notAuthenticatedMessage=User not authenticated. notAuthenticatedMessage=User not authenticated.
userNotFoundMessage=User not found. userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect. incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists. usernameExistsMessage=New Username already exists.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
############### ###############
@@ -68,6 +73,7 @@ pipelineOptions.header=Pipeline Configuration
pipelineOptions.pipelineNameLabel=Pipeline Name pipelineOptions.pipelineNameLabel=Pipeline Name
pipelineOptions.saveSettings=Save Operation Settings pipelineOptions.saveSettings=Save Operation Settings
pipelineOptions.pipelineNamePrompt=Enter pipeline name here pipelineOptions.pipelineNamePrompt=Enter pipeline name here
pipelineOptions.selectOperation=Select Operation
pipelineOptions.addOperationButton=Add operation pipelineOptions.addOperationButton=Add operation
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Download pipelineOptions.saveButton=Download
@@ -104,7 +110,7 @@ settings.accountSettings=Account Settings
changeCreds.title=Change Credentials changeCreds.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted) changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=New Username changeCreds.newUsername=New Username
changeCreds.oldPassword=Current Password changeCreds.oldPassword=Current Password
changeCreds.newPassword=New Password changeCreds.newPassword=New Password
@@ -118,7 +124,7 @@ account.accountSettings=Opcions del compte
account.adminSettings=Opcions d'Admin - Veure i afegir usuaris account.adminSettings=Opcions d'Admin - Veure i afegir usuaris
account.userControlSettings=Opcions de Control d'Usuari account.userControlSettings=Opcions de Control d'Usuari
account.changeUsername=Canvia nom usuari account.changeUsername=Canvia nom usuari
account.changeUsername=Canvia nom usuari account.newUsername=Nom d'usuari nou
account.password=Confirma contrasenya account.password=Confirma contrasenya
account.oldPassword=Password Antic account.oldPassword=Password Antic
account.newPassword=Password Nou account.newPassword=Password Nou
@@ -143,9 +149,11 @@ adminUserSettings.roles=Rols
adminUserSettings.role=Rol adminUserSettings.role=Rol
adminUserSettings.actions=Accions adminUserSettings.actions=Accions
adminUserSettings.apiUser=Usuari amb API limitada adminUserSettings.apiUser=Usuari amb API limitada
adminUserSettings.extraApiUser=Additional Limited API User
adminUserSettings.webOnlyUser=Usuari només WEB adminUserSettings.webOnlyUser=Usuari només WEB
adminUserSettings.demoUser=Demo User (No custom settings) adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.forceChange=Force user to change username/password on login adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Desar Usuari adminUserSettings.submit=Desar Usuari
############# #############
@@ -364,7 +372,7 @@ showJS.tags=JS
home.autoRedact.title=Auto Redact home.autoRedact.title=Auto Redact
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
showJS.tags=JS autoRedact.tags=Redact,Hide,black out,black,marker,hidden
home.tableExtraxt.title=PDF to CSV home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
@@ -384,6 +392,20 @@ home.split-by-sections.title=Split PDF by Sections
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=Add Stamp to PDF
home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@@ -459,6 +481,37 @@ HTMLToPDF.header=HTML To PDF
HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required
HTMLToPDF.submit=Convert HTMLToPDF.submit=Convert
HTMLToPDF.credit=Uses WeasyPrint HTMLToPDF.credit=Uses WeasyPrint
HTMLToPDF.zoom=Zoom level for displaying the website.
HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default)
HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default)
HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default)
HTMLToPDF.printBackground=Render the background of websites.
HTMLToPDF.defaultHeader=Enable Default Header (Name and page number)
HTMLToPDF.cssMediaType=Change the CSS media type of the page.
HTMLToPDF.none=None
HTMLToPDF.print=Print
HTMLToPDF.screen=Screen
#AddStampRequest
AddStampRequest.header=Stamp PDF
AddStampRequest.title=Stamp PDF
AddStampRequest.stampType=Stamp Type
AddStampRequest.stampText=Stamp Text
AddStampRequest.stampImage=Stamp Image
AddStampRequest.alphabet=Alphabet
AddStampRequest.fontSize=Font/Image Size
AddStampRequest.rotation=Rotation
AddStampRequest.opacity=Opacity
AddStampRequest.position=Position
AddStampRequest.overrideX=Override X Coordinate
AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Submit
#sanitizePDF #sanitizePDF
@@ -584,6 +637,18 @@ compare.document.1=Document 1
compare.document.2=Document 2 compare.document.2=Document 2
compare.submit=Comparar compare.submit=Comparar
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign #sign
sign.title=Sign sign.title=Sign
@@ -829,7 +894,6 @@ changeMetadata.keywords=Keywords:
changeMetadata.modDate=Data Modificació (yyyy/MM/dd HH:mm:ss): changeMetadata.modDate=Data Modificació (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Productor: changeMetadata.producer=Productor:
changeMetadata.subject=Assumpte: changeMetadata.subject=Assumpte:
changeMetadata.title=Títol:
changeMetadata.trapped=Atrapat: changeMetadata.trapped=Atrapat:
changeMetadata.selectText.4=Altres Metadades: changeMetadata.selectText.4=Altres Metadades:
changeMetadata.selectText.5=Afegir entrada personalizada changeMetadata.selectText.5=Afegir entrada personalizada
@@ -921,7 +985,7 @@ split-by-sections.vertical.label=Vertical Divisions
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
split-by-sections.vertical.placeholder=Enter number of vertical divisions split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One PDF
#licenses #licenses
licenses.nav=Licenses licenses.nav=Licenses

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,6 +11,7 @@ imgPrompt=Wählen Sie ein Bild
genericSubmit=Einreichen genericSubmit=Einreichen
processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein): pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
pageSelectionPrompt=Benutzerdefinierte Seitenauswahl (Geben Sie eine durch Kommas getrennte Liste von Seitenzahlen 1,5,6 oder Funktionen wie 2n+1 ein):
goToPage=Los goToPage=Los
true=Wahr true=Wahr
false=Falsch false=Falsch
@@ -19,6 +20,7 @@ save=Speichern
close=Schließen close=Schließen
filesSelected=Dateien ausgewählt filesSelected=Dateien ausgewählt
noFavourites=Keine Favoriten hinzugefügt noFavourites=Keine Favoriten hinzugefügt
downloadComplete=Download Complete
bored=Langeweile beim Warten? bored=Langeweile beim Warten?
alphabet=Alphabet alphabet=Alphabet
downloadPdf=PDF herunterladen downloadPdf=PDF herunterladen
@@ -42,36 +44,40 @@ red=Rot
green=Grün green=Grün
blue=Blau blue=Blau
custom=benutzerdefiniert... custom=benutzerdefiniert...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems! WorkInProgess=In Arbeit, funktioniert möglicherweise nicht oder ist fehlerhaft. Bitte melden Sie alle Probleme!
poweredBy=Powered by poweredBy=Powered by
yes=Ja
no=Nein
changedCredsMessage=Anmeldedaten geändert! changedCredsMessage=Anmeldedaten geändert!
notAuthenticatedMessage=Benutzer nicht authentifiziert. notAuthenticatedMessage=Benutzer nicht authentifiziert.
userNotFoundMessage=Benutzer nicht gefunden. userNotFoundMessage=Benutzer nicht gefunden.
incorrectPasswordMessage=Das Passwort ist falsch. incorrectPasswordMessage=Das Passwort ist falsch.
usernameExistsMessage=Neuer Benutzername existiert bereits. usernameExistsMessage=Neuer Benutzername existiert bereits.
deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=Pipeline Menu (Alpha) pipeline.header=Pipeline-Menü (Alpha)
pipeline.uploadButton=Upload Custom pipeline.uploadButton=Benutzerdefinierter Upload
pipeline.configureButton=Configure pipeline.configureButton=Konfigurieren
pipeline.defaultOption=Custom pipeline.defaultOption=Benutzerdefiniert
pipeline.submitButton=Submit pipeline.submitButton=Speichern
###################### ######################
# Pipeline Options # # Pipeline Options #
###################### ######################
pipelineOptions.header=Pipeline Configuration pipelineOptions.header=Pipeline-Konfiguration
pipelineOptions.pipelineNameLabel=Pipeline Name pipelineOptions.pipelineNameLabel=Pipeline-Name
pipelineOptions.saveSettings=Save Operation Settings pipelineOptions.saveSettings=Save Operation Settings
pipelineOptions.pipelineNamePrompt=Enter pipeline name here pipelineOptions.pipelineNamePrompt=Geben Sie hier den Namen der Pipeline ein
pipelineOptions.addOperationButton=Add operation pipelineOptions.selectOperation=Vorgang auswählen
pipelineOptions.addOperationButton=Vorgang hinzufügen
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Download pipelineOptions.saveButton=Downloaden
pipelineOptions.validateButton=Validate pipelineOptions.validateButton=Validieren
@@ -104,7 +110,7 @@ settings.accountSettings=Kontoeinstellungen
changeCreds.title=Anmeldeinformationen ändern changeCreds.title=Anmeldeinformationen ändern
changeCreds.header=Aktualisieren Sie Ihre Kontodaten changeCreds.header=Aktualisieren Sie Ihre Kontodaten
changeCreds.changeUserAndPassword=Sie verwenden Standard-Anmeldeinformationen. Bitte geben Sie ein neues Passwort (und ggf. einen Benutzernamen) ein. changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=Neuer Benutzername changeCreds.newUsername=Neuer Benutzername
changeCreds.oldPassword=Aktuelles Passwort changeCreds.oldPassword=Aktuelles Passwort
changeCreds.newPassword=Neues Passwort changeCreds.newPassword=Neues Passwort
@@ -118,11 +124,11 @@ account.accountSettings=Kontoeinstellungen
account.adminSettings=Admin Einstellungen - Benutzer anzeigen und hinzufügen account.adminSettings=Admin Einstellungen - Benutzer anzeigen und hinzufügen
account.userControlSettings=Benutzerkontrolle account.userControlSettings=Benutzerkontrolle
account.changeUsername=Benutzername ändern account.changeUsername=Benutzername ändern
account.changeUsername=Benutzername ändern account.newUsername=Neuer Benutzername
account.password=Bestätigungspasswort account.password=Bestätigungspasswort
account.oldPassword=Altes Passwort account.oldPassword=Altes Passwort
account.newPassword=Neues Passwort account.newPassword=Neues Passwort
account.changePassword=Password ändern account.changePassword=Passwort ändern
account.confirmNewPassword=Neues Passwort bestätigen account.confirmNewPassword=Neues Passwort bestätigen
account.signOut=Abmelden account.signOut=Abmelden
account.yourApiKey=Dein API Schlüssel account.yourApiKey=Dein API Schlüssel
@@ -143,8 +149,10 @@ adminUserSettings.roles=Rollen
adminUserSettings.role=Rolle adminUserSettings.role=Rolle
adminUserSettings.actions=Aktion adminUserSettings.actions=Aktion
adminUserSettings.apiUser=Eingeschränkter API-Benutzer adminUserSettings.apiUser=Eingeschränkter API-Benutzer
adminUserSettings.extraApiUser=Zusätzlicher eingeschränkter API-Benutzer
adminUserSettings.webOnlyUser=Nur Web-Benutzer adminUserSettings.webOnlyUser=Nur Web-Benutzer
adminUserSettings.demoUser=Demo User (No custom settings) adminUserSettings.demoUser=Demo-Benutzer (Keine benutzerdefinierten Einstellungen)
adminUserSettings.internalApiUser=Interner API-Benutzer
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
adminUserSettings.submit=Benutzer speichern adminUserSettings.submit=Benutzer speichern
@@ -281,8 +289,8 @@ home.removeBlanks.title=Leere Seiten entfernen
home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument
removeBlanks.tags=cleanup,streamline,non-content,organize removeBlanks.tags=cleanup,streamline,non-content,organize
home.removeAnnotations.title=Remove Annotations home.removeAnnotations.title=Anmerkungen entfernen
home.removeAnnotations.desc=Removes all comments/annotations from a PDF home.removeAnnotations.desc=Entfernt alle Kommentare/Anmerkungen aus einem PDF
removeAnnotations.tags=comments,highlight,notes,markup,remove removeAnnotations.tags=comments,highlight,notes,markup,remove
home.compare.title=Vergleichen home.compare.title=Vergleichen
@@ -302,7 +310,7 @@ home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern
scalePages.tags=resize,modify,dimension,adapt scalePages.tags=resize,modify,dimension,adapt
home.pipeline.title=Pipeline (Fortgeschritten) home.pipeline.title=Pipeline (Fortgeschritten)
home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch einen Pipeline Skript home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript
pipeline.tags=automate,sequence,scripted,batch-process pipeline.tags=automate,sequence,scripted,batch-process
home.add-page-numbers.title=Seitenzahlen hinzufügen home.add-page-numbers.title=Seitenzahlen hinzufügen
@@ -340,22 +348,22 @@ HTMLToPDF.tags=markup,web-content,transformation,convert
home.MarkdownToPDF.title=Markdown zu PDF home.MarkdownToPDF.title=Markdown zu PDF
home.MarkdownToPDF.desc=Konvertiert jede Markdown-Datei zu PDF home.MarkdownToPDF.desc=Konvertiert jede Markdown-Datei zu PDF
MarkdownToPDF.tags=markup,web-content,transformation,convert MarkdownToPDF.tags=markup,web-content,transformation,konvertieren
home.getPdfInfo.title=Alle Informationen anzeigen home.getPdfInfo.title=Alle Informationen anzeigen
home.getPdfInfo.desc=Erfasst alle möglichen Informationen in einer PDF home.getPdfInfo.desc=Erfasst alle möglichen Informationen in einer PDF
getPdfInfo.tags=infomation,data,stats,statistics getPdfInfo.tags=infomation,daten,statistik
home.extractPage.title=Seite(n) extrahieren home.extractPage.title=Seite(n) extrahieren
home.extractPage.desc=Extrahiert ausgewählte Seiten aus einer PDF home.extractPage.desc=Extrahiert ausgewählte Seiten aus einer PDF
extractPage.tags=extract extractPage.tags=extrahieren
home.PdfToSinglePage.title=PDF zu einer Seite zusammenfassen home.PdfToSinglePage.title=PDF zu einer Seite zusammenfassen
home.PdfToSinglePage.desc=Fügt alle PDF-Seiten zu einer einzigen großen Seite zusammen home.PdfToSinglePage.desc=Fügt alle PDF-Seiten zu einer einzigen großen Seite zusammen
PdfToSinglePage.tags=single page PdfToSinglePage.tags=einzelseite
home.showJS.title=Javascript anzeigen home.showJS.title=Javascript anzeigen
@@ -363,26 +371,40 @@ home.showJS.desc=Alle Javascript Funktionen in einer PDF anzeigen
showJS.tags=JS showJS.tags=JS
home.autoRedact.title=Automatisch zensieren/schwärzen home.autoRedact.title=Automatisch zensieren/schwärzen
home.autoRedact.desc=Automatisches zensierten (Schwärzen) von Text in einer PDF-Datei basierend auf dem eingegebenen Text home.autoRedact.desc=Automatisches Zensieren (Schwärzen) von Text in einer PDF-Datei basierend auf dem eingegebenen Text
showJS.tags=JS autoRedact.tags=zensieren,schwärzen
home.tableExtraxt.title=Tabelle extrahieren home.tableExtraxt.title=Tabelle extrahieren
home.tableExtraxt.desc=Tabelle aus PDF in CSV extrahieren home.tableExtraxt.desc=Tabelle aus PDF in CSV extrahieren
tableExtraxt.tags=CSV tableExtraxt.tags=CSV
home.autoSizeSplitPDF.title=Auto Split by Size/Count home.autoSizeSplitPDF.title=Teilen nach Größe/Anzahl
home.autoSizeSplitPDF.desc=Split a single PDF into multiple documents based on size, page count, or document count home.autoSizeSplitPDF.desc=Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf
autoSizeSplitPDF.tags=pdf,split,document,organization autoSizeSplitPDF.tags=pdf,teilen,dokument,organisation
home.overlay-pdfs.title=Overlay PDFs home.overlay-pdfs.title=PDF mit Overlay versehen
home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF home.overlay-pdfs.desc=Überlagert eine PDF über eine andere PDF
overlay-pdfs.tags=Overlay overlay-pdfs.tags=overlay,überlagern
home.split-by-sections.title=PDF in Abschnitte teilen
home.split-by-sections.desc=Teilen Sie jede Seite einer PDF-Datei in kleinere horizontale und vertikale Abschnitte auf
split-by-sections.tags=abschnitte,teilen,bearbeiten
home.AddStampRequest.title=Stempel zu PDF hinzufügen
home.AddStampRequest.desc=Fügen Sie an festgelegten Stellen Text oder Bildstempel hinzu
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.split-by-sections.title=Split PDF by Sections
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
split-by-sections.tags=Section Split, Divide, Customize
########################### ###########################
# # # #
@@ -408,7 +430,7 @@ autoRedact.useRegexLabel=Regex verwenden
autoRedact.wholeWordSearchLabel=Ganzes Wort suchen autoRedact.wholeWordSearchLabel=Ganzes Wort suchen
autoRedact.customPaddingLabel=Benutzerdefinierte Extra-Padding autoRedact.customPaddingLabel=Benutzerdefinierte Extra-Padding
autoRedact.convertPDFToImageLabel=PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten) autoRedact.convertPDFToImageLabel=PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten)
autoRedact.submitButton=zensieren autoRedact.submitButton=Zensieren
#showJS #showJS
@@ -459,6 +481,37 @@ HTMLToPDF.header=HTML zu PDF
HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc. HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc.
HTMLToPDF.submit=Konvertieren HTMLToPDF.submit=Konvertieren
HTMLToPDF.credit=Verwendet WeasyPrint HTMLToPDF.credit=Verwendet WeasyPrint
HTMLToPDF.zoom=Zoomstufe zur Darstellung der Website.
HTMLToPDF.pageWidth=Breite der Seite in Zentimetern. (Leer auf Standard)
HTMLToPDF.pageHeight=Höhe der Seite in Zentimetern. (Leer auf Standard)
HTMLToPDF.marginTop=Oberer Rand der Seite in Millimetern. (Leer auf Standard)
HTMLToPDF.marginBottom=Unterer Rand der Seite in Millimetern. (Leer auf Standard)
HTMLToPDF.marginLeft=Linker Rand der Seite in Millimetern. (Leer auf Standard)
HTMLToPDF.marginRight=Linker Rand der Seite in Millimetern. (Leer auf Standard)
HTMLToPDF.printBackground=Den Hintergrund der Website rendern.
HTMLToPDF.defaultHeader=Standardkopfzeile aktivieren (Name und Seitenzahl)
HTMLToPDF.cssMediaType=CSS-Medientyp der Seite ändern.
HTMLToPDF.none=Keine
HTMLToPDF.print=Drucken
HTMLToPDF.screen=Bildschirm
#AddStampRequest
AddStampRequest.header=PDF Stempel
AddStampRequest.title=PDF Stempel
AddStampRequest.stampType=Stempeltyp
AddStampRequest.stampText=Stempeltext
AddStampRequest.stampImage=Stampelbild
AddStampRequest.alphabet=Alphabet
AddStampRequest.fontSize=Schriftart/Bildgröße
AddStampRequest.rotation=Rotation
AddStampRequest.opacity=Deckkraft
AddStampRequest.position=Position
AddStampRequest.overrideX=X-Koordinate überschreiben
AddStampRequest.overrideY=Y-Koordinate überschreiben
AddStampRequest.customMargin=Benutzerdefinierter Rand
AddStampRequest.customColor=Benutzerdefinierte Textfarbe
AddStampRequest.submit=Abschicken
#sanitizePDF #sanitizePDF
@@ -511,7 +564,7 @@ crop.submit=Abschicken
#autoSplitPDF #autoSplitPDF
autoSplitPDF.title=PDF automatisch teilen autoSplitPDF.title=PDF automatisch teilen
autoSplitPDF.header=PDF automatisch teilen autoSplitPDF.header=PDF automatisch teilen
autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch, und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich. autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich.
autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend). autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend).
autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen. autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen.
autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling PDF den Rest. autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling PDF den Rest.
@@ -531,7 +584,7 @@ pipeline.title=Pipeline
pageLayout.title=Mehrseitiges Layout pageLayout.title=Mehrseitiges Layout
pageLayout.header=Mehrseitiges Layout pageLayout.header=Mehrseitiges Layout
pageLayout.pagesPerSheet=Seiten pro Blatt: pageLayout.pagesPerSheet=Seiten pro Blatt:
pageLayout.addBorder=Add Borders pageLayout.addBorder=Ränder hinzufügen
pageLayout.submit=Abschicken pageLayout.submit=Abschicken
@@ -547,11 +600,11 @@ scalePages.submit=Abschicken
certSign.title=Zertifikatsignierung certSign.title=Zertifikatsignierung
certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit) certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit)
certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus: certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus:
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below. certSign.jksNote=Hinweis: Wenn Ihr Zertifikatstyp unten nicht aufgeführt ist, konvertieren Sie ihn bitte mit dem Befehlszeilentool keytool in eine Java Keystore-Datei (.jks). Wählen Sie dann unten die Option „.jks-Datei“ aus.
certSign.selectKey=Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein): certSign.selectKey=Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein):
certSign.selectCert=Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein): certSign.selectCert=Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein):
certSign.selectP12=Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten): certSign.selectP12=Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten):
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore): certSign.selectJKS=Wählen Sie Ihre Java Keystore-Datei (.jks oder .keystore):
certSign.certType=Zertifikattyp certSign.certType=Zertifikattyp
certSign.password=Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden): certSign.password=Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden):
certSign.showSig=Signatur anzeigen certSign.showSig=Signatur anzeigen
@@ -572,9 +625,9 @@ removeBlanks.submit=Leere Seiten entfernen
#removeAnnotations #removeAnnotations
removeAnnotations.title=Remove Annotations removeAnnotations.title=Kommentare entfernen
removeAnnotations.header=Remove Annotations removeAnnotations.header=Kommentare entfernen
removeAnnotations.submit=Remove removeAnnotations.submit=Entfernen
#compare #compare
@@ -584,6 +637,18 @@ compare.document.1=Dokument 1
compare.document.2=Dokument 2 compare.document.2=Dokument 2
compare.submit=Vergleichen compare.submit=Vergleichen
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign #sign
sign.title=Signieren sign.title=Signieren
@@ -591,7 +656,7 @@ sign.header=PDFs signieren
sign.upload=Bild hochladen sign.upload=Bild hochladen
sign.draw=Signatur zeichnen sign.draw=Signatur zeichnen
sign.text=Texteingabe sign.text=Texteingabe
sign.clear=Klar sign.clear=Leeren
sign.add=Signieren sign.add=Signieren
@@ -663,7 +728,7 @@ compress.selectText.1=Manueller Modus Von 1 bis 4
compress.selectText.2=Optimierungsstufe: compress.selectText.2=Optimierungsstufe:
compress.selectText.3=4 (Schrecklich für Textbilder) compress.selectText.3=4 (Schrecklich für Textbilder)
compress.selectText.4=Automatischer Modus Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen compress.selectText.4=Automatischer Modus Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen
compress.selectText.5=Erwartete PDF-Größe (z. B. 25 MB, 10,8 MB, 25 KB) compress.selectText.5=Erwartete PDF-Größe (z.B. 25 MB, 10,8 MB, 25 KB)
compress.submit=Komprimieren compress.submit=Komprimieren
@@ -694,8 +759,8 @@ multiTool.title=PDF-Multitool
multiTool.header=PDF-Multitool multiTool.header=PDF-Multitool
#view pdf #view pdf
viewPdf.title=View PDF viewPdf.title=PDF anzeigen
viewPdf.header=View PDF viewPdf.header=PDF anzeigen
#pageRemover #pageRemover
pageRemover.title=Seiten entfernen pageRemover.title=Seiten entfernen
@@ -730,10 +795,10 @@ split.submit=Aufteilen
imageToPDF.title=Bild zu PDF imageToPDF.title=Bild zu PDF
imageToPDF.header=Bild zu PDF imageToPDF.header=Bild zu PDF
imageToPDF.submit=Umwandeln imageToPDF.submit=Umwandeln
imageToPDF.selectLabel=Image Fit Options imageToPDF.selectLabel=Bild anpassen
imageToPDF.fillPage=Fill Page imageToPDF.fillPage=Seite füllen
imageToPDF.fitDocumentToImage=Fit Page to Image imageToPDF.fitDocumentToImage=Seite an Bild anpassen
imageToPDF.maintainAspectRatio=Maintain Aspect Ratios imageToPDF.maintainAspectRatio=Seitenverhältnisse beibehalten
imageToPDF.selectText.2=PDF automatisch drehen imageToPDF.selectText.2=PDF automatisch drehen
imageToPDF.selectText.3=Mehrere Dateien verarbeiten (nur aktiv, wenn Sie mit mehreren Bildern arbeiten) imageToPDF.selectText.3=Mehrere Dateien verarbeiten (nur aktiv, wenn Sie mit mehreren Bildern arbeiten)
imageToPDF.selectText.4=In ein einziges PDF zusammenführen imageToPDF.selectText.4=In ein einziges PDF zusammenführen
@@ -823,13 +888,12 @@ changeMetadata.selectText.1=Bitte bearbeiten Sie die Variablen, die Sie ändern
changeMetadata.selectText.2=Alle Metadaten löschen changeMetadata.selectText.2=Alle Metadaten löschen
changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen: changeMetadata.selectText.3=Benutzerdefinierte Metadaten anzeigen:
changeMetadata.author=Autor: changeMetadata.author=Autor:
changeMetadata.creationDate=Erstellungsdatum (jjjj/MM/tt HH:mm:ss): changeMetadata.creationDate=Erstellungsdatum (JJJJ/MM/TT HH:mm:ss):
changeMetadata.creator=Ersteller: changeMetadata.creator=Ersteller:
changeMetadata.keywords=Schlüsselwörter: changeMetadata.keywords=Schlüsselwörter:
changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss): changeMetadata.modDate=Änderungsdatum (JJJJ/MM/TT HH:mm:ss):
changeMetadata.producer=Produzent: changeMetadata.producer=Produzent:
changeMetadata.subject=Betreff: changeMetadata.subject=Betreff:
changeMetadata.title=Titel:
changeMetadata.trapped=Gefangen: changeMetadata.trapped=Gefangen:
changeMetadata.selectText.4=Andere Metadaten: changeMetadata.selectText.4=Andere Metadaten:
changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
@@ -883,52 +947,52 @@ PDFToXML.submit=Konvertieren
#PDFToCSV #PDFToCSV
PDFToCSV.title=PDF zu CSV PDFToCSV.title=PDF zu CSV
PDFToCSV.header=PDF zu CSV PDFToCSV.header=PDF zu CSV
PDFToCSV.prompt=Choose page to extract table PDFToCSV.prompt=Seite mit der zu extrahierenden Tabelle wählen
PDFToCSV.submit=Extrakt PDFToCSV.submit=Extrahieren
#split-by-size-or-count #split-by-size-or-count
split-by-size-or-count.header=Split PDF by Size or Count split-by-size-or-count.header=PDF nach Größe oder Anzahl teilen
split-by-size-or-count.type.label=Select Split Type split-by-size-or-count.type.label=Teil-Modus wählen
split-by-size-or-count.type.size=By Size split-by-size-or-count.type.size=Nach Größe
split-by-size-or-count.type.pageCount=By Page Count split-by-size-or-count.type.pageCount=Nach Anzahl Seiten
split-by-size-or-count.type.docCount=By Document Count split-by-size-or-count.type.docCount=Nach Anzahl Dokumenten
split-by-size-or-count.value.label=Enter Value split-by-size-or-count.value.label=Wert eingeben
split-by-size-or-count.value.placeholder=Enter size (e.g., 2MB or 3KB) or count (e.g., 5) split-by-size-or-count.value.placeholder=Größe eingeben (z.B.: 2MB oder 3KB) oder Anzahl (z.B.: 5)
split-by-size-or-count.submit=Submit split-by-size-or-count.submit=Erstellen
#overlay-pdfs #overlay-pdfs
overlay-pdfs.header=Overlay PDF Files overlay-pdfs.header=PDF mit Overlay versehen
overlay-pdfs.baseFile.label=Select Base PDF File overlay-pdfs.baseFile.label=Basis-PDF-Datei auswählen
overlay-pdfs.overlayFiles.label=Select Overlay PDF Files overlay-pdfs.overlayFiles.label=Overlay-PDF-Datei auswählen
overlay-pdfs.mode.label=Select Overlay Mode overlay-pdfs.mode.label=Overlay-Modus auswählen
overlay-pdfs.mode.sequential=Sequential Overlay overlay-pdfs.mode.sequential=Sequentielles Overlay
overlay-pdfs.mode.interleaved=Interleaved Overlay overlay-pdfs.mode.interleaved=Verschachteltes Overlay
overlay-pdfs.mode.fixedRepeat=Fixed Repeat Overlay overlay-pdfs.mode.fixedRepeat=Feste-Wiederholung Overlay
overlay-pdfs.counts.label=Overlay Counts (for Fixed Repeat Mode) overlay-pdfs.counts.label=Overlay Anzahl (für Feste-Wiederholung)
overlay-pdfs.counts.placeholder=Enter comma-separated counts (e.g., 2,3,1) overlay-pdfs.counts.placeholder=Komma-separierte Anzahl eingeben (z.B.: 2,3,1)
overlay-pdfs.position.label=Select Overlay Position overlay-pdfs.position.label=Overlay Position auswählen
overlay-pdfs.position.foreground=Foreground overlay-pdfs.position.foreground=Vordergrund
overlay-pdfs.position.background=Background overlay-pdfs.position.background=Hintergrund
overlay-pdfs.submit=Submit overlay-pdfs.submit=Erstellen
#split-by-sections #split-by-sections
split-by-sections.title=Split PDF by Sections split-by-sections.title=PDF in Abschnitte teilen
split-by-sections.header=Split PDF into Sections split-by-sections.header=PDF in Abschnitte teilen
split-by-sections.horizontal.label=Horizontal Divisions split-by-sections.horizontal.label=Horizontale Teiler
split-by-sections.vertical.label=Vertical Divisions split-by-sections.vertical.label=Vertikale Teiler
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions split-by-sections.horizontal.placeholder=Anzahl horizontaler Teiler eingeben
split-by-sections.vertical.placeholder=Enter number of vertical divisions split-by-sections.vertical.placeholder=Anzahl vertikaler Teiler eingeben
split-by-sections.submit=Split PDF split-by-sections.submit=PDF teilen
split-by-sections.merge=Merge Into One PDF
#licenses #licenses
licenses.nav=Licenses licenses.nav=Lizenzen
licenses.title=3rd Party Licenses licenses.title=Lizenzen von Drittanbietern
licenses.header=3rd Party Licenses licenses.header=Lizenzen von Drittanbietern
licenses.module=Module licenses.module=Modul
licenses.version=Version licenses.version=Version
licenses.license=License licenses.license=Lizenz

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