Compare commits

...

206 Commits

Author SHA1 Message Date
Anthony Stirling
e0526b9584 Version bump 2023-05-14 15:43:56 +01:00
Anthony Stirling
f822f6d931 Merge pull request #150 from pesaventofilippo/ita-translation
Added Italian language
2023-05-14 15:37:25 +01:00
Filippo Pesavento
0a25d75682 Merge branch 'Frooodle:main' into ita-translation 2023-05-14 15:43:20 +02:00
Filippo Pesavento
0eea2c672f Added Italian translation 2023-05-14 15:43:12 +02:00
Anthony Stirling
e4e7b8f449 Merge pull request #148 from JabSYsEmb/main
fix style issue for rtl languages
2023-05-14 12:12:55 +01:00
JabSysEmb
54a5c621c4 add style in common file general.css 2023-05-14 14:03:09 +03:00
Filippo Pesavento
03e1d9a863 Added Italian flag 2023-05-14 11:54:01 +02:00
Filippo Pesavento
098fc340ca Added Italian language to navbar.html 2023-05-14 11:51:46 +02:00
JabSysEmb
27c8a03c16 fix style issue for rtl languages 2023-05-14 12:01:13 +03:00
Anthony Stirling
1ddf829a6d Update LocalRunGuide.md 2023-05-13 23:40:50 +01:00
Anthony Stirling
6952a8b72a Add files via upload 2023-05-13 17:04:21 +01:00
Anthony Stirling
2ee720760d Add files via upload 2023-05-13 16:58:00 +01:00
Anthony Stirling
ccee0eecb5 Update build.gradle 2023-05-13 13:28:20 +01:00
Anthony Stirling
2b7e97a09f Merge pull request #147 from Frooodle/hotfix
Minor fixes
2023-05-13 13:27:37 +01:00
Anthony Stirling
a2926b8fe9 Minor language stuff 2023-05-13 13:26:28 +01:00
Anthony Stirling
9009317f83 star in top right fix 2023-05-13 13:15:24 +01:00
Anthony Stirling
67772e7221 Update README.md 2023-05-13 11:12:50 +01:00
Anthony Stirling
cc02da47d7 Update push-docker.yml 2023-05-13 10:44:22 +01:00
Anthony Stirling
af540183a6 Merge pull request #146 from Frooodle/cleanups
Latest release
2023-05-13 10:35:22 +01:00
Anthony Stirling
6581bb4ab4 Improved API documentation 2023-05-13 10:32:47 +01:00
Anthony Stirling
53e5edcb75 0.8.0 instead of 1.0.0 2023-05-13 09:45:01 +01:00
Anthony Stirling
ae22066bad languages,add images and flags 2023-05-13 09:42:56 +01:00
systo
6ee5daf884 Merge remote-tracking branch 'origin/main' into cleanups 2023-05-13 09:14:21 +01:00
Rubens
93edc56f1d Create messages_ca_CA.properties
added catalan file
2023-05-13 08:16:25 +01:00
Rubens
41970348cd Update navbar.html
Add Catalan language to navbar
2023-05-13 08:16:25 +01:00
Anthony Stirling
815a823104 debugs 2023-05-12 20:55:18 +01:00
Anthony Stirling
c27f99ab58 99.9 for blanks 2023-05-12 20:42:45 +01:00
Anthony Stirling
ed35a878a5 update readme 2023-05-12 20:35:17 +01:00
Anthony Stirling
bd1ab5e80b narbar for favorites 2023-05-12 18:16:47 +01:00
Anthony Stirling
a1fae66800 favourites 2023-05-12 16:47:43 +01:00
Anthony Stirling
c4d1761687 lang 2023-05-12 14:02:37 +01:00
Anthony Stirling
a647347e10 Changes to blank detection, WIP for % 2023-05-11 23:05:33 +01:00
Anthony Stirling
2d42ae9a36 Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-05-11 21:56:17 +01:00
Anthony Stirling
8ce900acca compare imrpove 2023-05-11 21:55:57 +01:00
Anthony Stirling
60ee55b602 Update LocalRunGuide.md 2023-05-11 21:07:08 +01:00
Anthony Stirling
75cd62b7c2 Update LocalRunGuide.md 2023-05-11 20:35:09 +01:00
Anthony Stirling
55fe47d4f3 extra document changes 2023-05-11 14:36:20 +01:00
Anthony Stirling
440d5072b7 lang readme 2023-05-11 14:31:08 +01:00
Anthony Stirling
80968ee6c4 Merge remote-tracking branch 'origin/main' into cleanups 2023-05-11 14:23:51 +01:00
IZUMI-Zu
99254c964b add Chinese translate
Signed-off-by: BSS ZU <274620705z@gmail.com>
2023-05-11 11:41:49 +01:00
Anthony Stirling
d2a4cf74e8 APP_ROOT_PATH 2023-05-10 23:26:31 +01:00
Anthony Stirling
fd918ef15f local execution 2023-05-10 23:21:20 +01:00
Anthony Stirling
e16ed5ca1a changes for img to pdf #142 2023-05-10 22:51:01 +01:00
Anthony Stirling
25e5470c5e Fix for #140 2023-05-10 22:16:02 +01:00
Anthony Stirling
59320e6828 Merge pull request #141 from MayannF/customURI
Customize application uri (#136)
2023-05-10 20:12:45 +01:00
Mayann
f9aacb4c66 #136 Added environment variable APP_ROOT_PATH to expose the webapp behind a reverse proxy using subfolder. Minor changes to html pages to remove absolute path for some resources 2023-05-10 20:01:05 +02:00
Anthony Stirling
28faf3888c init home for toher featues 2023-05-08 22:55:01 +01:00
Saud Fatayerji
a9e22947ef Merge branch 'cleanups' of https://github.com/Frooodle/Stirling-PDF into cleanups 2023-05-08 20:07:23 +03:00
Saud Fatayerji
7585fc80b4 Made added draggable elements persist between pages. 2023-05-08 20:06:34 +03:00
Anthony Stirling
5139af9f48 organise imports 2023-05-08 15:26:41 +01:00
Anthony Stirling
f2521717ae more docs 2023-05-08 15:20:04 +01:00
Anthony Stirling
0f935b92cb api docs 2023-05-08 15:07:53 +01:00
Saud Fatayerji
5e3a2b456e Merge branch 'cleanups' of https://github.com/Frooodle/Stirling-PDF into cleanups 2023-05-08 16:54:34 +03:00
Anthony Stirling
632f29d1d6 ./ 2023-05-08 12:36:48 +01:00
systo
7353d69f1a Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-05-08 12:18:56 +01:00
Anthony Stirling
0b1cdf6a68 remove blanks 2023-05-08 12:18:48 +01:00
Saud Fatayerji
e0350b2837 Fixed error when clicking the delete draggable element with none available. 2023-05-08 12:36:39 +03:00
Saud Fatayerji
1a521505a6 Fixed bug with rainbow mode style 2023-05-08 04:29:26 +03:00
Saud Fatayerji
12d457e3ee Merge branch 'cleanups' of https://github.com/Frooodle/Stirling-PDF into cleanups 2023-05-08 04:24:42 +03:00
Saud Fatayerji
d58933ea8d Adjusted how styles work. Made signature canvas bigger. 2023-05-08 04:23:45 +03:00
Anthony Stirling
5bbfd15f38 other msg langs for eng 2023-05-08 00:24:30 +01:00
Anthony Stirling
acf4662d2f compare init, robots, sign lang and local js 2023-05-08 00:17:20 +01:00
Saud Fatayerji
1d55ee7f93 Merge branch 'cleanups' of https://github.com/Frooodle/Stirling-PDF into cleanups 2023-05-07 23:40:12 +03:00
Saud Fatayerji
b86afd7fc9 Revamped Signing page to allow multiple signatures and images 2023-05-07 23:39:34 +03:00
Anthony Stirling
a5164dc0b6 Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-05-07 13:18:39 +01:00
Anthony Stirling
cc919ea614 blank page test 2023-05-07 12:56:38 +01:00
Anthony Stirling
42d0d49682 Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-05-06 18:10:08 +01:00
Saud Fatayerji
07e81a117b Fixed signature scaling 2023-05-06 19:56:31 +03:00
Anthony Stirling
a56bcb09b0 Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-05-06 17:21:17 +01:00
Anthony Stirling
28b3272cd0 demo 2023-05-06 17:21:02 +01:00
Saud Fatayerji
5c7221d0d7 Merge branch 'cleanups' of https://github.com/Frooodle/Stirling-PDF into cleanups 2023-05-06 18:53:20 +03:00
Saud Fatayerji
0aa79d28f8 Fixed positional accuracy for signatures 2023-05-06 18:52:30 +03:00
Anthony Stirling
4b4cdb85ff documentation 2023-05-06 16:10:06 +01:00
Anthony Stirling
654f7742e4 font fix 2023-05-06 14:32:38 +01:00
Anthony Stirling
f082bca6c9 sign 2023-05-06 14:12:39 +01:00
Anthony Stirling
f7ace9d92a extractImages to sign 2023-05-06 14:04:55 +01:00
Anthony Stirling
9759b44cbb cleanup text 2023-05-06 13:58:36 +01:00
Anthony Stirling
5dfe8a83cd reapir 2023-05-06 12:36:31 +01:00
Anthony Stirling
3c47f21337 sign with text init 2023-05-06 00:48:56 +01:00
Anthony Stirling
b75360bdb1 nav 2023-05-05 20:15:22 +01:00
Anthony Stirling
32bace863c undo wrong commit file for sign 2023-05-04 23:22:33 +01:00
Anthony Stirling
acda1e4dd8 flatten pdf 2023-05-04 23:19:05 +01:00
Saud Fatayerji
a342def43f Fixed #74 (visual feedback of downloads) 2023-05-04 20:48:57 +03:00
Saud Fatayerji
b100435d9c Made the dialog close when clicking off of it. 2023-05-04 19:46:00 +03:00
Saud Fatayerji
b2a29e2b13 Restyled the sign page and cleaned up code in various places. 2023-05-04 00:07:51 +03:00
Anthony Stirling
fc0af56136 Merge remote-tracking branch 'origin/main' into cleanups 2023-05-02 23:00:06 +01:00
Anthony Stirling
fd246aac93 sign v2 2023-05-02 22:59:16 +01:00
LeeAStone
7d90ecf91f Correct typo in messages_en_GB.properties (#126)
Corrected spelling of "interactive"
2023-05-02 10:04:57 +01:00
Anthony Stirling
d2aa72d873 Update Dockerfile 2023-05-01 22:31:30 +01:00
Anthony Stirling
d0b57c0419 Update push-docker.yml 2023-05-01 22:16:06 +01:00
Anthony Stirling
5bee714437 utf8 bug fix and scan pages (#113) 2023-05-01 21:57:48 +01:00
Anthony Stirling
9af537c985 ocr remove images 2023-05-01 21:53:10 +01:00
Anthony Stirling
30c56a0ec9 lang 2023-05-01 18:50:20 +01:00
Anthony Stirling
563a72ad95 Merge branch 'main' into cleanups 2023-05-01 16:36:49 +01:00
Anthony Stirling
2d4aff3b08 Merge pull request #105 from jordyjordy/drag-n-drop
Drag n drop
2023-05-01 16:36:36 +01:00
Anthony Stirling
e0783cad60 Merge branch 'main' into drag-n-drop 2023-05-01 16:30:46 +01:00
Anthony Stirling
feaad367df Merge pull request #125 from Frooodle/dependabot/github_actions/dot-github/workflows/gradle/gradle-build-action-2.4.2
Bump gradle/gradle-build-action from 2.3.3 to 2.4.2 in /.github/workflows
2023-05-01 14:55:02 +01:00
dependabot[bot]
6b5b58ea94 Bump gradle/gradle-build-action in /.github/workflows
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.3.3 to 2.4.2.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.3.3...v2.4.2)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 13:50:35 +00:00
jordy
60d1d336d6 fix RTL drag offset 2023-05-01 10:01:28 +02:00
jordy
2b93407e64 fix file capitalization 2023-05-01 09:46:14 +02:00
Anthony Stirling
d5dec31b39 cleanups 2023-04-30 18:28:29 +01:00
Anthony Stirling
2608aa4c97 cleanup 2023-04-30 17:29:35 +01:00
Anthony Stirling
c174ca1c7e ocr fix 2 2023-04-30 14:47:39 +01:00
Anthony Stirling
80c26a9550 fix for OCR multi lang 2023-04-30 14:42:26 +01:00
jordy
f4ad6b963f Merge branch 'main' into drag-n-drop 2023-04-30 13:41:14 +02:00
jordy
b470cdf60c fix bugs in drag/drop, move styling for functions to separate sheets
To clean up the multi tool page and make the pdf adapters more re-usable the style for them was moved to separate pages that are inserted into head when the adapter is created.
2023-04-30 13:38:30 +02:00
Anthony Stirling
585bf4ccb4 about 2023-04-30 00:48:01 +01:00
Anthony Stirling
6b0fedfabf game init 2023-04-29 22:26:16 +01:00
jordy
9a1510a4f1 further refactor js
Turn the div adapters into injectable files so that each PdfContainer can be customized. And the adapters can be used in different PdfContainers as well.
2023-04-29 12:43:12 +02:00
Anthony Stirling
bcb4594afa docs 2023-04-28 23:54:53 +01:00
Anthony Stirling
0ebec74bed stop annoying fb identify tags in URL 2023-04-28 23:45:54 +01:00
Anthony Stirling
d10f5734fb Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-04-28 23:18:24 +01:00
Anthony Stirling
91171727e4 rework for API, folder changes, easter eggs and fun 2023-04-28 23:18:10 +01:00
jordy
e8a91d2631 refactor JS
Rather than having all the JS all in one big file, separate the code into modules.
2023-04-28 21:20:56 +02:00
Anthony Stirling
cd020e536b Merge branch 'main' into cleanups 2023-04-27 07:33:24 +01:00
Anthony Stirling
c05605a286 Update README.md 2023-04-26 22:17:47 +01:00
Anthony Stirling
63a698b679 Merge branch 'cleanups' of git@github.com:Frooodle/Stirling-PDF.git into cleanups 2023-04-26 22:01:32 +01:00
Anthony Stirling
3d3ef6f37b add title hover text 2023-04-26 21:54:12 +01:00
Anthony Stirling
9e839e130e Merge branch 'main' into cleanups 2023-04-26 15:15:32 +01:00
Anthony Stirling
3e2ca2fd5f Merge pull request #104 from jordyjordy/width-improvement
Width improvement
2023-04-26 13:57:55 +01:00
Anthony Stirling
ee26262b6e Merge remote-tracking branch 'origin/main' into cleanups 2023-04-26 13:18:36 +01:00
Anthony Stirling
4327af5133 bug fixes and image scan 2023-04-26 13:18:24 +01:00
Anthony Stirling
6cce2ee70a Merge branch 'main' into drag-n-drop 2023-04-25 18:14:01 +01:00
Anthony Stirling
70d07433d5 Merge branch 'main' into width-improvement 2023-04-25 18:13:52 +01:00
Anthony Stirling
8109c63250 Merge pull request #109 from Frooodle/dependabot/gradle/org.springframework.boot-spring-boot-starter-thymeleaf-3.0.6
Bump org.springframework.boot:spring-boot-starter-thymeleaf from 3.0.5 to 3.0.6
2023-04-25 16:41:26 +01:00
dependabot[bot]
32ba7361ac Bump org.springframework.boot:spring-boot-starter-thymeleaf
Bumps [org.springframework.boot:spring-boot-starter-thymeleaf](https://github.com/spring-projects/spring-boot) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.5...v3.0.6)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-thymeleaf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-25 15:40:32 +00:00
dependabot[bot]
a9da1b648f Merge pull request #110 from Frooodle/dependabot/gradle/org.springframework.boot-spring-boot-starter-test-3.0.6 2023-04-25 15:40:02 +00:00
dependabot[bot]
1fcd64a1ab Bump org.springframework.boot:spring-boot-starter-test
Bumps [org.springframework.boot:spring-boot-starter-test](https://github.com/spring-projects/spring-boot) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.5...v3.0.6)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-test
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-25 15:38:30 +00:00
Anthony Stirling
f8362b6bfa Merge pull request #111 from Frooodle/dependabot/gradle/org.springframework.boot-3.0.6
Bump org.springframework.boot from 3.0.5 to 3.0.6
2023-04-25 16:37:58 +01:00
Anthony Stirling
fb0541fddf Merge branch 'main' into dependabot/gradle/org.springframework.boot-3.0.6 2023-04-25 16:36:49 +01:00
Anthony Stirling
61b645c51c Merge pull request #112 from Frooodle/dependabot/gradle/org.springframework.boot-spring-boot-starter-web-3.0.6
Bump org.springframework.boot:spring-boot-starter-web from 3.0.5 to 3.0.6
2023-04-25 16:36:34 +01:00
dependabot[bot]
84b5a69074 Bump org.springframework.boot:spring-boot-starter-web
Bumps [org.springframework.boot:spring-boot-starter-web](https://github.com/spring-projects/spring-boot) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.5...v3.0.6)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-web
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 23:02:17 +00:00
dependabot[bot]
374f445953 Bump org.springframework.boot from 3.0.5 to 3.0.6
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.0.5...v3.0.6)

---
updated-dependencies:
- dependency-name: org.springframework.boot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 23:02:09 +00:00
jordy
bf78db9b75 drag-drop PoC 2023-04-23 15:55:11 +02:00
Jordy van der Tang
e3882b78db Merge branch 'Frooodle:main' into main 2023-04-23 15:54:45 +02:00
jordy
5389d4fc13 always scrollcontainer by page width on move 2023-04-23 14:16:28 +02:00
jordy
01529cc981 make pdf bar wider, hide buttons until hover 2023-04-23 13:56:39 +02:00
Anthony Stirling
93fb571725 Merge pull request #102 from Frooodle/cleanups
format and move everything, other in own folder
2023-04-23 09:45:01 +01:00
Anthony Stirling
ab4aea315a Merge branch 'main' into cleanups 2023-04-23 09:42:51 +01:00
Anthony Stirling
90ca50ae7a Merge pull request #103 from jordyjordy/main
Update Multi tool UI
2023-04-23 09:42:32 +01:00
jordy
3cb36d36bf address PR feedback 2023-04-23 09:59:19 +02:00
Jordy van der Tang
8f51f025d0 Merge branch 'main' into main 2023-04-22 19:41:07 +02:00
jordy
e048fc6653 fix RTL language directions
The positioning of the insert pdf buttons and the direction icon of the move page buttons are tied to the language direction, to fix this we retrieve the language direction from the document and use this to reverse some logic/elements for RTL languages.
2023-04-22 19:29:21 +02:00
jordy
a44fc62fee center pdfs
To correctly center the pdfs inside of the scrollable div we need a wrapper, ideally the 'safe center' flex-direction would be used, but this is not yet supported by most browsers.
2023-04-22 19:26:42 +02:00
jordy
9da29bf99a small css tweaks
lighten background
2023-04-22 14:57:39 +02:00
jordy
1edb669583 add imagehighlighter
Because the images are now much smaller with the image highlighter we can view images in more detail
2023-04-22 14:49:08 +02:00
Anthony Stirling
8e4e2469b0 Create CNAME 2023-04-22 13:19:30 +01:00
Anthony Stirling
f91953f67a Delete CNAME 2023-04-22 13:19:25 +01:00
Anthony Stirling
0bb61149e8 Create CNAME 2023-04-22 13:19:12 +01:00
Anthony Stirling
e5f7d1077d Delete CNAME 2023-04-22 13:13:22 +01:00
Anthony Stirling
7c91a77442 Create CNAME 2023-04-22 13:13:02 +01:00
jordy
fb24398b01 remove console.logs from prod code 2023-04-22 14:09:03 +02:00
jordy
43107965a9 use horizontal scroll for pages container
By making small thumbnails and aligning everything horizontally the space on the screen is used better.
2023-04-22 14:08:47 +02:00
Anthony Stirling
67b7435624 Merge branch 'main' into cleanups 2023-04-22 12:54:27 +01:00
Anthony Stirling
78d3fd3768 format and move everything, other in own folder 2023-04-22 12:51:01 +01:00
Anthony Stirling
243e4889b9 Merge pull request #101 from pl4nty/patch-1
Support GitHub Container Registry
2023-04-22 10:04:48 +01:00
Tom Plant
42564e683b Create Docker Hub tag from secret 2023-04-22 10:12:14 +10:00
Tom Plant
96097d1cd3 Set buildx context in CI/CD 2023-04-22 10:10:02 +10:00
Tom Plant
4f35a8d79f Enable caching for CI/CD container builds 2023-04-22 09:50:15 +10:00
Tom Plant
3b8bed5b0a Allow manual CI/CD runs 2023-04-22 09:46:27 +10:00
Tom Plant
5897f0e3ed Pin action versions 2023-04-22 09:45:11 +10:00
Tom Plant
2aba80d0ca Support GitHub Container Registry 2023-04-22 09:41:33 +10:00
Anthony Stirling
af6cd2e38b image updates 2023-04-22 00:18:32 +01:00
Anthony Stirling
5ab2664c70 fixes to saving and things 2023-04-22 00:08:36 +01:00
Anthony Stirling
39c31ef5d9 centre align, colour stuff and home icons 2023-04-21 23:32:39 +01:00
Anthony Stirling
bd1c7a35e2 settings bar changes 2023-04-21 19:58:07 +01:00
Anthony Stirling
0eed041986 messy code pretty icon 2023-04-21 18:03:36 +01:00
Anthony Stirling
1958c34bcb new sun moon 2023-04-21 17:52:40 +01:00
Anthony Stirling
f5ceede3cd darkmode test (weirdly slow not sure if PC) 2023-04-21 17:50:59 +01:00
Anthony Stirling
d98473f5e3 language stuff 2023-04-21 16:59:59 +01:00
Anthony Stirling
19d7027361 fixes 2023-04-21 13:17:35 +01:00
Anthony Stirling
a34c2863bd Merge branch 'main' of git@github.com:Frooodle/Stirling-PDF.git into
main
2023-04-21 13:15:46 +01:00
Anthony Stirling
4521dce1a9 init icons 2023-04-21 13:13:35 +01:00
Anthony Stirling
3564c1a45c Merge pull request #98 from manumora/main
Spanish language improvements
2023-04-21 11:57:20 +01:00
Manuel Mora Gordillo
b6e2d25462 Spanish language: fixed bug names of variables . Wrong characters corrected. Improved some texts 2023-04-21 11:48:49 +02:00
Anthony Stirling
256ce50c49 Merge pull request #97 from Frooodle/revert-94-patch-1
Revert "Support GitHub Container Registry"
2023-04-20 18:36:11 +01:00
Anthony Stirling
fb0ad73035 Revert "Support GitHub Container Registry" 2023-04-20 18:34:35 +01:00
Anthony Stirling
71636c3413 Merge pull request #96 from Sf298/main
Fixed rotate all button not working
2023-04-20 18:29:12 +01:00
Saud Fatayerji
5ef28bab9a Merge branch 'Frooodle:main' into main 2023-04-20 20:27:47 +03:00
Saud Fatayerji
757433629a Merge branch 'main' of https://github.com/Sf298/Stirling-PDF 2023-04-20 20:27:22 +03:00
Saud Fatayerji
050ae2a512 Fixed rotate all button not working 2023-04-20 20:26:38 +03:00
Anthony Stirling
4c81eac8fb Merge pull request #94 from pl4nty/patch-1
Support GitHub Container Registry
2023-04-20 17:43:59 +01:00
Anthony Stirling
a4544d7943 Merge branch 'main' into patch-1 2023-04-20 17:43:07 +01:00
Anthony Stirling
9d65537317 Merge pull request #95 from Sf298/main
Added "insert document" buttons that appear in the middle of the document. bug fixes
2023-04-20 17:39:38 +01:00
Saud Fatayerji
b6a284e2bc Merge branch 'Frooodle:main' into main 2023-04-20 19:36:14 +03:00
Saud Fatayerji
f4e5690841 Added "insert document" buttons that appear in the middle of the document 2023-04-20 19:34:58 +03:00
Anthony Stirling
fa84479254 Update README.md 2023-04-19 15:38:18 +01:00
Anthony Stirling
6ef1ca0a0b Update README.md 2023-04-19 15:35:58 +01:00
Anthony Stirling
927a1481a9 Update FUNDING.yml 2023-04-19 15:26:13 +01:00
Saud Fatayerji
7f9f45c720 Bug fixes and optimisations 2023-04-19 11:44:58 +03:00
Anthony Stirling
7ddc607fd2 Merge pull request #92 from Frooodle/dependabot/gradle/org.apache.pdfbox-pdfbox-2.0.28
Bump org.apache.pdfbox:pdfbox from 2.0.27 to 2.0.28
2023-04-18 18:53:35 +01:00
Anthony Stirling
4e1b0c6abe Merge branch 'main' into dependabot/gradle/org.apache.pdfbox-pdfbox-2.0.28 2023-04-18 14:27:13 +01:00
Tom Plant
e4d7b53112 Add GHCR perms to CI and pin Action versions 2023-04-18 22:36:38 +10:00
Tom Plant
f9ff57a26e Support GitHub Container Registry
Adds support for GitHub Container Registry to CI/CD and simplifies with third-party Actions
2023-04-18 22:32:37 +10:00
Anthony Stirling
2af9f19c3a nav bar active fix 2023-04-18 12:36:10 +01:00
Anthony Stirling
dbb8e2b245 remove pages movement 2023-04-18 11:07:39 +01:00
Anthony Stirling
689ad18c71 convert navbar improve 2023-04-18 10:52:38 +01:00
Anthony Stirling
4422787d4e Merge pull request #93 from Sf298/main
Added a page for visually editing individual pages of a pdf client-side
2023-04-18 07:55:21 +01:00
Saud Fatayerji
8d1057477b Merge branch 'main' of https://github.com/Sf298/Stirling-PDF 2023-04-18 05:18:20 +03:00
Saud Fatayerji
b3dff3a520 Added Page Manager to navbar and home page 2023-04-18 05:13:09 +03:00
Saud Fatayerji
6141be7310 Merge branch 'Frooodle:main' into main 2023-04-18 05:12:07 +03:00
Saud Fatayerji
2762459acf Created a page for visually editing individual pages of a pdf 2023-04-18 05:08:38 +03:00
dependabot[bot]
53d8d4dbb4 Bump org.apache.pdfbox:pdfbox from 2.0.27 to 2.0.28
Bumps org.apache.pdfbox:pdfbox from 2.0.27 to 2.0.28.

---
updated-dependencies:
- dependency-name: org.apache.pdfbox:pdfbox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 23:02:33 +00:00
Anthony Stirling
5ae72f3e6f Update home.html 2023-04-17 17:16:18 +01:00
Anthony Stirling
872b36124f Update home.html 2023-04-17 17:16:00 +01:00
Anthony Stirling
f762ab6aa7 Quick fix to dark mode button and reduce navbar size by removing PDF (#91) 2023-04-16 23:41:56 +01:00
169 changed files with 9383 additions and 2459 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: Frooodle # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

View File

@@ -46,7 +46,7 @@ jobs:
# with:
# languages: java
- uses: gradle/gradle-build-action@v2.3.3
- uses: gradle/gradle-build-action@v2.4.2
with:
gradle-version: 7.6
arguments: assemble --no-build-cache

View File

@@ -1,10 +1,10 @@
name: Push Docker Image with VersionNumber
on:
workflow_dispatch:
push:
branches:
- master
- testGit
- main
jobs:
push:
@@ -12,16 +12,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v3.11.0
with:
java-version: '17'
distribution: 'temurin'
- uses: gradle/gradle-build-action@v2.3.3
- uses: gradle/gradle-build-action@v2.4.2
with:
gradle-version: 7.6
arguments: clean build
@@ -37,43 +37,39 @@ jobs:
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
# - name: Check if tag exists
# id: checkIdExists
# continue-on-error: true
# run: |
# response=$(curl -s https://hub.docker.com/v2/repositories/frooodle/s-pdf/tags/?name=${{ steps.versionNumber.outputs.versionNumber }})
# result=$(echo $response | jq ".results")
# if [ "$result" == "[]" ]; then
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} doesnt exist. Continuing with build and push."
# else
# echo "Tag ${{ steps.versionNumber.outputs.versionNumber }} already exists. Skipping build and push."
# exit 1;
# fi
- name: Setup buildx
run: |
docker buildx create --name mybuilder
docker buildx use mybuilder
- name: Build and push versioned amd64 and v8
if: github.ref == 'refs/heads/main'
run: |
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}-alpha" .
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push versioned amd64 and v8
if: github.ref == 'refs/heads/master'
run: |
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:${{ steps.versionNumber.outputs.versionNumber }}" .
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push latest amd64 and v8
if: github.ref == 'refs/heads/master'
run: |
docker buildx build --platform="linux/amd64,linux/arm64/v8" --push --tag "frooodle/s-pdf:latest" .
- name: Generate tags
id: meta
uses: docker/metadata-action@v4.4.0
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ github.repository_owner }}/s-pdf
tags: |
${{ steps.versionNumber.outputs.versionNumber }}${{ github.ref == 'refs/heads/main' && '-alpha' || '' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Build and push
uses: docker/build-push-action@v4.0.0
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64/v8

4
.gitignore vendored
View File

@@ -109,4 +109,6 @@ local.properties
*.tar.gz
*.rar
/build
/build
/.vscode

1
CNAME Normal file
View File

@@ -0,0 +1 @@
stirlingtools.com

View File

@@ -1,5 +1,9 @@
# Build jbig2enc in a separate stage
FROM frooodle/stirling-pdf-base:latest
FROM frooodle/stirling-pdf-base:beta2
# Create scripts folder and copy local scripts
RUN mkdir /scripts
COPY ./scripts/* /scripts/
# Copy the application JAR file
COPY build/libs/*.jar app.jar
@@ -13,7 +17,6 @@ ENV APP_HOME_NAME="Stirling PDF"
#ENV APP_NAVBAR_NAME="Stirling PDF"
# Run the application
ENTRYPOINT java -jar /app.jar
RUN chmod +x /scripts/init.sh
ENTRYPOINT ["/scripts/init.sh"]
CMD ["java", "-jar", "/app.jar"]

View File

@@ -21,10 +21,9 @@ RUN git clone https://github.com/agl/jbig2enc && \
make && \
make install
# Main stage
FROM openjdk:17-jdk-slim
# Install necessary dependencies
# Main stage
FROM openjdk:17-jdk-slim AS base
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libreoffice-core \
@@ -33,12 +32,31 @@ RUN apt-get update && \
libreoffice-calc \
libreoffice-impress \
python3-uno \
python3-pip \
python3-pip \
unoconv \
pngquant \
unpaper \
pngquant \
unpaper \
ocrmypdf && \
pip install --user --upgrade ocrmypdf
rm -rf /var/lib/apt/lists/* && \
mkdir /usr/share/tesseract-ocr-original && \
cp -r /usr/share/tesseract-ocr/* /usr/share/tesseract-ocr-original && \
rm -rf /usr/share/tesseract-ocr
# Copy the jbig2enc binary from the builder stage
# Python packages stage
FROM base AS python-packages
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libffi-dev \
libssl-dev \
zlib1g-dev \
libjpeg-dev && \
pip install --upgrade pip && \
pip install --no-cache-dir \
opencv-python-headless && \
rm -rf /var/lib/apt/lists/*
# Final stage: Copy necessary files from the previous stage
FROM base
COPY --from=python-packages /usr/local /usr/local
COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2

View File

@@ -8,11 +8,18 @@ Fork Stirling-PDF and make a new branch out of Main
Then add reference to the language in the navbar by adding a new language entry to the dropdown
https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/navbar.html#L80
https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/navbar.html#L306
and add a flag svg file to
https://github.com/Frooodle/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/)
If your language isnt 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
```
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">Polish</a>
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a>
```
The data-language-code is the code used to reference the file in the next step.

View File

@@ -20,6 +20,8 @@ Depending on your requirements, you can choose the appropriate language pack for
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/4.00/tessdata`
# DO NOT REMOVE EXISTING ENG.TRAINEDDATA, ITS REQUIRED.
#### Docker
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
@@ -32,14 +34,14 @@ services:
your_service_name:
image: your_docker_image_name
volumes:
- /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata
```
#### Docker run
Add the following to your existing docker run command
```bash
-v /usr/share/tesseract-ocr/4.00/tessdata:/location/of/trainingData
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata
```
#### Non-Docker

137
LocalRunGuide.md Normal file
View File

@@ -0,0 +1,137 @@
To run the application without Docker, you will need to manually install all dependencies and build the necessary components.
Note that some dependencies might not be available in the standard repositories of all Linux distributions, and may require additional steps to install.
The following guide assumes you have a basic understanding of using a command line interface in your operating system.
It should work on most Linux distributions and MacOS. For Windows, you might need to use Windows Subsystem for Linux (WSL) for certain steps.
The amount of dependencies is to actually reduce overall size, ie installing LibreOffice sub components rather than full LibreOffice package.
### Step 1: Prerequisites
Install the following software, if not already installed:
- Java 17 or later
- Gradle 7.0 or later (included within repo so not needed on server)
- Git
- Python 3 (with pip)
- Make
- GCC/G++
- Automake
- Autoconf
- libtool
- pkg-config
- zlib1g-dev
- libleptonica-dev
For Debian-based systems, you can use the following command:
```bash
sudo apt-get update
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ java-17-openjdk python3 python3-pip
```
### Step 2: Clone and Build jbig2enc (Only required for certain OCR functionality)
```bash
git clone https:github.com/agl/jbig2enc
cd jbig2enc
./autogen.sh
./configure
make
sudo make install
```
### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for patern recognition functionality.
Install the following software:
- libreoffice-core
- libreoffice-common
- libreoffice-writer
- libreoffice-calc
- libreoffice-impress
- python3-uno
- unoconv
- pngquant
- unpaper
- ocrmypdf
- opencv-python-headless
For Debian-based systems, you can use the following command:
```bash
sudo apt-get install -y libreoffice-core libreoffice-common libreoffice-writer libreoffice-calc libreoffice-impress python3-uno unoconv pngquant unpaper ocrmypdf
pip3 install opencv-python-headless
```
### Step 4: Clone and Build Stirling-PDF
```bash
git clone https://github.com/Frooodle/Stirling-PDF.git
cd Stirling-PDF
./gradlew build
```
### Step 5: Move jar to desired location
After the build process, a `.jar` file will be generated in the `build/libs` directory.
You can move this file to a desired location, for example, `/opt/Stirling-PDF/`.
You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory.
This folder is required for the python scripts using OpenCV
### Step 6: Other files
#### OCR
If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running none english scanning.
##### Installing Language Packs
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/4.00/tessdata`
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.
### Step 7: Run Stirling-PDF
```bash
./gradlew bootRun
or
java -jar build/libs/app.jar
```
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:
```bash
export APP_HOME_NAME="Stirling PDF"
or
-DAPP_HOME_NAME="Stirling PDF"
```

View File

@@ -6,21 +6,27 @@
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Frooodle/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/frooodle/stirling-pdf?style=social)](https://github.com/Frooodle/stirling-pdf)
[![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)
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
Feel free to request any features of bug fixes either in github issues or our [Discord](https://discord.gg/Cn8pWhQRxZ)
Feel free to request any features or bug fixes either in github issues or our [Discord](https://discord.gg/Cn8pWhQRxZ)
![stirling-home](images/stirling-home.png)
## Features
- Full intractable GUI for merging/splitting/rotating/moving PDFs and their pages.
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
- Merge multiple PDFs together into a single resultant file
- Convert PDFs to and from images
- Reorganize PDF pages into different orders.
- Add images to PDFs at specified locations. (WIP)
- Add/Generate signatures
- Flatten PDFs
- Repair PDFs
- Detect and remove blank pages
- Compare 2 PDFs and show differences in text
- Add images to PDFs
- Rotating PDFs in 90 degree increments.
- Compressing PDFs to decrease their filesize. (Using OCRMyPDF)
- Add and remove passwords
@@ -34,6 +40,9 @@ Feel free to request any features of bug fixes either in github issues or our [D
- Dark mode support.
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
- Parallel file processing and downloads
- API for integration with external scripts
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
## Technologies used
- Spring Boot + Thymeleaf
@@ -42,35 +51,53 @@ Feel free to request any features of bug fixes either in github issues or our [D
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
- HTML, CSS, JavaScript
- Docker
- PDF.js
- PDF-LIB.js
## How to use
### Locally
Prerequisites
- Java 17 or later
- Gradle 7.0 or later
1. Clone or download the repository.
2. Build the project using Gradle by running `./gradlew build`
3. Start the application by running `./gradlew bootRun` or by calling the build jar in build/libs with java -jar jarName.jar
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md
### Docker
https://hub.docker.com/r/frooodle/s-pdf
Docker Run
```
docker run -p 8080:8080 frooodle/s-pdf
docker run -d \
-p 8080:8080 \
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \
--name stirling-pdf \
frooodle/s-pdf
Can also add these for customisation but are not required
-e APP_HOME_NAME="Stirling PDF" \
-e APP_HOME_DESCRIPTION="Your locally hosted one-stop-shop for all your PDF needs." \
-e APP_NAVBAR_NAME="Stirling PDF" \
-e ALLOW_GOOGLE_VISABILITY="true" \
-e APP_ROOT_PATH="/" \
-e APP_LOCALE="en_GB" \
```
Docker Compose
```
version: '3.3'
services:
s-pdf:
ports:
- '8080:8080'
image: frooodle/s-pdf
stirling-pdf:
image: frooodle/s-pdf
ports:
- '8080:8080'
volumes:
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages
# - /location/of/extraConfigs:/configs
# environment:
# APP_LOCALE: en_GB
# APP_HOME_NAME: Stirling PDF
# APP_HOME_DESCRIPTION: Your locally hosted one-stop-shop for all your PDF needs.
# APP_NAVBAR_NAME: Stirling PDF
# APP_ROOT_PATH: /
# ALLOW_GOOGLE_VISABILITY: true
```
@@ -78,6 +105,16 @@ services:
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
## Want to add your own language?
Stirling PDF currently supports
- English
- Arabic (العربية)
- German (Deutsch)
- French (Français)
- Spanish (Español)
- Chinese (简体中文)
- Catalan (Català)
- Italian (Italiano)
If you want to add your own language to Stirling-PDF please refer
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
@@ -95,4 +132,11 @@ Stirling PDF allows easy customization of the visible application name.
Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAVBAR_NAME with Docker or Java.
If running Java directly, you can also pass these as properties using -D arguments.
Using the same method you can also change the default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR to select your default language (Will always default to English on invalid locale)
Using the same method you can also change
- The default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR to select your default language (Will always default to English on invalid locale)
- Enable/Disable search engine visablility with ALLOW_GOOGLE_VISABILITY with true / false values. Default disable visability.
- Change root URI for Stirling-PDF ie change server.com/ to server.com/pdf-app by running APP_ROOT_PATH as pdf-app
## 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
[here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation

View File

@@ -1,11 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.5'
id 'org.springframework.boot' version '3.0.6'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'stirling.software'
version = '0.5.0'
version = '0.8.2'
sourceCompatibility = '17'
repositories {
@@ -13,17 +13,17 @@ repositories {
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.0.5'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.0.5'
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.0.5'
implementation 'org.springframework.boot:spring-boot-starter-web:3.0.6'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.0.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.0.6'
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
implementation 'commons-io:commons-io:2.11.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
//general PDF
implementation 'org.apache.pdfbox:pdfbox:2.0.27'
implementation 'com.itextpdf:itextpdf:5.5.13.3'
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
developmentOnly("org.springframework.boot:spring-boot-devtools")
}

BIN
images/DemoGif.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -0,0 +1,40 @@
import cv2
import numpy as np
import sys
import argparse
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)
total_pixels = thresholded_image.size
white_pixel_percentage = (white_pixels / total_pixels) * 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)
if blank:
# Return code 1: The image is considered blank.
sys.exit(1)
else:
# Return code 0: The image is not considered blank.
sys.exit(0)

9
scripts/init.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files
echo "Copying original files without overwriting existing files"
mkdir -p /usr/share/tesseract-ocr
cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr
# Run the main command
exec "$@"

134
scripts/split_photos.py Normal file
View File

@@ -0,0 +1,134 @@
import sys
import cv2
import numpy as np
import os
def find_photo_boundaries(image, background_color, tolerance=30, min_area=10000, min_contour_area=500):
mask = cv2.inRange(image, background_color - tolerance, background_color + tolerance)
mask = cv2.bitwise_not(mask)
kernel = np.ones((5,5),np.uint8)
mask = cv2.dilate(mask, kernel, iterations=2)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
photo_boundaries = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
area = w * h
contour_area = cv2.contourArea(contour)
if area >= min_area and contour_area >= min_contour_area:
photo_boundaries.append((x, y, w, h))
return photo_boundaries
def estimate_background_color(image, sample_points=5):
h, w, _ = image.shape
points = [
(0, 0),
(w - 1, 0),
(w - 1, h - 1),
(0, h - 1),
(w // 2, h // 2),
]
colors = []
for x, y in points:
colors.append(image[y, x])
return np.median(colors, axis=0)
def auto_rotate(image, angle_threshold=10):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) == 0:
return image
largest_contour = max(contours, key=cv2.contourArea)
mu = cv2.moments(largest_contour)
if mu["m00"] == 0:
return image
x_centroid = int(mu["m10"] / mu["m00"])
y_centroid = int(mu["m01"] / mu["m00"])
coords = np.column_stack(np.where(binary > 0))
u, _, vt = np.linalg.svd(coords - np.array([[y_centroid, x_centroid]]), full_matrices=False)
angle = np.arctan2(u[1, 0], u[0, 0]) * 180 / np.pi
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
if abs(angle) < angle_threshold:
return image
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
return cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
def crop_borders(image, border_color, tolerance=30):
mask = cv2.inRange(image, border_color - tolerance, border_color + tolerance)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) == 0:
return image
largest_contour = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(largest_contour)
return image[y:y+h, x:x+w]
def split_photos(input_file, output_directory, tolerance=30, min_area=10000, min_contour_area=500, angle_threshold=10, border_size=0):
image = cv2.imread(input_file)
background_color = estimate_background_color(image)
# Add a constant border around the image
image = cv2.copyMakeBorder(image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=background_color)
photo_boundaries = find_photo_boundaries(image, background_color, tolerance)
if not os.path.exists(output_directory):
os.makedirs(output_directory)
# Get the input file's base name without the extension
input_file_basename = os.path.splitext(os.path.basename(input_file))[0]
for idx, (x, y, w, h) in enumerate(photo_boundaries):
cropped_image = image[y:y+h, x:x+w]
cropped_image = auto_rotate(cropped_image, angle_threshold)
# Remove the added border
cropped_image = cropped_image[border_size:-border_size, border_size:-border_size]
output_path = os.path.join(output_directory, f"{input_file_basename}_{idx+1}.png")
cv2.imwrite(output_path, cropped_image)
print(f"Saved {output_path}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 split_photos.py <input_file> <output_directory> [tolerance] [min_area] [min_contour_area] [angle_threshold] [border_size]")
print("\nParameters:")
print(" <input_file> - The input scanned image containing multiple photos.")
print(" <output_directory> - The directory where the result images should be placed.")
print(" [tolerance] - Optional. Determines the range of color variation around the estimated background color (default: 30).")
print(" [min_area] - Optional. Sets the minimum area threshold for a photo (default: 10000).")
print(" [min_contour_area] - Optional. Sets the minimum contour area threshold for a photo (default: 500).")
print(" [angle_threshold] - Optional. Sets the minimum absolute angle required for the image to be rotated (default: 10).")
print(" [border_size] - Optional. Sets the size of the border added and removed to prevent white borders in the output (default: 0).")
sys.exit(1)
input_file = sys.argv[1]
output_directory = sys.argv[2]
tolerance = int(sys.argv[3]) if len(sys.argv) > 3 else 20
min_area = int(sys.argv[4]) if len(sys.argv) > 4 else 8000
min_contour_area = int(sys.argv[5]) if len(sys.argv) > 5 else 500
angle_threshold = int(sys.argv[6]) if len(sys.argv) > 6 else 60
border_size = int(sys.argv[7]) if len(sys.argv) > 7 else 0
split_photos(input_file, output_directory, tolerance=tolerance, min_area=min_area, min_contour_area=min_contour_area, angle_threshold=angle_threshold, border_size=border_size)

View File

@@ -1,4 +1,5 @@
package stirling.software.SPDF;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -6,32 +7,46 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LibreOfficeListener {
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
private static final int LISTENER_PORT = 2002;
private ExecutorService executorService;
private Process process;
private long lastActivityTime;
private LibreOfficeListener() {}
public static LibreOfficeListener getInstance() {
return INSTANCE;
}
private ExecutorService executorService;
private long lastActivityTime;
private Process process;
private LibreOfficeListener() {
}
private boolean isListenerRunning() {
try {
System.out.println("waiting for listener to start");
Socket socket = new Socket();
socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
socket.close();
return true;
} catch (IOException e) {
return false;
}
}
public void start() throws IOException {
// Check if the listener is already running
if (process != null && process.isAlive()) {
return;
}
// Start the listener process
process = Runtime.getRuntime().exec("unoconv --listener");
lastActivityTime = System.currentTimeMillis();
// Start a background thread to monitor the activity timeout
executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
@@ -49,46 +64,33 @@ public class LibreOfficeListener {
}
}
});
// Wait for the listener to start up
// Wait for the listener to start up
long startTime = System.currentTimeMillis();
long timeout = 30000; // Timeout after 30 seconds
while (System.currentTimeMillis() - startTime < timeout) {
if (isListenerRunning()) {
lastActivityTime = System.currentTimeMillis();
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // Check every 1 second
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // Check every 1 second
}
}
private boolean isListenerRunning() {
try {
System.out.println("waiting for listener to start");
Socket socket = new Socket();
socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
socket.close();
return true;
} catch (IOException e) {
return false;
}
}
public synchronized void stop() {
// Stop the activity timeout monitor thread
executorService.shutdownNow();
// Stop the listener process
if (process != null && process.isAlive()) {
process.destroy();
}
}
}

View File

@@ -6,6 +6,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SPdfApplication {
public static void main(String[] args) {
SpringApplication.run(SPdfApplication.class, args);
SpringApplication.run(SPdfApplication.class, args);
}
}

View File

@@ -3,41 +3,40 @@ package stirling.software.SPDF.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
public class AppConfig {
@Bean(name = "appName")
public String appName() {
String appName = System.getProperty("APP_HOME_NAME");
if (appName == null)
appName = System.getenv("APP_HOME_NAME");
return (appName != null) ? appName : "Stirling PDF";
}
@Bean(name = "appVersion")
public String appVersion() {
String version = getClass().getPackage().getImplementationVersion();
return (version != null) ? version : "0.3.3";
}
@Bean(name = "appName")
public String appName() {
String appName = System.getProperty("APP_HOME_NAME");
if(appName == null)
appName = System.getenv("APP_HOME_NAME");
return (appName != null) ? appName : "Stirling PDF";
}
@Bean(name = "navBarText")
public String navBarText() {
String navBarText = System.getProperty("APP_NAVBAR_NAME");
if(navBarText == null)
navBarText = System.getenv("APP_NAVBAR_NAME");
if(navBarText == null)
navBarText = System.getProperty("APP_HOME_NAME");
if(navBarText == null)
navBarText = System.getenv("APP_HOME_NAME");
return (navBarText != null) ? navBarText : "Stirling PDF";
}
@Bean(name = "homeText")
public String homeText() {
String homeText = System.getProperty("APP_HOME_DESCRIPTION");
if(homeText == null)
homeText = System.getenv("APP_HOME_DESCRIPTION");
if (homeText == null)
homeText = System.getenv("APP_HOME_DESCRIPTION");
return (homeText != null) ? homeText : "null";
}
@Bean(name = "navBarText")
public String navBarText() {
String navBarText = System.getProperty("APP_NAVBAR_NAME");
if (navBarText == null)
navBarText = System.getenv("APP_NAVBAR_NAME");
if (navBarText == null)
navBarText = System.getProperty("APP_HOME_NAME");
if (navBarText == null)
navBarText = System.getenv("APP_HOME_NAME");
return (navBarText != null) ? navBarText : "Stirling PDF";
}
}

View File

@@ -13,12 +13,25 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
@Configuration
public class Beans implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
registry.addInterceptor(new CleanUrlInterceptor());
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
String appLocaleEnv = System.getProperty("APP_LOCALE");
if(appLocaleEnv == null)
if (appLocaleEnv == null)
appLocaleEnv = System.getenv("APP_LOCALE");
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
@@ -37,16 +50,4 @@ public class Beans implements WebMvcConfigurer {
return slr;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}

View File

@@ -0,0 +1,45 @@
package stirling.software.SPDF.config;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class CleanUrlInterceptor implements HandlerInterceptor {
private static final Pattern LANG_PATTERN = Pattern.compile("&?lang=([^&]+)");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
String requestURI = request.getRequestURI();
// Keep the lang parameter if it exists
Matcher langMatcher = LANG_PATTERN.matcher(queryString);
String langQueryString = langMatcher.find() ? "lang=" + langMatcher.group(1) : "";
// Check if there are any other query parameters besides the lang parameter
String remainingQueryString = queryString.replaceAll(LANG_PATTERN.pattern(), "").replaceAll("&+", "&").replaceAll("^&|&$", "");
if (!remainingQueryString.isEmpty()) {
// Redirect to the URL without other query parameters
String redirectUrl = requestURI + (langQueryString.isEmpty() ? "" : "?" + langQueryString);
response.sendRedirect(redirectUrl);
return false;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
}

View File

@@ -0,0 +1,22 @@
package stirling.software.SPDF.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion();
version = (version != null) ? version : "1.0.0";
return new OpenAPI().components(new Components()).info(
new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
}
}

View File

@@ -1,180 +0,0 @@
package stirling.software.SPDF.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
@GetMapping("/ocr-pdf")
public ModelAndView ocrPdfPage() {
ModelAndView modelAndView = new ModelAndView("ocr-pdf");
modelAndView.addObject("languages", getAvailableTesseractLanguages());
modelAndView.addObject("currentPage", "ocr-pdf");
return modelAndView;
}
@PostMapping("/ocr-pdf")
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("languages") List<String> selectedLanguages,
@RequestParam(name = "sidecar", required = false) Boolean sidecar,
@RequestParam(name = "deskew", required = false) Boolean deskew,
@RequestParam(name = "clean", required = false) Boolean clean,
@RequestParam(name = "clean-final", required = false) Boolean cleanFinal,
@RequestParam(name = "ocrType", required = false) String ocrType) throws IOException, InterruptedException {
//--output-type pdfa
if (selectedLanguages == null || selectedLanguages.size() < 1) {
throw new IOException("Please select at least one language.");
}
// Validate and sanitize selected languages using regex
String languagePattern = "^[a-zA-Z]{3}$"; // Regex pattern for three-letter language codes
selectedLanguages = selectedLanguages.stream()
.filter(lang -> Pattern.matches(languagePattern, lang))
.collect(Collectors.toList());
if (selectedLanguages.isEmpty()) {
throw new IOException("None of the selected languages are valid.");
}
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the output file path
Path sidecarTextPath = null;
// Run OCR Command
String languageOption = String.join("+", selectedLanguages);
List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf","--verbose", "2", "--output-type", "pdf"));
if (sidecar != null && sidecar) {
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
command.add("--sidecar");
command.add(sidecarTextPath.toString());
}
if (deskew != null && deskew) {
command.add("--deskew");
}
if (clean != null && clean) {
command.add("--clean");
}
if (cleanFinal != null && cleanFinal) {
command.add("--clean-final");
}
if (ocrType != null && !ocrType.equals("")) {
if("skip-text".equals(ocrType)) {
command.add("--skip-text");
} else if("force-ocr".equals(ocrType)) {
command.add("--force-ocr");
} else if("Normal".equals(ocrType)) {
}
}
command.addAll(Arrays.asList("--language", languageOption,
tempInputFile.toString(), tempOutputFile.toString()));
//Run CLI command
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the OCR processed PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
// Return the OCR processed PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
HttpHeaders headers = new HttpHeaders();
if (sidecar != null && sidecar) {
// Create a zip file containing both the PDF and the text file
String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(outputFilename);
zipOut.putNextEntry(pdfEntry);
Files.copy(tempOutputFile, zipOut);
zipOut.closeEntry();
// Add text file to the zip
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
zipOut.putNextEntry(txtEntry);
Files.copy(sidecarTextPath, zipOut);
zipOut.closeEntry();
}
byte[] zipBytes = Files.readAllBytes(tempZipFile);
// Clean up the temporary zip file
Files.delete(tempZipFile);
Files.delete(tempOutputFile);
Files.delete(sidecarTextPath);
// Return the zip file containing both the PDF and the text file
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", outputZipFilename);
return ResponseEntity.ok().headers(headers).body(zipBytes);
} else {
// Return the OCR processed PDF as a response
Files.delete(tempOutputFile);
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", outputFilename);
return ResponseEntity.ok().headers(headers).body(pdfBytes);
}
}
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();
}
return Arrays.stream(files)
.filter(file -> file.getName().endsWith(".traineddata"))
.map(file -> file.getName().replace(".traineddata", ""))
.filter(lang -> !lang.equalsIgnoreCase("osd"))
.collect(Collectors.toList());
}
}

View File

@@ -1,43 +0,0 @@
package stirling.software.SPDF.controller;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class OverlayImageController {
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
@GetMapping("/add-image")
public String overlayImage(Model model) {
model.addAttribute("currentPage", "add-image");
return "add-image";
}
@PostMapping("/add-image")
public ResponseEntity<byte[]> overlayImage(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("fileInput2") MultipartFile imageFile, @RequestParam("x") float x,
@RequestParam("y") float y, @RequestParam("everyPage") boolean everyPage) {
try {
byte[] pdfBytes = pdfFile.getBytes();
byte[] imageBytes = imageFile.getBytes();
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage);
return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf");
} catch (IOException e) {
logger.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
}

View File

@@ -1,27 +0,0 @@
package stirling.software.SPDF.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PdfController {
private static final Logger logger = LoggerFactory.getLogger(PdfController.class);
@GetMapping("/home")
public String root(Model model) {
return "redirect:/";
}
@GetMapping("/")
public String home(Model model) {
model.addAttribute("currentPage", "home");
return "home";
}
}

View File

@@ -1,67 +1,78 @@
package stirling.software.SPDF.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
@GetMapping("/merge-pdfs")
public String hello(Model model) {
model.addAttribute("currentPage", "merge-pdfs");
return "merge-pdfs";
}
@PostMapping("/merge-pdfs")
public ResponseEntity<byte[]> mergePdfs(@RequestParam("fileInput") MultipartFile[] files) throws IOException {
// Read the input PDF files into PDDocument objects
List<PDDocument> documents = new ArrayList<>();
// Loop through the files array and read each file into a PDDocument
for (MultipartFile file : files) {
documents.add(PDDocument.load(file.getInputStream()));
}
PDDocument mergedDoc = mergeDocuments(documents);
// Return the merged PDF as a response
return PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_merged.pdf");
}
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
// Create a new empty document
PDDocument mergedDoc = new PDDocument();
// Iterate over the list of documents and add their pages to the merged document
for (PDDocument doc : documents) {
// Get all pages from the current document
PDPageTree pages = doc.getPages();
// Iterate over the pages and add them to the merged document
for (PDPage page : pages) {
mergedDoc.addPage(page);
}
}
// Return the merged document
return mergedDoc;
}
package stirling.software.SPDF.controller.api;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
// Create a new empty document
PDDocument mergedDoc = new PDDocument();
// Iterate over the list of documents and add their pages to the merged document
for (PDDocument doc : documents) {
// Get all pages from the current document
PDPageTree pages = doc.getPages();
// Iterate over the pages and add them to the merged document
for (PDPage page : pages) {
mergedDoc.addPage(page);
}
}
// Return the merged document
return mergedDoc;
}
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
@Operation(
summary = "Merge multiple PDF files into one",
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided."
)
public ResponseEntity<byte[]> mergePdfs(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF files to be merged into a single file", required = true)
MultipartFile[] files) throws IOException {
// Read the input PDF files into PDDocument objects
List<PDDocument> documents = new ArrayList<>();
// Loop through the files array and read each file into a PDDocument
for (MultipartFile file : files) {
documents.add(PDDocument.load(file.getInputStream()));
}
PDDocument mergedDoc = mergeDocuments(documents);
// Return the merged PDF as a response
ResponseEntity<byte[]> response = PdfUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
for (PDDocument doc : documents) {
// Close the document after processing
doc.close();
}
return response;
}
}

View File

@@ -1,121 +1,127 @@
package stirling.software.SPDF.controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
@GetMapping("/pdf-organizer")
public String pageOrganizer(Model model) {
model.addAttribute("currentPage", "pdf-organizer");
return "pdf-organizer";
}
@GetMapping("/remove-pages")
public String pageDeleter(Model model) {
model.addAttribute("currentPage", "remove-pages");
return "remove-pages";
}
@PostMapping("/remove-pages")
public ResponseEntity<byte[]> deletePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pagesToDelete") String pagesToDelete) throws IOException {
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
document.removePage(pageIndex);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
}
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array
for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
return newPageOrder;
}
@PostMapping("/rearrange-pages")
public ResponseEntity<byte[]> rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pageOrder.split(",");
// int[] newPageOrder = new int[pageOrderArr.length];
int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int i = 0; i < newPageOrder.size(); i++) {
newPages.add(document.getPage(newPageOrder.get(i)));
}
// Remove all the pages from the original document
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
document.removePage(i);
}
// Add the pages in the new order
for (PDPage page : newPages) {
document.addPage(page);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
}
}
}
package stirling.software.SPDF.controller.api;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@Operation(summary = "Remove pages from a PDF file",
description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.")
public ResponseEntity<byte[]> deletePages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file from which pages will be removed")
MultipartFile pdfFile,
@RequestParam("pagesToDelete")
@Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'")
String pagesToDelete) throws IOException {
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
document.removePage(pageIndex);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
}
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array
for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
return newPageOrder;
}
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
@Operation(summary = "Rearrange pages in a PDF file",
description = "This endpoint rearranges pages in a given PDF file based on the specified page order. Users can provide a page order as a comma-separated list of page numbers or page ranges.")
public ResponseEntity<byte[]> rearrangePages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to rearrange pages")
MultipartFile pdfFile,
@RequestParam("pageOrder")
@Parameter(description = "The new page order as a comma-separated list of page numbers or page ranges (e.g., '1,3,5-7')")
String pageOrder) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pageOrder.split(",");
// int[] newPageOrder = new int[pageOrderArr.length];
int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = pageOrderToString(pageOrderArr, totalPages);
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int i = 0; i < newPageOrder.size(); i++) {
newPages.add(document.getPage(newPageOrder.get(i)));
}
// Remove all the pages from the original document
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
document.removePage(i);
}
// Add the pages in the new order
for (PDPage page : newPages) {
document.addPage(page);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
}
}
}

View File

@@ -1,48 +1,53 @@
package stirling.software.SPDF.controller;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class RotationController {
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
@GetMapping("/rotate-pdf")
public String rotatePdfForm(Model model) {
model.addAttribute("currentPage", "rotate-pdf");
return "rotate-pdf";
}
@PostMapping("/rotate-pdf")
public ResponseEntity<byte[]> rotatePDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("angle") Integer angle) throws IOException {
// Load the PDF document
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the list of pages in the document
PDPageTree pages = document.getPages();
for (PDPage page : pages) {
page.setRotation(page.getRotation() + angle);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
}
}
package stirling.software.SPDF.controller.api;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class RotationController {
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@Operation(
summary = "Rotate a PDF file",
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90."
)
public ResponseEntity<byte[]> rotatePDF(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The PDF file to be rotated", required = true)
MultipartFile pdfFile,
@RequestParam("angle")
@Parameter(description = "The angle by which to rotate the PDF file. This should be a multiple of 90.", example = "90", required = true)
Integer angle) throws IOException {
// Load the PDF document
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the list of pages in the document
PDPageTree pages = document.getPages();
for (PDPage page : pages) {
page.setRotation(page.getRotation() + angle);
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
}
}

View File

@@ -1,134 +1,140 @@
package stirling.software.SPDF.controller;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
@GetMapping("/split-pdfs")
public String splitPdfForm(Model model) {
model.addAttribute("currentPage", "split-pdfs");
return "split-pdfs";
}
@PostMapping("/split-pages")
public ResponseEntity<Resource> splitPdf(@RequestParam("fileInput") MultipartFile file, @RequestParam("pages") String pages) throws IOException {
// parse user input
// open the pdf document
InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
List<Integer> pageNumbers = new ArrayList<>();
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
if (pages.toLowerCase().equals("all")) {
for (int i = 0; i < document.getNumberOfPages(); i++) {
pageNumbers.add(i);
}
} else {
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
String lastpage = String.valueOf(document.getNumberOfPages());
pageNumbersStr.add(lastpage);
}
for (String page : pageNumbersStr) {
if (page.contains("-")) {
String[] range = page.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
for (int i = start; i <= end; i++) {
pageNumbers.add(i);
}
} else {
pageNumbers.add(Integer.parseInt(page));
}
}
}
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int currentPage = 0;
for (int pageNumber : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) {
for (int i = currentPage; i < pageNumber; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.debug("Adding page {} to split document", i);
}
currentPage = pageNumber;
logger.debug("Setting current page to {}", currentPage);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
throw e;
}
}
// closing the original document
document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = "split_document_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
ByteArrayResource resource = new ByteArrayResource(data);
Files.delete(zipFile);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);
}
}
package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@RestController
public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
@Operation(summary = "Split a PDF file into separate documents",
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page.")
public ResponseEntity<Resource> splitPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be split")
MultipartFile file,
@RequestParam("pages")
@Parameter(description = "The pages to be included in separate documents. Specify individual page numbers (e.g., '1,3,5'), ranges (e.g., '1-3,5-7'), or 'all' for every page.")
String pages) throws IOException {
// parse user input
// open the pdf document
InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
List<Integer> pageNumbers = new ArrayList<>();
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
if (pages.toLowerCase().equals("all")) {
for (int i = 0; i < document.getNumberOfPages(); i++) {
pageNumbers.add(i);
}
} else {
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
String lastpage = String.valueOf(document.getNumberOfPages());
pageNumbersStr.add(lastpage);
}
for (String page : pageNumbersStr) {
if (page.contains("-")) {
String[] range = page.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
for (int i = start; i <= end; i++) {
pageNumbers.add(i);
}
} else {
pageNumbers.add(Integer.parseInt(page));
}
}
}
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int currentPage = 0;
for (int pageNumber : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) {
for (int i = currentPage; i < pageNumber; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.debug("Adding page {} to split document", i);
}
currentPage = pageNumber;
logger.debug("Setting current page to {}", currentPage);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
throw e;
}
}
// closing the original document
document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = "split_document_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
ByteArrayResource resource = new ByteArrayResource(data);
Files.delete(zipFile);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_split.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
}
}

View File

@@ -0,0 +1,115 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
@Operation(summary = "Convert PDF to image(s)",
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images.")
public ResponseEntity<Resource> convertToImage(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be converted")
MultipartFile file,
@RequestParam("imageFormat")
@Parameter(description = "The output image format", schema = @Schema(allowableValues = {"png", "jpeg", "jpg", "gif"}))
String imageFormat,
@RequestParam("singleOrMultiple")
@Parameter(description = "Choose between a single image containing all pages or separate images for each page", schema = @Schema(allowableValues = {"single", "multiple"}))
String singleOrMultiple,
@RequestParam("colorType")
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"rgb", "greyscale", "blackwhite"}))
String colorType,
@RequestParam("dpi")
@Parameter(description = "The DPI (dots per inch) for the output image(s)")
String dpi) throws IOException {
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = singleOrMultiple.equals("single");
byte[] result = null;
try {
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (singleImage) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else {
ByteArrayResource resource = new ByteArrayResource(result);
// return the Resource in the response
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
}
}
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
@Operation(summary = "Convert images to a PDF file",
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.")
public ResponseEntity<byte[]> convertToPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input images to be converted to a PDF file")
MultipartFile[] file,
@RequestParam(defaultValue = "false", name = "stretchToFit")
@Parameter(description = "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", example = "false")
boolean stretchToFit,
@RequestParam("colorType")
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"rgb", "greyscale", "blackwhite"}))
String colorType,
@RequestParam(defaultValue = "false", name = "autoRotate")
@Parameter(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true")
boolean autoRotate) throws IOException {
// Convert the file to PDF and get the resulting bytes
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate, colorType);
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_coverted.pdf");
}
private String getMediaType(String imageFormat) {
if (imageFormat.equalsIgnoreCase("PNG"))
return "image/png";
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
return "image/jpeg";
else if (imageFormat.equalsIgnoreCase("GIF"))
return "image/gif";
else
return "application/octet-stream";
}
}

View File

@@ -0,0 +1,78 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@RestController
public class ConvertOfficeController {
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension
String originalFilename = inputFile.getOriginalFilename();
if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension");
}
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Run the LibreOffice command
List<String> command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString()));
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
// Read the converted PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
return pdfBytes;
}
private boolean isValidFileExtension(String fileExtension) {
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
return fileExtension.matches(extensionPattern);
}
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
@Operation(
summary = "Convert a file to a PDF using OCR",
description = "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with '_convertedToPDF.pdf' appended."
)
public ResponseEntity<byte[]> processPdfWithOCR(
@RequestPart(required = true, value = "fileInput")
@Parameter(
description = "The input file to be converted to a PDF file using OCR",
required = true
)
MultipartFile inputFile
) throws IOException, InterruptedException {
// unused but can start server instance if startup time is to long
// LibreOfficeListener.getInstance().start();
byte[] pdfByteArray = convertToPdf(inputFile);
return PdfUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf");
}
}

View File

@@ -0,0 +1,72 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.PDFToFile;
@RestController
public class ConvertPDFToOffice {
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format.")
public ResponseEntity<byte[]> processPdfToHTML(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format.")
public ResponseEntity<byte[]> processPdfToPresentation(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
"ppt", "pptx", "odp" })) String outputFormat)
throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format.")
public ResponseEntity<byte[]> processPdfToRTForTXT(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
"rtf", "txt:Text" })) String outputFormat)
throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format.")
public ResponseEntity<byte[]> processPdfToWord(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
"doc", "docx", "odt" })) String outputFormat)
throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file.")
public ResponseEntity<byte[]> processPdfToXML(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
}
}

View File

@@ -1,4 +1,4 @@
package stirling.software.SPDF.controller.converters;
package stirling.software.SPDF.controller.api.converters;
import java.io.IOException;
import java.nio.file.Files;
@@ -6,32 +6,30 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
@RestController
public class ConvertPDFToPDFA {
@GetMapping("/pdf-to-pdfa")
public String pdfToPdfAForm(Model model) {
model.addAttribute("currentPage", "pdf-to-pdfa");
return "convert/pdf-to-pdfa";
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
@Operation(
summary = "Convert a PDF to a PDF/A",
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents."
)
public ResponseEntity<byte[]> pdfToPdfA(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true)
MultipartFile inputFile) throws IOException, InterruptedException {
@PostMapping("/pdf-to-pdfa")
public ResponseEntity<byte[]> pdfToPdfA(
@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
@@ -50,7 +48,7 @@ public class ConvertPDFToPDFA {
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
@@ -60,11 +58,7 @@ public class ConvertPDFToPDFA {
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", outputFilename);
return ResponseEntity.ok().headers(headers).body(pdfBytes);
}
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@@ -0,0 +1,128 @@
package stirling.software.SPDF.controller.api.other;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.imageio.ImageIO;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.ImageFinder;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@RestController
public class BlankPageController {
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
@Operation(
summary = "Remove blank pages from a PDF file",
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages."
)
public ResponseEntity<byte[]> removeBlankPages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file from which blank pages will be removed", required = true)
MultipartFile inputFile,
@RequestParam(defaultValue = "10", name = "threshold")
@Parameter(description = "The threshold value to determine blank pages", example = "10")
int threshold,
@RequestParam(defaultValue = "99.9", name = "whitePercent")
@Parameter(description = "The percentage of white color on a page to consider it as blank", example = "99.9")
float whitePercent) throws IOException, InterruptedException {
PDDocument document = null;
try {
document = PDDocument.load(inputFile.getInputStream());
PDPageTree pages = document.getDocumentCatalog().getPages();
PDFTextStripper textStripper = new PDFTextStripper();
List<Integer> pagesToKeepIndex = new ArrayList<>();
int pageIndex = 0;
PDFRenderer pdfRenderer = new PDFRenderer(document);
for (PDPage page : pages) {
System.out.println("checking page " + pageIndex);
textStripper.setStartPage(pageIndex + 1);
textStripper.setEndPage(pageIndex + 1);
String pageText = textStripper.getText(document);
boolean hasText = !pageText.trim().isEmpty();
if (hasText) {
pagesToKeepIndex.add(pageIndex);
System.out.println("page " + pageIndex + " has text");
} else {
boolean hasImages = hasImagesOnPage(page);
if (hasImages) {
System.out.println("page " + pageIndex + " has image");
Path tempFile = Files.createTempFile("image_", ".png");
// Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300);
ImageIO.write(image, "png", tempFile.toFile());
List<String> command = new ArrayList<>(Arrays.asList("python3", System.getProperty("user.dir") + "scripts/detect-blank-pages.py", tempFile.toString() ,"--threshold", String.valueOf(threshold), "--white_percent", String.valueOf(whitePercent)));
// Run CLI command
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
// does contain data
if (returnCode == 0) {
System.out.println("page " + pageIndex + " has image which is not blank");
pagesToKeepIndex.add(pageIndex);
} else {
System.out.println("Skipping, Image was blank for page #" + pageIndex);
}
}
}
pageIndex++;
}
System.out.print("pagesToKeep=" + pagesToKeepIndex.size());
// Remove pages not present in pagesToKeepIndex
List<Integer> pageIndices = IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
Collections.reverse(pageIndices); // Reverse to prevent index shifting during removal
for (Integer i : pageIndices) {
if (!pagesToKeepIndex.contains(i)) {
pages.remove(i);
}
}
return PdfUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_blanksRemoved.pdf");
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
if (document != null)
document.close();
}
}
private static boolean hasImagesOnPage(PDPage page) throws IOException {
ImageFinder imageFinder = new ImageFinder(page);
imageFinder.processPage(page);
return imageFinder.hasImages();
}
}

View File

@@ -1,92 +1,94 @@
package stirling.software.SPDF.controller;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
@GetMapping("/compress-pdf")
public String compressPdfForm(Model model) {
model.addAttribute("currentPage", "compress-pdf");
return "compress-pdf";
}
@PostMapping("/compress-pdf")
public ResponseEntity<byte[]> optimizePdf(
@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("optimizeLevel") int optimizeLevel,
@RequestParam(name = "fastWebView", required = false) Boolean fastWebView,
@RequestParam(name = "jbig2Lossy", required = false) Boolean jbig2Lossy) throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command
List<String> command = new ArrayList<>();
command.add("ocrmypdf");
command.add("--skip-text");
command.add("--tesseract-timeout=0");
command.add("--optimize");
command.add(String.valueOf(optimizeLevel));
command.add("--output-type");
command.add("pdf");
if (fastWebView != null && fastWebView) {
long fileSize = inputFile.getSize();
long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size
command.add("--fast-web-view");
command.add(String.valueOf(fastWebViewSize));
}
if (jbig2Lossy != null && jbig2Lossy) {
command.add("--jbig2-lossy");
}
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", outputFilename);
return ResponseEntity.ok().headers(headers).body(pdfBytes);
}
}
package stirling.software.SPDF.controller.api.other;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import io.swagger.v3.oas.annotations.media.Schema;
@RestController
public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
@Operation(
summary = "Optimize PDF file",
description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters."
)
public ResponseEntity<byte[]> optimizePdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be optimized.", required = true)
MultipartFile inputFile,
@RequestParam("optimizeLevel")
@Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.",
schema = @Schema(allowableValues = {"0", "1", "2", "3"}), example = "1")
int optimizeLevel,
@RequestParam(name = "fastWebView", required = false)
@Parameter(description = "If true, optimize the PDF for fast web view. This increases the file size by about 25%.", example = "false")
Boolean fastWebView,
@RequestParam(name = "jbig2Lossy", required = false)
@Parameter(description = "If true, apply lossy JB2 compression to the PDF file.", example = "false")
Boolean jbig2Lossy)
throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command
List<String> command = new ArrayList<>();
command.add("ocrmypdf");
command.add("--skip-text");
command.add("--tesseract-timeout=0");
command.add("--optimize");
command.add(String.valueOf(optimizeLevel));
command.add("--output-type");
command.add("pdf");
if (fastWebView != null && fastWebView) {
long fileSize = inputFile.getSize();
long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size
command.add("--fast-web-view");
command.add(String.valueOf(fastWebViewSize));
}
if (jbig2Lossy != null && jbig2Lossy) {
command.add("--jbig2-lossy");
}
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@@ -0,0 +1,159 @@
package stirling.software.SPDF.controller.api.other;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@RestController
public class ExtractImageScansController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
@Operation(summary = "Extract image scans from an input file",
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size.")
public ResponseEntity<byte[]> extractImageScans(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input file containing image scans")
MultipartFile inputFile,
@RequestParam(name = "angle_threshold", defaultValue = "5")
@Parameter(description = "The angle threshold for the image scan extraction", example = "5")
int angleThreshold,
@RequestParam(name = "tolerance", defaultValue = "20")
@Parameter(description = "The tolerance for the image scan extraction", example = "20")
int tolerance,
@RequestParam(name = "min_area", defaultValue = "8000")
@Parameter(description = "The minimum area for the image scan extraction", example = "8000")
int minArea,
@RequestParam(name = "min_contour_area", defaultValue = "500")
@Parameter(description = "The minimum contour area for the image scan extraction", example = "500")
int minContourArea,
@RequestParam(name = "border_size", defaultValue = "1")
@Parameter(description = "The border size for the image scan extraction", example = "1")
int borderSize) throws IOException, InterruptedException {
String fileName = inputFile.getOriginalFilename();
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
List<String> images = new ArrayList<>();
// Check if input file is a PDF
if (extension.equalsIgnoreCase("pdf")) {
// Load PDF document
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputFile.getBytes()))) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages();
images = new ArrayList<>();
// Create images of all pages
for (int i = 0; i < pageCount; i++) {
// Create temp file to save the image
Path tempFile = Files.createTempFile("image_", ".png");
// Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300);
ImageIO.write(image, "png", tempFile.toFile());
// Add temp file path to images list
images.add(tempFile.toString());
}
}
} else {
Path tempInputFile = Files.createTempFile("input_", "." + extension);
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Add input file path to images list
images.add(tempInputFile.toString());
}
List<byte[]> processedImageBytes = new ArrayList<>();
// Process each image
for (int i = 0; i < images.size(); i++) {
Path tempDir = Files.createTempDirectory("openCV_output");
List<String> command = new ArrayList<>(Arrays.asList(
"python3",
"./scripts/split_photos.py",
images.get(i),
tempDir.toString(),
"--angle_threshold", String.valueOf(angleThreshold),
"--tolerance", String.valueOf(tolerance),
"--min_area", String.valueOf(minArea),
"--min_contour_area", String.valueOf(minContourArea),
"--border_size", String.valueOf(borderSize)
));
// Run CLI command
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
// Read the output photos in temp directory
List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
for (Path tempOutputFile : tempOutputFiles) {
byte[] imageBytes = Files.readAllBytes(tempOutputFile);
processedImageBytes.add(imageBytes);
}
// Clean up the temporary directory
FileUtils.deleteDirectory(tempDir.toFile());
}
// Create zip file if multiple images
if (processedImageBytes.size() > 1) {
String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add processed images to the zip
for (int i = 0; i < processedImageBytes.size(); i++) {
ZipEntry entry = new ZipEntry(fileName.replaceFirst("[.][^.]+$", "") + "_" + (i + 1) + ".png");
zipOut.putNextEntry(entry);
zipOut.write(processedImageBytes.get(i));
zipOut.closeEntry();
}
}
byte[] zipBytes = Files.readAllBytes(tempZipFile);
// Clean up the temporary zip file
Files.delete(tempZipFile);
return PdfUtils.bytesToWebResponse(zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
} else {
// Return the processed image as a response
byte[] imageBytes = processedImageBytes.get(0);
return PdfUtils.bytesToWebResponse(imageBytes, fileName.replaceFirst("[.][^.]+$", "") + ".png", MediaType.IMAGE_PNG);
}
}
}

View File

@@ -1,4 +1,4 @@
package stirling.software.SPDF.controller;
package stirling.software.SPDF.controller.api.other;
import java.awt.Graphics2D;
import java.awt.Image;
@@ -18,33 +18,34 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Controller
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class ExtractImagesController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
@GetMapping("/extract-images")
public String extractImagesForm(Model model) {
model.addAttribute("currentPage", "extract-images");
return "extract-images";
}
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
@Operation(summary = "Extract images from a PDF file",
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format.")
public ResponseEntity<byte[]> extractImages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file containing images")
MultipartFile file,
@RequestParam("format")
@Parameter(description = "The output image format e.g., 'png', 'jpeg', or 'gif'", schema = @Schema(allowableValues = {"png", "jpeg", "gif"}))
String format) throws IOException {
@PostMapping("/extract-images")
public ResponseEntity<Resource> extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException {
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
PDDocument document = PDDocument.load(file.getBytes());
@@ -58,7 +59,7 @@ public class ExtractImagesController {
zos.setLevel(Deflater.BEST_COMPRESSION);
int imageIndex = 1;
int pageNum = 1;
// Iterate over each page
for (PDPage page : document.getPages()) {
@@ -72,21 +73,18 @@ public class ExtractImagesController {
RenderedImage renderedImage = image.getImage();
BufferedImage bufferedImage = null;
if (format.equalsIgnoreCase("png")) {
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_ARGB);
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
} else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) {
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_RGB);
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_RGB);
} else if (format.equalsIgnoreCase("gif")) {
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED);
}
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
}
// Write image to zip file
String imageName = "Image " + imageIndex + " (Page " + pageNum + ")." + format;
ZipEntry zipEntry = new ZipEntry(imageName);
zos.putNextEntry(zipEntry);
Graphics2D g = bufferedImage.createGraphics();
g.drawImage((Image) renderedImage, 0, 0, null);
g.dispose();
@@ -94,12 +92,11 @@ public class ExtractImagesController {
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
zos.write(imageBaos.toByteArray());
zos.closeEntry();
imageIndex++;
}
}
}
}
// Close ZipOutputStream and PDDocument
@@ -108,22 +105,8 @@ public class ExtractImagesController {
// Create ByteArrayResource from byte array
byte[] zipContents = baos.toByteArray();
ByteArrayResource resource = new ByteArrayResource(zipContents);
// Set content disposition header to indicate that the response should be downloaded as a file
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(zipContents.length);
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip");
// Return ResponseEntity with ByteArrayResource and headers
return ResponseEntity
.status(HttpStatus.OK)
.headers(headers)
.header("Cache-Control", "no-cache")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
return PdfUtils.boasToWebResponse(baos, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
}
}

View File

@@ -1,4 +1,4 @@
package stirling.software.SPDF.controller.security;
package stirling.software.SPDF.controller.api.other;
import java.io.IOException;
import java.text.ParseException;
@@ -11,49 +11,77 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
@RestController
public class MetadataController {
@GetMapping("/change-metadata")
public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "change-metadata");
return "security/change-metadata";
}
private String checkUndefined(String entry) {
// Check if the string is "undefined"
if("undefined".equals(entry)) {
if ("undefined".equals(entry)) {
// Return null if it is
return null;
}
// Return the original string if it's not "undefined"
return entry;
}
@PostMapping("/update-metadata")
public ResponseEntity<byte[]> metadata(@RequestParam("fileInput") MultipartFile pdfFile,
@RequestParam(value = "deleteAll", required = false, defaultValue = "false") Boolean deleteAll, @RequestParam(value = "author", required = false) String author,
@RequestParam(value = "creationDate", required = false) String creationDate, @RequestParam(value = "creator", required = false) String creator,
@RequestParam(value = "keywords", required = false) String keywords, @RequestParam(value = "modificationDate", required = false) String modificationDate,
@RequestParam(value = "producer", required = false) String producer, @RequestParam(value = "subject", required = false) String subject,
@RequestParam(value = "title", required = false) String title, @RequestParam(value = "trapped", required = false) String trapped,
@RequestParam Map<String, String> allRequestParams) throws IOException {
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
@Operation(summary = "Update metadata of a PDF file",
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields.")
public ResponseEntity<byte[]> metadata(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to update metadata")
MultipartFile pdfFile,
@RequestParam(value = "deleteAll", required = false, defaultValue = "false")
@Parameter(description = "Delete all metadata if set to true")
Boolean deleteAll,
@RequestParam(value = "author", required = false)
@Parameter(description = "The author of the document")
String author,
@RequestParam(value = "creationDate", required = false)
@Parameter(description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)")
String creationDate,
@RequestParam(value = "creator", required = false)
@Parameter(description = "The creator of the document")
String creator,
@RequestParam(value = "keywords", required = false)
@Parameter(description = "The keywords for the document")
String keywords,
@RequestParam(value = "modificationDate", required = false)
@Parameter(description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)")
String modificationDate,
@RequestParam(value = "producer", required = false)
@Parameter(description = "The producer of the document")
String producer,
@RequestParam(value = "subject", required = false)
@Parameter(description = "The subject of the document")
String subject,
@RequestParam(value = "title", required = false)
@Parameter(description = "The title of the document")
String title,
@RequestParam(value = "trapped", required = false)
@Parameter(description = "The trapped status of the document")
String trapped,
@RequestParam Map<String, String> allRequestParams)
throws IOException {
// Load the PDF file into a PDDocument
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the document information from the PDF
PDDocumentInformation info = document.getDocumentInformation();
// Check if each metadata value is "undefined" and set it to null if it is
author = checkUndefined(author);
creationDate = checkUndefined(creationDate);
@@ -64,8 +92,9 @@ public class MetadataController {
subject = checkUndefined(subject);
title = checkUndefined(title);
trapped = checkUndefined(trapped);
// If the "deleteAll" flag is set, remove all metadata from the document information
// If the "deleteAll" flag is set, remove all metadata from the document
// information
if (deleteAll) {
for (String key : info.getMetadataKeys()) {
info.setCustomMetadataValue(key, null);
@@ -83,7 +112,7 @@ public class MetadataController {
title = null;
trapped = null;
} else {
// Iterate through the request parameters and set the metadata values
// Iterate through the request parameters and set the metadata values
for (Entry<String, String> entry : allRequestParams.entrySet()) {
String key = entry.getKey();
// Check if the key is a standard metadata key
@@ -128,11 +157,9 @@ public class MetadataController {
info.setSubject(subject);
info.setTitle(title);
info.setTrapped(trapped);
document.setDocumentInformation(info);
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf");
}
}

View File

@@ -0,0 +1,201 @@
package stirling.software.SPDF.controller.api.other;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@RestController
public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", ""))
.filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList());
}
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation(summary = "Process a PDF file with OCR",
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.")
public ResponseEntity<byte[]> processPdfWithOCR(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be processed with OCR")
MultipartFile inputFile,
@RequestParam("languages")
@Parameter(description = "List of languages to use in OCR processing")
List<String> selectedLanguages,
@RequestParam(name = "sidecar", required = false)
@Parameter(description = "Include OCR text in a sidecar text file if set to true")
Boolean sidecar,
@RequestParam(name = "deskew", required = false)
@Parameter(description = "Deskew the input file if set to true")
Boolean deskew,
@RequestParam(name = "clean", required = false)
@Parameter(description = "Clean the input file if set to true")
Boolean clean,
@RequestParam(name = "clean-final", required = false)
@Parameter(description = "Clean the final output if set to true")
Boolean cleanFinal,
@RequestParam(name = "ocrType", required = false)
@Parameter(description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", schema = @Schema(allowableValues = {"skip-text", "force-ocr", "Normal"}))
String ocrType,
@RequestParam(name = "ocrRenderType", required = false, defaultValue = "hocr")
@Parameter(description = "Specify the OCR render type, either 'hocr' or 'sandwich'", schema = @Schema(allowableValues = {"hocr", "sandwich"}))
String ocrRenderType,
@RequestParam(name = "removeImagesAfter", required = false)
@Parameter(description = "Remove images from the output PDF if set to true")
Boolean removeImagesAfter) throws IOException, InterruptedException {
// --output-type pdfa
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
throw new IOException("Please select at least one language.");
}
if(!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) {
throw new IOException("ocrRenderType wrong");
}
// Get available Tesseract languages
List<String> availableLanguages = getAvailableTesseractLanguages();
// Validate selected languages
selectedLanguages = selectedLanguages.stream().filter(availableLanguages::contains).toList();
if (selectedLanguages.isEmpty()) {
throw new IOException("None of the selected languages are valid.");
}
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the output file path
Path sidecarTextPath = null;
// Run OCR Command
String languageOption = String.join("+", selectedLanguages);
List<String> command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf", "--pdf-renderer" , ocrRenderType));
if (sidecar != null && sidecar) {
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
command.add("--sidecar");
command.add(sidecarTextPath.toString());
}
if (deskew != null && deskew) {
command.add("--deskew");
}
if (clean != null && clean) {
command.add("--clean");
}
if (cleanFinal != null && cleanFinal) {
command.add("--clean-final");
}
if (ocrType != null && !ocrType.equals("")) {
if ("skip-text".equals(ocrType)) {
command.add("--skip-text");
} else if ("force-ocr".equals(ocrType)) {
command.add("--force-ocr");
} else if ("Normal".equals(ocrType)) {
}
}
command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString()));
// Run CLI command
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
// Remove images from the OCR processed PDF if the flag is set to true
if (removeImagesAfter != null && removeImagesAfter) {
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
List<String> gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString());
int gsReturnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand);
tempOutputFile = tempPdfWithoutImages;
}
// Read the OCR processed PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
// Return the OCR processed PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
if (sidecar != null && sidecar) {
// Create a zip file containing both the PDF and the text file
String outputZipFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(outputFilename);
zipOut.putNextEntry(pdfEntry);
Files.copy(tempOutputFile, zipOut);
zipOut.closeEntry();
// Add text file to the zip
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
zipOut.putNextEntry(txtEntry);
Files.copy(sidecarTextPath, zipOut);
zipOut.closeEntry();
}
byte[] zipBytes = Files.readAllBytes(tempZipFile);
// Clean up the temporary zip file
Files.delete(tempZipFile);
Files.delete(tempOutputFile);
Files.delete(sidecarTextPath);
// Return the zip file containing both the PDF and the text file
return PdfUtils.bytesToWebResponse(pdfBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
} else {
// Return the OCR processed PDF as a response
Files.delete(tempOutputFile);
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}
}

View File

@@ -0,0 +1,56 @@
package stirling.software.SPDF.controller.api.other;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class OverlayImageController {
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
@Operation(
summary = "Overlay image onto a PDF file",
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."
)
public ResponseEntity<byte[]> overlayImage(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to overlay the image onto.", required = true)
MultipartFile pdfFile,
@RequestParam("fileInput2")
@Parameter(description = "The image file to be overlaid onto the PDF.", required = true)
MultipartFile imageFile,
@RequestParam("x")
@Parameter(description = "The x-coordinate at which to place the top-left corner of the image.", example = "0")
float x,
@RequestParam("y")
@Parameter(description = "The y-coordinate at which to place the top-left corner of the image.", example = "0")
float y,
@RequestParam("everyPage")
@Parameter(description = "Whether to overlay the image onto every page of the PDF.", example = "false")
boolean everyPage) {
try {
byte[] pdfBytes = pdfFile.getBytes();
byte[] imageBytes = imageFile.getBytes();
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage);
return PdfUtils.bytesToWebResponse(result, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf");
} catch (IOException e) {
logger.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
}

View File

@@ -0,0 +1,66 @@
package stirling.software.SPDF.controller.api.other;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@RestController
public class RepairController {
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
@PostMapping(consumes = "multipart/form-data", value = "/repair")
@Operation(
summary = "Repair a PDF file",
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
)
public ResponseEntity<byte[]> repairPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be repaired", required = true)
MultipartFile inputFile) throws IOException, InterruptedException {
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
List<String> command = new ArrayList<>();
command.add("gs");
command.add("-o");
command.add(tempOutputFile.toString());
command.add("-sDEVICE=pdfwrite");
command.add(tempInputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_repaired.pdf";
return PdfUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@@ -0,0 +1,107 @@
package stirling.software.SPDF.controller.api.security;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
import io.swagger.v3.oas.annotations.media.Schema;
@RestController
public class PasswordController {
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
@Operation(
summary = "Remove password from a PDF file",
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password."
)
public ResponseEntity<byte[]> removePassword(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file from which the password should be removed", required = true)
MultipartFile fileInput,
@RequestParam(name = "password")
@Parameter(description = "The password of the PDF file", required = true)
String password) throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
}
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
@Operation(
summary = "Add password to a PDF file",
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file."
)
public ResponseEntity<byte[]> addPassword(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to which the password should be added", required = true)
MultipartFile fileInput,
@RequestParam(defaultValue = "", name = "password")
@Parameter(description = "The password to be added to the PDF file")
String password,
@RequestParam(defaultValue = "128", name = "keyLength")
@Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"}))
int keyLength,
@RequestParam(defaultValue = "false", name = "canAssembleDocument")
@Parameter(description = "Whether the document assembly is allowed", example = "false")
boolean canAssembleDocument,
@RequestParam(defaultValue = "false", name = "canExtractContent")
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
boolean canExtractContent,
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility")
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
boolean canExtractForAccessibility,
@RequestParam(defaultValue = "false", name = "canFillInForm")
@Parameter(description = "Whether form filling is allowed", example = "false")
boolean canFillInForm,
@RequestParam(defaultValue = "false", name = "canModify")
@Parameter(description = "Whether the document modification is allowed", example = "false")
boolean canModify,
@RequestParam(defaultValue = "false", name = "canModifyAnnotations")
@Parameter(description = "Whether modification of annotations is allowed", example = "false")
boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canPrint")
@Parameter(description = "Whether printing of the document is allowed", example = "false")
boolean canPrint,
@RequestParam(defaultValue = "false", name = "canPrintFaithful")
@Parameter(description = "Whether faithful printing is allowed", example = "false")
boolean canPrintFaithful
) throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes());
AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent);
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
ap.setCanFillInForm(!canFillInForm);
ap.setCanModify(!canModify);
ap.setCanModifyAnnotations(!canModifyAnnotations);
ap.setCanPrint(!canPrint);
ap.setCanPrintFaithful(!canPrintFaithful);
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
spp.setEncryptionKeyLength(keyLength);
spp.setPermissions(ap);
document.protect(spp);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
}
}

View File

@@ -0,0 +1,97 @@
package stirling.software.SPDF.controller.api.security;
import java.awt.Color;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.PdfUtils;
@RestController
public class WatermarkController {
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
@Operation(summary = "Add watermark to a PDF file",
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.")
public ResponseEntity<byte[]> addWatermark(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to add a watermark")
MultipartFile pdfFile,
@RequestParam("watermarkText")
@Parameter(description = "The watermark text to add to the PDF file")
String watermarkText,
@RequestParam(defaultValue = "30", name = "fontSize")
@Parameter(description = "The font size of the watermark text", example = "30")
float fontSize,
@RequestParam(defaultValue = "0", name = "rotation")
@Parameter(description = "The rotation of the watermark text in degrees", example = "0")
float rotation,
@RequestParam(defaultValue = "0.5", name = "opacity")
@Parameter(description = "The opacity of the watermark text (0.0 - 1.0)", example = "0.5")
float opacity,
@RequestParam(defaultValue = "50", name = "widthSpacer")
@Parameter(description = "The width spacer between watermark texts", example = "50")
int widthSpacer,
@RequestParam(defaultValue = "50", name = "heightSpacer")
@Parameter(description = "The height spacer between watermark texts", example = "50")
int heightSpacer) throws IOException {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Create a page in the document
for (PDPage page : document.getPages()) {
// Get the page's content stream
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
// Set transparency
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState);
// Set font of watermark
PDFont font = PDType1Font.HELVETICA_BOLD;
contentStream.beginText();
contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
// Set size and location of watermark
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
float watermarkHeight = heightSpacer + fontSize;
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
// Add the watermark text
for (int i = 0; i < watermarkRows; i++) {
for (int j = 0; j < watermarkCols; j++) {
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
contentStream.showTextWithPositioning(new Object[] { watermarkText });
}
}
contentStream.endText();
// Close the content stream
contentStream.close();
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
}
}

View File

@@ -1,97 +0,0 @@
package stirling.software.SPDF.controller.converters;
import java.io.IOException;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
@GetMapping("/img-to-pdf")
public String convertToPdfForm(Model model) {
model.addAttribute("currentPage", "img-to-pdf");
return "convert/img-to-pdf";
}
@GetMapping("/pdf-to-img")
public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@PostMapping("/img-to-pdf")
public ResponseEntity<byte[]> convertToPdf(@RequestParam("fileInput") MultipartFile[] file,
@RequestParam(defaultValue = "false", name = "stretchToFit") boolean stretchToFit,
@RequestParam(defaultValue = "true", name = "autoRotate") boolean autoRotate) throws IOException {
// Convert the file to PDF and get the resulting bytes
System.out.println(stretchToFit);
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate);
return PdfUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_coverted.pdf");
}
@PostMapping("/pdf-to-img")
public ResponseEntity<Resource> convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat,
@RequestParam("singleOrMultiple") String singleOrMultiple, @RequestParam("colorType") String colorType, @RequestParam("dpi") String dpi) throws IOException {
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = singleOrMultiple.equals("single");
byte[] result = null;
try {
result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (singleImage) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
ResponseEntity<Resource> response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else {
ByteArrayResource resource = new ByteArrayResource(result);
// return the Resource in the response
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToImages.zip").contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength()).body(resource);
}
}
private String getMediaType(String imageFormat) {
if (imageFormat.equalsIgnoreCase("PNG"))
return "image/png";
else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG"))
return "image/jpeg";
else if (imageFormat.equalsIgnoreCase("GIF"))
return "image/gif";
else
return "application/octet-stream";
}
}

View File

@@ -1,80 +0,0 @@
package stirling.software.SPDF.controller.converters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@Controller
public class ConvertOfficeController {
@GetMapping("/file-to-pdf")
public String convertToPdfForm(Model model) {
model.addAttribute("currentPage", "file-to-pdf");
return "convert/file-to-pdf";
}
@PostMapping("/file-to-pdf")
public ResponseEntity<byte[]> processPdfWithOCR(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
//unused but can start server instance if startup time is to long
//LibreOfficeListener.getInstance().start();
byte[] pdfByteArray = convertToPdf(inputFile);
return PdfUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf");
}
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension
String originalFilename = inputFile.getOriginalFilename();
if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension");
}
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Run the LibreOffice command
List<String> command = new ArrayList<>(Arrays.asList("unoconv", "-vvv",
"-f",
"pdf",
"-o",
tempOutputFile.toString(),
tempInputFile.toString()));
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
// Read the converted PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
return pdfBytes;
}
private boolean isValidFileExtension(String fileExtension) {
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
return fileExtension.matches(extensionPattern);
}
}

View File

@@ -1,97 +0,0 @@
package stirling.software.SPDF.controller.converters;
import java.io.IOException;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import stirling.software.SPDF.utils.PDFToFile;
@Controller
public class ConvertPDFToOffice {
@GetMapping("/pdf-to-word")
public ModelAndView pdfToWord() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
modelAndView.addObject("currentPage", "pdf-to-word");
return modelAndView;
}
@GetMapping("/pdf-to-presentation")
public ModelAndView pdfToPresentation() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
modelAndView.addObject("currentPage", "pdf-to-presentation");
return modelAndView;
}
@GetMapping("/pdf-to-text")
public ModelAndView pdfToText() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
modelAndView.addObject("currentPage", "pdf-to-text");
return modelAndView;
}
@GetMapping("/pdf-to-html")
public ModelAndView pdfToHTML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
modelAndView.addObject("currentPage", "pdf-to-html");
return modelAndView;
}
@GetMapping("/pdf-to-xml")
public ModelAndView pdfToXML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
modelAndView.addObject("currentPage", "pdf-to-xml");
return modelAndView;
}
@PostMapping("/pdf-to-word")
public ResponseEntity<byte[]> processPdfToWord(@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
@PostMapping("/pdf-to-presentation")
public ResponseEntity<byte[]> processPdfToPresentation(@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
}
@PostMapping("/pdf-to-text")
public ResponseEntity<byte[]> processPdfToRTForTXT(@RequestParam("fileInput") MultipartFile inputFile,
@RequestParam("outputFormat") String outputFormat) throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
@PostMapping("/pdf-to-html")
public ResponseEntity<byte[]> processPdfToHTML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
}
@PostMapping("/pdf-to-xml")
public ResponseEntity<byte[]> processPdfToXML(@RequestParam("fileInput") MultipartFile inputFile) throws IOException, InterruptedException {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
}
}

View File

@@ -1,81 +0,0 @@
package stirling.software.SPDF.controller.security;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
@Controller
public class PasswordController {
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
@GetMapping("/add-password")
public String addPasswordForm(Model model) {
model.addAttribute("currentPage", "add-password");
return "security/add-password";
}
@GetMapping("/remove-password")
public String removePasswordForm(Model model) {
model.addAttribute("currentPage", "remove-password");
return "security/remove-password";
}
@GetMapping("/change-permissions")
public String permissionsForm(Model model) {
model.addAttribute("currentPage", "change-permissions");
return "security/change-permissions";
}
@PostMapping("/remove-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(name = "password") String password) throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_password_removed.pdf");
}
@PostMapping("/add-password")
public ResponseEntity<byte[]> compressPDF(@RequestParam("fileInput") MultipartFile fileInput, @RequestParam(defaultValue = "", name = "password") String password,
@RequestParam(defaultValue = "128", name = "keyLength") int keyLength, @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument,
@RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent,
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility,
@RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, @RequestParam(defaultValue = "false", name = "canModify") boolean canModify,
@RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations,
@RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful)
throws IOException {
PDDocument document = PDDocument.load(fileInput.getBytes());
AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent);
ap.setCanExtractForAccessibility(!canExtractForAccessibility);
ap.setCanFillInForm(!canFillInForm);
ap.setCanModify(!canModify);
ap.setCanModifyAnnotations(!canModifyAnnotations);
ap.setCanPrint(!canPrint);
ap.setCanPrintFaithful(!canPrintFaithful);
StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap);
spp.setEncryptionKeyLength(keyLength);
spp.setPermissions(ap);
document.protect(spp);
return PdfUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")+ "_passworded.pdf");
}
}

View File

@@ -1,157 +0,0 @@
package stirling.software.SPDF.controller.security;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WatermarkRemover;
@Controller
public class WatermarkController {
@GetMapping("/add-watermark")
public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "add-watermark");
return "security/add-watermark";
}
@GetMapping("/remove-watermark")
public String removeWatermarkForm(Model model) {
model.addAttribute("currentPage", "remove-watermark");
return "security/remove-watermark";
}
@PostMapping("/add-watermark")
public ResponseEntity<byte[]> addWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText,
@RequestParam(defaultValue = "30", name = "fontSize") float fontSize, @RequestParam(defaultValue = "0", name = "rotation") float rotation,
@RequestParam(defaultValue = "0.5", name = "opacity") float opacity,
@RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer)
throws IOException {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Create a page in the document
for (PDPage page : document.getPages()) {
// Get the page's content stream
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
// Set transparency
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState);
// Set font of watermark
PDFont font = PDType1Font.HELVETICA_BOLD;
contentStream.beginText();
contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
// Set size and location of watermark
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
float watermarkHeight = heightSpacer + fontSize;
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
// Add the watermark text
for (int i = 0; i < watermarkRows; i++) {
for (int j = 0; j < watermarkCols; j++) {
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
contentStream.showTextWithPositioning(new Object[] { watermarkText });
}
}
contentStream.endText();
// Close the content stream
contentStream.close();
}
return PdfUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
}
@PostMapping("/remove-watermark")
public ResponseEntity<byte[]> removeWatermark(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("watermarkText") String watermarkText) throws Exception {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Create a new PDF document for the output
PDDocument outputDocument = new PDDocument();
// Loop through the pages
int numPages = document.getNumberOfPages();
for (int i = 0; i < numPages; i++) {
PDPage page = document.getPage(i);
// Process the content stream to remove the watermark text
WatermarkRemover editor = new WatermarkRemover(watermarkText) {};
editor.processPage(page);
editor.processPage(page);
// Add the page to the output document
outputDocument.addPage(page);
}
for (PDPage page : outputDocument.getPages()) {
List<PDAnnotation> annotations = page.getAnnotations();
List<PDAnnotation> annotationsToRemove = new ArrayList<>();
for (PDAnnotation annotation : annotations) {
if (annotation instanceof PDAnnotationMarkup) {
PDAnnotationMarkup markup = (PDAnnotationMarkup) annotation;
String contents = markup.getContents();
if (contents != null && contents.contains(watermarkText)) {
annotationsToRemove.add(markup);
}
}
}
annotations.removeAll(annotationsToRemove);
}
PDDocumentCatalog catalog = outputDocument.getDocumentCatalog();
PDAcroForm acroForm = catalog.getAcroForm();
if (acroForm != null) {
List<PDField> fields = acroForm.getFields();
for (PDField field : fields) {
String fieldValue = field.getValueAsString();
if (fieldValue.contains(watermarkText)) {
field.setValue(fieldValue.replace(watermarkText, ""));
}
}
}
return PdfUtils.pdfDocToWebResponse(outputDocument, "removed.pdf");
}
}

View File

@@ -0,0 +1,86 @@
package stirling.software.SPDF.controller.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Hidden;
@Controller
public class ConverterWebController {
@GetMapping("/img-to-pdf")
@Hidden
public String convertImgToPdfForm(Model model) {
model.addAttribute("currentPage", "img-to-pdf");
return "convert/img-to-pdf";
}
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@GetMapping("/file-to-pdf")
@Hidden
public String convertToPdfForm(Model model) {
model.addAttribute("currentPage", "file-to-pdf");
return "convert/file-to-pdf";
}
//PDF TO......
@GetMapping("/pdf-to-html")
@Hidden
public ModelAndView pdfToHTML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
modelAndView.addObject("currentPage", "pdf-to-html");
return modelAndView;
}
@GetMapping("/pdf-to-presentation")
@Hidden
public ModelAndView pdfToPresentation() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
modelAndView.addObject("currentPage", "pdf-to-presentation");
return modelAndView;
}
@GetMapping("/pdf-to-text")
@Hidden
public ModelAndView pdfToText() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
modelAndView.addObject("currentPage", "pdf-to-text");
return modelAndView;
}
@GetMapping("/pdf-to-word")
@Hidden
public ModelAndView pdfToWord() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
modelAndView.addObject("currentPage", "pdf-to-word");
return modelAndView;
}
@GetMapping("/pdf-to-xml")
@Hidden
public ModelAndView pdfToXML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
modelAndView.addObject("currentPage", "pdf-to-xml");
return modelAndView;
}
@GetMapping("/pdf-to-pdfa")
@Hidden
public String pdfToPdfAForm(Model model) {
model.addAttribute("currentPage", "pdf-to-pdfa");
return "convert/pdf-to-pdfa";
}
}

View File

@@ -0,0 +1,95 @@
package stirling.software.SPDF.controller.web;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.v3.oas.annotations.Hidden;
@Controller
public class GeneralWebController {
@GetMapping("/merge-pdfs")
@Hidden
public String mergePdfForm(Model model) {
model.addAttribute("currentPage", "merge-pdfs");
return "merge-pdfs";
}
@GetMapping("/about")
@Hidden
public String gameForm(Model model) {
model.addAttribute("currentPage", "about");
return "about";
}
@GetMapping("/multi-tool")
@Hidden
public String multiToolForm(Model model) {
model.addAttribute("currentPage", "multi-tool");
return "multi-tool";
}
@GetMapping("/")
public String home(Model model) {
model.addAttribute("currentPage", "home");
return "home";
}
@GetMapping("/home")
public String root(Model model) {
return "redirect:/";
}
@GetMapping("/remove-pages")
@Hidden
public String pageDeleter(Model model) {
model.addAttribute("currentPage", "remove-pages");
return "remove-pages";
}
@GetMapping("/pdf-organizer")
@Hidden
public String pageOrganizer(Model model) {
model.addAttribute("currentPage", "pdf-organizer");
return "pdf-organizer";
}
@GetMapping("/rotate-pdf")
@Hidden
public String rotatePdfForm(Model model) {
model.addAttribute("currentPage", "rotate-pdf");
return "rotate-pdf";
}
@GetMapping("/split-pdfs")
@Hidden
public String splitPdfForm(Model model) {
model.addAttribute("currentPage", "split-pdfs");
return "split-pdfs";
}
@GetMapping("/sign")
@Hidden
public String signForm(Model model) {
model.addAttribute("currentPage", "sign");
return "sign";
}
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
@Hidden
public String getRobotsTxt() {
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISABILITY");
if (allowGoogleVisibility == null)
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISABILITY");
if (allowGoogleVisibility == null)
allowGoogleVisibility = "false";
if (Boolean.parseBoolean(allowGoogleVisibility)) {
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
} else {
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
}
}
}

View File

@@ -0,0 +1,111 @@
package stirling.software.SPDF.controller.web;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Hidden;
@Controller
public class OtherWebController {
@GetMapping("/compress-pdf")
@Hidden
public String compressPdfForm(Model model) {
model.addAttribute("currentPage", "compress-pdf");
return "other/compress-pdf";
}
@GetMapping("/extract-image-scans")
@Hidden
public ModelAndView extractImageScansForm() {
ModelAndView modelAndView = new ModelAndView("other/extract-image-scans");
modelAndView.addObject("currentPage", "extract-image-scans");
return modelAndView;
}
@GetMapping("/extract-images")
@Hidden
public String extractImagesForm(Model model) {
model.addAttribute("currentPage", "extract-images");
return "other/extract-images";
}
@GetMapping("/flatten")
@Hidden
public String flattenForm(Model model) {
model.addAttribute("currentPage", "flatten");
return "other/flatten";
}
@GetMapping("/change-metadata")
@Hidden
public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "change-metadata");
return "other/change-metadata";
}
@GetMapping("/compare")
@Hidden
public String compareForm(Model model) {
model.addAttribute("currentPage", "compare");
return "other/compare";
}
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/4.00/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();
}
return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", ""))
.filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList());
}
@GetMapping("/ocr-pdf")
@Hidden
public ModelAndView ocrPdfPage() {
ModelAndView modelAndView = new ModelAndView("other/ocr-pdf");
modelAndView.addObject("languages", getAvailableTesseractLanguages());
modelAndView.addObject("currentPage", "ocr-pdf");
return modelAndView;
}
@GetMapping("/add-image")
@Hidden
public String overlayImage(Model model) {
model.addAttribute("currentPage", "add-image");
return "other/add-image";
}
@GetMapping("/adjust-contrast")
@Hidden
public String contrast(Model model) {
model.addAttribute("currentPage", "adjust-contrast");
return "other/adjust-contrast";
}
@GetMapping("/repair")
@Hidden
public String repairForm(Model model) {
model.addAttribute("currentPage", "repair");
return "other/repair";
}
@GetMapping("/remove-blanks")
@Hidden
public String removeBlanksForm(Model model) {
model.addAttribute("currentPage", "remove-blanks");
return "other/remove-blanks";
}
}

View File

@@ -0,0 +1,37 @@
package stirling.software.SPDF.controller.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import io.swagger.v3.oas.annotations.Hidden;
@Controller
public class SecurityWebController {
@GetMapping("/add-password")
@Hidden
public String addPasswordForm(Model model) {
model.addAttribute("currentPage", "add-password");
return "security/add-password";
}
@GetMapping("/change-permissions")
@Hidden
public String permissionsForm(Model model) {
model.addAttribute("currentPage", "change-permissions");
return "security/change-permissions";
}
@GetMapping("/remove-password")
@Hidden
public String removePasswordForm(Model model) {
model.addAttribute("currentPage", "remove-password");
return "security/remove-password";
}
@GetMapping("/add-watermark")
@Hidden
public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "add-watermark");
return "security/add-watermark";
}
}

View File

@@ -28,5 +28,5 @@ public class ErrorUtils {
modelAndView.addObject("stackTrace", stackTrace);
return modelAndView;
}
}

View File

@@ -0,0 +1,130 @@
package stirling.software.SPDF.utils;
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,4 +1,5 @@
package stirling.software.SPDF.utils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -14,14 +15,13 @@ import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
public class PDFToFile {
public ResponseEntity<byte[]> processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter)
throws IOException, InterruptedException {
public ResponseEntity<byte[]> processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException {
if (!"application/pdf".equals(inputFile.getContentType())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
@@ -32,16 +32,15 @@ public class PDFToFile {
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
// Validate output format
List<String> allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "html","xml","txt:Text");
List<String> allowedFormats = Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "html", "xml", "txt:Text");
if (!allowedFormats.contains(outputFormat)) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
Path tempInputFile = null;
Path tempOutputDir = null;
byte[] fileBytes;
// Prepare response
HttpHeaders headers = new HttpHeaders();
String fileName = "temp.file";
try {
// Save the uploaded file to a temporary location
@@ -52,9 +51,8 @@ public class PDFToFile {
tempOutputDir = Files.createTempDirectory("output_");
// Run the LibreOffice command
List<String> command = new ArrayList<>(Arrays.asList(
"soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString()
));
List<String> command = new ArrayList<>(
Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString()));
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
// Get output files
@@ -63,16 +61,14 @@ public class PDFToFile {
if (outputFiles.size() == 1) {
// Return single output file
File outputFile = outputFiles.get(0);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
if(outputFormat.equals("txt:Text")) {
outputFormat="txt";
if (outputFormat.equals("txt:Text")) {
outputFormat = "txt";
}
headers.setContentDispositionFormData("attachment", pdfBaseName + "." + outputFormat);
fileName = pdfBaseName + "." + outputFormat;
fileBytes = FileUtils.readFileToByteArray(outputFile);
} else {
// Return output files in a ZIP archive
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", pdfBaseName + "To" + outputFormat + ".zip");
fileName = pdfBaseName + "To" + outputFormat + ".zip";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
@@ -96,6 +92,6 @@ public class PDFToFile {
if (tempOutputDir != null)
FileUtils.deleteDirectory(tempOutputDir.toFile());
}
return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
return PdfUtils.bytesToWebResponse(fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
}
}

View File

@@ -2,13 +2,23 @@ package stirling.software.SPDF.utils;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -16,10 +26,13 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
@@ -31,110 +44,34 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate) throws IOException {
try (PDDocument doc = new PDDocument()) {
for (MultipartFile file : files) {
// Create a temporary file for the image
File imageFile = Files.createTempFile("image", ".jpg").toFile();
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = file.getInputStream()) {
byte[] buffer = new byte[1024];
int len;
// Read from the input stream and write to the file
while ((len = input.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
logger.info("Image successfully written to file: {}", imageFile.getAbsolutePath());
} catch (IOException e) {
logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e);
throw e;
}
// Create a new PDF page
PDPage page = new PDPage();
doc.addPage(page);
// Create an image object from the image file
PDImageXObject image = PDImageXObject.createFromFileByContent(imageFile, doc);
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
if (autoRotate && ((image.getWidth() > image.getHeight() && pageHeight > pageWidth) || (image.getWidth() < image.getHeight() && pageWidth > pageHeight))) {
// Rotate the page 90 degrees if the image better fits the page in landscape orientation
page.setRotation(90);
pageWidth = page.getMediaBox().getHeight();
pageHeight = page.getMediaBox().getWidth();
}
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
if (stretchToFit) {
if (page.getRotation() == 0 || page.getRotation() == 180) {
// Stretch the image to fit the whole page
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
} else {
// Adjust the width and height of the page when rotated
contentStream.drawImage(image, 0, 0, pageHeight, pageWidth);
}
logger.info("Image successfully added to PDF, stretched to fit page");
} else {
// Ensure the image fits the page but maintain the image's aspect ratio
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
float pageAspectRatio = pageWidth / pageHeight;
// Determine the scale factor to fit the image onto the page
float scaleFactor = 1.0f;
if (imageAspectRatio > pageAspectRatio) {
// Image is wider than the page, scale to fit the width
scaleFactor = pageWidth / image.getWidth();
} else {
// Image is taller than the page, scale to fit the height
scaleFactor = pageHeight / image.getHeight();
}
// Calculate the position of the image on the page
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
// Draw the image onto the page
if (page.getRotation() == 0 || page.getRotation() == 180) {
contentStream.drawImage(image, xPos, yPos, image.getWidth() * scaleFactor, image.getHeight() * scaleFactor);
} else {
// Adjust the width and height of the page when rotated
contentStream.drawImage(image, yPos, xPos, image.getHeight() * scaleFactor, image.getWidth() * scaleFactor);
}
logger.info("Image successfully added to PDF, maintaining aspect ratio");
}
} catch (IOException e) {
logger.error("Error adding image to PDF", e);
throw e;
}
// Delete the temporary file
imageFile.delete();
}
// Create a ByteArrayOutputStream to save the PDF to
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream);
logger.info("PDF successfully saved to byte array");
return byteArrayOutputStream.toByteArray();
}
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
}
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
}
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI)
throws IOException, Exception {
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName, MediaType mediaType) throws IOException {
// Return the PDF as a response
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
headers.setContentLength(bytes.length);
String encodedDocName = URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
headers.setContentDispositionFormData("attachment", encodedDocName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
}
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI) throws IOException, Exception {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages();
@@ -144,7 +81,7 @@ public class PdfUtils {
for (int i = 0; i < pageCount; i++) {
images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
}
if (singleImage) {
// Combine all images into a single big image
BufferedImage combined = new BufferedImage(images.get(0).getWidth(), images.get(0).getHeight() * pageCount, BufferedImage.TYPE_INT_RGB);
@@ -187,51 +124,158 @@ public class PdfUtils {
throw e;
}
}
public static byte[] imageToPdf(MultipartFile[] files, boolean stretchToFit, boolean autoRotate, String colorType) throws IOException {
try (PDDocument doc = new PDDocument()) {
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
if (originalFilename != null && (originalFilename.toLowerCase().endsWith(".tiff") || originalFilename.toLowerCase().endsWith(".tif")) ) {
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
int numPages = reader.getNumImages(true);
for (int i = 0; i < numPages; i++) {
BufferedImage pageImage = reader.read(i);
BufferedImage convertedImage = convertColorType(pageImage, colorType);
PDImageXObject pdImage = LosslessFactory.createFromImage(doc, convertedImage);
addImageToDocument(doc, pdImage, stretchToFit, autoRotate);
}
} else {
File imageFile = Files.createTempFile("image", ".png").toFile();
try (FileOutputStream fos = new FileOutputStream(imageFile); InputStream input = file.getInputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
BufferedImage image = ImageIO.read(imageFile);
BufferedImage convertedImage = convertColorType(image, colorType);
PDImageXObject pdImage = LosslessFactory.createFromImage(doc, convertedImage);
addImageToDocument(doc, pdImage, stretchToFit, autoRotate);
} catch (IOException e) {
logger.error("Error writing image to file: {}", imageFile.getAbsolutePath(), e);
throw e;
} finally {
imageFile.delete();
}
}
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream);
logger.info("PDF successfully saved to byte array");
return byteArrayOutputStream.toByteArray();
}
}
private static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
BufferedImage convertedImage;
switch (colorType) {
case "greyscale":
convertedImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
case "blackwhite":
convertedImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
default: // full color
convertedImage = sourceImage;
break;
}
return convertedImage;
}
private static void addImageToDocument(PDDocument doc, PDImageXObject image, boolean stretchToFit, boolean autoRotate) throws IOException {
boolean imageIsLandscape = image.getWidth() > image.getHeight();
PDRectangle pageSize = PDRectangle.A4;
if (autoRotate && imageIsLandscape) {
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
}
PDPage page = new PDPage(pageSize);
doc.addPage(page);
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
if (stretchToFit) {
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
} else {
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
float pageAspectRatio = pageWidth / pageHeight;
float scaleFactor = 1.0f;
if (imageAspectRatio > pageAspectRatio) {
scaleFactor = pageWidth / image.getWidth();
} else {
scaleFactor = pageHeight / image.getHeight();
}
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
contentStream.drawImage(image, xPos, yPos, image.getWidth() * scaleFactor, image.getHeight() * scaleFactor);
}
} catch (IOException e) {
logger.error("Error adding image to PDF", e);
throw e;
}
}
public static X509Certificate[] loadCertificateChainFromKeystore(InputStream keystoreInputStream, String keystorePassword) throws Exception {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(keystoreInputStream, keystorePassword.toCharArray());
String alias = keystore.aliases().nextElement();
Certificate[] certChain = keystore.getCertificateChain(alias);
X509Certificate[] x509CertChain = new X509Certificate[certChain.length];
for (int i = 0; i < certChain.length; i++) {
x509CertChain[i] = (X509Certificate) certChain[i];
}
return x509CertChain;
}
public static KeyPair loadKeyPairFromKeystore(InputStream keystoreInputStream, String keystorePassword) throws Exception {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(keystoreInputStream, keystorePassword.toCharArray());
String alias = keystore.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, keystorePassword.toCharArray());
Certificate cert = keystore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, privateKey);
}
public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) throws IOException {
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes));
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes));
// Get the first page of the PDF
int pages = document.getNumberOfPages();
for (int i = 0; i < pages; i++) {
PDPage page = document.getPage(i);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
// Create an image object from the image bytes
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
// Draw the image onto the page at the specified x and y coordinates
contentStream.drawImage(image, x, y);
logger.info("Image successfully overlayed onto PDF");
if (everyPage == false && i == 0) {
break;
}
} catch (IOException e) {
// Log an error message if there is an issue overlaying the image onto the PDF
logger.error("Error overlaying image onto PDF", e);
throw e;
// Get the first page of the PDF
int pages = document.getNumberOfPages();
for (int i = 0; i < pages; i++) {
PDPage page = document.getPage(i);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) {
// Create an image object from the image bytes
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
// Draw the image onto the page at the specified x and y coordinates
contentStream.drawImage(image, x, y);
logger.info("Image successfully overlayed onto PDF");
if (!everyPage && i == 0) {
break;
}
} catch (IOException e) {
// Log an error message if there is an issue overlaying the image onto the PDF
logger.error("Error overlaying image onto PDF", e);
throw e;
}
}
// Create a ByteArrayOutputStream to save the PDF to
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
logger.info("PDF successfully saved to byte array");
return baos.toByteArray();
}
public static ResponseEntity<byte[]> iTextDocToWebResponse(Document document, String docName) throws IOException, DocumentException {
// Close the document
document.close();
// Open Byte Array and save document to it
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos).close();
return PdfUtils.boasToWebResponse(baos, docName);
}
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
@@ -243,19 +287,4 @@ public class PdfUtils {
return PdfUtils.boasToWebResponse(baos, docName);
}
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName);
}
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
// Return the PDF as a response
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentLength(bytes.length);
headers.setContentDispositionFormData("attachment", docName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
}

View File

@@ -9,15 +9,26 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
public class ProcessExecutor {
public enum Processes {
LIBRE_OFFICE,
OCR_MY_PDF
public enum Processes {
LIBRE_OFFICE, OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT
}
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
public static ProcessExecutor getInstance(Processes processType) {
return instances.computeIfAbsent(processType, key -> {
int semaphoreLimit = switch (key) {
case LIBRE_OFFICE -> 1;
case OCR_MY_PDF -> 2;
case PYTHON_OPENCV -> 8;
case GHOSTSCRIPT -> 16;
};
return new ProcessExecutor(semaphoreLimit);
});
}
private final Semaphore semaphore;
@@ -25,78 +36,67 @@ public class ProcessExecutor {
this.semaphore = new Semaphore(semaphoreLimit);
}
public static ProcessExecutor getInstance(Processes processType) {
return instances.computeIfAbsent(processType, key -> {
int semaphoreLimit = switch (key) {
case LIBRE_OFFICE -> 1;
case OCR_MY_PDF -> 2;
};
return new ProcessExecutor(semaphoreLimit);
});
public int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
int exitCode = 1;
semaphore.acquire();
try {
System.out.print("Running command: " + String.join(" ", command));
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
// Read the error stream and standard output stream concurrently
List<String> errorLines = new ArrayList<>();
List<String> outputLines = new ArrayList<>();
Thread errorReaderThread = new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = errorReader.readLine()) != null) {
errorLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread outputReaderThread = new Thread(() -> {
try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = outputReader.readLine()) != null) {
outputLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
errorReaderThread.start();
outputReaderThread.start();
// Wait for the conversion process to complete
exitCode = process.waitFor();
// Wait for the reader threads to finish
errorReaderThread.join();
outputReaderThread.join();
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
System.out.println("Command output:\n" + outputMessage);
}
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
System.out.println("Command error output:\n" + errorMessage);
if (exitCode != 0) {
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
}
}
} finally {
semaphore.release();
}
return exitCode;
}
public int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
int exitCode = 1;
semaphore.acquire();
try {
System.out.print("Running command: " + String.join(" ", command));
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
// Read the error stream and standard output stream concurrently
List<String> errorLines = new ArrayList<>();
List<String> outputLines = new ArrayList<>();
Thread errorReaderThread = new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = errorReader.readLine()) != null) {
errorLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread outputReaderThread = new Thread(() -> {
try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = outputReader.readLine()) != null) {
outputLines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
errorReaderThread.start();
outputReaderThread.start();
// Wait for the conversion process to complete
exitCode = process.waitFor();
// Wait for the reader threads to finish
errorReaderThread.join();
outputReaderThread.join();
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
System.out.println("Command output:\n" + outputMessage);
}
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
System.out.println("Command error output:\n" + errorMessage);
if (exitCode != 0) {
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
}
}
} finally {
semaphore.release();
}
return exitCode;
}
}

View File

@@ -1,69 +0,0 @@
package stirling.software.SPDF.utils;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSString;
public class WatermarkRemover extends PDFStreamEngine {
private final String watermarkText;
private final Pattern pattern;
public WatermarkRemover(String watermarkText) {
this.watermarkText = watermarkText;
this.pattern = Pattern.compile(Pattern.quote(watermarkText));
}
@Override
protected void processOperator(Operator operator, List<COSBase> operands) throws IOException {
String operation = operator.getName();
boolean processText = false;
if ("Tj".equals(operation) || "TJ".equals(operation) || "'".equals(operation) || "\"".equals(operation)) {
processText = true;
}
if (processText) {
for(int j = 0 ; j < operands.size(); ++j) {
COSBase operand = operands.get(j);
if (operand instanceof COSString) {
COSString cosString = (COSString) operand;
String string = cosString.getString();
Matcher matcher = pattern.matcher(string);
if (matcher.find()) {
string = matcher.replaceAll("");
cosString.setValue(string.getBytes());
}
} else if (operand instanceof COSArray) {
COSArray array = (COSArray) operand;
for (int i = 0; i < array.size(); i++) {
COSBase item = array.get(i);
if (item instanceof COSString) {
COSString cosString = (COSString) item;
String string = cosString.getString();
Matcher matcher = pattern.matcher(string);
if (matcher.find()) {
System.out.println("operation =" + operation);
System.out.println("1 =" + string);
string = matcher.replaceAll("");
cosString.setValue(string.getBytes());
array.set(i, cosString);
operands.set(j, array);
}
}
}
}
}
}
super.processOperator(operator, operands);
}
}

View File

@@ -17,8 +17,9 @@ server.error.include-exception=true
server.error.include-message=always
server.servlet.session.tracking-modes=cookie
server.servlet.context-path=${APP_ROOT_PATH:/}
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.encoding=UTF-8

View File

@@ -21,6 +21,9 @@ false=\u062E\u0637\u0623
unknown=\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641
save=\u062D\u0641\u0638
close=\u0625\u063A\u0644\u0627\u0642
filesSelected = الملفات المحددة
noFavourites = لم تتم إضافة أي مفضلات
bored = الانتظار بالملل؟
#############
# HOME-PAGE #
@@ -31,14 +34,18 @@ navbar.convert=تحويل
navbar.security=الأمان
navbar.other=أخرى
navbar.darkmode=الوضع الداكن
navbar.pageOps=عمليات الصفحة
home.merge.title=دمج ملفات PDF
home.multiTool.title=أداة متعددة PDF
home.multiTool.desc=دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها
home.merge.title=دمج ملفات
home.merge.desc=دمج ملفات PDF متعددة في ملف واحد بسهولة.
home.split.title=انقسام ملفات PDF
home.split.title=انقسام ملفات
home.split.desc=تقسيم ملفات PDF إلى مستندات متعددة
home.rotate.title=تدوير ملفات PDF
home.rotate.title=تدوير ملفات
home.rotate.desc=قم بتدوير ملفات PDF الخاصة بك بسهولة.
home.imageToPdf.title=صورة إلى PDF
@@ -47,7 +54,7 @@ home.imageToPdf.desc=تحويل الصور (PNG ، JPEG ، GIF) إلى PDF.
home.pdfToImage.title=تحويل PDF إلى صورة
home.pdfToImage.desc=تحويل ملف PDF إلى صورة. (PNG ، JPEG ، GIF)
home.pdfOrganiser.title=منظم PDF
home.pdfOrganiser.title=منظم
home.pdfOrganiser.desc=إزالة / إعادة ترتيب الصفحات بأي ترتيب
home.addImage.title=إضافة صورة إلى ملف PDF
@@ -71,7 +78,7 @@ home.addPassword.desc=تشفير مستند PDF الخاص بك بكلمة مر
home.removePassword.title=إزالة كلمة المرور
home.removePassword.desc=إزالة الحماية بكلمة مرور من مستند PDF الخاص بك.
home.compressPdfs.title=ضغط ملفات PDF
home.compressPdfs.title=ضغط ملفات
home.compressPdfs.desc=ضغط ملفات PDF لتقليل حجم الملف.
home.changeMetadata.title=\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629
@@ -87,24 +94,90 @@ home.ocr.desc=\u064A\u0642\u0648\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u06
home.extractImages.title=\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631
home.extractImages.desc=\u064A\u0633\u062A\u062E\u0631\u062C \u062C\u0645\u064A\u0639 \u0627\u0644\u0635\u0648\u0631 \u0645\u0646 \u0645\u0644\u0641 PDF \u0648\u064A\u062D\u0641\u0638\u0647\u0627 \u0641\u064A \u0627\u0644\u0631\u0645\u0632 \u0627\u0644\u0628\u0631\u064A\u062F\u064A
home.pdfToPDFA.title = \u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A
home.pdfToPDFA.desc = \u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649
home.pdfToPDFA.title=\u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A
home.pdfToPDFA.desc=\u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649
home.PDFToWord.title = تحويل PDF إلى Word
home.PDFToWord.desc = تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT)
home.PDFToWord.title=تحويل PDF إلى Word
home.PDFToWord.desc=تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT)
home.PDFToPresentation.title = PDF للعرض التقديمي
home.PDFToPresentation.desc = تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP)
home.PDFToPresentation.title=PDF للعرض التقديمي
home.PDFToPresentation.desc=تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP)
home.PDFToText.title = تحويل PDF إلى نص / RTF
home.PDFToText.desc = تحويل PDF إلى تنسيق نص أو RTF
home.PDFToText.title=تحويل PDF إلى نص / RTF
home.PDFToText.desc=تحويل PDF إلى تنسيق نص أو RTF
home.PDFToHTML.title = تحويل PDF إلى HTML
home.PDFToHTML.desc = تحويل PDF إلى تنسيق HTML
home.PDFToHTML.title=تحويل PDF إلى HTML
home.PDFToHTML.desc=تحويل PDF إلى تنسيق HTML
home.PDFToXML.title=تحويل PDF إلى XML
home.PDFToXML.desc=تحويل PDF إلى تنسيق XML
home.ScannerImageSplit.title=كشف / انقسام الصور الممسوحة ضوئيًا
home.ScannerImageSplit.desc=تقسيم عدة صور من داخل صورة / ملف PDF
home.sign.title = تسجيل الدخول
home.sign.desc = إضافة التوقيع إلى PDF عن طريق الرسم أو النص أو الصورة
home.flatten.title = تسطيح
home.flatten.desc = قم بإزالة كافة العناصر والنماذج التفاعلية من ملف PDF
home.repair.title = إصلاح
home.repair.desc = يحاول إصلاح ملف PDF تالف / معطل
home.removeBlanks.title = إزالة الصفحات الفارغة
home.removeBlanks.desc = يكتشف ويزيل الصفحات الفارغة من المستند
home.compare.title = قارن
home.compare.desc = يقارن ويظهر الاختلافات بين 2 من مستندات PDF
downloadPdf = تنزيل PDF
text=نص
font=الخط
removeBlanks.title = إزالة الفراغات
removeBlanks.header = إزالة الصفحات الفارغة
removeBlanks.threshold = العتبة:
removeBlanks.thresholdDesc = الحد الفاصل لتحديد مدى بياض البكسل الأبيض
removeBlanks.whitePercent = نسبة الأبيض (٪):
removeBlanks.whitePercentDesc = النسبة المئوية للصفحة التي يجب أن تكون بيضاء لتتم إزالتها
removeBlanks.submit = إزالة الفراغات
compare.title=يقارن
compare.header=قارن ملفات PDF
compare.document.1=المستند 1
compare.document.2=المستند 2
compare.submit=يقارن
sign.title = تسجيل الدخول
sign.header = توقيع ملفات PDF
sign.upload = تحميل الصورة
sign.draw = رسم التوقيع
Sign.text = إدخال النص
sign.clear=واضح
sign.add = إضافة
repair.title = إصلاح
repair.header = إصلاح ملفات PDF
repair.submit = الإصلاح
flatten.title = تسطيح
flatten.header = تسوية ملفات PDF
flatten.submit = تسطيح
ScannerImageSplit.selectText.1=عتبة الزاوية:
ScannerImageSplit.selectText.2=تعيين الحد الأدنى للزاوية المطلقة المطلوبة لتدوير الصورة (افتراضي: 10).
ScannerImageSplit.selectText.3=التسامح:
ScannerImageSplit.selectText.4=يحدد نطاق تباين اللون حول لون الخلفية المقدر (الافتراضي: 30).
ScannerImageSplit.selectText.5=أدنى مساحة:
ScannerImageSplit.selectText.6=تعيين الحد الأدنى لمنطقة الصورة (الافتراضي: 10000).
ScannerImageSplit.selectText.7=الحد الأدنى لمنطقة المحيط:
ScannerImageSplit.selectText.8=تعيين الحد الأدنى لمنطقة المحيط للصورة
ScannerImageSplit.selectText.9=حجم الحدود:
ScannerImageSplit.selectText.10=يضبط حجم الحدود المضافة والمزالة لمنع الحدود البيضاء في الإخراج (الافتراضي: 1).
home.PDFToXML.title = تحويل PDF إلى XML
home.PDFToXML.desc = تحويل PDF إلى تنسيق XML
navbar.settings=\u0625\u0639\u062F\u0627\u062F\u0627\u062A
settings.title=\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A
@@ -129,6 +202,8 @@ ocr.selectText.7=\u0641\u0631\u0636 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\
ocr.selectText.8=\u0639\u0627\u062F\u064A (\u062E\u0637\u0623 \u0625\u0630\u0627 \u0643\u0627\u0646 PDF \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635)
ocr.selectText.9=\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629
ocr.selectText.10=\u0648\u0636\u0639 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641
ocr.selectText.11 = إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور ، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل)
ocr.selectText.12 = نوع العرض (متقدم)
ocr.help=\u064A\u0631\u062C\u0649 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0648\u062B\u0627\u0626\u0642 \u062D\u0648\u0644 \u0643\u064A\u0641\u064A\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0647\u0630\u0627 \u0644\u0644\u063A\u0627\u062A \u0623\u062E\u0631\u0649 \u0648 / \u0623\u0648 \u0627\u0644\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0644\u064A\u0633 \u0641\u064A \u0639\u0627\u0645\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0621
ocr.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0648 Tesseract \u0644 OCR.
ocr.submit=\u0645\u0639\u0627\u0644\u062C\u0629 PDF \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 OCR
@@ -149,7 +224,7 @@ fileToPDF.submit=\u062A\u062D\u0648\u064A\u0644 \u0625\u0644\u0649 PDF
#Add image
addImage.title=إضافة صورة
addImage.header=إضافة صورة إلى PDF (العمل قيد التقدم)
addImage.header=إضافة صورة إلى PDF
addImage.everyPage=كل صفحة؟
addImage.submit=إضافة صورة
@@ -177,6 +252,9 @@ pdfOrganiser.title=منظم الصفحة
pdfOrganiser.header=منظم صفحات PDF
pdfOrganiser.submit=إعادة ترتيب الصفحات
#multiTool
multiTool.title=أداة متعددة PDF
multiTool.header=أداة متعددة PDF
#pageRemover
pageRemover.title=مزيل الصفحة
@@ -322,32 +400,32 @@ pdfToPDFA.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\
pdfToPDFA.submit=\u062A\u062D\u0648\u064A\u0644
PDFToWord.title = تحويل PDF إلى Word
PDFToWord.header = تحويل PDF إلى Word
PDFToWord.selectText.1 = تنسيق ملف الإخراج
PDFToWord.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToWord.submit = تحويل
PDFToWord.title=تحويل PDF إلى Word
PDFToWord.header=تحويل PDF إلى Word
PDFToWord.selectText.1=تنسيق ملف الإخراج
PDFToWord.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToWord.submit=تحويل
PDFToPresentation.title = PDF للعرض التقديمي
PDFToPresentation.header = PDF للعرض التقديمي
PDFToPresentation.selectText.1 = تنسيق ملف الإخراج
PDFToPresentation.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملف.
PDFToPresentation.submit = تحويل
PDFToPresentation.title=PDF للعرض التقديمي
PDFToPresentation.header=PDF للعرض التقديمي
PDFToPresentation.selectText.1=تنسيق ملف الإخراج
PDFToPresentation.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملف.
PDFToPresentation.submit=تحويل
PDFToText.title = تحويل PDF إلى نص / RTF
PDFToText.header = تحويل PDF إلى نص / RTF
PDFToText.selectText.1 = تنسيق ملف الإخراج
PDFToText.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToText.submit = تحويل
PDFToText.title=تحويل PDF إلى نص / RTF
PDFToText.header=تحويل PDF إلى نص / RTF
PDFToText.selectText.1=تنسيق ملف الإخراج
PDFToText.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToText.submit=تحويل
PDFToHTML.title = PDF إلى HTML
PDFToHTML.header = PDF إلى HTML
PDFToHTML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToHTML.submit = تحويل
PDFToHTML.title=PDF إلى HTML
PDFToHTML.header=PDF إلى HTML
PDFToHTML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToHTML.submit=تحويل
PDFToXML.title = تحويل PDF إلى XML
PDFToXML.header = تحويل PDF إلى XML
PDFToXML.credit = تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToXML.submit = تحويل
PDFToXML.title=تحويل PDF إلى XML
PDFToXML.header=تحويل PDF إلى XML
PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToXML.submit=تحويل

View File

@@ -0,0 +1,444 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Selecciona PDF(s)
multiPdfPrompt=Selecciona PDFs (2+)
multiPdfDropPrompt=Selecciona (o arrossega) els documents PDF
imgPrompt=Selecciona Imatge(s)
genericSubmit=Envia
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) :
goToPage=Anar
true=Verdader
false=Fals
unknown=Desconegut
save=Desa
close=Tanca
filesSelected=fitxers seleccionats
noFavourites=No s'ha afegit cap favorit
bored=Avorrit esperant?
#############
# HOME-PAGE #
#############
home.desc=L'eina allotjada localment per a necessitats PDF.
navbar.convert=Converteix
navbar.security=Seguretat
navbar.other=Altres
navbar.darkmode=Mode Fost
navbar.pageOps=Operacions de Pàgina
home.multiTool.title=PDF Multi Tool
home.multiTool.desc=Fusiona, Rota, Reorganitza, i Esborra pàgines
home.merge.title=Fusiona
home.merge.desc=Fusiona fàcilment pàgines en una.
home.split.title=Divideix
home.split.desc=Divideix PDFs en múltiples documents
home.rotate.title=Rota
home.rotate.desc=Rota els PDFs.
home.imageToPdf.title=Imatge a PDF
home.imageToPdf.desc=Converteix imatge (PNG, JPEG, GIF) a PDF.
home.pdfToImage.title=PDF a Imatge
home.pdfToImage.desc=Converteix PDF a imatge. (PNG, JPEG, GIF)
home.pdfOrganiser.title=Organitza
home.pdfOrganiser.desc=Elimina/Reorganitza pàgines en qualsevol ordre
home.addImage.title=Afegir imatge a PDF
home.addImage.desc=Afegeix imatge en un PDF (En progrés)
home.watermark.title=Afegir Marca d'aigua
home.watermark.desc=Afegir Marca d'aigua personalitzada en un PDF
home.remove-watermark.title=Treure Marca d'Aigua
home.remove-watermark.desc=Treu Marca d'Aigua d'un PDF
home.permissions.title=Canvia permissos
home.permissions.desc=Canvia permisos del document PDF
home.removePages.title=Elimina
home.removePages.desc=Elimina pàgines del document PDF.
home.addPassword.title=Afegir Password
home.addPassword.desc=Xifra document PDF amb password.
home.removePassword.title=Elimina Password
home.removePassword.desc=Elimia Password de document PDF.
home.compressPdfs.title=Comprimeix
home.compressPdfs.desc=Comprimeix PDFs per reduir la mida.
home.changeMetadata.title=Canvia Metadades
home.changeMetadata.desc=Canvia/Treu/Afegeix matadades al document PDF.
home.fileToPDF.title=Converteix arxiu a PDF
home.fileToPDF.desc=Converteix qualsevol arxiu a PDF (DOCX, PNG, XLS, PPT, TXT i més)
home.ocr.title=Executa exploracions OCR i/o neteja escanejos
home.ocr.desc=Neteja escanejats i detecta text d'imatges dins d'un PDF i el torna a afegir com a text.
home.extractImages.title=Extreu Imatges
home.extractImages.desc=Extreu les Imatges del PDF i les desa a zip
home.pdfToPDFA.title=PDF a PDF/A
home.pdfToPDFA.desc=Converteix PDF a PDF/A per desar a llarg termini.
home.PDFToWord.title=PDF a Word
home.PDFToWord.desc=Converteix PDF a formats de Word (DOC, DOCX and ODT)
home.PDFToPresentation.title=PDF a Presentació
home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP)
home.PDFToText.title=PDF a Text/RTF
home.PDFToText.desc=Converteix PDF a Text o format RTF
home.PDFToHTML.title=PDF a HTML
home.PDFToHTML.desc=Converteix PDF a format HTML
home.PDFToXML.title=PDF a XML
home.PDFToXML.desc=Converteix PDF a format XML
home.ScannerImageSplit.title=Detecta/Divideix fotos escanejades
home.ScannerImageSplit.desc=Divideix múltiples fotos dins del PDF/foto
home.sign.title=Sign
home.sign.desc=Afegeix signatura al PDF mitjançant dibuix, text o imatge
home.flatten.title=Aplanar
home.flatten.desc=Elimineu tots els elements i formularis interactius d'un PDF
home.repair.title=Reparar
home.repair.desc=Intenta reparar un PDF danyat o trencat
home.removeBlanks.title=Elimina les pàgines en blanc
home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document
home.compare.title=Compara
home.compare.desc=Compara i mostra les diferències entre 2 documents PDF
downloadPdf=Descarregueu PDF
text=Text
font=Tipus de lletra
removeBlanks.title=Elimina els espais en blanc
removeBlanks.header=Elimina les pàgines en blanc
removeBlanks.threshold=Llindar:
removeBlanks.thresholdDesc=Llindar per determinar el blanc que ha de ser un píxel blanc
removeBlanks.whitePercent=Percentatge blanc (%):
removeBlanks.whitePercentDesc=Percentatge de pàgina que ha de ser blanca per eliminar-la
removeBlanks.submit=Elimina els espais en blanc
compare.title=Comparar
compare.header=Compara PDF
compare.document.1=Document 1
compare.document.2=Document 2
compare.submit=Comparar
sign.title=Sign
sign.header=Firma els PDF
sign.upload=Penja la imatge
sign.draw=Dibuixa la signatura
sign.text=Entrada de text
sign.clear=Esborrar
sign.add=Afegeix
repair.title=Reparar
repair.header=Repara els PDF
repair.submit=Reparar
flatten.title=Aplanar
flatten.header=Aplana els PDF
flatten.submit=Aplanar
ScannerImageSplit.selectText.1=Llindar d'angle:
ScannerImageSplit.selectText.2=Estableix l'angle absolut mínim necessari perquè la imatge es giri (per defecte: 10).
ScannerImageSplit.selectText.3=Tolerància:
ScannerImageSplit.selectText.4=Determina l'interval de variació de color al voltant del color de fons estimat (per defecte: 30).
ScannerImageSplit.selectText.5=Àrea Mínima:
ScannerImageSplit.selectText.6=Estableix el llindar d'àrea mínima per a una foto (per defecte: 10000).
ScannerImageSplit.selectText.7=Àrea de contorn mínima:
ScannerImageSplit.selectText.8=Estableix el llindar mínim de l'àrea de contorn per a una foto
ScannerImageSplit.selectText.9=Mida Vora:
ScannerImageSplit.selectText.10=Estableix la mida de la vora afegida i eliminada per evitar vores blanques a la sortida (per defecte: 1).
navbar.settings=Opcions
settings.title=Opcions
settings.update=Actualització Disponible
settings.appVersion=Versió App:
settings.downloadOption.title=Trieu l'opció de descàrrega (per a descàrregues d'un sol fitxer no zip):
settings.downloadOption.1=Obre mateixa finestra
settings.downloadOption.2=Obre mateixa finestra
settings.downloadOption.3=Descarrega Arxiu
settings.zipThreshold=Comprimiu els fitxers quan el nombre de fitxers baixats superi
#OCR
ocr.title=OCR / Neteja escaneig
ocr.header=Neteja Escanejos / OCR (Reconeixement òptic de caràcters)
ocr.selectText.1=Selecciona els idiomes que s'han de detectar dins del PDF (els que s'indiquen són els detectats):
ocr.selectText.2=Produeix un fitxer de text que contingui text OCR juntament amb el PDF editat per OCR
ocr.selectText.3=Corregeix pàgines escanejades amb un angle esbiaixat girant-les de nou al seu lloc
ocr.selectText.4=Neteja la pàgina, de manera que és menys probable que l'OCR trobi soroll de text de fons. (Sense canvis de sortida)
ocr.selectText.5=Neteja la pàgina, de manera que és menys probable que l'OCR trobi text al soroll de fons, mantenint la neteja a la sortida.
ocr.selectText.6=Ignora les pàgines que tenen text interactiu, només les pàgines OCR que són imatges
ocr.selectText.7=Força OCR, l'OCR de cada pàgina elimina tots els elements de text originals
ocr.selectText.8=Normal (error si el PDF conté text)
ocr.selectText.9=Opcions Addicionals
ocr.selectText.10=Mode OCR
ocr.selectText.11=Elimia Imatges després de l'OCR (Alimina TOTES les imatges, útil si és part d'un procés de conversió)
ocr.selectText.12=Tipus de Renderització (Avançat)
ocr.help=Llegiu aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker
ocr.credit=Aquest servei empra OCRmyPDF i Tesseract per OCR.
ocr.submit=Processa PDF amb OCR
extractImages.title=Extreu Imatges
extractImages.header=Extreu Imatges
extractImages.selectText=Selecciona el format d'imatge al qual convertir les imatges extretes
extractImages.submit=Extreu
#File to PDF
fileToPDF.title=Arxiu a PDF
fileToPDF.header=Converteix arxiu a PDF
fileToPDF.credit=Utilitza LibreOffice i Unoconv per a la conversió.
fileToPDF.supportedFileTypes=Els tipus de fitxers admesos haurien d'incloure el següent, però per obtenir una llista completa actualitzada dels formats compatibles, consulteu la documentació de LibreOffice
fileToPDF.submit=Converteix a PDF
#compress
compress.title=Comprimeix
compress.header=Comprimeix PDF
compress.credit=Utilitza OCRmyPDF per Compressió/Optimització de PDF.
compress.selectText.1=Nivell Optimització:
compress.selectText.2=0 (Sense Optimització)
compress.selectText.3=1 (Defecte, sense pèrdua Optimització)
compress.selectText.4=2 (Pèrdua Optimització)
compress.selectText.5=3 (Pèrdua Optimització, més agressiu)
compress.selectText.6=Activa la visualització web ràpida (linealitza PDF)
compress.selectText.7=Activa pèrdua codificació JBIG2
compress.submit=Comprimeix
#Add image
addImage.title=Afegir Imatge
addImage.header=Afegir Imatge a PDF (en construcció)
addImage.everyPage=Totes les pàgines?
addImage.submit=Afegir Imatge
#merge
merge.title=Fusiona
merge.header=Fusiona múltiples PDFs (2+)
merge.submit=Fusiona
#pdfOrganiser
pdfOrganiser.title=Organitzador de pàgines
pdfOrganiser.header=Organitzador de pàgines PDF
pdfOrganiser.submit=Reorganitza Pàgines
#multiTool
multiTool.title=PDF Multi Tool
multiTool.header=PDF Multi Tool
#pageRemover
pageRemover.title=Eliminació Pàgines
pageRemover.header=Eliminació Pàgines PDF
pageRemover.pagesToDelete=Pàgines a esborrar (Números de pàgina) :
pageRemover.submit=Esborra Pàgines
#rotate
rotate.title=Rota PDF
rotate.header=Rota PDF
rotate.selectAngle=Selecciona l'angle de gir (en múltiples de 90 graus):
rotate.submit=Rota
#merge
split.title=Divideix PDF
split.header=Divideix PDF
split.desc.1=Els números seleccionats són el número de pàgina en què voleu fer la divisió
split.desc.2=Per tant, seleccionant 1,3,7-8 dividiria un document de 10 pàgines en 6 PDFS separats amb:
split.desc.3=Document #1: Pàgina 1
split.desc.4=Document #2: Pàgina 2 i 3
split.desc.5=Document #3: Pàgina 4, 5 i 6
split.desc.6=Document #4: Pàgina 7
split.desc.7=Document #5: Pàgina 8
split.desc.8=Document #6: Pàgina 9 i 10
split.splitPages=Introdueix pàgines per dividir-les:
split.submit=Divideix
#merge
imageToPDF.title=Imatge a PDF
imageToPDF.header=Imatge a PDF
imageToPDF.submit=Converteix
imageToPDF.selectText.1=Estirar per adaptar
imageToPDF.selectText.2=Auto rota PDF
imageToPDF.selectText.3=Lògica de diversos fitxers (només està activada si es treballa amb diverses imatges)
imageToPDF.selectText.4=Combina en un únic PDF
imageToPDF.selectText.5=Converteix per separar PDFs
#pdfToImage
pdfToImage.title=PDF a Imatge
pdfToImage.header=PDF a Imatge
pdfToImage.selectText=Format Imatge
pdfToImage.singleOrMultiple=Tipus Imatge Resultant
pdfToImage.single=Única Imatge Gran
pdfToImage.multi=Múltiples Imatges
pdfToImage.colorType=Tipus Color
pdfToImage.color=Color
pdfToImage.grey=Escala de Grisos
pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!)
pdfToImage.submit=Converteix
#addPassword
addPassword.title=Afegir Password
addPassword.header=Afegir password (Encriptat)
addPassword.selectText.1=PDF a encriptar
addPassword.selectText.2=Password
addPassword.selectText.3=Longitud clau de xifratge
addPassword.selectText.4=Valors més alts són més forts, però els valors més baixos tenen una millor compatibilitat.
addPassword.selectText.5=Permissos a Establir
addPassword.selectText.6=Evita muntatge del document
addPassword.selectText.7=Evita extracció de contingut
addPassword.selectText.8=Evita extracció per accessibilitat
addPassword.selectText.9=Evita emplenar formularis
addPassword.selectText.10=Evita modificacions
addPassword.selectText.11=Evita modificacions d'annotacions
addPassword.selectText.12=Evita impressió
addPassword.selectText.13=Evita impressió de diferents formats
addPassword.submit=Encripta
#watermark
watermark.title=Afegir Marca d'Aigua
watermark.header=Afegir Marca d'Aigua
watermark.selectText.1=Seleciona PDF per afegir Marca d'Aigua:
watermark.selectText.2=Text de la Marca d'Aigua
watermark.selectText.3=Mida de la Font:
watermark.selectText.4=Rotació (0-360):
watermark.selectText.5=separació d'amplada (Espai horitzontal entre cada Marca d'Aigua):
watermark.selectText.6=separació d'alçada (Espai vertical entre cada Marca d'Aigua):
watermark.selectText.7=Opacitat (0% - 100%):
watermark.submit=Afegir Marca d'Aigua
#remove-watermark
remove-watermark.title=Elimina Marca d'Aigua
remove-watermark.header=Elimina Marca d'Aigua
remove-watermark.selectText.1=Seleciona PDF per eliminar Marca d'Aigua:
remove-watermark.selectText.2=Text de la Marca d'Aigua:
remove-watermark.submit=Elimina Marca d'Aigua
#Change permissions
permissions.title=Canviar Permissos
permissions.header=Canviar Permissos
permissions.warning=Advertència perquè aquests permisos siguin inalterables, es recomana establir-los amb una contrasenya a través de la pàgina d'afegir contrasenya
permissions.selectText.1=Selecciona PDF per Canviar Permissos
permissions.selectText.2=Permissos a canviar
permissions.selectText.3=Evita muntatge del document
permissions.selectText.4=Evita extracció de contingut
permissions.selectText.5=evita extracció de contingut per accessibilitat
permissions.selectText.6=Evita emplenar formularis
permissions.selectText.7=Evita modificacions
permissions.selectText.8=Evita modificacions d'annotacions
permissions.selectText.9=Evita impressió
permissions.selectText.10=Evita impressió de diferents formats
permissions.submit=Canviar Permissos
#remove password
removePassword.title=Treure Password
removePassword.header=Treure Password (Decriptar)
removePassword.selectText.1=Selecciona PDF a Decriptar
removePassword.selectText.2=Password
removePassword.submit=Treu Password
changeMetadata.title=Canvia Metadades
changeMetadata.header=Canvia Metadades
changeMetadata.selectText.1=Edit les variables a canviar
changeMetadata.selectText.2=Neteja totes les matadades
changeMetadata.selectText.3=Mostra Metadades Personalitzades:
changeMetadata.author=Autor:
changeMetadata.creationDate=Data Creació (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creador:
changeMetadata.keywords=Keywords:
changeMetadata.modDate=Data Modificació (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Productor:
changeMetadata.subject=Assumpte:
changeMetadata.title=Títol:
changeMetadata.trapped=Atrapat:
changeMetadata.selectText.4=Altres Metadades:
changeMetadata.selectText.5=Afegir entrada personalizada
changeMetadata.submit=Canvia
xlsToPdf.title=Excel a PDF
xlsToPdf.header=Excel a PDF
xlsToPdf.selectText.1=Selecciona arxiu XLS o XLSX a convertir
xlsToPdf.convert=Converteix
pdfToPDFA.title=PDF a PDF/A
pdfToPDFA.header=PDF a PDF/A
pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
pdfToPDFA.submit=Converteix
PDFToWord.title=PDF a Word
PDFToWord.header=PDF a Word
PDFToWord.selectText.1=Format d'Arxiu de Sortida
PDFToWord.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToWord.submit=Converteix
PDFToPresentation.title=PDF a Presentació
PDFToPresentation.header=PDF a Presentació
PDFToPresentation.selectText.1=Format d'Arxiu de Sortida
PDFToPresentation.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToPresentation.submit=Converteix
PDFToText.title=PDF a Text/RTF
PDFToText.header=PDF a Text/RTF
PDFToText.selectText.1=Format d'Arxiu de Sortida
PDFToText.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToText.submit=Converteix
PDFToHTML.title=PDF a HTML
PDFToHTML.header=PDF a HTML
PDFToHTML.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToHTML.submit=Converteix
PDFToXML.title=PDF a XML
PDFToXML.header=PDF a XML
PDFToXML.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToXML.submit=Converteix

View File

@@ -17,7 +17,9 @@ false=Falsch
unknown=Unbekannt
save=Speichern
close=Schließen
filesSelected=Dateien ausgewählt
noFavourites=Keine Favoriten hinzugefügt
bored=Gelangweiltes Warten?
#############
# HOME-PAGE #
#############
@@ -27,14 +29,18 @@ navbar.convert=Konvertieren
navbar.security=Sicherheit
navbar.other=Anderes
navbar.darkmode=Dark Mode
navbar.pageOps=Seitenoperationen
home.merge.title=PDFs zusammenführen
home.multiTool.title=PDF-Multitool
home.multiTool.desc=Seiten zusammenführen, drehen, neu anordnen und entfernen
home.merge.title=Zusammenführen
home.merge.desc=Mehrere PDF-Dateien zu einer einzigen zusammenführen.
home.split.title=PDFs aufteilen
home.split.title=Aufteilen
home.split.desc=PDFs in mehrere Dokumente aufteilen.
home.rotate.title=PDFs drehen
home.rotate.title=Drehen
home.rotate.desc=Drehen Sie Ihre PDFs ganz einfach.
home.imageToPdf.title=Bild zu PDF
@@ -43,7 +49,7 @@ home.imageToPdf.desc=Konvertieren Sie ein Bild (PNG, JPEG, GIF) in ein PDF.
home.pdfToImage.title=PDF zu Bild
home.pdfToImage.desc=Konvertieren Sie ein PDF in ein Bild (PNG, JPEG, GIF).
home.pdfOrganiser.title=PDF organisieren
home.pdfOrganiser.title=Organisieren
home.pdfOrganiser.desc=Seiten entfernen und Seitenreihenfolge ändern.
home.addImage.title=Bild einfügen
@@ -58,7 +64,7 @@ home.remove-watermark.desc=Wasserzeichen aus Ihrem PDF-Dokument entfernen.
home.permissions.title=Berechtigungen ändern
home.permissions.desc=Die Berechtigungen für Ihr PDF-Dokument verändern.
home.removePages.title=Seiten entfernen
home.removePages.title=Entfernen
home.removePages.desc=Ungewollte Seiten aus dem PDF entfernen.
home.addPassword.title=Passwort hinzufügen
@@ -67,7 +73,7 @@ home.addPassword.desc=Das PDF mit einem Passwort verschlüsseln.
home.removePassword.title=Passwort entfernen
home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
home.compressPdfs.title=PDF komprimieren
home.compressPdfs.title=Komprimieren
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
home.changeMetadata.title=Metadaten ändern
@@ -100,6 +106,69 @@ home.PDFToHTML.desc=PDF in HTML-Format konvertieren
home.PDFToXML.title=PDF in XML
home.PDFToXML.desc=PDF in XML-Format konvertieren
home.ScannerImageSplit.title=Gescannte Fotos erkennen/aufteilen
home.ScannerImageSplit.desc=Teilt mehrere Fotos innerhalb eines Fotos/PDF
home.sign.title=Signieren
home.sign.desc=Fügt PDF-Signaturen durch Zeichnung, Text oder Bild hinzu
home.flatten.title=Abflachen
home.flatten.desc=Alle interaktiven Elemente und Formulare aus einem PDF entfernen
home.repair.title=Reparatur
home.repair.desc=Versucht, ein beschädigtes/kaputtes PDF zu reparieren
home.removeBlanks.title=Leere Seiten entfernen
home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument
home.compare.title=Vergleichen
home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an
downloadPdf=PDF herunterladen
text=Text
font=Schriftart
removeBlanks.title=Leerzeichen entfernen
removeBlanks.header=Leere Seiten entfernen
removeBlanks.threshold=Schwellenwert:
removeBlanks.thresholdDesc=Schwellenwert zur Bestimmung, wie weiß ein weißer Pixel sein muss
removeBlanks.whitePercent=Weißprozentsatz (%):
removeBlanks.whitePercentDesc=Prozentsatz der Seite, die weiß sein muss, um entfernt zu werden
removeBlanks.submit=Leerzeichen entfernen
compare.title=Vergleichen
compare.header=PDFs vergleichen
compare.document.1=Dokument 1
compare.document.2=Dokument 2
compare.submit=Vergleichen
sign.title=Signieren
sign.header=PDFs signieren
sign.upload=Bild hochladen
sign.draw=Signatur zeichnen
sign.text=Texteingabe
sign.clear=Klar
sign.add=Hinzufügen
repair.title=Reparieren
Repair.header=PDFs reparieren
repair.submit=Reparieren
flatten.title=Abflachen
flatten.header=PDFs reduzieren
flatten.submit=Abflachen
ScannerImageSplit.selectText.1=Winkelschwelle:
ScannerImageSplit.selectText.2=Legt den minimalen absoluten Winkel fest, der erforderlich ist, damit das Bild gedreht werden kann (Standard: 10).
ScannerImageSplit.selectText.3=Toleranz:
ScannerImageSplit.selectText.4=Bestimmt den Bereich der Farbvariation um die geschätzte Hintergrundfarbe herum (Standard: 30).
ScannerImageSplit.selectText.5=Mindestbereich:
ScannerImageSplit.selectText.6=Legt den minimalen Bereichsschwellenwert für ein Foto fest (Standard: 10000).
ScannerImageSplit.selectText.7=Minimaler Konturbereich:
ScannerImageSplit.selectText.8=Legt den minimalen Konturbereichsschwellenwert für ein Foto fest
ScannerImageSplit.selectText.9=Randgröße:
ScannerImageSplit.selectText.10=Legt die Größe des hinzugefügten und entfernten Randes fest, um weiße Ränder in der Ausgabe zu verhindern (Standard: 1).
navbar.settings=Einstellungen
settings.title=Einstellungen
@@ -124,6 +193,8 @@ ocr.selectText.7=OCR erzwingen, OCR wird jede Seite entfernen und alle ursprüng
ocr.selectText.8=Normal (Fehler, wenn PDF Text enthält)
ocr.selectText.9=Zusätzliche Einstellungen
ocr.selectText.10=OCR-Modus
ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts)
ocr.selectText.12=Rendertyp (Erweitert)
ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können
ocr.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR.
ocr.submit=PDF mit OCR verarbeiten
@@ -148,7 +219,7 @@ fileToPDF.submit=In PDF konvertieren
#Add image
addImage.title=Bild hinzufügen
addImage.header=Ein Bild einfügen (Work in progress)
addImage.header=Ein Bild einfügen
addImage.everyPage=Jede Seite?
addImage.submit=Bild hinzufügen
@@ -176,6 +247,9 @@ pdfOrganiser.title=Seiten anordnen
pdfOrganiser.header=PDF Seitenorganisation
pdfOrganiser.submit=Seiten anordnen
#Mehrfachwerkzeug
multiTool.title=PDF-Multitool
multiTool.header=PDF-Multitool
#pageRemover
pageRemover.title=Seiten entfernen

View File

@@ -17,24 +17,31 @@ false=False
unknown=Unknown
save=Save
close=Close
filesSelected=files selected
noFavourites=No favourites added
bored=Bored Waiting?
#############
# HOME-PAGE #
#############
home.desc=Your locally hosted one-stop-shop for all your PDF needs.
navbar.convert=Convert
navbar.security=Security
navbar.other=Other
navbar.darkmode=Dark Mode
navbar.pageOps=Page Operations
home.merge.title=Merge PDFs
home.multiTool.title=PDF Multi Tool
home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages
home.merge.title=Merge
home.merge.desc=Easily merge multiple PDFs into one.
home.split.title=Split PDFs
home.split.title=Split
home.split.desc=Split PDFs into multiple documents
home.rotate.title=Rotate PDFs
home.rotate.title=Rotate
home.rotate.desc=Easily rotate your PDFs.
home.imageToPdf.title=Image to PDF
@@ -43,10 +50,10 @@ home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
home.pdfToImage.title=PDF to Image
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
home.pdfOrganiser.title=PDF Organiser
home.pdfOrganiser.title=Organise
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
home.addImage.title=Add image onto PDF
home.addImage.title=Add image
home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress)
home.watermark.title=Add Watermark
@@ -58,7 +65,7 @@ home.remove-watermark.desc=Remove watermarks from your PDF document.
home.permissions.title=Change Permissions
home.permissions.desc=Change the permissions of your PDF document
home.removePages.title=Remove Pages
home.removePages.title=Remove
home.removePages.desc=Delete unwanted pages from your PDF document.
home.addPassword.title=Add Password
@@ -67,7 +74,7 @@ home.addPassword.desc=Encrypt your PDF document with a password.
home.removePassword.title=Remove Password
home.removePassword.desc=Remove password protection from your PDF document.
home.compressPdfs.title=Compress PDFs
home.compressPdfs.title=Compress
home.compressPdfs.desc=Compress PDFs to reduce their file size.
home.changeMetadata.title=Change Metadata
@@ -76,13 +83,13 @@ home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
home.fileToPDF.title=Convert file to PDF
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
home.ocr.title=Run OCR on PDF and/or Cleanup scans
home.ocr.title=OCR / Cleanup scans
home.ocr.desc=Cleanup scans and detects text from images within a PDF and re-adds it as text.
home.extractImages.title=Extract Images
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
home.pdfToPDFA.title=Convert PDF to PDF/A
home.pdfToPDFA.title=PDF to PDF/A
home.pdfToPDFA.desc=Convert PDF to PDF/A for long-term storage
home.PDFToWord.title=PDF to Word
@@ -100,7 +107,68 @@ home.PDFToHTML.desc=Convert PDF to HTML format
home.PDFToXML.title=PDF to XML
home.PDFToXML.desc=Convert PDF to XML format
home.ScannerImageSplit.title=Detect/Split Scanned photos
home.ScannerImageSplit.desc=Splits multiple photos from within a photo/PDF
home.sign.title=Sign
home.sign.desc=Adds signature to PDF by drawing, text or image
home.flatten.title=Flatten
home.flatten.desc=Remove all interactive elements and forms from a PDF
home.repair.title=Repair
home.repair.desc=Tries to repair a corrupt/broken PDF
home.removeBlanks.title=Remove Blank pages
home.removeBlanks.desc=Detects and removes blank pages from a document
home.compare.title=Compare
home.compare.desc=Compares and shows the differences between 2 PDF Documents
downloadPdf=Download PDF
text=Text
font=Font
removeBlanks.title=Remove Blanks
removeBlanks.header=Remove Blank Pages
removeBlanks.threshold=Threshold:
removeBlanks.thresholdDesc=Threshold for determining how white a white pixel must be
removeBlanks.whitePercent=White Percent (%):
removeBlanks.whitePercentDesc=Percent of page that must be white to be removed
removeBlanks.submit=Remove Blanks
compare.title=Compare
compare.header=Compare PDFs
compare.document.1=Document 1
compare.document.2=Document 2
compare.submit=Compare
sign.title=Sign
sign.header=Sign PDFs
sign.upload=Upload Image
sign.draw=Draw Signature
sign.text=Text Input
sign.clear=Clear
sign.add=Add
repair.title=Repair
repair.header=Repair PDFs
repair.submit=Repair
flatten.title=Flatten
flatten.header=Flatten PDFs
flatten.submit=Flatten
ScannerImageSplit.selectText.1=Angle Threshold:
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
ScannerImageSplit.selectText.3=Tolerance:
ScannerImageSplit.selectText.4=Determines the range of color variation around the estimated background color (default: 30).
ScannerImageSplit.selectText.5=Minimum Area:
ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000).
ScannerImageSplit.selectText.7=Minimum Contour Area:
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo
ScannerImageSplit.selectText.9=Border Size:
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
navbar.settings=Settings
settings.title=Settings
@@ -113,6 +181,7 @@ settings.downloadOption.3=Download file
settings.zipThreshold=Zip files when the number of downloaded files exceeds
#OCR
@@ -123,11 +192,13 @@ ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
ocr.selectText.3=Correct pages were scanned at a skewed angle by rotating them back into place
ocr.selectText.4=Clean page so its less likely that OCR will find text in background noise. (No output change)
ocr.selectText.5=Clean page so its less likely that OCR will find text in background noise, maintains cleanup in output.
ocr.selectText.6=Ignores pages that have interacive text on them, only OCRs pages that are images
ocr.selectText.6=Ignores pages that have interactive text on them, only OCRs pages that are images
ocr.selectText.7=Force OCR, will OCR Every page removing all original text elements
ocr.selectText.8=Normal (Will error if PDF contains text)
ocr.selectText.9=Additional Settings
ocr.selectText.10=OCR Mode
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
ocr.selectText.12=Render Type (Advanced)
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
ocr.submit=Process PDF with OCR
@@ -164,7 +235,7 @@ compress.submit=Compress
#Add image
addImage.title=Add Image
addImage.header=Add image to PDF (Work in progress)
addImage.header=Add image to PDF
addImage.everyPage=Every Page?
addImage.submit=Add image
@@ -179,6 +250,10 @@ pdfOrganiser.title=Page Organiser
pdfOrganiser.header=PDF Page Organiser
pdfOrganiser.submit=Rearrange Pages
#multiTool
multiTool.title=PDF Multi Tool
multiTool.header=PDF Multi Tool
#pageRemover
pageRemover.title=Page Remover

View File

@@ -17,7 +17,9 @@ false=Falso
unknown=Desconocido
save=Guardar
close=Cerrar
filesSelected=archivos seleccionados
noFavourites=No se agregaron favoritos
bored=¿Aburrido de esperar?
#############
# HOME-PAGE #
#############
@@ -27,14 +29,18 @@ navbar.convert=Convertir
navbar.security=Seguridad
navbar.other=Otro
navbar.darkmode=Modo oscuro
navbar.pageOps=Operaciones de página
home.merge.title=Une PDFs
home.multiTool.title=Multiherramienta PDF
home.multiTool.desc=Combinar, rotar, reorganizar y eliminar páginas
home.merge.title=Unir
home.merge.desc=Unir fácilmente múltiples PDFs en uno.
home.split.title=Divide PDFs
home.split.title=Divide
home.split.desc=Divide PDFs en múltiples documentos
home.rotate.title=Rota PDFs
home.rotate.title=Rota
home.rotate.desc=Rota fácilmente tus PDFs.
home.imageToPdf.title=Imagen a PDF
@@ -43,7 +49,7 @@ home.imageToPdf.desc=Convierte una imagen (PNG, JPEG, GIF) a PDF.
home.pdfToImage.title=PDF a Imagen
home.pdfToImage.desc=Convierte un PDF a una imagen. (PNG, JPEG, GIF)
home.pdfOrganiser.title=Organizador PDF
home.pdfOrganiser.title=Organizador
home.pdfOrganiser.desc=Elimina/Reorganiza páginas en cualquier orden
home.addImage.title=Agregar imagen al PDF
@@ -58,7 +64,7 @@ home.remove-watermark.desc=Elimina marcas de agua de tu documento PDF.
home.permissions.title=Cambia Permisos
home.permissions.desc=Cambia los permisos de tu documento PDF
home.removePages.title=Elimina Páginas
home.removePages.title=Elimina
home.removePages.desc=Elimina páginas no deseadas de tu documento PDF.
home.addPassword.title=Añade Contraseña
@@ -67,7 +73,7 @@ home.addPassword.desc=Encripta tu documento PDF con una contraseña.
home.removePassword.title=Elimina Contraseña
home.removePassword.desc=Elimina la contraseña de tu documento PDF.
home.compressPdfs.title=Comprime PDFs
home.compressPdfs.title=Comprime
home.compressPdfs.desc=Comprime PDFs para reducir el tamaño del fichero.
home.changeMetadata.title=Cambia Metadatos
@@ -88,8 +94,8 @@ home.pdfToPDFA.desc=Convierte PDF to PDF/A para almacenamiento a largo plazo
home.PDFToWord.title=PDF a Word
home.PDFToWord.desc=Convertir formatos PDF a Word (DOC, DOCX y ODT)
home.PDFToPresentation.title=PDF a presentación
home.PDFToPresentation.desc=Convertir PDF a formatos de presentación (PPT, PPTX y ODP)
home.PDFToPresentation.title=PDF a presentación
home.PDFToPresentation.desc=Convertir PDF a formatos de presentación (PPT, PPTX y ODP)
home.PDFToText.title=PDF a texto/RTF
home.PDFToText.desc=Convertir PDF a texto o formato RTF
@@ -100,6 +106,68 @@ home.PDFToHTML.desc=Convertir PDF a formato HTML
home.PDFToXML.title=PDF a XML
home.PDFToXML.desc=Convertir PDF a formato XML
home.ScannerImageSplit.title=Detectar/Dividir fotos escaneadas
home.ScannerImageSplit.desc=Dividir varias fotos dentro de una foto/PDF
home.sign.title=Firmar
home.sign.desc=Añade firma a PDF mediante dibujo, texto o imagen
home.flatten.title=Aplanar
home.flatten.desc=Eliminar todos los elementos y formularios interactivos de un PDF
home.repair.title=Reparar
home.repair.desc=Intenta reparar un PDF corrupto/roto
home.removeBlanks.title=Eliminar páginas en blanco
home.removeBlanks.desc=Detecta y elimina páginas en blanco de un documento
home.compare.title=Comparar
home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF
downloadPdf=Descargar PDF
text=Texto
font=Fuente
removeBlanks.title=Eliminar espacios en blanco
removeBlanks.header=Eliminar páginas en blanco
removeBlanks.threshold=Umbral:
removeBlanks.thresholdDesc=Umbral para determinar qué tan blanco debe ser un píxel blanco
removeBlanks.whitePercent=Porcentaje de blanco (%):
removeBlanks.whitePercentDesc=Porcentaje de página que debe ser blanca para ser eliminada
removeBlanks.submit=Eliminar espacios en blanco
compare.title=Comparar
compare.header=Comparar archivos PDF
compare.document.1=Documento 1
compare.document.2=Documento 2
compare.submit=Comparar
sign.title=Firmar
sign.header=Firmar archivos PDF
sign.upload=Subir imagen
sign.draw=Dibujar firma
sign.text=Entrada de texto
sign.clear=Borrar
sign.add=Agregar
repair.title=Reparar
repair.header=Reparar archivos PDF
repair.submit=Reparar
flatten.title=Aplanar
flatten.header=Acoplar archivos PDF
flatten.submit=Aplanar
ScannerImageSplit.selectText.1=Umbral de ángulo:
ScannerImageSplit.selectText.2=Establece el ángulo absoluto mínimo requerido para rotar la imagen (predeterminado: 10).
ScannerImageSplit.selectText.3=Tolerancia:
ScannerImageSplit.selectText.4=Determina el rango de variación de color alrededor del color de fondo estimado (predeterminado: 30).
ScannerImageSplit.selectText.5=Área mínima:
ScannerImageSplit.selectText.6=Establece el umbral mínimo de área para una foto (predeterminado: 10000).
ScannerImageSplit.selectText.7=Área de contorno mínima:
ScannerImageSplit.selectText.8=Establece el umbral mínimo del área de contorno para una foto
ScannerImageSplit.selectText.9=Tamaño del borde:
ScannerImageSplit.selectText.10=Establece el tamaño del borde agregado y eliminado para evitar bordes blancos en la salida (predeterminado: 1).
navbar.settings=Ajustes
settings.title=Ajustes
@@ -117,16 +185,18 @@ settings.zipThreshold=Ficheros Zip cuando excede el número de ficheros descarga
#OCR
ocr.title=OCR / Escaneo de limpieza
ocr.header=Escaneos de limpieza / OCR (Reconocimiento óptico de caracteres)
ocr.SeleccionaText.1=Selecciona los idiomas que se detectarán en el PDF (Los enumerados son los detectados actualmente):
ocr.SeleccionaText.2=Produzca un archivo de texto que contenga texto OCR junto con el PDF editado con OCR
ocr.SeleccionaText.3=Corrija las páginas que se escanearon en un ángulo torcido girándolas nuevamente a su lugar
ocr.SeleccionaText.4=Limpie la página para que sea menos probable que el OCR encuentre texto en el ruido de fondo. (Sin cambio de salida)
ocr.SeleccionaText.5=Limpie la página para que sea menos probable que el OCR encuentre texto en el ruido de fondo, mantiene la limpieza en la salida.
ocr.SeleccionaText.6=Ignora las páginas que tienen texto interactivo, solo las páginas OCR que son imágenes
ocr.SeleccionaText.7=Fuerza OCR, OCR eliminará en cada página todo el texto original
ocr.SeleccionaText.8=Normal (Se producirá un error si el PDF contiene texto)
ocr.SeleccionaText.9=Ajustes Adicionales
ocr.SeleccionaText.10=Modo OCR
ocr.selectText.1=Selecciona los idiomas que se detectarán en el PDF (Los enumerados son los detectados actualmente):
ocr.selectText.2=Produzca un archivo de texto que contenga texto OCR junto con el PDF editado con OCR
ocr.selectText.3=Corrija las páginas que se escanearon en un ángulo torcido girándolas nuevamente a su lugar
ocr.selectText.4=Limpie la página para que sea menos probable que el OCR encuentre texto en el ruido de fondo. (Sin cambio de salida)
ocr.selectText.5=Limpie la página para que sea menos probable que el OCR encuentre texto en el ruido de fondo, mantiene la limpieza en la salida.
ocr.selectText.6=Ignora las páginas que tienen texto interactivo, solo las páginas OCR que son imágenes
ocr.selectText.7=Fuerza OCR, OCR eliminará en cada página todo el texto original
ocr.selectText.8=Normal (Se producirá un error si el PDF contiene texto)
ocr.selectText.9=Ajustes Adicionales
ocr.selectText.10=Modo OCR
ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión)
ocr.selectText.12=Tipo de procesamiento (avanzado)
ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en docker
ocr.credit=Este servicio utiliza OCRmyPDF y Tesseract para OCR.
ocr.submit=Procesa PDF con OCR
@@ -135,7 +205,7 @@ ocr.submit=Procesa PDF con OCR
extractImages.title=Extraer imágenes
extractImages.header=Extraer imágenes
extractImages.SeleccionaText=Selecciona el formato de imagen para convertir las imágenes extraídas
extractImages.selectText=Selecciona el formato de imagen para convertir las imágenes extraídas
extractImages.submit=Extraer
@@ -151,33 +221,36 @@ fileToPDF.submit=Convertir a PDF
compress.title=Comprimir
compress.header=Comprimir PDF
compress.credit=Este servicio usa OCRmyPDF para la Compresión/Optimizatión del PDF.
compress.SeleccionaText.1=Nivel de Optimización:
compress.SeleccionaText.2=0 (Sin optimización)
compress.SeleccionaText.3=1 (Por defecto, optimización sin pérdidas)
compress.SeleccionaText.4=2 (Optimización con pérdida)
compress.SeleccionaText.5=3 (Optimización con pérdida, más agresiva)
compress.SeleccionaText.6=Habilita la vista web rápida (linealizar PDF)
compress.SeleccionaText.7=Habilita la codificación JBIG2 con pérdida
compress.selectText.1=Nivel de Optimización:
compress.selectText.2=0 (Sin optimización)
compress.selectText.3=1 (Por defecto, optimización sin pérdidas)
compress.selectText.4=2 (Optimización con pérdida)
compress.selectText.5=3 (Optimización con pérdida, más agresiva)
compress.selectText.6=Habilita la vista web rápida (linealizar PDF)
compress.selectText.7=Habilita la codificación JBIG2 con pérdida
compress.submit=Comprimir
#Add image
addImage.title=Añade Imagen
addImage.header=Añade image de PDF (Trabajo en progreso)
addImage.header=Añade image de PDF
addImage.everyPage=¿Todas las páginas?
addImage.submit=Añade imagen
#merge
merge.title=Mezcla
merge.header=Mezcla múltiples PDFs (2+)
merge.submit=Mezcla
merge.title=Une
merge.header=Une múltiples PDFs (2+)
merge.submit=Une
#pdfOrganiser
pdfOrganiser.title=Organizador de páginas
pdfOrganiser.header=Organizador de páginas PDF
pdfOrganiser.submit=Organiza páginas
#herramienta multiple
multiTool.title=Multiherramienta PDF
multiTool.header=Multiherramienta PDF
#pageRemover
pageRemover.title=Eliminador de páginas
@@ -199,13 +272,13 @@ split.title=Dividir PDF
split.header=Dividir PDF
split.desc.1=Los números que selecciona son el número de página en el que desea hacer una división
split.desc.2=Como tal, seleccionar 1,3,7-8 dividiría un documento de 10 páginas en 6 archivos PDF separados con:
split.desc.3=Documento #1: Page 1
split.desc.4=Documento #2: Page 2 and 3
split.desc.5=Documento #3: Page 4, 5 and 6
split.desc.6=Documento #4: Page 7
split.desc.7=Documento #5: Page 8
split.desc.8=Documento #6: Page 9 and 10
split.splitPages=Introduzca las páginas para dividir en:
split.desc.3=Documento #1: Página 1
split.desc.4=Documento #2: Páginas 2 y 3
split.desc.5=Documento #3: Páginas 4, 5 y 6
split.desc.6=Documento #4: Página 7
split.desc.7=Documento #5: Página 8
split.desc.8=Documento #6: Páginas 9 y 10
split.splitPages=Introduzca las páginas para dividir:
split.submit=Dividir
@@ -213,16 +286,16 @@ split.submit=Dividir
imageToPDF.title=Imagen a PDF
imageToPDF.header=Imagen a PDF
imageToPDF.submit=Convertir
imageToPDF.SeleccionaText.1=Estirar para ajustar
imageToPDF.SeleccionaText.2=Auto rotación PDF
imageToPDF.SeleccionaText.3=Lógica de archivos múltiples (Únicamente activado si funciona con multiples imágenes)
imageToPDF.SeleccionaText.4=Une en un único PDF
imageToPDF.SeleccionaText.5=Convertir a PDFs separados
imageToPDF.selectText.1=Estirar para ajustar
imageToPDF.selectText.2=Auto rotación PDF
imageToPDF.selectText.3=Lógica de archivos múltiples (Únicamente activado si funciona con multiples imágenes)
imageToPDF.selectText.4=Une en un único PDF
imageToPDF.selectText.5=Convertir a PDFs separados
#pdfToImage
pdfToImage.title=PDF a Imagen
pdfToImage.header=PDF a Imagen
pdfToImage.SeleccionaText=Formato de Imagen
pdfToImage.selectText=Formato de Imagen
pdfToImage.singleOrMultiple=Tipo resultante de imagen
pdfToImage.single=Imagen Grande Única
pdfToImage.multi=Múltiples Imágenes
@@ -235,68 +308,68 @@ pdfToImage.submit=Convertir
#addPassword
addPassword.title=Añade Contraseña
addPassword.header=Añade contraseña (Encripta)
addPassword.SeleccionaText.1=Selecciona PDF para encriptar
addPassword.SeleccionaText.2=Contraseña
addPassword.SeleccionaText.3=Longitud de la clave de cifrado
addPassword.SeleccionaText.4=Valores altos son más fuertes, pero valores bajos tienen mejor compatibilidad.
addPassword.SeleccionaText.5=Permisos para establecer
addPassword.SeleccionaText.6=Impedir el ensamblaje del documento
addPassword.SeleccionaText.7=Impedir la extracción de contenido
addPassword.SeleccionaText.8=Impedir la extracción para la accesibilidad
addPassword.SeleccionaText.9=Impedir rellenar formulario
addPassword.SeleccionaText.10=Impedir modificación
addPassword.SeleccionaText.11=Impedir modificación de anotaciones
addPassword.SeleccionaText.12=Impedir imprimir
addPassword.SeleccionaText.13=Impedir imprimir diferentes formatos
addPassword.selectText.1=Selecciona PDF para encriptar
addPassword.selectText.2=Contraseña
addPassword.selectText.3=Longitud de la clave de cifrado
addPassword.selectText.4=Valores altos son más fuertes, pero valores bajos tienen mejor compatibilidad.
addPassword.selectText.5=Permisos para establecer
addPassword.selectText.6=Impedir el ensamblaje del documento
addPassword.selectText.7=Impedir la extracción de contenido
addPassword.selectText.8=Impedir la extracción para la accesibilidad
addPassword.selectText.9=Impedir rellenar formulario
addPassword.selectText.10=Impedir modificación
addPassword.selectText.11=Impedir modificación de anotaciones
addPassword.selectText.12=Impedir imprimir
addPassword.selectText.13=Impedir imprimir diferentes formatos
addPassword.submit=Encripta
#watermark
watermark.title=Añade marca de agua
watermark.header=Añade marca de agua
watermark.SeleccionaText.1=Selecciona PDF para añadir marca de agua:
watermark.SeleccionaText.2=Texto de la marca de agua:
watermark.SeleccionaText.3=Tamaño de la Fuente:
watermark.SeleccionaText.4=Rotación (0-360):
watermark.SeleccionaText.5=Ancho (Espacio entre cada marca de agua horizontalmente):
watermark.SeleccionaText.6=Alto (Espacio entre cada marca de agua verticalmente):
watermark.SeleccionaText.7=Opacidad (0% - 100%):
watermark.selectText.1=Selecciona PDF para añadir marca de agua:
watermark.selectText.2=Texto de la marca de agua:
watermark.selectText.3=Tamaño de la Fuente:
watermark.selectText.4=Rotación (0-360):
watermark.selectText.5=Ancho (Espacio entre cada marca de agua horizontalmente):
watermark.selectText.6=Alto (Espacio entre cada marca de agua verticalmente):
watermark.selectText.7=Opacidad (0% - 100%):
watermark.submit=Añade marca de agua
#remove-watermark
remove-watermark.title=Elimina marca de agua
remove-watermark.header=Elimina marca de agua
remove-watermark.SeleccionaText.1=Selecciona PDF para eliminar la marca de agua:
remove-watermark.SeleccionaText.2=Texto de la marca de agua:
remove-watermark.selectText.1=Selecciona PDF para eliminar la marca de agua:
remove-watermark.selectText.2=Texto de la marca de agua:
remove-watermark.submit=Elimina marca de agua
#Change permissions
permissions.title=Cambiar Permisos
permissions.header=Cambiar Permisos
permissions.warning=Advertencia para que estos permisos no se puedan cambiar, se recomienda configurarlos con una contraseña a través de la página de cambio de contraseña
permissions.SeleccionaText.1=Selecciona PDF para cambiar los permisos
permissions.SeleccionaText.2=Permisos a establecer
permissions.SeleccionaText.3=Impedir el ensamblaje del documento
permissions.SeleccionaText.4=Impedir la extracción de contenido
permissions.SeleccionaText.5=Impedir la extracción para la accesibilidad
permissions.SeleccionaText.6=Impedir rellenar formulario
permissions.SeleccionaText.7=Impedir modificación
permissions.SeleccionaText.8=Impedir modificación de anotaciones
permissions.SeleccionaText.9=Impedir imprimir
permissions.SeleccionaText.10=Impedir imprimir diferentes formatos
permissions.selectText.1=Selecciona PDF para cambiar los permisos
permissions.selectText.2=Permisos a establecer
permissions.selectText.3=Impedir el ensamblaje del documento
permissions.selectText.4=Impedir la extracción de contenido
permissions.selectText.5=Impedir la extracción para la accesibilidad
permissions.selectText.6=Impedir rellenar formulario
permissions.selectText.7=Impedir modificación
permissions.selectText.8=Impedir modificación de anotaciones
permissions.selectText.9=Impedir imprimir
permissions.selectText.10=Impedir imprimir diferentes formatos
permissions.submit=Cambiar
#remove password
removePassword.title=Elimina contraseña
removePassword.header=Elimina contraseña (Desencripta)
removePassword.SeleccionaText.1=Selecciona PDF para Desencriptar
removePassword.SeleccionaText.2=Contraseña
removePassword.selectText.1=Selecciona PDF para Desencriptar
removePassword.selectText.2=Contraseña
removePassword.submit=Elimina
changeMetadata.title=Cambia Metadatos
changeMetadata.header=Cambia Metadatos
changeMetadata.SeleccionaText.1=Edite las variables que desea cambiar
changeMetadata.SeleccionaText.2=Elimina todos los metadatos
changeMetadata.SeleccionaText.3=Mostrar metadatos personalizados:
changeMetadata.selectText.1=Edite las variables que desea cambiar
changeMetadata.selectText.2=Elimina todos los metadatos
changeMetadata.selectText.3=Mostrar metadatos personalizados:
changeMetadata.author=Autor:
changeMetadata.creationDate=Fecha de Creación (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creador:
@@ -306,13 +379,13 @@ changeMetadata.producer=Productor:
changeMetadata.subject=Asunto:
changeMetadata.title=Título:
changeMetadata.trapped=Trapped:
changeMetadata.SeleccionaText.4=Otros Metadatos:
changeMetadata.SeleccionaText.5=Agregar entrada de metadatos personalizados
changeMetadata.selectText.4=Otros Metadatos:
changeMetadata.selectText.5=Agregar entrada de metadatos personalizados
changeMetadata.submit=Cambia
xlsToPdf.title=Excel a PDF
xlsToPdf.header=Excel a PDF
xlsToPdf.SeleccionaText.1=Selecciona hoja de cálculo de Excel XLS o XLSX para convertir
xlsToPdf.selectText.1=Selecciona hoja de cálculo de Excel XLS o XLSX para convertir
xlsToPdf.convert=convertir
@@ -328,29 +401,29 @@ pdfToPDFA.submit=Convertir
PDFToWord.title=PDF a Word
PDFToWord.header=PDF a Word
PDFToWord.selectText.1=Formato de archivo de salida
PDFToWord.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToWord.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToWord.submit=Convertir
PDFToPresentation.title=PDF a presentación
PDFToPresentation.header=PDF a presentación
PDFToPresentation.title=PDF a presentación
PDFToPresentation.header=PDF a presentación
PDFToPresentation.selectText.1=Formato de archivo de salida
PDFToPresentation.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToPresentation.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToPresentation.submit=Convertir
PDFToText.title=PDF a texto/RTF
PDFToText.header=PDF a texto/RTF
PDFToText.selectText.1=Formato de archivo de salida
PDFToText.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToText.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToText.submit=Convertir
PDFToHTML.title=PDF a HTML
PDFToHTML.header=PDF a HTML
PDFToHTML.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToHTML.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToHTML.submit=Convertir
PDFToXML.title=PDF a XML
PDFToXML.header=PDF a XML
PDFToXML.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToXML.credit=Este servicio utiliza LibreOffice para la conversión de archivos.
PDFToXML.submit=Convertir

View File

@@ -21,7 +21,9 @@ false=Faux
unknown=Inconnu
save=Enregistrer
close=Fermer
filesSelected=fichiers sélectionnés
noFavourites=Aucun favori ajouté
bored=Ennuyé d'attendre ?
#############
# HOME-PAGE #
#############
@@ -31,14 +33,18 @@ navbar.convert=Convertir
navbar.security=Sécurité
navbar.other=Autre
navbar.darkmode=Mode sombre
navbar.pageOps=Opérations de page
home.merge.title=Fusionner des PDF
home.multiTool.title=Multi-outil PDF
home.multiTool.desc=Fusionner, faire pivoter, réorganiser et supprimer des pages
home.merge.title=Fusionnez
home.merge.desc=Fusionnez facilement plusieurs PDF en un seul.
home.split.title=Fractionner les PDF
home.split.title=Fractionner
home.split.desc=Diviser les PDF en plusieurs documents
home.rotate.title=Faire pivoter les PDF
home.rotate.title=Tourner
home.rotate.desc=Faites pivoter facilement vos PDF.
home.imageToPdf.title=Image au format PDF
@@ -47,7 +53,7 @@ home.imageToPdf.desc=Convertir une image (PNG, JPEG, GIF) en PDF.
home.pdfToImage.title=PDF vers image
home.pdfToImage.desc=Convertir un PDF en image. (PNG, JPEG, GIF)
home.pdfOrganiser.title=Organisateur PDF
home.pdfOrganiser.title=Organisateur
home.pdfOrganiser.desc=Supprimer/Réorganiser les pages dans n'importe quel ordre
home.addImage.title=Ajouter une image au PDF
@@ -62,7 +68,7 @@ home.remove-watermark.desc=Supprimez les filigranes de votre document PDF.
home.permissions.title=Modifier les autorisations
home.permissions.desc=Modifier les permissions de votre document PDF
home.removePages.title=Supprimer des pages
home.removePages.title=Supprimer
home.removePages.desc=Supprimez les pages inutiles de votre document PDF.
home.addPassword.title=Ajouter un mot de passe
@@ -71,7 +77,7 @@ home.addPassword.desc=Cryptez votre document PDF avec un mot de passe.
home.removePassword.title=Supprimer le mot de passe
home.removePassword.desc=Supprimez la protection par mot de passe de votre document PDF.
home.compressPdfs.title=Compresser les PDF
home.compressPdfs.title=Compresser
home.compressPdfs.desc=Compressez les PDF pour réduire leur taille de fichier.
home.changeMetadata.title=Modifier les métadonnées
@@ -106,6 +112,69 @@ home.PDFToHTML.desc=Convertir le PDF au format HTML
home.PDFToXML.title=PDF vers XML
home.PDFToXML.desc=Convertir le PDF au format XML
home.ScannerImageSplit.title=Détecter/diviser les photos numérisées
home.ScannerImageSplit.desc=Divise plusieurs photos à partir d'une photo/PDF
home.sign.title=Signe
home.sign.desc=Ajoute une signature au PDF par dessin, texte ou image
home.flatten.title=Aplatir
home.flatten.desc=Supprimer tous les éléments et formulaires interactifs d'un PDF
home.repair.title=Réparer
home.repair.desc=Essaye de réparer un PDF corrompu/cassé
home.removeBlanks.title=Supprimer les pages vierges
home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document
home.compare.title=Comparer
home.compare.desc=Compare et affiche les différences entre 2 documents PDF
downloadPdf=Télécharger le PDF
text=Texte
font=Police
removeBlanks.title=Supprimer les blancs
removeBlanks.header=Supprimer les pages vierges
removeBlanks.threshold=Seuil :
removeBlanks.thresholdDesc=Seuil pour déterminer à quel point un pixel blanc doit être blanc
removeBlanks.whitePercent=Pourcentage blanc (%) :
removeBlanks.whitePercentDesc=Pourcentage de page qui doit être blanche pour être supprimée
removeBlanks.submit=Supprimer les blancs
compare.title=Comparer
compare.header=Comparer des PDF
compare.document.1=Document 1
compare.document.2=Document 2
compare.submit=Comparer
sign.title=Signe
sign.header=Signer des PDF
sign.upload=Télécharger l'image
sign.draw=Dessiner une signature
sign.text=Saisie de texte
sign.clear=Effacer
sign.add=Ajouter
repair.title=Réparer
repair.header=Réparer les PDF
repair.submit=Réparer
flatten.title=Aplatir
flatten.header=Aplatir les PDF
flatten.submit=Aplatir
ScannerImageSplit.selectText.1=Seuil d'angle :
ScannerImageSplit.selectText.2=Définit l'angle absolu minimum requis pour la rotation de l'image (par défaut : 10).
ScannerImageSplit.selectText.3=Tolérance :
ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d'arrière-plan estimée (par défaut : 30).
ScannerImageSplit.selectText.5=Zone minimale :
ScannerImageSplit.selectText.6=Définit le seuil de zone minimum pour une photo (par défaut : 10000).
ScannerImageSplit.selectText.7=Zone de contour minimale :
ScannerImageSplit.selectText.8=Définit le seuil de zone de contour minimum pour une photo
ScannerImageSplit.selectText.9=Taille de la bordure :
ScannerImageSplit.selectText.10=Définit la taille de la bordure ajoutée et supprimée pour éviter les bordures blanches dans la sortie (par défaut : 1).
navbar.settings=Paramètres
settings.title=Paramètres
settings.update=Mise à jour disponible
@@ -130,6 +199,8 @@ ocr.selectText.7=Forcer l'OCR, OCR chaque page supprimera tous les éléments de
ocr.selectText.8=Normal (Erreur si le PDF contient du texte)
ocr.selectText.9=Paramètres supplémentaires
ocr.selectText.10=Mode ROC
ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion)
ocr.selectText.12=Type de rendu (avancé)
ocr.help=Veuillez lire cette documentation pour savoir comment l'utiliser pour d'autres langues et/ou une utilisation non dans docker
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR.
ocr.submit=Traiter PDF avec OCR
@@ -151,7 +222,7 @@ fileToPDF.submit=Convertir en PDF
#Add image
addImage.title=Ajouter une image
addImage.header=Ajouter une image au PDF (Travail en cours)
addImage.header=Ajouter une image au PDF
addImage.everyPage=Chaque page?
addImage.submit=Ajouter une image
@@ -179,6 +250,10 @@ pdfOrganiser.title=Organisateur de pages
pdfOrganiser.header=Organisateur de pages PDF
pdfOrganiser.submit=Réorganiser les pages
#Outil Multi-fonction
multiTool.title=Multi-outil PDF
multiTool.header=Outil multiple PDF
#pageRemover
pageRemover.title=Suppresseur de pages

View File

@@ -0,0 +1,432 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=Scegli PDF
multiPdfPrompt=Scegli 2 o più PDF
multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF
imgPrompt=Scegli immagine/i
genericSubmit=Invia
processTimeWarning=Nota: Questo processo potrebbe richiedere fino a un minuto in base alla dimensione dei file
pageOrderPrompt=Ordine delle pagine (inserisci una lista di numeri separati da virgola):
goToPage=Vai
true=Vero
false=Falso
unknown=Sconosciuto
save=Salva
close=Chiudi
filesSelected=file selezionati
noFavourites=Nessun preferito
bored=Stanco di aspettare?
#############
# HOME-PAGE #
#############
home.desc=La tua pagina self-hostata per gestire qualsiasi PDF.
navbar.convert=Converti
navbar.security=Sicurezza
navbar.other=Altro
navbar.darkmode=Modalità Scura
navbar.pageOps=Modifica pagine
home.multiTool.title=Multifunzione PDF
home.multiTool.desc=Unisci, Ruota, Riordina, e Rimuovi pagine
home.merge.title=Unisci
home.merge.desc=Unisci facilmente più PDF in uno.
home.split.title=Dividi
home.split.desc=Dividi un singolo PDF in più documenti.
home.rotate.title=Ruota
home.rotate.desc=Ruota un PDF.
home.imageToPdf.title=Da immagine a PDF
home.imageToPdf.desc=Converti un'immagine (PNG, JPEG, GIF) in PDF.
home.pdfToImage.title=Da PDF a immagine
home.pdfToImage.desc=Converti un PDF in un'immagine. (PNG, JPEG, GIF)
home.pdfOrganiser.title=Organizza
home.pdfOrganiser.desc=Rimuovi/Riordina le pagine in qualsiasi ordine.
home.addImage.title=Aggiungi Immagine
home.addImage.desc=Aggiungi un'immagine in un punto specifico del PDF (Work in progress)
home.watermark.title=Aggiungi Filigrana
home.watermark.desc=Aggiungi una filigrana al tuo PDF.
home.remove-watermark.title=Rimuovi Filigrana
home.remove-watermark.desc=Rimuovi la filigrana dal tuo PDF.
home.permissions.title=Cambia Permessi
home.permissions.desc=Cambia i permessi del tuo PDF.
home.removePages.title=Rimuovi
home.removePages.desc=Elimina alcune pagine dal PDF.
home.addPassword.title=Aggiungi Password
home.addPassword.desc=Crittografa il tuo PDF con una password.
home.removePassword.title=Rimuovi Password
home.removePassword.desc=Rimuovi la password dal tuo PDF.
home.compressPdfs.title=Comprimi
home.compressPdfs.desc=Comprimi PDF per ridurne le dimensioni.
home.changeMetadata.title=Modifica Proprietà
home.changeMetadata.desc=Modifica/Aggiungi/Rimuovi le proprietà di un documento PDF.
home.fileToPDF.title=Converti file in PDF
home.fileToPDF.desc=Converti quasi ogni file in PDF (DOCX, PNG, XLS, PPT, TXT e altro)
home.ocr.title=OCR / Pulisci scansioni
home.ocr.desc=Pulisci scansioni ed estrai testo da immagini, convertendo le immagini in testo puro.
home.extractImages.title=Estrai immagini
home.extractImages.desc=Estrai tutte le immagini da un PDF e salvale come zip.
home.pdfToPDFA.title=Converti in PDF/A
home.pdfToPDFA.desc=Converti un PDF nel formato PDF/A per archiviazione a lungo termine.
home.PDFToWord.title=Da PDF a Word
home.PDFToWord.desc=Converti un PDF nei formati Word (DOC, DOCX e ODT)
home.PDFToPresentation.title=Da PDF a presentazioni
home.PDFToPresentation.desc=Converti un PDF in presentazioni (PPT, PPTX and ODP)
home.PDFToText.title=Da PDF a testo/RTF
home.PDFToText.desc=Converti un PDF in testo o RTF.
home.PDFToHTML.title=Da PDF ad HTML
home.PDFToHTML.desc=Converti un PDF in HTML.
home.PDFToXML.title=Da PDF a XML
home.PDFToXML.desc=Converti un PDF in XML.
home.ScannerImageSplit.title=Trova/Dividi foto scansionate
home.ScannerImageSplit.desc=Estrai più foto da una singola foto o PDF.
home.sign.title=Firma
home.sign.desc=Aggiungi una firma al PDF da disegno, testo o immagine.
home.flatten.title=Appiattisci
home.flatten.desc=Rimuovi tutti gli elementi interattivi e moduli da un PDF.
home.repair.title=Ripara
home.repair.desc=Prova a riparare un PDF corrotto.
home.removeBlanks.title=Rimuovi pagine vuote
home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF.
home.compare.title=Compara
home.compare.desc=Vedi e compara le differenze tra due PDF.
downloadPdf=Scarica PDF
text=Testo
font=Font
removeBlanks.title=Rimuovi spazi vuoti
removeBlanks.header=Rimuovi pagine vuote
removeBlanks.threshold=Soglia:
removeBlanks.thresholdDesc=Soglia che determina un pixel 'bianco'
removeBlanks.whitePercent=Percentuale di bianco (%):
removeBlanks.whitePercentDesc=Percentuale della pagina che deve essere bianca per venire rimossa
removeBlanks.submit=Rimuovi
compare.title=Compara
compare.header=Compara PDF
compare.document.1=Documento 1
compare.document.2=Documento 2
compare.submit=Compara
sign.title=Firma
sign.header=Firma PDF
sign.upload=Carica immagine
sign.draw=Disegna Firma
sign.text=Testo
sign.clear=Cancella
sign.add=Aggiungi
repair.title=Ripara
repair.header=Ripara PDF
repair.submit=Ripara
flatten.title=Appiattisci
flatten.header=Appiattisci PDF
flatten.submit=Appiattisci
ScannerImageSplit.selectText.1=Soglia angolo:
ScannerImageSplit.selectText.2=Imposta il minimo angolo richiesto perché l'immagine venga ruotata (default: 10).
ScannerImageSplit.selectText.3=Tolleranza:
ScannerImageSplit.selectText.4=Imposta lo spettro di colori attorno al colore di sfondo stimato (default: 30).
ScannerImageSplit.selectText.5=Area minima:
ScannerImageSplit.selectText.6=Imposta l'area minima di una foto (default: 10000).
ScannerImageSplit.selectText.7=Area di contorno minima:
ScannerImageSplit.selectText.8=Imposta l'area minima del contorno di una foto
ScannerImageSplit.selectText.9=Spessore bordo:
ScannerImageSplit.selectText.10=Imposta lo spessore del bordo aggiunto o rimosso per prevenire bordi bianchi nel risultato (default: 1).
navbar.settings=Impostazioni
settings.title=Impostazioni
settings.update=Aggiornamento disponibile
settings.appVersion=Versione App:
settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi):
settings.downloadOption.1=Apri in questa finestra
settings.downloadOption.2=Apri in una nuova finestra
settings.downloadOption.3=Scarica file
settings.zipThreshold=Comprimi file in .zip quando il numero di download supera
#OCR
ocr.title=OCR / Pulisci scansioni
ocr.header=Pulisci scansioni / OCR (riconoscimento testo)
ocr.selectText.1=Scegli lingue da usare per il riconoscimento testo (L'elenco contiene quelle attualmente disponibili):
ocr.selectText.2=Crea file di testo contenente il testo estratto oltre al PDF originale
ocr.selectText.3=Sistema le pagine che sono state scansionate storte ruotandole in posizione corretta.
ocr.selectText.4=Pulisci il foglio in modo da evitare errori nella lettura. (non cambia il risultato)
ocr.selectText.5=Pulisci il foglio in modo da evitare errori nella lettura. (cambia il risultato)
ocr.selectText.6=Ignora pagine che contengono testo interattivo, scansiona solo pagine che contengono immagini
ocr.selectText.7=Forza scansione, scansiona ogni pagina rimuovendo gli elementi originali
ocr.selectText.8=Normale (Darà errore se il PDF contiene testo)
ocr.selectText.9=Impostazioni extra
ocr.selectText.10=Modalità OCR
ocr.selectText.11=Rimuovi immagini dopo la scansione (Rimuove TUTTE le immagini, utile solo come parte del processo di conversione)
ocr.selectText.12=Modalità di rendering (avanzato)
ocr.help=Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker
ocr.credit=Questo servizio utilizza OCRmyPDF e Tesseract per l'OCR.
ocr.submit=Scansiona testo nel PDF con OCR
extractImages.title=Estrai immagini
extractImages.header=Estrai immagini
extractImages.selectText=Seleziona il formato in cui salvare le immagini estratte
extractImages.submit=Estrai
#File to PDF
fileToPDF.title=Converti file in PDF
fileToPDF.header=Converti qualsiasi file in PDF
fileToPDF.credit=Questo servizio utilizza LibreOffice e Unoconv per la conversione dei file.
fileToPDF.supportedFileTypes=I formati file supportati dovrebbero includere quelli sottostanti. Tuttavia, per una lista aggiornata controlla la documentazione di LibreOffice
fileToPDF.submit=Converti in PDF
#compress
compress.title=Comprimi
compress.header=Comprimi PDF
compress.credit=Questo servizio utilizza OCRmyPDF per la compressione e ottimizzazione.
compress.selectText.1=Livello di ottimizzazione:
compress.selectText.2=0 (Nessuna compressione)
compress.selectText.3=1 (Default, nessuna perdita di qualità)
compress.selectText.4=2 (Perdita di qualità)
compress.selectText.5=3 (Perdita di qualità, più aggressivo)
compress.selectText.6=Visualizzazione rapida sul web (linearizza PDF)
compress.selectText.7=Attiva codifica JBIG2 (lossy)
compress.submit=Comprimi
#Add image
addImage.title=Aggiungi Immagine
addImage.header=Aggiungi un'immagine ad un PDF.
addImage.everyPage=Ogni pagina?
addImage.submit=Aggiungi immagine
#merge
merge.title=Unisci
merge.header=Unisci 2 o più PDF
merge.submit=Unisci
#pdfOrganiser
pdfOrganiser.title=Organizza pagine
pdfOrganiser.header=Organizza le pagine di un PDF
pdfOrganiser.submit=Riordina pagine
#multiTool
multiTool.title=Multifunzione PDF
multiTool.header=Multifunzione PDF
#pageRemover
pageRemover.title=Rimuovi pagine
pageRemover.header=Rimuovi pagine da un PDF
pageRemover.pagesToDelete=Pagine da eliminare (inserisci una lista di numeri separati da virgola):
pageRemover.submit=Rimuovi pagine
#rotate
rotate.title=Ruota PDF
rotate.header=Ruota PDF
rotate.selectAngle=Scegli angolo di rotazione (in multipli di 90 gradi):
rotate.submit=Ruota
#split
split.title=Dividi PDF
split.header=Dividi PDF
split.desc.1=I numeri che scegli sono le pagine a cui desideri dividere il documento
split.desc.2=Per esempio inserendo 1,3,7-8 separeresti un documento di 10 pagine in 6 diversi PDF con:
split.desc.3=Documento #1: Pagina 1
split.desc.4=Documento #2: Pagine 2 e 3
split.desc.5=Documento #3: Pagine 4, 5 e 6
split.desc.6=Documento #4: Pagina 7
split.desc.7=Documento #5: Pagina 8
split.desc.8=Documento #6: Pagine 9 e 10
split.splitPages=Inserisci pagine a cui dividere:
split.submit=Dividi
#imageToPDF
imageToPDF.title=Immagine a PDF
imageToPDF.header=Immagine a PDF
imageToPDF.submit=Converti
imageToPDF.selectText.1=Allarga per riempire
imageToPDF.selectText.2=Ruota automaticamente PDF
imageToPDF.selectText.3=Logica multi-file (funziona solo se ci sono più immagini)
imageToPDF.selectText.4=Unisci in un unico PDF
imageToPDF.selectText.5=Converti in PDF separati
#pdfToImage
pdfToImage.title=PDF a immagine
pdfToImage.header=PDF a immagine
pdfToImage.selectText=Formato immagini
pdfToImage.singleOrMultiple=Tipo di immagine
pdfToImage.single=Unica immagine larga
pdfToImage.multi=Più immagini
pdfToImage.colorType=Tipo di colore
pdfToImage.color=A colori
pdfToImage.grey=Scala di grigi
pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!)
pdfToImage.submit=Converti
#addPassword
addPassword.title=Aggiungi Password
addPassword.header=Aggiungi password (crittografa)
addPassword.selectText.1=Seleziona PDF da crittografare
addPassword.selectText.2=Password
addPassword.selectText.3=Lunghezza chiave
addPassword.selectText.4=Valori più grandi sono più sicuri, ma valori più piccoli offrono una compatibilità maggiore.
addPassword.selectText.5=Permessi
addPassword.selectText.6=Previeni assemblaggio del documento
addPassword.selectText.7=Previeni estrazione del contenuto
addPassword.selectText.8=Previeni estrazione per accessibilità
addPassword.selectText.9=Previeni compilazione dei moduli
addPassword.selectText.10=Previeni modifiche
addPassword.selectText.11=Previeni annotazioni
addPassword.selectText.12=Previeni stampa
addPassword.selectText.13=Previeni stampa in diversi formati
addPassword.submit=Crittografa
#watermark
watermark.title=Aggiungi Filigrana
watermark.header=Aggiungi filigrana
watermark.selectText.1=Seleziona PDF a cui aggiungere la filigrana:
watermark.selectText.2=Testo:
watermark.selectText.3=Dimensione carattere:
watermark.selectText.4=Rotazione (0-360):
watermark.selectText.5=spazio orizzontale (tra ogni filigrana):
watermark.selectText.6=spazio verticale (tra ogni filigrana):
watermark.selectText.7=Opacità (0% - 100%):
watermark.submit=Aggiungi Filigrana
#remove-watermark
remove-watermark.title=Rimuovi Filigrana
remove-watermark.header=Rimuovi filigrana
remove-watermark.selectText.1=Seleziona PDF da cui rimuovere la filigrana:
remove-watermark.selectText.2=Testo:
remove-watermark.submit=Rimuovi Filigrana
#Change permissions
permissions.title=Cambia Permessi
permissions.header=Cambia permessi
permissions.warning=Attenzione: per avere questi permessi non modificabili è raccomandabile impostarli attraverso una password
permissions.selectText.1=Seleziona PDF a cui cambiare permessi
permissions.selectText.2=Permessi da impostare
permissions.selectText.3=Previeni assemblaggio del documento
permissions.selectText.4=Previeni estrazione del contenuto
permissions.selectText.5=Previeni estrazione per accessibilità
permissions.selectText.6=Previeni compilazione dei moduli
permissions.selectText.7=Previeni modifiche
permissions.selectText.8=Previeni annotazioni
permissions.selectText.9=Previeni stampa
permissions.selectText.10=Previeni stampa in diversi formati
permissions.submit=Cambia Permessi
#remove password
removePassword.title=Rimuovi Password
removePassword.header=Rimuovi password (de-crittografa)
removePassword.selectText.1=Seleziona PDF da decrittare
removePassword.selectText.2=Password
removePassword.submit=Rimuovi Password
changeMetadata.title=Cambia Proprietà
changeMetadata.header=Cambia Proprietà
changeMetadata.selectText.1=Imposta i dati che vuoi cambiare
changeMetadata.selectText.2=Cancella tutte le proprietà
changeMetadata.selectText.3=Visualizza proprietà custom:
changeMetadata.author=Autore:
changeMetadata.creationDate=Data di creazione (yyyy/MM/dd HH:mm:ss):
changeMetadata.creator=Creatore:
changeMetadata.keywords=Parole chiave:
changeMetadata.modDate=Data di modifica (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Produttore:
changeMetadata.subject=Oggetto:
changeMetadata.title=Titolo:
changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Altre proprietà:
changeMetadata.selectText.5=Aggiungi proprietà personalizzata:
changeMetadata.submit=Cambia Proprietà
xlsToPdf.title=Da Excel a PDF
xlsToPdf.header=Da Excel a PDF
xlsToPdf.selectText.1=Seleziona un foglio XLS o XLSX da convertire
xlsToPdf.convert=Converti
pdfToPDFA.title=Da PDF a PDF/A
pdfToPDFA.header=Da PDF a PDF/A
pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A.
pdfToPDFA.submit=Converti
PDFToWord.title=Da PDF a Word
PDFToWord.header=Da PDF a Word
PDFToWord.selectText.1=Formato file di output
PDFToWord.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToWord.submit=Converti
PDFToPresentation.title=Da PDF a presentazione
PDFToPresentation.header=Da PDF a presentazione
PDFToPresentation.selectText.1=Formato file di output
PDFToPresentation.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToPresentation.submit=Converti
PDFToText.title=Da PDF a testo/RTF
PDFToText.header=Da PDF a testo/RTF
PDFToText.selectText.1=Formato file di output
PDFToText.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToText.submit=Converti
PDFToHTML.title=Da PDF a HTML
PDFToHTML.header=Da PDF a HTML
PDFToHTML.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToHTML.submit=Converti
PDFToXML.title=Da PDF a XML
PDFToXML.header=Da PDF a XML
PDFToXML.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToXML.submit=Converti

View File

@@ -0,0 +1,431 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr
pdfPrompt=选择PDF
multiPdfPrompt=选择多个PDF2个或更多
multiPdfDropPrompt=选择或拖拽所需的PDF
imgPrompt=选择图像
genericSubmit=提交
processTimeWarning=警告:此过程可能需要多达一分钟,具体时间取决于文件大小
pageOrderPrompt=页面顺序(输入逗号分隔的页码列表):
goToPage=
true=True
false=False
unknown=未知
save=保存
close=关闭
filesSelected=\u9009\u62E9\u7684\u6587\u4EF6
noFavourites=\u6CA1\u6709\u6DFB\u52A0\u6536\u85CF\u5939
bored=\u65E0\u804A\u7B49\u5F85\uFF1F
#############
# HOME-PAGE #
#############
home.desc=您的本地托管一站式服务满足您的所有PDF需求。
navbar.convert=转换
navbar.security=安全
navbar.other=其他
navbar.darkmode=暗模式
navbar.pageOps=页面操作
home.multiTool.title=PDF多功能工具
home.multiTool.desc=合并、旋转、重新排列和删除PDF页面
home.merge.title=合并
home.merge.desc=轻松合并多个PDF为一个。
home.split.title=拆分
home.split.desc=将 PDF 拆分为多个文档。
home.rotate.title=旋转
home.rotate.desc=旋转PDF。
home.imageToPdf.title=转换图像到PDF
home.imageToPdf.desc=转换图像PNG, JPEG, GIF到 PDF。
home.pdfToImage.title=转换PDF到图像
home.pdfToImage.desc=转换PDF到图像PNG, JPEG, GIF
home.pdfOrganiser.title=整理
home.pdfOrganiser.desc=按任何顺序删除/重新排列页面。
home.addImage.title=在PDF中添加图片
home.addImage.desc=将图像添加到PDF的设定位置上
home.watermark.title=添加水印
home.watermark.desc=在PDF中添加一个自定义的水印。
home.remove-watermark.title=去除水印
home.remove-watermark.desc=从你的PDF文档中去除水印。
home.permissions.title=更改权限
home.permissions.desc=改变你的PDF文档的权限。
home.removePages.title=删除
home.removePages.desc=从你的PDF文档中删除不需要的页面。
home.addPassword.title=添加密码
home.addPassword.desc=用密码来加密你的PDF文档。
home.removePassword.title=删除密码
home.removePassword.desc=从你的PDF文档中移除密码保护。
home.compressPdfs.title=压缩
home.compressPdfs.desc=压缩PDF文件以减少其文件大小。
home.changeMetadata.title=更改元数据
home.changeMetadata.desc=更改/删除/添加PDF文档的元数据。
home.fileToPDF.title=将文件转换为PDF文件
home.fileToPDF.desc=将几乎所有文件转换为PDFDOCX、PNG、XLS、PPT、TXT等
home.ocr.title=运行OCR/清理扫描
home.ocr.desc=清理和检测PDF中的文本图像并将其重新添加为文本。
home.extractImages.title=提取图像
home.extractImages.desc=从PDF中提取所有的图像并将其保存到压缩包中。
home.pdfToPDFA.title=PDF To PDF/A
home.pdfToPDFA.desc=将PDF转换为PDF/A以便长期保存
home.PDFToWord.title=PDF to Word
home.PDFToWord.desc=将PDF转换为Word格式DOC、DOCX和ODT
home.PDFToPresentation.title=PDF To Presentation
home.PDFToPresentation.desc=将PDF转换成演示文稿格式PPT、PPTX和ODP
home.PDFToText.title=PDF To Text/RTF
home.PDFToText.desc=将PDF转换为文本或RTF格式
home.PDFToHTML.title=PDF To HTML
home.PDFToHTML.desc=将PDF转换为HTML格式
home.PDFToXML.title=PDF To XML
home.PDFToXML.desc=将PDF转换为XML格式
home.ScannerImageSplit.title=检测/分割扫描的照片
home.ScannerImageSplit.desc=从一张照片/PDF中分割出多张照片
home.sign.title=\u6807\u5FD7
home.sign.desc=\u901A\u8FC7\u7ED8\u56FE\u3001\u6587\u672C\u6216\u56FE\u50CF\u5411 PDF \u6DFB\u52A0\u7B7E\u540D
home.flatten.title=\u5C55\u5E73
home.flatten.desc=\u4ECE PDF \u4E2D\u5220\u9664\u6240\u6709\u4EA4\u4E92\u5143\u7D20\u548C\u8868\u5355
home.repair.title=\u4FEE\u590D
home.repair.desc=\u5C1D\u8BD5\u4FEE\u590D\u635F\u574F/\u635F\u574F\u7684 PDF
home.removeBlanks.title=\u5220\u9664\u7A7A\u767D\u9875
home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7A7A\u767D\u9875
home.compare.title=\u6BD4\u8F83
home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02
downloadPdf=\u4E0B\u8F7DPDF
text=\u6587\u672C
font=\u5B57\u4F53
removeBlanks.title=\u5220\u9664\u7A7A\u767D
removeBlanks.header=\u5220\u9664\u7A7A\u767D\u9875
removeBlanks.threshold=\u9608\u503C\uFF1A
removeBlanks.thresholdDesc=\u786E\u5B9A\u767D\u8272\u50CF\u7D20\u5FC5\u987B\u6709\u591A\u767D\u7684\u9608\u503C
removeBlanks.whitePercent=\u767D\u8272\u767E\u5206\u6BD4\uFF08%\uFF09\uFF1A
removeBlanks.whitePercentDesc=\u5FC5\u987B\u4E3A\u767D\u8272\u624D\u80FD\u5220\u9664\u7684\u9875\u9762\u767E\u5206\u6BD4
removeBlanks.submit=\u5220\u9664\u7A7A\u767D
compare.title=\u6BD4\u8F83
compare.header=\u6BD4\u8F83 PDF
compare.document.1=\u6587\u6863 1
compare.document.2=\u6587\u6863 2
compare.submit=\u6BD4\u8F83
sign.title=\u7B7E\u540D
sign.header=\u7B7E\u7F72 PDF
sign.upload=\u4E0A\u4F20\u56FE\u7247
sign.draw=\u7ED8\u5236\u7B7E\u540D
sign.text=\u6587\u672C\u8F93\u5165
sign.clear=\u6E05\u9664
sign.add=\u6DFB\u52A0
repair.title=\u4FEE\u590D
repair.header=\u4FEE\u590D PDF
repair.submit=\u4FEE\u590D
flatten.title=\u5C55\u5E73
flatten.header=\u5C55\u5E73 PDF
flatten.submit=\u5C55\u5E73
ScannerImageSplit.selectText.1=角度阈值:
ScannerImageSplit.selectText.2=设置图像被旋转所需的最小绝对角度默认10
ScannerImageSplit.selectText.3=公差:
ScannerImageSplit.selectText.4=确定估计背景颜色周围的颜色变化范围默认值30
ScannerImageSplit.selectText.5=最小面积:
ScannerImageSplit.selectText.6=设置照片的最小面积阈值默认10000
ScannerImageSplit.selectText.7=最小轮廓面积:
ScannerImageSplit.selectText.8=设置照片的最小轮廓面积阈值。
ScannerImageSplit.selectText.9=边框尺寸:
ScannerImageSplit.selectText.10=设置添加和删除的边框大小以防止输出中出现白边默认值1
navbar.settings=设置
settings.title=设置
settings.update=可更新
settings.appVersion=应用程序版本:
settings.downloadOption.title=选择下载选项(单个文件非压缩文件):
settings.downloadOption.1=在同一窗口打开
settings.downloadOption.2=在新窗口中打开
settings.downloadOption.3=下载文件
settings.zipThreshold=当下载的文件数量超过限制时,将文件压缩。
#OCR
ocr.title=OCR/扫描清理
ocr.header=清理扫描件/OCR光学字符识别
ocr.selectText.1=选择要在PDF中检测的语言列出的语言是目前检测到的
ocr.selectText.2=生成包含OCR文本的文本文件与OCR编辑的PDF一起。
ocr.selectText.3=通过将页面旋转回原位来纠正偏斜的扫描角度
ocr.selectText.4=清理页面降低OCR在噪点中识别到文本的可能。(没有输出变化)
ocr.selectText.5=清洁页面降低OCR在噪点中识别到文本的可能保持输出的清洁。
ocr.selectText.6=忽略有交互式文本的页面只对有图像的页面进行OCR。
ocr.selectText.7=强制OCR将OCR每个页面删除所有的原始文本元素。
ocr.selectText.8=Normal (如果PDF包含文本将出现错误)
ocr.selectText.9=额外设置
ocr.selectText.10=OCR模式
ocr.selectText.11=OCR后移除图像移除所有图像只有在转换步骤中才有用
ocr.selectText.12=渲染类型(高级)
ocr.help=请阅读此文档,了解如何将其用于其他语言和/或不在docker中使用。
ocr.credit=此服务使用OCRmyPDF和Tesseract进行OCR。
ocr.submit=用OCR处理PDF
extractImages.title=提取图像
extractImages.header=提取图像
extractImages.selectText=选择图像格式,将提取的图像转换为
extractImages.submit=提取
#File to PDF
fileToPDF.title=文件转换为PDF
fileToPDF.header=将任何文件转换为PDF。
fileToPDF.credit=本服务使用LibreOffice和Unoconv进行文件转换。
fileToPDF.supportedFileTypes=支持的文件类型应该包括以下几种但是对于支持的格式的完整更新列表请参考LibreOffice文档。
fileToPDF.submit=转换为 PDF
#compress
compress.title=压缩
compress.header=压缩PDF
compress.credit=此服务使用OCRmyPDF进行PDF压缩/优化。
compress.selectText.1=优化水平:
compress.selectText.2=0 (无优化)
compress.selectText.3=1 (默认,无损优化)
compress.selectText.4=2 (有损优化)
compress.selectText.5=3 (有损优化,更激进)
compress.selectText.6=启用快速网络视图线性化PDF
compress.selectText.7=启用有损的JBIG2编码
compress.submit=压缩
#Add image
addImage.title=添加图像
addImage.header=添加图片到PDF (Work in progress)
addImage.everyPage=每一页?
addImage.submit=添加图片
#merge
merge.title=合并
merge.header=合并多个PDF2个以上
merge.submit=合并
#pdfOrganiser
pdfOrganiser.title=页面排序
pdfOrganiser.header=PDF页面排序
pdfOrganiser.submit=重新排列页面
#multiTool
multiTool.title=PDF多功能工具
multiTool.header=PDF多功能工具
#pageRemover
pageRemover.title=删除页面
pageRemover.header=PDF页面移除器
pageRemover.pagesToDelete=要删除的页面(输入一个用逗号分隔的页码列表):
pageRemover.submit=删除页面
#rotate
rotate.title=旋转PDF
rotate.header=旋转PDF
rotate.selectAngle=选择旋转角度以90度的倍数
rotate.submit=旋转
#merge
split.title=拆分PDF
split.header=拆分PDF
split.desc.1=选择希望进行分割的页数
split.desc.2=如选择1,3,7-8将把一个10页的文件分割成6个独立的PDF
split.desc.3=Document #1: Page 1
split.desc.4=Document #2: Page 2 and 3
split.desc.5=Document #3: Page 4, 5 and 6
split.desc.6=Document #4: Page 7
split.desc.7=Document #5: Page 8
split.desc.8=Document #6: Page 9 and 10
split.splitPages=输入要分割的页面:
split.submit=拆分
#merge
imageToPDF.title=图片转PDF
imageToPDF.header=图像转为PDF
imageToPDF.submit=转换
imageToPDF.selectText.1=拉伸至适合的尺寸
imageToPDF.selectText.2=自动旋转PDF
imageToPDF.selectText.3=多文件逻辑(仅在处理多个图像时启用)
imageToPDF.selectText.4=合并成一个PDF文件
imageToPDF.selectText.5=转换为独立的PDF文件
#pdfToImage
pdfToImage.title=PDF to Image
pdfToImage.header=PDF转图片
pdfToImage.selectText=图像格式
pdfToImage.singleOrMultiple=图像结果类型
pdfToImage.single=单张图片
pdfToImage.multi=多张图片
pdfToImage.colorType=颜色类型
pdfToImage.color=颜色
pdfToImage.grey=灰度
pdfToImage.blackwhite=黑白(可能会丢失数据!)。
pdfToImage.submit=转换
#addPassword
addPassword.title=添加密码
addPassword.header=添加密码(加密)。
addPassword.selectText.1=选择要加密的PDF。
addPassword.selectText.2=密码
addPassword.selectText.3=加密密钥长度
addPassword.selectText.4=值越高越强,但值越低兼容性越好。
addPassword.selectText.5=要设置的权限
addPassword.selectText.6=防止文件的拼接。
addPassword.selectText.7=防止内容提取
addPassword.selectText.8=防止为可访问性提取内容
addPassword.selectText.9=防止填写表格
addPassword.selectText.10=防止修改
addPassword.selectText.11=防止修改注释
addPassword.selectText.12=防止打印
addPassword.selectText.13=防止打印不同的格式
addPassword.submit=加密
#watermark
watermark.title=添加水印
watermark.header=添加水印
watermark.selectText.1=选择要添加水印的PDF
watermark.selectText.2=水印文本:
watermark.selectText.3=字体大小:
watermark.selectText.4=旋转0-360
watermark.selectText.5=widthSpacer水平方向上每个水印之间的空间
watermark.selectText.6=heightSpacer每个水印之间的垂直空间
watermark.selectText.7=透明度0% - 100%
watermark.submit=添加水印
#remove-watermark
remove-watermark.title=去除水印
remove-watermark.header=去除水印
remove-watermark.selectText.1=选择要去除水印的PDF
remove-watermark.selectText.2=水印文本:
remove-watermark.submit=移除水印
#Change permissions
permissions.title=更改权限
permissions.header=改变权限
permissions.warning=警告,为了使这些权限不能被改变,建议通过添加密码页面设置密码。
permissions.selectText.1=选择PDF来改变权限
permissions.selectText.2=要设置的权限
permissions.selectText.3=防止文件的拼接
permissions.selectText.4=防止内容提取
permissions.selectText.5=防止提取内容的可访问性
permissions.selectText.6=防止填写表格
permissions.selectText.7=防止修改
permissions.selectText.8=防止修改注释
permissions.selectText.9=防止打印
permissions.selectText.10=防止打印不同的格式
permissions.submit=改变
#remove password
removePassword.title=删除密码
removePassword.header=移除密码(解密)。
removePassword.selectText.1=选择要解密的PDF
removePassword.selectText.2=密码
removePassword.submit=删除
changeMetadata.title=更改元数据
changeMetadata.header=更改元数据
changeMetadata.selectText.1=请编辑你想要改变的变量。
changeMetadata.selectText.2=删除所有元数据
changeMetadata.selectText.3=显示自定义元数据:
changeMetadata.author=作者:
changeMetadata.creationDate=创建日期yyyy/MM/dd HH:mm:ss
changeMetadata.creator=创建者:
changeMetadata.keywords=关键词:
changeMetadata.modDate=修改日期yyyy/MM/dd HH:mm:ss
changeMetadata.producer=生产者:
changeMetadata.subject=主题:
changeMetadata.title=标题:
changeMetadata.trapped=被困:
changeMetadata.selectText.4=其他元数据:
changeMetadata.selectText.5=添加自定义元数据条目
changeMetadata.submit=更改
xlsToPdf.title=Excel转PDF
xlsToPdf.header=Excel转PDF
xlsToPdf.selectText.1=选择要转换的XLS或XLSX Excel表格
xlsToPdf.convert=转换
pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF to PDF/A
pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换
pdfToPDFA.submit=转换
PDFToWord.title=PDF to Word
PDFToWord.header=将PDF转换成Word
PDFToWord.selectText.1=输出文件格式
PDFToWord.credit=此服务使用LibreOffice进行文件转换。
PDFToWord.submit=转换
PDFToPresentation.title=PDF To Presentation
PDFToPresentation.header=将PDF转为演示文稿
PDFToPresentation.selectText.1=输出文件格式
PDFToPresentation.credit=该服务使用LibreOffice进行文件转换。
PDFToPresentation.submit=转换
PDFToText.title=PDF To Text/RTF
PDFToText.header=将PDF转换成文本/RTF
PDFToText.selectText.1=输出文件格式
PDFToText.credit=该服务使用LibreOffice进行文件转换。
PDFToText.submit=转换
PDFToHTML.title=PDF To HTML
PDFToHTML.header=将PDF转换成HTML
PDFToHTML.credit=此服务使用LibreOffice进行文件转换。
PDFToHTML.submit=转换
PDFToXML.title=PDF To XML
PDFToXML.header=将PDF转换为XML
PDFToXML.credit=此服务使用LibreOffice进行文件转换。
PDFToXML.submit=转换

View File

@@ -1,27 +1,39 @@
/* Dark Mode Styles */
body {
background-color: #333 !important;
color: #fff !important;
--body-background-color: 51, 51, 51;
--base-font-color: 255, 255, 255;
background-color: rgb(var(--body-background-color)) !important;
color: rgb(var(--base-font-color)) !important;
}
.dark-card {
background-color: #333 !important;
color: white !important;
background-color: rgb(var(--body-background-color)) !important;
color: rgb(var(--base-font-color)) !important;
}
.jumbotron {
background-color: #222; /* or any other dark color */
color: #fff !important; /* or any other light color */
color: rgb(var(--base-font-color)) !important; /* or any other light color */
}
.list-group {
background-color: #222 !important;
color: fff !important;
color: rgb(var(--base-font-color)) !important;
}
.list-group-item {
background-color: #222 !important;
color: fff !important;
color: rgb(var(--base-font-color)) !important;
}
#support-section {
background-color: #444 !important;
}
#pages-container-wrapper {
--background-color: rgba(255, 255, 255, 0.046) !important;
--scroll-bar-color: #4c4c4c !important;
--scroll-bar-thumb: #d3d3d3 !important;
--scroll-bar-thumb-hover: rgb(var(--base-font-color)) !important;
}
.favorite-icon img {
filter: brightness(0) invert(1) !important;
}

View File

@@ -0,0 +1,78 @@
#drag-container {
position: fixed;
display:flex;
inset: 0;
pointer-events: none;
z-index: 10000;
visibility: hidden;
}
#drag-container:not(:empty) {
visibility: visible;
}
#drag-container .dragged-img {
position: fixed;
max-width: 200px;
max-height: 200px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.58);
transform-origin: top left;
}
.drag-manager_dragging {
width: 0px;
visibility: hidden;
}
.drag-manager_draghover {
width: 375px !important;
}
.drag-manager_draghover .insert-file-button-container {
display: none !important;
}
.drag-manager_draghover .button-container {
visibility: hidden !important;
}
html[lang-direction=ltr] .drag-manager_draghover img {
left: calc(50% + 62.5px) !important;
}
html[lang-direction=rtl] .drag-manager_draghover img {
left: 125px
}
.drag-manager_dragging-container .hide-on-drag {
display: none !important;
}
.drag-manager_endpoint {
width: 80px;
height: 100%;
background-color: #FFFFFF10;
transition: width 0.1s;
animation: end-drop-expand .3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.drag-manager_endpoint svg {
width: 50px;
height: 50px;
}
.drag-manager_endpoint.drag-manager_draghover {
width: 150px !important;
}
@keyframes end-drop-expand {
from {
width: 0;
}
to {
width: 80px;
}
}

View File

@@ -16,8 +16,53 @@
html[lang-direction=ltr] * {
direction: ltr;
}
html[lang-direction=rtl] * {
direction: rtl;
text-align: right;
}
.ignore-rtl {
direction: ltr !important;
text-align: left !important;
}
.align-top {
position: absolute;
top: 0;
}
.align-center-right {
position: absolute;
right: 0;
top: 50%;
}
.align-center-left {
position: absolute;
left: 0;
top: 50%;
}
.align-bottom {
position: absolute;
bottom: 0;
}
.btn-group > label:first-of-type {
border-top-left-radius: 0.25rem !important;
border-bottom-left-radius: 0.25rem !important;
}
html[lang-direction="rtl"] input.form-check-input {
position: relative;
margin-left: 0px;
}
html[lang-direction="rtl"] label.form-check-label {
display: inline;
}
.margin-auto-parent {
width: 100%;
display: flex;
}
.margin-center {
margin: 0 auto;
}

View File

@@ -0,0 +1,40 @@
#image-highlighter {
position: fixed;
display:flex;
inset: 0;
z-index: 10000;
background-color: rgba(0, 0, 0, 0);
visibility: hidden;
align-items: center;
justify-content: center;
transition: visbility 0.1s linear, background-color 0.1s linear;
}
#image-highlighter > * {
max-width: 80vw;
max-height: 80vh;
animation: image-highlight .1s linear;
transition: transform .1s linear, opacity .1s linear;
}
#image-highlighter > *.remove {
transform: scale(0.8) !important;
opacity: 0 !important;
}
#image-highlighter:not(:empty) {
background-color: rgba(0, 0, 0, 0.37);
visibility: visible;
}
@keyframes image-highlight {
from {
transform: scale(0.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}

View File

@@ -0,0 +1,5 @@
/* Dark Mode Styles */
body {
--body-background-color: 255, 255, 255;
--base-font-color: 33, 37, 41;
}

View File

@@ -0,0 +1,87 @@
.pdf-actions_button-container {
z-index: 2;
display:flex;
opacity: 0;
transition: opacity 0.1s linear;
}
.pdf-actions_container:hover .pdf-actions_button-container {
opacity: 1;
}
.pdf-actions_button-container > * {
padding: 0.25rem 0.5rem;
margin: 3px;
display: block;
}
.pdf-actions_container svg {
width: 16px;
height: 16px;
}
.pdf-actions_container:nth-child(1) .pdf-actions_move-left-button {
display: none;
}
.pdf-actions_container:last-child .pdf-actions_move-right-button {
display: none;
}
/* "insert pdf" buttons that appear on the right when hover */
.pdf-actions_insert-file-button-container {
translate: 0 -50%;
width: 80px;
height: 100%;
z-index: 1;
opacity: 0;
transition: opacity 0.2s;
}
.pdf-actions_insert-file-button-container.left {
left: -20px;
}
.pdf-actions_insert-file-button-container.right {
right: -20px;
}
html[lang-direction=ltr] .pdf-actions_insert-file-button-container.right {
display:none;
}
html[lang-direction=rtl] .pdf-actions_insert-file-button-container.left {
display:none;
}
.pdf-actions_insert-file-button-container.left .pdf-actions_insert-file-button {
left: 0;
translate: 0 -50%;
}
.pdf-actions_insert-file-button-container.right .pdf-actions_insert-file-button {
right: 0;
translate: 0 -50%;
}
html[lang-direction=ltr] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.right {
display: block;
}
html[lang-direction=rtl] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.left {
display: block;
}
.pdf-actions_insert-file-button-container:hover {
opacity: 1;
transition: opacity 0.05s;
}
.pdf-actions_insert-file-button {
position: absolute;
top: 50%;
right: 50%;
translate: 50% -50%;
aspect-ratio: 1;
border-radius: 100px;
}

View File

@@ -0,0 +1,37 @@
/* Rainbow Mode Styles */
body {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%);
color: #fff !important;
--body-background-color: 255, 255, 255;
--base-font-color: 33, 37, 41;
}
.dark-card {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
color: white !important;
}
.jumbotron {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%);
color: #fff !important;
}
.list-group {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
color: fff !important;
}
.list-group-item {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
color: fff !important;
}
#support-section {
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
}
#pages-container-wrapper {
--background-color: rgba(255, 255, 255, 0.046) !important;
--scroll-bar-color: #4c4c4c !important;
--scroll-bar-thumb: #d3d3d3 !important;
--scroll-bar-thumb-hover: #ffffff !important;
}

View File

@@ -0,0 +1,26 @@
.tab-group {
}
.tab-container {
display: none;
}
.tab-container.active {
display: block;
border: 1px solid rgba(var(--base-font-color), 0.25);
padding: 15px;
}
.tab-buttons > button {
margin-bottom: -1px;
background: 0 0;
border: 1px solid transparent;
color: rgb(var(--base-font-color));
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
}
.tab-buttons > button.active {
background-color: rgb(var(--body-background-color));
border-color: rgba(var(--base-font-color), 0.25) rgba(var(--base-font-color), 0.25) rgb(var(--body-background-color));
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_37)">
<path d="M13 0H6C5.46957 0 4.96086 0.210714 4.58579 0.585786C4.21071 0.960859 4 1.46957 4 2C3.46957 2 2.96086 2.21071 2.58579 2.58579C2.21071 2.96086 2 3.46957 2 4V14C2 14.5304 2.21071 15.0391 2.58579 15.4142C2.96086 15.7893 3.46957 16 4 16H11C11.5304 16 12.0391 15.7893 12.4142 15.4142C12.7893 15.0391 13 14.5304 13 14C13.5304 14 14.0391 13.7893 14.4142 13.4142C14.7893 13.0391 15 12.5304 15 12V2C15 1.46957 14.7893 0.960859 14.4142 0.585786C14.0391 0.210714 13.5304 0 13 0ZM13 13V4C13 3.46957 12.7893 2.96086 12.4142 2.58579C12.0391 2.21071 11.5304 2 11 2H5C5 1.73478 5.10536 1.48043 5.29289 1.29289C5.48043 1.10536 5.73478 1 6 1H13C13.2652 1 13.5196 1.10536 13.7071 1.29289C13.8946 1.48043 14 1.73478 14 2V12C14 12.2652 13.8946 12.5196 13.7071 12.7071C13.5196 12.8946 13.2652 13 13 13ZM3 4C3 3.73478 3.10536 3.48043 3.29289 3.29289C3.48043 3.10536 3.73478 3 4 3H11C11.2652 3 11.5196 3.10536 11.7071 3.29289C11.8946 3.48043 12 3.73478 12 4V14C12 14.2652 11.8946 14.5196 11.7071 14.7071C11.5196 14.8946 11.2652 15 11 15H4C3.73478 15 3.48043 14.8946 3.29289 14.7071C3.10536 14.5196 3 14.2652 3 14V4Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1_37">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 11.5a.5.5 0 0 0 .5.5h11.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 11H1.5a.5.5 0 0 0-.5.5zm14-7a.5.5 0 0 1-.5.5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H14.5a.5.5 0 0 1 .5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file" viewBox="0 0 16 16">
<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 284 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-card-list" viewBox="0 0 16 16">
<path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
<path d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-1-5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zM4 8a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm0 2.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 638 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard-data" viewBox="0 0 16 16">
<path d="M4 11a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1zm6-4a1 1 0 1 1 2 0v5a1 1 0 1 1-2 0V7zM7 9a1 1 0 0 1 2 0v3a1 1 0 1 1-2 0V9z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-droplet" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M7.21.8C7.69.295 8 0 8 0c.109.363.234.708.371 1.038.812 1.946 2.073 3.35 3.197 4.6C12.878 7.096 14 8.345 14 10a6 6 0 0 1-12 0C2 6.668 5.58 2.517 7.21.8zm.413 1.021A31.25 31.25 0 0 0 5.794 3.99c-.726.95-1.436 2.008-1.96 3.07C3.304 8.133 3 9.138 3 10a5 5 0 0 0 10 0c0-1.201-.796-2.157-2.181-3.7l-.03-.032C9.75 5.11 8.5 3.72 7.623 1.82z"/>
<path fill-rule="evenodd" d="M4.553 7.776c.82-1.641 1.717-2.753 2.093-3.13l.708.708c-.29.29-1.128 1.311-1.907 2.87l-.894-.448z"/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-pdf" viewBox="0 0 16 16">
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
<path d="M4.603 14.087a.81.81 0 0 1-.438-.42c-.195-.388-.13-.776.08-1.102.198-.307.526-.568.897-.787a7.68 7.68 0 0 1 1.482-.645 19.697 19.697 0 0 0 1.062-2.227 7.269 7.269 0 0 1-.43-1.295c-.086-.4-.119-.796-.046-1.136.075-.354.274-.672.65-.823.192-.077.4-.12.602-.077a.7.7 0 0 1 .477.365c.088.164.12.356.127.538.007.188-.012.396-.047.614-.084.51-.27 1.134-.52 1.794a10.954 10.954 0 0 0 .98 1.686 5.753 5.753 0 0 1 1.334.05c.364.066.734.195.96.465.12.144.193.32.2.518.007.192-.047.382-.138.563a1.04 1.04 0 0 1-.354.416.856.856 0 0 1-.51.138c-.331-.014-.654-.196-.933-.417a5.712 5.712 0 0 1-.911-.95 11.651 11.651 0 0 0-1.997.406 11.307 11.307 0 0 1-1.02 1.51c-.292.35-.609.656-.927.787a.793.793 0 0 1-.58.029zm1.379-1.901c-.166.076-.32.156-.459.238-.328.194-.541.383-.647.547-.094.145-.096.25-.04.361.01.022.02.036.026.044a.266.266 0 0 0 .035-.012c.137-.056.355-.235.635-.572a8.18 8.18 0 0 0 .45-.606zm1.64-1.33a12.71 12.71 0 0 1 1.01-.193 11.744 11.744 0 0 1-.51-.858 20.801 20.801 0 0 1-.5 1.05zm2.446.45c.15.163.296.3.435.41.24.19.407.253.498.256a.107.107 0 0 0 .07-.015.307.307 0 0 0 .094-.125.436.436 0 0 0 .059-.2.095.095 0 0 0-.026-.063c-.052-.062-.2-.152-.518-.209a3.876 3.876 0 0 0-.612-.053zM8.078 7.8a6.7 6.7 0 0 0 .2-.828c.031-.188.043-.343.038-.465a.613.613 0 0 0-.032-.198.517.517 0 0 0-.145.04c-.087.035-.158.106-.196.283-.04.192-.03.469.046.822.024.111.054.227.09.346z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-ppt" viewBox="0 0 16 16">
<path d="M7 5.5a1 1 0 0 0-1 1V13a.5.5 0 0 0 1 0v-2h1.188a2.75 2.75 0 0 0 0-5.5H7zM8.188 10H7V6.5h1.188a1.75 1.75 0 1 1 0 3.5z"/>
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-richtext" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
<path d="M4.5 12.5A.5.5 0 0 1 5 12h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm0-2A.5.5 0 0 1 5 10h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm1.639-3.708 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047l1.888.974V8.5a.5.5 0 0 1-.5.5H5a.5.5 0 0 1-.5-.5V8s1.54-1.274 1.639-1.208zM6.25 6a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 623 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-word" viewBox="0 0 16 16">
<path d="M5.485 6.879a.5.5 0 1 0-.97.242l1.5 6a.5.5 0 0 0 .967.01L8 9.402l1.018 3.73a.5.5 0 0 0 .967-.01l1.5-6a.5.5 0 0 0-.97-.242l-1.036 4.144-.997-3.655a.5.5 0 0 0-.964 0l-.997 3.655L5.485 6.88z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>

After

Width:  |  Height:  |  Size: 510 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-x" viewBox="0 0 16 16">
<path d="M6.854 7.146a.5.5 0 1 0-.708.708L7.293 9l-1.147 1.146a.5.5 0 0 0 .708.708L8 9.707l1.146 1.147a.5.5 0 0 0 .708-.708L8.707 9l1.147-1.146a.5.5 0 0 0-.708-.708L8 8.293 6.854 7.146z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-pdf" viewBox="0 0 16 16">
<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
<path d="M4.603 12.087a.81.81 0 0 1-.438-.42c-.195-.388-.13-.776.08-1.102.198-.307.526-.568.897-.787a7.68 7.68 0 0 1 1.482-.645 19.701 19.701 0 0 0 1.062-2.227 7.269 7.269 0 0 1-.43-1.295c-.086-.4-.119-.796-.046-1.136.075-.354.274-.672.65-.823.192-.077.4-.12.602-.077a.7.7 0 0 1 .477.365c.088.164.12.356.127.538.007.187-.012.395-.047.614-.084.51-.27 1.134-.52 1.794a10.954 10.954 0 0 0 .98 1.686 5.753 5.753 0 0 1 1.334.05c.364.065.734.195.96.465.12.144.193.32.2.518.007.192-.047.382-.138.563a1.04 1.04 0 0 1-.354.416.856.856 0 0 1-.51.138c-.331-.014-.654-.196-.933-.417a5.716 5.716 0 0 1-.911-.95 11.642 11.642 0 0 0-1.997.406 11.311 11.311 0 0 1-1.021 1.51c-.29.35-.608.655-.926.787a.793.793 0 0 1-.58.029zm1.379-1.901c-.166.076-.32.156-.459.238-.328.194-.541.383-.647.547-.094.145-.096.25-.04.361.01.022.02.036.026.044a.27.27 0 0 0 .035-.012c.137-.056.355-.235.635-.572a8.18 8.18 0 0 0 .45-.606zm1.64-1.33a12.647 12.647 0 0 1 1.01-.193 11.666 11.666 0 0 1-.51-.858 20.741 20.741 0 0 1-.5 1.05zm2.446.45c.15.162.296.3.435.41.24.19.407.253.498.256a.107.107 0 0 0 .07-.015.307.307 0 0 0 .094-.125.436.436 0 0 0 .059-.2.095.095 0 0 0-.026-.063c-.052-.062-.2-.152-.518-.209a3.881 3.881 0 0 0-.612-.053zM8.078 5.8a6.7 6.7 0 0 0 .2-.828c.031-.188.043-.343.038-.465a.613.613 0 0 0-.032-.198.517.517 0 0 0-.145.04c-.087.035-.158.106-.196.283-.04.192-.03.469.046.822.024.111.054.227.09.346z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-zip" viewBox="0 0 16 16">
<path d="M6.5 7.5a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v.938l.4 1.599a1 1 0 0 1-.416 1.074l-.93.62a1 1 0 0 1-1.109 0l-.93-.62a1 1 0 0 1-.415-1.074l.4-1.599V7.5zm2 0h-1v.938a1 1 0 0 1-.03.243l-.4 1.598.93.62.93-.62-.4-1.598a1 1 0 0 1-.03-.243V7.5z"/>
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm5.5-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H9v1H8v1h1v1H8v1h1v1H7.5V5h-1V4h1V3h-1V2h1V1z"/>
</svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-filetype-html" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 4.5V11h-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5Zm-9.736 7.35v3.999h-.791v-1.714H1.79v1.714H1V11.85h.791v1.626h1.682V11.85h.79Zm2.251.662v3.337h-.794v-3.337H4.588v-.662h3.064v.662H6.515Zm2.176 3.337v-2.66h.038l.952 2.159h.516l.946-2.16h.038v2.661h.715V11.85h-.8l-1.14 2.596H9.93L8.79 11.85h-.805v3.999h.706Zm4.71-.674h1.696v.674H12.61V11.85h.79v3.325Z"/>
</svg>

After

Width:  |  Height:  |  Size: 565 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-filetype-txt" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-2v-1h2a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5ZM1.928 15.849v-3.337h1.136v-.662H0v.662h1.134v3.337h.794Zm4.689-3.999h-.894L4.9 13.289h-.035l-.832-1.439h-.932l1.228 1.983-1.24 2.016h.862l.853-1.415h.035l.85 1.415h.907l-1.253-1.992 1.274-2.007Zm1.93.662v3.337h-.794v-3.337H6.619v-.662h3.064v.662H8.546Z"/>
</svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-filetype-xml" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2v-1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5ZM3.527 11.85h-.893l-.823 1.439h-.036L.943 11.85H.012l1.227 1.983L0 15.85h.861l.853-1.415h.035l.85 1.415h.908l-1.254-1.992 1.274-2.007Zm.954 3.999v-2.66h.038l.952 2.159h.516l.946-2.16h.038v2.661h.715V11.85h-.8l-1.14 2.596h-.025L4.58 11.85h-.806v3.999h.706Zm4.71-.674h1.696v.674H8.4V11.85h.791v3.325Z"/>
</svg>

After

Width:  |  Height:  |  Size: 588 B

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-cn" viewBox="0 0 640 480">
<defs>
<path id="a" fill="#ff0" d="M-.6.8 0-1 .6.8-1-.3h2z"/>
</defs>
<path fill="#ee1c25" d="M0 0h640v480H0z"/>
<use xlink:href="#a" width="30" height="20" transform="matrix(71.9991 0 0 72 120 120)"/>
<use xlink:href="#a" width="30" height="20" transform="matrix(-12.33562 -20.5871 20.58684 -12.33577 240.3 48)"/>
<use xlink:href="#a" width="30" height="20" transform="matrix(-3.38573 -23.75998 23.75968 -3.38578 288 95.8)"/>
<use xlink:href="#a" width="30" height="20" transform="matrix(6.5991 -23.0749 23.0746 6.59919 288 168)"/>
<use xlink:href="#a" width="30" height="20" transform="matrix(14.9991 -18.73557 18.73533 14.99929 240 216)"/>
</svg>

After

Width:  |  Height:  |  Size: 795 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 640 480">
<path fill="#ffce00" d="M0 320h640v160H0z"/>
<path d="M0 0h640v160H0z"/>
<path fill="#d00" d="M0 160h640v160H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

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